diff --git a/PDQ_MinLib/FreeSansBold9pt7b.h b/PDQ_MinLib/FreeSansBold9pt7b.h new file mode 100644 index 0000000..4fda0a5 --- /dev/null +++ b/PDQ_MinLib/FreeSansBold9pt7b.h @@ -0,0 +1,225 @@ +const uint8_t FreeSansBold9pt7bBitmaps[] PROGMEM = { + 0xFF, 0xFF, 0xFE, 0x48, 0x7E, 0xEF, 0xDF, 0xBF, 0x74, 0x40, 0x19, 0x86, + 0x67, 0xFD, 0xFF, 0x33, 0x0C, 0xC3, 0x33, 0xFE, 0xFF, 0x99, 0x86, 0x61, + 0x90, 0x10, 0x1F, 0x1F, 0xDE, 0xFF, 0x3F, 0x83, 0xC0, 0xFC, 0x1F, 0x09, + 0xFC, 0xFE, 0xF7, 0xF1, 0xE0, 0x40, 0x38, 0x10, 0x7C, 0x30, 0xC6, 0x20, + 0xC6, 0x40, 0xC6, 0x40, 0x7C, 0x80, 0x39, 0x9C, 0x01, 0x3E, 0x03, 0x63, + 0x02, 0x63, 0x04, 0x63, 0x0C, 0x3E, 0x08, 0x1C, 0x0E, 0x01, 0xF8, 0x3B, + 0x83, 0xB8, 0x3F, 0x01, 0xE0, 0x3E, 0x67, 0x76, 0xE3, 0xEE, 0x1C, 0xF3, + 0xC7, 0xFE, 0x3F, 0x70, 0xFF, 0xF4, 0x18, 0x63, 0x1C, 0x73, 0x8E, 0x38, + 0xE3, 0x8E, 0x18, 0x70, 0xC3, 0x06, 0x08, 0x61, 0x83, 0x0E, 0x38, 0x71, + 0xC7, 0x1C, 0x71, 0xC6, 0x38, 0xE3, 0x18, 0x40, 0x21, 0x3E, 0x45, 0x28, + 0x38, 0x70, 0xE7, 0xFF, 0xE7, 0x0E, 0x1C, 0xFC, 0x9C, 0xFF, 0xC0, 0xFC, + 0x08, 0xC4, 0x23, 0x10, 0x84, 0x62, 0x11, 0x88, 0x00, 0x3E, 0x3F, 0x9D, + 0xDC, 0x7E, 0x3F, 0x1F, 0x8F, 0xC7, 0xE3, 0xF1, 0xDD, 0xCF, 0xE3, 0xE0, + 0x08, 0xFF, 0xF3, 0x9C, 0xE7, 0x39, 0xCE, 0x73, 0x80, 0x3E, 0x3F, 0xB8, + 0xFC, 0x70, 0x38, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x0F, 0xF7, 0xF8, + 0x3C, 0x7F, 0xE7, 0xE7, 0x07, 0x0C, 0x0E, 0x07, 0x07, 0xE7, 0xE7, 0x7E, + 0x3C, 0x0E, 0x1E, 0x1E, 0x2E, 0x2E, 0x4E, 0x4E, 0x8E, 0xFF, 0xFF, 0x0E, + 0x0E, 0x0E, 0x7F, 0x3F, 0x90, 0x18, 0x0D, 0xE7, 0xFB, 0x9E, 0x07, 0x03, + 0x81, 0xF1, 0xFF, 0xE7, 0xC0, 0x3E, 0x3F, 0x9C, 0xFC, 0x0E, 0xE7, 0xFB, + 0xDF, 0xC7, 0xE3, 0xF1, 0xDD, 0xEF, 0xE3, 0xE0, 0xFF, 0xFF, 0xC0, 0xE0, + 0xE0, 0x60, 0x70, 0x30, 0x38, 0x1C, 0x0C, 0x0E, 0x07, 0x03, 0x80, 0x3F, + 0x1F, 0xEE, 0x3F, 0x87, 0xE3, 0xCF, 0xC7, 0xFB, 0xCF, 0xE1, 0xF8, 0x7F, + 0x3D, 0xFE, 0x3F, 0x00, 0x3E, 0x3F, 0xBD, 0xDC, 0x7E, 0x3F, 0x1F, 0xDE, + 0xFF, 0x3B, 0x81, 0xF9, 0xCF, 0xE3, 0xC0, 0xFC, 0x00, 0x07, 0xE0, 0xFC, + 0x00, 0x07, 0xE5, 0xE0, 0x00, 0x83, 0xC7, 0xDF, 0x0C, 0x07, 0x80, 0xF8, + 0x1F, 0x01, 0x80, 0xFF, 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xFC, 0x00, 0x70, + 0x3F, 0x03, 0xE0, 0x38, 0x7D, 0xF1, 0xE0, 0x80, 0x00, 0x3E, 0x3F, 0xB8, + 0xFC, 0x70, 0x38, 0x1C, 0x1C, 0x1C, 0x1C, 0x0E, 0x00, 0x03, 0x81, 0xC0, + 0x03, 0xF0, 0x0F, 0xFC, 0x1E, 0x0E, 0x38, 0x02, 0x70, 0xE9, 0x63, 0x19, + 0xC2, 0x19, 0xC6, 0x11, 0xC6, 0x33, 0xC6, 0x32, 0x63, 0xFE, 0x73, 0xDC, + 0x3C, 0x00, 0x1F, 0xF8, 0x07, 0xF0, 0x07, 0x00, 0xF0, 0x0F, 0x80, 0xF8, + 0x1D, 0x81, 0x9C, 0x19, 0xC3, 0x8C, 0x3F, 0xE7, 0xFE, 0x70, 0x66, 0x07, + 0xE0, 0x70, 0xFF, 0x9F, 0xFB, 0x83, 0xF0, 0x7E, 0x0F, 0xFF, 0x3F, 0xF7, + 0x06, 0xE0, 0xFC, 0x1F, 0x83, 0xFF, 0xEF, 0xF8, 0x1F, 0x83, 0xFE, 0x78, + 0xE7, 0x07, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x07, 0x07, 0x78, + 0xF3, 0xFE, 0x1F, 0x80, 0xFF, 0x8F, 0xFC, 0xE0, 0xEE, 0x0E, 0xE0, 0x7E, + 0x07, 0xE0, 0x7E, 0x07, 0xE0, 0x7E, 0x0E, 0xE0, 0xEF, 0xFC, 0xFF, 0x80, + 0xFF, 0xFF, 0xF8, 0x1C, 0x0E, 0x07, 0xFB, 0xFD, 0xC0, 0xE0, 0x70, 0x38, + 0x1F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF8, 0x1C, 0x0E, 0x07, 0xFB, 0xFD, 0xC0, + 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x00, 0x0F, 0x87, 0xF9, 0xE3, 0xB8, 0x3E, + 0x01, 0xC0, 0x38, 0xFF, 0x1F, 0xE0, 0x6E, 0x0D, 0xE3, 0x9F, 0xD0, 0xF2, + 0xE0, 0xFC, 0x1F, 0x83, 0xF0, 0x7E, 0x0F, 0xFF, 0xFF, 0xFF, 0x07, 0xE0, + 0xFC, 0x1F, 0x83, 0xF0, 0x7E, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xE7, 0xE7, 0xE7, 0x7E, 0x3C, + 0xE0, 0xEE, 0x1C, 0xE3, 0x8E, 0x70, 0xEE, 0x0F, 0xC0, 0xFE, 0x0F, 0x70, + 0xE7, 0x0E, 0x38, 0xE1, 0xCE, 0x0E, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, + 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xFF, 0xFF, 0xF8, 0x7F, 0xE1, + 0xFF, 0x87, 0xFE, 0x1F, 0xEC, 0x7F, 0xB3, 0x7E, 0xCD, 0xFB, 0x37, 0xEC, + 0xDF, 0x9E, 0x7E, 0x79, 0xF9, 0xE7, 0xE7, 0x9C, 0xE0, 0xFE, 0x1F, 0xC3, + 0xFC, 0x7F, 0xCF, 0xD9, 0xFB, 0xBF, 0x37, 0xE7, 0xFC, 0x7F, 0x87, 0xF0, + 0xFE, 0x0E, 0x0F, 0x81, 0xFF, 0x1E, 0x3C, 0xE0, 0xEE, 0x03, 0xF0, 0x1F, + 0x80, 0xFC, 0x07, 0xE0, 0x3B, 0x83, 0x9E, 0x3C, 0x7F, 0xC0, 0xF8, 0x00, + 0xFF, 0x9F, 0xFB, 0x87, 0xF0, 0x7E, 0x0F, 0xC3, 0xFF, 0xF7, 0xFC, 0xE0, + 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x00, 0x0F, 0x81, 0xFF, 0x1E, 0x3C, 0xE0, + 0xEE, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE1, 0xBB, 0x8F, 0x9E, 0x3C, + 0x7F, 0xE0, 0xFB, 0x80, 0x08, 0xFF, 0x8F, 0xFC, 0xE0, 0xEE, 0x0E, 0xE0, + 0xEE, 0x0E, 0xFF, 0xCF, 0xFC, 0xE0, 0xEE, 0x0E, 0xE0, 0xEE, 0x0E, 0xE0, + 0xF0, 0x3F, 0x0F, 0xFB, 0xC7, 0xF0, 0x7E, 0x01, 0xFC, 0x1F, 0xF0, 0x3F, + 0x00, 0xFC, 0x1D, 0xC7, 0xBF, 0xE1, 0xF8, 0xFF, 0xFF, 0xC7, 0x03, 0x81, + 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0, 0xE0, 0xFC, + 0x1F, 0x83, 0xF0, 0x7E, 0x0F, 0xC1, 0xF8, 0x3F, 0x07, 0xE0, 0xFC, 0x1F, + 0xC7, 0xBF, 0xE1, 0xF0, 0x60, 0x67, 0x0E, 0x70, 0xE3, 0x0C, 0x30, 0xC3, + 0x9C, 0x19, 0x81, 0x98, 0x1F, 0x80, 0xF0, 0x0F, 0x00, 0xF0, 0x06, 0x00, + 0x61, 0xC3, 0xB8, 0xE1, 0x9C, 0x70, 0xCE, 0x3C, 0xE3, 0x36, 0x71, 0x9B, + 0x30, 0xED, 0x98, 0x36, 0x7C, 0x1B, 0x3C, 0x0F, 0x1E, 0x07, 0x8F, 0x01, + 0xC3, 0x80, 0xE1, 0x80, 0x70, 0xE7, 0x8E, 0x39, 0xC1, 0xF8, 0x1F, 0x80, + 0xF0, 0x07, 0x00, 0xF0, 0x1F, 0x81, 0x9C, 0x39, 0xC7, 0x0E, 0x70, 0xE0, + 0xE0, 0xFC, 0x39, 0xC7, 0x18, 0xC3, 0xB8, 0x36, 0x07, 0xC0, 0x70, 0x0E, + 0x01, 0xC0, 0x38, 0x07, 0x00, 0xE0, 0xFF, 0xFF, 0xC0, 0xE0, 0xE0, 0xF0, + 0x70, 0x70, 0x70, 0x78, 0x38, 0x38, 0x1F, 0xFF, 0xF8, 0xFF, 0xEE, 0xEE, + 0xEE, 0xEE, 0xEE, 0xEE, 0xEF, 0xF0, 0x86, 0x10, 0x86, 0x10, 0x84, 0x30, + 0x84, 0x30, 0x80, 0xFF, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x7F, 0xF0, + 0x18, 0x1C, 0x3C, 0x3E, 0x36, 0x66, 0x63, 0xC3, 0xFF, 0xC0, 0xCC, 0x3F, + 0x1F, 0xEE, 0x38, 0x0E, 0x3F, 0x9E, 0xEE, 0x3B, 0x9E, 0xFF, 0x9E, 0xE0, + 0xE0, 0x38, 0x0E, 0x03, 0xBC, 0xFF, 0xBC, 0xEE, 0x1F, 0x87, 0xE1, 0xF8, + 0x7F, 0x3B, 0xFE, 0xEF, 0x00, 0x1F, 0x3F, 0xDC, 0x7C, 0x0E, 0x07, 0x03, + 0x80, 0xE3, 0x7F, 0x8F, 0x00, 0x03, 0x81, 0xC0, 0xE7, 0x77, 0xFB, 0xBF, + 0x8F, 0xC7, 0xE3, 0xF1, 0xFD, 0xEF, 0xF3, 0xB8, 0x3E, 0x3F, 0x9C, 0xDC, + 0x3F, 0xFF, 0xFF, 0x81, 0xC3, 0x7F, 0x8F, 0x00, 0x3B, 0xDD, 0xFF, 0xB9, + 0xCE, 0x73, 0x9C, 0xE7, 0x00, 0x3B, 0xBF, 0xDD, 0xFC, 0x7E, 0x3F, 0x1F, + 0x8F, 0xEF, 0x7F, 0x9D, 0xC0, 0xFC, 0x77, 0xF1, 0xF0, 0xE0, 0x70, 0x38, + 0x1D, 0xEF, 0xFF, 0x9F, 0x8F, 0xC7, 0xE3, 0xF1, 0xF8, 0xFC, 0x7E, 0x38, + 0xFC, 0x7F, 0xFF, 0xFF, 0xFE, 0x77, 0x07, 0x77, 0x77, 0x77, 0x77, 0x77, + 0x7F, 0xE0, 0xE0, 0x70, 0x38, 0x1C, 0x7E, 0x77, 0x73, 0xF1, 0xF8, 0xFE, + 0x77, 0x39, 0xDC, 0x6E, 0x38, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xEF, 0x7B, + 0xFF, 0xFE, 0x39, 0xF8, 0xE7, 0xE3, 0x9F, 0x8E, 0x7E, 0x39, 0xF8, 0xE7, + 0xE3, 0x9F, 0x8E, 0x70, 0xEF, 0x7F, 0xF8, 0xFC, 0x7E, 0x3F, 0x1F, 0x8F, + 0xC7, 0xE3, 0xF1, 0xC0, 0x1E, 0x1F, 0xE7, 0x3B, 0x87, 0xE1, 0xF8, 0x7E, + 0x1D, 0xCE, 0x7F, 0x87, 0x80, 0xEF, 0x3F, 0xEF, 0x3B, 0x87, 0xE1, 0xF8, + 0x7E, 0x1F, 0xCE, 0xFF, 0xBB, 0xCE, 0x03, 0x80, 0xE0, 0x38, 0x00, 0x3B, + 0xBF, 0xFD, 0xFC, 0x7E, 0x3F, 0x1F, 0x8F, 0xEF, 0x7F, 0x9D, 0xC0, 0xE0, + 0x70, 0x38, 0x1C, 0xEF, 0xFF, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x80, 0x3E, + 0x3F, 0xB8, 0xFC, 0x0F, 0xC3, 0xFC, 0x3F, 0xC7, 0xFF, 0x1F, 0x00, 0x73, + 0xBF, 0xF7, 0x39, 0xCE, 0x73, 0x9E, 0x70, 0xE3, 0xF1, 0xF8, 0xFC, 0x7E, + 0x3F, 0x1F, 0x8F, 0xC7, 0xFF, 0xBD, 0xC0, 0xE1, 0x98, 0x67, 0x39, 0xCC, + 0x33, 0x0D, 0xC3, 0xE0, 0x78, 0x1E, 0x07, 0x00, 0xE3, 0x1D, 0x9E, 0x66, + 0x79, 0x99, 0xE6, 0x77, 0xB8, 0xD2, 0xC3, 0xCF, 0x0F, 0x3C, 0x3C, 0xF0, + 0x73, 0x80, 0x73, 0x9C, 0xE3, 0xF0, 0x78, 0x1E, 0x07, 0x81, 0xE0, 0xFC, + 0x73, 0x9C, 0xE0, 0xE1, 0xD8, 0x67, 0x39, 0xCE, 0x33, 0x0E, 0xC3, 0xE0, + 0x78, 0x1E, 0x03, 0x00, 0xC0, 0x70, 0x38, 0x0E, 0x00, 0xFE, 0xFE, 0x0E, + 0x1C, 0x38, 0x38, 0x70, 0xE0, 0xFF, 0xFF, 0x37, 0x66, 0x66, 0x6E, 0xE6, + 0x66, 0x66, 0x67, 0x30, 0xFF, 0xFF, 0x80, 0xCE, 0x66, 0x66, 0x67, 0x76, + 0x66, 0x66, 0x6E, 0xC0, 0x71, 0x8E, 0x0C, 0x00, 0xF0, 0x00, 0x8F, 0x10, + 0x1C, 0xF3, 0x83, 0xFF, 0xFC, 0x7F, 0xFF, 0xE3, 0xF0, 0xFE, 0x1E, 0x07, + 0xC1, 0xC0, 0x38, 0xF8, 0x01, 0xFF, 0x80, 0x1F, 0xF8, 0x01, 0xFF, 0x80, + 0x1F, 0x1C, 0x03, 0x81, 0xE0, 0x78, 0x3F, 0x0F, 0xC7, 0xFF, 0xFE, 0x3F, + 0xFF, 0xC3, 0xCF, 0x38, 0x08, 0xF1, 0x00, 0x0F, 0x00, 0x00, 0xEE, 0xFD, + 0xDF, 0xBB, 0x80, 0x0E, 0xEF, 0xDD, 0xFB, 0xB8, 0x00, 0xEE, 0xFD, 0xDF, + 0xBB, 0x80, 0x00, 0xE0, 0x1C, 0x03, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, + 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x1F, 0xFF, 0xDF, 0xFF, 0x0F, 0xF8, 0x0F, + 0xE0, 0x1F, 0xC0, 0x3F, 0x80, 0x7F, 0x01, 0xEF, 0x03, 0x06, 0x04, 0x04, + 0x00 +}; + +const GFXglyph FreeSansBold9pt7bGlyphs[] PROGMEM = { + { 0, 0, 0, 5, 0, 1 }, // 0x20 ' ' + { 0, 3, 13, 6, 2, -12 }, // 0x21 '!' + { 5, 7, 5, 9, 1, -12 }, // 0x22 '"' + { 10, 10, 12, 10, 0, -11 }, // 0x23 '#' + { 25, 9, 15, 10, 1, -13 }, // 0x24 '$' + { 42, 16, 13, 16, 0, -12 }, // 0x25 '%' + { 68, 12, 13, 13, 1, -12 }, // 0x26 '&' + { 88, 3, 5, 5, 1, -12 }, // 0x27 ''' + { 90, 6, 17, 6, 1, -12 }, // 0x28 '(' + { 103, 6, 17, 6, 0, -12 }, // 0x29 ')' + { 116, 5, 6, 7, 1, -12 }, // 0x2A '*' + { 120, 7, 8, 11, 2, -7 }, // 0x2B '+' + { 127, 3, 5, 4, 1, -1 }, // 0x2C ',' + { 129, 5, 2, 6, 0, -5 }, // 0x2D '-' + { 131, 3, 2, 4, 1, -1 }, // 0x2E '.' + { 132, 5, 13, 5, 0, -12 }, // 0x2F '/' + { 141, 9, 13, 10, 1, -12 }, // 0x30 '0' + { 156, 5, 13, 10, 2, -12 }, // 0x31 '1' + { 165, 9, 13, 10, 1, -12 }, // 0x32 '2' + { 180, 8, 13, 10, 1, -12 }, // 0x33 '3' + { 193, 8, 13, 10, 2, -12 }, // 0x34 '4' + { 206, 9, 13, 10, 1, -12 }, // 0x35 '5' + { 221, 9, 13, 10, 1, -12 }, // 0x36 '6' + { 236, 9, 13, 10, 0, -12 }, // 0x37 '7' + { 251, 10, 13, 10, 0, -12 }, // 0x38 '8' + { 268, 9, 13, 10, 1, -12 }, // 0x39 '9' + { 283, 3, 9, 4, 1, -8 }, // 0x3A ':' + { 287, 3, 12, 4, 1, -8 }, // 0x3B ';' + { 292, 9, 9, 11, 1, -8 }, // 0x3C '<' + { 303, 9, 6, 11, 1, -6 }, // 0x3D '=' + { 310, 9, 9, 11, 1, -8 }, // 0x3E '>' + { 321, 9, 13, 11, 1, -12 }, // 0x3F '?' + { 336, 16, 15, 18, 0, -12 }, // 0x40 '@' + { 366, 12, 13, 13, 0, -12 }, // 0x41 'A' + { 386, 11, 13, 13, 1, -12 }, // 0x42 'B' + { 404, 12, 13, 13, 1, -12 }, // 0x43 'C' + { 424, 12, 13, 13, 1, -12 }, // 0x44 'D' + { 444, 9, 13, 12, 1, -12 }, // 0x45 'E' + { 459, 9, 13, 11, 1, -12 }, // 0x46 'F' + { 474, 11, 13, 14, 1, -12 }, // 0x47 'G' + { 492, 11, 13, 13, 1, -12 }, // 0x48 'H' + { 510, 3, 13, 6, 1, -12 }, // 0x49 'I' + { 515, 8, 13, 10, 1, -12 }, // 0x4A 'J' + { 528, 12, 13, 13, 1, -12 }, // 0x4B 'K' + { 548, 8, 13, 11, 1, -12 }, // 0x4C 'L' + { 561, 14, 13, 16, 1, -12 }, // 0x4D 'M' + { 584, 11, 13, 14, 1, -12 }, // 0x4E 'N' + { 602, 13, 13, 14, 1, -12 }, // 0x4F 'O' + { 624, 11, 13, 12, 1, -12 }, // 0x50 'P' + { 642, 13, 14, 14, 1, -12 }, // 0x51 'Q' + { 665, 12, 13, 13, 1, -12 }, // 0x52 'R' + { 685, 11, 13, 12, 1, -12 }, // 0x53 'S' + { 703, 9, 13, 12, 2, -12 }, // 0x54 'T' + { 718, 11, 13, 13, 1, -12 }, // 0x55 'U' + { 736, 12, 13, 12, 0, -12 }, // 0x56 'V' + { 756, 17, 13, 17, 0, -12 }, // 0x57 'W' + { 784, 12, 13, 12, 0, -12 }, // 0x58 'X' + { 804, 11, 13, 12, 1, -12 }, // 0x59 'Y' + { 822, 9, 13, 11, 1, -12 }, // 0x5A 'Z' + { 837, 4, 17, 6, 1, -12 }, // 0x5B '[' + { 846, 5, 13, 5, 0, -12 }, // 0x5C '\' + { 855, 4, 17, 6, 0, -12 }, // 0x5D ']' + { 864, 8, 8, 11, 1, -12 }, // 0x5E '^' + { 872, 10, 1, 10, 0, 4 }, // 0x5F '_' + { 874, 3, 2, 5, 0, -12 }, // 0x60 '`' + { 875, 10, 10, 10, 1, -9 }, // 0x61 'a' + { 888, 10, 13, 11, 1, -12 }, // 0x62 'b' + { 905, 9, 10, 10, 1, -9 }, // 0x63 'c' + { 917, 9, 13, 11, 1, -12 }, // 0x64 'd' + { 932, 9, 10, 10, 1, -9 }, // 0x65 'e' + { 944, 5, 13, 6, 1, -12 }, // 0x66 'f' + { 953, 9, 14, 11, 1, -9 }, // 0x67 'g' + { 969, 9, 13, 11, 1, -12 }, // 0x68 'h' + { 984, 3, 13, 5, 1, -12 }, // 0x69 'i' + { 989, 4, 17, 5, 0, -12 }, // 0x6A 'j' + { 998, 9, 13, 10, 1, -12 }, // 0x6B 'k' + { 1013, 3, 13, 5, 1, -12 }, // 0x6C 'l' + { 1018, 14, 10, 16, 1, -9 }, // 0x6D 'm' + { 1036, 9, 10, 11, 1, -9 }, // 0x6E 'n' + { 1048, 10, 10, 11, 1, -9 }, // 0x6F 'o' + { 1061, 10, 14, 11, 1, -9 }, // 0x70 'p' + { 1079, 9, 14, 11, 1, -9 }, // 0x71 'q' + { 1095, 6, 10, 7, 1, -9 }, // 0x72 'r' + { 1103, 9, 10, 10, 1, -9 }, // 0x73 's' + { 1115, 5, 12, 6, 1, -11 }, // 0x74 't' + { 1123, 9, 10, 11, 1, -9 }, // 0x75 'u' + { 1135, 10, 10, 10, 0, -9 }, // 0x76 'v' + { 1148, 14, 10, 14, 0, -9 }, // 0x77 'w' + { 1166, 10, 10, 10, 0, -9 }, // 0x78 'x' + { 1179, 10, 14, 10, 0, -9 }, // 0x79 'y' + { 1197, 8, 10, 9, 1, -9 }, // 0x7A 'z' + { 1207, 4, 17, 7, 1, -12 }, // 0x7B '{' + { 1216, 1, 17, 5, 2, -12 }, // 0x7C '|' + { 1219, 4, 17, 7, 2, -12 }, // 0x7D '}' + { 1228, 8, 2, 9, 0, -4 }, // 0x7E '~' + { 1231, 20, 20, 22, 1, -12 }, // 0x7F gear icon + { 1231, 0, 0, 10, 0, 1 }, // 0x80 10px space to match numbers + { 1231, 0, 0, 4, 0, 1 }, // 0x81 4px space to match period + { 1282, 11, 15, 13, 1, -10 }, // 0x82 numpad icon + { 1304, 15, 15, 17, 1, -10 }, // 0x83 star icon +}; + +const GFXfont FreeSansBold9pt7b PROGMEM = { + (uint8_t *)FreeSansBold9pt7bBitmaps, + (GFXglyph *)FreeSansBold9pt7bGlyphs, + 0x20, //first character + 0x83, //last character + 22 //yAdvance (newline) +}; diff --git a/PDQ_MinLib/PDQ_FastPin.h b/PDQ_MinLib/PDQ_FastPin.h new file mode 100644 index 0000000..d96af0f --- /dev/null +++ b/PDQ_MinLib/PDQ_FastPin.h @@ -0,0 +1,489 @@ +// +// This excellent template library is from FastLED http://fastled.io +// +// I was considering writing something similar, but FastPin seemed perfect. +// +// Thanks FastLED people! Here is the license from FastLED followed by the +// header +// + +/* + +The MIT License (MIT) + +Copyright (c) 2013 FastLED + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +#ifndef __INC_FASTPIN_H +#define __INC_FASTPIN_H + +#include + +// Arduino.h needed for convinience functions digitalPinToPort/BitMask/portOutputRegister and the pinMode methods. +#include + +#define NO_PIN 255 + +// Class to ensure that a minimum amount of time has kicked since the last time run - and delay if not enough time has passed yet +// this should make sure that chipsets that have +template class CMinWait { + long mLastMicros; +public: + CMinWait() { mLastMicros = 0; } + + void wait() { + long diff = micros() - mLastMicros; + while(diff < WAIT) { + diff = micros() - mLastMicros; + } + } + + void mark() { mLastMicros = micros(); } +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Pin access class - needs to tune for various platforms (naive fallback solution?) +// +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +#define _CYCLES(_PIN) (((_PIN >= 62 ) || (_PIN>=42 && _PIN<=49) || (_PIN>=14 && _PIN <=17) || (_PIN>=6 && _PIN <=9)) ? 2 : 1) +#else +#define _CYCLES(_PIN) ((_PIN >= 24) ? 2 : 1) +#endif + +class Selectable { +public: + virtual void select() = 0; + virtual void release() = 0; + virtual bool isSelected() = 0; +}; + +class Pin : public Selectable { + uint8_t mPinMask; + uint8_t mPin; + volatile uint8_t *mPort; + + void _init() { + mPinMask = digitalPinToBitMask(mPin); + mPort = portOutputRegister(digitalPinToPort(mPin)); + } +public: + Pin(int pin) : mPin(pin) { _init(); } + + typedef volatile uint8_t * port_ptr_t; + typedef uint8_t port_t; + + inline void setOutput() { pinMode(mPin, OUTPUT); } + inline void setInput() { pinMode(mPin, INPUT); } + + inline void hi() __attribute__ ((always_inline)) { *mPort |= mPinMask; } + inline void lo() __attribute__ ((always_inline)) { *mPort &= ~mPinMask; } + + inline void strobe() __attribute__ ((always_inline)) { hi(); lo(); } + + inline void hi(register port_ptr_t port) __attribute__ ((always_inline)) { *port |= mPinMask; } + inline void lo(register port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~mPinMask; } + inline void set(register port_t val) __attribute__ ((always_inline)) { *mPort = val; } + + inline void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; } + + port_t hival() __attribute__ ((always_inline)) { return *mPort | mPinMask; } + port_t loval() __attribute__ ((always_inline)) { return *mPort & ~mPinMask; } + port_ptr_t port() __attribute__ ((always_inline)) { return mPort; } + port_t mask() __attribute__ ((always_inline)) { return mPinMask; } + + virtual void select() { hi(); } + virtual void release() { lo(); } + virtual bool isSelected() { return (*mPort & mPinMask) == mPinMask; } +}; + +class OutputPin : public Pin { +public: + OutputPin(int pin) : Pin(pin) { setOutput(); } +}; + +class InputPin : public Pin { +public: + InputPin(int pin) : Pin(pin) { setInput(); } +}; + +/// The simplest level of Pin class. This relies on runtime functions durinig initialization to get the port/pin mask for the pin. Most +/// of the accesses involve references to these static globals that get set up. This won't be the fastest set of pin operations, but it +/// will provide pin level access on pretty much all arduino environments. In addition, it includes some methods to help optimize access in +/// various ways. Namely, the versions of hi, lo, and fastset that take the port register as a passed in register variable (saving a global +/// dereference), since these functions are aggressively inlined, that can help collapse out a lot of extraneous memory loads/dereferences. +/// +/// In addition, if, while writing a bunch of data to a pin, you know no other pins will be getting written to, you can get/cache a value of +/// the pin's port register and use that to do a full set to the register. This results in one being able to simply do a store to the register, +/// vs. the load, and/or, and store that would be done normally. +/// +/// There are platform specific instantiations of this class that provide direct i/o register access to pins for much higher speed pin twiddling. +/// +/// Note that these classes are all static functions. So the proper usage is Pin<13>::hi(); or such. Instantiating objects is not recommended, +/// as passing Pin objects around will likely -not- have the effect you're expecting. +template class FastPin { + static uint8_t sPinMask; + static volatile uint8_t *sPort; + static void _init() { + sPinMask = digitalPinToBitMask(PIN); + sPort = portOutputRegister(digitalPinToPort(PIN)); + } +public: + typedef volatile uint8_t * port_ptr_t; + typedef uint8_t port_t; + + inline static void setOutput() { _init(); pinMode(PIN, OUTPUT); } + inline static void setInput() { _init(); pinMode(PIN, INPUT); } + + inline static void hi() __attribute__ ((always_inline)) { *sPort |= sPinMask; } + inline static void lo() __attribute__ ((always_inline)) { *sPort &= ~sPinMask; } + + inline static void strobe() __attribute__ ((always_inline)) { hi(); lo(); } + + inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { *port |= sPinMask; } + inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { *port &= ~sPinMask; } + inline static void set(register port_t val) __attribute__ ((always_inline)) { *sPort = val; } + + inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; } + + static port_t hival() __attribute__ ((always_inline)) { return *sPort | sPinMask; } + static port_t loval() __attribute__ ((always_inline)) { return *sPort & ~sPinMask; } + static port_ptr_t port() __attribute__ ((always_inline)) { return sPort; } + static port_t mask() __attribute__ ((always_inline)) { return sPinMask; } +}; + +template uint8_t FastPin::sPinMask; +template volatile uint8_t *FastPin::sPort; + +/// Class definition for a Pin where we know the port registers at compile time for said pin. This allows us to make +/// a lot of optimizations, as the inlined hi/lo methods will devolve to a single io register write/bitset. +template class _AVRPIN { +public: + typedef volatile uint8_t * port_ptr_t; + typedef uint8_t port_t; + + inline static void setOutput() { _DDR::r() |= _MASK; } + inline static void setInput() { _DDR::r() &= ~_MASK; } + + inline static void hi() __attribute__ ((always_inline)) { _PORT::r() |= _MASK; } + inline static void lo() __attribute__ ((always_inline)) { _PORT::r() &= ~_MASK; } + inline static void set(register uint8_t val) __attribute__ ((always_inline)) { _PORT::r() = val; } + + inline static void strobe() __attribute__ ((always_inline)) { hi(); lo(); } + + inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { hi(); } + inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { lo(); } + inline static void fastset(register port_ptr_t port, register uint8_t val) __attribute__ ((always_inline)) { set(val); } + + inline static port_t hival() __attribute__ ((always_inline)) { return _PORT::r() | _MASK; } + inline static port_t loval() __attribute__ ((always_inline)) { return _PORT::r() & ~_MASK; } + inline static port_ptr_t port() __attribute__ ((always_inline)) { return &_PORT::r(); } + inline static port_t mask() __attribute__ ((always_inline)) { return _MASK; } +}; + +/// Template definition for teensy 3.0 style ARM pins, providing direct access to the various GPIO registers. Note that this +/// uses the full port GPIO registers. In theory, in some way, bit-band register access -should- be faster, however I have found +/// that something about the way gcc does register allocation results in the bit-band code being slower. It will need more fine tuning. +template class _ARMPIN { +public: + typedef volatile uint32_t * port_ptr_t; + typedef uint32_t port_t; + + inline static void setOutput() { pinMode(PIN, OUTPUT); } // TODO: perform MUX config { _PDDR::r() |= _MASK; } + inline static void setInput() { pinMode(PIN, INPUT); } // TODO: preform MUX config { _PDDR::r() &= ~_MASK; } + + inline static void hi() __attribute__ ((always_inline)) { _PSOR::r() = _MASK; } + inline static void lo() __attribute__ ((always_inline)) { _PCOR::r() = _MASK; } + inline static void set(register port_t val) __attribute__ ((always_inline)) { _PDOR::r() = val; } + + inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); } + + inline static void toggle() __attribute__ ((always_inline)) { _PTOR::r() = _MASK; } + + inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { hi(); } + inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { lo(); } + inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; } + + inline static port_t hival() __attribute__ ((always_inline)) { return _PDOR::r() | _MASK; } + inline static port_t loval() __attribute__ ((always_inline)) { return _PDOR::r() & ~_MASK; } + inline static port_ptr_t port() __attribute__ ((always_inline)) { return &_PDOR::r(); } + inline static port_t mask() __attribute__ ((always_inline)) { return _MASK; } +}; + +/// Template definition for teensy 3.0 style ARM pins using bit banding, providing direct access to the various GPIO registers. GCC +/// does a poor job of optimizing around these accesses so they are not being used just yet. +template class _ARMPIN_BITBAND { +public: + typedef volatile uint32_t * port_ptr_t; + typedef uint32_t port_t; + + inline static void setOutput() { pinMode(PIN, OUTPUT); } // TODO: perform MUX config { _PDDR::r() |= _MASK; } + inline static void setInput() { pinMode(PIN, INPUT); } // TODO: preform MUX config { _PDDR::r() &= ~_MASK; } + + inline static void hi() __attribute__ ((always_inline)) { *_PDOR::template rx<_BIT>() = 1; } + inline static void lo() __attribute__ ((always_inline)) { *_PDOR::template rx<_BIT>() = 0; } + inline static void set(register port_t val) __attribute__ ((always_inline)) { *_PDOR::template rx<_BIT>() = val; } + + inline static void strobe() __attribute__ ((always_inline)) { toggle(); toggle(); } + + inline static void toggle() __attribute__ ((always_inline)) { *_PTOR::template rx<_BIT>() = 1; } + + inline static void hi(register port_ptr_t port) __attribute__ ((always_inline)) { *port = 1; } + inline static void lo(register port_ptr_t port) __attribute__ ((always_inline)) { *port = 0; } + inline static void fastset(register port_ptr_t port, register port_t val) __attribute__ ((always_inline)) { *port = val; } + + inline static port_t hival() __attribute__ ((always_inline)) { return 1; } + inline static port_t loval() __attribute__ ((always_inline)) { return 0; } + inline static port_ptr_t port() __attribute__ ((always_inline)) { return _PDOR::template rx<_BIT>(); } + inline static port_t mask() __attribute__ ((always_inline)) { return 1; } +}; + +/// AVR definitions for pins. Getting around the fact that I can't pass GPIO register addresses in as template arguments by instead creating +/// a custom type for each GPIO register with a single, static, aggressively inlined function that returns that specific GPIO register. A similar +/// trick is used a bit further below for the ARM GPIO registers (of which there are far more than on AVR!) +typedef volatile uint8_t & reg8_t; +#define _R(T) struct __gen_struct_ ## T +#define _RD8(T) struct __gen_struct_ ## T { static inline reg8_t r() { return T; }}; +#define _IO(L) _RD8(DDR ## L); _RD8(PORT ## L); _RD8(PIN ## L); +#define _DEFPIN_AVR(PIN, MASK, L) template<> class FastPin : public _AVRPIN {}; + +// ARM definitions +#define GPIO_BITBAND_ADDR(reg, bit) (((uint32_t)&(reg) - 0x40000000) * 32 + (bit) * 4 + 0x42000000) +#define GPIO_BITBAND_PTR(reg, bit) ((uint32_t *)GPIO_BITBAND_ADDR((reg), (bit))) + +typedef volatile uint32_t & reg32_t; +typedef volatile uint32_t * ptr_reg32_t; + +#define _RD32(T) struct __gen_struct_ ## T { static __attribute__((always_inline)) inline reg32_t r() { return T; } \ + template static __attribute__((always_inline)) inline ptr_reg32_t rx() { return GPIO_BITBAND_PTR(T, BIT); } }; +#define _IO32(L) _RD32(GPIO ## L ## _PDOR); _RD32(GPIO ## L ## _PSOR); _RD32(GPIO ## L ## _PCOR); _RD32(GPIO ## L ## _PTOR); _RD32(GPIO ## L ## _PDIR); _RD32(GPIO ## L ## _PDDR); + +#define _DEFPIN_ARM(PIN, BIT, L) template<> class FastPin : public _ARMPIN {}; + +// Don't use bit band'd pins for now, the compiler generates far less efficient code around them +// #define _DEFPIN_ARM(PIN, BIT, L) template<> class Pin : public _ARMPIN_BITBAND {}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// Pin definitions for AVR and ARM. If there are pin definitions supplied below for the platform being +// built on, then much higher speed access will be possible, namely with direct GPIO register accesses. +// +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +#if defined(FORCE_SOFTWARE_PINS) +#warning "Softwrae pin support forced pin access will be slightly slower. See fastpin.h for info." +#define NO_HARDWARE_PIN_SUPPORT + +#elif defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny45__) +_IO(B); + +_DEFPIN_AVR(0, 0x01, B); _DEFPIN_AVR(1, 0x02, B); _DEFPIN_AVR(2, 0x04, B); _DEFPIN_AVR(3, 0x08, B); +_DEFPIN_AVR(4, 0x10, B); _DEFPIN_AVR(5, 0x20, B); + +#elif(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny25__) +_IO(A); _IO(B); + +_DEFPIN_AVR(0, 0x01, A); _DEFPIN_AVR(1, 0x02, A); _DEFPIN_AVR(2, 0x04, A); _DEFPIN_AVR(3, 0x08, A); +_DEFPIN_AVR(4, 0x10, A); _DEFPIN_AVR(5, 0x20, A); _DEFPIN_AVR(6, 0x40, A); _DEFPIN_AVR(7, 0x80, A); +_DEFPIN_AVR(8, 0x04, B); _DEFPIN_AVR(9, 0x02, B); _DEFPIN_AVR(10, 0x01, B); + +#elif defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) +// Accelerated port definitions for arduino avrs +_IO(D); _IO(B); _IO(C); +_DEFPIN_AVR( 0, 0x01, D); _DEFPIN_AVR( 1, 0x02, D); _DEFPIN_AVR( 2, 0x04, D); _DEFPIN_AVR( 3, 0x08, D); +_DEFPIN_AVR( 4, 0x10, D); _DEFPIN_AVR( 5, 0x20, D); _DEFPIN_AVR( 6, 0x40, D); _DEFPIN_AVR( 7, 0x80, D); +_DEFPIN_AVR( 8, 0x01, B); _DEFPIN_AVR( 9, 0x02, B); _DEFPIN_AVR(10, 0x04, B); _DEFPIN_AVR(11, 0x08, B); +_DEFPIN_AVR(12, 0x10, B); _DEFPIN_AVR(13, 0x20, B); _DEFPIN_AVR(14, 0x01, C); _DEFPIN_AVR(15, 0x02, C); +_DEFPIN_AVR(16, 0x04, C); _DEFPIN_AVR(17, 0x08, C); _DEFPIN_AVR(18, 0x10, C); _DEFPIN_AVR(19, 0x20, C); + +#define SPI_DATA 11 +#define SPI_CLOCK 13 +#define SPI_SELECT 10 +#define AVR_HARDWARE_SPI + +#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) +// megas + +_IO(A); _IO(B); _IO(C); _IO(D); _IO(E); _IO(F); _IO(G); _IO(H); _IO(J); _IO(K); _IO(L); + +_DEFPIN_AVR(0, 1, E); _DEFPIN_AVR(1, 2, E); _DEFPIN_AVR(2, 16, E); _DEFPIN_AVR(3, 32, E); +_DEFPIN_AVR(4, 32, G); _DEFPIN_AVR(5, 8, E); _DEFPIN_AVR(6, 8, H); _DEFPIN_AVR(7, 16, H); +_DEFPIN_AVR(8, 32, H); _DEFPIN_AVR(9, 64, H); _DEFPIN_AVR(10, 16, B); _DEFPIN_AVR(11, 32, B); +_DEFPIN_AVR(12, 64, B); _DEFPIN_AVR(13, 128, B); _DEFPIN_AVR(14, 2, J); _DEFPIN_AVR(15, 1, J); +_DEFPIN_AVR(16, 2, H); _DEFPIN_AVR(17, 1, H); _DEFPIN_AVR(18, 8, D); _DEFPIN_AVR(19, 4, D); +_DEFPIN_AVR(20, 2, D); _DEFPIN_AVR(21, 1, D); _DEFPIN_AVR(22, 1, A); _DEFPIN_AVR(23, 2, A); +_DEFPIN_AVR(24, 4, A); _DEFPIN_AVR(25, 8, A); _DEFPIN_AVR(26, 16, A); _DEFPIN_AVR(27, 32, A); +_DEFPIN_AVR(28, 64, A); _DEFPIN_AVR(29, 128, A); _DEFPIN_AVR(30, 128, C); _DEFPIN_AVR(31, 64, C); +_DEFPIN_AVR(32, 32, C); _DEFPIN_AVR(33, 16, C); _DEFPIN_AVR(34, 8, C); _DEFPIN_AVR(35, 4, C); +_DEFPIN_AVR(36, 2, C); _DEFPIN_AVR(37, 1, C); _DEFPIN_AVR(38, 128, D); _DEFPIN_AVR(39, 4, G); +_DEFPIN_AVR(40, 2, G); _DEFPIN_AVR(41, 1, G); _DEFPIN_AVR(42, 128, L); _DEFPIN_AVR(43, 64, L); +_DEFPIN_AVR(44, 32, L); _DEFPIN_AVR(45, 16, L); _DEFPIN_AVR(46, 8, L); _DEFPIN_AVR(47, 4, L); +_DEFPIN_AVR(48, 2, L); _DEFPIN_AVR(49, 1, L); _DEFPIN_AVR(50, 8, B); _DEFPIN_AVR(51, 4, B); +_DEFPIN_AVR(52, 2, B); _DEFPIN_AVR(53, 1, B); _DEFPIN_AVR(54, 1, F); _DEFPIN_AVR(55, 2, F); +_DEFPIN_AVR(56, 4, F); _DEFPIN_AVR(57, 8, F); _DEFPIN_AVR(58, 16, F); _DEFPIN_AVR(59, 32, F); +_DEFPIN_AVR(60, 64, F); _DEFPIN_AVR(61, 128, F); _DEFPIN_AVR(62, 1, K); _DEFPIN_AVR(63, 2, K); +_DEFPIN_AVR(64, 4, K); _DEFPIN_AVR(65, 8, K); _DEFPIN_AVR(66, 16, K); _DEFPIN_AVR(67, 32, K); +_DEFPIN_AVR(68, 64, K); _DEFPIN_AVR(69, 128, K); + +#define SPI_DATA 51 +#define SPI_CLOCK 52 +#define SPI_SELECT 53 +#define AVR_HARDWARE_SPI + +#elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) +// Xark: Add ATMega644,644P,1284 and 1284P (using pinout from http://maniacbug.wordpress.com/2011/11/27/arduino-on-atmega1284p-4/) + +_IO(A); _IO(B); _IO(C); _IO(D); + +_DEFPIN_AVR( 0, (1<<0), B); _DEFPIN_AVR( 1, (1<<1), B); _DEFPIN_AVR( 2, (1<<2), B); _DEFPIN_AVR( 3, (1<<3), B); +_DEFPIN_AVR( 4, (1<<4), B); _DEFPIN_AVR( 5, (1<<5), B); _DEFPIN_AVR( 6, (1<<6), B); _DEFPIN_AVR( 7, (1<<7), B); + +_DEFPIN_AVR( 8, (1<<0), D); _DEFPIN_AVR( 9, (1<<1), D); _DEFPIN_AVR(10, (1<<2), D); _DEFPIN_AVR(11, (1<<3), D); +_DEFPIN_AVR(12, (1<<4), D); _DEFPIN_AVR(13, (1<<5), D); _DEFPIN_AVR(14, (1<<6), D); _DEFPIN_AVR(15, (1<<7), D); + +_DEFPIN_AVR(16, (1<<0), C); _DEFPIN_AVR(17, (1<<1), C); _DEFPIN_AVR(18, (1<<2), C); _DEFPIN_AVR(19, (1<<3), C); +_DEFPIN_AVR(20, (1<<4), C); _DEFPIN_AVR(21, (1<<5), C); _DEFPIN_AVR(22, (1<<6), C); _DEFPIN_AVR(23, (1<<7), C); + +_DEFPIN_AVR(24, (1<<0), A); _DEFPIN_AVR(25, (1<<1), A); _DEFPIN_AVR(26, (1<<2), A); _DEFPIN_AVR(27, (1<<3), A); +_DEFPIN_AVR(28, (1<<4), A); _DEFPIN_AVR(29, (1<<5), A); _DEFPIN_AVR(30, (1<<6), A); _DEFPIN_AVR(31, (1<<7), A); + +#define SPI_DATA 5 +#define SPI_CLOCK 7 +#define SPI_SELECT 4 +#define AVR_HARDWARE_SPI + +// Leonardo, teensy, blinkm +#elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY) +// Leonardo, teensy, blinkm +#elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY) + +// teensy defs +_IO(B); _IO(C); _IO(D); _IO(E); _IO(F); + +_DEFPIN_AVR(0, 1, B); _DEFPIN_AVR(1, 2, B); _DEFPIN_AVR(2, 4, B); _DEFPIN_AVR(3, 8, B); +_DEFPIN_AVR(4, 128, B); _DEFPIN_AVR(5, 1, D); _DEFPIN_AVR(6, 2, D); _DEFPIN_AVR(7, 4, D); +_DEFPIN_AVR(8, 8, D); _DEFPIN_AVR(9, 64, C); _DEFPIN_AVR(10, 128, C); _DEFPIN_AVR(11, 64, D); +_DEFPIN_AVR(12, 128, D); _DEFPIN_AVR(13, 16, B); _DEFPIN_AVR(14, 32, B); _DEFPIN_AVR(15, 64, B); +_DEFPIN_AVR(16, 128, F); _DEFPIN_AVR(17, 64, F); _DEFPIN_AVR(18, 32, F); _DEFPIN_AVR(19, 16, F); +_DEFPIN_AVR(20, 2, F); _DEFPIN_AVR(21, 1, F); _DEFPIN_AVR(22, 16, D); _DEFPIN_AVR(23, 32, D); + +#define SPI_DATA 2 +#define SPI_CLOCK 1 +#define SPI_SELECT 3 +#define AVR_HARDWARE_SPI + +#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) +// teensy++ 2 defs + +_IO(A); _IO(B); _IO(C); _IO(D); _IO(E); _IO(F); + +_DEFPIN_AVR(0, 1, D); _DEFPIN_AVR(1, 2, D); _DEFPIN_AVR(2, 4, D); _DEFPIN_AVR(3, 8, D); +_DEFPIN_AVR(4, 16, D); _DEFPIN_AVR(5, 32, D); _DEFPIN_AVR(6, 64, D); _DEFPIN_AVR(7, 128, D); +_DEFPIN_AVR(8, 1, E); _DEFPIN_AVR(9, 2, E); _DEFPIN_AVR(10, 1, C); _DEFPIN_AVR(11, 2, C); +_DEFPIN_AVR(12, 4, C); _DEFPIN_AVR(13, 8, C); _DEFPIN_AVR(14, 16, C); _DEFPIN_AVR(15, 32, C); +_DEFPIN_AVR(16, 64, C); _DEFPIN_AVR(17, 128, C); _DEFPIN_AVR(18, 64, E); _DEFPIN_AVR(19, 128, E); +_DEFPIN_AVR(20, 1, B); _DEFPIN_AVR(21, 2, B); _DEFPIN_AVR(22, 4, B); _DEFPIN_AVR(23, 8, B); +_DEFPIN_AVR(24, 16, B); _DEFPIN_AVR(25, 32, B); _DEFPIN_AVR(26, 64, B); _DEFPIN_AVR(27, 128, B); +_DEFPIN_AVR(28, 1, A); _DEFPIN_AVR(29, 2, A); _DEFPIN_AVR(30, 4, A); _DEFPIN_AVR(31, 8, A); +_DEFPIN_AVR(32, 16, A); _DEFPIN_AVR(33, 32, A); _DEFPIN_AVR(34, 64, A); _DEFPIN_AVR(35, 128, A); +_DEFPIN_AVR(36, 16, E); _DEFPIN_AVR(37, 32, E); _DEFPIN_AVR(38, 1, F); _DEFPIN_AVR(39, 2, F); +_DEFPIN_AVR(40, 4, F); _DEFPIN_AVR(41, 8, F); _DEFPIN_AVR(42, 16, F); _DEFPIN_AVR(43, 32, F); +_DEFPIN_AVR(44, 64, F); _DEFPIN_AVR(45, 128, F); + +#define SPI_DATA 22 +#define SPI_CLOCK 21 +#define SPI_SELECT 20 +#define AVR_HARDWARE_SPI + +#elif defined(__AVR_ATmega32U4__) + +// leonard defs +_IO(B); _IO(C); _IO(D); _IO(E); _IO(F); + +_DEFPIN_AVR(0, 4, D); _DEFPIN_AVR(1, 8, D); _DEFPIN_AVR(2, 2, D); _DEFPIN_AVR(3, 1, D); +_DEFPIN_AVR(4, 16, D); _DEFPIN_AVR(5, 64, C); _DEFPIN_AVR(6, 128, D); _DEFPIN_AVR(7, 64, E); +_DEFPIN_AVR(8, 16, B); _DEFPIN_AVR(9, 32, B); _DEFPIN_AVR(10, 64, B); _DEFPIN_AVR(11, 128, B); +_DEFPIN_AVR(12, 64, D); _DEFPIN_AVR(13, 128, C); _DEFPIN_AVR(14, 8, B); _DEFPIN_AVR(15, 2, B); +_DEFPIN_AVR(16, 4, B); _DEFPIN_AVR(17, 1, B); _DEFPIN_AVR(18, 128, F); _DEFPIN_AVR(19, 64, F); +_DEFPIN_AVR(20, 32, F); _DEFPIN_AVR(21, 16, F); _DEFPIN_AVR(22, 2, F); _DEFPIN_AVR(23, 0, F); + +#define SPI_DATA 16 +#define SPI_CLOCK 15 +#define AVR_HARDWARE_SPI + +#elif defined(__MK20DX128__) && defined(CORE_TEENSY) + +_IO32(A); _IO32(B); _IO32(C); _IO32(D); _IO32(E); + +_DEFPIN_ARM(0, 16, B); _DEFPIN_ARM(1, 17, B); _DEFPIN_ARM(2, 0, D); _DEFPIN_ARM(3, 12, A); +_DEFPIN_ARM(4, 13, A); _DEFPIN_ARM(5, 7, D); _DEFPIN_ARM(6, 4, D); _DEFPIN_ARM(7, 2, D); +_DEFPIN_ARM(8, 3, D); _DEFPIN_ARM(9, 3, C); _DEFPIN_ARM(10, 4, C); _DEFPIN_ARM(11, 6, C); +_DEFPIN_ARM(12, 7, C); _DEFPIN_ARM(13, 5, C); _DEFPIN_ARM(14, 1, D); _DEFPIN_ARM(15, 0, C); +_DEFPIN_ARM(16, 0, B); _DEFPIN_ARM(17, 1, B); _DEFPIN_ARM(18, 3, B); _DEFPIN_ARM(19, 2, B); +_DEFPIN_ARM(20, 5, D); _DEFPIN_ARM(21, 6, D); _DEFPIN_ARM(22, 1, C); _DEFPIN_ARM(23, 2, C); +_DEFPIN_ARM(24, 5, A); _DEFPIN_ARM(25, 19, B); _DEFPIN_ARM(26, 1, E); _DEFPIN_ARM(27, 9, C); +_DEFPIN_ARM(28, 8, C); _DEFPIN_ARM(29, 10, C); _DEFPIN_ARM(30, 11, C); _DEFPIN_ARM(31, 0, E); +_DEFPIN_ARM(32, 18, B); _DEFPIN_ARM(33, 4, A); + +#define SPI_DATA 11 +#define SPI_CLOCK 13 +#define ARM_HARDWARE_SPI + +#elif defined(__SAM3X8E__) + +DUE_IO32(A); +DUE_IO32(B); +DUE_IO32(C); +DUE_IO32(D); + +_DEFPIN_DUE(0, 8, A); _DEFPIN_DUE(1, 9, A); _DEFPIN_DUE(2, 25, B); _DEFPIN_DUE(3, 28, C); +_DEFPIN_DUE(4, 26, C); _DEFPIN_DUE(5, 25, C); _DEFPIN_DUE(6, 24, C); _DEFPIN_DUE(7, 23, C); +_DEFPIN_DUE(8, 22, C); _DEFPIN_DUE(9, 21, C); _DEFPIN_DUE(10, 29, C); _DEFPIN_DUE(11, 7, D); +_DEFPIN_DUE(12, 8, D); _DEFPIN_DUE(13, 27, B); _DEFPIN_DUE(14, 4, D); _DEFPIN_DUE(15, 5, D); +_DEFPIN_DUE(16, 13, A); _DEFPIN_DUE(17, 12, A); _DEFPIN_DUE(18, 11, A); _DEFPIN_DUE(19, 10, A); +_DEFPIN_DUE(20, 12, B); _DEFPIN_DUE(21, 13, B); _DEFPIN_DUE(22, 26, B); _DEFPIN_DUE(23, 14, A); +_DEFPIN_DUE(24, 15, A); _DEFPIN_DUE(25, 0, D); _DEFPIN_DUE(26, 1, D); _DEFPIN_DUE(27, 2, D); +_DEFPIN_DUE(28, 3, D); _DEFPIN_DUE(29, 6, D); _DEFPIN_DUE(30, 9, D); _DEFPIN_DUE(31, 7, A); +_DEFPIN_DUE(32, 10, D); _DEFPIN_DUE(33, 1, C); _DEFPIN_DUE(34, 2, C); _DEFPIN_DUE(35, 3, C); +_DEFPIN_DUE(36, 4, C); _DEFPIN_DUE(37, 5, C); _DEFPIN_DUE(38, 6, C); _DEFPIN_DUE(39, 7, C); +_DEFPIN_DUE(40, 8, C); _DEFPIN_DUE(41, 9, C); _DEFPIN_DUE(42, 19, A); _DEFPIN_DUE(43, 20, A); +_DEFPIN_DUE(44, 19, C); _DEFPIN_DUE(45, 18, C); _DEFPIN_DUE(46, 17, C); _DEFPIN_DUE(47, 16, C); +_DEFPIN_DUE(48, 15, C); _DEFPIN_DUE(49, 14, C); _DEFPIN_DUE(50, 13, C); _DEFPIN_DUE(51, 12, C); +_DEFPIN_DUE(52, 21, B); _DEFPIN_DUE(53, 14, B); _DEFPIN_DUE(54, 16, A); _DEFPIN_DUE(55, 24, A); +_DEFPIN_DUE(56, 23, A); _DEFPIN_DUE(57, 22, A); _DEFPIN_DUE(58, 6, A); _DEFPIN_DUE(59, 4, A); +_DEFPIN_DUE(60, 3, A); _DEFPIN_DUE(61, 2, A); _DEFPIN_DUE(62, 17, B); _DEFPIN_DUE(63, 18, B); +_DEFPIN_DUE(64, 19, B); _DEFPIN_DUE(65, 20, B); _DEFPIN_DUE(66, 15, B); _DEFPIN_DUE(67, 16, B); +_DEFPIN_DUE(68, 1, A); _DEFPIN_DUE(69, 0, A); _DEFPIN_DUE(70, 17, A); _DEFPIN_DUE(71, 18, A); +_DEFPIN_DUE(72, 30, C); _DEFPIN_DUE(73, 21, A); _DEFPIN_DUE(74, 25, A); _DEFPIN_DUE(75, 26, A); +_DEFPIN_DUE(76, 27, A); _DEFPIN_DUE(77, 28, A); _DEFPIN_DUE(78, 23, B); + +#else + +#warning "No pin/port mappings found, pin access will be slightly slower. See fastpin.h for info." +#define NO_HARDWARE_PIN_SUPPORT + +#endif + +#endif diff --git a/PDQ_MinLib/PDQ_GFX.h b/PDQ_MinLib/PDQ_GFX.h new file mode 100644 index 0000000..abc5735 --- /dev/null +++ b/PDQ_MinLib/PDQ_GFX.h @@ -0,0 +1,1389 @@ +// This is the PDQ re-mixed version of Adafruit's library from Xark +// here is the original copyright notice and license: + +/* +This is the core graphics library for all our displays, providing a common +set of graphics primitives (points, lines, circles, etc.). It needs to be +paired with a hardware-specific library for each display device we carry +(to handle the lower-level functions). + +Adafruit invests time and resources providing this open source code, please +support Adafruit & open-source hardware by purchasing products from Adafruit! + +Copyright (c) 2013 Adafruit Industries. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + +// This PDQ optimized version is by Xark +// +// Inspiration from Paul Stoffregen and the Teensy 3.1 community. +// +// GOALS: +// 1) Maintain "sketch" compatibility with original Adafruit libraries. +// 2) Be as much faster as is reasonably possible honoring goal 1. :-) +// 3) Be at least as small as Adafruit libraries. +// +// I believe all three of these have largely been achieved: +// 1) Near full compatibility. Only minor initialization changes in original sketch. +// 2) Between ~2.5 and ~12 times faster (fillRect ~2.5x, drawLine ~12x). +// An average of ~4x faster over entire "graphictest.ino" benchmark. +// +// Even if this library is faster, it was based on the Adafruit original. +// Adafruit deserves your support for making their library open-source (and +// for having some nice LCD modules and all kinds of other great parts too). +// Consider giving them your support if possible! + +#ifndef _PDQ_GFX_H +#define _PDQ_GFX_H + +#include "Arduino.h" +#include "Print.h" + +#ifndef pgm_read_byte + #define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#endif +#ifndef pgm_read_word + #define pgm_read_word(addr) (*(const unsigned short *)(addr)) +#endif +#ifndef pgm_read_dword + #define pgm_read_dword(addr) (*(const unsigned long *)(addr)) +#endif +#if !defined(__INT_MAX__) || (__INT_MAX__ > 0xFFFFL) + #define pgm_read_pointer(addr) ((void *)pgm_read_dword(addr)) +#else + #define pgm_read_pointer(addr) ((void *)pgm_read_word(addr)) +#endif + +#include "gfxfont.h" + +#define GFX_FONT_PACKED + +typedef int coord_t; // type used for coordinates (signed) for parameters (int16_t used for storage) +typedef uint16_t color_t; // type used for colors (unsigned) + +// swap any type +template +static inline void swapValue(T& x, T& y) +{ + T tmp = x; + x = y; + y = tmp; +} + +// minimum value for any type +template +static inline T minValue(T& x, T& y) +{ + return x < y ? x : y; +} + +// maximum value for any type +template +static inline T maxValue(T& x, T& y) +{ + return x >= y ? x : y; +} + +template +class PDQ_GFX : public Print { + +public: + PDQ_GFX(coord_t w, coord_t h); // Constructor (called by HW driver) + + // Graphic primitives + // drawPixel MUST be defined by the driver subclass (and has no generic fall-back): + + // These are generic versions of routines for drivers that don't provide device-optimized code. + // Drivers are required to have these functions (without "_" postfix), but can fall back to using + // these if needed (they should not be called directly with "_" postfix or it will bypass any + // device-optimized implementations). + static void drawLine_(coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color); + static void drawFastVLine_(coord_t x, coord_t y, coord_t h, color_t color); + static void drawFastHLine_(coord_t x, coord_t y, coord_t w, color_t color); + static void fillRect_(coord_t x, coord_t y, coord_t w, coord_t h, color_t color); + static void fillScreen_(color_t color); + + // These are usually overridden in the driver subclass to be useful (but not internally referenced) + static void setRotation(uint8_t r); // only swaps width/height if not supported by driver + static void invertDisplay(boolean i); // only if supported by driver + + // These exist in PDQ_GFX (and generally have no subclass override) + static void drawRect(coord_t x, coord_t y, coord_t w, coord_t h, color_t color); + static void drawCircle(coord_t x0, coord_t y0, coord_t r, color_t color); + static void drawCircleHelper(coord_t x0, coord_t y0, coord_t r, uint8_t cornername, color_t color); + static void fillCircle(coord_t x0, coord_t y0, coord_t r, color_t color); + static void fillCircleHelper(coord_t x0, coord_t y0, coord_t r, uint8_t cornername, coord_t delta, color_t color); + static void drawTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color); + static void fillTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color); + static void drawRoundRect(coord_t x0, coord_t y0, coord_t w, coord_t h, coord_t radius, color_t color); + static void fillRoundRect(coord_t x0, coord_t y0, coord_t w, coord_t h, coord_t radius, color_t color); + static void drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color); + static void drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg); + static void drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color); + static void drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg); + static void drawXBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color); + static void drawChar(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size); + static void drawCharGFX(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size); + static inline void setCursor(coord_t x, coord_t y); + static inline void setBound(coord_t x, coord_t y); + static inline void setTextColor(color_t c); + static inline void setTextColor(color_t c, color_t bg); + static inline void setTextSize(uint8_t s); + static inline void setTextWrap(boolean w); + static inline void cp437(boolean x=true); + static inline void setFont(const GFXfont *f = NULL); + + static inline coord_t width() __attribute__ ((always_inline)) { return _width; } + static inline coord_t height() __attribute__ ((always_inline)) { return _height; } + static inline uint8_t getRotation() __attribute__ ((always_inline)) { return rotation; } + static inline coord_t getCursorX() __attribute__ ((always_inline)) { return cursor_x; } + static inline coord_t getCursorY() __attribute__ ((always_inline)) { return cursor_y; } + static inline void getTextBounds(const char *string, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi = _width); + static inline void getTextBounds(const __FlashStringHelper *s, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi = _width); + + virtual size_t write(uint8_t); // used by Arduino "Print.h" (and the one required virtual function) + +protected: + static GFXfont* gfxFont; + static coord_t WIDTH, HEIGHT; // This is the 'raw' display w/h - never changes + static coord_t _width, _height; // Display w/h as modified by current rotation + static coord_t cursor_x, cursor_y; + static coord_t bound_x1, bound_x2; + static color_t textcolor, textbgcolor; + static uint8_t textsize; + static uint8_t rotation; + static boolean wrap; // If set, 'wrap' text at right edge of display + static boolean _cp437; // If set, use correct CP437 charset (default is off) +}; + + +template +class PDQ_GFX_Button_ +{ +public: + PDQ_GFX_Button_(); + void initButton(PDQ_GFX *gfx, coord_t x, coord_t y, coord_t w, coord_t h, color_t outline, color_t fill, color_t textcolor, const char *label, uint8_t textsize); + void drawButton(boolean inverted = false); + boolean contains(coord_t x, coord_t y); + + void press(boolean p); + boolean isPressed(); + boolean justPressed(); + boolean justReleased(); + +private: + PDQ_GFX *_gfx; + int16_t _x, _y; + int16_t _w, _h; + uint8_t _textsize; + color_t _outlinecolor, _fillcolor, _textcolor; + char _label[10]; + + boolean currstate, laststate; +}; + +// ----------------------------------------------- +extern const unsigned char glcdfont[] PROGMEM; + +template +int16_t PDQ_GFX::WIDTH; // This is the 'raw' display w/h - never changes +template +int16_t PDQ_GFX::HEIGHT; +template +int16_t PDQ_GFX::_width; // Display w/h as modified by current rotation +template +int16_t PDQ_GFX::_height; +template +int16_t PDQ_GFX::cursor_x; +template +int16_t PDQ_GFX::cursor_y; +template +int16_t PDQ_GFX::bound_x1; +template +int16_t PDQ_GFX::bound_x2; +template +color_t PDQ_GFX::textcolor; +template +color_t PDQ_GFX::textbgcolor; +template +uint8_t PDQ_GFX::textsize; +template +uint8_t PDQ_GFX::rotation; +template +boolean PDQ_GFX::wrap; // If set, 'wrap' text at right edge of display +template +boolean PDQ_GFX::_cp437; // If set, use correct CP437 charset (default is off) +template +GFXfont *PDQ_GFX::gfxFont; + +template +PDQ_GFX::PDQ_GFX(coord_t w, coord_t h) +{ + WIDTH = (int16_t)w; + HEIGHT = (int16_t)h; + _width = (int16_t)w; + _height = (int16_t)h; + cursor_x = 0; + cursor_y = 0; + bound_x1 = 0; + bound_x2 = WIDTH; + rotation = 0; + textsize = 1; + textcolor = 0xffff; + textbgcolor = 0xffff; + wrap = true; + _cp437 = false; + gfxFont = NULL; +} + +// Draw a circle outline +template +void PDQ_GFX::drawCircle(coord_t x0, coord_t y0, coord_t r, color_t color) +{ + coord_t f = 1 - r; + coord_t ddF_x = 1; + coord_t ddF_y = -2 * r; + coord_t x = 0; + coord_t y = r; + + HW::drawPixel(x0 , y0+r, color); + HW::drawPixel(x0 , y0-r, color); + HW::drawPixel(x0+r, y0 , color); + HW::drawPixel(x0-r, y0 , color); + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + HW::drawPixel(x0 + x, y0 + y, color); + HW::drawPixel(x0 - x, y0 + y, color); + HW::drawPixel(x0 + x, y0 - y, color); + HW::drawPixel(x0 - x, y0 - y, color); + HW::drawPixel(x0 + y, y0 + x, color); + HW::drawPixel(x0 - y, y0 + x, color); + HW::drawPixel(x0 + y, y0 - x, color); + HW::drawPixel(x0 - y, y0 - x, color); + } +} + +template +void PDQ_GFX::drawCircleHelper( coord_t x0, coord_t y0, coord_t r, uint8_t cornername, color_t color) +{ + coord_t f = 1 - r; + coord_t ddF_x = 1; + coord_t ddF_y = -2 * r; + coord_t x = 0; + coord_t y = r; + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + if (cornername & 0x4) + { + HW::drawPixel(x0 + x, y0 + y, color); + HW::drawPixel(x0 + y, y0 + x, color); + } + if (cornername & 0x2) + { + HW::drawPixel(x0 + x, y0 - y, color); + HW::drawPixel(x0 + y, y0 - x, color); + } + if (cornername & 0x8) + { + HW::drawPixel(x0 - y, y0 + x, color); + HW::drawPixel(x0 - x, y0 + y, color); + } + if (cornername & 0x1) + { + HW::drawPixel(x0 - y, y0 - x, color); + HW::drawPixel(x0 - x, y0 - y, color); + } + } +} + +template +void PDQ_GFX::fillCircle(coord_t x0, coord_t y0, coord_t r, color_t color) +{ + HW::drawFastVLine(x0, y0-r, 2*r+1, color); + fillCircleHelper(x0, y0, r, 3, 0, color); +} + +// Used to do circles and roundrects +template +void PDQ_GFX::fillCircleHelper(coord_t x0, coord_t y0, coord_t r, uint8_t cornername, coord_t delta, color_t color) +{ + coord_t f = 1 - r; + coord_t ddF_x = 1; + coord_t ddF_y = -2 * r; + coord_t x = 0; + coord_t y = r; + + while (x < y) + { + if (f >= 0) + { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + if (cornername & 0x1) + { + HW::drawFastVLine(x0+x, y0-y, 2*y+1+delta, color); + HW::drawFastVLine(x0+y, y0-x, 2*x+1+delta, color); + } + if (cornername & 0x2) + { + HW::drawFastVLine(x0-x, y0-y, 2*y+1+delta, color); + HW::drawFastVLine(x0-y, y0-x, 2*x+1+delta, color); + } + } +} + +// Bresenham's algorithm - thx Wikipedia +template +void PDQ_GFX::drawLine_(coord_t x0, coord_t y0, coord_t x1, coord_t y1, color_t color) +{ + int8_t steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) + { + swapValue(x0, y0); + swapValue(x1, y1); + } + + if (x0 > x1) + { + swapValue(x0, x1); + swapValue(y0, y1); + } + + coord_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + coord_t err = dx / 2; + coord_t ystep; + + if (y0 < y1) + { + ystep = 1; + } + else + { + ystep = -1; + } + + for (; x0<=x1; x0++) + { + if (steep) + { + HW::drawPixel(y0, x0, color); + } + else + { + HW::drawPixel(x0, y0, color); + } + err -= dy; + if (err < 0) + { + y0 += ystep; + err += dx; + } + } +} + +// Draw a rectangle +template +void PDQ_GFX::drawRect(coord_t x, coord_t y, coord_t w, coord_t h, color_t color) +{ + HW::drawFastHLine(x , y , w, color); + HW::drawFastHLine(x , y+h-1, w, color); + HW::drawFastVLine(x , y , h, color); + HW::drawFastVLine(x+w-1, y , h, color); +} + +template +void PDQ_GFX::drawFastVLine_(coord_t x, coord_t y, coord_t h, color_t color) +{ + // Used by driver when it has no special support + HW::drawLine(x, y, x, y+h-1, color); +} + +template +void PDQ_GFX::drawFastHLine_(coord_t x, coord_t y, coord_t w, color_t color) +{ + // Used by driver when it has no special support + HW::drawLine(x, y, x+w-1, y, color); +} + +template +void PDQ_GFX::fillRect_(coord_t x, coord_t y, coord_t w, coord_t h, color_t color) +{ + // Used by driver when it has no special support + for (coord_t i=x; i +void PDQ_GFX::fillScreen_(color_t color) +{ + // Used by driver when it has no special support + HW::fillRect(0, 0, _width, _height, color); +} + +// Draw a rounded rectangle +template +void PDQ_GFX::drawRoundRect(coord_t x, coord_t y, coord_t w, coord_t h, coord_t r, color_t color) +{ + // smarter version + HW::drawFastHLine(x+r , y , w-2*r, color); // Top + HW::drawFastHLine(x+r , y+h-1, w-2*r, color); // Bottom + HW::drawFastVLine(x , y+r , h-2*r, color); // Left + HW::drawFastVLine(x+w-1, y+r , h-2*r, color); // Right + // draw four corners + drawCircleHelper(x+r , y+r , r, 1, color); + drawCircleHelper(x+w-r-1, y+r , r, 2, color); + drawCircleHelper(x+w-r-1, y+h-r-1, r, 4, color); + drawCircleHelper(x+r , y+h-r-1, r, 8, color); +} + +// Fill a rounded rectangle +template +void PDQ_GFX::fillRoundRect(coord_t x, coord_t y, coord_t w, coord_t h, coord_t r, color_t color) +{ + // smarter version + HW::fillRect(x+r, y, w-2*r, h, color); + + // draw four corners + fillCircleHelper(x+w-r-1, y+r, r, 1, h-2*r-1, color); + fillCircleHelper(x+r , y+r, r, 2, h-2*r-1, color); +} + +// Draw a triangle +template +void PDQ_GFX::drawTriangle(coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color) +{ + HW::drawLine(x0, y0, x1, y1, color); + HW::drawLine(x1, y1, x2, y2, color); + HW::drawLine(x2, y2, x0, y0, color); +} + +// Fill a triangle +template +void PDQ_GFX::fillTriangle( coord_t x0, coord_t y0, coord_t x1, coord_t y1, coord_t x2, coord_t y2, color_t color) +{ + coord_t a, b, y, last; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) + { + swapValue(y0, y1); + swapValue(x0, x1); + } + if (y1 > y2) + { + swapValue(y2, y1); + swapValue(x2, x1); + } + if (y0 > y1) + { + swapValue(y0, y1); + swapValue(x0, x1); + } + + if (y0 == y2) // Handle awkward all-on-same-line case as its own thing + { + a = b = x0; + if (x1 < a) + a = x1; + else if (x1 > b) + b = x1; + if (x2 < a) + a = x2; + else if (x2 > b) + b = x2; + HW::drawFastHLine(a, y0, b-a+1, color); + return; + } + + coord_t dx01 = x1 - x0; + coord_t dy01 = y1 - y0; + coord_t dx02 = x2 - x0; + coord_t dy02 = y2 - y0; + coord_t dx12 = x2 - x1; + coord_t dy12 = y2 - y1; + int32_t sa = 0; + int32_t sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) + last = y1; // Include y1 scanline + else + last = y1-1; // Skip it + + for (y = y0; y <= last; y++) + { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + swapValue(a, b); + HW::drawFastHLine(a, y, b-a+1, color); + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + sa = dx12 * (y - y1); + sb = dx02 * (y - y0); + for (; y <= y2; y++) + { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + /* longhand: + a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + swapValue(a, b); + HW::drawFastHLine(a, y, b-a+1, color); + } +} + +// Draw a 1-bit image (bitmap) at the specified (x, y) position from the +// provided bitmap buffer (must be PROGMEM memory) using the specified +// foreground color (unset bits are transparent). +template +void PDQ_GFX::drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color) +{ + coord_t i, j, byteWidth = (w + 7) / 8; + uint8_t byte; + + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + if (i % 8 == 0) + byte = pgm_read_byte(bitmap + j * byteWidth + i / 8); + else + byte <<= 1; + + if (byte & 0x80) + HW::drawPixel(x+i, y+j, color); + } + } +} + +// Draw a 1-bit image (bitmap) at the specified (x, y) position from the +// provided bitmap buffer (must be PROGMEM memory) using the specified +// foreground (for set bits) and background (for clear bits) colors. +template +void PDQ_GFX::drawBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg) +{ + coord_t i, j, byteWidth = (w + 7) / 8; + uint8_t byte; + + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + if (i % 8 == 0) + byte = pgm_read_byte(bitmap + j * byteWidth + i / 8); + else + byte <<= 1; + + if (byte & 0x80) + HW::drawPixel(x+i, y+j, color); + else + HW::drawPixel(x+i, y+j, bg); + } + } +} + +// drawBitmap() variant for RAM-resident (not PROGMEM) bitmaps. +template +void PDQ_GFX::drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color) +{ + coord_t i, j, byteWidth = (w + 7) / 8; + uint8_t byte; + + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + if (i % 8 == 0) + byte = bitmap[j * byteWidth + i / 8]; + else + byte <<= 1; + + if (byte & 0x80) + HW::drawPixel(x+i, y+j, color); + } + } +} + +// drawBitmap() variant w/background for RAM-resident (not PROGMEM) bitmaps. +template +void PDQ_GFX::drawBitmap(coord_t x, coord_t y, uint8_t *bitmap, coord_t w, coord_t h, color_t color, color_t bg) +{ + coord_t i, j, byteWidth = (w + 7) / 8; + uint8_t byte; + + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + if (i % 8 == 0) + byte = bitmap[j * byteWidth + i / 8]; + else + byte <<= 1; + + if (byte & 0x80) + HW::drawPixel(x+i, y+j, color); + else + HW::drawPixel(x+i, y+j, bg); + } + } +} + +// Draw XBitMap Files (*.xbm), exported from GIMP, +// Usage: Export from GIMP to *.xbm, rename *.xbm to *.c and open in editor. +// C Array can be directly used with this function +template +void PDQ_GFX::drawXBitmap(coord_t x, coord_t y, const uint8_t *bitmap, coord_t w, coord_t h, color_t color) +{ + coord_t i, j, byteWidth = (w + 7) / 8; + uint8_t byte; + + for (j = 0; j < h; j++) + { + for (i = 0; i < w; i++) + { + if (i % 8 == 0) + byte = pgm_read_byte(bitmap + j * byteWidth + i / 8); + else + byte >>= 1; + + if (byte & 0x01) + HW::drawPixel(x+i, y+j, color); + } + } +} + +template +size_t PDQ_GFX::write(uint8_t c) +{ + // 'Classic' built-in font + if (!gfxFont) + { + if (c == '\n') + { + cursor_x = 0; + cursor_y += (coord_t)textsize*8; + } + else if (c != '\r') + { + HW::drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize); + cursor_x += textsize*6; + if (wrap && (cursor_x > (_width - textsize*6))) + { + cursor_x = 0; + cursor_y += textsize*8; + } + } + } + else + { + if(c == '\n') + { + cursor_x = bound_x1; + cursor_y += (coord_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } + else if (c != '\r') + { + uint8_t first = pgm_read_byte(&gfxFont->first); + if ((c >= first) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last))) + { + uint8_t c2 = c - pgm_read_byte(&gfxFont->first); + GFXglyph *glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c2]); + uint8_t w = pgm_read_byte(&glyph->width); + uint8_t h = pgm_read_byte(&glyph->height); + // Is there an associated bitmap? + if ((w > 0) && (h > 0)) + { + coord_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); // sic + if(wrap && ((cursor_x + textsize * (xo + w)) >= bound_x2)) + { + // Drawing character would go off right edge; wrap to new line + cursor_x = bound_x1; + cursor_y += (coord_t)textsize * + (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } + HW::drawCharGFX(cursor_x, cursor_y, c, textcolor, textbgcolor, textsize); + } + cursor_x += pgm_read_byte(&glyph->xAdvance) * (coord_t)textsize; + } + } + } + return 1; +} + +// Draw a character with built-in font +template +void PDQ_GFX::drawChar(coord_t x, coord_t y, unsigned char c, color_t color, color_t bg, uint8_t size) +{ + if ((x >= _width) || + (y >= _height) || + ((x + (6 * size) - 1) < 0) || + ((y + (8 * size) - 1) < 0)) + return; + + uint8_t is_opaque = (bg != color); + + if(!_cp437 && (c >= 176)) // Handle 'classic' charset behavior + c++; + + for (int8_t i=0; i<6; i++) + { + uint8_t line; + + if (i == 5) + line = 0x0; + else + line = pgm_read_byte(glcdfont+(c*5)+i); + + if (size == 1) + { + for (int8_t j = 0; j < 8; j++) + { + if (line & 0x1) + { + HW::drawPixel(x+i, y+j, color); + } + else if (is_opaque) + { + HW::drawPixel(x+i, y+j, bg); + } + line >>= 1; + } + } + else + { + for (int8_t j = 0; j < 8; j++) + { + if (line & 0x1) + { + HW::fillRect(x+(i*size), y+(j*size), size, size, color); + } + else if (is_opaque) + { + HW::fillRect(x+(i*size), y+(j*size), size, size, bg); + } + line >>= 1; + } + } + } +} + +// Draw a character with GFX font +template +void PDQ_GFX::drawCharGFX(coord_t x, coord_t y, unsigned char c, color_t color, color_t /*bg*/, uint8_t size) +{ + // Character is assumed previously filtered by write() to eliminate + // newlines, returns, non-printable characters, etc. Calling drawChar() + // directly with 'bad' characters of font may cause mayhem! + + c -= pgm_read_byte(&gfxFont->first); + GFXglyph *glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); + uint8_t *bitmap = (uint8_t *)pgm_read_pointer(&gfxFont->bitmap); + + uint16_t bo = pgm_read_word(&glyph->bitmapOffset); + uint8_t w = pgm_read_byte(&glyph->width); + uint8_t h = pgm_read_byte(&glyph->height); + // uint8_t xa = pgm_read_byte(&glyph->xAdvance); + int8_t xo = pgm_read_byte(&glyph->xOffset); + int8_t yo = pgm_read_byte(&glyph->yOffset); + + // Todo: Add character clipping here + + // NOTE: THERE IS NO 'BACKGROUND' COLOR OPTION ON CUSTOM FONTS. + // THIS IS ON PURPOSE AND BY DESIGN. The background color feature + // has typically been used with the 'classic' font to overwrite old + // screen contents with new data. This ONLY works because the + // characters are a uniform size; it's not a sensible thing to do with + // proportionally-spaced fonts with glyphs of varying sizes (and that + // may overlap). To replace previously-drawn text when using a custom + // font, use the getTextBounds() function to determine the smallest + // rectangle encompassing a string, erase the area with fillRect(), + // then draw new text. This WILL infortunately 'blink' the text, but + // is unavoidable. Drawing 'background' pixels will NOT fix this, + // only creates a new set of problems. Have an idea to work around + // this (a canvas object type for MCUs that can afford the RAM and + // displays supporting setAddrWindow() and pushColors()), but haven't + // implemented this yet. + + if (bo & 0x8000) { + // packed font + uint8_t bits_cnt = 0; + uint8_t bits; + uint8_t cnt,cnt2; + bo &= 0x7FFF; + coord_t _y = y+yo; + for (coord_t yy=0; yy= w) + { + cnt2 = w-xx; + } else { + cnt2 = cnt; + } + + if (bits & 0x8) + { + if (size == 1) + { + HW::drawFastHLine(_x, _y, cnt2, color); + _x += cnt2; + } + else + { + HW::fillRect(x+xo+xx*size, y+yo+yy*size, cnt2*size, size, color); + // _x not used if size > 1, so not need to increment it + } + } else { + _x += cnt2; + } + + cnt -= cnt2; + if (cnt == 0) { + bits >>= 4; + cnt = (bits & 0x7)+1; + bits_cnt--; + } + } + } + } else { + uint8_t bit = 0; + uint8_t bits = 0; + if (size == 1) + { + coord_t _y = y+yo; + for (coord_t yy=0; yy +void PDQ_GFX::setCursor(coord_t x, coord_t y) +{ + cursor_x = (int16_t)x; + cursor_y = (int16_t)y; +} + +template +void PDQ_GFX::setBound(coord_t x1, coord_t x2) +{ + bound_x1 = (int16_t)x1; + bound_x2 = (int16_t)x2; +} + +template +void PDQ_GFX::setTextSize(uint8_t s) +{ + textsize = (s > 0) ? s : 1; +} + +template +void PDQ_GFX::setTextColor(color_t c) +{ + // For 'transparent' background, we'll set the bg + // to the same as fg instead of using a flag + textcolor = c; + textbgcolor = c; +} + +template +void PDQ_GFX::setTextColor(color_t c, color_t b) +{ + textcolor = c; + textbgcolor = b; +} + +template +void PDQ_GFX::setTextWrap(boolean w) +{ + wrap = w; +} + +template +void PDQ_GFX::setRotation(uint8_t x) +{ + // Used by driver when it has no special support + rotation = x & 3; + switch(rotation) { + case 0: + case 2: + _width = WIDTH; + _height = HEIGHT; + break; + case 1: + case 3: + _width = HEIGHT; + _height = WIDTH; + break; + } +} + +template +void PDQ_GFX::cp437(boolean x) +{ + _cp437 = x; +} + +template +void PDQ_GFX::setFont(const GFXfont *f) +{ + if (f) // Font struct pointer passed in? + { + if (!gfxFont) // And no current font struct? + { + // Switching from classic to new font behavior. + // Move cursor pos down 6 pixels so it's on baseline. + cursor_y += 6; + } + } + else if (gfxFont) // NULL passed. Current font struct defined? + { + // Switching from new to classic font behavior. + // Move cursor pos up 6 pixels so it's at top-left of char. + cursor_y -= 6; + } + gfxFont = (GFXfont *)f; +} + +// Pass string and a cursor position, returns UL corner and W,H. +template +void PDQ_GFX::getTextBounds(const char *str, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi) +{ + uint8_t c; // Current character + + coord_t xs = x; + coord_t xe = xs+wi; + + *x1 = x; + *y1 = y; + *w = *h = 0; + + if (gfxFont) + { + GFXglyph *glyph; + uint8_t first = pgm_read_byte(&gfxFont->first); + uint8_t last = pgm_read_byte(&gfxFont->last); + uint8_t gw, gh, xa; + int8_t xo, yo; + int16_t minx = _width, miny = _height, maxx = -1, maxy = -1; + coord_t gx1, gy1, gx2, gy2; + coord_t ts = (coord_t)textsize, ya = (coord_t)textsize * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + + while((c = *str++)) + { + if (c != '\n') // Not a newline + { + if (c != '\r') // Not a carriage return, is normal char + { + if ((c >= first) && (c <= last)) // Char present in current font + { + c -= first; + glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); + gw = pgm_read_byte(&glyph->width); + gh = pgm_read_byte(&glyph->height); + xa = pgm_read_byte(&glyph->xAdvance); + xo = pgm_read_byte(&glyph->xOffset); + yo = pgm_read_byte(&glyph->yOffset); + if (wrap && ((x + (((int16_t)xo + gw) * ts)) >= xe)) // Line wrap + { + x = xs; // Reset x to 0 + y += ya; // Advance y by 1 line + } + gx1 = x + xo * ts; + gy1 = y + yo * ts; + gx2 = gx1 + gw * ts - 1; + gy2 = gy1 + gh * ts - 1; + if (gx1 < minx) + minx = gx1; + if (gy1 < miny) + miny = gy1; + if (gx2 > maxx) + maxx = gx2; + if (gy2 > maxy) + maxy = gy2; + x += xa * ts; + } + } // Carriage return = do nothing + } + else // Newline + { + x = xs; // Reset x + y += ya; // Advance y by 1 line + } + } + // End of string + *x1 = minx; + *y1 = miny; + if (maxx >= minx) + *w = maxx - minx + 1; + if (maxy >= miny) + *h = maxy - miny + 1; + + } + else // Default font + { + uint16_t lineWidth = 0, maxWidth = 0; // Width of current, all lines + + while((c = *str++)) + { + if (c != '\n') // Not a newline + { + if (c != '\r') // Not a carriage return, is normal char + { + lineWidth += textsize * 6; // Includes interchar x gap + if (wrap && (cursor_x > (_width - textsize*6))) + { + x = 0; + y += textsize*8; + + if (lineWidth > maxWidth) // Save widest line + maxWidth = lineWidth; + lineWidth = textsize * 6; // First char on new line + } + } // Carriage return = do nothing + } + else // Newline + { + x = 0; // Reset x to 0 + y += textsize * 8; // Advance y by 1 line + if (lineWidth > maxWidth) // Save widest line + maxWidth = lineWidth; + lineWidth = 0; // Reset lineWidth for new line + } + } + // End of string + if (lineWidth) // Add height of last (or only) line + y += textsize * 8; + *w = maxWidth - 1; // Don't include last interchar x gap + *h = y - *y1; + + } // End classic vs custom font +} + +// Same as above, but for PROGMEM strings +template +void PDQ_GFX::getTextBounds(const __FlashStringHelper *str, coord_t x, coord_t y, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h, coord_t wi) +{ + uint8_t *s = (uint8_t *)str; + uint8_t c; + coord_t xs = x; + coord_t xe = xs+wi; + + *x1 = x; + *y1 = y; + *w = *h = 0; + + if (gfxFont) + { + GFXglyph *glyph; + uint8_t first = pgm_read_byte(&gfxFont->first); + uint8_t last = pgm_read_byte(&gfxFont->last); + uint8_t gw, gh, xa; + int8_t xo, yo; + int16_t minx = _width, miny = _height, maxx = -1, maxy = -1; + coord_t gx1, gy1, gx2, gy2; + coord_t ts = (coord_t)textsize; + coord_t ya = ts * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + + while((c = pgm_read_byte(s++))) + { + if (c != '\n') // Not a newline + { + if (c != '\r') // Not a carriage return, is normal char + { + if ((c >= first) && (c <= last)) // Char present in current font + { + c -= first; + glyph = &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); + gw = pgm_read_byte(&glyph->width); + gh = pgm_read_byte(&glyph->height); + xa = pgm_read_byte(&glyph->xAdvance); + xo = pgm_read_byte(&glyph->xOffset); + yo = pgm_read_byte(&glyph->yOffset); + if (wrap && ((x + (((int16_t)xo + gw) * ts)) >= xe)) // Line wrap + { + x = xs; // Reset x to 0 + y += ya; // Advance y by 1 line + } + gx1 = x + xo * ts; + gy1 = y + yo * ts; + gx2 = gx1 + gw * ts - 1; + gy2 = gy1 + gh * ts - 1; + if (gx1 < minx) + minx = gx1; + if (gy1 < miny) + miny = gy1; + if (gx2 > maxx) + maxx = gx2; + if (gy2 > maxy) + maxy = gy2; + x += xa * ts; + } + } // Carriage return = do nothing + } + else // Newline + { + x = xs; // Reset x + y += ya; // Advance y by 1 line + } + } + // End of string + *x1 = minx; + *y1 = miny; + if (maxx >= minx) + *w = maxx - minx + 1; + if (maxy >= miny) + *h = maxy - miny + 1; + + } + else // Default font + { + uint16_t lineWidth = 0, maxWidth = 0; // Width of current, all lines + + while((c = pgm_read_byte(s++))) + { + if (c != '\n') // Not a newline + { + if (c != '\r') // Not a carriage return, is normal char + { + if (wrap && ((x + textsize * 6) >= _width)) + { + x = 0; // Reset x to 0 + y += textsize * 8; // Advance y by 1 line + if (lineWidth > maxWidth) // Save widest line + maxWidth = lineWidth; + lineWidth = textsize * 6; // First char on new line + } + else // No line wrap, just keep incrementing X + { + lineWidth += textsize * 6; // Includes interchar x gap + } + } // Carriage return = do nothing + } + else // Newline + { + x = 0; // Reset x to 0 + y += textsize * 8; // Advance y by 1 line + if (lineWidth > maxWidth) // Save widest line + maxWidth = lineWidth; + lineWidth = 0; // Reset lineWidth for new line + } + } + // End of string + if (lineWidth) // Add height of last (or only) line + y += textsize * 8; + *w = maxWidth - 1; // Don't include last interchar x gap + *h = y - *y1; + + } // End classic vs custom font +} + +template +void PDQ_GFX::invertDisplay(boolean i) +{ + // Used by driver when it has no special support + // Do nothing, must be supported by driver +} + +/***************************************************************************/ +// code for the GFX button UI element + +template +PDQ_GFX_Button_::PDQ_GFX_Button_() +{ + _gfx = 0; +} + +template +void PDQ_GFX_Button_::initButton(PDQ_GFX *gfx, coord_t x, coord_t y, coord_t w, coord_t h, color_t outline, color_t fill, color_t textcolor, const char *label, uint8_t textsize) +{ + _gfx = gfx; + _x = x; + _y = y; + _w = w; + _h = h; + _outlinecolor = outline; + _fillcolor = fill; + _textcolor = textcolor; + _textsize = textsize; + strncpy(_label, label, 9); + _label[9] = 0; +} + +template +void PDQ_GFX_Button_::drawButton(boolean inverted) +{ + uint16_t fill, outline, text; + + if (!inverted) + { + fill = _fillcolor; + outline = _outlinecolor; + text = _textcolor; + } + else + { + fill = _textcolor; + outline = _outlinecolor; + text = _fillcolor; + } + + _gfx->fillRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, fill); + _gfx->drawRoundRect(_x - (_w/2), _y - (_h/2), _w, _h, min(_w,_h)/4, outline); + + _gfx->setCursor(_x - strlen(_label)*3*_textsize, _y-4*_textsize); + _gfx->setTextColor(text); + _gfx->setTextSize(_textsize); + _gfx->print(_label); +} + +template +boolean PDQ_GFX_Button_::contains(coord_t x, coord_t y) +{ + if ((x < (_x - _w/2)) || (x > (_x + _w/2))) + return false; + if ((y < (_y - _h/2)) || (y > (_y + _h/2))) + return false; + return true; +} + +template +void PDQ_GFX_Button_::press(boolean p) +{ + laststate = currstate; + currstate = p; +} + +template +boolean PDQ_GFX_Button_::isPressed() +{ + return currstate; +} + +template +boolean PDQ_GFX_Button_::justPressed() +{ + return (currstate && !laststate); +} + +template +boolean PDQ_GFX_Button_::justReleased() +{ + return (!currstate && laststate); +} + +#endif // _PDQ_GFX_H diff --git a/PDQ_MinLib/PDQ_ILI9341.h b/PDQ_MinLib/PDQ_ILI9341.h new file mode 100644 index 0000000..8cc4051 --- /dev/null +++ b/PDQ_MinLib/PDQ_ILI9341.h @@ -0,0 +1,1064 @@ +// This is the PDQ re-mixed version of Adafruit's library +// here is the original copyright notice: + +/*************************************************** + This is an Arduino Library for the Adafruit 2.2" SPI display. + This library works with the Adafruit 2.2" TFT Breakout w/SD card + ----> http://www.adafruit.com/products/1480 + + Check out the links above for our tutorials and wiring diagrams + These displays use SPI to communicate, 4 or 5 pins are required to + interface (RST is optional) + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ + +//=============================================================== +// This PDQ optimized version is by Xark +// +// Inspiration from Paul Stoffregen and the Teensy 3.1 community. +// +// GOALS: +// 1) Maintain "sketch" compatibility with original Adafruit libraries. +// 2) Be as much faster as is reasonably possible honoring goal 1. :-) +// 3) Be at least as small as Adafruit libraries. +// +// I believe all three of these have largely been achieved: +// 1) Near full compatibility. Only minor initialization changes in original sketch. +// 2) Between ~2.5 and ~12 times faster (fillRect ~2.5x, drawLine ~12x). +// An average of ~4x faster over entire "graphictest.ino" benchmark. +// +// Even if this library is faster, it was based on the Adafruit original. +// Adafruit deserves your support for making their library open-source (and +// for having some nice LCD modules and all kinds of other great parts too). +// Consider giving them your support if possible! + +#if !defined(_PDQ_ILI9341H_) +#define _PDQ_ILI9341H_ + +#include "Arduino.h" +#include "Print.h" + +#include "PDQ_GFX.h" + +#include + +#if !defined(ILI9341_CS_PIN) || !defined(ILI9341_DC_PIN) +#error Oops! You need to #include "PDQ_ILI9341_config.h" (modified with your pin configuration and options) from your sketch before #include "PDQ_ILI9341.h". +#endif + +#include "PDQ_FastPin.h" + +#if !defined(__AVR_ATtiny85__) && !defined(__AVR_ATtiny45__) +#define INLINE inline +#define INLINE_OPT __attribute__((always_inline)) +#else +#define INLINE +#define INLINE_OPT +#endif + +// Color definitions +enum +{ + ILI9341_BLACK = 0x0000, + ILI9341_BLUE = 0x001F, + ILI9341_RED = 0xF800, + ILI9341_GREEN = 0x07E0, + ILI9341_CYAN = 0x07FF, + ILI9341_MAGENTA = 0xF81F, + ILI9341_YELLOW = 0xFFE0, + ILI9341_WHITE = 0xFFFF, +}; + +class PDQ_ILI9341 : public PDQ_GFX +{ + public: + // ILI9341 commands + // For datasheet see https://www.adafruit.com/products/1480 + enum + { + ILI9341_NOP = 0x00, + ILI9341_SWRESET = 0x01, + ILI9341_RDDID = 0x04, + ILI9341_RDDST = 0x09, + + ILI9341_SLPIN = 0x10, + ILI9341_SLPOUT = 0x11, + ILI9341_PTLON = 0x12, + ILI9341_NORON = 0x13, + + ILI9341_RDMODE = 0x0A, + ILI9341_RDMADCTL = 0x0B, + ILI9341_RDPIXFMT = 0x0C, + ILI9341_RDIMGFMT = 0x0A, + ILI9341_RDSELFDIAG = 0x0F, + + ILI9341_INVOFF = 0x20, + ILI9341_INVON = 0x21, + ILI9341_GAMMASET = 0x26, + ILI9341_DISPOFF = 0x28, + ILI9341_DISPON = 0x29, + + ILI9341_CASET = 0x2A, + ILI9341_PASET = 0x2B, + ILI9341_RAMWR = 0x2C, + ILI9341_RAMRD = 0x2E, + + ILI9341_PTLAR = 0x30, + ILI9341_MADCTL = 0x36, + ILI9341_PIXFMT = 0x3A, + + ILI9341_FRMCTR1 = 0xB1, + ILI9341_FRMCTR2 = 0xB2, + ILI9341_FRMCTR3 = 0xB3, + ILI9341_INVCTR = 0xB4, + ILI9341_DFUNCTR = 0xB6, + + ILI9341_PWCTR1 = 0xC0, + ILI9341_PWCTR2 = 0xC1, + ILI9341_PWCTR3 = 0xC2, + ILI9341_PWCTR4 = 0xC3, + ILI9341_PWCTR5 = 0xC4, + ILI9341_VMCTR1 = 0xC5, + ILI9341_VMCTR2 = 0xC7, + + ILI9341_RDID1 = 0xDA, + ILI9341_RDID2 = 0xDB, + ILI9341_RDID3 = 0xDC, + ILI9341_RDID4 = 0xDD, + + ILI9341_GMCTRP1 = 0xE0, + ILI9341_GMCTRN1 = 0xE1, + + // ILI9341_PWCTR6 = 0xFC, + }; + + // some other misc. constants + enum + { + // screen dimensions + ILI9341_TFTWIDTH = 240, + ILI9341_TFTHEIGHT = 320, + + // MADCTL bits + ILI9341_MADCTL_MH = 0x04, // bit 2 = 0 for refresh left -> right, 1 for refresh right -> left + ILI9341_MADCTL_RGB = 0x00, // bit 3 = 0 for RGB color order + ILI9341_MADCTL_BGR = 0x08, // bit 3 = 1 for BGR color order + ILI9341_MADCTL_ML = 0x10, // bit 4 = 0 for refresh top -> bottom, 1 for bottom -> top + ILI9341_MADCTL_MV = 0x20, // bit 5 = 0 for column, row order (portrait), 1 for row, column order (landscape) + ILI9341_MADCTL_MX = 0x40, // bit 6 = 0 for left -> right, 1 for right -> left order + ILI9341_MADCTL_MY = 0x80, // bit 7 = 0 for top -> bottom, 1 for bottom -> top + + // delay indicator bit for commandList() + DELAY = 0x80 + }; + + // higher-level routines + PDQ_ILI9341(); + static void inline begin(void); + static void setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1); + static void pushColor(uint16_t color); + static void pushColor(uint16_t color, int cnt); + + // Pass 8-bit (each) R,G,B, get back 16-bit packed color + static INLINE uint16_t color565(uint8_t r, uint8_t g, uint8_t b) + { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + static INLINE uint16_t Color565(uint8_t r, uint8_t g, uint8_t b) // older inconsistent name for compatibility + { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + + // required driver primitive methods (all except drawPixel can call generic version in PDQ_GFX with "_" postfix). + static void drawPixel(int x, int y, uint16_t color); + static void drawFastVLine(int x, int y, int h, uint16_t color); + static void drawFastHLine(int x, int y, int w, uint16_t color); + static void setRotation(uint8_t r); + static void invertDisplay(boolean i); + + static inline void fillScreen(uint16_t color) __attribute__((always_inline)) + { + fillScreen_(color); // call generic version + } + + static void drawLine(int x0, int y0, int x1, int y1, uint16_t color); + static void fillRect(int x, int y, int w, int h, uint16_t color); + + // === lower-level internal routines ========= + static void commandList(const uint8_t *addr); + + // NOTE: Make sure each spi_begin() is matched with a single spi_end() (and don't call either twice) + // set CS back to low (LCD selected) + static inline void spi_begin() __attribute__((always_inline)) + { +#if ILI9341_SAVE_SPCR && defined(AVR_HARDWARE_SPI) + swapValue(save_SPCR, SPCR); // swap initial/current SPCR settings +#endif + FastPin::lo(); // CS <= LOW (selected) + } + + // NOTE: Make sure each spi_begin() is matched with a single spi_end() (and don't call either twice) + // reset CS back to high (LCD unselected) + static inline void spi_end() __attribute__((always_inline)) + { + FastPin::hi(); // CS <= HIGH (deselected) +#if ILI9341_SAVE_SPCR && defined(AVR_HARDWARE_SPI) + swapValue(SPCR, save_SPCR); // swap current/initial SPCR settings +#endif + } + +#if defined(AVR_HARDWARE_SPI) + // 10 cycle delay (including "call") + static void delay10() __attribute__((noinline)) __attribute__((naked)) + { + __asm__ __volatile__ + ( + // +4 (call to get here) +#if !defined(__AVR_HAVE_RAMPD__) + " adiw r24,0\n" // +2 (2-cycle NOP) +#else + " nop\n" // +1 (1-cycle NOP) +#endif + " ret\n" // +4 (or +5 on >64KB AVR with RAMPD reg) + // = 10 cycles + : : : + ); + } + + // 13 cycle delay (including "call") + static void delay13() __attribute__((noinline)) __attribute__((naked)) + { + __asm__ __volatile__ + ( + // +4 (call to get here) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) +#if !defined(__AVR_HAVE_RAMPD__) + " nop\n" // +1 (1-cycle NOP) +#endif + " ret\n" // +4 (or +5 on >64KB AVR with RAMPD reg) + // = 13 cycles + : : : + ); + } + + // 15 cycle delay (including "call") + static void delay15() __attribute__((noinline)) __attribute__((naked)) + { + __asm__ __volatile__ + ( + // +4 (call to get here) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) +#if !defined(__AVR_HAVE_RAMPD__) + " nop\n" // +1 (1-cycle NOP) +#endif + " ret\n" // +4 (or +5 on >64KB AVR with RAMPD reg) + // = 15 cycles + : : : + ); + } + + // 17 cycle delay (including "call") + static void delay17() __attribute__((noinline)) __attribute__((naked)) + { + __asm__ __volatile__ + ( + // +4 (call to get here) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) + " adiw r24,0\n" // +2 (2-cycle NOP) +#if !defined(__AVR_HAVE_RAMPD__) + " nop\n" // +1 (2-cycle NOP) +#endif + " ret\n" // +4 (or +5 on >64KB AVR with RAMPD reg) + // = 17 cycles + : : : + ); + } + + // normal SPI write with minimal hand-tuned delay (assuming max DIV2 SPI rate) + static INLINE void spiWrite(uint8_t data) INLINE_OPT + { + SPDR = data; + __asm__ __volatile__ + ( + " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) + : : : + ); + } + + // special SPI write with minimal hand-tuned delay (assuming max DIV2 SPI rate) - minus 2 cycles for RS (etc.) change + static INLINE void spiWrite_preCmd(uint8_t data) INLINE_OPT + { + SPDR = data; + + __asm__ __volatile__ + ( + " call _ZN11PDQ_ILI93417delay15Ev\n" // call mangled delay15 (compiler would needlessly save/restore regs) + : : : + ); + } + + // SPI 16-bit write with minimal hand-tuned delay (assuming max DIV2 SPI rate) + static INLINE void spiWrite16(uint16_t data) INLINE_OPT + { + uint8_t temp; + __asm__ __volatile__ + ( + " out %[spi],%[hi]\n" // write SPI data (18 cycles until next write) + " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) + " out %[spi],%[lo]\n" // write SPI data (18 cycles until next write) + " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) + + : [temp] "=d" (temp) + : [spi] "i" (_SFR_IO_ADDR(SPDR)), [lo] "r" ((uint8_t)data), [hi] "r" ((uint8_t)(data>>8)) + : + ); + } + + // SPI 16-bit write with minimal hand-tuned delay (assuming max DIV2 SPI rate) minus 2 cycles + static INLINE void spiWrite16_preCmd(uint16_t data) INLINE_OPT + { + uint8_t temp; + __asm__ __volatile__ + ( + " out %[spi],%[hi]\n" // write SPI data (18 cycles until next write) + " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) + " out %[spi],%[lo]\n" // write SPI data (18 cycles until next write) + " call _ZN11PDQ_ILI93417delay15Ev\n" // call mangled delay15 (compiler would needlessly save/restore regs) + + : [temp] "=d" (temp) + : [spi] "i" (_SFR_IO_ADDR(SPDR)), [lo] "r" ((uint8_t)data), [hi] "r" ((uint8_t)(data>>8)) + : + ); + } + + // SPI 16-bit write with minimal hand-tuned delay (assuming max DIV2 SPI rate) minus 4 cycles + static INLINE void spiWrite16_lineDraw(uint16_t data) INLINE_OPT + { + uint8_t temp; + __asm__ __volatile__ + ( + " out %[spi],%[hi]\n" // write SPI data (18 cycles until next write) + " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) + " out %[spi],%[lo]\n" // write SPI data (18 cycles until next write) + + : [temp] "=d" (temp) + : [spi] "i" (_SFR_IO_ADDR(SPDR)), [lo] "r" ((uint8_t)data), [hi] "r" ((uint8_t)(data>>8)) + : + ); + } + + // normal SPI write with minimal hand-tuned delay (assuming max DIV2 SPI rate) + static INLINE void spiWrite16(uint16_t data, int count) INLINE_OPT + { + uint8_t temp; + __asm__ __volatile__ + ( + " sbiw %[count],0\n" // test count + " brmi 4f\n" // if < 0 then done + " breq 4f\n" // if == 0 then done + "1: out %[spi],%[hi]\n" // write SPI data (18 cycles until next write) + " call _ZN11PDQ_ILI93417delay17Ev\n" // call mangled delay17 (compiler would needlessly save/restore regs) + " out %[spi],%[lo]\n" // write SPI data (18 cycles until next write) + " call _ZN11PDQ_ILI93417delay13Ev\n" // call mangled delay13 (compiler would needlessly save/restore regs) + " sbiw %[count],1\n" // +2 decrement count + " brne 1b\n" // +2/1 if != 0 then loop + // = 13 + 2 + 2 (17 cycles) + "4:\n" + + : [temp] "=d" (temp), [count] "+w" (count) + : [spi] "i" (_SFR_IO_ADDR(SPDR)), [lo] "r" ((uint8_t)data), [hi] "r" ((uint8_t)(data>>8)) + : + ); + } + +#else // bit-bang +#if defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny45__) + // USI hardware assisted + static void spiWrite(uint8_t data) __attribute__((noinline)) + { + USIDR = data; + __asm__ __volatile__ + ( + " out %[spi],%[clkp0]\n" // MSB + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" // LSB + " out %[spi],%[clkp1]\n" + : + : [spi] "i" (_SFR_IO_ADDR(USICR)), [clkp0] "a" ((uint8_t)((1<>8; + __asm__ __volatile__ + ( + " out %[spi],%[clkp0]\n" // MSB + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" + " out %[spi],%[clkp1]\n" + " out %[spi],%[clkp0]\n" // LSB + " out %[spi],%[clkp1]\n" + : + : [spi] "i" (_SFR_IO_ADDR(USICR)), [clkp0] "a" ((uint8_t)((1<>= 1) + { + if (data & bit) + FastPin::hi(); + else + FastPin::lo(); + + FastPin::hi(); + FastPin::lo(); + } + } + static void spiWrite16(uint16_t data) __attribute__((noinline)) + { + spiWrite(data >> 8); + spiWrite(data & 0xff); + } +#endif + static INLINE void spiWrite_preCmd(uint8_t data) INLINE_OPT + { + spiWrite(data); + } + static INLINE void spiWrite16_preCmd(uint16_t data) INLINE_OPT + { + spiWrite16(data); + } + static INLINE void spiWrite16_lineDraw(uint16_t data) INLINE_OPT + { + spiWrite16(data); + } + static INLINE void spiWrite16(uint16_t data, int count) INLINE_OPT + { + while (count-- > 0) + spiWrite16(data); + } + static inline void delay10() { } + static inline void delay13() { } + static inline void delay15() { } + static inline void delay17() { } +#endif + + // write SPI byte with RS (aka D/C) pin set low to indicate a command byte (and then reset back to high when done) + static INLINE void writeCommand(uint8_t data) INLINE_OPT + { + FastPin::lo(); // RS <= LOW indicate command byte + spiWrite_preCmd(data); + FastPin::hi(); // RS <= HIGH indicate data byte (always assumed left in data mode) + } + + // write SPI byte with RS assumed low indicating a data byte + static inline void writeData(uint8_t data) __attribute__((always_inline)) + { + spiWrite(data); + } + + // internal version that does not spi_begin()/spi_end() + static INLINE void setAddrWindow_(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) INLINE_OPT + { + writeCommand(ILI9341_CASET); // column address set + spiWrite16(x0); // XSTART + spiWrite16_preCmd(x1); // XEND + writeCommand(ILI9341_PASET); // row address set + spiWrite16(y0); // YSTART + spiWrite16_preCmd(y1); // YEND + writeCommand(ILI9341_RAMWR); // write to RAM + } + +#if ILI9341_SAVE_SPCR && defined(AVR_HARDWARE_SPI) + static volatile uint8_t save_SPCR; // initial SPCR value/saved SPCR value (swapped in spi_begin/spi_end) +#endif +}; + +typedef PDQ_GFX_Button_ PDQ_GFX_Button; + +/*************************************************** + This is an Arduino Library for the Adafruit 2.2" SPI display. + This library works with the Adafruit 2.2" TFT Breakout w/SD card + ----> http://www.adafruit.com/products/1480 + + Check out the links above for our tutorials and wiring diagrams + These displays use SPI to communicate, 4 or 5 pins are required to + interface (RST is optional) + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ + +#if ILI9341_SAVE_SPCR && defined(AVR_HARDWARE_SPI) +// static data needed by base class +volatile uint8_t PDQ_ILI9341::save_SPCR; +#endif + +// Constructor when using hardware SPI. +PDQ_ILI9341::PDQ_ILI9341() : PDQ_GFX(ILI9341_TFTWIDTH, ILI9341_TFTHEIGHT) +{ +#if defined(AVR_HARDWARE_SPI) + // must reference these functions from C++ or they will be stripped by linker (called from inline asm) + delay10(); + delay13(); + delay15(); + delay17(); +#endif +} + +// Companion code to the above tables. Reads and issues +// a series of LCD commands stored in PROGMEM byte array. +void PDQ_ILI9341::commandList(const uint8_t *addr) +{ + uint8_t numCommands, numArgs; + uint16_t ms; + + numCommands = pgm_read_byte(addr++); // Number of commands to follow + while (numCommands--) // For each command... + { + writeCommand(pgm_read_byte(addr++)); // Read, issue command + numArgs = pgm_read_byte(addr++); // Number of args to follow + ms = numArgs & DELAY; // If hibit set, delay follows args + numArgs &= ~DELAY; // Mask out delay bit + while (numArgs--) // For each argument... + { + writeData(pgm_read_byte(addr++)); // Read, issue argument + } + + if (ms) + { + ms = pgm_read_byte(addr++); // Read post-command delay time (ms) + if(ms == 255) + ms = 500; // If 255, delay for 500 ms + delay(ms); + } + } +} + +void PDQ_ILI9341::begin(void) +{ + + // set CS and RS pin directions to output + FastPin::setOutput(); + FastPin::setOutput(); +#if !defined(AVR_HARDWARE_SPI) + #if defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) + USICR = (0<::setInput(); + #endif + FastPin::setOutput(); + FastPin::setOutput(); + FastPin::lo(); + FastPin::lo(); +#endif + + FastPin::hi(); // CS <= HIGH (deselected, so no spurious data) + FastPin::hi(); // RS <= HIGH (default data byte) + +#if defined(AVR_HARDWARE_SPI) + #if ILI9341_SAVE_SPCR + uint8_t oldSPCR = SPCR; // save initial SPCR settings + #endif + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setClockDivider(SPI_CLOCK_DIV2); // 8 MHz (full! speed!) [1 byte every 18 cycles] +#endif + +#if ILI9341_SAVE_SPCR && defined(AVR_HARDWARE_SPI) + save_SPCR = SPCR; // save SPCR settings + SPCR = oldSPCR; // restore previous SPCR settings (spi_begin/spi_end will switch between the two) +#endif + spi_begin(); + + // Initialization commands for ILI9341 screens + static const uint8_t ILI9341_cmds[] PROGMEM = + { + 22, + ILI9341_SWRESET, DELAY, // 1 + 5, + 0xEF, 3, // 2 + 0x03, 0x80, 0x02, + 0xCF, 3, // 3 + 0x00, 0xC1, 0x30, + 0xED, 4, // 4 + 0x64, 0x03, 0x12, 0x81, + 0xE8, 3, // 5 + 0x85, 0x00, 0x78, + 0xCB, 5, // 6 + 0x39, 0x2C, 0x00, 0x34, 0x02, + 0xF7, 1, // 7 + 0x20, + 0xEA, 2, // 8 + 0x00, 0x00, + ILI9341_PWCTR1, 1, // 9 power control + 0x23, // VRH[5:0] + ILI9341_PWCTR2, 1, // 10 power control + 0x10, // SAP[2:0];BT[3:0] + ILI9341_VMCTR1, 2, // 11 VCM control + 0x3e, 0x28, + ILI9341_VMCTR2, 1, // 12 VCM control2 + 0x86, // -- + ILI9341_MADCTL, 1, // 13 + (ILI9341_MADCTL_MX | ILI9341_MADCTL_BGR), + ILI9341_PIXFMT, 1, // 14 + 0x55, + ILI9341_FRMCTR1, 2, // 15 + 0x00, 0x18, + ILI9341_DFUNCTR, 3, // 16 + 0x08, 0x82, 0x27, + 0xF2, 1, // 17 3Gamma Function Disable + 0x00, + ILI9341_GAMMASET, 1, // 18 Gamma curve selected + 0x01, + ILI9341_GMCTRP1, 15, // 19 Set Gamma + 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, + ILI9341_GMCTRN1, 15, // 20 + 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, + ILI9341_SLPOUT, DELAY, // 21 + 120, + ILI9341_DISPON, 0, // 22 + }; + + commandList(ILI9341_cmds); + + spi_end(); +} + +void PDQ_ILI9341::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) +{ + spi_begin(); + + setAddrWindow_(x0, y0, x1, y1); + + spi_end(); +} + +void PDQ_ILI9341::pushColor(uint16_t color) +{ + spi_begin(); + + spiWrite16_preCmd(color); + + spi_end(); +} + +void PDQ_ILI9341::pushColor(uint16_t color, int count) +{ + spi_begin(); + + spiWrite16(color, count); + + spi_end(); +} + +void PDQ_ILI9341::drawPixel(int x, int y, uint16_t color) +{ + if ((x < 0) ||(x >= _width) || (y < 0) || (y >= _height)) + return; + + spi_begin(); + + setAddrWindow_(x, y, x, y); + + spiWrite16_preCmd(color); + + spi_end(); +} + +void PDQ_ILI9341::drawFastVLine(int x, int y, int h, uint16_t color) +{ + // clipping + if ((x < 0) || (x >= _width) || (y >= _height)) + return; + + if (y < 0) + { + h += y; + y = 0; + } + + int y1 = y+h; + + if (y1 < 0) + return; + + if (y1 > _height) + h = _height-y; + + spi_begin(); + + setAddrWindow_(x, y, x, _height); + spiWrite16(color, h); + + spi_end(); +} + + +void PDQ_ILI9341::drawFastHLine(int x, int y, int w, uint16_t color) +{ + // clipping + if ((x >= _width) || (y < 0) || (y >= _height)) + return; + + if (x < 0) + { + w += x; + x = 0; + } + + int x1 = x+w; + + if (x1 < 0) + return; + + if (x1 > _width) + w = _width-w; + + spi_begin(); + + setAddrWindow_(x, y, _width, y); + spiWrite16(color, w); + + spi_end(); +} + +void PDQ_ILI9341::fillRect(int x, int y, int w, int h, uint16_t color) +{ + // rudimentary clipping (drawChar w/big text requires this) + if ((x >= _width) || (y >= _height)) + return; + if (x < 0) + { + w += x; + x = 0; + } + if (y < 0) + { + h += y; + y = 0; + } + if ((x + w) > _width) + w = _width - x; + if ((y + h) > _height) + h = _height - y; + + spi_begin(); + + setAddrWindow_(x, y, x+w-1, _height); + + for (; h > 0; h--) + { + spiWrite16(color, w); + } + + spi_end(); +} + +// Bresenham's algorithm - thx Wikipedia +void PDQ_ILI9341::drawLine(int x0, int y0, int x1, int y1, uint16_t color) +{ +#if 0 && defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny45__) + drawLine_(x0, y0, x1, y1, color); +#else + int8_t steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) + { + swapValue(x0, y0); + swapValue(x1, y1); + } + + if (x0 > x1) + { + swapValue(x0, x1); + swapValue(y0, y1); + } + + if (x1 < 0) + return; + + int dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int err = dx / 2; + int8_t ystep; + + if (y0 < y1) + { + ystep = 1; + } + else + { + ystep = -1; + } + + uint8_t setaddr = 1; + +#if 0 && defined(__AVR_ATtiny85__) && !defined(__AVR_ATtiny45__) + int end = steep ? _height-1 : _width-1; + if (x1 > end) + x1 = end; + + for (; x0 <= x1; x0++) + { + if ((x0 >= 0) && (y0 >= 0) && (y0 <= end)) + break; + + err -= dy; + if (err < 0) + { + err += dx; + y0 += ystep; + } + } + + if (x0 > x1) + return; + + spi_begin(); + + for (; x0 <= x1; x0++) + { + if (setaddr) + { + if (steep) + setAddrWindow_(y0, x0, y0, end+1); + else + setAddrWindow_(x0, y0, end+1, y0); + setaddr = 0; + } + spiWrite16_lineDraw(color); + err -= dy; + if (err < 0) + { + y0 += ystep; + if ((y0 < 0) || (y0 > end)) + break; + err += dx; + setaddr = 1; + } + } +#else + if (steep) // y increments every iteration (y0 is x-axis, and x0 is y-axis) + { + if (x1 >= _height) + x1 = _height - 1; + + for (; x0 <= x1; x0++) + { + if ((x0 >= 0) && (y0 >= 0) && (y0 < _width)) + break; + + err -= dy; + if (err < 0) + { + err += dx; + y0 += ystep; + } + } + + if (x0 > x1) + return; + + spi_begin(); + + for (; x0 <= x1; x0++) + { + if (setaddr) + { + setAddrWindow_(y0, x0, y0, _height); + setaddr = 0; + } + spiWrite16_lineDraw(color); + err -= dy; + if (err < 0) + { + y0 += ystep; + if ((y0 < 0) || (y0 >= _width)) + break; + err += dx; + setaddr = 1; + } +#if defined(AVR_HARDWARE_SPI) + else + { + __asm__ __volatile__ + ( + " call _ZN11PDQ_ILI93417delay10Ev\n" + : : : + ); + } +#endif + } + } + else // x increments every iteration (x0 is x-axis, and y0 is y-axis) + { + if (x1 >= _width) + x1 = _width - 1; + + for (; x0 <= x1; x0++) + { + if ((x0 >= 0) && (y0 >= 0) && (y0 < _height)) + break; + + err -= dy; + if (err < 0) + { + err += dx; + y0 += ystep; + } + } + + if (x0 > x1) + return; + + spi_begin(); + + for (; x0 <= x1; x0++) + { + if (setaddr) + { + setAddrWindow_(x0, y0, _width, y0); + setaddr = 0; + } + spiWrite16_lineDraw(color); + err -= dy; + if (err < 0) + { + y0 += ystep; + if ((y0 < 0) || (y0 >= _height)) + break; + err += dx; + setaddr = 1; + } +#if defined(AVR_HARDWARE_SPI) + else + { + __asm__ __volatile__ + ( + " call _ZN11PDQ_ILI93417delay10Ev\n" + : : : + ); + } +#endif + } + } +#endif + + spi_end(); +#endif +} + +void PDQ_ILI9341::setRotation(uint8_t m) +{ + rotation = (m & 3); // can't be higher than 3 + + spi_begin(); + + writeCommand(ILI9341_MADCTL); + + switch (rotation) + { + default: + case 0: + writeData(ILI9341_MADCTL_MX | ILI9341_MADCTL_BGR); + _width = ILI9341_TFTWIDTH; + _height = ILI9341_TFTHEIGHT; + break; + case 1: + writeData(ILI9341_MADCTL_MV | ILI9341_MADCTL_BGR); + _width = ILI9341_TFTHEIGHT; + _height = ILI9341_TFTWIDTH; + break; + case 2: + writeData(ILI9341_MADCTL_MY | ILI9341_MADCTL_BGR); + _width = ILI9341_TFTWIDTH; + _height = ILI9341_TFTHEIGHT; + break; + case 3: + writeData(ILI9341_MADCTL_MV | ILI9341_MADCTL_MY | ILI9341_MADCTL_MX | ILI9341_MADCTL_BGR); + _width = ILI9341_TFTHEIGHT; + _height = ILI9341_TFTWIDTH; + break; + } + + spi_end(); +} + +void PDQ_ILI9341::invertDisplay(boolean i) +{ + spi_begin(); + + writeCommand(i ? ILI9341_INVON : ILI9341_INVOFF); + + spi_end(); +} + +#endif // !defined(_PDQ_ILI9341H_) diff --git a/PDQ_MinLib/Picopixel.h b/PDQ_MinLib/Picopixel.h new file mode 100644 index 0000000..c1a0092 --- /dev/null +++ b/PDQ_MinLib/Picopixel.h @@ -0,0 +1,120 @@ +// Picopixel by Sebastian Weber. A tiny font +// with all characters within a 6 pixel height. + +const uint8_t PicopixelBitmaps[] PROGMEM = { + 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, + 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, + 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, + 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, + 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, + 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, + 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, + 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, + 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, + 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, + 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, + 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, + 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, + 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, + 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A}; + +const GFXglyph PicopixelGlyphs[] PROGMEM = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 2, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 3, 6, 4, 0, -4}, // 0x24 '$' + {9, 3, 5, 4, 0, -4}, // 0x25 '%' + {11, 4, 5, 5, 0, -4}, // 0x26 '&' + {14, 1, 2, 2, 0, -4}, // 0x27 ''' + {15, 2, 5, 3, 0, -4}, // 0x28 '(' + {17, 2, 5, 3, 0, -4}, // 0x29 ')' + {19, 3, 3, 4, 0, -3}, // 0x2A '*' + {21, 3, 3, 4, 0, -3}, // 0x2B '+' + {23, 2, 2, 3, 0, 0}, // 0x2C ',' + {24, 3, 1, 4, 0, -2}, // 0x2D '-' + {25, 1, 1, 2, 0, 0}, // 0x2E '.' + {26, 3, 5, 4, 0, -4}, // 0x2F '/' + {28, 3, 5, 4, 0, -4}, // 0x30 '0' + {30, 2, 5, 3, 0, -4}, // 0x31 '1' + {32, 3, 5, 4, 0, -4}, // 0x32 '2' + {34, 3, 5, 4, 0, -4}, // 0x33 '3' + {36, 3, 5, 4, 0, -4}, // 0x34 '4' + {38, 3, 5, 4, 0, -4}, // 0x35 '5' + {40, 3, 5, 4, 0, -4}, // 0x36 '6' + {42, 3, 5, 4, 0, -4}, // 0x37 '7' + {44, 3, 5, 4, 0, -4}, // 0x38 '8' + {46, 3, 5, 4, 0, -4}, // 0x39 '9' + {48, 1, 3, 2, 0, -3}, // 0x3A ':' + {49, 2, 4, 3, 0, -3}, // 0x3B ';' + {50, 2, 3, 3, 0, -3}, // 0x3C '<' + {51, 3, 3, 4, 0, -3}, // 0x3D '=' + {53, 2, 3, 3, 0, -3}, // 0x3E '>' + {54, 3, 5, 4, 0, -4}, // 0x3F '?' + {56, 3, 5, 4, 0, -4}, // 0x40 '@' + {58, 3, 5, 4, 0, -4}, // 0x41 'A' + {60, 3, 5, 4, 0, -4}, // 0x42 'B' + {62, 3, 5, 4, 0, -4}, // 0x43 'C' + {64, 3, 5, 4, 0, -4}, // 0x44 'D' + {66, 3, 5, 4, 0, -4}, // 0x45 'E' + {68, 3, 5, 4, 0, -4}, // 0x46 'F' + {70, 3, 5, 4, 0, -4}, // 0x47 'G' + {72, 3, 5, 4, 0, -4}, // 0x48 'H' + {74, 1, 5, 2, 0, -4}, // 0x49 'I' + {75, 3, 5, 4, 0, -4}, // 0x4A 'J' + {77, 3, 5, 4, 0, -4}, // 0x4B 'K' + {79, 3, 5, 4, 0, -4}, // 0x4C 'L' + {81, 5, 5, 6, 0, -4}, // 0x4D 'M' + {85, 4, 5, 5, 0, -4}, // 0x4E 'N' + {88, 3, 5, 4, 0, -4}, // 0x4F 'O' + {90, 3, 5, 4, 0, -4}, // 0x50 'P' + {92, 3, 6, 4, 0, -4}, // 0x51 'Q' + {95, 3, 5, 4, 0, -4}, // 0x52 'R' + {97, 3, 5, 4, 0, -4}, // 0x53 'S' + {99, 3, 5, 4, 0, -4}, // 0x54 'T' + {101, 3, 5, 4, 0, -4}, // 0x55 'U' + {103, 3, 5, 4, 0, -4}, // 0x56 'V' + {105, 5, 5, 6, 0, -4}, // 0x57 'W' + {109, 3, 5, 4, 0, -4}, // 0x58 'X' + {111, 3, 5, 4, 0, -4}, // 0x59 'Y' + {113, 3, 5, 4, 0, -4}, // 0x5A 'Z' + {115, 2, 5, 3, 0, -4}, // 0x5B '[' + {117, 3, 5, 4, 0, -4}, // 0x5C '\' + {119, 2, 5, 3, 0, -4}, // 0x5D ']' + {121, 3, 2, 4, 0, -4}, // 0x5E '^' + {122, 4, 1, 4, 0, 1}, // 0x5F '_' + {123, 2, 2, 3, 0, -4}, // 0x60 '`' + {124, 3, 4, 4, 0, -3}, // 0x61 'a' + {126, 3, 5, 4, 0, -4}, // 0x62 'b' + {128, 3, 3, 4, 0, -2}, // 0x63 'c' + {130, 3, 5, 4, 0, -4}, // 0x64 'd' + {132, 3, 4, 4, 0, -3}, // 0x65 'e' + {134, 2, 5, 3, 0, -4}, // 0x66 'f' + {136, 3, 5, 4, 0, -3}, // 0x67 'g' + {138, 3, 5, 4, 0, -4}, // 0x68 'h' + {140, 1, 5, 2, 0, -4}, // 0x69 'i' + {141, 2, 6, 3, 0, -4}, // 0x6A 'j' + {143, 3, 5, 4, 0, -4}, // 0x6B 'k' + {145, 2, 5, 3, 0, -4}, // 0x6C 'l' + {147, 5, 3, 6, 0, -2}, // 0x6D 'm' + {149, 3, 3, 4, 0, -2}, // 0x6E 'n' + {151, 3, 3, 4, 0, -2}, // 0x6F 'o' + {153, 3, 4, 4, 0, -2}, // 0x70 'p' + {155, 3, 4, 4, 0, -2}, // 0x71 'q' + {157, 2, 3, 3, 0, -2}, // 0x72 'r' + {158, 3, 4, 4, 0, -3}, // 0x73 's' + {160, 2, 5, 3, 0, -4}, // 0x74 't' + {162, 3, 3, 4, 0, -2}, // 0x75 'u' + {164, 3, 3, 4, 0, -2}, // 0x76 'v' + {166, 5, 3, 6, 0, -2}, // 0x77 'w' + {168, 3, 3, 4, 0, -2}, // 0x78 'x' + {170, 3, 4, 4, 0, -2}, // 0x79 'y' + {172, 3, 4, 4, 0, -3}, // 0x7A 'z' + {174, 3, 5, 4, 0, -4}, // 0x7B '{' + {176, 1, 6, 2, 0, -4}, // 0x7C '|' + {177, 3, 5, 4, 0, -4}, // 0x7D '}' + {179, 4, 2, 5, 0, -3}}; // 0x7E '~' + +const GFXfont Picopixel PROGMEM = {(uint8_t *)PicopixelBitmaps, + (GFXglyph *)PicopixelGlyphs, 0x20, 0x7E, 7}; + +// Approx. 852 bytes diff --git a/PDQ_MinLib/README.md b/PDQ_MinLib/README.md new file mode 100644 index 0000000..48bd2e5 --- /dev/null +++ b/PDQ_MinLib/README.md @@ -0,0 +1,51 @@ +PDQ_GFX_Libs with packed font support +============ +Version with special "packed" font support. Also include TTF2GFX for create custom GFX fonts from TTF. +Speed up improvement for GFX text drawing about 2-3 times. +Allow to create custom fonts with subset of chars (not all ASCII table). +Special "packing" algorithm for GFX font allow to save 20-50% of space + +PDQ_GFX_Libs +============ + +An optimized fork of Adafruit's GFX library (and LCD drivers) for Arduino (AVR). + +This is a replacement "re-mix" of the Adafruit GFX library and associated hardware drivers. + +Currently supported are ILI9340, ILI9341, ST7735 and ST7781 LCD drivers (and compatible chipsets). + +It is between 2.5x and 12x faster than the Adafruit libraries for SPI LCDs, but it aims to be completely "sketch compatible" (so you +can easily speed up your sketches that are using Adafruit's library). You just need to change the name of the #include and "tft" +object. A version of the Adafruit "benchmark" example is included (PDQ_graphicsbest.ino) with each driver library. + +This includes the 1.8", 2.2" and 2.8" SPI TFT LCD boards or "touch shields" that are commonly available from Adafruit and +many other vendors. These are commonly 128x128, 128x160 or 240x320 (but the library supports rotation). + +I would also like to thank the excellent http://fastled.io/ project for creating the "FastPin.h" template header that allows +for full speed GPIO while allowing the use of "friendly" Arduino pin numbering (it is included in the PDQ driver libraries). + +New features in latest commit ("v1.1.5" 2016-04-09) include: + + * Synced core functions with Adafruit_GFX (few changes - I think their clipping is still broken [fixed in PDQ_GFX]) + * Support for new fonts as seen in Adafruit_GFX GitHub + * Minor bugfixes + +New features in latest commit ("v1.0.0" 2015-05-30) include: + + * Arduino IDE 1.6.x support (mainly information in library.properties, but also tested and 100% warning free). + * New ATtiny85 support for IL934x using USI SPI (not quite as fast as 328P - but can run 20MHz PLL to make up). + * New support for parallel ST7781 driver as used in Seeed Studio 2.8" Touch Shield (also sold by Radio Shack). This is the fastest LCD supported currently. + * "Bit-banged" SPI support. Not as fast, but can use (nearly) any pins. + * Added pushColor with a count that can speed up application rendering runs of same color. + * Tidied up files and made sure all drivers were updated with latest tweaks. + +Suggestions, issues, bugs and comments welcome. Via https://hackaday.io/Xark or visit #Arduino channel on Freenode.net IRC. +I have also posted a write-up about the development of this library at http://hackaday.io/Xark (describes most of the optimizations done). + + +Issues +------ + +Currently, the library may only be used from the INO file in your project. You _cannot_ include it in a header file and +use it from other CPP files. The current workaround is to write wrapper functions or classes, declare them in a header +file, and then implement them in the INO file. \ No newline at end of file diff --git a/PDQ_MinLib/TomThumb.h b/PDQ_MinLib/TomThumb.h new file mode 100644 index 0000000..8bc4339 --- /dev/null +++ b/PDQ_MinLib/TomThumb.h @@ -0,0 +1,471 @@ +/** +** The original 3x5 font is licensed under the 3-clause BSD license: +** +** Copyright 1999 Brian J. Swetland +** Copyright 1999 Vassilii Khachaturov +** Portions (of vt100.c/vt100.h) copyright Dan Marks +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions, and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions, and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the authors may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** Modifications to Tom Thumb for improved readability are from Robey Pointer, +** see: +** http://robey.lag.net/2010/01/23/tiny-monospace-font.html +** +** The original author does not have any objection to relicensing of Robey +** Pointer's modifications (in this file) in a more permissive license. See +** the discussion at the above blog, and also here: +** http://opengameart.org/forumtopic/how-to-submit-art-using-the-3-clause-bsd-license +** +** Feb 21, 2016: Conversion from Linux BDF --> Adafruit GFX font, +** with the help of this Python script: +** https://gist.github.com/skelliam/322d421f028545f16f6d +** William Skellenger (williamj@skellenger.net) +** Twitter: @skelliam +** +*/ + +#define TOMTHUMB_USE_EXTENDED 0 + +const uint8_t TomThumbBitmaps[] PROGMEM = { + 0x00, /* 0x20 space */ + 0x80, 0x80, 0x80, 0x00, 0x80, /* 0x21 exclam */ + 0xA0, 0xA0, /* 0x22 quotedbl */ + 0xA0, 0xE0, 0xA0, 0xE0, 0xA0, /* 0x23 numbersign */ + 0x60, 0xC0, 0x60, 0xC0, 0x40, /* 0x24 dollar */ + 0x80, 0x20, 0x40, 0x80, 0x20, /* 0x25 percent */ + 0xC0, 0xC0, 0xE0, 0xA0, 0x60, /* 0x26 ampersand */ + 0x80, 0x80, /* 0x27 quotesingle */ + 0x40, 0x80, 0x80, 0x80, 0x40, /* 0x28 parenleft */ + 0x80, 0x40, 0x40, 0x40, 0x80, /* 0x29 parenright */ + 0xA0, 0x40, 0xA0, /* 0x2A asterisk */ + 0x40, 0xE0, 0x40, /* 0x2B plus */ + 0x40, 0x80, /* 0x2C comma */ + 0xE0, /* 0x2D hyphen */ + 0x80, /* 0x2E period */ + 0x20, 0x20, 0x40, 0x80, 0x80, /* 0x2F slash */ + 0x60, 0xA0, 0xA0, 0xA0, 0xC0, /* 0x30 zero */ + 0x40, 0xC0, 0x40, 0x40, 0x40, /* 0x31 one */ + 0xC0, 0x20, 0x40, 0x80, 0xE0, /* 0x32 two */ + 0xC0, 0x20, 0x40, 0x20, 0xC0, /* 0x33 three */ + 0xA0, 0xA0, 0xE0, 0x20, 0x20, /* 0x34 four */ + 0xE0, 0x80, 0xC0, 0x20, 0xC0, /* 0x35 five */ + 0x60, 0x80, 0xE0, 0xA0, 0xE0, /* 0x36 six */ + 0xE0, 0x20, 0x40, 0x80, 0x80, /* 0x37 seven */ + 0xE0, 0xA0, 0xE0, 0xA0, 0xE0, /* 0x38 eight */ + 0xE0, 0xA0, 0xE0, 0x20, 0xC0, /* 0x39 nine */ + 0x80, 0x00, 0x80, /* 0x3A colon */ + 0x40, 0x00, 0x40, 0x80, /* 0x3B semicolon */ + 0x20, 0x40, 0x80, 0x40, 0x20, /* 0x3C less */ + 0xE0, 0x00, 0xE0, /* 0x3D equal */ + 0x80, 0x40, 0x20, 0x40, 0x80, /* 0x3E greater */ + 0xE0, 0x20, 0x40, 0x00, 0x40, /* 0x3F question */ + 0x40, 0xA0, 0xE0, 0x80, 0x60, /* 0x40 at */ + 0x40, 0xA0, 0xE0, 0xA0, 0xA0, /* 0x41 A */ + 0xC0, 0xA0, 0xC0, 0xA0, 0xC0, /* 0x42 B */ + 0x60, 0x80, 0x80, 0x80, 0x60, /* 0x43 C */ + 0xC0, 0xA0, 0xA0, 0xA0, 0xC0, /* 0x44 D */ + 0xE0, 0x80, 0xE0, 0x80, 0xE0, /* 0x45 E */ + 0xE0, 0x80, 0xE0, 0x80, 0x80, /* 0x46 F */ + 0x60, 0x80, 0xE0, 0xA0, 0x60, /* 0x47 G */ + 0xA0, 0xA0, 0xE0, 0xA0, 0xA0, /* 0x48 H */ + 0xE0, 0x40, 0x40, 0x40, 0xE0, /* 0x49 I */ + 0x20, 0x20, 0x20, 0xA0, 0x40, /* 0x4A J */ + 0xA0, 0xA0, 0xC0, 0xA0, 0xA0, /* 0x4B K */ + 0x80, 0x80, 0x80, 0x80, 0xE0, /* 0x4C L */ + 0xA0, 0xE0, 0xE0, 0xA0, 0xA0, /* 0x4D M */ + 0xA0, 0xE0, 0xE0, 0xE0, 0xA0, /* 0x4E N */ + 0x40, 0xA0, 0xA0, 0xA0, 0x40, /* 0x4F O */ + 0xC0, 0xA0, 0xC0, 0x80, 0x80, /* 0x50 P */ + 0x40, 0xA0, 0xA0, 0xE0, 0x60, /* 0x51 Q */ + 0xC0, 0xA0, 0xE0, 0xC0, 0xA0, /* 0x52 R */ + 0x60, 0x80, 0x40, 0x20, 0xC0, /* 0x53 S */ + 0xE0, 0x40, 0x40, 0x40, 0x40, /* 0x54 T */ + 0xA0, 0xA0, 0xA0, 0xA0, 0x60, /* 0x55 U */ + 0xA0, 0xA0, 0xA0, 0x40, 0x40, /* 0x56 V */ + 0xA0, 0xA0, 0xE0, 0xE0, 0xA0, /* 0x57 W */ + 0xA0, 0xA0, 0x40, 0xA0, 0xA0, /* 0x58 X */ + 0xA0, 0xA0, 0x40, 0x40, 0x40, /* 0x59 Y */ + 0xE0, 0x20, 0x40, 0x80, 0xE0, /* 0x5A Z */ + 0xE0, 0x80, 0x80, 0x80, 0xE0, /* 0x5B bracketleft */ + 0x80, 0x40, 0x20, /* 0x5C backslash */ + 0xE0, 0x20, 0x20, 0x20, 0xE0, /* 0x5D bracketright */ + 0x40, 0xA0, /* 0x5E asciicircum */ + 0xE0, /* 0x5F underscore */ + 0x80, 0x40, /* 0x60 grave */ + 0xC0, 0x60, 0xA0, 0xE0, /* 0x61 a */ + 0x80, 0xC0, 0xA0, 0xA0, 0xC0, /* 0x62 b */ + 0x60, 0x80, 0x80, 0x60, /* 0x63 c */ + 0x20, 0x60, 0xA0, 0xA0, 0x60, /* 0x64 d */ + 0x60, 0xA0, 0xC0, 0x60, /* 0x65 e */ + 0x20, 0x40, 0xE0, 0x40, 0x40, /* 0x66 f */ + 0x60, 0xA0, 0xE0, 0x20, 0x40, /* 0x67 g */ + 0x80, 0xC0, 0xA0, 0xA0, 0xA0, /* 0x68 h */ + 0x80, 0x00, 0x80, 0x80, 0x80, /* 0x69 i */ + 0x20, 0x00, 0x20, 0x20, 0xA0, 0x40, /* 0x6A j */ + 0x80, 0xA0, 0xC0, 0xC0, 0xA0, /* 0x6B k */ + 0xC0, 0x40, 0x40, 0x40, 0xE0, /* 0x6C l */ + 0xE0, 0xE0, 0xE0, 0xA0, /* 0x6D m */ + 0xC0, 0xA0, 0xA0, 0xA0, /* 0x6E n */ + 0x40, 0xA0, 0xA0, 0x40, /* 0x6F o */ + 0xC0, 0xA0, 0xA0, 0xC0, 0x80, /* 0x70 p */ + 0x60, 0xA0, 0xA0, 0x60, 0x20, /* 0x71 q */ + 0x60, 0x80, 0x80, 0x80, /* 0x72 r */ + 0x60, 0xC0, 0x60, 0xC0, /* 0x73 s */ + 0x40, 0xE0, 0x40, 0x40, 0x60, /* 0x74 t */ + 0xA0, 0xA0, 0xA0, 0x60, /* 0x75 u */ + 0xA0, 0xA0, 0xE0, 0x40, /* 0x76 v */ + 0xA0, 0xE0, 0xE0, 0xE0, /* 0x77 w */ + 0xA0, 0x40, 0x40, 0xA0, /* 0x78 x */ + 0xA0, 0xA0, 0x60, 0x20, 0x40, /* 0x79 y */ + 0xE0, 0x60, 0xC0, 0xE0, /* 0x7A z */ + 0x60, 0x40, 0x80, 0x40, 0x60, /* 0x7B braceleft */ + 0x80, 0x80, 0x00, 0x80, 0x80, /* 0x7C bar */ + 0xC0, 0x40, 0x20, 0x40, 0xC0, /* 0x7D braceright */ + 0x60, 0xC0, /* 0x7E asciitilde */ +#if (TOMTHUMB_USE_EXTENDED) + 0x80, 0x00, 0x80, 0x80, 0x80, /* 0xA1 exclamdown */ + 0x40, 0xE0, 0x80, 0xE0, 0x40, /* 0xA2 cent */ + 0x60, 0x40, 0xE0, 0x40, 0xE0, /* 0xA3 sterling */ + 0xA0, 0x40, 0xE0, 0x40, 0xA0, /* 0xA4 currency */ + 0xA0, 0xA0, 0x40, 0xE0, 0x40, /* 0xA5 yen */ + 0x80, 0x80, 0x00, 0x80, 0x80, /* 0xA6 brokenbar */ + 0x60, 0x40, 0xA0, 0x40, 0xC0, /* 0xA7 section */ + 0xA0, /* 0xA8 dieresis */ + 0x60, 0x80, 0x60, /* 0xA9 copyright */ + 0x60, 0xA0, 0xE0, 0x00, 0xE0, /* 0xAA ordfeminine */ + 0x40, 0x80, 0x40, /* 0xAB guillemotleft */ + 0xE0, 0x20, /* 0xAC logicalnot */ + 0xC0, /* 0xAD softhyphen */ + 0xC0, 0xC0, 0xA0, /* 0xAE registered */ + 0xE0, /* 0xAF macron */ + 0x40, 0xA0, 0x40, /* 0xB0 degree */ + 0x40, 0xE0, 0x40, 0x00, 0xE0, /* 0xB1 plusminus */ + 0xC0, 0x40, 0x60, /* 0xB2 twosuperior */ + 0xE0, 0x60, 0xE0, /* 0xB3 threesuperior */ + 0x40, 0x80, /* 0xB4 acute */ + 0xA0, 0xA0, 0xA0, 0xC0, 0x80, /* 0xB5 mu */ + 0x60, 0xA0, 0x60, 0x60, 0x60, /* 0xB6 paragraph */ + 0xE0, 0xE0, 0xE0, /* 0xB7 periodcentered */ + 0x40, 0x20, 0xC0, /* 0xB8 cedilla */ + 0x80, 0x80, 0x80, /* 0xB9 onesuperior */ + 0x40, 0xA0, 0x40, 0x00, 0xE0, /* 0xBA ordmasculine */ + 0x80, 0x40, 0x80, /* 0xBB guillemotright */ + 0x80, 0x80, 0x00, 0x60, 0x20, /* 0xBC onequarter */ + 0x80, 0x80, 0x00, 0xC0, 0x60, /* 0xBD onehalf */ + 0xC0, 0xC0, 0x00, 0x60, 0x20, /* 0xBE threequarters */ + 0x40, 0x00, 0x40, 0x80, 0xE0, /* 0xBF questiondown */ + 0x40, 0x20, 0x40, 0xE0, 0xA0, /* 0xC0 Agrave */ + 0x40, 0x80, 0x40, 0xE0, 0xA0, /* 0xC1 Aacute */ + 0xE0, 0x00, 0x40, 0xE0, 0xA0, /* 0xC2 Acircumflex */ + 0x60, 0xC0, 0x40, 0xE0, 0xA0, /* 0xC3 Atilde */ + 0xA0, 0x40, 0xA0, 0xE0, 0xA0, /* 0xC4 Adieresis */ + 0xC0, 0xC0, 0xA0, 0xE0, 0xA0, /* 0xC5 Aring */ + 0x60, 0xC0, 0xE0, 0xC0, 0xE0, /* 0xC6 AE */ + 0x60, 0x80, 0x80, 0x60, 0x20, 0x40, /* 0xC7 Ccedilla */ + 0x40, 0x20, 0xE0, 0xC0, 0xE0, /* 0xC8 Egrave */ + 0x40, 0x80, 0xE0, 0xC0, 0xE0, /* 0xC9 Eacute */ + 0xE0, 0x00, 0xE0, 0xC0, 0xE0, /* 0xCA Ecircumflex */ + 0xA0, 0x00, 0xE0, 0xC0, 0xE0, /* 0xCB Edieresis */ + 0x40, 0x20, 0xE0, 0x40, 0xE0, /* 0xCC Igrave */ + 0x40, 0x80, 0xE0, 0x40, 0xE0, /* 0xCD Iacute */ + 0xE0, 0x00, 0xE0, 0x40, 0xE0, /* 0xCE Icircumflex */ + 0xA0, 0x00, 0xE0, 0x40, 0xE0, /* 0xCF Idieresis */ + 0xC0, 0xA0, 0xE0, 0xA0, 0xC0, /* 0xD0 Eth */ + 0xC0, 0x60, 0xA0, 0xE0, 0xA0, /* 0xD1 Ntilde */ + 0x40, 0x20, 0xE0, 0xA0, 0xE0, /* 0xD2 Ograve */ + 0x40, 0x80, 0xE0, 0xA0, 0xE0, /* 0xD3 Oacute */ + 0xE0, 0x00, 0xE0, 0xA0, 0xE0, /* 0xD4 Ocircumflex */ + 0xC0, 0x60, 0xE0, 0xA0, 0xE0, /* 0xD5 Otilde */ + 0xA0, 0x00, 0xE0, 0xA0, 0xE0, /* 0xD6 Odieresis */ + 0xA0, 0x40, 0xA0, /* 0xD7 multiply */ + 0x60, 0xA0, 0xE0, 0xA0, 0xC0, /* 0xD8 Oslash */ + 0x80, 0x40, 0xA0, 0xA0, 0xE0, /* 0xD9 Ugrave */ + 0x20, 0x40, 0xA0, 0xA0, 0xE0, /* 0xDA Uacute */ + 0xE0, 0x00, 0xA0, 0xA0, 0xE0, /* 0xDB Ucircumflex */ + 0xA0, 0x00, 0xA0, 0xA0, 0xE0, /* 0xDC Udieresis */ + 0x20, 0x40, 0xA0, 0xE0, 0x40, /* 0xDD Yacute */ + 0x80, 0xE0, 0xA0, 0xE0, 0x80, /* 0xDE Thorn */ + 0x60, 0xA0, 0xC0, 0xA0, 0xC0, 0x80, /* 0xDF germandbls */ + 0x40, 0x20, 0x60, 0xA0, 0xE0, /* 0xE0 agrave */ + 0x40, 0x80, 0x60, 0xA0, 0xE0, /* 0xE1 aacute */ + 0xE0, 0x00, 0x60, 0xA0, 0xE0, /* 0xE2 acircumflex */ + 0x60, 0xC0, 0x60, 0xA0, 0xE0, /* 0xE3 atilde */ + 0xA0, 0x00, 0x60, 0xA0, 0xE0, /* 0xE4 adieresis */ + 0x60, 0x60, 0x60, 0xA0, 0xE0, /* 0xE5 aring */ + 0x60, 0xE0, 0xE0, 0xC0, /* 0xE6 ae */ + 0x60, 0x80, 0x60, 0x20, 0x40, /* 0xE7 ccedilla */ + 0x40, 0x20, 0x60, 0xE0, 0x60, /* 0xE8 egrave */ + 0x40, 0x80, 0x60, 0xE0, 0x60, /* 0xE9 eacute */ + 0xE0, 0x00, 0x60, 0xE0, 0x60, /* 0xEA ecircumflex */ + 0xA0, 0x00, 0x60, 0xE0, 0x60, /* 0xEB edieresis */ + 0x80, 0x40, 0x80, 0x80, 0x80, /* 0xEC igrave */ + 0x40, 0x80, 0x40, 0x40, 0x40, /* 0xED iacute */ + 0xE0, 0x00, 0x40, 0x40, 0x40, /* 0xEE icircumflex */ + 0xA0, 0x00, 0x40, 0x40, 0x40, /* 0xEF idieresis */ + 0x60, 0xC0, 0x60, 0xA0, 0x60, /* 0xF0 eth */ + 0xC0, 0x60, 0xC0, 0xA0, 0xA0, /* 0xF1 ntilde */ + 0x40, 0x20, 0x40, 0xA0, 0x40, /* 0xF2 ograve */ + 0x40, 0x80, 0x40, 0xA0, 0x40, /* 0xF3 oacute */ + 0xE0, 0x00, 0x40, 0xA0, 0x40, /* 0xF4 ocircumflex */ + 0xC0, 0x60, 0x40, 0xA0, 0x40, /* 0xF5 otilde */ + 0xA0, 0x00, 0x40, 0xA0, 0x40, /* 0xF6 odieresis */ + 0x40, 0x00, 0xE0, 0x00, 0x40, /* 0xF7 divide */ + 0x60, 0xE0, 0xA0, 0xC0, /* 0xF8 oslash */ + 0x80, 0x40, 0xA0, 0xA0, 0x60, /* 0xF9 ugrave */ + 0x20, 0x40, 0xA0, 0xA0, 0x60, /* 0xFA uacute */ + 0xE0, 0x00, 0xA0, 0xA0, 0x60, /* 0xFB ucircumflex */ + 0xA0, 0x00, 0xA0, 0xA0, 0x60, /* 0xFC udieresis */ + 0x20, 0x40, 0xA0, 0x60, 0x20, 0x40, /* 0xFD yacute */ + 0x80, 0xC0, 0xA0, 0xC0, 0x80, /* 0xFE thorn */ + 0xA0, 0x00, 0xA0, 0x60, 0x20, 0x40, /* 0xFF ydieresis */ + 0x00, /* 0x11D gcircumflex */ + 0x60, 0xC0, 0xE0, 0xC0, 0x60, /* 0x152 OE */ + 0x60, 0xE0, 0xC0, 0xE0, /* 0x153 oe */ + 0xA0, 0x60, 0xC0, 0x60, 0xC0, /* 0x160 Scaron */ + 0xA0, 0x60, 0xC0, 0x60, 0xC0, /* 0x161 scaron */ + 0xA0, 0x00, 0xA0, 0x40, 0x40, /* 0x178 Ydieresis */ + 0xA0, 0xE0, 0x60, 0xC0, 0xE0, /* 0x17D Zcaron */ + 0xA0, 0xE0, 0x60, 0xC0, 0xE0, /* 0x17E zcaron */ + 0x00, /* 0xEA4 uni0EA4 */ + 0x00, /* 0x13A0 uni13A0 */ + 0x80, /* 0x2022 bullet */ + 0xA0, /* 0x2026 ellipsis */ + 0x60, 0xE0, 0xE0, 0xC0, 0x60, /* 0x20AC Euro */ + 0xE0, 0xA0, 0xA0, 0xA0, 0xE0, /* 0xFFFD uniFFFD */ +#endif /* (TOMTHUMB_USE_EXTENDED) */ +}; + +/* {offset, width, height, advance cursor, x offset, y offset} */ +const GFXglyph TomThumbGlyphs[] PROGMEM = { + {0, 8, 1, 2, 0, -5}, /* 0x20 space */ + {1, 8, 5, 2, 0, -5}, /* 0x21 exclam */ + {6, 8, 2, 4, 0, -5}, /* 0x22 quotedbl */ + {8, 8, 5, 4, 0, -5}, /* 0x23 numbersign */ + {13, 8, 5, 4, 0, -5}, /* 0x24 dollar */ + {18, 8, 5, 4, 0, -5}, /* 0x25 percent */ + {23, 8, 5, 4, 0, -5}, /* 0x26 ampersand */ + {28, 8, 2, 2, 0, -5}, /* 0x27 quotesingle */ + {30, 8, 5, 3, 0, -5}, /* 0x28 parenleft */ + {35, 8, 5, 3, 0, -5}, /* 0x29 parenright */ + {40, 8, 3, 4, 0, -5}, /* 0x2A asterisk */ + {43, 8, 3, 4, 0, -4}, /* 0x2B plus */ + {46, 8, 2, 3, 0, -2}, /* 0x2C comma */ + {48, 8, 1, 4, 0, -3}, /* 0x2D hyphen */ + {49, 8, 1, 2, 0, -1}, /* 0x2E period */ + {50, 8, 5, 4, 0, -5}, /* 0x2F slash */ + {55, 8, 5, 4, 0, -5}, /* 0x30 zero */ + {60, 8, 5, 3, 0, -5}, /* 0x31 one */ + {65, 8, 5, 4, 0, -5}, /* 0x32 two */ + {70, 8, 5, 4, 0, -5}, /* 0x33 three */ + {75, 8, 5, 4, 0, -5}, /* 0x34 four */ + {80, 8, 5, 4, 0, -5}, /* 0x35 five */ + {85, 8, 5, 4, 0, -5}, /* 0x36 six */ + {90, 8, 5, 4, 0, -5}, /* 0x37 seven */ + {95, 8, 5, 4, 0, -5}, /* 0x38 eight */ + {100, 8, 5, 4, 0, -5}, /* 0x39 nine */ + {105, 8, 3, 2, 0, -4}, /* 0x3A colon */ + {108, 8, 4, 3, 0, -4}, /* 0x3B semicolon */ + {112, 8, 5, 4, 0, -5}, /* 0x3C less */ + {117, 8, 3, 4, 0, -4}, /* 0x3D equal */ + {120, 8, 5, 4, 0, -5}, /* 0x3E greater */ + {125, 8, 5, 4, 0, -5}, /* 0x3F question */ + {130, 8, 5, 4, 0, -5}, /* 0x40 at */ + {135, 8, 5, 4, 0, -5}, /* 0x41 A */ + {140, 8, 5, 4, 0, -5}, /* 0x42 B */ + {145, 8, 5, 4, 0, -5}, /* 0x43 C */ + {150, 8, 5, 4, 0, -5}, /* 0x44 D */ + {155, 8, 5, 4, 0, -5}, /* 0x45 E */ + {160, 8, 5, 4, 0, -5}, /* 0x46 F */ + {165, 8, 5, 4, 0, -5}, /* 0x47 G */ + {170, 8, 5, 4, 0, -5}, /* 0x48 H */ + {175, 8, 5, 4, 0, -5}, /* 0x49 I */ + {180, 8, 5, 4, 0, -5}, /* 0x4A J */ + {185, 8, 5, 4, 0, -5}, /* 0x4B K */ + {190, 8, 5, 4, 0, -5}, /* 0x4C L */ + {195, 8, 5, 4, 0, -5}, /* 0x4D M */ + {200, 8, 5, 4, 0, -5}, /* 0x4E N */ + {205, 8, 5, 4, 0, -5}, /* 0x4F O */ + {210, 8, 5, 4, 0, -5}, /* 0x50 P */ + {215, 8, 5, 4, 0, -5}, /* 0x51 Q */ + {220, 8, 5, 4, 0, -5}, /* 0x52 R */ + {225, 8, 5, 4, 0, -5}, /* 0x53 S */ + {230, 8, 5, 4, 0, -5}, /* 0x54 T */ + {235, 8, 5, 4, 0, -5}, /* 0x55 U */ + {240, 8, 5, 4, 0, -5}, /* 0x56 V */ + {245, 8, 5, 4, 0, -5}, /* 0x57 W */ + {250, 8, 5, 4, 0, -5}, /* 0x58 X */ + {255, 8, 5, 4, 0, -5}, /* 0x59 Y */ + {260, 8, 5, 4, 0, -5}, /* 0x5A Z */ + {265, 8, 5, 4, 0, -5}, /* 0x5B bracketleft */ + {270, 8, 3, 4, 0, -4}, /* 0x5C backslash */ + {273, 8, 5, 4, 0, -5}, /* 0x5D bracketright */ + {278, 8, 2, 4, 0, -5}, /* 0x5E asciicircum */ + {280, 8, 1, 4, 0, -1}, /* 0x5F underscore */ + {281, 8, 2, 3, 0, -5}, /* 0x60 grave */ + {283, 8, 4, 4, 0, -4}, /* 0x61 a */ + {287, 8, 5, 4, 0, -5}, /* 0x62 b */ + {292, 8, 4, 4, 0, -4}, /* 0x63 c */ + {296, 8, 5, 4, 0, -5}, /* 0x64 d */ + {301, 8, 4, 4, 0, -4}, /* 0x65 e */ + {305, 8, 5, 4, 0, -5}, /* 0x66 f */ + {310, 8, 5, 4, 0, -4}, /* 0x67 g */ + {315, 8, 5, 4, 0, -5}, /* 0x68 h */ + {320, 8, 5, 2, 0, -5}, /* 0x69 i */ + {325, 8, 6, 4, 0, -5}, /* 0x6A j */ + {331, 8, 5, 4, 0, -5}, /* 0x6B k */ + {336, 8, 5, 4, 0, -5}, /* 0x6C l */ + {341, 8, 4, 4, 0, -4}, /* 0x6D m */ + {345, 8, 4, 4, 0, -4}, /* 0x6E n */ + {349, 8, 4, 4, 0, -4}, /* 0x6F o */ + {353, 8, 5, 4, 0, -4}, /* 0x70 p */ + {358, 8, 5, 4, 0, -4}, /* 0x71 q */ + {363, 8, 4, 4, 0, -4}, /* 0x72 r */ + {367, 8, 4, 4, 0, -4}, /* 0x73 s */ + {371, 8, 5, 4, 0, -5}, /* 0x74 t */ + {376, 8, 4, 4, 0, -4}, /* 0x75 u */ + {380, 8, 4, 4, 0, -4}, /* 0x76 v */ + {384, 8, 4, 4, 0, -4}, /* 0x77 w */ + {388, 8, 4, 4, 0, -4}, /* 0x78 x */ + {392, 8, 5, 4, 0, -4}, /* 0x79 y */ + {397, 8, 4, 4, 0, -4}, /* 0x7A z */ + {401, 8, 5, 4, 0, -5}, /* 0x7B braceleft */ + {406, 8, 5, 2, 0, -5}, /* 0x7C bar */ + {411, 8, 5, 4, 0, -5}, /* 0x7D braceright */ + {416, 8, 2, 4, 0, -5}, /* 0x7E asciitilde */ +#if (TOMTHUMB_USE_EXTENDED) + {418, 8, 5, 2, 0, -5}, /* 0xA1 exclamdown */ + {423, 8, 5, 4, 0, -5}, /* 0xA2 cent */ + {428, 8, 5, 4, 0, -5}, /* 0xA3 sterling */ + {433, 8, 5, 4, 0, -5}, /* 0xA4 currency */ + {438, 8, 5, 4, 0, -5}, /* 0xA5 yen */ + {443, 8, 5, 2, 0, -5}, /* 0xA6 brokenbar */ + {448, 8, 5, 4, 0, -5}, /* 0xA7 section */ + {453, 8, 1, 4, 0, -5}, /* 0xA8 dieresis */ + {454, 8, 3, 4, 0, -5}, /* 0xA9 copyright */ + {457, 8, 5, 4, 0, -5}, /* 0xAA ordfeminine */ + {462, 8, 3, 3, 0, -5}, /* 0xAB guillemotleft */ + {465, 8, 2, 4, 0, -4}, /* 0xAC logicalnot */ + {467, 8, 1, 3, 0, -3}, /* 0xAD softhyphen */ + {468, 8, 3, 4, 0, -5}, /* 0xAE registered */ + {471, 8, 1, 4, 0, -5}, /* 0xAF macron */ + {472, 8, 3, 4, 0, -5}, /* 0xB0 degree */ + {475, 8, 5, 4, 0, -5}, /* 0xB1 plusminus */ + {480, 8, 3, 4, 0, -5}, /* 0xB2 twosuperior */ + {483, 8, 3, 4, 0, -5}, /* 0xB3 threesuperior */ + {486, 8, 2, 3, 0, -5}, /* 0xB4 acute */ + {488, 8, 5, 4, 0, -5}, /* 0xB5 mu */ + {493, 8, 5, 4, 0, -5}, /* 0xB6 paragraph */ + {498, 8, 3, 4, 0, -4}, /* 0xB7 periodcentered */ + {501, 8, 3, 4, 0, -3}, /* 0xB8 cedilla */ + {504, 8, 3, 2, 0, -5}, /* 0xB9 onesuperior */ + {507, 8, 5, 4, 0, -5}, /* 0xBA ordmasculine */ + {512, 8, 3, 3, 0, -5}, /* 0xBB guillemotright */ + {515, 8, 5, 4, 0, -5}, /* 0xBC onequarter */ + {520, 8, 5, 4, 0, -5}, /* 0xBD onehalf */ + {525, 8, 5, 4, 0, -5}, /* 0xBE threequarters */ + {530, 8, 5, 4, 0, -5}, /* 0xBF questiondown */ + {535, 8, 5, 4, 0, -5}, /* 0xC0 Agrave */ + {540, 8, 5, 4, 0, -5}, /* 0xC1 Aacute */ + {545, 8, 5, 4, 0, -5}, /* 0xC2 Acircumflex */ + {550, 8, 5, 4, 0, -5}, /* 0xC3 Atilde */ + {555, 8, 5, 4, 0, -5}, /* 0xC4 Adieresis */ + {560, 8, 5, 4, 0, -5}, /* 0xC5 Aring */ + {565, 8, 5, 4, 0, -5}, /* 0xC6 AE */ + {570, 8, 6, 4, 0, -5}, /* 0xC7 Ccedilla */ + {576, 8, 5, 4, 0, -5}, /* 0xC8 Egrave */ + {581, 8, 5, 4, 0, -5}, /* 0xC9 Eacute */ + {586, 8, 5, 4, 0, -5}, /* 0xCA Ecircumflex */ + {591, 8, 5, 4, 0, -5}, /* 0xCB Edieresis */ + {596, 8, 5, 4, 0, -5}, /* 0xCC Igrave */ + {601, 8, 5, 4, 0, -5}, /* 0xCD Iacute */ + {606, 8, 5, 4, 0, -5}, /* 0xCE Icircumflex */ + {611, 8, 5, 4, 0, -5}, /* 0xCF Idieresis */ + {616, 8, 5, 4, 0, -5}, /* 0xD0 Eth */ + {621, 8, 5, 4, 0, -5}, /* 0xD1 Ntilde */ + {626, 8, 5, 4, 0, -5}, /* 0xD2 Ograve */ + {631, 8, 5, 4, 0, -5}, /* 0xD3 Oacute */ + {636, 8, 5, 4, 0, -5}, /* 0xD4 Ocircumflex */ + {641, 8, 5, 4, 0, -5}, /* 0xD5 Otilde */ + {646, 8, 5, 4, 0, -5}, /* 0xD6 Odieresis */ + {651, 8, 3, 4, 0, -4}, /* 0xD7 multiply */ + {654, 8, 5, 4, 0, -5}, /* 0xD8 Oslash */ + {659, 8, 5, 4, 0, -5}, /* 0xD9 Ugrave */ + {664, 8, 5, 4, 0, -5}, /* 0xDA Uacute */ + {669, 8, 5, 4, 0, -5}, /* 0xDB Ucircumflex */ + {674, 8, 5, 4, 0, -5}, /* 0xDC Udieresis */ + {679, 8, 5, 4, 0, -5}, /* 0xDD Yacute */ + {684, 8, 5, 4, 0, -5}, /* 0xDE Thorn */ + {689, 8, 6, 4, 0, -5}, /* 0xDF germandbls */ + {695, 8, 5, 4, 0, -5}, /* 0xE0 agrave */ + {700, 8, 5, 4, 0, -5}, /* 0xE1 aacute */ + {705, 8, 5, 4, 0, -5}, /* 0xE2 acircumflex */ + {710, 8, 5, 4, 0, -5}, /* 0xE3 atilde */ + {715, 8, 5, 4, 0, -5}, /* 0xE4 adieresis */ + {720, 8, 5, 4, 0, -5}, /* 0xE5 aring */ + {725, 8, 4, 4, 0, -4}, /* 0xE6 ae */ + {729, 8, 5, 4, 0, -4}, /* 0xE7 ccedilla */ + {734, 8, 5, 4, 0, -5}, /* 0xE8 egrave */ + {739, 8, 5, 4, 0, -5}, /* 0xE9 eacute */ + {744, 8, 5, 4, 0, -5}, /* 0xEA ecircumflex */ + {749, 8, 5, 4, 0, -5}, /* 0xEB edieresis */ + {754, 8, 5, 3, 0, -5}, /* 0xEC igrave */ + {759, 8, 5, 3, 0, -5}, /* 0xED iacute */ + {764, 8, 5, 4, 0, -5}, /* 0xEE icircumflex */ + {769, 8, 5, 4, 0, -5}, /* 0xEF idieresis */ + {774, 8, 5, 4, 0, -5}, /* 0xF0 eth */ + {779, 8, 5, 4, 0, -5}, /* 0xF1 ntilde */ + {784, 8, 5, 4, 0, -5}, /* 0xF2 ograve */ + {789, 8, 5, 4, 0, -5}, /* 0xF3 oacute */ + {794, 8, 5, 4, 0, -5}, /* 0xF4 ocircumflex */ + {799, 8, 5, 4, 0, -5}, /* 0xF5 otilde */ + {804, 8, 5, 4, 0, -5}, /* 0xF6 odieresis */ + {809, 8, 5, 4, 0, -5}, /* 0xF7 divide */ + {814, 8, 4, 4, 0, -4}, /* 0xF8 oslash */ + {818, 8, 5, 4, 0, -5}, /* 0xF9 ugrave */ + {823, 8, 5, 4, 0, -5}, /* 0xFA uacute */ + {828, 8, 5, 4, 0, -5}, /* 0xFB ucircumflex */ + {833, 8, 5, 4, 0, -5}, /* 0xFC udieresis */ + {838, 8, 6, 4, 0, -5}, /* 0xFD yacute */ + {844, 8, 5, 4, 0, -4}, /* 0xFE thorn */ + {849, 8, 6, 4, 0, -5}, /* 0xFF ydieresis */ + {855, 8, 1, 2, 0, -1}, /* 0x11D gcircumflex */ + {856, 8, 5, 4, 0, -5}, /* 0x152 OE */ + {861, 8, 4, 4, 0, -4}, /* 0x153 oe */ + {865, 8, 5, 4, 0, -5}, /* 0x160 Scaron */ + {870, 8, 5, 4, 0, -5}, /* 0x161 scaron */ + {875, 8, 5, 4, 0, -5}, /* 0x178 Ydieresis */ + {880, 8, 5, 4, 0, -5}, /* 0x17D Zcaron */ + {885, 8, 5, 4, 0, -5}, /* 0x17E zcaron */ + {890, 8, 1, 2, 0, -1}, /* 0xEA4 uni0EA4 */ + {891, 8, 1, 2, 0, -1}, /* 0x13A0 uni13A0 */ + {892, 8, 1, 2, 0, -3}, /* 0x2022 bullet */ + {893, 8, 1, 4, 0, -1}, /* 0x2026 ellipsis */ + {894, 8, 5, 4, 0, -5}, /* 0x20AC Euro */ + {899, 8, 5, 4, 0, -5}, /* 0xFFFD uniFFFD */ +#endif /* (TOMTHUMB_USE_EXTENDED) */ +}; + +const GFXfont TomThumb PROGMEM = {(uint8_t *)TomThumbBitmaps, + (GFXglyph *)TomThumbGlyphs, 0x20, 0x7E, 6}; diff --git a/PDQ_MinLib/gfxfont.h b/PDQ_MinLib/gfxfont.h new file mode 100644 index 0000000..78f3a47 --- /dev/null +++ b/PDQ_MinLib/gfxfont.h @@ -0,0 +1,29 @@ +// This is the PDQ re-mixed version of Adafruit's library from Xark +// here is the original copyright notice and license: + +// Font structures for newer Adafruit_GFX (1.1 and later). +// Example fonts are included in 'Fonts' directory. +// To use a font in your Arduino sketch, #include the corresponding .h +// file and pass address of GFXfont struct to setFont(). Pass NULL to +// revert to 'classic' fixed-space bitmap font. + +#ifndef _GFXFONT_H_ +#define _GFXFONT_H_ + +struct GFXglyph // Data stored PER GLYPH +{ + uint16_t bitmapOffset; // Pointer into GFXfont->bitmap + uint8_t width, height; // Bitmap dimensions in pixels + uint8_t xAdvance; // Distance to advance cursor (x axis) + int8_t xOffset, yOffset; // Dist from cursor pos to UL corner +}; + +struct GFXfont // Data stored for FONT AS A WHOLE: +{ + uint8_t *bitmap; // Glyph bitmaps, concatenated + GFXglyph *glyph; // Glyph array + uint8_t first, last; // ASCII extents + uint8_t yAdvance; // Newline distance (y axis) +}; + +#endif // _GFXFONT_H_ diff --git a/PDQ_MinLib/glcdfont.c b/PDQ_MinLib/glcdfont.c new file mode 100644 index 0000000..7231f92 --- /dev/null +++ b/PDQ_MinLib/glcdfont.c @@ -0,0 +1,278 @@ +// This is the 'classic' fixed-space bitmap font for Adafruit_GFX since 1.0. +// See gfxfont.h for newer custom bitmap font info. + +#ifndef FONT5X7_H +#define FONT5X7_H + +#ifdef __AVR__ + #include + #include +#elif defined(ESP8266) + #include +#else + #define PROGMEM +#endif + +// Standard ASCII 5x7 font + +const unsigned char glcdfont[] PROGMEM = + { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, + 0x18, 0x3C, 0x7E, 0x3C, 0x18, + 0x1C, 0x57, 0x7D, 0x57, 0x1C, + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, + 0x00, 0x18, 0x3C, 0x18, 0x00, + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, + 0x00, 0x18, 0x24, 0x18, 0x00, + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, + 0x30, 0x48, 0x3A, 0x06, 0x0E, + 0x26, 0x29, 0x79, 0x29, 0x26, + 0x40, 0x7F, 0x05, 0x05, 0x07, + 0x40, 0x7F, 0x05, 0x25, 0x3F, + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, + 0x08, 0x1C, 0x1C, 0x3E, 0x7F, + 0x14, 0x22, 0x7F, 0x22, 0x14, + 0x5F, 0x5F, 0x00, 0x5F, 0x5F, + 0x06, 0x09, 0x7F, 0x01, 0x7F, + 0x00, 0x66, 0x89, 0x95, 0x6A, + 0x60, 0x60, 0x60, 0x60, 0x60, + 0x94, 0xA2, 0xFF, 0xA2, 0x94, + 0x08, 0x04, 0x7E, 0x04, 0x08, + 0x10, 0x20, 0x7E, 0x20, 0x10, + 0x08, 0x08, 0x2A, 0x1C, 0x08, + 0x08, 0x1C, 0x2A, 0x08, 0x08, + 0x1E, 0x10, 0x10, 0x10, 0x10, + 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, + 0x30, 0x38, 0x3E, 0x38, 0x30, + 0x06, 0x0E, 0x3E, 0x0E, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5F, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x07, 0x00, + 0x14, 0x7F, 0x14, 0x7F, 0x14, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, + 0x23, 0x13, 0x08, 0x64, 0x62, + 0x36, 0x49, 0x56, 0x20, 0x50, + 0x00, 0x08, 0x07, 0x03, 0x00, + 0x00, 0x1C, 0x22, 0x41, 0x00, + 0x00, 0x41, 0x22, 0x1C, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, + 0x08, 0x08, 0x3E, 0x08, 0x08, + 0x00, 0x80, 0x70, 0x30, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x60, 0x60, 0x00, + 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3E, 0x51, 0x49, 0x45, 0x3E, + 0x00, 0x42, 0x7F, 0x40, 0x00, + 0x72, 0x49, 0x49, 0x49, 0x46, + 0x21, 0x41, 0x49, 0x4D, 0x33, + 0x18, 0x14, 0x12, 0x7F, 0x10, + 0x27, 0x45, 0x45, 0x45, 0x39, + 0x3C, 0x4A, 0x49, 0x49, 0x31, + 0x41, 0x21, 0x11, 0x09, 0x07, + 0x36, 0x49, 0x49, 0x49, 0x36, + 0x46, 0x49, 0x49, 0x29, 0x1E, + 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x40, 0x34, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x00, 0x41, 0x22, 0x14, 0x08, + 0x02, 0x01, 0x59, 0x09, 0x06, + 0x3E, 0x41, 0x5D, 0x59, 0x4E, + 0x7C, 0x12, 0x11, 0x12, 0x7C, + 0x7F, 0x49, 0x49, 0x49, 0x36, + 0x3E, 0x41, 0x41, 0x41, 0x22, + 0x7F, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x49, 0x49, 0x49, 0x41, + 0x7F, 0x09, 0x09, 0x09, 0x01, + 0x3E, 0x41, 0x41, 0x51, 0x73, + 0x7F, 0x08, 0x08, 0x08, 0x7F, + 0x00, 0x41, 0x7F, 0x41, 0x00, + 0x20, 0x40, 0x41, 0x3F, 0x01, + 0x7F, 0x08, 0x14, 0x22, 0x41, + 0x7F, 0x40, 0x40, 0x40, 0x40, + 0x7F, 0x02, 0x1C, 0x02, 0x7F, + 0x7F, 0x04, 0x08, 0x10, 0x7F, + 0x3E, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x09, 0x09, 0x09, 0x06, + 0x3E, 0x41, 0x51, 0x21, 0x5E, + 0x7F, 0x09, 0x19, 0x29, 0x46, + 0x26, 0x49, 0x49, 0x49, 0x32, + 0x03, 0x01, 0x7F, 0x01, 0x03, + 0x3F, 0x40, 0x40, 0x40, 0x3F, + 0x1F, 0x20, 0x40, 0x20, 0x1F, + 0x3F, 0x40, 0x38, 0x40, 0x3F, + 0x63, 0x14, 0x08, 0x14, 0x63, + 0x03, 0x04, 0x78, 0x04, 0x03, + 0x61, 0x59, 0x49, 0x4D, 0x43, + 0x00, 0x7F, 0x41, 0x41, 0x41, + 0x02, 0x04, 0x08, 0x10, 0x20, + 0x00, 0x41, 0x41, 0x41, 0x7F, + 0x04, 0x02, 0x01, 0x02, 0x04, + 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x03, 0x07, 0x08, 0x00, + 0x20, 0x54, 0x54, 0x78, 0x40, + 0x7F, 0x28, 0x44, 0x44, 0x38, + 0x38, 0x44, 0x44, 0x44, 0x28, + 0x38, 0x44, 0x44, 0x28, 0x7F, + 0x38, 0x54, 0x54, 0x54, 0x18, + 0x00, 0x08, 0x7E, 0x09, 0x02, + 0x18, 0xA4, 0xA4, 0x9C, 0x78, + 0x7F, 0x08, 0x04, 0x04, 0x78, + 0x00, 0x44, 0x7D, 0x40, 0x00, + 0x20, 0x40, 0x40, 0x3D, 0x00, + 0x7F, 0x10, 0x28, 0x44, 0x00, + 0x00, 0x41, 0x7F, 0x40, 0x00, + 0x7C, 0x04, 0x78, 0x04, 0x78, + 0x7C, 0x08, 0x04, 0x04, 0x78, + 0x38, 0x44, 0x44, 0x44, 0x38, + 0xFC, 0x18, 0x24, 0x24, 0x18, + 0x18, 0x24, 0x24, 0x18, 0xFC, + 0x7C, 0x08, 0x04, 0x04, 0x08, + 0x48, 0x54, 0x54, 0x54, 0x24, + 0x04, 0x04, 0x3F, 0x44, 0x24, + 0x3C, 0x40, 0x40, 0x20, 0x7C, + 0x1C, 0x20, 0x40, 0x20, 0x1C, + 0x3C, 0x40, 0x30, 0x40, 0x3C, + 0x44, 0x28, 0x10, 0x28, 0x44, + 0x4C, 0x90, 0x90, 0x90, 0x7C, + 0x44, 0x64, 0x54, 0x4C, 0x44, + 0x00, 0x08, 0x36, 0x41, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, + 0x00, 0x41, 0x36, 0x08, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x02, + 0x3C, 0x26, 0x23, 0x26, 0x3C, + 0x1E, 0xA1, 0xA1, 0x61, 0x12, + 0x3A, 0x40, 0x40, 0x20, 0x7A, + 0x38, 0x54, 0x54, 0x55, 0x59, + 0x21, 0x55, 0x55, 0x79, 0x41, + 0x22, 0x54, 0x54, 0x78, 0x42, // a-umlaut + 0x21, 0x55, 0x54, 0x78, 0x40, + 0x20, 0x54, 0x55, 0x79, 0x40, + 0x0C, 0x1E, 0x52, 0x72, 0x12, + 0x39, 0x55, 0x55, 0x55, 0x59, + 0x39, 0x54, 0x54, 0x54, 0x59, + 0x39, 0x55, 0x54, 0x54, 0x58, + 0x00, 0x00, 0x45, 0x7C, 0x41, + 0x00, 0x02, 0x45, 0x7D, 0x42, + 0x00, 0x01, 0x45, 0x7C, 0x40, + 0x7D, 0x12, 0x11, 0x12, 0x7D, // A-umlaut + 0xF0, 0x28, 0x25, 0x28, 0xF0, + 0x7C, 0x54, 0x55, 0x45, 0x00, + 0x20, 0x54, 0x54, 0x7C, 0x54, + 0x7C, 0x0A, 0x09, 0x7F, 0x49, + 0x32, 0x49, 0x49, 0x49, 0x32, + 0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut + 0x32, 0x4A, 0x48, 0x48, 0x30, + 0x3A, 0x41, 0x41, 0x21, 0x7A, + 0x3A, 0x42, 0x40, 0x20, 0x78, + 0x00, 0x9D, 0xA0, 0xA0, 0x7D, + 0x3D, 0x42, 0x42, 0x42, 0x3D, // O-umlaut + 0x3D, 0x40, 0x40, 0x40, 0x3D, + 0x3C, 0x24, 0xFF, 0x24, 0x24, + 0x48, 0x7E, 0x49, 0x43, 0x66, + 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, + 0xFF, 0x09, 0x29, 0xF6, 0x20, + 0xC0, 0x88, 0x7E, 0x09, 0x03, + 0x20, 0x54, 0x54, 0x79, 0x41, + 0x00, 0x00, 0x44, 0x7D, 0x41, + 0x30, 0x48, 0x48, 0x4A, 0x32, + 0x38, 0x40, 0x40, 0x22, 0x7A, + 0x00, 0x7A, 0x0A, 0x0A, 0x72, + 0x7D, 0x0D, 0x19, 0x31, 0x7D, + 0x26, 0x29, 0x29, 0x2F, 0x28, + 0x26, 0x29, 0x29, 0x29, 0x26, + 0x30, 0x48, 0x4D, 0x40, 0x20, + 0x38, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x38, + 0x2F, 0x10, 0xC8, 0xAC, 0xBA, + 0x2F, 0x10, 0x28, 0x34, 0xFA, + 0x00, 0x00, 0x7B, 0x00, 0x00, + 0x08, 0x14, 0x2A, 0x14, 0x22, + 0x22, 0x14, 0x2A, 0x14, 0x08, + 0x55, 0x00, 0x55, 0x00, 0x55, // #176 (25% block) missing in old code + 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 50% block + 0xFF, 0x55, 0xFF, 0x55, 0xFF, // 75% block + 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x10, 0x10, 0x10, 0xFF, 0x00, + 0x14, 0x14, 0x14, 0xFF, 0x00, + 0x10, 0x10, 0xFF, 0x00, 0xFF, + 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x14, 0x14, 0x14, 0xFC, 0x00, + 0x14, 0x14, 0xF7, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x14, 0x14, 0xF4, 0x04, 0xFC, + 0x14, 0x14, 0x17, 0x10, 0x1F, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0x1F, 0x00, + 0x10, 0x10, 0x10, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0xF0, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0xFF, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x14, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x1F, 0x10, 0x17, + 0x00, 0x00, 0xFC, 0x04, 0xF4, + 0x14, 0x14, 0x17, 0x10, 0x17, + 0x14, 0x14, 0xF4, 0x04, 0xF4, + 0x00, 0x00, 0xFF, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0xF7, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x17, 0x14, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0xF4, 0x14, + 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x00, 0x00, 0x1F, 0x10, 0x1F, + 0x00, 0x00, 0x00, 0x1F, 0x14, + 0x00, 0x00, 0x00, 0xFC, 0x14, + 0x00, 0x00, 0xF0, 0x10, 0xF0, + 0x10, 0x10, 0xFF, 0x10, 0xFF, + 0x14, 0x14, 0x14, 0xFF, 0x14, + 0x10, 0x10, 0x10, 0x1F, 0x00, + 0x00, 0x00, 0x00, 0xF0, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x38, 0x44, 0x44, 0x38, 0x44, + 0xFC, 0x4A, 0x4A, 0x4A, 0x34, // sharp-s or beta + 0x7E, 0x02, 0x02, 0x06, 0x06, + 0x02, 0x7E, 0x02, 0x7E, 0x02, + 0x63, 0x55, 0x49, 0x41, 0x63, + 0x38, 0x44, 0x44, 0x3C, 0x04, + 0x40, 0x7E, 0x20, 0x1E, 0x20, + 0x06, 0x02, 0x7E, 0x02, 0x02, + 0x99, 0xA5, 0xE7, 0xA5, 0x99, + 0x1C, 0x2A, 0x49, 0x2A, 0x1C, + 0x4C, 0x72, 0x01, 0x72, 0x4C, + 0x30, 0x4A, 0x4D, 0x4D, 0x30, + 0x30, 0x48, 0x78, 0x48, 0x30, + 0xBC, 0x62, 0x5A, 0x46, 0x3D, + 0x3E, 0x49, 0x49, 0x49, 0x00, + 0x7E, 0x01, 0x01, 0x01, 0x7E, + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, + 0x44, 0x44, 0x5F, 0x44, 0x44, + 0x40, 0x51, 0x4A, 0x44, 0x40, + 0x40, 0x44, 0x4A, 0x51, 0x40, + 0x00, 0x00, 0xFF, 0x01, 0x03, + 0xE0, 0x80, 0xFF, 0x00, 0x00, + 0x08, 0x08, 0x6B, 0x6B, 0x08, + 0x36, 0x12, 0x36, 0x24, 0x36, + 0x06, 0x0F, 0x09, 0x0F, 0x06, + 0x00, 0x00, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x10, 0x10, 0x00, + 0x30, 0x40, 0xFF, 0x01, 0x01, + 0x00, 0x1F, 0x01, 0x01, 0x1E, + 0x00, 0x19, 0x1D, 0x17, 0x12, + 0x00, 0x3C, 0x3C, 0x3C, 0x3C, + 0x00, 0x00, 0x00, 0x00, 0x00 // #255 NBSP +}; + +#endif // FONT5X7_H diff --git a/PDQ_MinLib/org_01.h b/PDQ_MinLib/org_01.h new file mode 100644 index 0000000..eabbd92 --- /dev/null +++ b/PDQ_MinLib/org_01.h @@ -0,0 +1,128 @@ +// Org_v01 by Orgdot (www.orgdot.com/aliasfonts). A tiny, +// stylized font with all characters within a 6 pixel height. + +const uint8_t Org_01Bitmaps[] PROGMEM = { + 0xE8, 0xA0, 0x57, 0xD5, 0xF5, 0x00, 0xFD, 0x3E, 0x5F, 0x80, 0x88, 0x88, + 0x88, 0x80, 0xF4, 0xBF, 0x2E, 0x80, 0x80, 0x6A, 0x40, 0x95, 0x80, 0xAA, + 0x80, 0x5D, 0x00, 0xC0, 0xF0, 0x80, 0x08, 0x88, 0x88, 0x00, 0xFC, 0x63, + 0x1F, 0x80, 0xF8, 0xF8, 0x7F, 0x0F, 0x80, 0xF8, 0x7E, 0x1F, 0x80, 0x8C, + 0x7E, 0x10, 0x80, 0xFC, 0x3E, 0x1F, 0x80, 0xFC, 0x3F, 0x1F, 0x80, 0xF8, + 0x42, 0x10, 0x80, 0xFC, 0x7F, 0x1F, 0x80, 0xFC, 0x7E, 0x1F, 0x80, 0x90, + 0xB0, 0x2A, 0x22, 0xF0, 0xF0, 0x88, 0xA8, 0xF8, 0x4E, 0x02, 0x00, 0xFD, + 0x6F, 0x0F, 0x80, 0xFC, 0x7F, 0x18, 0x80, 0xF4, 0x7D, 0x1F, 0x00, 0xFC, + 0x21, 0x0F, 0x80, 0xF4, 0x63, 0x1F, 0x00, 0xFC, 0x3F, 0x0F, 0x80, 0xFC, + 0x3F, 0x08, 0x00, 0xFC, 0x2F, 0x1F, 0x80, 0x8C, 0x7F, 0x18, 0x80, 0xF9, + 0x08, 0x4F, 0x80, 0x78, 0x85, 0x2F, 0x80, 0x8D, 0xB1, 0x68, 0x80, 0x84, + 0x21, 0x0F, 0x80, 0xFD, 0x6B, 0x5A, 0x80, 0xFC, 0x63, 0x18, 0x80, 0xFC, + 0x63, 0x1F, 0x80, 0xFC, 0x7F, 0x08, 0x00, 0xFC, 0x63, 0x3F, 0x80, 0xFC, + 0x7F, 0x29, 0x00, 0xFC, 0x3E, 0x1F, 0x80, 0xF9, 0x08, 0x42, 0x00, 0x8C, + 0x63, 0x1F, 0x80, 0x8C, 0x62, 0xA2, 0x00, 0xAD, 0x6B, 0x5F, 0x80, 0x8A, + 0x88, 0xA8, 0x80, 0x8C, 0x54, 0x42, 0x00, 0xF8, 0x7F, 0x0F, 0x80, 0xEA, + 0xC0, 0x82, 0x08, 0x20, 0x80, 0xD5, 0xC0, 0x54, 0xF8, 0x80, 0xF1, 0xFF, + 0x8F, 0x99, 0xF0, 0xF8, 0x8F, 0x1F, 0x99, 0xF0, 0xFF, 0x8F, 0x6B, 0xA4, + 0xF9, 0x9F, 0x10, 0x8F, 0x99, 0x90, 0xF0, 0x55, 0xC0, 0x8A, 0xF9, 0x90, + 0xF8, 0xFD, 0x63, 0x10, 0xF9, 0x99, 0xF9, 0x9F, 0xF9, 0x9F, 0x80, 0xF9, + 0x9F, 0x20, 0xF8, 0x88, 0x47, 0x1F, 0x27, 0xC8, 0x42, 0x00, 0x99, 0x9F, + 0x99, 0x97, 0x8C, 0x6B, 0xF0, 0x96, 0x69, 0x99, 0x9F, 0x10, 0x2E, 0x8F, + 0x2B, 0x22, 0xF8, 0x89, 0xA8, 0x0F, 0xE0}; + +const GFXglyph Org_01Glyphs[] PROGMEM = {{0, 0, 0, 6, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 1, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 5, 5, 6, 0, -4}, // 0x24 '$' + {10, 5, 5, 6, 0, -4}, // 0x25 '%' + {14, 5, 5, 6, 0, -4}, // 0x26 '&' + {18, 1, 1, 2, 0, -4}, // 0x27 ''' + {19, 2, 5, 3, 0, -4}, // 0x28 '(' + {21, 2, 5, 3, 0, -4}, // 0x29 ')' + {23, 3, 3, 4, 0, -3}, // 0x2A '*' + {25, 3, 3, 4, 0, -3}, // 0x2B '+' + {27, 1, 2, 2, 0, 0}, // 0x2C ',' + {28, 4, 1, 5, 0, -2}, // 0x2D '-' + {29, 1, 1, 2, 0, 0}, // 0x2E '.' + {30, 5, 5, 6, 0, -4}, // 0x2F '/' + {34, 5, 5, 6, 0, -4}, // 0x30 '0' + {38, 1, 5, 2, 0, -4}, // 0x31 '1' + {39, 5, 5, 6, 0, -4}, // 0x32 '2' + {43, 5, 5, 6, 0, -4}, // 0x33 '3' + {47, 5, 5, 6, 0, -4}, // 0x34 '4' + {51, 5, 5, 6, 0, -4}, // 0x35 '5' + {55, 5, 5, 6, 0, -4}, // 0x36 '6' + {59, 5, 5, 6, 0, -4}, // 0x37 '7' + {63, 5, 5, 6, 0, -4}, // 0x38 '8' + {67, 5, 5, 6, 0, -4}, // 0x39 '9' + {71, 1, 4, 2, 0, -3}, // 0x3A ':' + {72, 1, 4, 2, 0, -3}, // 0x3B ';' + {73, 3, 5, 4, 0, -4}, // 0x3C '<' + {75, 4, 3, 5, 0, -3}, // 0x3D '=' + {77, 3, 5, 4, 0, -4}, // 0x3E '>' + {79, 5, 5, 6, 0, -4}, // 0x3F '?' + {83, 5, 5, 6, 0, -4}, // 0x40 '@' + {87, 5, 5, 6, 0, -4}, // 0x41 'A' + {91, 5, 5, 6, 0, -4}, // 0x42 'B' + {95, 5, 5, 6, 0, -4}, // 0x43 'C' + {99, 5, 5, 6, 0, -4}, // 0x44 'D' + {103, 5, 5, 6, 0, -4}, // 0x45 'E' + {107, 5, 5, 6, 0, -4}, // 0x46 'F' + {111, 5, 5, 6, 0, -4}, // 0x47 'G' + {115, 5, 5, 6, 0, -4}, // 0x48 'H' + {119, 5, 5, 6, 0, -4}, // 0x49 'I' + {123, 5, 5, 6, 0, -4}, // 0x4A 'J' + {127, 5, 5, 6, 0, -4}, // 0x4B 'K' + {131, 5, 5, 6, 0, -4}, // 0x4C 'L' + {135, 5, 5, 6, 0, -4}, // 0x4D 'M' + {139, 5, 5, 6, 0, -4}, // 0x4E 'N' + {143, 5, 5, 6, 0, -4}, // 0x4F 'O' + {147, 5, 5, 6, 0, -4}, // 0x50 'P' + {151, 5, 5, 6, 0, -4}, // 0x51 'Q' + {155, 5, 5, 6, 0, -4}, // 0x52 'R' + {159, 5, 5, 6, 0, -4}, // 0x53 'S' + {163, 5, 5, 6, 0, -4}, // 0x54 'T' + {167, 5, 5, 6, 0, -4}, // 0x55 'U' + {171, 5, 5, 6, 0, -4}, // 0x56 'V' + {175, 5, 5, 6, 0, -4}, // 0x57 'W' + {179, 5, 5, 6, 0, -4}, // 0x58 'X' + {183, 5, 5, 6, 0, -4}, // 0x59 'Y' + {187, 5, 5, 6, 0, -4}, // 0x5A 'Z' + {191, 2, 5, 3, 0, -4}, // 0x5B '[' + {193, 5, 5, 6, 0, -4}, // 0x5C '\' + {197, 2, 5, 3, 0, -4}, // 0x5D ']' + {199, 3, 2, 4, 0, -4}, // 0x5E '^' + {200, 5, 1, 6, 0, 1}, // 0x5F '_' + {201, 1, 1, 2, 0, -4}, // 0x60 '`' + {202, 4, 4, 5, 0, -3}, // 0x61 'a' + {204, 4, 5, 5, 0, -4}, // 0x62 'b' + {207, 4, 4, 5, 0, -3}, // 0x63 'c' + {209, 4, 5, 5, 0, -4}, // 0x64 'd' + {212, 4, 4, 5, 0, -3}, // 0x65 'e' + {214, 3, 5, 4, 0, -4}, // 0x66 'f' + {216, 4, 5, 5, 0, -3}, // 0x67 'g' + {219, 4, 5, 5, 0, -4}, // 0x68 'h' + {222, 1, 4, 2, 0, -3}, // 0x69 'i' + {223, 2, 5, 3, 0, -3}, // 0x6A 'j' + {225, 4, 5, 5, 0, -4}, // 0x6B 'k' + {228, 1, 5, 2, 0, -4}, // 0x6C 'l' + {229, 5, 4, 6, 0, -3}, // 0x6D 'm' + {232, 4, 4, 5, 0, -3}, // 0x6E 'n' + {234, 4, 4, 5, 0, -3}, // 0x6F 'o' + {236, 4, 5, 5, 0, -3}, // 0x70 'p' + {239, 4, 5, 5, 0, -3}, // 0x71 'q' + {242, 4, 4, 5, 0, -3}, // 0x72 'r' + {244, 4, 4, 5, 0, -3}, // 0x73 's' + {246, 5, 5, 6, 0, -4}, // 0x74 't' + {250, 4, 4, 5, 0, -3}, // 0x75 'u' + {252, 4, 4, 5, 0, -3}, // 0x76 'v' + {254, 5, 4, 6, 0, -3}, // 0x77 'w' + {257, 4, 4, 5, 0, -3}, // 0x78 'x' + {259, 4, 5, 5, 0, -3}, // 0x79 'y' + {262, 4, 4, 5, 0, -3}, // 0x7A 'z' + {264, 3, 5, 4, 0, -4}, // 0x7B '{' + {266, 1, 5, 2, 0, -4}, // 0x7C '|' + {267, 3, 5, 4, 0, -4}, // 0x7D '}' + {269, 5, 3, 6, 0, -3}}; // 0x7E '~' + +const GFXfont Org_01 PROGMEM = {(uint8_t *)Org_01Bitmaps, + (GFXglyph *)Org_01Glyphs, 0x20, 0x7E, 7}; + +// Approx. 943 bytes diff --git a/README.md b/README.md index 8b7e7e9..33a74b6 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,72 @@ -# ubitxv6 -uBitx v6.3.1 Arduino sketch -IMPORTANT: It will compile only if you place this in the Arduino's own sketch directory! This is because of the restricted places that the Arduino searches for it's include files (the headers). +# uBiTXv6 -- This is refactored to remove dependencies on any library except the standard Arduino libraries of SPI, I2C, EEPROM, etc. -- This works with ILI9341 display controller. The pins used by the TFT display are the same as that of the 16x2 LCD display of the previous versions. -- As the files are now split into .cpp files, the nano gui, morse reader, etc. can be reused in other projects as well +This project is found at https://github.com/reedbn/ubitxv6/ -This is released under GPL v3 license. +It was forked from https://github.com/afarhan/ubitxv6/ + +The purpose of this project is to clean up (modularize) the source code, and add features that were not present +in Ashhar's original version of the project, without requiring any hardware modifications to a stock uBiTXv6. + +New features include: + +* Much faster screen refresh (vs Ashhar's 6.3.1 aka 6.0 release) +* Morse code readback for sightless operation +* Save/recall your favorite frequencies +* When adjusting settings, the existing/current setting is shown as reference +* Cancel touch recalibration + +User Manual: https://docs.google.com/document/d/1jlllZbvFMCzO1MJLzlJDGb10HXSehlFNMDPsxGJZtvY/edit?usp=drivesdk + +# Installing on Your Radio + +There are plenty of tutorials on how to upload sketches to Arduino Nanos. Just search for them. Addtionally, +Ashhar created a video explaining the process specifically for uBiTX v6: https://www.youtube.com/watch?v=3n_V3prSJ_E + +I developed this code using the Arduino IDE 1.8.9 toolchain, with -Wall and -Wextra compiler options turned on. +Arduino IDE 1.8.13 was reported to compile too big (see https://groups.io/g/BITX20/topic/75008576), but this +should be resolved in this project's tag R1.5.1. + +# Personalized Callsign + +To edit the callsign displayed, open the file `callsign.cpp` and change the string. Then re-compile and upload. + +# Future Features/Modifications + +There are some features that would be good to add, but I just didn't get around to. + +* Setting to choose the tuning step size +* Setting to choose whether or not the knob tuning should accelerate (current behavior) or have a fixed interval +* Provide an option in each menu screen to load the default option for each setting + +While the current code (as of 2020-05-05) is ~100 bytes shy of the full 30720 available on the nano, there's still +opportunity to add new features by "creating" room. Below is a list of places you might create room: + +I added lots of bounds checking, especially on string writes, that, if removed, could free a good number of bytes. +While keeping them is best practice, for a non-IoT, non-critical piece of hardware, it shouldn't be a huge issue. + +I added the RACK to the CAT to better emulate the FT-817 (I hope, at least!). Removing the RACK's and just leaving +the default ACK's will also free up bytes. + +I added a bunch of strings to the menuing with the intention of helping people understand their functions, but +technically they're not necessary, and could all be removed. + +I switched to a smaller footprint font than Ashhar's original code, but there are MUCH smaller fonts out there. +Changing to a lower resolution, scaled up font can save hundreds or thousands of bytes, but won't look as pretty. +Also, the star, gear, and numpad icons will need to be either added to the new font, or replaced with characters. + +The first change I made to this fork was to replace Ashhar's original (incredibly slow) screen drawing routines +with PDQ. Since that change, Ashhar has updated his drawing routine to be MUCH faster than his original, but +still slightly slower than PDQ. It may be that Ashhar's new routines are smaller that PDQ, but I don't actually +know that for certain. + +There are a good number of instances of back-to-back calls of strncpy_P and displayText. Creating a single +function that performs these operations together, and then calling that new function instead of the +back-to-back calls everywhere may save space. + +# License + +The majority of this code is released under GPL v3 license, per Ashhar's original code. + +The files in the PDQ_MinLib subdirectory were copied from https://github.com/XarkLabs/PDQ_GFX_Libs, and have Adafruit's BSD License. + +The files in the toneAC2 were copied from https://bitbucket.org/teckel12/arduino-toneac2/wiki/Home, and are GPL v3 licensed. diff --git a/bands.cpp b/bands.cpp new file mode 100644 index 0000000..de05d66 --- /dev/null +++ b/bands.cpp @@ -0,0 +1,130 @@ +#include + +#include "bands.h" +#include "utils.h" + +/* + * These are the bands for USA. Your bands may vary + */ +struct Band_t { + uint32_t min; + uint32_t max; + uint8_t band_meters; + char name[3];//Two characters + null terminator. Fixed width so we don't need to build separate pointers +}; + +const char UNKNOWN_BAND_NAME [] PROGMEM = "??"; + +constexpr Band_t bands [] PROGMEM { +// { 0UL, 255UL, 255, "U8"},//Utility conversion option +// { 0UL, 65535UL, 254, "UF"},//Utility conversion option +// { 530000UL, 1700000UL, 253, "AM"},//Broadcast AM, actually centers at 268, but uint8 can't do that +// { 1800000UL, 2000000UL, 160, "A0"},//0xA0 is 160 + { 3500000UL, 4000000UL, 80, "80"}, +// { 5330500UL, 5403500UL, 60, "60"}, + { 7000000UL, 7300000UL, 40, "40"}, + {10100000UL, 10150000UL, 30, "30"}, + {14000000UL, 14350000UL, 20, "20"}, + {18068000UL, 18168000UL, 17, "17"}, + {21000000UL, 21450000UL, 15, "15"}, +// {24890000UL, 24990000UL, 12, "12"}, +// {26965000UL, 27405000UL, 11, "CB"},//Citizen's Band + {28000000UL, 29700000UL, 10, "10"}, +}; +constexpr uint8_t NUM_BANDS = sizeof(bands)/sizeof(bands[0]); + +int8_t findBandIndexFromBand(const uint8_t target_band) +{ + Band_t band; + for(uint8_t i = 0; i < NUM_BANDS; ++i){ + memcpy_P(&band,&bands[i],sizeof(band)); + if(target_band == band.band_meters){ + return i; + } + } + + return -1; +} + +int8_t findBandIndexFromFreq(uint32_t frequency) +{ + Band_t band; + for(uint8_t i = 0; i < NUM_BANDS; ++i){ + memcpy_P(&band,&bands[i],sizeof(band)); + if(frequency <= band.max){ + if(band.min <= frequency){ + return i; + } + //No bands overlap, and they are ordered in strictly increasing frequency, so we need search no further + return -1; + } + } + + return -1; +} + +void getBandString(const unsigned long frequency, + char* band_string_out, + uint16_t max_string_length) +{ + int8_t band_index = findBandIndexFromFreq(frequency); + if(-1 == band_index){ + strncpy_P(band_string_out,UNKNOWN_BAND_NAME,max_string_length); + } + else{ + Band_t band; + memcpy_P(&band,&bands[band_index],sizeof(band)); + strncpy_P(band_string_out,band.name,max_string_length); + } +} + +uint32_t getFreqInBand(const uint32_t frequency, + const uint8_t target_band) +{ + int8_t target_band_index = findBandIndexFromBand(target_band); + if(-1 == target_band_index){ + //Hard to target a band we don't know about... + return frequency; + } + + //See if we're currrently in a valid band + int8_t current_band_index = findBandIndexFromFreq(frequency); + + if(-1 == current_band_index){ + //We're not in a known band - just go to the center of the target band + Band_t band; + memcpy_P(&band,&bands[target_band_index],sizeof(band)); + return band.min + ((band.max - band.min)/2/100)*100;//midpoint truncated to 100Hz resolution + } + else{ + //We're in a known band. Match the relative position in the target band. + Band_t current_band; + memcpy_P(¤t_band,&bands[current_band_index],sizeof(current_band)); + Band_t target_band; + memcpy_P(&target_band,&bands[target_band_index],sizeof(target_band)); + const uint32_t range_current = current_band.max - current_band.min; + const uint32_t range_target = target_band.max - target_band.min; + return (((frequency - current_band.min) * (uint64_t)range_target / range_current + target_band.min)/100)*100;//truncated 100Hz + } +} + +bool isFreqInBand(const uint32_t frequency, + const uint8_t check_band) +{ + int8_t band_index = findBandIndexFromBand(check_band); + + if(-1 == band_index){ + //Unknown band - can't be in it + return false; + } + + Band_t band; + memcpy_P(&band,&bands[band_index],sizeof(band)); + if( (frequency <= band.max) + && (band.min <= frequency)){ + return true; + } + + return false; +} + diff --git a/bands.h b/bands.h new file mode 100644 index 0000000..8303e5e --- /dev/null +++ b/bands.h @@ -0,0 +1,11 @@ +#include + +void getBandString(const uint32_t frequency, + char* band_string_out, + const uint16_t max_string_length); + +uint32_t getFreqInBand(const uint32_t frequency, + const uint8_t target_band); + +bool isFreqInBand(const uint32_t frequency, + const uint8_t band); \ No newline at end of file diff --git a/button.cpp b/button.cpp new file mode 100644 index 0000000..536f196 --- /dev/null +++ b/button.cpp @@ -0,0 +1,50 @@ +#include "button.h" + +#include + +#include "color_theme.h" +#include "nano_gui.h" +#include "scratch_space.h" + +void drawButton(Button* button) +{ + uint16_t tc = COLOR_INACTIVE_TEXT; + uint16_t bgc = COLOR_INACTIVE_BACKGROUND; + const uint16_t bdc = COLOR_INACTIVE_BORDER; + switch(button->status()) + { + case ButtonStatus_e::Stateless://Fallthrough intended + case ButtonStatus_e::Inactive://Fallthrough intended + default: + { + //Colors are initialized for this, so we're done + break; + } + case ButtonStatus_e::Active: + { + tc = COLOR_ACTIVE_TEXT; + bgc = COLOR_ACTIVE_BACKGROUND; + break; + } + } + + + if(nullptr != button->text){ + strncpy_P(b,button->text,sizeof(b)); + } + else if(nullptr != button->text_override){ + button->text_override(b,sizeof(b)); + } + else{ + //Something's messed up + //Serial.println(F("No text for button!")); + return; + } + displayText(b, button->x, button->y, button->w, button->h, tc, bgc, bdc); +} + +void extractAndDrawButton(Button* button_out, const Button* button_P) +{ + memcpy_P(button_out,button_P,sizeof(*button_out)); + drawButton(button_out); +} diff --git a/button.h b/button.h new file mode 100644 index 0000000..7a9e33d --- /dev/null +++ b/button.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +enum ButtonStatus_e : uint8_t { + Stateless, + Inactive, + Active +}; + +struct Button { + int16_t x, y, w, h; + const char* text;//nullptr if text_override should be used + void (*text_override)(char* text_out, const uint16_t max_text_size);//nullptr if text should be used + ButtonStatus_e (*status)();//Used for coloring and morse menu + void (*on_select)();//Action to take when selected + char morse; +}; + +void extractAndDrawButton(Button* button_out, const Button* button_P); diff --git a/button_press_e.h b/button_press_e.h new file mode 100644 index 0000000..9147d4b --- /dev/null +++ b/button_press_e.h @@ -0,0 +1,7 @@ +#pragma once + +enum ButtonPress_e : uint8_t { + NotPressed, + ShortPress, + LongPress +}; diff --git a/button_timing.h b/button_timing.h new file mode 100644 index 0000000..fdac0ea --- /dev/null +++ b/button_timing.h @@ -0,0 +1,5 @@ +#pragma once + +static const uint8_t DEBOUNCE_DELAY_MS = 50; +static const uint16_t LONG_PRESS_TIME_MS = 3000; +static const uint8_t LONG_PRESS_POLL_TIME_MS = 10; \ No newline at end of file diff --git a/callsign.cpp b/callsign.cpp new file mode 100644 index 0000000..4c7203a --- /dev/null +++ b/callsign.cpp @@ -0,0 +1,4 @@ +#include "callsign.h" + +const char CALLSIGN_STRING_PRIVATE [] PROGMEM = "CALLSIGN"; +const char* const CALLSIGN_STRING = CALLSIGN_STRING_PRIVATE; \ No newline at end of file diff --git a/callsign.h b/callsign.h new file mode 100644 index 0000000..481c143 --- /dev/null +++ b/callsign.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * VERSION_STRING is a PROGMEM string, so extract it before use, e.g. + * strncpy_P(char_buffer_out,VERSION_STRING,size_of_char_buffer_out); + */ +extern const char* const CALLSIGN_STRING; \ No newline at end of file diff --git a/color_theme.h b/color_theme.h new file mode 100644 index 0000000..d76155e --- /dev/null +++ b/color_theme.h @@ -0,0 +1,22 @@ +#pragma once + +#include "colors.h" + +static const unsigned int COLOR_TEXT = DISPLAY_WHITE; +static const unsigned int COLOR_BACKGROUND = DISPLAY_NAVY; + +static const unsigned int COLOR_ACTIVE_VFO_TEXT = DISPLAY_WHITE; +static const unsigned int COLOR_ACTIVE_VFO_BACKGROUND = DISPLAY_BLACK; + +static const unsigned int COLOR_INACTIVE_VFO_TEXT = DISPLAY_GREEN; +static const unsigned int COLOR_INACTIVE_VFO_BACKGROUND = DISPLAY_BLACK; + +static const unsigned int COLOR_INACTIVE_TEXT = DISPLAY_GREEN; +static const unsigned int COLOR_INACTIVE_BACKGROUND = DISPLAY_BLACK; +static const unsigned int COLOR_INACTIVE_BORDER = DISPLAY_DARKGREY; + +static const unsigned int COLOR_ACTIVE_TEXT = DISPLAY_BLACK; +static const unsigned int COLOR_ACTIVE_BACKGROUND = DISPLAY_ORANGE; +static const unsigned int COLOR_ACTIVE_BORDER = DISPLAY_WHITE; + +static const unsigned int COLOR_VERSION_TEXT = DISPLAY_LIGHTGREY; \ No newline at end of file diff --git a/colors.h b/colors.h new file mode 100644 index 0000000..bb56762 --- /dev/null +++ b/colors.h @@ -0,0 +1,20 @@ +// Color definitions +static const uint16_t DISPLAY_BLACK = 0x0000; ///< 0, 0, 0 +static const uint16_t DISPLAY_NAVY = 0x000F; ///< 0, 0, 123 +static const uint16_t DISPLAY_DARKGREEN = 0x03E0; ///< 0, 125, 0 +static const uint16_t DISPLAY_DARKCYAN = 0x03EF; ///< 0, 125, 123 +static const uint16_t DISPLAY_MAROON = 0x7800; ///< 123, 0, 0 +static const uint16_t DISPLAY_PURPLE = 0x780F; ///< 123, 0, 123 +static const uint16_t DISPLAY_OLIVE = 0x7BE0; ///< 123, 125, 0 +static const uint16_t DISPLAY_LIGHTGREY = 0xC618; ///< 198, 195, 198 +static const uint16_t DISPLAY_DARKGREY = 0x7BEF; ///< 123, 125, 123 +static const uint16_t DISPLAY_BLUE = 0x001F; ///< 0, 0, 255 +static const uint16_t DISPLAY_GREEN = 0x07E0; ///< 0, 255, 0 +static const uint16_t DISPLAY_CYAN = 0x07FF; ///< 0, 255, 255 +static const uint16_t DISPLAY_RED = 0xF800; ///< 255, 0, 0 +static const uint16_t DISPLAY_MAGENTA = 0xF81F; ///< 255, 0, 255 +static const uint16_t DISPLAY_YELLOW = 0xFFE0; ///< 255, 255, 0 +static const uint16_t DISPLAY_WHITE = 0xFFFF; ///< 255, 255, 255 +static const uint16_t DISPLAY_ORANGE = 0xFD20; ///< 255, 165, 0 +static const uint16_t DISPLAY_GREENYELLOW = 0xAFE5; ///< 173, 255, 41 +static const uint16_t DISPLAY_PINK = 0xFC18; ///< 255, 130, 198 diff --git a/encoder.cpp b/encoder.cpp new file mode 100644 index 0000000..73bdc06 --- /dev/null +++ b/encoder.cpp @@ -0,0 +1,125 @@ +#include +#include + +#include "encoder.h" +#include "pin_definitions.h" + +//Normal encoder state +uint8_t prev_enc = 0; +int8_t enc_count = 0; + +//Momentum encoder state +int16_t enc_count_periodic = 0; +int8_t momentum[3] = {0}; +static const uint16_t CALLBACK_PERIOD_MS = 200; +static const uint8_t MOMENTUM_MULTIPLIER = 1; + +uint8_t enc_state (void) +{ + return (digitalRead(PIN_ENC_B) << 1) + (digitalRead(PIN_ENC_A) << 0); +} + +/* + * SmittyHalibut's encoder handling, using interrupts. Should be quicker, smoother handling. + * The Interrupt Service Routine for Pin Change Interrupts on A0-A5. + */ +ISR (PCINT1_vect) +{ + uint8_t cur_enc = enc_state(); + if (prev_enc == cur_enc) { + //Serial.println("unnecessary ISR"); + return; + } + //Serial.print(prev_enc); + //Serial.println(cur_enc); + + //these transitions point to the enccoder being rotated anti-clockwise + if ((prev_enc == 0 && cur_enc == 2) || + (prev_enc == 2 && cur_enc == 3) || + (prev_enc == 3 && cur_enc == 1) || + (prev_enc == 1 && cur_enc == 0)) + { + enc_count -= 1; + enc_count_periodic -= 1; + } + //these transitions point to the enccoder being rotated clockwise + else if ((prev_enc == 0 && cur_enc == 1) || + (prev_enc == 1 && cur_enc == 3) || + (prev_enc == 3 && cur_enc == 2) || + (prev_enc == 2 && cur_enc == 0)) + { + enc_count += 1; + enc_count_periodic += 1; + } + else { + // A change to two states, we can't tell whether it was forward or backward, so we skip it. + //Serial.println("skip"); + } + prev_enc = cur_enc; // Record state for next pulse interpretation +} + +/* + * Setup the encoder interrupts and global variables. + */ +void pci_setup(byte pin) { + *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin + PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt + PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group +} + +void enc_setup(void) +{ + enc_count = 0; + // This is already done in setup() ? + //pinMode(PIN_ENC_A, INPUT); + //pinMode(PIN_ENC_B, INPUT); + prev_enc = enc_state(); + + // Setup Pin Change Interrupts for the encoder inputs + pci_setup(PIN_ENC_A); + pci_setup(PIN_ENC_B); + + //Set up timer interrupt for momentum + TCCR1A = 0;//"normal" mode + TCCR1B = 3;//clock divider of 64 + TCNT1 = 0;//start counting at 0 + OCR1A = F_CPU * (unsigned long)CALLBACK_PERIOD_MS / 1000 / 64;//set target number + TIMSK1 |= (1 << OCIE1A);//enable interrupt +} + +ISR(TIMER1_COMPA_vect) +{ + momentum[2] = momentum[1]; + momentum[1] = momentum[0]; + momentum[0] = enc_count_periodic; + enc_count_periodic = 0; +} + +int8_t min_momentum_mag() +{ + int8_t min_mag = 127; + for(uint8_t i = 0; i < sizeof(momentum)/sizeof(momentum[0]); ++i){ + int8_t mag = abs(momentum[i]); + if(mag < min_mag){ + min_mag = mag; + } + } + return min_mag; +} + +int enc_read(void) { + if(0 != enc_count){ + int16_t ret = enc_count; + int8_t s = (enc_count < 0) ? -1 : 1; + int8_t momentum_mag = min_momentum_mag(); + if(momentum_mag >= 20){ + ret += s*40; + } + else if(momentum_mag >= 5){ + ret += s*(20 + momentum_mag)/(20 - momentum_mag); + } + enc_count = 0; + return ret; + } + return 0; +} \ No newline at end of file diff --git a/encoder.h b/encoder.h new file mode 100644 index 0000000..b3bd8a7 --- /dev/null +++ b/encoder.h @@ -0,0 +1,4 @@ +#pragma once + +void enc_setup(void); +int enc_read(void); diff --git a/glcdfont.c b/glcdfont.c new file mode 100644 index 0000000..c2c52ca --- /dev/null +++ b/glcdfont.c @@ -0,0 +1,6 @@ +//Include the font so that it's built by the arduino +//default builder, despite being in a subdirectory +//#include "PDQ_MinLib/glcdfont.c" + +#include +const unsigned char glcdfont[] PROGMEM = {0}; \ No newline at end of file diff --git a/keyer.cpp b/keyer.cpp index aa4fdc2..0a13dca 100644 --- a/keyer.cpp +++ b/keyer.cpp @@ -1,5 +1,7 @@ -#include -#include "ubitx.h" +#include "toneAC2/toneAC2.h" +#include "pin_definitions.h" +#include "settings.h" +#include "tuner.h" /** CW Keyer @@ -28,72 +30,25 @@ */ //CW ADC Range -int cwAdcSTFrom = 0; -int cwAdcSTTo = 50; -int cwAdcBothFrom = 51; -int cwAdcBothTo = 300; -int cwAdcDotFrom = 301; -int cwAdcDotTo = 600; -int cwAdcDashFrom = 601; -int cwAdcDashTo = 800; -//byte cwKeyType = 0; //0: straight, 1 : iambica, 2: iambicb +//static const unsigned int cwAdcSTFrom = 0; +static const unsigned int cwAdcSTTo = 50; +static const unsigned int cwAdcBothFrom = cwAdcSTTo + 1; +static const unsigned int cwAdcBothTo = 300; +static const unsigned int cwAdcDotFrom = cwAdcBothTo + 1; +static const unsigned int cwAdcDotTo = 600; +static const unsigned int cwAdcDashFrom = cwAdcDotTo + 1; +static const unsigned int cwAdcDashTo = 800; -byte delayBeforeCWStartTime = 50; - - - - -// in milliseconds, this is the parameter that determines how long the tx will hold between cw key downs -//#define CW_TIMEOUT (600l) //Change to CW Delaytime for value save to eeprom -#define PADDLE_DOT 1 -#define PADDLE_DASH 2 -#define PADDLE_BOTH 3 -#define PADDLE_STRAIGHT 4 - -//we store the last padde's character -//to alternatively send dots and dashes -//when both are simultaneously pressed -char lastPaddle = 0; - -/* -//reads the analog keyer pin and reports the paddle -byte getPaddle(){ - int paddle = analogRead(ANALOG_KEYER); - //handle the ptt as the straight key - - if (digitalRead(PTT) == 0) - return PADDLE_STRAIGHT; - - if (paddle > 800) // above 4v is up - return 0; - - if (!Iambic_Key) - return PADDLE_STRAIGHT; - - if (paddle > 600) // 4-3v is dot - return PADDLE_DASH; - else if (paddle > 300) //1-2v is dash - return PADDLE_DOT; - else if (paddle > 50) - return PADDLE_BOTH; //both are between 1 and 2v - else - return PADDLE_STRAIGHT; //less than 1v is the straight key -} -*/ /** * Starts transmitting the carrier with the sidetone * It assumes that we have called cwTxStart and not called cwTxStop * each time it is called, the cwTimeOut is pushed further into the future */ void cwKeydown(){ + toneAC2(PIN_CW_TONE, globalSettings.cwSideToneFreq); + digitalWrite(PIN_CW_KEY, 1); - keyDown = 1; //tracks the CW_KEY - tone(CW_TONE, (int)sideTone); - digitalWrite(CW_KEY, 1); - - //Modified by KD8CEC, for CW Delay Time save to eeprom - //cwTimeout = millis() + CW_TIMEOUT; - cwTimeout = millis() + cwDelayTime * 10; + globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs; } /** @@ -101,13 +56,10 @@ void cwKeydown(){ * Pushes the cwTimeout further into the future */ void cwKeyUp(){ - keyDown = 0; //tracks the CW_KEY - noTone(CW_TONE); - digitalWrite(CW_KEY, 0); + noToneAC2(); + digitalWrite(PIN_CW_KEY, 0); - //Modified by KD8CEC, for CW Delay Time save to eeprom - //cwTimeout = millis() + CW_TIMEOUT; - cwTimeout = millis() + cwDelayTime * 10; + globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs; } //Variables for Ron's new logic @@ -119,37 +71,29 @@ void cwKeyUp(){ enum KSTYPE {IDLE, CHK_DIT, CHK_DAH, KEYED_PREP, KEYED, INTER_ELEMENT }; static unsigned long ktimer; unsigned char keyerState = IDLE; +uint8_t keyerControl = 0; //Below is a test to reduce the keying error. do not delete lines //create by KD8CEC for compatible with new CW Logic -char update_PaddleLatch(byte isUpdateKeyState) { +char update_PaddleLatch(bool isUpdateKeyState) { unsigned char tmpKeyerControl = 0; - - int paddle = analogRead(ANALOG_KEYER); - //diagnostic, VU2ESE - //itoa(paddle, b, 10); - //printLine2(b); - - //use the PTT as the key for tune up, quick QSOs - if (digitalRead(PTT) == 0) - tmpKeyerControl |= DIT_L; - else if (paddle >= cwAdcDashFrom && paddle <= cwAdcDashTo) + unsigned int paddle = analogRead(PIN_ANALOG_KEYER); + if (paddle >= cwAdcDashFrom && paddle <= cwAdcDashTo) tmpKeyerControl |= DAH_L; else if (paddle >= cwAdcDotFrom && paddle <= cwAdcDotTo) tmpKeyerControl |= DIT_L; else if (paddle >= cwAdcBothFrom && paddle <= cwAdcBothTo) - tmpKeyerControl |= (DAH_L | DIT_L) ; - else - { - if (Iambic_Key) + tmpKeyerControl |= (DAH_L | DIT_L) ; + else{ + if (KeyerMode_e::KEYER_STRAIGHT != globalSettings.keyerMode) tmpKeyerControl = 0 ; - else if (paddle >= cwAdcSTFrom && paddle <= cwAdcSTTo) + else if (paddle <= cwAdcDashTo) tmpKeyerControl = DIT_L ; else - tmpKeyerControl = 0 ; + tmpKeyerControl = 0 ; } - if (isUpdateKeyState == 1) + if (isUpdateKeyState) keyerControl |= tmpKeyerControl; return tmpKeyerControl; @@ -160,22 +104,56 @@ char update_PaddleLatch(byte isUpdateKeyState) { // modified by KD8CEC ******************************************************************************/ void cwKeyer(void){ - lastPaddle = 0; bool continue_loop = true; - unsigned tmpKeyControl = 0; + char tmpKeyControl = 0; - if( Iambic_Key ) { - while(continue_loop) { - switch (keyerState) { + if((KeyerMode_e::KEYER_STRAIGHT == globalSettings.keyerMode) + || (digitalRead(PIN_PTT) == 0)){//use the PTT as the key for tune up, quick QSOs + while(1){ + tmpKeyControl = update_PaddleLatch(0) | (digitalRead(PIN_PTT)?0:DIT_L); + //Serial.println((int)tmpKeyControl); + if ((tmpKeyControl & DIT_L) == DIT_L) { + // if we are here, it is only because the key is pressed + if (!globalSettings.txActive){ + startTx(TuningMode_e::TUNE_CW); + globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs; + } + cwKeydown(); + + while ( tmpKeyControl & DIT_L == DIT_L){ + tmpKeyControl = update_PaddleLatch(0) | (digitalRead(PIN_PTT)?0:DIT_L); + //Serial.println((int)tmpKeyControl); + } + + cwKeyUp(); + } + else{ + if (0 < globalSettings.cwExpirationTimeMs && globalSettings.cwExpirationTimeMs < millis()){ + globalSettings.cwExpirationTimeMs = 0; + stopTx(); + } + return;//Tx stop control by Main Loop + } + + checkCAT(); + } //end of while + + } + else{//KEYER_IAMBIC_* + while(continue_loop){ + switch(keyerState){ case IDLE: tmpKeyControl = update_PaddleLatch(0); - if ( tmpKeyControl == DAH_L || tmpKeyControl == DIT_L || - tmpKeyControl == (DAH_L | DIT_L) || (keyerControl & 0x03)) { - update_PaddleLatch(1); + if((tmpKeyControl == DAH_L)//Currently dah + ||(tmpKeyControl == DIT_L)//Currently dit + ||(tmpKeyControl == (DAH_L | DIT_L))//Currently both + ||( keyerControl & (DAH_L | DIT_L))){//Resolving either + update_PaddleLatch(true); keyerState = CHK_DIT; - }else{ - if (0 < cwTimeout && cwTimeout < millis()){ - cwTimeout = 0; + } + else{ + if (0 < globalSettings.cwExpirationTimeMs && globalSettings.cwExpirationTimeMs < millis()){ + globalSettings.cwExpirationTimeMs = 0; stopTx(); } continue_loop = false; @@ -185,7 +163,7 @@ void cwKeyer(void){ case CHK_DIT: if (keyerControl & DIT_L) { keyerControl |= DIT_PROC; - ktimer = cwSpeed; + ktimer = globalSettings.cwDitDurationMs; keyerState = KEYED_PREP; }else{ keyerState = CHK_DAH; @@ -194,7 +172,7 @@ void cwKeyer(void){ case CHK_DAH: if (keyerControl & DAH_L) { - ktimer = cwSpeed*3; + ktimer = 3*globalSettings.cwDitDurationMs; keyerState = KEYED_PREP; }else{ keyerState = IDLE; @@ -203,13 +181,9 @@ void cwKeyer(void){ case KEYED_PREP: //modified KD8CEC - if (!inTx){ - //DelayTime Option - active_delay(delayBeforeCWStartTime * 2); - - keyDown = 0; - cwTimeout = millis() + cwDelayTime * 10; //+ CW_TIMEOUT; - startTx(TX_CW); + if (!globalSettings.txActive){ + globalSettings.cwExpirationTimeMs = millis() + globalSettings.cwActiveTimeoutMs; + startTx(TuningMode_e::TUNE_CW); } ktimer += millis(); // set ktimer to interval end time keyerControl &= ~(DIT_L + DAH_L); // clear both paddle latch bits @@ -220,10 +194,11 @@ void cwKeyer(void){ case KEYED: if (millis() > ktimer) { // are we at end of key down ? - cwKeyUp(); - ktimer = millis() + cwSpeed; // inter-element time + cwKeyUp(); + ktimer = millis() + globalSettings.cwDitDurationMs; // inter-element time keyerState = INTER_ELEMENT; // next state - }else if (keyerControl & IAMBICB) { + } + else if(KeyerMode_e::KEYER_IAMBIC_B == globalSettings.keyerMode){ update_PaddleLatch(1); // early paddle latch in Iambic B mode } break; @@ -245,48 +220,7 @@ void cwKeyer(void){ checkCAT(); } //end of while - } - else{ - while(1){ - char state = update_PaddleLatch(0); - // Serial.println((int)state); - if (state == DIT_L) { - // if we are here, it is only because the key is pressed - if (!inTx){ - startTx(TX_CW); - - //DelayTime Option - active_delay(delayBeforeCWStartTime * 2); - - keyDown = 0; - cwTimeout = millis() + cwDelayTime * 10; //+ CW_TIMEOUT; - } - cwKeydown(); - - while ( update_PaddleLatch(0) == DIT_L ) - active_delay(1); - - cwKeyUp(); - } - else{ - if (0 < cwTimeout && cwTimeout < millis()){ - cwTimeout = 0; - keyDown = 0; - stopTx(); - } - //if (!cwTimeout) //removed by KD8CEC - // return; - // got back to the beginning of the loop, if no further activity happens on straight key - // we will time out, and return out of this routine - //delay(5); - //delay_background(5, 3); //removed by KD8CEC - //continue; //removed by KD8CEC - return; //Tx stop control by Main Loop - } - - checkCAT(); - } //end of while - } //end of elese + }//end of KEYER_IAMBIC_* } diff --git a/menu.cpp b/menu.cpp new file mode 100644 index 0000000..680716d --- /dev/null +++ b/menu.cpp @@ -0,0 +1,43 @@ +#include "menu.h" +#include "menu_main.h" + +void runActiveMenu(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob) +{ + Menu_t* parent_menu = rootMenu;//rootMenu is it's own parent + Menu_t* active_menu = rootMenu; + while(nullptr != active_menu->active_submenu){ + parent_menu = active_menu; + active_menu = parent_menu->active_submenu; + } + MenuReturn_e mr = active_menu->runMenu(tuner_button,touch_button,touch_point,knob); + switch(mr){ + case MenuReturn_e::StillActive://Fallthrough intended + case MenuReturn_e::ExitedNoRedraw: + { + //Nothing to do here - just return + break; + } + default://Fallthrough intended. Default to this menu being active + case MenuReturn_e::ExitedRedraw: + { + //Turn off submenu, redraw, then return + parent_menu->active_submenu = nullptr; + parent_menu->initMenu(); + break; + } + }//end switch +} + +void enterSubmenu(Menu_t *const submenu) +{ + Menu_t* current_menu = rootMenu; + while(nullptr != current_menu->active_submenu){ + current_menu = current_menu->active_submenu; + } + current_menu->active_submenu = submenu; + submenu->initMenu(); +} + diff --git a/menu.h b/menu.h new file mode 100644 index 0000000..ef46054 --- /dev/null +++ b/menu.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include "button_press_e.h" +#include "point.h" + +enum MenuReturn_e : uint8_t { + StillActive, + ExitedRedraw, + ExitedNoRedraw +}; + +struct Menu_t { + void (*const initMenu)();//Any initial draw routines or state initialization + MenuReturn_e (*const runMenu)(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob); + Menu_t* active_submenu; +}; + +static const uint8_t MENU_KNOB_COUNTS_PER_ITEM = 10; + +void runActiveMenu(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob); + +void enterSubmenu(Menu_t *const submenu); diff --git a/menu_main.cpp b/menu_main.cpp new file mode 100644 index 0000000..08e8ff4 --- /dev/null +++ b/menu_main.cpp @@ -0,0 +1,192 @@ +#include "menu_main.h" +#include "menu_main_buttons.h" + +#include +#include + +#include "button.h" +#include "color_theme.h" +#include "menu_utils.h" +#include "morse.h" +#include "nano_gui.h" +#include "scratch_space.h" +#include "settings.h" +#include "tuner.h"//THRESHOLD_USB_LSB +#include "utils.h" + +void drawMainMenu(void); +MenuReturn_e runMainMenu(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob); +Menu_t mainMenu = { + drawMainMenu, + runMainMenu, + nullptr +}; + +Menu_t* const rootMenu = &mainMenu; + +bool mainMenuSelecting = false;//Tracks if we're selecting buttons with knob, or adjusting frequency +int16_t mainMenuSelectedItemRaw = 0;//Allow negative only for easier checks on wrap around + +void drawMainMenu(void) +{ + displayClear(COLOR_BACKGROUND); + Button button; + Button* bp; + for(uint8_t i = 0; i < MAIN_MENU_NUM_BUTTONS; ++i){ + memcpy_P(&bp, &(mainMenuButtons[i]), sizeof(bp)); + extractAndDrawButton(&button,bp); + } + drawVersion(); + drawCallsign(); + + ltoa(GetActiveVfoFreq(),b,10); + morseText(b); +} + +void drawMainMenuIncrement() +{ + //State variables + static uint32_t last_freq = 0; + static Vfo_e last_vfo = Vfo_e::VFO_A; + static VfoMode_e last_mode = VfoMode_e::VFO_MODE_LSB; + static bool last_split = false; + static bool last_rit = false; + static TuningMode_e last_tuning = TuningMode_e::TUNE_SSB; + + Button button; + + if((last_freq != GetActiveVfoFreq()) + ||(last_vfo != globalSettings.activeVfo)){ + extractAndDrawButton(&button,&bVfoA); + extractAndDrawButton(&button,&bVfoB); + updateBandButtons(last_freq); + last_freq = GetActiveVfoFreq(); + last_vfo = globalSettings.activeVfo; + + //We set this here so that we're always hearing what's displayed + setFrequency(last_freq); + } + + if(last_mode != GetActiveVfoMode()){ + updateSidebandButtons(); + last_mode = GetActiveVfoMode(); + } + + if(last_split != globalSettings.splitOn){ + extractAndDrawButton(&button,&bVfoA); + extractAndDrawButton(&button,&bVfoB); + extractAndDrawButton(&button,&bSpl); + last_split = globalSettings.splitOn; + } + + if(last_rit != globalSettings.ritOn){ + extractAndDrawButton(&button,&bRit); + last_rit = globalSettings.ritOn; + } + + if(last_tuning != globalSettings.tuningMode){ + extractAndDrawButton(&button,&bCw); + last_tuning = globalSettings.tuningMode; + } +} + +void mainMenuTune(int16_t knob) +{ + if(0 == knob){ + //Nothing to do - we're already set! + return; + } + + const uint32_t current_freq = GetActiveVfoFreq(); + const uint32_t new_freq = current_freq + (50 * knob); + + SetActiveVfoFreq(new_freq); + autoSelectSidebandChanged(current_freq); +} + +MenuReturn_e runMainMenu(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob) +{ + //Check tuner_button + if(ButtonPress_e::NotPressed != tuner_button){ + switch(tuner_button){ + default://Fallthrough intended + case ButtonPress_e::NotPressed: + { + //Nothing to do + break; + } + case ButtonPress_e::ShortPress: + { + if(mainMenuSelecting){ + uint8_t menu_index = mainMenuSelectedItemRaw/MENU_KNOB_COUNTS_PER_ITEM; + Button button; + Button* bp; + memcpy_P(&bp,&(mainMenuButtons[menu_index]),sizeof(bp)); + memcpy_P(&button,bp,sizeof(button)); + endSelector(&button); + button.on_select(); + } + else{ + initSelector(&mainMenuSelectedItemRaw, + mainMenuButtons, + MAIN_MENU_NUM_BUTTONS, + MorsePlaybackType_e::PlayChar); + } + mainMenuSelecting = !mainMenuSelecting; + + //Don't handle touch or knob on this run + return MenuReturn_e::StillActive;//main menu always returns StillActive + break; + } + case ButtonPress_e::LongPress: + { + if(!globalSettings.morseMenuOn){ + globalSettings.morseMenuOn = true;//set before playing + morseLetter(2); + } + else{ + morseLetter(4); + globalSettings.morseMenuOn = false;//unset after playing + } + SaveSettingsToEeprom(); + //Don't handle touch or knob on this run + return MenuReturn_e::StillActive;//main menu always returns StillActive + break; + } + }//switch + }//tuner_button + + else if(ButtonPress_e::NotPressed != touch_button){ + //We treat long and short presses the same, so no need to have a switch + Button button; + if(findPressedButton(mainMenuButtons,MAIN_MENU_NUM_BUTTONS,&button,touch_point)){ + button.on_select(); + } + else{ + //Touch detected, but not on our buttons, so ignore + } + }//touch_button + + else{//Neither button input type found, so handle the knob + if(mainMenuSelecting){ + adjustSelector(&mainMenuSelectedItemRaw, + knob, + mainMenuButtons, + MAIN_MENU_NUM_BUTTONS, + MorsePlaybackType_e::PlayChar); + } + else{ + mainMenuTune(knob); + } + } + + drawMainMenuIncrement(); + + return MenuReturn_e::StillActive;//main menu always returns StillActive +} \ No newline at end of file diff --git a/menu_main.h b/menu_main.h new file mode 100644 index 0000000..9be09c0 --- /dev/null +++ b/menu_main.h @@ -0,0 +1,2 @@ +#include "menu.h" +extern Menu_t* const rootMenu; diff --git a/menu_main_buttons.cpp b/menu_main_buttons.cpp new file mode 100644 index 0000000..034b830 --- /dev/null +++ b/menu_main_buttons.cpp @@ -0,0 +1,637 @@ +#include "menu_main_buttons.h" + +#include +#include +#include //F() + +#include "bands.h" +#include "button.h" +#include "callsign.h" +#include "color_theme.h" +#include "menu_main.h" +#include "menu_numpad.h" +#include "menu_quicklist.h" +#include "morse.h" +#include "nano_gui.h" +#include "scratch_space.h" +#include "settings.h" +#include "setup.h" +#include "tuner.h" +#include "utils.h" +#include "version.h" + +static const unsigned int LAYOUT_VFO_LABEL_X = 0; +static const unsigned int LAYOUT_VFO_LABEL_Y = 10; +static const unsigned int LAYOUT_VFO_LABEL_WIDTH = 159; +static const unsigned int LAYOUT_VFO_LABEL_HEIGHT = 36; +static const unsigned int LAYOUT_VFO_LABEL_PITCH_X = 160; + +static const unsigned int LAYOUT_MODE_TEXT_X = 0; +static const unsigned int LAYOUT_MODE_TEXT_Y = LAYOUT_VFO_LABEL_Y + LAYOUT_VFO_LABEL_HEIGHT + 1; +static const unsigned int LAYOUT_MODE_TEXT_WIDTH = 320; +static const unsigned int LAYOUT_MODE_TEXT_HEIGHT = 36; + +static const unsigned int LAYOUT_BUTTON_X = 2; +static const unsigned int LAYOUT_BUTTON_Y = LAYOUT_MODE_TEXT_Y + LAYOUT_MODE_TEXT_HEIGHT + 1; +static const unsigned int LAYOUT_BUTTON_WIDTH = 60; +static const unsigned int LAYOUT_BUTTON_HEIGHT = 36; +static const unsigned int LAYOUT_BUTTON_PITCH_X = 64; +static const unsigned int LAYOUT_BUTTON_PITCH_Y = 40; + +static const unsigned int LAYOUT_CW_TEXT_X = 0; +static const unsigned int LAYOUT_CW_TEXT_Y = LAYOUT_BUTTON_Y + 3*LAYOUT_BUTTON_PITCH_Y + 1; +static const unsigned int LAYOUT_CW_TEXT_WIDTH = 220; +static const unsigned int LAYOUT_CW_TEXT_HEIGHT = 36; + +static const unsigned int LAYOUT_VERSION_TEXT_X = LAYOUT_CW_TEXT_X + LAYOUT_CW_TEXT_WIDTH + 1; +static const unsigned int LAYOUT_VERSION_TEXT_Y = LAYOUT_CW_TEXT_Y; +static const unsigned int LAYOUT_VERSION_TEXT_WIDTH = 320 - LAYOUT_CW_TEXT_WIDTH - 1; +static const unsigned int LAYOUT_VERSION_TEXT_HEIGHT = LAYOUT_CW_TEXT_HEIGHT; + +static const unsigned int LAYOUT_TX_X = 280; +static const unsigned int LAYOUT_TX_Y = LAYOUT_MODE_TEXT_Y; +static const unsigned int LAYOUT_TX_WIDTH = 40; +static const unsigned int LAYOUT_TX_HEIGHT = 36; + +void drawTx() +{ + if(globalSettings.txActive){ + strncpy_P(b,(const char*)F("TX"),sizeof(b)); + displayText(b,LAYOUT_TX_X,LAYOUT_TX_Y,LAYOUT_TX_WIDTH,LAYOUT_TX_HEIGHT,COLOR_ACTIVE_TEXT,COLOR_ACTIVE_BACKGROUND,COLOR_BACKGROUND); + } + else{ + displayFillrect(LAYOUT_TX_X,LAYOUT_TX_Y,LAYOUT_TX_WIDTH,LAYOUT_TX_HEIGHT,COLOR_BACKGROUND); + } +} + +void drawVersion() +{ + strncpy_P(b,VERSION_STRING,sizeof(b)); + displayText(b,LAYOUT_VERSION_TEXT_X,LAYOUT_VERSION_TEXT_Y,LAYOUT_VERSION_TEXT_WIDTH,LAYOUT_VERSION_TEXT_HEIGHT,COLOR_VERSION_TEXT,COLOR_BACKGROUND,COLOR_BACKGROUND); +} + +void drawCallsign() +{ + strncpy_P(b,CALLSIGN_STRING,sizeof(b)); + displayText(b,LAYOUT_CW_TEXT_X,LAYOUT_CW_TEXT_Y,LAYOUT_CW_TEXT_WIDTH,LAYOUT_CW_TEXT_HEIGHT,COLOR_VERSION_TEXT,COLOR_BACKGROUND,COLOR_BACKGROUND); +} + +void toVfoA(char* text_out, const uint16_t max_text_size); +ButtonStatus_e bsVfoA(); +void osVfoA(); +constexpr Button bVfoA PROGMEM = { + LAYOUT_VFO_LABEL_X + 0*LAYOUT_VFO_LABEL_PITCH_X, + LAYOUT_VFO_LABEL_Y, + LAYOUT_VFO_LABEL_WIDTH, + LAYOUT_VFO_LABEL_HEIGHT, + nullptr, + toVfoA, + bsVfoA, + osVfoA, + 'A' +}; + +void toVfoB(char* text_out, const uint16_t max_text_size); +ButtonStatus_e bsVfoB(); +void osVfoB(); +constexpr Button bVfoB PROGMEM = { + LAYOUT_VFO_LABEL_X + 1*LAYOUT_VFO_LABEL_PITCH_X, + LAYOUT_VFO_LABEL_Y, + LAYOUT_VFO_LABEL_WIDTH, + LAYOUT_VFO_LABEL_HEIGHT, + nullptr, + toVfoB, + bsVfoB, + osVfoB, + 'B' +}; + +constexpr char txtRit [] PROGMEM = "RIT"; +ButtonStatus_e bsRit(); +void osRit(); +constexpr Button bRit PROGMEM = { + LAYOUT_BUTTON_X + 0*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 0*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtRit, + nullptr, + bsRit, + osRit, + 'R' +}; + +constexpr char txtUsb [] PROGMEM = "USB"; +ButtonStatus_e bsUsb(); +void osUsb(); +constexpr Button bUsb PROGMEM = { + LAYOUT_BUTTON_X + 1*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 0*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtUsb, + nullptr, + bsUsb, + osUsb, + 'U' +}; + +constexpr char txtLsb [] PROGMEM = "LSB"; +ButtonStatus_e bsLsb(); +void osLsb(); +constexpr Button bLsb PROGMEM = { + LAYOUT_BUTTON_X + 2*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 0*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtLsb, + nullptr, + bsLsb, + osLsb, + 'L' +}; + +constexpr char txtCw [] PROGMEM = "CW"; +ButtonStatus_e bsCw(); +void osCw(); +constexpr Button bCw PROGMEM = { + LAYOUT_BUTTON_X + 3*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 0*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtCw, + nullptr, + bsCw, + osCw, + 'C' +}; + +constexpr char txtSpl [] PROGMEM = "SPL"; +ButtonStatus_e bsSpl(); +void osSpl(); +constexpr Button bSpl PROGMEM = { + LAYOUT_BUTTON_X + 4*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 0*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtSpl, + nullptr, + bsSpl, + osSpl, + 'S' +}; + +constexpr char txt80 [] PROGMEM = "80"; +ButtonStatus_e bs80(); +void os80(); +constexpr Button b80 PROGMEM = { + LAYOUT_BUTTON_X + 0*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 1*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txt80, + nullptr, + bs80, + os80, + '8' +}; + +constexpr char txt40 [] PROGMEM = "40"; +ButtonStatus_e bs40(); +void os40(); +constexpr Button b40 PROGMEM = { + LAYOUT_BUTTON_X + 1*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 1*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txt40, + nullptr, + bs40, + os40, + '4' +}; + +constexpr char txt30 [] PROGMEM = "30"; +ButtonStatus_e bs30(); +void os30(); +constexpr Button b30 PROGMEM = { + LAYOUT_BUTTON_X + 2*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 1*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txt30, + nullptr, + bs30, + os30, + '3' +}; + +constexpr char txt20 [] PROGMEM = "20"; +ButtonStatus_e bs20(); +void os20(); +constexpr Button b20 PROGMEM = { + LAYOUT_BUTTON_X + 3*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 1*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txt20, + nullptr, + bs20, + os20, + '2' +}; + +constexpr char txt17 [] PROGMEM = "17"; +ButtonStatus_e bs17(); +void os17(); +constexpr Button b17 PROGMEM = { + LAYOUT_BUTTON_X + 4*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 1*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txt17, + nullptr, + bs17, + os17, + '7' +}; + +constexpr char txt15 [] PROGMEM = "15"; +ButtonStatus_e bs15(); +void os15(); +constexpr Button b15 PROGMEM = { + LAYOUT_BUTTON_X + 0*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 2*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txt15, + nullptr, + bs15, + os15, + '5' +}; + +constexpr char txt10 [] PROGMEM = "10"; +ButtonStatus_e bs10(); +void os10(); +constexpr Button b10 PROGMEM = { + LAYOUT_BUTTON_X + 1*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 2*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txt10, + nullptr, + bs10, + os10, + '1' +}; + +constexpr char txtQuickList [] PROGMEM = "\x83";//star icon +ButtonStatus_e bsIgnore(); +void osQuickList(); +constexpr Button bQuickList PROGMEM = { + LAYOUT_BUTTON_X + 2*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 2*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtQuickList, + nullptr, + bsIgnore, + osQuickList, + 'Q' +}; + +constexpr char txtMenu [] PROGMEM = "\x7F";//gear icon +ButtonStatus_e bsIgnore(); +void osMenu(); +constexpr Button bMenu PROGMEM = { + LAYOUT_BUTTON_X + 3*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 2*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtMenu, + nullptr, + bsIgnore, + osMenu, + 'M' +}; + +constexpr char txtNumpad [] PROGMEM = "\x82";//numpad icon +ButtonStatus_e bsIgnore(); +void osNumpad(); +constexpr Button bNumpad PROGMEM = { + LAYOUT_BUTTON_X + 4*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 2*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtNumpad, + nullptr, + bsIgnore, + osNumpad, + 'F' +}; + +const Button* const mainMenuButtons [] PROGMEM = { + &bVfoA, &bVfoB, + + &bRit, &bUsb, &bLsb, &bCw, &bSpl, + &b80, &b40, &b30, &b20, &b17, + &b15, &b10, &bQuickList, &bMenu, &bNumpad +}; + +const uint8_t MAIN_MENU_NUM_BUTTONS = sizeof(mainMenuButtons) / sizeof(mainMenuButtons[0]); + +void updateBandButtons(const uint32_t old_freq) +{ + const Button* band_buttons[] = {&b80,&b40,&b30,&b20,&b17,&b15,&b10}; + const uint8_t bands [] = { 80, 40, 30, 20, 17, 15, 10}; + const uint32_t curr_freq = GetActiveVfoFreq(); + + Button button; + for(uint8_t i = 0; i < sizeof(bands)/sizeof(bands[0]); ++i){ + if(isFreqInBand(old_freq,bands[i]) != isFreqInBand(curr_freq,bands[i])){ + extractAndDrawButton(&button,band_buttons[i]); + morseBool(ButtonStatus_e::Active == button.status()); + } + } +} + +void toVfo(char* text_out, const uint16_t max_text_size, const Vfo_e vfo) +{ + if(max_text_size < 2){ + return;//Can't do much with that space + } + if(max_text_size < (3+10+1)){ + //Give an indicator that's debuggable + text_out[0] = 'X'; + text_out[1] = '\0'; + return; + } + + //Normal operation + if (globalSettings.splitOn){ + if(vfo == globalSettings.activeVfo){ + text_out[0] = 'R'; + } + else{ + text_out[0] = 'T'; + } + } + else{ + if(VFO_A == vfo){ + text_out[0] = 'A'; + } + else if(VFO_B == vfo){ + text_out[0] = 'B'; + } + else{ + text_out[0] = '?'; + } + } + text_out[1] = ':'; + text_out[2] = ' '; + if(VFO_A == vfo){ + formatFreq(globalSettings.vfoA.frequency, text_out+3, max_text_size-3, 10); + } + else if(VFO_B == vfo){ + formatFreq(globalSettings.vfoB.frequency, text_out+3, max_text_size-3, 10); + } + else{ + text_out[3] = '?'; + text_out[4] = '\0'; + } +} + +ButtonStatus_e bsVfo(const Vfo_e vfo){ + return (vfo == globalSettings.activeVfo) ? ButtonStatus_e::Active : ButtonStatus_e::Inactive; +} + +void osVfo(const Vfo_e vfo){ + const uint32_t old_freq = GetActiveVfoFreq(); + + if(globalSettings.ritOn){ + osRit();//Turn off RIT + } + + globalSettings.activeVfo = vfo; + SaveSettingsToEeprom(); + + ltoa(GetActiveVfoFreq(),b,10); + morseText(b); + + Button button; + extractAndDrawButton(&button,&bVfoA); + extractAndDrawButton(&button,&bVfoB); + updateBandButtons(old_freq); + updateSidebandButtons(); +} + +void toVfoA(char* text_out, const uint16_t max_text_size){ + toVfo(text_out,max_text_size,Vfo_e::VFO_A); +} +ButtonStatus_e bsVfoA(){ + return bsVfo(Vfo_e::VFO_A); +} +void osVfoA(){ + osVfo(Vfo_e::VFO_A); +} + +void toVfoB(char* text_out, const uint16_t max_text_size){ + toVfo(text_out,max_text_size,Vfo_e::VFO_B); +} +ButtonStatus_e bsVfoB(){ + return bsVfo(Vfo_e::VFO_B); +} +void osVfoB(){ + osVfo(Vfo_e::VFO_B); +} + +ButtonStatus_e bsRit(){ + return globalSettings.ritOn ? ButtonStatus_e::Active : ButtonStatus_e::Inactive; +} +void osRit(){ + Button button; + if(!globalSettings.ritOn){ + globalSettings.ritOn = true; + globalSettings.ritFrequency = GetActiveVfoFreq(); + + strncpy_P(b,(const char*)F("TX: "),sizeof(b)); + formatFreq(globalSettings.ritFrequency, b + strlen(b), sizeof(b)-strlen(b)); + if (VFO_A == globalSettings.activeVfo){ + displayText(b, LAYOUT_VFO_LABEL_X + 0*LAYOUT_VFO_LABEL_PITCH_X, LAYOUT_MODE_TEXT_Y, LAYOUT_VFO_LABEL_WIDTH, LAYOUT_MODE_TEXT_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND, TextJustification_e::Left); + } + else{ + displayText(b, LAYOUT_VFO_LABEL_X + 1*LAYOUT_VFO_LABEL_PITCH_X, LAYOUT_MODE_TEXT_Y, LAYOUT_VFO_LABEL_WIDTH, LAYOUT_MODE_TEXT_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND, TextJustification_e::Left); + } + } + else{ + globalSettings.ritOn = false; + setFrequency(globalSettings.ritFrequency); + + displayFillrect(LAYOUT_MODE_TEXT_X,LAYOUT_MODE_TEXT_Y,LAYOUT_MODE_TEXT_WIDTH,LAYOUT_MODE_TEXT_HEIGHT, COLOR_BACKGROUND); + if(Vfo_e::VFO_A == globalSettings.activeVfo){ + extractAndDrawButton(&button,&bVfoA); + } + else{ + extractAndDrawButton(&button,&bVfoB); + } + } + + extractAndDrawButton(&button,&bRit); +} + +void osSidebandMode(VfoMode_e mode){ + SetActiveVfoMode(mode); + setFrequency(GetActiveVfoFreq()); + SaveSettingsToEeprom(); + + Button button; + extractAndDrawButton(&button,&bUsb); + extractAndDrawButton(&button,&bLsb); +} + +void updateSidebandButtons() +{ + osSidebandMode(GetActiveVfoMode()); +} + +ButtonStatus_e bsUsb(){ + return (VfoMode_e::VFO_MODE_USB == GetActiveVfoMode()) ? ButtonStatus_e::Active : ButtonStatus_e::Inactive; +} + +void osUsb(){ + osSidebandMode(VfoMode_e::VFO_MODE_USB); +} + +ButtonStatus_e bsLsb(){ + return (VfoMode_e::VFO_MODE_LSB == GetActiveVfoMode()) ? ButtonStatus_e::Active : ButtonStatus_e::Inactive; +} + +void osLsb(){ + osSidebandMode(VfoMode_e::VFO_MODE_LSB); +} + +ButtonStatus_e bsCw(){ + return (TuningMode_e::TUNE_CW == globalSettings.tuningMode) ? ButtonStatus_e::Active : ButtonStatus_e::Inactive; +} + +void osCw(){ + if(TuningMode_e::TUNE_CW != globalSettings.tuningMode){ + globalSettings.tuningMode = TuningMode_e::TUNE_CW; + } + else{ + globalSettings.tuningMode = TuningMode_e::TUNE_SSB; + } + + setFrequency(GetActiveVfoFreq()); + + Button button; + extractAndDrawButton(&button,&bCw); +} + +ButtonStatus_e bsSpl(){ + return globalSettings.splitOn ? ButtonStatus_e::Active : ButtonStatus_e::Inactive; +} + +void osSpl(){ + globalSettings.splitOn = !globalSettings.splitOn; + + Button button; + extractAndDrawButton(&button,&bSpl); + extractAndDrawButton(&button,&bVfoA); + extractAndDrawButton(&button,&bVfoB); +} + +ButtonStatus_e bsBand(const uint8_t band){ + return isFreqInBand(GetActiveVfoFreq(),band) ? ButtonStatus_e::Active : ButtonStatus_e::Inactive; +} + +void osBand(const uint8_t band){ + const uint32_t old_freq = GetActiveVfoFreq(); + SetActiveVfoFreq(getFreqInBand(old_freq,band)); + if(autoSelectSidebandChanged(old_freq)){ + updateSidebandButtons(); + } + + Button button; + if(Vfo_e::VFO_A == globalSettings.activeVfo){ + extractAndDrawButton(&button,&bVfoA); + } + else if(Vfo_e::VFO_B == globalSettings.activeVfo){ + extractAndDrawButton(&button,&bVfoB); + } + + updateBandButtons(old_freq); +} + +ButtonStatus_e bs80(){ + return bsBand(80); +} + +void os80(){ + osBand(80); +} + +ButtonStatus_e bs40(){ + return bsBand(40); +} + +void os40(){ + osBand(40); +} + +ButtonStatus_e bs30(){ + return bsBand(30); +} + +void os30(){ + osBand(30); +} + +ButtonStatus_e bs20(){ + return bsBand(20); +} + +void os20(){ + osBand(20); +} + +ButtonStatus_e bs17(){ + return bsBand(17); +} + +void os17(){ + osBand(17); +} + +ButtonStatus_e bs15(){ + return bsBand(15); +} + +void os15(){ + osBand(15); +} + +ButtonStatus_e bs10(){ + return bsBand(10); +} + +void os10(){ + osBand(10); +} + +ButtonStatus_e bsIgnore(){ + return ButtonStatus_e::Stateless; +} + +void osQuickList(){ + enterSubmenu(quickListMenu); +} + +void osMenu(){ + enterSubmenu(setupMenu); +} + +void osNumpad(){ + enterSubmenu(numpadMenu); +} diff --git a/menu_main_buttons.h b/menu_main_buttons.h new file mode 100644 index 0000000..8d5a0ae --- /dev/null +++ b/menu_main_buttons.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "button.h" + +extern const Button* const mainMenuButtons[]; +extern const uint8_t MAIN_MENU_NUM_BUTTONS; + +extern const Button bVfoA; +extern const Button bVfoB; +extern const Button bRit; +extern const Button bCw; +extern const Button bSpl; +void updateBandButtons(const uint32_t old_freq); +void updateSidebandButtons(); +void drawTx(); +void drawVersion(); +void drawCallsign(); diff --git a/menu_np_ql_shared.cpp b/menu_np_ql_shared.cpp new file mode 100644 index 0000000..fc988d1 --- /dev/null +++ b/menu_np_ql_shared.cpp @@ -0,0 +1,52 @@ +#include "menu_np_ql_shared.h" + +#include + +#include "button.h" +#include "menu_utils.h" + +MenuReturn_e runNpQlShared(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob, + int16_t *const menuSelectedItemRaw, + const Button *const *const menu_buttons, + const uint8_t menu_num_buttons, + ButtonPress_e *const selection_mode) +{ + if(ButtonPress_e::NotPressed != tuner_button){ + uint8_t menu_index = *menuSelectedItemRaw/MENU_KNOB_COUNTS_PER_ITEM; + Button button; + Button* bp; + memcpy_P(&bp,&(menu_buttons[menu_index]),sizeof(bp)); + memcpy_P(&button,bp,sizeof(button)); + *selection_mode = tuner_button; + button.on_select(); + }//tuner_button + + else if(ButtonPress_e::NotPressed != touch_button){ + Button button; + if(findPressedButton(menu_buttons,menu_num_buttons,&button,touch_point)){ + *selection_mode = touch_button; + button.on_select(); + } + else{ + //Touch detected, but not on our buttons, so ignore + } + }//touch_button + + else{//Neither button input type found, so handle the knob + adjustSelector(menuSelectedItemRaw, + knob, + menu_buttons, + menu_num_buttons, + MorsePlaybackType_e::PlayChar); + } + + if(ButtonPress_e::NotPressed == *selection_mode){ + return MenuReturn_e::ExitedRedraw; + } + else{ + return MenuReturn_e::StillActive; + } +} diff --git a/menu_np_ql_shared.h b/menu_np_ql_shared.h new file mode 100644 index 0000000..9a260c9 --- /dev/null +++ b/menu_np_ql_shared.h @@ -0,0 +1,14 @@ +#pragma once + +#include "button.h" +#include "menu.h" + +MenuReturn_e runNpQlShared(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob, + int16_t *const menuSelectedItemRaw, + const Button *const *const menu_buttons, + const uint8_t menu_num_buttons, + ButtonPress_e *const selection_mode); + diff --git a/menu_numpad.cpp b/menu_numpad.cpp new file mode 100644 index 0000000..1fa060f --- /dev/null +++ b/menu_numpad.cpp @@ -0,0 +1,61 @@ +#include "menu_numpad.h" +#include "menu_numpad_buttons.h" + +#include + +#include "color_theme.h" +#include "menu_np_ql_shared.h" +#include "menu_utils.h" +#include "nano_gui.h" + +void initNumpad(void); +MenuReturn_e runNumpad(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob); +Menu_t numpad_menu = { + initNumpad, + runNumpad, + nullptr +}; + +Menu_t *const numpadMenu = &numpad_menu; + +int16_t numpadMenuSelectedItemRaw = 0;//Allow negative only for easier checks on wrap around + +void drawNumpad(void) +{ + displayFillrect(0,47,320,200,COLOR_BACKGROUND); + Button button; + Button* bp; + for(uint8_t i = 0; i < NUMPAD_MENU_NUM_BUTTONS; ++i){ + memcpy_P(&bp, &(numpadMenuButtons[i]), sizeof(bp)); + extractAndDrawButton(&button,bp); + } +} + +void initNumpad(void) +{ + numpadMenuFrequency = 0; + numpadSelectionMode = ButtonPress_e::LongPress;//Anything except NotPressed + drawNumpad(); + initSelector(&numpadMenuSelectedItemRaw, + numpadMenuButtons, + NUMPAD_MENU_NUM_BUTTONS, + MorsePlaybackType_e::PlayChar); +} + +MenuReturn_e runNumpad(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob) +{ + return runNpQlShared(tuner_button, + touch_button, + touch_point, + knob, + &numpadMenuSelectedItemRaw, + numpadMenuButtons, + NUMPAD_MENU_NUM_BUTTONS, + &numpadSelectionMode); +} \ No newline at end of file diff --git a/menu_numpad.h b/menu_numpad.h new file mode 100644 index 0000000..ae53306 --- /dev/null +++ b/menu_numpad.h @@ -0,0 +1,2 @@ +#include "menu.h" +extern Menu_t* const numpadMenu; diff --git a/menu_numpad_buttons.cpp b/menu_numpad_buttons.cpp new file mode 100644 index 0000000..c9cdabe --- /dev/null +++ b/menu_numpad_buttons.cpp @@ -0,0 +1,161 @@ +#include "menu_numpad_buttons.h" + +#include +#include //F() + +#include "color_theme.h" +#include "nano_gui.h" +#include "scratch_space.h" +#include "settings.h" +#include "tuner.h"//setFrequency +#include "utils.h" + +static const unsigned int LAYOUT_MODE_TEXT_X = 60; +static const unsigned int LAYOUT_MODE_TEXT_Y = 47; +static const unsigned int LAYOUT_MODE_TEXT_WIDTH = 140; +static const unsigned int LAYOUT_MODE_TEXT_HEIGHT = 36; + +static const unsigned int LAYOUT_BUTTON_X = 2; +static const unsigned int LAYOUT_BUTTON_Y = LAYOUT_MODE_TEXT_Y + LAYOUT_MODE_TEXT_HEIGHT + 1; +static const unsigned int LAYOUT_BUTTON_WIDTH = 60; +static const unsigned int LAYOUT_BUTTON_HEIGHT = 36; +static const unsigned int LAYOUT_BUTTON_PITCH_X = 64; +static const unsigned int LAYOUT_BUTTON_PITCH_Y = 40; + +uint32_t numpadMenuFrequency; +ButtonPress_e numpadSelectionMode; + +#define D_STRINGIFY(x) #x +#define D_STRING(x) D_STRINGIFY(x) +#define NUMBER_BUTTON_GENERATE(number,x,y) \ + constexpr char txt##number [] PROGMEM = "" D_STRING(number);\ + ButtonStatus_e bs##number();\ + void os##number();\ + constexpr Button b##number PROGMEM = {\ + LAYOUT_BUTTON_X + x*LAYOUT_BUTTON_PITCH_X,\ + LAYOUT_BUTTON_Y + y*LAYOUT_BUTTON_PITCH_Y,\ + LAYOUT_BUTTON_WIDTH,\ + LAYOUT_BUTTON_HEIGHT,\ + txt##number,\ + nullptr,\ + bsNumpad,\ + os##number,\ + '0'+number\ + } + +ButtonStatus_e bsNumpad(void){ + return ButtonStatus_e::Stateless; +} + + +// 1 2 3 Ok +// 4 5 6 0 <- +// 7 8 9 Can +NUMBER_BUTTON_GENERATE(1,0,0); +NUMBER_BUTTON_GENERATE(2,1,0); +NUMBER_BUTTON_GENERATE(3,2,0); +NUMBER_BUTTON_GENERATE(4,0,1); +NUMBER_BUTTON_GENERATE(5,1,1); +NUMBER_BUTTON_GENERATE(6,2,1); +NUMBER_BUTTON_GENERATE(7,0,2); +NUMBER_BUTTON_GENERATE(8,1,2); +NUMBER_BUTTON_GENERATE(9,2,2); +NUMBER_BUTTON_GENERATE(0,3,1); + +constexpr char txtOk [] PROGMEM = "OK"; +void osOk(); +constexpr Button bOk PROGMEM = { + LAYOUT_BUTTON_X + 4*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 0*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtOk, + nullptr, + bsNumpad, + osOk, + 'K' +}; + +constexpr char txtBackspace [] PROGMEM = "<-"; +void osBackspace(); +constexpr Button bBackspace PROGMEM = { + LAYOUT_BUTTON_X + 4*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 1*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtBackspace, + nullptr, + bsNumpad, + osBackspace, + 'B' +}; + +constexpr char txtCancel [] PROGMEM = "Can"; +void osCancel(); +constexpr Button bCancel PROGMEM = { + LAYOUT_BUTTON_X + 4*LAYOUT_BUTTON_PITCH_X, + LAYOUT_BUTTON_Y + 2*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtCancel, + nullptr, + bsNumpad, + osCancel, + 'C' +}; + +//Declare in menu select order, not graphical order +const Button* const numpadMenuButtons [] PROGMEM = { + &b1, &b2, &b3, &b4, &b5, &b6, &b7, &b8, &b9, &b0, + &bOk, &bBackspace, &bCancel +}; +const uint8_t NUMPAD_MENU_NUM_BUTTONS = sizeof(numpadMenuButtons)/sizeof(numpadMenuButtons[0]); + +void updateCurrentEnteredFrequency(void) +{ + formatFreq(numpadMenuFrequency,b,sizeof(b),0); + strncat_P(b,(const char*)F(" Hz"),sizeof(b)-strlen(b)); + displayText(b,LAYOUT_MODE_TEXT_X,LAYOUT_MODE_TEXT_Y,LAYOUT_MODE_TEXT_WIDTH,LAYOUT_MODE_TEXT_HEIGHT,COLOR_TEXT,COLOR_BACKGROUND,COLOR_BACKGROUND,TextJustification_e::Right); +} + +void osNumpad(uint8_t new_digit) +{ + numpadMenuFrequency *= 10; + numpadMenuFrequency += new_digit; + updateCurrentEnteredFrequency(); +} + +void osBackspace(void) +{ + numpadMenuFrequency /= 10; + updateCurrentEnteredFrequency(); +} + +#define NUMBER_ONSELECT_GENERATE(number) \ + void os##number(void){\ + osNumpad(number);\ + } + +NUMBER_ONSELECT_GENERATE(0); +NUMBER_ONSELECT_GENERATE(1); +NUMBER_ONSELECT_GENERATE(2); +NUMBER_ONSELECT_GENERATE(3); +NUMBER_ONSELECT_GENERATE(4); +NUMBER_ONSELECT_GENERATE(5); +NUMBER_ONSELECT_GENERATE(6); +NUMBER_ONSELECT_GENERATE(7); +NUMBER_ONSELECT_GENERATE(8); +NUMBER_ONSELECT_GENERATE(9); + +void osOk(void) +{ + SetActiveVfoFreq(numpadMenuFrequency); + SaveSettingsToEeprom(); + setFrequency(numpadMenuFrequency); + numpadSelectionMode = ButtonPress_e::NotPressed; +} + +void osCancel(void) +{ + numpadSelectionMode = ButtonPress_e::NotPressed; +} diff --git a/menu_numpad_buttons.h b/menu_numpad_buttons.h new file mode 100644 index 0000000..3628a3f --- /dev/null +++ b/menu_numpad_buttons.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include "button.h" +#include "button_press_e.h" + +extern const Button* const numpadMenuButtons[]; +extern const uint8_t NUMPAD_MENU_NUM_BUTTONS; + +extern const uint32_t NUMPAD_MENU_EXIT_FREQ; +extern uint32_t numpadMenuFrequency; +extern ButtonPress_e numpadSelectionMode;//NotPressed means exit menu. Other press types are consumed by selectors diff --git a/menu_quicklist.cpp b/menu_quicklist.cpp new file mode 100644 index 0000000..63526af --- /dev/null +++ b/menu_quicklist.cpp @@ -0,0 +1,64 @@ +#include "menu_quicklist.h" +#include "menu_quicklist_buttons.h" + +#include +#include //F() + +#include "color_theme.h" +#include "menu_np_ql_shared.h" +#include "menu_utils.h" +#include "nano_gui.h" +#include "scratch_space.h" + +void initQuickList(void); +MenuReturn_e runQuickList(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob); +Menu_t quickList_menu = { + initQuickList, + runQuickList, + nullptr +}; + +Menu_t *const quickListMenu = &quickList_menu; + +int16_t quickListMenuSelectedItemRaw = 0;//Allow negative only for easier checks on wrap around + +void drawQuickList(void) +{ + displayFillrect(0,47,320,200,COLOR_BACKGROUND); + Button button; + Button* bp; + for(uint8_t i = 0; i < QUICKLIST_MENU_NUM_BUTTONS; ++i){ + memcpy_P(&bp, &(quickListMenuButtons[i]), sizeof(bp)); + extractAndDrawButton(&button,bp); + } + strncpy_P(b,(const char*)F("Short press = load\nLong press = save"),sizeof(b)); + displayText(b,10,47,170,200,COLOR_TEXT,COLOR_BACKGROUND,COLOR_BACKGROUND); +} + +void initQuickList(void) +{ + quickListSelectionMode = ButtonPress_e::ShortPress;//Anything except NotPressed + drawQuickList(); + initSelector(&quickListMenuSelectedItemRaw, + quickListMenuButtons, + QUICKLIST_MENU_NUM_BUTTONS, + MorsePlaybackType_e::PlayChar); +} + +MenuReturn_e runQuickList(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob) +{ + return runNpQlShared(tuner_button, + touch_button, + touch_point, + knob, + &quickListMenuSelectedItemRaw, + quickListMenuButtons, + QUICKLIST_MENU_NUM_BUTTONS, + &quickListSelectionMode); +} \ No newline at end of file diff --git a/menu_quicklist.h b/menu_quicklist.h new file mode 100644 index 0000000..62fcbc0 --- /dev/null +++ b/menu_quicklist.h @@ -0,0 +1,2 @@ +#include "menu.h" +extern Menu_t* const quickListMenu; diff --git a/menu_quicklist_buttons.cpp b/menu_quicklist_buttons.cpp new file mode 100644 index 0000000..5b4fdfa --- /dev/null +++ b/menu_quicklist_buttons.cpp @@ -0,0 +1,137 @@ +#include "menu_quicklist_buttons.h" + +#include +#include //F() + +#include "color_theme.h" +#include "nano_gui.h" +#include "scratch_space.h" +#include "settings.h" +#include "utils.h" + +static const unsigned int LAYOUT_BUTTON_X = 180; +static const unsigned int LAYOUT_BUTTON_Y = 47; +static const unsigned int LAYOUT_BUTTON_WIDTH = 140; +static const unsigned int LAYOUT_BUTTON_HEIGHT = 36; +static const unsigned int LAYOUT_BUTTON_PITCH_X = LAYOUT_BUTTON_WIDTH + 1; +static const unsigned int LAYOUT_BUTTON_PITCH_Y = 40; + +ButtonPress_e quickListSelectionMode; + +#define D_STRINGIFY(x) #x +#define D_STRING(x) D_STRINGIFY(x) +#define QUICKLIST_BUTTON_GENERATE(number) \ + void toQL##number(char* text_out, const uint16_t max_text_size);\ + void osQL##number();\ + constexpr Button bQL##number PROGMEM = {\ + LAYOUT_BUTTON_X,\ + LAYOUT_BUTTON_Y + number*LAYOUT_BUTTON_PITCH_Y,\ + LAYOUT_BUTTON_WIDTH,\ + LAYOUT_BUTTON_HEIGHT,\ + nullptr,\ + toQL##number,\ + bsQuickList,\ + osQL##number,\ + '0'+number\ + } + +ButtonStatus_e bsQuickList(void){ + return ButtonStatus_e::Stateless; +} + +//Unfortunately there's no easy way to auto-generate NUM_QUICKLIST_SETTINGS copies of this +QUICKLIST_BUTTON_GENERATE(0); +QUICKLIST_BUTTON_GENERATE(1); +QUICKLIST_BUTTON_GENERATE(2); +QUICKLIST_BUTTON_GENERATE(3); + +constexpr char txtQLCancel [] PROGMEM = "Can"; +void osQLCancel(); +constexpr Button bQLCancel PROGMEM = { + LAYOUT_BUTTON_X, + LAYOUT_BUTTON_Y + 4*LAYOUT_BUTTON_PITCH_Y, + LAYOUT_BUTTON_WIDTH, + LAYOUT_BUTTON_HEIGHT, + txtQLCancel, + nullptr, + bsQuickList, + osQLCancel, + 'C' +}; + +//Declare in menu select order, not graphical order +const Button* const quickListMenuButtons [] PROGMEM = { + &bQL0, &bQL1, &bQL2, &bQL3, &bQLCancel +}; +const uint8_t QUICKLIST_MENU_NUM_BUTTONS = sizeof(quickListMenuButtons)/sizeof(quickListMenuButtons[0]); + +void osQuickListRecall(uint8_t index) +{ + SetActiveVfoFreq(globalSettings.quickList[index].frequency); + SetActiveVfoMode(globalSettings.quickList[index].mode); +} + +void osQuickListSave(uint8_t index) +{ + globalSettings.quickList[index].frequency = GetActiveVfoFreq(); + globalSettings.quickList[index].mode = GetActiveVfoMode(); + SaveSettingsToEeprom(); +} + +void osQuickList(uint8_t index) +{ + if(ButtonPress_e::ShortPress == quickListSelectionMode){ + osQuickListRecall(index); + } + else if(ButtonPress_e::LongPress == quickListSelectionMode){ + osQuickListSave(index); + } + quickListSelectionMode = ButtonPress_e::NotPressed;//Selection was made. Exit. +} + +#define QUICKLIST_ONSELECT_GENERATE(number) \ + void osQL##number(void){\ + osQuickList(number);\ + } + +//Unfortunately there's no easy way to auto-generate NUM_QUICKLIST_SETTINGS copies of this +QUICKLIST_ONSELECT_GENERATE(0); +QUICKLIST_ONSELECT_GENERATE(1); +QUICKLIST_ONSELECT_GENERATE(2); +QUICKLIST_ONSELECT_GENERATE(3); + +#define QUICKLIST_TEXTOVERRIDE_GENERATE(number) \ + void toQL##number(char* text_out, const uint16_t max_text_size){\ + toQuickList(number, text_out, max_text_size);\ + } + +void toQuickList(uint8_t index, char* text_out, const uint16_t max_text_size) +{ + + if(max_text_size < 2){ + return;//Can't do much with that space + } + if(max_text_size < (3+10+1)){ + //Give an indicator that's debuggable + text_out[0] = 'X'; + text_out[1] = '\0'; + return; + } + + //Normal operation + text_out[0] = '0'+index;//Assume a 1-digit number for now + text_out[1] = ':'; + text_out[2] = ' '; + formatFreq(globalSettings.quickList[index].frequency, text_out+3, max_text_size-3, 10); +} + +//Unfortunately there's no easy way to auto-generate NUM_QUICKLIST_SETTINGS copies of this +QUICKLIST_TEXTOVERRIDE_GENERATE(0); +QUICKLIST_TEXTOVERRIDE_GENERATE(1); +QUICKLIST_TEXTOVERRIDE_GENERATE(2); +QUICKLIST_TEXTOVERRIDE_GENERATE(3); + +void osQLCancel() +{ + quickListSelectionMode = ButtonPress_e::NotPressed; +} diff --git a/menu_quicklist_buttons.h b/menu_quicklist_buttons.h new file mode 100644 index 0000000..d03017c --- /dev/null +++ b/menu_quicklist_buttons.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "button.h" +#include "button_press_e.h" + +extern const Button* const quickListMenuButtons[]; +extern const uint8_t QUICKLIST_MENU_NUM_BUTTONS; + +extern ButtonPress_e quickListSelectionMode;//NotPressed means exit menu. Other press types are consumed by selectors diff --git a/menu_utils.cpp b/menu_utils.cpp new file mode 100644 index 0000000..d5fcf6e --- /dev/null +++ b/menu_utils.cpp @@ -0,0 +1,103 @@ +#include "menu_utils.h" + +#include + +#include "button.h" +#include "color_theme.h" +#include "morse.h" +#include "nano_gui.h" +#include "utils.h" + +bool findPressedButton(const Button* const* buttons, + const uint8_t num_buttons, + Button *const button_out, + const Point touch_point) +{ + Button* bp; + for(uint16_t i = 0; i < num_buttons; ++i){ + memcpy_P(&bp,&(buttons[i]),sizeof(bp)); + memcpy_P(button_out,bp,sizeof(*button_out)); + if((button_out->x <= touch_point.x) + &&(touch_point.x <= button_out->x + button_out->w) + &&(button_out->y <= touch_point.y) + &&(touch_point.y <= button_out->y + button_out->h)){ + return true; + } + } + + return false; +} + +void movePuck(const Button *const b_old, + const Button *const b_new) +{ + if(nullptr != b_old){ + displayRect(b_old->x,b_old->y,b_old->w,b_old->h,COLOR_INACTIVE_BORDER); + } + if(nullptr != b_new){ + displayRect(b_new->x,b_new->y,b_new->w,b_new->h,COLOR_ACTIVE_BORDER); + } +} + +void playButtonMorse(const Button *const button, + const MorsePlaybackType_e play_type) +{ + if(MorsePlaybackType_e::PlayText == play_type){ + morseText(button->text); + } + else{ + morseLetter(button->morse); + } + + const ButtonStatus_e bs = button->status(); + if(ButtonStatus_e::Inactive == bs){ + morseBool(false); + } + else if(ButtonStatus_e::Active == bs){ + morseBool(true); + } +} + +void initSelector(int16_t *const raw_select_val_in_out, + const Button* const* buttons, + const uint8_t num_buttons, + const MorsePlaybackType_e play_type) +{ + *raw_select_val_in_out = 0; + if(0 < num_buttons){ + Button button; + Button* bp; + memcpy_P(&bp,&(buttons[0]),sizeof(bp)); + memcpy_P(&button,bp,sizeof(button)); + movePuck(nullptr,&button); + playButtonMorse(&button,play_type); + } +} + +void adjustSelector(int16_t *const raw_select_val_in_out, + const int16_t knob, + const Button* const* buttons, + const uint8_t num_buttons, + const MorsePlaybackType_e play_type) +{ + const uint8_t prev_select = (*raw_select_val_in_out)/MENU_KNOB_COUNTS_PER_ITEM; + *raw_select_val_in_out = LIMIT((*raw_select_val_in_out)+knob,0,num_buttons*MENU_KNOB_COUNTS_PER_ITEM - 1); + const uint8_t new_select = (*raw_select_val_in_out)/MENU_KNOB_COUNTS_PER_ITEM; + if(prev_select != new_select){ + Button prev_button; + Button* bp; + memcpy_P(&bp,&(buttons[prev_select]),sizeof(bp)); + memcpy_P(&prev_button,bp,sizeof(prev_button)); + Button new_button; + memcpy_P(&bp,&(buttons[new_select]),sizeof(bp)); + memcpy_P(&new_button,bp,sizeof(new_button)); + + movePuck(&prev_button,&new_button); + playButtonMorse(&new_button,play_type); + } +} + +void endSelector(const Button *const button) +{ + movePuck(button,nullptr); +} diff --git a/menu_utils.h b/menu_utils.h new file mode 100644 index 0000000..d84be30 --- /dev/null +++ b/menu_utils.h @@ -0,0 +1,27 @@ +#pragma once + +#include "button.h" +#include "menu.h" + +//Returns true if button was found, false otherwise +bool findPressedButton(const Button* const* buttons, + const uint8_t num_buttons, + Button *const button_out, + const Point touch_point); + +enum MorsePlaybackType_e : uint8_t { + PlayChar, + PlayText +}; +void initSelector(int16_t *const raw_select_val_in_out, + const Button* const* buttons, + const uint8_t num_buttons, + const MorsePlaybackType_e); + +void adjustSelector(int16_t *const raw_select_val_in_out, + int16_t knob, + const Button* const* buttons, + const uint8_t num_buttons, + const MorsePlaybackType_e); + +void endSelector(const Button *const button); diff --git a/morse.cpp b/morse.cpp index d1b6dd5..eb7d9e6 100644 --- a/morse.cpp +++ b/morse.cpp @@ -1,72 +1,79 @@ -#include -#include "ubitx.h" +#include "toneAC2/toneAC2.h" + +#include "encoder.h" #include "morse.h" +#include "pin_definitions.h" +#include "settings.h" + +struct Morse { + char letter; + unsigned char code; +}; + /* * Each byte of the morse table stores one letter. * The 0 is a dot, a 1 is a dash * From the Most significant byte onwards, the letter is padded with 1s. * The first zero after the 1s indicates the start of the letter, it MUST be discarded */ - -extern int cwSpeed; -struct Morse { - char letter; - unsigned char code; -}; - static const PROGMEM struct Morse morse_table[] = { -{'a', 0xf9}, // 11111001 -{'b', 0xe8}, // 11101000 -{'c', 0xea}, // 11101010 -{'d', 0xf4}, // 11110100 -{'e', 0xfc}, // 11111100 -{'f', 0xe4}, // 11100100 -{'g', 0xf6}, // 11110110 -{'h', 0xe0}, // 11100000 -{'i', 0xf8}, // 11111000 -{'j', 0xe7}, // 11100111 -{'k', 0xf6}, // 11110101 -{'l', 0xe4}, // 11100100 -{'m', 0xfb}, // 11111011 -{'n', 0xfa}, // 11111010 -{'o', 0xf7}, // 11110111 -{'p', 0xe6}, // 11100110 -{'q', 0xed}, // 11101101 -{'r', 0xf2}, // 11110010 -{'s', 0xf0}, // 11110000 -{'t', 0xfd}, // 11111101 -{'u', 0xf1}, // 11110001 -{'v', 0xe1}, // 11100001 -{'w', 0xf3}, // 11110011 -{'x', 0xe9}, // 11101001 -{'y', 0xe3}, // 11101011 -{'z', 0xec}, // 11101100 -{'1', 0xcf}, // 11001111 -{'2', 0xc7}, // 11000111 -{'3', 0xc3}, // 11000011 -{'4', 0xc1}, // 11000001 -{'5', 0xc0}, // 11000000 -{'6', 0xd0}, // 11010000 -{'7', 0xd8}, // 11011000 -{'8', 0xdc}, // 11011100 -{'9', 0xde}, // 11011110 -{'0', 0xdf}, // 11011111 -{'.', 0xd5}, // 110010101 -{',', 0xd3}, // 110110011 //AD7U 20191217 -{'?', 0xcc}, // 11001100 //AD7U 20191217 - Added +{'a', 0b11111001}, +{'b', 0b11101000}, +{'c', 0b11101010}, +{'d', 0b11110100}, +{'e', 0b11111100}, +{'f', 0b11100010}, +{'g', 0b11110110}, +{'h', 0b11100000}, +{'i', 0b11111000}, +{'j', 0b11100111}, +{'k', 0b11110101}, +{'l', 0b11100100}, +{'m', 0b11111011}, +{'n', 0b11111010}, +{'o', 0b11110111}, +{'p', 0b11100110}, +{'q', 0b11101101}, +{'r', 0b11110010}, +{'s', 0b11110000}, +{'t', 0b11111101}, +{'u', 0b11110001}, +{'v', 0b11100001}, +{'w', 0b11110011}, +{'x', 0b11101001}, +{'y', 0b11101011}, +{'z', 0b11101100}, +{'1', 0b11001111}, +{'2', 0b11000111}, +{'3', 0b11000011}, +{'4', 0b11000001}, +{'5', 0b11000000}, +{'6', 0b11010000}, +{'7', 0b11011000}, +{'8', 0b11011100}, +{'9', 0b11011110}, +{'0', 0b11011111}, +{'.', 0b10010101}, +{',', 0b10110011}, +{'?', 0b10001100}, +{ 2 , 0b11010101}, // ASCII 0x02 is Start of Text - +{ 4 , 0b10000101}, // ASCII 0x04 is End of Transmission - is too long for our encoding scheme in 8 bits, but fits }; -static void morseLetter(char c){ +void morseLetter(char c, uint16_t dit_duration_ms){ + if(!globalSettings.morseMenuOn){ + return; + } unsigned char mask = 0x80; //handle space character as three dashes if (c == ' '){ - active_delay(cwSpeed * 9); - Serial.print(' '); + delay(7 * dit_duration_ms); + //Serial.print(' '); return; } - for (int i = 0; i < sizeof(morse_table)/ sizeof(struct Morse); i++){ + for (unsigned int i = 0; i < sizeof(morse_table)/ sizeof(struct Morse); i++){ struct Morse m; memcpy_P(&m, morse_table + i, sizeof(struct Morse)); @@ -79,37 +86,44 @@ static void morseLetter(char c){ //now we are at the first zero, skip and carry on mask = mask >> 1; while(mask){ - tone(CW_TONE, sideTone,10000); + toneAC2(PIN_CW_TONE, globalSettings.cwSideToneFreq); if (mask & code){ - delay(3 * (int)cwSpeed); + delay(3 * dit_duration_ms); //Serial.print('-'); } else{ - delay((int)cwSpeed); + delay(dit_duration_ms); //Serial.print('.'); } //Serial.print('#'); - noTone(CW_TONE); - delay((int)cwSpeed); // space between dots and dashes + noToneAC2(); + delay(dit_duration_ms); // space between dots and dashes mask = mask >> 1; } //Serial.println('@'); - delay(200); // space between letters is a dash (3 dots), one dot's space has already been sent + delay(2*dit_duration_ms); // space between letters is a dash (3 dots), one dot's space has already been sent + break;//We've played the letter, so don't bother checking the rest of the list } } } -void morseText(char *text){ -// while (1){ - noTone(CW_TONE); - delay(1000); - tone(CW_TONE, 600); - delay(1000); -// } - - Serial.println(sideTone); - while(*text){ - morseLetter(*text++); +static const uint8_t RELATIVE_OFFSET_HZ = 100; +void morseText(const char *text, uint16_t dit_duration_ms){ + int16_t total_counts = 0; + morseBool(false); + enc_read();//Don't count initial tone against total_counts + while(*text && (abs(total_counts) < 10)){ + morseLetter(*text++, dit_duration_ms); + total_counts += enc_read(); } } +void morseBool(bool val){ + if(!globalSettings.morseMenuOn){ + return; + } + toneAC2(PIN_CW_TONE, globalSettings.cwSideToneFreq + (val ? RELATIVE_OFFSET_HZ : -RELATIVE_OFFSET_HZ)); + delay(3*globalSettings.cwDitDurationMs); + noToneAC2(); + delay(3*globalSettings.cwDitDurationMs); +} diff --git a/morse.h b/morse.h index 9c2de08..b842996 100644 --- a/morse.h +++ b/morse.h @@ -1,3 +1,7 @@ +#include "settings.h" //sends out morse code at the speed set by cwSpeed -extern int cwSpeed; //this is actuall the dot period in milliseconds -void morseText(char *text); +void morseLetter(char c, uint16_t dit_duration_ms = globalSettings.cwDitDurationMs); +void morseText(const char *text, uint16_t dit_duration_ms = globalSettings.cwDitDurationMs); + +//Plays either a higher or lower tone to indicate a boolean value +void morseBool(bool val); diff --git a/nano_font.cpp b/nano_font.cpp new file mode 100644 index 0000000..b292824 --- /dev/null +++ b/nano_font.cpp @@ -0,0 +1,12 @@ +#include "nano_font.h" +#include + +#include "PDQ_MinLib/FreeSansBold9pt7b.h" +//#include "PDQ_MinLib/Picopixel.h" +//#include "PDQ_MinLib/org_01.h" +//#include "PDQ_MinLib/TomThumb.h" + +const GFXfont* ubitx_font = &FreeSansBold9pt7b; +//const GFXfont* ubitx_font = &Picopixel; +//const GFXfont* ubitx_font = &Org_01; +//const GFXfont* ubitx_font = &TomThumb; diff --git a/nano_font.h b/nano_font.h new file mode 100644 index 0000000..9ce554a --- /dev/null +++ b/nano_font.h @@ -0,0 +1,9 @@ +#ifndef _NANO_FONT +#define _NANO_FONT + +#include +#include "PDQ_MinLib/gfxfont.h" + +extern const GFXfont* ubitx_font; + +#endif //_NANO_FONT \ No newline at end of file diff --git a/nano_gui.cpp b/nano_gui.cpp index 97d7ddb..821d1d7 100644 --- a/nano_gui.cpp +++ b/nano_gui.cpp @@ -1,604 +1,176 @@ #include -#include -#include "ubitx.h" #include "nano_gui.h" +#include "colors.h" +#include "pin_definitions.h" +#include "push_button.h" +#include "scratch_space.h" +#include "settings.h" +#include "touch.h" -//#include "Adafruit_GFX.h" -//#include #include #include -#define TFT_CS 10 -#define TFT_RS 9 +/***************** + * Begin TFT functions + *****************/ +#define ILI9341_CS_PIN PIN_TFT_CS +#define ILI9341_DC_PIN PIN_TFT_DC +#define ILI9341_SAVE_SPCR (1) //Save state before/after to play nice with the touch screen +#include "PDQ_MinLib/PDQ_ILI9341.h" +PDQ_ILI9341 tft; -GFXfont *gfxFont = NULL; -//int touch_x, touch_y; -//XPT2046_Touchscreen ts(CS_PIN); -//TS_Point ts_point; -struct Point ts_point; +#include "nano_font.h" -//filled from a test run of calibration routine -int slope_x=104, slope_y=137, offset_x=28, offset_y=29; - -void readTouchCalibration(){ - EEPROM.get(SLOPE_X, slope_x); - EEPROM.get(SLOPE_Y, slope_y); - EEPROM.get(OFFSET_X, offset_x); - EEPROM.get(OFFSET_Y, offset_y); - -/* - //for debugging - Serial.print(slope_x); Serial.print(' '); - Serial.print(slope_y); Serial.print(' '); - Serial.print(offset_x); Serial.print(' '); - Serial.println(offset_y); Serial.println(' '); -*/ - -} - -void writeTouchCalibration(){ - EEPROM.put(SLOPE_X, slope_x); - EEPROM.put(SLOPE_Y, slope_y); - EEPROM.put(OFFSET_X, offset_x); - EEPROM.put(OFFSET_Y, offset_y); -} - -#define Z_THRESHOLD 400 -#define Z_THRESHOLD_INT 75 -#define MSEC_THRESHOLD 3 -#define SPI_SETTING SPISettings(2000000, MSBFIRST, SPI_MODE0) - -static uint32_t msraw=0x80000000; -static int16_t xraw=0, yraw=0, zraw=0; -static uint8_t rotation = 1; - -static int16_t touch_besttwoavg( int16_t x , int16_t y , int16_t z ) { - int16_t da, db, dc; - int16_t reta = 0; - if ( x > y ) da = x - y; else da = y - x; - if ( x > z ) db = x - z; else db = z - x; - if ( z > y ) dc = z - y; else dc = y - z; - - if ( da <= db && da <= dc ) reta = (x + y) >> 1; - else if ( db <= da && db <= dc ) reta = (x + z) >> 1; - else reta = (y + z) >> 1; // else if ( dc <= da && dc <= db ) reta = (x + y) >> 1; - - return (reta); -} - -static void touch_update(){ - int16_t data[6]; - - uint32_t now = millis(); - if (now - msraw < MSEC_THRESHOLD) return; - - SPI.beginTransaction(SPI_SETTING); - digitalWrite(CS_PIN, LOW); - SPI.transfer(0xB1 /* Z1 */); - int16_t z1 = SPI.transfer16(0xC1 /* Z2 */) >> 3; - int z = z1 + 4095; - int16_t z2 = SPI.transfer16(0x91 /* X */) >> 3; - z -= z2; - if (z >= Z_THRESHOLD) { - SPI.transfer16(0x91 /* X */); // dummy X measure, 1st is always noisy - data[0] = SPI.transfer16(0xD1 /* Y */) >> 3; - data[1] = SPI.transfer16(0x91 /* X */) >> 3; // make 3 x-y measurements - data[2] = SPI.transfer16(0xD1 /* Y */) >> 3; - data[3] = SPI.transfer16(0x91 /* X */) >> 3; - } - else data[0] = data[1] = data[2] = data[3] = 0; // Compiler warns these values may be used unset on early exit. - data[4] = SPI.transfer16(0xD0 /* Y */) >> 3; // Last Y touch power down - data[5] = SPI.transfer16(0) >> 3; - digitalWrite(CS_PIN, HIGH); - SPI.endTransaction(); - //Serial.printf("z=%d :: z1=%d, z2=%d ", z, z1, z2); - if (z < 0) z = 0; - if (z < Z_THRESHOLD) { // if ( !touched ) { - // Serial.println(); - zraw = 0; - return; - } - zraw = z; - - int16_t x = touch_besttwoavg( data[0], data[2], data[4] ); - int16_t y = touch_besttwoavg( data[1], data[3], data[5] ); - - //Serial.printf(" %d,%d", x, y); - //Serial.println(); - if (z >= Z_THRESHOLD) { - msraw = now; // good read completed, set wait - switch (rotation) { - case 0: - xraw = 4095 - y; - yraw = x; - break; - case 1: - xraw = x; - yraw = y; - break; - case 2: - xraw = y; - yraw = 4095 - x; - break; - default: // 3 - xraw = 4095 - x; - yraw = 4095 - y; - } +void displayInit(void){ + //Pulling this low 6 times should exit deep sleep mode + pinMode(PIN_TFT_CS,OUTPUT); + for(uint8_t i = 0; i < 6; ++i){ + digitalWrite(PIN_TFT_CS,HIGH); + digitalWrite(PIN_TFT_CS,LOW); } -} + digitalWrite(PIN_TFT_CS,HIGH);//Disable writing for now - -boolean readTouch(){ - touch_update(); - if (zraw >= Z_THRESHOLD) { - ts_point.x = xraw; - ts_point.y = yraw; -// Serial.print(ts_point.x); Serial.print(",");Serial.println(ts_point.y); - return true; - } - return false; + tft.begin(); + tft.setFont(ubitx_font); + tft.setTextWrap(true); + tft.setTextColor(DISPLAY_GREEN,DISPLAY_BLACK); + tft.setTextSize(1); + tft.setRotation(1); } -void scaleTouch(struct Point *p){ - p->x = ((long)(p->x - offset_x) * 10l)/ (long)slope_x; - p->y = ((long)(p->y - offset_y) * 10l)/ (long)slope_y; - -// Serial.print(p->x); Serial.print(",");Serial.println(p->y); - -// p->y = ((long)(p->y) * 10l)/(long)(slope_y) - offset_y; +void displayPixel(unsigned int x, unsigned int y, unsigned int c){ + tft.fillRect(x,y,1,1,c); } - -#if !defined(__INT_MAX__) || (__INT_MAX__ > 0xFFFF) - #define pgm_read_pointer(addr) ((void *)pgm_read_dword(addr)) -#else - #define pgm_read_pointer(addr) ((void *)pgm_read_word(addr)) -#endif - -inline GFXglyph * pgm_read_glyph_ptr(const GFXfont *gfxFont, uint8_t c) -{ -#ifdef __AVR__ - return &(((GFXglyph *)pgm_read_pointer(&gfxFont->glyph))[c]); -#else - // expression in __AVR__ section may generate "dereferencing type-punned pointer will break strict-aliasing rules" warning - // In fact, on other platforms (such as STM32) there is no need to do this pointer magic as program memory may be read in a usual way - // So expression may be simplified - return gfxFont->glyph + c; -#endif //__AVR__ +void displayHline(unsigned int x, unsigned int y, unsigned int w, unsigned int c){ + tft.fillRect(x,y,w,1,c); } -inline uint8_t * pgm_read_bitmap_ptr(const GFXfont *gfxFont){ -#ifdef __AVR__ - return (uint8_t *)pgm_read_pointer(&gfxFont->bitmap); -#else - // expression in __AVR__ section generates "dereferencing type-punned pointer will break strict-aliasing rules" warning - // In fact, on other platforms (such as STM32) there is no need to do this pointer magic as program memory may be read in a usual way - // So expression may be simplified - return gfxFont->bitmap; -#endif //__AVR__ +void displayVline(unsigned int x, unsigned int y, unsigned int l, unsigned int c){ + tft.fillRect(x,y,1,l,c); } -inline static void utft_write(unsigned char d){ - SPI.transfer(d); +void displayClear(unsigned int color){ + tft.fillRect(0,0,320,240,color); } -inline static void utftCmd(unsigned char VH){ - *(portOutputRegister(digitalPinToPort(TFT_RS))) &= ~digitalPinToBitMask(TFT_RS);//LCD_RS=0; - utft_write(VH); +void displayRect(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned int c){ + tft.fillRect(x,y,w,1,c); + tft.fillRect(x,y,1,h,c); + tft.fillRect(x,y+h-1,w,1,c); + tft.fillRect(x+w-1,y,1,h,c); } -inline static void utftData(unsigned char VH){ - *(portOutputRegister(digitalPinToPort(TFT_RS)))|= digitalPinToBitMask(TFT_RS);//LCD_RS=1; - utft_write(VH); +void displayFillrect(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned int c){ + tft.fillRect(x,y,w,h,c); } - -static void utftAddress(unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2){ - - utftCmd(0x2a); - utftData(x1>>8); - utftData(x1); - utftData(x2>>8); - utftData(x2); - utftCmd(0x2b); - utftData(y1>>8); - utftData(y1); - utftData(y2>>8); - utftData(y2); - utftCmd(0x2c); +void displayChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg) { + tft.drawCharGFX(x,y,c,color,bg,1); } -void displayPixel(unsigned int x, unsigned int y, unsigned int c){ - unsigned int i,j; - digitalWrite(TFT_CS,LOW); - - utftCmd(0x02c); //write_memory_start - utftAddress(x,y,x,y); - utftData(c>>8); - utftData(c); - - digitalWrite(TFT_CS,HIGH); +void displayRawText(const char *text, int x1, int y1, int w, int color, int background){ + tft.setTextColor(color,background); + tft.setCursor(x1,y1); + tft.setBound(x1,x1+w); + tft.print(text); } -void displayHline(unsigned int x, unsigned int y, unsigned int l, unsigned int c){ - unsigned int i,j; +void displayText(const char *const text, int x1, int y1, int w, int h, int color, int background, int border, TextJustification_e justification) +{ + displayFillrect(x1, y1, w ,h, background); + displayRect(x1, y1, w ,h, border); - digitalWrite(TFT_CS,LOW); - utftCmd(0x02c); //write_memory_start - l=l+x; - utftAddress(x,y,l,y); - j = l; - for(i=1;i<=j;i++) - { - utftData(c>>8); - utftData(c); + int16_t x1_out; + int16_t y1_out; + uint16_t width_out; + uint16_t height_out; + tft.getTextBounds(text,x1,y1,&x1_out,&y1_out,&width_out,&height_out,w); + if(TextJustification_e::Center == justification){ + x1 += (w - ( (int32_t)width_out + (x1_out-x1)))/2; } - digitalWrite(TFT_CS,HIGH); - checkCAT(); -} - -void displayVline(unsigned int x, unsigned int y, unsigned int l, unsigned int c){ - unsigned int i,j; - digitalWrite(TFT_CS,LOW); - - utftCmd(0x02c); //write_memory_start - l=l+y; - utftAddress(x,y,x,l); - j = l; - for(i=1;i<=l;i++) - { - utftData(c>>8); - utftData(c); + else if(TextJustification_e::Right == justification){ + x1 += w - ((int32_t)width_out + (x1_out-x1)); } - digitalWrite(TFT_CS,HIGH); - checkCAT(); -} - -void displayClear(unsigned int color){ - unsigned int i,m; - - digitalWrite(TFT_CS,LOW); - utftAddress(0,0,320,240); - for(i=0;i<320;i++) - for(m=0;m<240;m++){ - utftData(color>>8); - utftData(color); - } - digitalWrite(TFT_CS,HIGH); -} - -void displayRect(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned int c){ - displayHline(x , y , w, c); - displayHline(x , y+h, w, c); - displayVline(x , y , h, c); - displayVline(x+w, y , h, c); -} - -void displayFillrect(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned int c){ - unsigned int i; - - for(i=0;iyAdvance + h - ( (int32_t)height_out))/2; + displayRawText(text,x1,y1,w,color,background); } -bool xpt2046_Init(){ - pinMode(CS_PIN, OUTPUT); - digitalWrite(CS_PIN, HIGH); +void drawCross(int16_t x_center,int16_t y_center,uint16_t color) +{ + constexpr uint8_t HALF_SIZE = 10; + displayHline(x_center-HALF_SIZE,y_center,2*HALF_SIZE,color); + displayVline(x_center,y_center-HALF_SIZE,2*HALF_SIZE,color); } -void displayInit(void){ - - SPI.begin(); - SPI.setClockDivider(SPI_CLOCK_DIV4); // 4 MHz (half speed) - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - - gfxFont = &ubitx_font; - pinMode(TFT_CS,OUTPUT); - pinMode(TFT_RS,OUTPUT); - - - digitalWrite(TFT_CS,LOW); //CS - utftCmd(0xCB); - utftData(0x39); - utftData(0x2C); - utftData(0x00); - utftData(0x34); - utftData(0x02); - - utftCmd(0xCF); - utftData(0x00); - utftData(0XC1); - utftData(0X30); - - utftCmd(0xE8); - utftData(0x85); - utftData(0x00); - utftData(0x78); - - utftCmd(0xEA); - utftData(0x00); - utftData(0x00); - - utftCmd(0xED); - utftData(0x64); - utftData(0x03); - utftData(0X12); - utftData(0X81); - - utftCmd(0xF7); - utftData(0x20); - - utftCmd(0xC0); //Power control - utftData(0x23); //VRH[5:0] - - utftCmd(0xC1); //Power control - utftData(0x10); //SAP[2:0];BT[3:0] - - utftCmd(0xC5); //VCM control - utftData(0x3e); //Contrast - utftData(0x28); - - utftCmd(0xC7); //VCM control2 - utftData(0x86); //-- - - utftCmd(0x36); // Memory Access Control - utftData(0x28); // Make this horizontal display - - utftCmd(0x3A); - utftData(0x55); - - utftCmd(0xB1); - utftData(0x00); - utftData(0x18); - - utftCmd(0xB6); // Display Function Control - utftData(0x08); - utftData(0x82); - utftData(0x27); - - utftCmd(0x11); //Exit Sleep - delay(120); - - utftCmd(0x29); //Display on - utftCmd(0x2c); - digitalWrite(TFT_CS,HIGH); - - //now to init the touch screen controller - //ts.begin(); - //ts.setRotation(1); - xpt2046_Init(); +void setupTouch(){ + constexpr int16_t CROSS_CORNER_OFFSET = 20; + constexpr Point CROSS_CORNER_POINTS [] = { + {CROSS_CORNER_OFFSET,CROSS_CORNER_OFFSET},//Top left + {PDQ_ILI9341::ILI9341_TFTHEIGHT-CROSS_CORNER_OFFSET,CROSS_CORNER_OFFSET},//Top right + {CROSS_CORNER_OFFSET, PDQ_ILI9341::ILI9341_TFTWIDTH-CROSS_CORNER_OFFSET},//Bottom left + {PDQ_ILI9341::ILI9341_TFTHEIGHT-CROSS_CORNER_OFFSET,PDQ_ILI9341::ILI9341_TFTWIDTH-CROSS_CORNER_OFFSET}//Bottom right + }; - readTouchCalibration(); -} - -// Draw a character -/**************************************************************************/ -/*! - @brief Draw a single character - @param x Bottom left corner x coordinate - @param y Bottom left corner y coordinate - @param c The 8-bit font-indexed character (likely ascii) - @param color 16-bit 5-6-5 Color to draw chraracter with - @param bg 16-bit 5-6-5 Color to fill background with (if same as color, no background) - @param size_x Font magnification level in X-axis, 1 is 'original' size - @param size_y Font magnification level in Y-axis, 1 is 'original' size -*/ -/**************************************************************************/ -#define FAST_TEXT 1 + displayClear(DISPLAY_BLACK); + strncpy_P(b,(const char*)F("Click on the cross\nPush tune to cancel"),sizeof(b)); + displayText(b, 20,100, 200, 50, DISPLAY_WHITE, DISPLAY_BLACK, DISPLAY_BLACK); -void displayChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg) { - c -= (uint8_t)pgm_read_byte(&gfxFont->first); - GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c); - uint8_t *bitmap = pgm_read_bitmap_ptr(gfxFont); - - uint16_t bo = pgm_read_word(&glyph->bitmapOffset); - uint8_t w = pgm_read_byte(&glyph->width), - h = pgm_read_byte(&glyph->height); - int8_t xo = pgm_read_byte(&glyph->xOffset), - yo = pgm_read_byte(&glyph->yOffset); - uint8_t xx, yy, bits = 0, bit = 0; - int16_t xo16 = 0, yo16 = 0; - - digitalWrite(TFT_CS,LOW); + Point cal_points[sizeof(CROSS_CORNER_POINTS)/sizeof(CROSS_CORNER_POINTS[0])]; -#ifdef FAST_TEXT - uint16_t hpc = 0; // Horizontal foreground pixel count - for(yy=0; yy>= 1; + for(uint8_t i = 0; i < sizeof(CROSS_CORNER_POINTS)/sizeof(CROSS_CORNER_POINTS[0]); ++i){ + drawCross(CROSS_CORNER_POINTS[i].x,CROSS_CORNER_POINTS[i].y,DISPLAY_WHITE); + while(!readTouch(&cal_points[i])){ + if(ButtonPress_e::NotPressed != CheckTunerButton()){ + return; } - // Draw pixels for this line as we are about to increment yy - if (hpc) { - displayHline(x+xo+xx-hpc, y+yo+yy, hpc, color); - hpc=0; - } - checkCAT(); + delay(100); } -#else - for(yy=0; yyfirst); - if((c >= first) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last))) { - GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c - first); - ext += (uint8_t)pgm_read_byte(&glyph->xAdvance); - } - }//end of the while loop of the characters to be printed - return ext; -} - -void displayRawText(char *text, int x1, int y1, int color, int background){ - while(*text){ - char c = *text++; - - uint8_t first = pgm_read_byte(&gfxFont->first); - if((c >= first) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last))) { - - GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c - first); - uint8_t w = pgm_read_byte(&glyph->width), - h = pgm_read_byte(&glyph->height); - if((w > 0) && (h > 0)) { // Is there an associated bitmap? - int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); // sic - displayChar(x1, y1+TEXT_LINE_HEIGHT, c, color, background); - checkCAT(); - } - x1 += (uint8_t)pgm_read_byte(&glyph->xAdvance); - } - }//end of the while loop of the characters to be printed -} - -// The generic routine to display one line on the LCD -void displayText(char *text, int x1, int y1, int w, int h, int color, int background, int border) { - - displayFillrect(x1, y1, w ,h, background); - displayRect(x1, y1, w ,h, border); - - x1 += (w - displayTextExtent(text))/2; - y1 += (h - TEXT_LINE_HEIGHT)/2; - while(*text){ - char c = *text++; - - uint8_t first = pgm_read_byte(&gfxFont->first); - if((c >= first) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last))) { - - GFXglyph *glyph = pgm_read_glyph_ptr(gfxFont, c - first); - uint8_t w = pgm_read_byte(&glyph->width), - h = pgm_read_byte(&glyph->height); - if((w > 0) && (h > 0)) { // Is there an associated bitmap? - int16_t xo = (int8_t)pgm_read_byte(&glyph->xOffset); // sic - displayChar(x1, y1+TEXT_LINE_HEIGHT, c, color, background); - checkCAT(); - } - x1 += (uint8_t)pgm_read_byte(&glyph->xAdvance); - } - }//end of the while loop of the characters to be printed -} - -void setupTouch(){ - int x1, y1, x2, y2, x3, y3, x4, y4; - - displayClear(DISPLAY_BLACK); - displayText("Click on the cross", 20,100, 200, 50, DISPLAY_WHITE, DISPLAY_BLACK, DISPLAY_BLACK); - - // TOP-LEFT - displayHline(10,20,20,DISPLAY_WHITE); - displayVline(20,10,20, DISPLAY_WHITE); - - while(!readTouch()) - delay(100); - while(readTouch()) - delay(100); - x1 = ts_point.x; - y1 = ts_point.y; - - //rubout the previous one - displayHline(10,20,20,DISPLAY_BLACK); - displayVline(20,10,20, DISPLAY_BLACK); - - delay(1000); - - //TOP RIGHT - displayHline(290,20,20,DISPLAY_WHITE); - displayVline(300,10,20, DISPLAY_WHITE); - - while(!readTouch()) - delay(100); - while(readTouch()) - delay(100); - x2 = ts_point.x; - y2 = ts_point.y; + drawCross(CROSS_CORNER_POINTS[i].x,CROSS_CORNER_POINTS[i].y,DISPLAY_BLACK); + delay(1000);//Ensure that nobody is pressing the screen before we do the next point + } - displayHline(290,20,20,DISPLAY_BLACK); - displayVline(300,10,20, DISPLAY_BLACK); + //We can get nicer scaling if we allow more resolution on the divisor + constexpr int32_t SCALE_SENSITIVITY_MULTIPLIER = 10; - delay(1000); + const int16_t diff_x_top = cal_points[1].x - cal_points[0].x; + const int16_t diff_x_bottom = cal_points[3].x - cal_points[2].x; + constexpr int32_t diff_x_target = CROSS_CORNER_POINTS[1].x - CROSS_CORNER_POINTS[0].x; - //BOTTOM LEFT - displayHline(10,220,20,DISPLAY_WHITE); - displayVline(20,210,20, DISPLAY_WHITE); - - while(!readTouch()) - delay(100); - x3 = ts_point.x; - y3 = ts_point.y; - - while(readTouch()) - delay(100); - displayHline(10,220,20,DISPLAY_BLACK); - displayVline(20,210,20, DISPLAY_BLACK); + //Average the measured differences + globalSettings.touchSlopeX = SCALE_SENSITIVITY_MULTIPLIER*(diff_x_top + diff_x_bottom) / (2*diff_x_target); - delay(1000); + const int16_t diff_y_left = cal_points[2].y - cal_points[0].y; + const int16_t diff_y_right = cal_points[3].y - cal_points[1].y; + constexpr int32_t diff_y_target = CROSS_CORNER_POINTS[2].y - CROSS_CORNER_POINTS[0].y; - //BOTTOM RIGHT - displayHline(290,220,20,DISPLAY_WHITE); - displayVline(300,210,20, DISPLAY_WHITE); + //Average the measured differences + globalSettings.touchSlopeY = SCALE_SENSITIVITY_MULTIPLIER*(diff_y_left + diff_y_right) / (2*diff_y_target); - while(!readTouch()) - delay(100); - x4 = ts_point.x; - y4 = ts_point.y; - - - displayHline(290,220,20,DISPLAY_BLACK); - displayVline(300,210,20, DISPLAY_BLACK); + globalSettings.touchOffsetX = cal_points[0].x - ((CROSS_CORNER_OFFSET * globalSettings.touchSlopeX)/SCALE_SENSITIVITY_MULTIPLIER); + globalSettings.touchOffsetY = cal_points[0].y - ((CROSS_CORNER_OFFSET * globalSettings.touchSlopeY)/SCALE_SENSITIVITY_MULTIPLIER); - // we average two readings and divide them by half and store them as scaled integers 10 times their actual, fractional value - //the x points are located at 20 and 300 on x axis, hence, the delta x is 280, we take 28 instead, to preserve fractional value, - //there are two readings (x1,x2) and (x3, x4). Hence, we have to divide by 28 * 2 = 56 - slope_x = ((x4 - x3) + (x2 - x1))/56; - //the y points are located at 20 and 220 on the y axis, hence, the delta is 200. we take it as 20 instead, to preserve the fraction value - //there are two readings (y1, y2) and (y3, y4). Hence we have to divide by 20 * 2 = 40 - slope_y = ((y3 - y1) + (y4 - y2))/40; - - //x1, y1 is at 20 pixels - offset_x = x1 + -((20 * slope_x)/10); - offset_y = y1 + -((20 * slope_y)/10); /* - Serial.print(x1);Serial.print(':');Serial.println(y1); - Serial.print(x2);Serial.print(':');Serial.println(y2); - Serial.print(x3);Serial.print(':');Serial.println(y3); - Serial.print(x4);Serial.print(':');Serial.println(y4); + for(uint8_t i = 0; i < sizeof(cal_points)/sizeof(cal_points[0]); ++i){ + Serial.print(cal_points[i].x);Serial.print(':');Serial.println(cal_points[i].y); + } //for debugging - Serial.print(slope_x); Serial.print(' '); - Serial.print(slope_y); Serial.print(' '); - Serial.print(offset_x); Serial.print(' '); - Serial.println(offset_y); Serial.println(' '); -*/ - writeTouchCalibration(); - displayClear(DISPLAY_BLACK); + Serial.print(globalSettings.touchSlopeX); Serial.print(' '); + Serial.print(globalSettings.touchSlopeY); Serial.print(' '); + Serial.print(globalSettings.touchOffsetX); Serial.print(' '); + Serial.println(globalSettings.touchOffsetY); Serial.println(' '); +*/ + + SaveSettingsToEeprom(); } diff --git a/nano_gui.h b/nano_gui.h index 4ae4719..c8681ad 100644 --- a/nano_gui.h +++ b/nano_gui.h @@ -1,11 +1,12 @@ #ifndef _NANO_GUI_H_ #define _NANO_GUI_H_ -/* UI functions */ -struct Point { - int x, y; +enum TextJustification_e : uint8_t +{ + Left, + Right, + Center }; -extern struct Point ts_point; void displayInit(); void displayClear(unsigned int color); @@ -15,36 +16,10 @@ void displayVline(unsigned int x, unsigned int y, unsigned int l, unsigned int c void displayRect(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned int c); void displayFillrect(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned int c); void displayChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg); -int displayTextExtent(char *text); -void displayRawText(char *text, int x1, int y1, int color, int background); -void displayText(char *text, int x1, int y1, int w, int h, int color, int background, int border); +void displayText(const char *const text, int x1, int y1, int w, int h, int color, int background, int border, TextJustification_e justification = TextJustification_e::Center); -/* touch functions */ -boolean readTouch(); - -void setupTouch(); -void scaleTouch(struct Point *p); - -// Color definitions -#define DISPLAY_BLACK 0x0000 ///< 0, 0, 0 -#define DISPLAY_NAVY 0x000F ///< 0, 0, 123 -#define DISPLAY_DARKGREEN 0x03E0 ///< 0, 125, 0 -#define DISPLAY_DARKCYAN 0x03EF ///< 0, 125, 123 -#define DISPLAY_MAROON 0x7800 ///< 123, 0, 0 -#define DISPLAY_PURPLE 0x780F ///< 123, 0, 123 -#define DISPLAY_OLIVE 0x7BE0 ///< 123, 125, 0 -#define DISPLAY_LIGHTGREY 0xC618 ///< 198, 195, 198 -#define DISPLAY_DARKGREY 0x7BEF ///< 123, 125, 123 -#define DISPLAY_BLUE 0x001F ///< 0, 0, 255 -#define DISPLAY_GREEN 0x07E0 ///< 0, 255, 0 -#define DISPLAY_CYAN 0x07FF ///< 0, 255, 255 -#define DISPLAY_RED 0xF800 ///< 255, 0, 0 -#define DISPLAY_MAGENTA 0xF81F ///< 255, 0, 255 -#define DISPLAY_YELLOW 0xFFE0 ///< 255, 255, 0 -#define DISPLAY_WHITE 0xFFFF ///< 255, 255, 255 -#define DISPLAY_ORANGE 0xFD20 ///< 255, 165, 0 -#define DISPLAY_GREENYELLOW 0xAFE5 ///< 173, 255, 41 -#define DISPLAY_PINK 0xFC18 ///< 255, 130, 198 +/* these functions are called universally to update the display */ +void drawTx(); #define TEXT_LINE_HEIGHT 18 #define TEXT_LINE_INDENT 5 @@ -53,379 +28,4 @@ void scaleTouch(struct Point *p); #define BUTTON_CHECK #define BUTTON_SPINNER -/// Font data stored PER GLYPH -typedef struct { - uint16_t bitmapOffset; ///< Pointer into GFXfont->bitmap - uint8_t width; ///< Bitmap dimensions in pixels - uint8_t height; ///< Bitmap dimensions in pixels - uint8_t xAdvance; ///< Distance to advance cursor (x axis) - int8_t xOffset; ///< X dist from cursor pos to UL corner - int8_t yOffset; ///< Y dist from cursor pos to UL corner -} GFXglyph; - -/// Data stored for FONT AS A WHOLE -typedef struct { - uint8_t *bitmap; ///< Glyph bitmaps, concatenated - GFXglyph *glyph; ///< Glyph array - uint8_t first; ///< ASCII extents (first char) - uint8_t last; ///< ASCII extents (last char) - uint8_t yAdvance; ///< Newline distance (y axis) -} GFXfont; - -const uint8_t ubitxBitmaps[] PROGMEM = { - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, - 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, - 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, - 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, - 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, - 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, - 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, - 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, - 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, - 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, - 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, - 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, - 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, - 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, - 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, - 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, - 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, - 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, - 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, - 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0, - 0xFF, 0xFF, 0xFF, 0xFF, 0x21, 0xFF, 0xE0, 0xF3, 0xF9, 0xFC, 0xFE, 0x7E, - 0x3B, 0x1D, 0x84, 0x0E, 0x70, 0x73, 0x83, 0x9C, 0x18, 0xC7, 0xFF, 0xBF, - 0xFD, 0xFF, 0xE3, 0x9C, 0x18, 0xC0, 0xC6, 0x0E, 0x7D, 0xFF, 0xEF, 0xFF, - 0x18, 0xC0, 0xC6, 0x0E, 0x70, 0x73, 0x83, 0x9C, 0x00, 0x04, 0x01, 0xF8, - 0x7F, 0xCF, 0xDE, 0xE4, 0xEE, 0x4E, 0xE4, 0x0F, 0x40, 0x7E, 0x03, 0xFC, - 0x0F, 0xE0, 0x5E, 0x04, 0xFE, 0x4F, 0xE4, 0xFF, 0x4E, 0x7F, 0xE3, 0xFC, - 0x04, 0x00, 0x40, 0x04, 0x00, 0x3C, 0x06, 0x07, 0xE0, 0x60, 0xFF, 0x0C, - 0x0C, 0x30, 0x80, 0xC3, 0x18, 0x0C, 0x31, 0x00, 0xFF, 0x30, 0x07, 0xE6, - 0x00, 0x3C, 0x60, 0x00, 0x0C, 0x7C, 0x00, 0xCF, 0xE0, 0x19, 0xC6, 0x01, - 0x98, 0x70, 0x31, 0x87, 0x03, 0x1C, 0x60, 0x60, 0xFE, 0x04, 0x07, 0xC0, - 0x0F, 0x80, 0x1F, 0xC0, 0x3D, 0xE0, 0x38, 0xE0, 0x3C, 0xE0, 0x1D, 0xC0, - 0x1F, 0xC0, 0x0F, 0x00, 0x3F, 0x8C, 0x7B, 0xDC, 0x71, 0xDC, 0xF1, 0xFC, - 0xF0, 0xF8, 0xF0, 0x78, 0x79, 0xFC, 0x7F, 0xFC, 0x3F, 0x9E, 0x00, 0x00, - 0xFF, 0xFF, 0xE6, 0x60, 0x0C, 0x71, 0x8E, 0x31, 0xC7, 0x38, 0xE3, 0x8E, - 0x38, 0xE3, 0x8E, 0x38, 0x71, 0xC7, 0x0E, 0x38, 0x70, 0xC0, 0xC3, 0x86, - 0x1C, 0x70, 0xE3, 0x8E, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xCE, 0x38, 0xE7, - 0x1C, 0x63, 0x8C, 0x00, 0x10, 0x10, 0x10, 0xFE, 0x7C, 0x38, 0x6C, 0x44, - 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x60, - 0x06, 0x00, 0x60, 0x06, 0x00, 0xFF, 0xF2, 0xFE, 0xFF, 0xFF, 0xC0, 0xFF, - 0xF0, 0x04, 0x08, 0x30, 0x60, 0x83, 0x06, 0x08, 0x10, 0x60, 0xC1, 0x06, - 0x0C, 0x10, 0x20, 0xC0, 0x3F, 0x8F, 0xF9, 0xEF, 0x78, 0xFE, 0x0F, 0xC1, - 0xF8, 0x3F, 0x07, 0xE0, 0xFC, 0x1F, 0x83, 0xF0, 0x7E, 0x0F, 0xE3, 0xDE, - 0xF3, 0xFE, 0x3F, 0x80, 0x80, 0x06, 0x1C, 0x7F, 0xFF, 0xE1, 0xC3, 0x87, - 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x3F, 0x8F, 0xFB, 0xEF, - 0xF8, 0x7E, 0x0F, 0xC1, 0xC0, 0x38, 0x0F, 0x03, 0xC0, 0xF0, 0x7C, 0x1F, - 0x07, 0x80, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0x8F, 0xFB, 0xCF, - 0xF0, 0xFE, 0x1E, 0x03, 0xC0, 0x70, 0x7C, 0x0F, 0xC0, 0xFC, 0x03, 0x80, - 0x7E, 0x0F, 0xC1, 0xFC, 0xFB, 0xFE, 0x3F, 0x80, 0x80, 0x07, 0xC0, 0x7C, - 0x0F, 0xC0, 0xFC, 0x1F, 0xC3, 0xBC, 0x33, 0xC7, 0x3C, 0x63, 0xCE, 0x3C, - 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3F, - 0xEF, 0xFD, 0xFF, 0xB8, 0x06, 0x00, 0xC0, 0x1F, 0xE7, 0xFE, 0xF1, 0xE0, - 0x1C, 0x03, 0x80, 0x70, 0x0F, 0xC1, 0xFC, 0xFB, 0xFE, 0x3F, 0x80, 0x80, - 0x1F, 0x87, 0xF9, 0xE7, 0xB8, 0x7E, 0x01, 0xC0, 0x3B, 0xE7, 0xFE, 0xFB, - 0xFE, 0x1F, 0x83, 0xF0, 0x7E, 0x0F, 0xE1, 0xDE, 0xFB, 0xFE, 0x3F, 0x80, - 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0E, 0x01, 0xC0, 0x3C, 0x03, 0x80, - 0x70, 0x0F, 0x00, 0xE0, 0x0E, 0x01, 0xE0, 0x1C, 0x01, 0xC0, 0x1C, 0x03, - 0xC0, 0x3C, 0x00, 0x3F, 0x87, 0xFC, 0xF9, 0xEE, 0x0E, 0xE0, 0xEE, 0x0E, - 0x71, 0xC3, 0xF8, 0x7F, 0xCF, 0x1E, 0xE0, 0xEE, 0x0F, 0xE0, 0xFE, 0x0E, - 0xF1, 0xE7, 0xFC, 0x3F, 0x80, 0x40, 0x3F, 0x0F, 0xFB, 0xEF, 0x70, 0xFE, - 0x0F, 0xC1, 0xF8, 0x3F, 0x0F, 0xF3, 0xEF, 0xFC, 0xFB, 0x80, 0x70, 0x0F, - 0xC3, 0xFE, 0xF3, 0xFE, 0x3F, 0x80, 0x80, 0xFF, 0x80, 0x00, 0xFF, 0xF0, - 0xFF, 0x80, 0x00, 0xFF, 0xF2, 0xDE, 0x00, 0x70, 0x1F, 0x0F, 0xE7, 0xF0, - 0xF8, 0x0E, 0x00, 0xFC, 0x03, 0xF8, 0x0F, 0xE0, 0x1F, 0x00, 0x30, 0xFF, - 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xF0, 0xC0, 0x0F, 0x80, 0x7F, 0x00, 0xFC, 0x03, 0xF0, 0x07, 0x03, 0xF1, - 0xFC, 0xFE, 0x0F, 0x80, 0xC0, 0x00, 0x1F, 0x0F, 0xF3, 0xFF, 0x78, 0xFE, - 0x0F, 0xC1, 0xC0, 0x38, 0x0F, 0x03, 0xE0, 0xF8, 0x3C, 0x07, 0x00, 0xE0, - 0x00, 0x03, 0x80, 0x70, 0x0E, 0x01, 0xC0, 0x00, 0xFC, 0x00, 0x0F, 0xFE, - 0x00, 0xF0, 0x3C, 0x07, 0x00, 0x38, 0x38, 0x00, 0x30, 0xC0, 0x00, 0xE6, - 0x0F, 0xF1, 0x98, 0x73, 0xC7, 0xC3, 0x87, 0x0F, 0x0C, 0x1C, 0x3C, 0x30, - 0x61, 0xF1, 0xC1, 0x86, 0xC7, 0x0E, 0x1B, 0x1C, 0x38, 0xEC, 0x3B, 0xEF, - 0x38, 0xFD, 0xF8, 0x70, 0xC1, 0x80, 0xE0, 0x00, 0x01, 0xC0, 0x10, 0x03, - 0xFF, 0xC0, 0x03, 0xFF, 0x00, 0x03, 0xC0, 0x07, 0xC0, 0x07, 0xE0, 0x07, - 0xE0, 0x0F, 0xE0, 0x0E, 0xF0, 0x0E, 0xF0, 0x1E, 0x70, 0x1C, 0x78, 0x1C, - 0x78, 0x3C, 0x38, 0x3F, 0xFC, 0x3F, 0xFC, 0x7F, 0xFC, 0x70, 0x1E, 0xF0, - 0x1E, 0xF0, 0x0E, 0xE0, 0x0F, 0xFF, 0x03, 0xFF, 0xCF, 0xFF, 0xBC, 0x7E, - 0xF0, 0x3B, 0xC0, 0xEF, 0x03, 0xBF, 0xFE, 0xFF, 0xE3, 0xFF, 0xCF, 0x0F, - 0xBC, 0x0F, 0xF0, 0x1F, 0xC0, 0xFF, 0x03, 0xFF, 0xFE, 0xFF, 0xFB, 0xFF, - 0x80, 0x03, 0xC0, 0x1F, 0xF0, 0xFF, 0xF1, 0xF1, 0xE7, 0x81, 0xEF, 0x01, - 0xFC, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0F, - 0x00, 0xEF, 0x01, 0xDE, 0x07, 0x9F, 0xFE, 0x1F, 0xFC, 0x1F, 0xE0, 0x04, - 0x00, 0xFE, 0x03, 0xFF, 0x8F, 0xFF, 0x38, 0x7E, 0xE0, 0x7B, 0x80, 0xFE, - 0x03, 0xF8, 0x07, 0xE0, 0x1F, 0x80, 0x7E, 0x01, 0xF8, 0x07, 0xE0, 0x3F, - 0x80, 0xFE, 0x07, 0xBF, 0xFE, 0xFF, 0xF3, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xFE, 0xFF, 0xF7, - 0xFF, 0xBC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xFF, 0xFF, - 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xE0, 0x0E, 0x00, - 0xE0, 0x0F, 0xFE, 0xFF, 0xEF, 0xFE, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, - 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0x03, 0xE0, 0x0F, 0xF8, 0x1F, 0xFC, - 0x3E, 0x3E, 0x78, 0x0F, 0x70, 0x0F, 0xF0, 0x00, 0xF0, 0x00, 0xF0, 0x00, - 0xF0, 0x7F, 0xF0, 0x7F, 0xF0, 0x7F, 0xF0, 0x07, 0x78, 0x0F, 0x7C, 0x1F, - 0x3F, 0xFF, 0x1F, 0xFB, 0x0F, 0xF3, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, - 0x0F, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, - 0x03, 0xF8, 0x0F, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3F, - 0x80, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x01, 0xE0, 0x3C, - 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3C, 0x07, - 0x80, 0xFE, 0x1F, 0xC3, 0xF8, 0x7F, 0xFE, 0xFF, 0xCF, 0xF0, 0x00, 0x00, - 0xE0, 0x3D, 0xC0, 0xFB, 0x83, 0xE7, 0x07, 0x8E, 0x1E, 0x1C, 0x78, 0x39, - 0xE0, 0x77, 0x80, 0xFF, 0x01, 0xFF, 0x03, 0xFE, 0x07, 0x9E, 0x0E, 0x1E, - 0x1C, 0x1E, 0x38, 0x3C, 0x70, 0x3C, 0xE0, 0x3D, 0xC0, 0x3C, 0xF0, 0x0F, - 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, - 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, - 0xFF, 0xF8, 0x1F, 0xFC, 0x0F, 0xFE, 0x07, 0xFF, 0x83, 0xFF, 0xC3, 0xFF, - 0xE1, 0xFF, 0xB0, 0xFF, 0xDC, 0x6F, 0xEE, 0x77, 0xF7, 0x3B, 0xF9, 0x9D, - 0xFC, 0xCC, 0xFE, 0x76, 0x7F, 0x3F, 0x3F, 0x8F, 0x9F, 0xC7, 0xCF, 0xE3, - 0xC7, 0xF1, 0xE3, 0xC0, 0xE0, 0x3F, 0xC0, 0xFF, 0x03, 0xFE, 0x0F, 0xFC, - 0x3F, 0xF0, 0xFE, 0xE3, 0xFB, 0x8F, 0xE7, 0x3F, 0x9E, 0xFE, 0x3B, 0xF8, - 0xFF, 0xE1, 0xFF, 0x83, 0xFE, 0x0F, 0xF8, 0x1F, 0xE0, 0x7F, 0x80, 0xF0, - 0x03, 0xE0, 0x07, 0xFC, 0x07, 0xFF, 0x07, 0xCF, 0xC7, 0x81, 0xF3, 0xC0, - 0x7B, 0xC0, 0x1D, 0xE0, 0x0F, 0xF0, 0x07, 0xF8, 0x03, 0xFC, 0x01, 0xFE, - 0x00, 0xFF, 0x00, 0x73, 0xC0, 0x79, 0xF0, 0x7C, 0x7F, 0xFC, 0x1F, 0xFC, - 0x07, 0xFC, 0x00, 0x20, 0x00, 0xFF, 0x07, 0xFF, 0x3F, 0xFD, 0xC3, 0xFE, - 0x07, 0xF0, 0x1F, 0x80, 0xFC, 0x0F, 0xE0, 0xFF, 0xFF, 0xBF, 0xF9, 0xFF, - 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, 0x00, 0x03, - 0xE0, 0x07, 0xFC, 0x07, 0xFF, 0x07, 0xCF, 0xC7, 0x80, 0xF3, 0xC0, 0x7B, - 0xC0, 0x1F, 0xE0, 0x0F, 0xF0, 0x07, 0xF8, 0x03, 0xFC, 0x01, 0xFE, 0x04, - 0xFF, 0x07, 0x73, 0xC3, 0xF9, 0xF0, 0xFC, 0x7F, 0xFC, 0x1F, 0xFF, 0x07, - 0xFF, 0xC0, 0x20, 0xC0, 0xFF, 0x83, 0xFF, 0xCF, 0xFF, 0xBC, 0x3F, 0xF0, - 0x3F, 0xC0, 0xFF, 0x03, 0xFC, 0x0F, 0xFF, 0xFB, 0xFF, 0x8F, 0xFF, 0xBC, - 0x1E, 0xF0, 0x3B, 0xC0, 0xEF, 0x03, 0xBC, 0x0E, 0xF0, 0x3F, 0xC0, 0xF0, - 0x0F, 0x80, 0xFF, 0xC7, 0xFF, 0xBE, 0x1E, 0xF0, 0x3B, 0xC0, 0xFF, 0x00, - 0x3F, 0x80, 0x7F, 0xE0, 0xFF, 0xE0, 0x3F, 0xC0, 0x0F, 0x00, 0x1F, 0x80, - 0x7F, 0x03, 0xFF, 0x1F, 0x7F, 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, - 0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, - 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, - 0x0F, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3F, 0x80, 0xFE, - 0x03, 0xF8, 0x0F, 0xE0, 0x3F, 0xC0, 0xFF, 0x07, 0x9F, 0xFE, 0x7F, 0xF0, - 0x7F, 0x80, 0x20, 0x00, 0xE0, 0x1F, 0xC0, 0x3B, 0xC0, 0xF3, 0x81, 0xE7, - 0x03, 0x8F, 0x0F, 0x0E, 0x1C, 0x1C, 0x38, 0x3C, 0x70, 0x39, 0xC0, 0x73, - 0x80, 0xE7, 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x03, 0xC0, 0x07, 0x80, - 0x0F, 0x00, 0xF0, 0x38, 0x1D, 0xC1, 0xE0, 0xF7, 0x87, 0xC3, 0xDE, 0x1F, - 0x0F, 0x78, 0x7C, 0x38, 0xE3, 0xB0, 0xE3, 0x8E, 0xC7, 0x8F, 0x3B, 0x9C, - 0x1C, 0xEE, 0x70, 0x73, 0x39, 0xC1, 0xDC, 0x67, 0x07, 0x71, 0xB8, 0x0F, - 0xC7, 0xE0, 0x3E, 0x1F, 0x80, 0xF8, 0x3E, 0x01, 0xE0, 0xF0, 0x07, 0x83, - 0xC0, 0x1E, 0x0F, 0x00, 0xF0, 0x3D, 0xF0, 0x79, 0xE1, 0xE1, 0xE3, 0xC3, - 0xCF, 0x03, 0xFC, 0x07, 0xF8, 0x07, 0xE0, 0x07, 0x80, 0x0F, 0x00, 0x3F, - 0x00, 0x7F, 0x01, 0xFE, 0x07, 0x9E, 0x0F, 0x3C, 0x3C, 0x3C, 0xF8, 0x3D, - 0xE0, 0x78, 0xF0, 0x1F, 0xE0, 0x79, 0xE0, 0xF3, 0xC3, 0xC3, 0xC7, 0x87, - 0x9E, 0x07, 0x3C, 0x0F, 0x70, 0x0F, 0xE0, 0x1F, 0x80, 0x1F, 0x00, 0x3C, - 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x00, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x80, - 0x78, 0x07, 0x80, 0x78, 0x07, 0x80, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xCE, 0x73, 0x9C, 0xE7, - 0x39, 0xCE, 0x73, 0x9C, 0xE7, 0x39, 0xCF, 0xFF, 0xE0, 0xC1, 0x81, 0x02, - 0x06, 0x04, 0x08, 0x18, 0x30, 0x20, 0x60, 0xC0, 0x81, 0x83, 0x02, 0x06, - 0xFF, 0xFF, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, - 0x1C, 0x71, 0xC7, 0xFF, 0xFF, 0xC0, 0x0F, 0x00, 0xF0, 0x0F, 0x01, 0xF8, - 0x1B, 0x83, 0x9C, 0x39, 0xC3, 0x0C, 0x70, 0xE7, 0x0E, 0xE0, 0x70, 0xFF, - 0xFF, 0xFF, 0xFC, 0x71, 0x86, 0x3F, 0x87, 0xFC, 0xFF, 0xEE, 0x1E, 0x01, - 0xE0, 0xFE, 0x7F, 0xEF, 0x8E, 0xE1, 0xEE, 0x1E, 0xF3, 0xEF, 0xFE, 0x7E, - 0xE1, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x7B, 0xE3, - 0xFF, 0x9F, 0xFE, 0xF8, 0xF7, 0x83, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF8, - 0x3F, 0xC1, 0xDF, 0x9E, 0xFF, 0xE7, 0xFE, 0x00, 0x40, 0x1F, 0x83, 0xFC, - 0x7F, 0xEF, 0x0E, 0xE0, 0xEE, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0F, 0x0E, - 0x79, 0xE7, 0xFC, 0x3F, 0x80, 0x00, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, - 0x00, 0x71, 0xE7, 0x3F, 0xF7, 0xFF, 0xF0, 0xFE, 0x0F, 0xE0, 0x7E, 0x07, - 0xE0, 0x7E, 0x0F, 0xF0, 0xF7, 0x9F, 0x7F, 0xF3, 0xF7, 0x00, 0x00, 0x1F, - 0x07, 0xFC, 0x7B, 0xEE, 0x0E, 0xE0, 0xEF, 0xFE, 0xFF, 0xFF, 0xFF, 0xE0, - 0x0E, 0x0E, 0xF1, 0xE7, 0xFC, 0x3F, 0x80, 0x40, 0x00, 0x1E, 0x3E, 0x3C, - 0x3C, 0xFF, 0xFF, 0xFF, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, - 0x3C, 0x3C, 0x1E, 0x73, 0xF7, 0x7F, 0xFF, 0x0F, 0xF0, 0xFE, 0x07, 0xE0, - 0x7E, 0x07, 0xE0, 0x7F, 0x0F, 0x79, 0xF7, 0xFF, 0x3F, 0x70, 0x07, 0x00, - 0x7F, 0x0F, 0x7F, 0xE3, 0xFC, 0xE0, 0x1C, 0x03, 0x80, 0x70, 0x0E, 0x01, - 0xDF, 0x3F, 0xF7, 0xFF, 0xF1, 0xFC, 0x1F, 0x83, 0xF0, 0x7E, 0x0F, 0xC1, - 0xF8, 0x3F, 0x07, 0xE0, 0xFC, 0x1C, 0xFF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFC, 0x39, 0xCE, 0x70, 0x1C, 0xE7, 0x39, 0xCE, 0x73, 0x9C, 0xE7, 0x39, - 0xCE, 0x7F, 0xFF, 0xC0, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0xF0, 0x0F, - 0x0E, 0xF1, 0xEF, 0x3C, 0xF7, 0x8F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x8F, - 0x3C, 0xF3, 0xCF, 0x1E, 0xF1, 0xEF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFC, 0xF7, 0xCF, 0x9F, 0xFF, 0xFB, 0xFF, 0xFF, 0xF8, 0x78, 0xFF, - 0x0E, 0x1F, 0xE1, 0xC3, 0xFC, 0x38, 0x7F, 0x87, 0x0F, 0xF0, 0xE1, 0xFE, - 0x1C, 0x3F, 0xC3, 0x87, 0xF8, 0x70, 0xFF, 0x0E, 0x1E, 0xEF, 0x9F, 0xFB, - 0xFF, 0xF8, 0xFE, 0x0F, 0xC1, 0xF8, 0x3F, 0x07, 0xE0, 0xFC, 0x1F, 0x83, - 0xF0, 0x7E, 0x0E, 0x1F, 0x81, 0xFF, 0x1F, 0xFD, 0xE1, 0xEF, 0x07, 0x70, - 0x3F, 0x81, 0xFC, 0x0F, 0xE0, 0x7F, 0x83, 0x9E, 0x3C, 0xFF, 0xC3, 0xFC, - 0x01, 0x00, 0xF7, 0xC7, 0xFF, 0x3F, 0xFD, 0xF1, 0xEF, 0x07, 0xF8, 0x3F, - 0xC1, 0xFE, 0x0F, 0xF0, 0x7F, 0x83, 0xBE, 0x3D, 0xFF, 0xCF, 0x7C, 0x78, - 0x83, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x00, 0x1E, 0x77, 0xF7, 0x7F, - 0xFF, 0x0F, 0xE0, 0xFE, 0x07, 0xE0, 0x7E, 0x07, 0xE0, 0xFF, 0x0F, 0xF9, - 0xF7, 0xFF, 0x3F, 0x70, 0x47, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0xEF, - 0xFF, 0xFF, 0x8E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x00, 0x3F, - 0x8F, 0xFB, 0xEF, 0xF8, 0x7F, 0x01, 0xFE, 0x1F, 0xF0, 0xFF, 0x01, 0xFC, - 0x1F, 0xC3, 0xFF, 0xF7, 0xFC, 0x08, 0x00, 0x38, 0x70, 0xE7, 0xFF, 0xFF, - 0xCE, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0F, 0x8F, 0x04, 0xF0, 0x7F, - 0x07, 0xF0, 0x7F, 0x07, 0xF0, 0x7F, 0x07, 0xF0, 0x7F, 0x07, 0xF0, 0x7F, - 0x0F, 0x79, 0xF7, 0xFF, 0x3F, 0x70, 0x80, 0xF0, 0x7B, 0x83, 0x9E, 0x1C, - 0x71, 0xE3, 0x8E, 0x1E, 0x70, 0x73, 0x83, 0xB8, 0x1D, 0xC0, 0x7E, 0x03, - 0xE0, 0x1F, 0x00, 0x78, 0x00, 0xF0, 0xE1, 0xDC, 0x78, 0x77, 0x1F, 0x3D, - 0xC7, 0xCE, 0x79, 0xF3, 0x8E, 0xEC, 0xE3, 0xBB, 0x78, 0xEC, 0xFC, 0x1F, - 0x3F, 0x07, 0xCF, 0xC1, 0xF1, 0xE0, 0x7C, 0x78, 0x0E, 0x1E, 0x00, 0x78, - 0xF3, 0xC7, 0x8F, 0x78, 0x3B, 0x81, 0xFC, 0x07, 0xC0, 0x1E, 0x01, 0xF0, - 0x1F, 0xC0, 0xEF, 0x0F, 0x78, 0xF1, 0xE7, 0x87, 0x80, 0xF0, 0x7B, 0x83, - 0x9E, 0x1C, 0xF1, 0xE3, 0x8E, 0x1E, 0x70, 0x73, 0x83, 0xB8, 0x1D, 0xC0, - 0x7E, 0x03, 0xE0, 0x1F, 0x00, 0x78, 0x03, 0x80, 0x1C, 0x01, 0xE0, 0x3E, - 0x01, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFC, 0x1E, 0x0F, 0x83, 0xC1, 0xE0, - 0xF0, 0x78, 0x3C, 0x0F, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0x3C, 0xF9, 0xC3, - 0x87, 0x0E, 0x1C, 0x38, 0x73, 0xC7, 0x0F, 0x07, 0x0E, 0x1C, 0x38, 0x70, - 0xE1, 0xC3, 0xE7, 0xC7, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xC3, - 0xCF, 0x0C, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0xC7, 0x3C, 0xE3, 0x8E, 0x38, - 0xE3, 0x8E, 0xF3, 0xCE, 0x00, 0x10, 0x0F, 0x85, 0xBD, 0xE1, 0xF0, 0x08, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, 0xFC, - 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xF0, 0x1F, 0x80, - 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0xC0 }; - -const GFXglyph ubitxGlyphs[] PROGMEM = { - { 0, 13, 18, 17, 2, -17 }, // 0x14 - { 30, 13, 18, 17, 2, -17 }, // 0x15 - { 60, 13, 18, 17, 2, -17 }, // 0x16 - { 90, 13, 18, 17, 2, -17 }, // 0x17 - { 120, 13, 18, 17, 2, -17 }, // 0x18 - { 150, 13, 18, 17, 2, -17 }, // 0x19 - { 180, 13, 18, 17, 2, -17 }, // 0x1A - { 210, 13, 18, 17, 2, -17 }, // 0x1B - { 240, 13, 18, 17, 2, -17 }, // 0x1C - { 270, 13, 18, 17, 2, -17 }, // 0x1D - { 300, 13, 18, 17, 2, -17 }, // 0x1E - { 330, 13, 18, 17, 2, -17 }, // 0x1F - { 360, 0, 0, 7, 0, 1 }, // 0x20 ' ' - { 360, 3, 17, 8, 3, -16 }, // 0x21 '!' - { 367, 9, 7, 11, 1, -17 }, // 0x22 '"' - { 375, 13, 18, 13, 0, -16 }, // 0x23 '#' - { 405, 12, 21, 13, 1, -17 }, // 0x24 '$' - { 437, 20, 17, 21, 1, -16 }, // 0x25 '%' - { 480, 16, 18, 17, 1, -16 }, // 0x26 '&' - { 516, 4, 7, 6, 1, -17 }, // 0x27 ''' - { 520, 6, 23, 8, 1, -17 }, // 0x28 '(' - { 538, 6, 23, 8, 1, -17 }, // 0x29 ')' - { 556, 8, 8, 9, 1, -17 }, // 0x2A '*' - { 564, 12, 11, 14, 1, -10 }, // 0x2B '+' - { 581, 3, 8, 7, 2, -3 }, // 0x2C ',' - { 584, 6, 3, 8, 1, -7 }, // 0x2D '-' - { 587, 3, 4, 7, 2, -3 }, // 0x2E '.' - { 589, 7, 17, 7, 0, -16 }, // 0x2F '/' - { 604, 11, 18, 13, 1, -16 }, // 0x30 '0' - { 629, 7, 17, 13, 2, -16 }, // 0x31 '1' - { 644, 11, 17, 13, 1, -16 }, // 0x32 '2' - { 668, 11, 18, 13, 1, -16 }, // 0x33 '3' - { 693, 12, 17, 13, 1, -16 }, // 0x34 '4' - { 719, 11, 18, 13, 1, -16 }, // 0x35 '5' - { 744, 11, 18, 13, 1, -16 }, // 0x36 '6' - { 769, 12, 17, 13, 1, -16 }, // 0x37 '7' - { 795, 12, 18, 13, 1, -16 }, // 0x38 '8' - { 822, 11, 18, 13, 1, -16 }, // 0x39 '9' - { 847, 3, 12, 8, 3, -11 }, // 0x3A ':' - { 852, 3, 16, 8, 3, -11 }, // 0x3B ';' - { 858, 12, 11, 14, 1, -10 }, // 0x3C '<' - { 875, 12, 9, 14, 1, -9 }, // 0x3D '=' - { 889, 12, 11, 14, 1, -10 }, // 0x3E '>' - { 906, 11, 18, 15, 2, -17 }, // 0x3F '?' - { 931, 22, 21, 23, 1, -17 }, // 0x40 '@' - { 989, 16, 18, 17, 1, -17 }, // 0x41 'A' - { 1025, 14, 18, 17, 2, -17 }, // 0x42 'B' - { 1057, 15, 19, 17, 1, -17 }, // 0x43 'C' - { 1093, 14, 18, 17, 2, -17 }, // 0x44 'D' - { 1125, 13, 18, 16, 2, -17 }, // 0x45 'E' - { 1155, 12, 18, 15, 2, -17 }, // 0x46 'F' - { 1182, 16, 18, 19, 1, -17 }, // 0x47 'G' - { 1218, 14, 18, 17, 2, -17 }, // 0x48 'H' - { 1250, 3, 18, 7, 2, -17 }, // 0x49 'I' - { 1257, 11, 19, 13, 1, -17 }, // 0x4A 'J' - { 1284, 15, 18, 17, 2, -17 }, // 0x4B 'K' - { 1318, 12, 18, 15, 2, -17 }, // 0x4C 'L' - { 1345, 17, 18, 20, 2, -17 }, // 0x4D 'M' - { 1384, 14, 18, 17, 2, -17 }, // 0x4E 'N' - { 1416, 17, 19, 19, 1, -17 }, // 0x4F 'O' - { 1457, 13, 18, 16, 2, -17 }, // 0x50 'P' - { 1487, 17, 19, 19, 1, -17 }, // 0x51 'Q' - { 1528, 14, 18, 17, 2, -17 }, // 0x52 'R' - { 1560, 14, 19, 16, 1, -17 }, // 0x53 'S' - { 1594, 14, 18, 15, 0, -17 }, // 0x54 'T' - { 1626, 14, 19, 17, 2, -17 }, // 0x55 'U' - { 1660, 15, 18, 16, 1, -17 }, // 0x56 'V' - { 1694, 22, 18, 23, 0, -17 }, // 0x57 'W' - { 1744, 15, 18, 16, 1, -17 }, // 0x58 'X' - { 1778, 15, 18, 16, 1, -17 }, // 0x59 'Y' - { 1812, 13, 18, 15, 1, -17 }, // 0x5A 'Z' - { 1842, 5, 23, 8, 2, -17 }, // 0x5B '[' - { 1857, 7, 17, 7, 0, -16 }, // 0x5C '\' - { 1872, 6, 23, 8, 0, -17 }, // 0x5D ']' - { 1890, 12, 11, 14, 1, -16 }, // 0x5E '^' - { 1907, 15, 2, 13, -1, 4 }, // 0x5F '_' - { 1911, 5, 3, 8, 0, -17 }, // 0x60 '`' - { 1913, 12, 14, 13, 1, -12 }, // 0x61 'a' - { 1934, 13, 19, 15, 1, -17 }, // 0x62 'b' - { 1965, 12, 14, 13, 1, -12 }, // 0x63 'c' - { 1986, 12, 19, 15, 1, -17 }, // 0x64 'd' - { 2015, 12, 14, 13, 1, -12 }, // 0x65 'e' - { 2036, 8, 18, 8, 0, -17 }, // 0x66 'f' - { 2054, 12, 18, 15, 1, -12 }, // 0x67 'g' - { 2081, 11, 18, 15, 2, -17 }, // 0x68 'h' - { 2106, 3, 18, 7, 2, -17 }, // 0x69 'i' - { 2113, 5, 23, 7, 0, -17 }, // 0x6A 'j' - { 2128, 12, 18, 13, 1, -17 }, // 0x6B 'k' - { 2155, 3, 18, 7, 2, -17 }, // 0x6C 'l' - { 2162, 19, 13, 21, 1, -12 }, // 0x6D 'm' - { 2193, 11, 13, 15, 2, -12 }, // 0x6E 'n' - { 2211, 13, 14, 15, 1, -12 }, // 0x6F 'o' - { 2234, 13, 18, 15, 1, -12 }, // 0x70 'p' - { 2264, 12, 18, 15, 1, -12 }, // 0x71 'q' - { 2291, 7, 13, 9, 2, -12 }, // 0x72 'r' - { 2303, 11, 14, 13, 1, -12 }, // 0x73 's' - { 2323, 7, 17, 8, 0, -15 }, // 0x74 't' - { 2338, 12, 14, 15, 1, -12 }, // 0x75 'u' - { 2359, 13, 13, 13, 0, -12 }, // 0x76 'v' - { 2381, 18, 13, 19, 0, -12 }, // 0x77 'w' - { 2411, 13, 13, 13, 0, -12 }, // 0x78 'x' - { 2433, 13, 18, 13, 0, -12 }, // 0x79 'y' - { 2463, 10, 13, 12, 1, -12 }, // 0x7A 'z' - { 2480, 7, 23, 9, 1, -17 }, // 0x7B '{' - { 2501, 2, 23, 7, 2, -17 }, // 0x7C '|' - { 2507, 6, 23, 9, 2, -17 }, // 0x7D '}' - { 2525, 11, 5, 14, 1, -7 }, // 0x7E '~' - { 2532, 13, 18, 17, 2, -17 } }; // 0x7F - -const GFXfont ubitx_font PROGMEM = { - (uint8_t *)ubitxBitmaps, - (GFXglyph *)ubitxGlyphs, - 0x14, 0x7F, 33 }; - -// Approx. 3325 bytes - -// Color definitions -#define DISPLAY_BLACK 0x0000 ///< 0, 0, 0 -#define DISPLAY_NAVY 0x000F ///< 0, 0, 123 -#define DISPLAY_DARKGREEN 0x03E0 ///< 0, 125, 0 -#define DISPLAY_DARKCYAN 0x03EF ///< 0, 125, 123 -#define DISPLAY_MAROON 0x7800 ///< 123, 0, 0 -#define DISPLAY_PURPLE 0x780F ///< 123, 0, 123 -#define DISPLAY_OLIVE 0x7BE0 ///< 123, 125, 0 -#define DISPLAY_LIGHTGREY 0xC618 ///< 198, 195, 198 -#define DISPLAY_DARKGREY 0x7BEF ///< 123, 125, 123 -#define DISPLAY_BLUE 0x001F ///< 0, 0, 255 -#define DISPLAY_GREEN 0x07E0 ///< 0, 255, 0 -#define DISPLAY_CYAN 0x07FF ///< 0, 255, 255 -#define DISPLAY_RED 0xF800 ///< 255, 0, 0 -#define DISPLAY_MAGENTA 0xF81F ///< 255, 0, 255 -#define DISPLAY_YELLOW 0xFFE0 ///< 255, 255, 0 -#define DISPLAY_WHITE 0xFFFF ///< 255, 255, 255 -#define DISPLAY_ORANGE 0xFD20 ///< 255, 165, 0 -#define DISPLAY_GREENYELLOW 0xAFE5 ///< 173, 255, 41 -#define DISPLAY_PINK 0xFC18 ///< 255, 130, 198 - #endif // _NANO_GUI_H_ - - diff --git a/pin_definitions.h b/pin_definitions.h new file mode 100644 index 0000000..a1521ad --- /dev/null +++ b/pin_definitions.h @@ -0,0 +1,45 @@ +#pragma once + +/* + * Pin configurations for uBiTXv6 + * + * Attached devices are: + * * A rotory encoder for tuning, which has a built-in push button for selection + * * A Push-to-talk button input + * * A morse keyer input, using analog voltage divider for paddle support + * * A SI5351A 3-output frequency generator + * * A model tjctm24028-spi touch screen LCD, which has: + * * An ILI9341 display controller + * * A XPT2046 touch controller + * * A TX/RX output control pin + * * A tone output pin to provide audio feedback to the operator when sending CW + * * Three filter selection output control pins + * * A CW keydown output + * * Optionally, a serial CAT interface + */ + +static const uint8_t PIN_ENC_A = A0; // Tuning encoder interface +static const uint8_t PIN_ENC_B = A1; // Tuning encoder interface +static const uint8_t PIN_ENC_PUSH_BUTTON = A2; // Tuning encoder interface +static const uint8_t PIN_PTT = A3; // Sense it for ssb and as a straight key for cw operation +//A4 is I2C SDA +//A5 is I2C SCK +static const uint8_t PIN_ANALOG_KEYER = A6; // This is used as keyer. The analog port has 4.7K pull up resistor. Details are in the circuit description on www.hfsignals.com +static const uint8_t PIN_ANALOG_SPARE = A7; // Not used yet + +//13 is SPI CLK +//12 is SPI MISO +//11 is SPI MOSI +static const uint8_t PIN_TFT_CS = 10; // Selects the LCD controller on SPI interface (active low) +static const uint8_t PIN_TFT_DC = 9; // Tells the LCD controller if it's getting data (D, high) or commands (C, low) +static const uint8_t PIN_TOUCH_CS = 8; // Selects the touch controller on SPI interface (active low) +static const uint8_t PIN_TX_RXn = 7; // Pin from the Nano to the radio to switch to TX (HIGH) and RX(LOW) +static const uint8_t PIN_CW_TONE = 6; // Generates a square wave sidetone while sending the CW +static const uint8_t PIN_TX_LPF_A = 5; // The 30 MHz LPF is permanently connected in the output of the PA... +static const uint8_t PIN_TX_LPF_B = 4; // ...Alternatively, either 3.5 MHz, 7 MHz or 14 Mhz LPFs are... +static const uint8_t PIN_TX_LPF_C = 3; // ...switched inline depending upon the TX frequency +static const uint8_t PIN_CW_KEY = 2; // Pin goes high during CW keydown to transmit the carrier. + // ... The PIN_CW_KEY is needed in addition to the TX/RX key as the... + // ...key can be up within a tx period +//1 is UART RX +//0 is UART TX diff --git a/point.h b/point.h new file mode 100644 index 0000000..f716a8d --- /dev/null +++ b/point.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +struct Point { + int16_t x; + int16_t y; +}; diff --git a/push_button.cpp b/push_button.cpp new file mode 100644 index 0000000..fb34813 --- /dev/null +++ b/push_button.cpp @@ -0,0 +1,34 @@ +#include +#include "button_timing.h" +#include "pin_definitions.h" +#include "push_button.h" + +bool IsButtonPressed() +{ + //Button has a pullup, so it reads high normally, + //and reads low when pressed down + return !digitalRead(PIN_ENC_PUSH_BUTTON); +} + +ButtonPress_e CheckTunerButton(){ + if (!IsButtonPressed()){ + return ButtonPress_e::NotPressed; + } + delay(DEBOUNCE_DELAY_MS); + if (!IsButtonPressed()){//debounce + return ButtonPress_e::NotPressed; + } + + uint16_t down_time = 0; + while(IsButtonPressed() && (down_time < LONG_PRESS_TIME_MS)){ + delay(LONG_PRESS_POLL_TIME_MS); + down_time += LONG_PRESS_POLL_TIME_MS; + } + + if(down_time < LONG_PRESS_TIME_MS){ + return ButtonPress_e::ShortPress; + } + else{ + return ButtonPress_e::LongPress; + } +} diff --git a/push_button.h b/push_button.h new file mode 100644 index 0000000..c83a173 --- /dev/null +++ b/push_button.h @@ -0,0 +1,5 @@ +#pragma once + +#include "button_press_e.h" + +ButtonPress_e CheckTunerButton(); diff --git a/scratch_space.h b/scratch_space.h new file mode 100644 index 0000000..50b1f5a --- /dev/null +++ b/scratch_space.h @@ -0,0 +1,13 @@ +/** + * The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory. + * We have to be very careful with variables that are declared inside the functions as they are + * created in a memory region called the stack. The stack has just a few bytes of space on the Arduino + * if you declare large strings inside functions, they can easily exceed the capacity of the stack + * and mess up your programs. + * We circumvent this by declaring a few global buffers as kitchen counters where we can + * slice and dice our strings. These strings are mostly used to control the display or handle + * the input and output from the USB port. We must keep a count of the bytes used while reading + * the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable. + */ + +extern char c[30], b[128]; \ No newline at end of file diff --git a/settings.cpp b/settings.cpp new file mode 100644 index 0000000..7abad2e --- /dev/null +++ b/settings.cpp @@ -0,0 +1,193 @@ +#include //memset +#include +#include +#include //only needed for debugging's Serial.print stuff +#include "nano_gui.h"//redrawVFOs() function +#include "settings.h" +#include "si5351.h" + +/** + * These are the "magic" indices where these user changable settinngs are stored in the EEPROM + */ +static const uint16_t EEPROM_ADDR_MASTER_CAL = 0;//int32_t +//4 is currently unused, but may have been LSB_CAL on other versions +static const uint16_t EEPROM_ADDR_USB_CAL = 8;//uint32_t +//12 is currently unused, but may have been CW_SIDETONE on other versions? +static const uint16_t EEPROM_ADDR_VFO_A_FREQ = 16;//uint32_t +static const uint16_t EEPROM_ADDR_VFO_B_FREQ = 20;//uint32_t +static const uint16_t EEPROM_ADDR_CW_SIDETONE = 24;//uint32_t +static const uint16_t EEPROM_ADDR_CW_DIT_TIME = 28;//uint32_t +static const uint16_t EEPROM_ADDR_TOUCH_SLOPE_X = 32;//int16_t +static const uint16_t EEPROM_ADDR_TOUCH_SLOPE_Y = 36;//int16_t +static const uint16_t EEPROM_ADDR_TOUCH_OFFSET_X = 40;//int16_t +static const uint16_t EEPROM_ADDR_TOUCH_OFFSET_Y = 44;//int16_t +static const uint16_t EEPROM_ADDR_MORSE_MENU = 46;//uint8_t +static const uint16_t EEPROM_ADDR_CW_DELAYTIME = 48;//uint16_t +static const uint16_t EEPROM_ADDR_VFO_A_MODE = 256;//uint8_t +static const uint16_t EEPROM_ADDR_VFO_B_MODE = 257;//uint8_t +static const uint16_t EEPROM_ADDR_CW_KEY_TYPE = 358;//uint8_t +static const uint16_t EEPROM_ADDR_QUICKLIST_FREQ = 630;//uint32_t array of size NUM_QUICKLIST_SETTINGS +static const uint16_t EEPROM_ADDR_QUICKLIST_MODE = 710;//uint8_t array of size NUM_QUICKLIST_SETTINGS + + +template +bool LoadSane(T& dest,uint16_t addr, T min, T max) +{ + T read_value; + EEPROM.get(addr,read_value); + if((min <= read_value) && (read_value <= max)){ + dest = read_value; + //Serial.print(addr); + //Serial.print(F(":")); + //Serial.println(dest); + return true; + } + //Serial.print(addr); + //Serial.print(F(": Not valid: ")); + //Serial.print(read_value); + //Serial.print(F(" Leaving value at ")); + //Serial.println(dest); + return false; +} + +//This is the non-extern version that actually gets built +SettingsRam globalSettings; + +void LoadDefaultSettings() +{ + memset(&globalSettings,0x00,sizeof(globalSettings)); + + globalSettings.oscillatorCal = 0L; + globalSettings.usbCarrierFreq = 11052000UL; + + globalSettings.activeVfo = Vfo_e::VFO_A; + globalSettings.vfoA.frequency = 7150000UL; + globalSettings.vfoA.mode = VFO_MODE_LSB; + globalSettings.vfoB.frequency = 14150000UL; + globalSettings.vfoB.mode = VFO_MODE_USB; + + for(uint8_t i = 0; i < NUM_QUICKLIST_SETTINGS; ++i){ + globalSettings.quickList[i].frequency = i*1000000; + globalSettings.quickList[i].mode = VfoMode_e::VFO_MODE_LSB; + } + + globalSettings.keyerMode = KEYER_STRAIGHT; + globalSettings.cwSideToneFreq = 800; + globalSettings.cwDitDurationMs = 100; + globalSettings.cwActiveTimeoutMs = 50; + + globalSettings.touchSlopeX = 104; + globalSettings.touchSlopeY = 137; + globalSettings.touchOffsetX = 28; + globalSettings.touchOffsetY = 29; + + globalSettings.ritOn = false; + globalSettings.ritFrequency = globalSettings.vfoA.frequency; + + globalSettings.tuningMode = TuningMode_e::TUNE_SSB; + + globalSettings.splitOn = false; + + globalSettings.txActive = false; + globalSettings.txCatActive = false; + globalSettings.cwExpirationTimeMs = 0; + globalSettings.morseMenuOn = false; +} + +void LoadSettingsFromEeprom() +{ + LoadSane(globalSettings.usbCarrierFreq,EEPROM_ADDR_USB_CAL,11048000UL,11060000UL); + LoadSane(globalSettings.vfoA.frequency,EEPROM_ADDR_VFO_A_FREQ,SI5351_MIN_FREQUENCY_HZ,SI5351_MAX_FREQUENCY_HZ); + LoadSane(globalSettings.vfoB.frequency,EEPROM_ADDR_VFO_B_FREQ,SI5351_MIN_FREQUENCY_HZ,SI5351_MAX_FREQUENCY_HZ); + LoadSane(globalSettings.cwSideToneFreq,EEPROM_ADDR_CW_SIDETONE,100UL,2000UL); + LoadSane(globalSettings.cwDitDurationMs,EEPROM_ADDR_CW_DIT_TIME,10U,1000U); + if(LoadSane(globalSettings.cwActiveTimeoutMs,EEPROM_ADDR_CW_DELAYTIME,10U,100U)){ + globalSettings.cwActiveTimeoutMs *= 10;//scale by 10 for legacy reasons + } + LoadSane(globalSettings.vfoA.mode,EEPROM_ADDR_VFO_A_MODE,VFO_MODE_LSB,VFO_MODE_USB); + LoadSane(globalSettings.vfoB.mode,EEPROM_ADDR_VFO_B_MODE,VFO_MODE_LSB,VFO_MODE_USB); + LoadSane(globalSettings.keyerMode,EEPROM_ADDR_CW_KEY_TYPE,KEYER_STRAIGHT,KEYER_IAMBIC_B); + + uint8_t morse_on = 0; + LoadSane(morse_on,EEPROM_ADDR_MORSE_MENU,(uint8_t)0,(uint8_t)1); + globalSettings.morseMenuOn = morse_on; + + for(uint8_t i = 0; i < NUM_QUICKLIST_SETTINGS; ++i){ + LoadSane(globalSettings.quickList[i].frequency,EEPROM_ADDR_QUICKLIST_FREQ+(sizeof(uint32_t)*i),SI5351_MIN_FREQUENCY_HZ,SI5351_MAX_FREQUENCY_HZ); + LoadSane(globalSettings.quickList[i].mode,EEPROM_ADDR_QUICKLIST_MODE+(sizeof(uint8_t)*i),VFO_MODE_LSB,VFO_MODE_USB); + } + + //No sanity check on these - cal your heart out + EEPROM.get(EEPROM_ADDR_MASTER_CAL,globalSettings.oscillatorCal); + EEPROM.get(EEPROM_ADDR_TOUCH_SLOPE_X,globalSettings.touchSlopeX); + EEPROM.get(EEPROM_ADDR_TOUCH_SLOPE_Y,globalSettings.touchSlopeY); + EEPROM.get(EEPROM_ADDR_TOUCH_OFFSET_X,globalSettings.touchOffsetX); + EEPROM.get(EEPROM_ADDR_TOUCH_OFFSET_Y,globalSettings.touchOffsetY); +} + +void SaveSettingsToEeprom() +{ + //Serial.println(F("Saving...")); + EEPROM.put(EEPROM_ADDR_MASTER_CAL,globalSettings.oscillatorCal); + EEPROM.put(EEPROM_ADDR_USB_CAL,globalSettings.usbCarrierFreq); + EEPROM.put(EEPROM_ADDR_VFO_A_FREQ,globalSettings.vfoA.frequency); + EEPROM.put(EEPROM_ADDR_VFO_B_FREQ,globalSettings.vfoB.frequency); + EEPROM.put(EEPROM_ADDR_CW_SIDETONE,globalSettings.cwSideToneFreq); + EEPROM.put(EEPROM_ADDR_CW_DIT_TIME,globalSettings.cwDitDurationMs); + EEPROM.put(EEPROM_ADDR_TOUCH_SLOPE_X,globalSettings.touchSlopeX); + EEPROM.put(EEPROM_ADDR_TOUCH_SLOPE_Y,globalSettings.touchSlopeY); + EEPROM.put(EEPROM_ADDR_TOUCH_OFFSET_X,globalSettings.touchOffsetX); + EEPROM.put(EEPROM_ADDR_TOUCH_OFFSET_Y,globalSettings.touchOffsetY); + EEPROM.put(EEPROM_ADDR_CW_DELAYTIME,globalSettings.cwActiveTimeoutMs/10);//scale by 10 for legacy reasons + EEPROM.put(EEPROM_ADDR_VFO_A_MODE,globalSettings.vfoA.mode); + EEPROM.put(EEPROM_ADDR_VFO_B_MODE,globalSettings.vfoB.mode); + EEPROM.put(EEPROM_ADDR_CW_KEY_TYPE,globalSettings.keyerMode); + EEPROM.put(EEPROM_ADDR_MORSE_MENU,(uint8_t)globalSettings.morseMenuOn); + + for(uint8_t i = 0; i < NUM_QUICKLIST_SETTINGS; ++i){ + EEPROM.put(EEPROM_ADDR_QUICKLIST_FREQ+(sizeof(uint32_t)*i),globalSettings.quickList[i].frequency); + EEPROM.put(EEPROM_ADDR_QUICKLIST_MODE+(sizeof(uint8_t)*i),globalSettings.quickList[i].mode); + } +} + +uint32_t GetActiveVfoFreq() +{ + if(VFO_A == globalSettings.activeVfo){ + return globalSettings.vfoA.frequency; + } + else{ + return globalSettings.vfoB.frequency; + } +} + +void SetActiveVfoFreq(uint32_t frequency) +{ + if(VFO_A == globalSettings.activeVfo) + { + globalSettings.vfoA.frequency = frequency; + } + else{ + globalSettings.vfoB.frequency = frequency; + } +} + +VfoMode_e GetActiveVfoMode() +{ + if(VFO_A == globalSettings.activeVfo){ + return globalSettings.vfoA.mode; + } + else{ + return globalSettings.vfoB.mode; + } +} + +void SetActiveVfoMode(VfoMode_e mode) +{ + if(VFO_A == globalSettings.activeVfo) + { + globalSettings.vfoA.mode = mode; + } + else{ + globalSettings.vfoB.mode = mode; + } +} diff --git a/settings.h b/settings.h new file mode 100644 index 0000000..19d4e28 --- /dev/null +++ b/settings.h @@ -0,0 +1,116 @@ +/* + * This class deals with all of the radio settings, + * so that other areas of the code doesn't have to + * + * Example usage: + * LoadSettingsFromEeprom(); + * Serial.println(globalSettings.vfoAFreq); + * globalSettings.vfoAFreq = 12345678; + * SaveSettingsToEeprom(); + * Serial.println(globalSettings.vfoAFreq); + */ + +#pragma once + +#include //uint8_t etc. + +static const uint8_t NUM_QUICKLIST_SETTINGS = 4; + +/* + * Loads default values for all settings + */ +void LoadDefaultSettings(); + +/* + * Loads all persistent settings from the EEPROM + */ +void LoadSettingsFromEeprom(); + +/* + * Saves all persistent settings to the EEPROM + * + * It's a little CPU-cycle-wasteful to save EVERYTHING + * each time, but keeps things simple + */ +void SaveSettingsToEeprom(); + +/* + * These are all of the settings + * Note that not all settings are saved to the EEPROM + */ +enum Vfo_e : uint8_t +{ + VFO_A, + VFO_B +}; + +enum VfoMode_e : uint8_t +{ + VFO_MODE_LSB = 2, + VFO_MODE_USB = 3 +}; + +struct VfoSettings_t +{ + uint32_t frequency; + VfoMode_e mode; +}; + +enum TuningMode_e : uint8_t +{ + TUNE_SSB, + TUNE_CW +}; + +enum KeyerMode_e : uint8_t +{ + KEYER_STRAIGHT, + KEYER_IAMBIC_A, + KEYER_IAMBIC_B +}; + +/* + * This is the definition of the settings/state variables + */ +struct SettingsRam +{ + uint32_t oscillatorCal; + uint32_t usbCarrierFreq; + + Vfo_e activeVfo; + VfoSettings_t vfoA; + VfoSettings_t vfoB; + + VfoSettings_t quickList[4]; + + KeyerMode_e keyerMode; + uint32_t cwSideToneFreq; + uint16_t cwDitDurationMs; + uint16_t cwActiveTimeoutMs; + + int16_t touchSlopeX; + int16_t touchSlopeY; + int16_t touchOffsetX; + int16_t touchOffsetY; + + bool ritOn; + uint32_t ritFrequency; + + TuningMode_e tuningMode; + + bool splitOn; + + bool txActive; + bool txCatActive; + uint32_t cwExpirationTimeMs; + bool morseMenuOn; +}; + +//This is the shared declaration +extern SettingsRam globalSettings; + +//Some convenience functions +uint32_t GetActiveVfoFreq(); +void SetActiveVfoFreq(uint32_t frequency); +VfoMode_e GetActiveVfoMode(); +void SetActiveVfoMode(VfoMode_e mode); diff --git a/setup.cpp b/setup.cpp index 616cfda..64691f9 100644 --- a/setup.cpp +++ b/setup.cpp @@ -1,8 +1,16 @@ -#include -#include +#include "toneAC2/toneAC2.h" +#include "colors.h" +#include "encoder.h" +#include "menu.h" #include "morse.h" -#include "ubitx.h" #include "nano_gui.h" +#include "pin_definitions.h" +#include "scratch_space.h" +#include "setup.h" +#include "settings.h" +#include "si5351.h" +#include "tuner.h" +#include "utils.h" /** Menus * The Radio menus are accessed by tapping on the function button. @@ -17,287 +25,644 @@ * - If the menu item is NOT clicked on, then the menu's prompt is to be displayed */ -void setupExit(){ - menuOn = 0; -} - - //this is used by the si5351 routines in the ubitx_5351 file -extern int32_t calibration; -extern uint32_t si5351bx_vcoa; - -void setupFreq(){ - int knob = 0; - int32_t prev_calibration; - - displayDialog("Set Frequency", "Push TUNE to Save"); - - //round off the the nearest khz - frequency = (frequency/1000l)* 1000l; - setFrequency(frequency); - - displayRawText("You should have a", 20, 50, DISPLAY_CYAN, DISPLAY_NAVY); - displayRawText("signal exactly at ", 20, 75, DISPLAY_CYAN, DISPLAY_NAVY); - ltoa(frequency/1000l, c, 10); - strcat(c, " KHz"); - displayRawText(c, 20, 100, DISPLAY_CYAN, DISPLAY_NAVY); - - displayRawText("Rotate to zerobeat", 20, 180, DISPLAY_CYAN, DISPLAY_NAVY); - //keep clear of any previous button press - while (btnDown()) - active_delay(100); - active_delay(100); - - prev_calibration = calibration; - calibration = 0; - -// ltoa(calibration/8750, c, 10); -// strcpy(b, c); -// strcat(b, "Hz"); -// printLine2(b); - - while (!btnDown()) - { - knob = enc_read(); - if (knob != 0) - calibration += knob * 875; - /* else if (knob < 0) - calibration -= 875; */ - else - continue; //don't update the frequency or the display - - si5351bx_setfreq(0, usbCarrier); //set back the cardrier oscillator anyway, cw tx switches it off - si5351_set_calibration(calibration); - setFrequency(frequency); - - //displayRawText("Rotate to zerobeat", 20, 120, DISPLAY_CYAN, DISPLAY_NAVY); - - ltoa(calibration, b, 10); - displayText(b, 100, 140, 100, 26, DISPLAY_CYAN, DISPLAY_NAVY, DISPLAY_WHITE); - } +static const unsigned int COLOR_TEXT = DISPLAY_WHITE; +static const unsigned int COLOR_BACKGROUND = DISPLAY_BLACK; +static const unsigned int COLOR_TITLE_BACKGROUND = DISPLAY_NAVY; +static const unsigned int COLOR_SETTING_BACKGROUND = DISPLAY_NAVY; +static const unsigned int COLOR_ACTIVE_BORDER = DISPLAY_WHITE; +static const unsigned int COLOR_INACTIVE_BORDER = COLOR_BACKGROUND; + +static const unsigned int LAYOUT_TITLE_X = 12; +static const unsigned int LAYOUT_TITLE_Y = 12; +static const unsigned int LAYOUT_TITLE_WIDTH = 296; +static const unsigned int LAYOUT_TITLE_HEIGHT = 35; + +static const unsigned int LAYOUT_ITEM_X = 30; +static const unsigned int LAYOUT_ITEM_Y = LAYOUT_TITLE_Y + LAYOUT_TITLE_HEIGHT + 5; +static const unsigned int LAYOUT_ITEM_WIDTH = 260; +static const unsigned int LAYOUT_ITEM_HEIGHT = 30; +static const unsigned int LAYOUT_ITEM_PITCH_Y = LAYOUT_ITEM_HEIGHT + 1; + +static const unsigned int LAYOUT_SETTING_REF_VALUE_X = LAYOUT_ITEM_X; +static const unsigned int LAYOUT_SETTING_REF_VALUE_Y = LAYOUT_ITEM_Y + 3*LAYOUT_ITEM_PITCH_Y; +static const unsigned int LAYOUT_SETTING_REF_VALUE_WIDTH = LAYOUT_ITEM_WIDTH; +static const unsigned int LAYOUT_SETTING_REF_VALUE_HEIGHT = LAYOUT_ITEM_HEIGHT; + +static const unsigned int LAYOUT_SETTING_VALUE_X = LAYOUT_ITEM_X; +static const unsigned int LAYOUT_SETTING_VALUE_Y = LAYOUT_ITEM_Y + 4*LAYOUT_ITEM_PITCH_Y; +static const unsigned int LAYOUT_SETTING_VALUE_WIDTH = LAYOUT_ITEM_WIDTH; +static const unsigned int LAYOUT_SETTING_VALUE_HEIGHT = LAYOUT_ITEM_HEIGHT; + +static const unsigned int LAYOUT_INSTRUCTIONS_TEXT_X = 20; +static const unsigned int LAYOUT_INSTRUCTIONS_TEXT_Y = LAYOUT_ITEM_Y; +static const unsigned int LAYOUT_INSTRUCTIONS_TEXT_WIDTH = LAYOUT_ITEM_WIDTH; +static const unsigned int LAYOUT_INSTRUCTIONS_TEXT_HEIGHT = LAYOUT_SETTING_REF_VALUE_Y - LAYOUT_ITEM_Y - 1; + +static const unsigned int LAYOUT_CONFIRM_TEXT_X = 20; +static const unsigned int LAYOUT_CONFIRM_TEXT_Y = LAYOUT_ITEM_Y + 5*LAYOUT_ITEM_PITCH_Y; +static const unsigned int LAYOUT_CONFIRM_TEXT_WIDTH = LAYOUT_ITEM_WIDTH; +static const unsigned int LAYOUT_CONFIRM_TEXT_HEIGHT = LAYOUT_ITEM_HEIGHT; + +constexpr char strYes [] PROGMEM = "Yes"; +constexpr char strNo [] PROGMEM = "No"; +constexpr char strHz [] PROGMEM = "Hz"; + +void displayDialog(const char* title, + const char* instructions){ + displayClear(COLOR_BACKGROUND); + strncpy_P(b,title,sizeof(b)); + displayText(b, LAYOUT_TITLE_X, LAYOUT_TITLE_Y, LAYOUT_TITLE_WIDTH, LAYOUT_TITLE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_ACTIVE_BORDER); + strncpy_P(b,instructions,sizeof(b)); + displayText(b, LAYOUT_INSTRUCTIONS_TEXT_X, LAYOUT_INSTRUCTIONS_TEXT_Y, LAYOUT_INSTRUCTIONS_TEXT_WIDTH, LAYOUT_INSTRUCTIONS_TEXT_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND, TextJustification_e::Left); + strncpy_P(b,(const char*)F("Push Tune to Save"),sizeof(b)); + displayText(b, LAYOUT_CONFIRM_TEXT_X, LAYOUT_CONFIRM_TEXT_Y, LAYOUT_CONFIRM_TEXT_WIDTH, LAYOUT_CONFIRM_TEXT_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_BACKGROUND); +} - EEPROM.put(MASTER_CAL, calibration); - initOscillators(); - si5351_set_calibration(calibration); - setFrequency(frequency); - - //debounce and delay - while(btnDown()) - active_delay(50); - active_delay(100); -} - -void setupBFO(){ - int knob = 0; - unsigned long prevCarrier; - - prevCarrier = usbCarrier; - - displayDialog("Set BFO", "Press TUNE to Save"); - - usbCarrier = 11053000l; - si5351bx_setfreq(0, usbCarrier); - printCarrierFreq(usbCarrier); - - while (!btnDown()){ - knob = enc_read(); - - if (knob != 0) - usbCarrier -= 50 * knob; - else - continue; //don't update the frequency or the display - - si5351bx_setfreq(0, usbCarrier); - setFrequency(frequency); - printCarrierFreq(usbCarrier); - - active_delay(100); - } +struct SettingScreen_t { + const char* const Title; + const char* const AdditionalText; + const uint16_t KnobDivider; + const int16_t StepSize;//int so that it can be negative + void (*Initialize)(long int* start_value_out); + void (*Validate)(const long int candidate_value_in, long int* validated_value_out); + void (*OnValueChange)(const long int new_value, char* buff_out, const size_t buff_out_size); + void (*Finalize)(const long int final_value); +}; + +void drawSetting(const SettingScreen_t* const screen) +{ + displayDialog(screen->Title, + screen->AdditionalText); +} - EEPROM.put(USB_CAL, usbCarrier); - si5351bx_setfreq(0, usbCarrier); - setFrequency(frequency); - updateDisplay(); - menuOn = 0; +//State variables for settings +int32_t setupMenuRawValue = 0; +int32_t setupMenuLastValue = 0; +const SettingScreen_t* activeSettingP; + +void activateSetting(SettingScreen_t* new_setting_P); + +void initSetting(); +MenuReturn_e runSetting(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob); +Menu_t setupMenuActiveSettingMenu = { + initSetting, + runSetting, + nullptr +}; + +void initSetting() +{ + if(nullptr == activeSettingP){ + return; + } + SettingScreen_t screen = {nullptr,nullptr,0,0,nullptr,nullptr,nullptr,nullptr}; + memcpy_P(&screen,activeSettingP,sizeof(screen)); + drawSetting(&screen); + screen.Initialize(&setupMenuLastValue); + screen.OnValueChange(setupMenuLastValue,b,sizeof(b)); + displayText(b, LAYOUT_SETTING_VALUE_X, LAYOUT_SETTING_VALUE_Y, LAYOUT_SETTING_VALUE_WIDTH, LAYOUT_SETTING_VALUE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_BACKGROUND); + displayText(b, LAYOUT_SETTING_REF_VALUE_X, LAYOUT_SETTING_REF_VALUE_Y, LAYOUT_SETTING_REF_VALUE_WIDTH, LAYOUT_SETTING_REF_VALUE_HEIGHT, COLOR_SETTING_BACKGROUND, COLOR_BACKGROUND, COLOR_BACKGROUND); + setupMenuRawValue = setupMenuLastValue * (int32_t)screen.KnobDivider; } -void setupCwDelay(){ - int knob = 0; - int prev_cw_delay; +MenuReturn_e runSetting(const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob) +{ + if(nullptr == activeSettingP){ + return MenuReturn_e::ExitedRedraw; + } + SettingScreen_t screen = {nullptr,nullptr,0,0,nullptr,nullptr,nullptr,nullptr}; + memcpy_P(&screen,activeSettingP,sizeof(screen)); - displayDialog("Set CW T/R Delay", "Press tune to Save"); + if(ButtonPress_e::NotPressed != tuner_button){ + //Long or short press, we do the same thing + screen.Finalize(setupMenuLastValue); + return MenuReturn_e::ExitedRedraw; + } - active_delay(500); - prev_cw_delay = cwDelayTime; + (void)touch_button;(void)touch_point;//TODO: handle touch input? - itoa(10 * (int)cwDelayTime, b, 10); - strcat(b, " msec"); - displayText(b, 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); + if(0 != knob){ + setupMenuRawValue += knob * screen.StepSize; - while (!btnDown()){ - knob = enc_read(); + const int32_t candidate_value = setupMenuRawValue / (int32_t)screen.KnobDivider; + int32_t value = 0; + screen.Validate(candidate_value,&value); - if (knob < 0 && cwDelayTime > 10) - cwDelayTime -= 10; - else if (knob > 0 && cwDelayTime < 100) - cwDelayTime += 10; - else - continue; //don't update the frequency or the display + //If we're going out of bounds, prevent the raw value from going too far out + if(candidate_value != value){ + setupMenuRawValue = value * (int32_t)screen.KnobDivider; + } - itoa(10 * (int)cwDelayTime, b, 10); - strcat(b, " msec"); - displayText(b, 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); - + if(value != setupMenuLastValue){ + screen.OnValueChange(value,b,sizeof(b)); + displayText(b, LAYOUT_SETTING_VALUE_X, LAYOUT_SETTING_VALUE_Y, LAYOUT_SETTING_VALUE_WIDTH, LAYOUT_SETTING_VALUE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_BACKGROUND); + setupMenuLastValue = value; + } } - EEPROM.put(CW_DELAYTIME, cwDelayTime); - - -// cwDelayTime = getValueByKnob(10, 1000, 50, cwDelayTime, "CW Delay>", " msec"); - - active_delay(500); - menuOn = 0; -} - -void setupKeyer(){ - int tmp_key, knob; - - displayDialog("Set CW Keyer", "Press tune to Save"); - - if (!Iambic_Key) - displayText("< Hand Key >", 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); - else if (keyerControl & IAMBICB) - displayText("< Iambic A >", 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); - else - displayText("< Iambic B >", 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); - - if (!Iambic_Key) - tmp_key = 0; //hand key - else if (keyerControl & IAMBICB) - tmp_key = 2; //Iambic B - else - tmp_key = 1; - - while (!btnDown()) + return MenuReturn_e::StillActive; +} + +void activateSetting(const SettingScreen_t *const new_setting_P) +{ + activeSettingP = new_setting_P; + enterSubmenu(&setupMenuActiveSettingMenu); +} + +//Local Oscillator +void ssLocalOscInitialize(long int* start_value_out){ { - knob = enc_read(); - if (knob == 0){ - active_delay(50); - continue; - } - if (knob < 0 && tmp_key > 0) - tmp_key--; - if (knob > 0) - tmp_key++; - if (tmp_key > 2) - tmp_key = 0; - - if (tmp_key == 0) - displayText("< Hand Key >", 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); - else if (tmp_key == 1) - displayText("< Iambic A >", 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); - else if (tmp_key == 2) - displayText("< Iambic B >", 100, 100, 120, 26, DISPLAY_CYAN, DISPLAY_BLACK, DISPLAY_BLACK); + uint32_t freq = GetActiveVfoFreq(); + freq = (freq/1000L) * 1000L;//round off the current frequency the nearest kHz + setFrequency(freq); + } + *start_value_out = globalSettings.oscillatorCal; +} +void ssLocalOscValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = candidate_value_in;//No check - allow anything +} +void ssLocalOscChange(const long int new_value, char* buff_out, const size_t buff_out_size) +{ + si5351_set_calibration(new_value); + setFrequency(GetActiveVfoFreq()); + const long int u = abs(new_value); + if(new_value != u){ + strncpy_P(buff_out,(const char*)F("-"),buff_out_size); + ++buff_out; + } + formatFreq(u,buff_out,buff_out_size - strlen(buff_out)); + strncat_P(buff_out,strHz,buff_out_size - strlen(buff_out)); +} +void ssLocalOscFinalize(const long int final_value) +{ + globalSettings.oscillatorCal = final_value; + SaveSettingsToEeprom(); + si5351_set_calibration(globalSettings.oscillatorCal); + setFrequency(GetActiveVfoFreq()); +} +const char SS_LOCAL_OSC_T [] PROGMEM = "Local Oscillator"; +const char SS_LOCAL_OSC_A [] PROGMEM = "Tune so that the dial displays\na known freq, then tune here\nuntil the signal is zerobeat"; +const SettingScreen_t ssLocalOsc PROGMEM = { + SS_LOCAL_OSC_T, + SS_LOCAL_OSC_A, + 1, + 50, + ssLocalOscInitialize, + ssLocalOscValidate, + ssLocalOscChange, + ssLocalOscFinalize +}; +void runLocalOscSetting(){activateSetting(&ssLocalOsc);} + +//BFO +void ssBfoInitialize(long int* start_value_out){ + si5351bx_setfreq(0, globalSettings.usbCarrierFreq); + *start_value_out = globalSettings.usbCarrierFreq; +} +void ssBfoValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = LIMIT(candidate_value_in,11048000L,11060000L); +} +void ssBfoChange(const long int new_value, char* buff_out, const size_t buff_out_size) +{ + globalSettings.usbCarrierFreq = new_value; + setFrequency(GetActiveVfoFreq()); + si5351bx_setfreq(0, new_value); + formatFreq(new_value,buff_out,buff_out_size); + strncat_P(buff_out,strHz,buff_out_size - strlen(buff_out)); +} +void ssBfoFinalize(const long int final_value) +{ + globalSettings.usbCarrierFreq = final_value; + SaveSettingsToEeprom(); + si5351bx_setfreq(0, globalSettings.usbCarrierFreq); + setFrequency(GetActiveVfoFreq()); +} +const char SS_BFO_T [] PROGMEM = "Beat Frequency Osc (BFO)"; +const char SS_BFO_A [] PROGMEM = "Tune until the audio is\nbetween 300-3000Hz"; +const SettingScreen_t ssBfo PROGMEM = { + SS_BFO_T, + SS_BFO_A, + 1, + -50,//Negative to make dial more intuitive: turning clockwise increases the perceived audio frequency + ssBfoInitialize, + ssBfoValidate, + ssBfoChange, + ssBfoFinalize +}; +void runBfoSetting(){activateSetting(&ssBfo);} + +//CW Tone +void ssCwToneInitialize(long int* start_value_out) +{ + *start_value_out = globalSettings.cwSideToneFreq; +} +void ssCwToneValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = LIMIT(candidate_value_in,100,2000); +} +void ssCwToneChange(const long int new_value, char* buff_out, const size_t buff_out_size) +{ + globalSettings.cwSideToneFreq = new_value; + toneAC2(PIN_CW_TONE, globalSettings.cwSideToneFreq); + ltoa(globalSettings.cwSideToneFreq,buff_out,10); + strncat_P(buff_out,strHz,buff_out_size - strlen(buff_out)); +} +void ssCwToneFinalize(const long int final_value) +{ + noToneAC2(); + globalSettings.cwSideToneFreq = final_value; + SaveSettingsToEeprom(); +} +const char SS_CW_TONE_T [] PROGMEM = "Tone"; +const char SS_CW_TONE_A [] PROGMEM = "Select a frequency that\nCW mode to tune for"; +const SettingScreen_t ssTone PROGMEM = { + SS_CW_TONE_T, + SS_CW_TONE_A, + 1, + 1, + ssCwToneInitialize, + ssCwToneValidate, + ssCwToneChange, + ssCwToneFinalize +}; +void runToneSetting(){activateSetting(&ssTone);} + +//CW Switch Delay +void ssCwSwitchDelayInitialize(long int* start_value_out) +{ + *start_value_out = globalSettings.cwActiveTimeoutMs; +} +void ssCwSwitchDelayValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = LIMIT(candidate_value_in,100,1000); +} +void ssCwSwitchDelayChange(const long int new_value, char* buff_out, const size_t buff_out_size) +{ + ltoa(new_value,buff_out,10); + strncat_P(buff_out,(const char*)F("ms"),buff_out_size - strlen(buff_out)); + morseText(buff_out); + enc_read();//Consume any rotations during morse playback +} +void ssCwSwitchDelayFinalize(const long int final_value) +{ + globalSettings.cwActiveTimeoutMs = final_value; + SaveSettingsToEeprom(); +} +const char SS_CW_SWITCH_T [] PROGMEM = "Tx to Rx Delay"; +const char SS_CW_SWITCH_A [] PROGMEM = "How long to wait before\nswitching from TX to RX when\nin CW mode"; +const SettingScreen_t ssCwSwitchDelay PROGMEM = { + SS_CW_SWITCH_T, + SS_CW_SWITCH_A, + 1, + 10, + ssCwSwitchDelayInitialize, + ssCwSwitchDelayValidate, + ssCwSwitchDelayChange, + ssCwSwitchDelayFinalize +}; +void runCwSwitchDelaySetting(){activateSetting(&ssCwSwitchDelay);} + +//CW Keyer +void ssKeyerInitialize(long int* start_value_out) +{ + *start_value_out = globalSettings.keyerMode; +} +void ssKeyerValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = LIMIT(candidate_value_in,(uint8_t)KeyerMode_e::KEYER_STRAIGHT,(uint8_t)KeyerMode_e::KEYER_IAMBIC_B); +} +void ssKeyerChange(const long int new_value, char* buff_out, const size_t buff_out_size) +{ + char m; + if(KeyerMode_e::KEYER_STRAIGHT == new_value){ + strncpy_P(buff_out,(const char*)F("Hand Key"),buff_out_size); + m = 'S'; } + else if(KeyerMode_e::KEYER_IAMBIC_A == new_value){ + strncpy_P(buff_out,(const char*)F("Iambic A"),buff_out_size); + m = 'A'; + } + else{ + strncpy_P(buff_out,(const char*)F("Iambic B"),buff_out_size); + m = 'B'; + } + morseLetter(m); + enc_read();//Consume any rotations during morse playback +} +void ssKeyerFinalize(const long int final_value) +{ + globalSettings.keyerMode = (KeyerMode_e)final_value; + SaveSettingsToEeprom(); +} +const char SS_KEYER_T [] PROGMEM = "Keyer Type"; +const char SS_KEYER_A [] PROGMEM = "Select which type of keyer\nor paddle is being used"; +const SettingScreen_t ssKeyer PROGMEM = { + SS_KEYER_T, + SS_KEYER_A, + 10, + 1, + ssKeyerInitialize, + ssKeyerValidate, + ssKeyerChange, + ssKeyerFinalize +}; +void runKeyerSetting(){activateSetting(&ssKeyer);} + +//Morse menu playback +void ssMorseMenuInitialize(long int* start_value_out) +{ + *start_value_out = globalSettings.morseMenuOn; +} +void ssMorseMenuValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = LIMIT(candidate_value_in,0,1); +} +void ssMorseMenuChange(const long int new_value, char* buff_out, const size_t buff_out_size) +{ + char m; + if(new_value){ + strncpy_P(buff_out,strYes,buff_out_size); + m = 'Y'; + } + else{ + strncpy_P(buff_out,strNo,buff_out_size); + m = 'N'; + } + morseLetter(m); + enc_read();//Consume any rotations during morse playback +} +void ssMorseMenuFinalize(const long int final_value) +{ + globalSettings.morseMenuOn = final_value; + SaveSettingsToEeprom(); +} +const char SS_MORSE_MENU_T [] PROGMEM = "Menu Audio"; +const char SS_MORSE_MENU_A [] PROGMEM = "Menu selections will play\nmorse code"; +const SettingScreen_t ssMorseMenu PROGMEM = { + SS_MORSE_MENU_T, + SS_MORSE_MENU_A, + 10, + 1, + ssMorseMenuInitialize, + ssMorseMenuValidate, + ssMorseMenuChange, + ssMorseMenuFinalize +}; +void runMorseMenuSetting(){activateSetting(&ssMorseMenu);} + +//CW Speed +void ssCwSpeedInitialize(long int* start_value_out) +{ + *start_value_out = 1200L/globalSettings.cwDitDurationMs; +} +void ssCwSpeedValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = LIMIT(candidate_value_in,1,100); +} +void ssCwSpeedChange(const long int new_value, char* buff_out, const size_t /*buff_out_size*/) +{ + ltoa(new_value, buff_out, 10); + morseText(buff_out,1200L/new_value); + enc_read();//Consume any rotations during morse playback +} +void ssCwSpeedFinalize(const long int final_value) +{ + globalSettings.cwDitDurationMs = 1200L/final_value; + SaveSettingsToEeprom(); +} +const char SS_CW_SPEED_T [] PROGMEM = "Play Speed"; +const char SS_CW_SPEED_A [] PROGMEM = "Speed to play CW characters"; +const SettingScreen_t ssCwSpeed PROGMEM = { + SS_CW_SPEED_T, + SS_CW_SPEED_A, + 5, + 1, + ssCwSpeedInitialize, + ssCwSpeedValidate, + ssCwSpeedChange, + ssCwSpeedFinalize +}; +void runCwSpeedSetting(){activateSetting(&ssCwSpeed);} + +//Reset all settings +void ssResetAllInitialize(long int* start_value_out) +{ + *start_value_out = 0;//Default to NOT resetting +} +void ssResetAllValidate(const long int candidate_value_in, long int* validated_value_out) +{ + *validated_value_out = LIMIT(candidate_value_in,0,1); +} +void ssResetAllChange(const long int new_value, char* buff_out, const size_t buff_out_size) +{ + char m; + if(new_value){ + strncpy_P(buff_out,strYes,buff_out_size); + m = 'Y'; + } + else{ + strncpy_P(buff_out,strNo,buff_out_size); + m = 'N'; + } + morseLetter(m); + enc_read();//Consume any rotations during morse playback +} +void ssResetAllFinalize(const long int final_value) +{ + if(final_value){ + LoadDefaultSettings(); + SaveSettingsToEeprom(); + setup(); + } +} +const char SS_RESET_ALL_T [] PROGMEM = "Reset All"; +const char SS_RESET_ALL_A [] PROGMEM = "Resets all calibrations and\nsettings to their default\nvalues"; +const SettingScreen_t ssResetAll PROGMEM = { + SS_RESET_ALL_T, + SS_RESET_ALL_A, + 20, + 1, + ssResetAllInitialize, + ssResetAllValidate, + ssResetAllChange, + ssResetAllFinalize +}; +void runResetAllSetting(){activateSetting(&ssResetAll);} + +struct MenuItem_t { + const char* const ItemName; + void (*OnSelect)(); +}; + +void initSetupMenu(const MenuItem_t* const menu_items, + const uint16_t num_items); +MenuReturn_e runSetupMenu(const MenuItem_t* const menu_items, + const uint16_t num_items, + const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob); + +#define GENERATE_MENU_T(menu_name) \ + void initSetupMenu##menu_name(void)\ + {\ + initSetupMenu(menuItems##menu_name,sizeof(menuItems##menu_name)/sizeof(menuItems##menu_name[0]));\ + }\ + MenuReturn_e runSetupMenu##menu_name(const ButtonPress_e tuner_button,\ + const ButtonPress_e touch_button,\ + const Point touch_point,\ + const int16_t knob)\ + {\ + return runSetupMenu(menuItems##menu_name,\ + sizeof(menuItems##menu_name)/sizeof(menuItems##menu_name[0]),\ + tuner_button,\ + touch_button,\ + touch_point,\ + knob\ + );\ + }\ + Menu_t setupMenu##menu_name = {\ + initSetupMenu##menu_name,\ + runSetupMenu##menu_name,\ + nullptr\ + };\ + void run##menu_name##Menu(void)\ + {\ + enterSubmenu(&setupMenu##menu_name);\ + }\ + +const char MI_TOUCH [] PROGMEM = "Touch Screen"; +void setupTouchSetting(); + +const char MT_CAL [] PROGMEM = "Calibrations"; +const MenuItem_t menuItemsCalibration [] PROGMEM { + {MT_CAL,nullptr},//Title + {SS_LOCAL_OSC_T,runLocalOscSetting}, + {SS_BFO_T,runBfoSetting}, + {MI_TOUCH,setupTouchSetting}, +}; +GENERATE_MENU_T(Calibration); + +void setupTouchSetting(){ + setupTouch(); + initSetupMenuCalibration(); +} - active_delay(500); - if (tmp_key == 0) - Iambic_Key = false; - else if (tmp_key == 1){ - Iambic_Key = true; - keyerControl &= ~IAMBICB; +const char MT_CW [] PROGMEM = "CW Setup"; +const MenuItem_t menuItemsCw [] PROGMEM { + {MT_CW,nullptr},//Title + {SS_CW_TONE_T,runToneSetting}, + {SS_CW_SWITCH_T,runCwSwitchDelaySetting}, + {SS_KEYER_T,runKeyerSetting}, + {SS_MORSE_MENU_T,runMorseMenuSetting}, + {SS_CW_SPEED_T,runCwSpeedSetting}, +}; +GENERATE_MENU_T(Cw); + +const char MT_SETTINGS [] PROGMEM = "Settings"; +const MenuItem_t menuItemsSetupRoot [] PROGMEM { + {MT_SETTINGS,nullptr},//Title + {MT_CAL,runCalibrationMenu}, + {MT_CW,runCwMenu}, + {SS_RESET_ALL_T,runResetAllSetting}, +}; +GENERATE_MENU_T(SetupRoot); +Menu_t *const setupMenu = &setupMenuSetupRoot; + +const char MI_EXIT [] PROGMEM = "Exit"; +const MenuItem_t exitMenu PROGMEM = {MI_EXIT,nullptr}; + +void drawMenu(const MenuItem_t* const items, const uint16_t num_items) +{ + displayClear(COLOR_BACKGROUND); + MenuItem_t mi = {"",nullptr}; + memcpy_P(&mi,&items[0],sizeof(mi)); + strncpy_P(b,mi.ItemName,sizeof(b)); + displayText(b, LAYOUT_TITLE_X, LAYOUT_TITLE_Y, LAYOUT_TITLE_WIDTH, LAYOUT_TITLE_HEIGHT, COLOR_TEXT, COLOR_TITLE_BACKGROUND, COLOR_ACTIVE_BORDER); + for(unsigned int i = 1; i < num_items; ++i){ + memcpy_P(&mi,&items[i],sizeof(mi)); + strncpy_P(b,mi.ItemName,sizeof(b)); + displayText(b, LAYOUT_ITEM_X, LAYOUT_ITEM_Y + (i-1)*LAYOUT_ITEM_PITCH_Y, LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_INACTIVE_BORDER, TextJustification_e::Left); } - else if (tmp_key == 2){ - Iambic_Key = true; - keyerControl |= IAMBICB; + memcpy_P(&mi,&exitMenu,sizeof(mi)); + strncpy_P(b,mi.ItemName,sizeof(b)); + displayText(b, LAYOUT_ITEM_X, LAYOUT_ITEM_Y + (num_items-1)*LAYOUT_ITEM_PITCH_Y, LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_TEXT, COLOR_BACKGROUND, COLOR_INACTIVE_BORDER, TextJustification_e::Left); +} + +void movePuck(unsigned int old_index, + unsigned int new_index) +{ + //Don't update if we're already on the right selection + if(old_index == new_index){ + return; } - - EEPROM.put(CW_KEY_TYPE, tmp_key); - - menuOn = 0; -} - -void drawSetupMenu(){ - displayClear(DISPLAY_BLACK); - - displayText("Setup", 10, 10, 300, 35, DISPLAY_WHITE, DISPLAY_NAVY, DISPLAY_WHITE); - displayRect(10,10,300,220, DISPLAY_WHITE); - - displayRawText("Set Freq...", 30, 50, DISPLAY_WHITE, DISPLAY_NAVY); - displayRawText("Set BFO...", 30, 80, DISPLAY_WHITE, DISPLAY_NAVY); - displayRawText("CW Delay...", 30, 110, DISPLAY_WHITE, DISPLAY_NAVY); - displayRawText("CW Keyer...", 30, 140, DISPLAY_WHITE, DISPLAY_NAVY); - displayRawText("Touch Screen...", 30, 170, DISPLAY_WHITE, DISPLAY_NAVY); - displayRawText("Exit", 30, 200, DISPLAY_WHITE, DISPLAY_NAVY); -} - -static int prevPuck = -1; -void movePuck(int i){ - if (prevPuck >= 0) - displayRect(15, 49 + (prevPuck * 30), 290, 25, DISPLAY_NAVY); - displayRect(15, 49 + (i * 30), 290, 25, DISPLAY_WHITE); - prevPuck = i; - -} - -void doSetup2(){ - int select=0, i,btnState; - - drawSetupMenu(); - movePuck(select); - - //wait for the button to be raised up - while(btnDown()) - active_delay(50); - active_delay(50); //debounce - - menuOn = 2; - - while (menuOn){ - i = enc_read(); - - if (i > 0){ - if (select + i < 60) - select += i; - movePuck(select/10); - } - if (i < 0 && select - i >= 0){ - select += i; //caught ya, i is already -ve here, so you add it - movePuck(select/10); - } + else if(((unsigned int)-1) != old_index){ + //Clear old + displayRect(LAYOUT_ITEM_X, LAYOUT_ITEM_Y + (old_index*LAYOUT_ITEM_PITCH_Y), LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_INACTIVE_BORDER); + } + //Draw new + displayRect(LAYOUT_ITEM_X, LAYOUT_ITEM_Y + (new_index*LAYOUT_ITEM_PITCH_Y), LAYOUT_ITEM_WIDTH, LAYOUT_ITEM_HEIGHT, COLOR_ACTIVE_BORDER); +} - if (!btnDown()){ - active_delay(50); - continue; - } +int16_t setupMenuSelector = 0; + +void initSetupMenu(const MenuItem_t* const menu_items, + const uint16_t num_items) +{ + drawMenu(menu_items,num_items); + setupMenuSelector = 0; + movePuck(-1,0);//Force draw of puck +} - //wait for the touch to lift off and debounce - while(btnDown()){ - active_delay(50); +MenuReturn_e runSetupMenu(const MenuItem_t* const menu_items, + const uint16_t num_items, + const ButtonPress_e tuner_button, + const ButtonPress_e touch_button, + const Point touch_point, + const int16_t knob) +{ + const int16_t cur_index = setupMenuSelector/MENU_KNOB_COUNTS_PER_ITEM; + const int16_t exit_index = num_items - 1; + + if(ButtonPress_e::NotPressed != tuner_button){ + //Don't care what kind of press + if(exit_index <= cur_index){ + return MenuReturn_e::ExitedRedraw; } - active_delay(300); - - if (select < 10) - setupFreq(); - else if (select < 20 ) - setupBFO(); - else if (select < 30 ) - setupCwDelay(); - else if (select < 40) - setupKeyer(); - else if (select < 50) - setupTouch(); - else - break; //exit setup was chosen - //setupExit(); - //redraw - drawSetupMenu(); + + MenuItem_t mi = {"",nullptr}; + memcpy_P(&mi,&menu_items[cur_index+1],sizeof(mi));//The 0th element in the array is the title, so offset by 1 + mi.OnSelect(); } - //debounce the button - while(btnDown()) - active_delay(50); - active_delay(50); + (void)touch_button;(void)touch_point;//TODO: handle touch input? + + if(0 != knob){ + setupMenuSelector = LIMIT(setupMenuSelector + knob,0,(int16_t)(num_items*MENU_KNOB_COUNTS_PER_ITEM - 1)); + const int16_t new_index = setupMenuSelector/MENU_KNOB_COUNTS_PER_ITEM; + if(cur_index != new_index){ + movePuck(cur_index,new_index); + if(globalSettings.morseMenuOn){//Only spend cycles copying menu item into RAM if we actually need to + if(exit_index <= cur_index){ + strncpy_P(b,MI_EXIT,sizeof(b)); + } + else{ + MenuItem_t mi = {"",nullptr}; + memcpy_P(&mi,&menu_items[cur_index+1],sizeof(mi));//The 0th element in the array is the title, so offset by 1 + strncpy_P(b,mi.ItemName,sizeof(b)); + } + morseText(b); + } + } + } - checkCAT(); - guiUpdate(); + return MenuReturn_e::StillActive; } diff --git a/setup.h b/setup.h new file mode 100644 index 0000000..16e6c07 --- /dev/null +++ b/setup.h @@ -0,0 +1,9 @@ +#pragma once + +#include "menu.h" + +extern Menu_t* const setupMenu; + +void setupTouch(); +void runLocalOscSetting(); +void runBfoSetting(); \ No newline at end of file diff --git a/ubitx_si5351.cpp b/si5351.cpp similarity index 92% rename from ubitx_si5351.cpp rename to si5351.cpp index 544648a..d42687d 100644 --- a/ubitx_si5351.cpp +++ b/si5351.cpp @@ -1,6 +1,7 @@ #include #include -#include "ubitx.h" +#include "settings.h" +#include "si5351.h" // ************* SI5315 routines - tks Jerry Gaffke, KE7ER *********************** @@ -48,7 +49,6 @@ uint32_t si5351bx_vcoa = (SI5351BX_XTAL*SI5351BX_MSA); // 25mhzXtal calibrate uint8_t si5351bx_rdiv = 0; // 0-7, CLK pin sees fout/(2**rdiv) uint8_t si5351bx_drive[3] = {3, 3, 3}; // 0=2ma 1=4ma 2=6ma 3=8ma for CLK 0,1,2 uint8_t si5351bx_clken = 0xFF; // Private, all CLK output drivers off -int32_t calibration = 0; void i2cWrite(uint8_t reg, uint8_t val) { // write reg via i2c Wire.beginTransmission(SI5351BX_ADDR); @@ -66,12 +66,11 @@ void i2cWriten(uint8_t reg, uint8_t *vals, uint8_t vcnt) { // write array void si5351bx_init() { // Call once at power-up, start PLLA - uint8_t reg; uint32_t msxp1; Wire.begin(); i2cWrite(149, 0); // SpreadSpectrum off i2cWrite(3, si5351bx_clken); // Disable all CLK output drivers i2cWrite(183, SI5351BX_XTALPF << 6); // Set 25mhz crystal load capacitance - msxp1 = 128 * SI5351BX_MSA - 512; // and msxp2=0, msxp3=1, not fractional + uint32_t msxp1 = 128 * SI5351BX_MSA - 512; // and msxp2=0, msxp3=1, not fractional uint8_t vals[8] = {0, 1, BB2(msxp1), BB1(msxp1), BB0(msxp1), 0, 0, 0}; i2cWriten(26, vals, 8); // Write to 8 PLLA msynth regs i2cWrite(177, 0x20); // Reset PLLA (0x80 resets PLLB) @@ -86,7 +85,7 @@ void si5351bx_init() { // Call once at power-up, start PLLA void si5351bx_setfreq(uint8_t clknum, uint32_t fout) { // Set a CLK to fout Hz uint32_t msa, msb, msc, msxp1, msxp2, msxp3p2top; - if ((fout < 500000) || (fout > 109000000)) // If clock freq out of range + if ((fout < SI5351_MIN_FREQUENCY_HZ) || (fout > SI5351_MAX_FREQUENCY_HZ)) // If clock freq out of range si5351bx_clken |= 1 << clknum; // shut down the clock else { msa = si5351bx_vcoa / fout; // Integer part of vco/fout @@ -115,14 +114,13 @@ void si5351bx_setfreq(uint8_t clknum, uint32_t fout) { // Set a CLK to fout Hz void si5351_set_calibration(int32_t cal){ si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + cal; // apply the calibration correction factor - si5351bx_setfreq(0, usbCarrier); + si5351bx_setfreq(0, globalSettings.usbCarrierFreq); } void initOscillators(){ //initialize the SI5351 si5351bx_init(); - si5351bx_vcoa = (SI5351BX_XTAL * SI5351BX_MSA) + calibration; // apply the calibration correction factor - si5351bx_setfreq(0, usbCarrier); + si5351_set_calibration(globalSettings.oscillatorCal); } diff --git a/si5351.h b/si5351.h new file mode 100644 index 0000000..f2afc85 --- /dev/null +++ b/si5351.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +static const uint32_t SI5351_MIN_FREQUENCY_HZ = 500000; +static const uint32_t SI5351_MAX_FREQUENCY_HZ = 109000000; + +void initOscillators(); +void si5351bx_setfreq(uint8_t clknum, uint32_t fout); +void si5351_set_calibration(int32_t cal); //calibration is a small value that is nudged to make up for the inaccuracies of the reference 25 MHz crystal frequency diff --git a/toneAC2/examples/toneAC2_demo/toneAC2_demo.pde b/toneAC2/examples/toneAC2_demo/toneAC2_demo.pde new file mode 100644 index 0000000..fa1b6e6 --- /dev/null +++ b/toneAC2/examples/toneAC2_demo/toneAC2_demo.pde @@ -0,0 +1,27 @@ +// --------------------------------------------------------------------------- +// Be sure to include an in-line 100 ohm resistor on one pin as you normally do when connecting a piezo or speaker. +// --------------------------------------------------------------------------- + +#include + +// Melody liberated from the toneMelody Arduino example sketch by Tom Igoe. +int melody[] = { 262, 196, 196, 220, 196, 0, 247, 262 }; +int noteDurations[] = { 4, 8, 8, 4, 4, 4, 4, 4 }; + +void setup() {} // Nothing to setup, just start playing! + +void loop() { + for (unsigned long freq = 125; freq <= 15000; freq += 10) { + toneAC2(2, 3, freq, 1); // Play the frequency (125 Hz to 15 kHz sweep in 10 Hz steps) for 1ms. + } + + delay(1000); // Wait a second. + + for (int thisNote = 0; thisNote < 8; thisNote++) { + int noteDuration = 1000/noteDurations[thisNote]; + toneAC2(2, 3, melody[thisNote], noteDuration, true); // Play thisNote at full volume for noteDuration in the background. + delay(noteDuration * 4 / 3); // Wait while the tone plays in the background, plus another 33% delay between notes. + } + + while(1); // Stop (so it doesn't repeat forever driving you crazy--you're welcome). +} diff --git a/toneAC2/keywords.txt b/toneAC2/keywords.txt new file mode 100644 index 0000000..9930b76 --- /dev/null +++ b/toneAC2/keywords.txt @@ -0,0 +1,18 @@ +################################### +# Syntax Coloring Map For toneAC2 +################################### + +################################### +# Datatypes (KEYWORD1) +################################### + +################################### +# Methods and Functions (KEYWORD2) +################################### + +toneAC2 KEYWORD2 +noToneAC2 KEYWORD2 + +################################### +# Constants (LITERAL1) +################################### diff --git a/toneAC2/toneAC2.cpp b/toneAC2/toneAC2.cpp new file mode 100644 index 0000000..39c5732 --- /dev/null +++ b/toneAC2/toneAC2.cpp @@ -0,0 +1,56 @@ +// --------------------------------------------------------------------------- +// Created by Tim Eckel - teckel@leethost.com +// Copyright 2015 License: GNU GPL v3 http://www.gnu.org/licenses/gpl-3.0.html +// +// See "toneAC2.h" for purpose, syntax, version history, links, and more. +// --------------------------------------------------------------------------- + +#include "toneAC2.h" + +unsigned long _tAC2_time; // Used to track end note with timer when playing note in the background. +volatile uint8_t *_pinMode1; // Pin modes. +uint8_t _pinMask1 = 0; // Bitmask for pins. +volatile uint8_t *_pinOutput1; // Output port registers for each pin. +int _tAC2_prescale[] = { 2, 16, 64, 128, 256, 512, 2048 }; // Prescaler. + +void toneAC2(uint8_t pin1, unsigned int frequency, unsigned long length, uint8_t background) { + long top; + uint8_t prescaler; + + for (prescaler = 1; prescaler < 8; prescaler++) { // Find the appropriate prescaler + top = F_CPU / (long) frequency / (long) _tAC2_prescale[prescaler - 1] - 1; // Calculate the top. + if (top < 256) break; // Fits, break out of for loop. + } + if (top > 255) { noToneAC2(); return; } // Frequency is out of range, turn off sound and return. + + if (length > 0) _tAC2_time = millis() + length - 1; else _tAC2_time = 0xFFFFFFFF; // Set when the note should end, or play "forever". + + if (_pinMask1 == 0) { // This gets the port registers and bitmaps for the two pins and sets the pins to output mode. + _pinMask1 = digitalPinToBitMask(pin1); // Get the port register bitmask for pin 1. + _pinOutput1 = portOutputRegister(digitalPinToPort(pin1)); // Get the output port register for pin 1. + _pinMode1 = (uint8_t *) portModeRegister(digitalPinToPort(pin1)); // Get the port mode register for pin 1. + *_pinMode1 |= _pinMask1; // Set pin 1 to Output mode. + } + + OCR2A = top; // Set the top. + if (TCNT2 > top) TCNT2 = top; // Counter over the top, put within range. + TCCR2B = _BV(WGM22) | prescaler; // Set Fast PWM and prescaler. + TCCR2A = _BV(WGM20) | _BV(WGM21); // Fast PWM and normal port operation, OC2A/OC2B disconnected. + TIMSK2 &= ~_BV(OCIE2A); // Stop timer 2 interrupt while we set the pin states. + TIMSK2 |= _BV(OCIE2A); // Activate the timer interrupt. + + if (length > 0 && !background) { delay(length); noToneAC2(); } // Just a simple delay, doesn't return control till finished. +} + +void noToneAC2() { + TIMSK2 &= ~_BV(OCIE2A); // Remove the timer interrupt. + TCCR2B = _BV(CS22); // Default clock prescaler of 64. + TCCR2A = _BV(WGM20); // Set to defaults so PWM can work like normal (PWM, phase corrected, 8bit). + *_pinMode1 &= ~_pinMask1; // Set pin 1 to INPUT. + _pinMask1 = 0; // Flag so we know note is no longer playing. +} + +ISR(TIMER2_COMPA_vect) { // Timer interrupt vector. + if (millis() > _tAC2_time) noToneAC2(); // Check to see if it's time for the note to end. + *_pinOutput1 ^= _pinMask1; // Toggle the pin 1 state. +} \ No newline at end of file diff --git a/toneAC2/toneAC2.h b/toneAC2/toneAC2.h new file mode 100644 index 0000000..f186d48 --- /dev/null +++ b/toneAC2/toneAC2.h @@ -0,0 +1,73 @@ +// --------------------------------------------------------------------------- +// toneAC2 Library - v1.1 - 09/15/2015 +// +// AUTHOR/LICENSE: +// Created by Tim Eckel - teckel@leethost.com +// Copyright 2015 License: GNU GPL v3 http://www.gnu.org/licenses/gpl-3.0.html +// +// LINKS: +// Project home: https://bitbucket.org/teckel12/arduino-toneac/wiki/Home +// Blog: http://forum.arduino.cc/index.php?topic=142097.0 +// +// DISCLAIMER: +// This software is furnished "as is", without technical support, and with no +// warranty, express or implied, as to its usefulness for any purpose. +// +// PURPOSE: +// Replacement to the standard tone library with the advantage of nearly twice +// the volume, 800 bytes smaller compiled code size, and less stress on the +// speaker. This alternate version uses timer 2 and allows for flexible pin +// assignment. The primary version (toneAC) allows for higher frequencies, +// higher quality, and even smaller code size. However, toneAC is fixed to +// using the PWM timer 1 pins unlike toneAC2 which can use any two pins. Both +// exclusively use port registers for the fast and smallest code possible. +// +// USAGE: +// Connection is very similar to a piezo or standard speaker. Except, instead +// of connecting one speaker wire to ground you connect both speaker wires to +// Arduino pins. Unlike toneAC, with toneAC2 you can connect to any two pins. +// Just as usual when connecting a speaker, make sure you add an in-line 100 +// ohm resistor between one of the pins and the speaker wire. +// +// SYNTAX: +// toneAC2( pin1, pin2, frequency [, length [, background ]] ) - Play a note. +// Parameters: +// * pin1 - Pin to attach one of the speaker wires. +// * pin2 - Pin to attach the other speaker wire. +// * frequency - Play the specified frequency indefinitely, turn off with noToneAC2(). +// * length - [optional] Set the length to play in milliseconds. (default: 0 [forever], range: 0 to 2^32-1) +// * background - [optional] Play note in background or pause till finished? (default: false, values: true/false) +// noToneAC2() - Stop playing. +// +// HISTORY: +// 09/15/2015 v1.1 - Fix a potential race condition with _tAC2_time. Moved +// development to Bitbucket. +// +// 01/27/2013 v1.0 - Initial release. +// +// --------------------------------------------------------------------------- + +#ifndef toneAC2_h + #define toneAC2_h + + #if defined(ARDUINO) && ARDUINO >= 100 + #include + #else + #include + #endif + + // This doesn't currently work. Would require more work than simply doing this. + #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega128__) + #define TCCR2A TCCR2 + #define TCCR2B TCCR2 + #define TIMSK2 TIMSK + #define COM2A1 COM21 + #define COM2A0 COM20 + #define OCIE2A OCIE2 + #define OCR2A OCR2 + #define TIMER2_COMPA_vect TIMER2_COMP_vect + #endif + + void toneAC2(uint8_t pin1, unsigned int frequency = 0, unsigned long length = 0, uint8_t background = false); + void noToneAC2(); +#endif \ No newline at end of file diff --git a/toneAC2Proxy.cpp b/toneAC2Proxy.cpp new file mode 100644 index 0000000..5e118f1 --- /dev/null +++ b/toneAC2Proxy.cpp @@ -0,0 +1 @@ +#include "toneAC2/toneAC2.cpp" diff --git a/touch.cpp b/touch.cpp new file mode 100644 index 0000000..56c84d4 --- /dev/null +++ b/touch.cpp @@ -0,0 +1,139 @@ +#include "touch.h" + +#include + +#include "pin_definitions.h" +#include "settings.h" + +constexpr int16_t Z_THRESHOLD = 400; +constexpr uint8_t MSEC_THRESHOLD = 3;//Max sample rate is 125kHz, but we'll limit ourselves conservatively + +constexpr uint8_t START_COMMAND = 1 << 7; +constexpr uint8_t CHANNEL_Y = 1 << 4; +constexpr uint8_t CHANNEL_Z1 = 3 << 4; +constexpr uint8_t CHANNEL_Z2 = 4 << 4; +constexpr uint8_t CHANNEL_X = 5 << 4; +constexpr uint8_t CHANNEL_TEMPERATURE = 7 << 4; +constexpr uint8_t USE_8_INSTEAD_OF_12_BIT = 1 << 3; +constexpr uint8_t USE_SINGLE_ENDED_MEASUREMENT = 1 << 2; +constexpr uint8_t POWER_OFF = 0 << 0; +constexpr uint8_t POWER_ADC = 1 << 0; +constexpr uint8_t POWER_REF = 2 << 0; +constexpr uint8_t POWER_ADC_REF = 3 << 0; + +constexpr uint16_t MEASURE_X = START_COMMAND | POWER_ADC | CHANNEL_Y;//X and Y channel labelling flip due to screen orientation +constexpr uint16_t MEASURE_Y = START_COMMAND | POWER_ADC | CHANNEL_X;//X and Y channel labelling flip due to screen orientation +constexpr uint16_t MEASURE_Z1 = START_COMMAND | POWER_ADC | CHANNEL_Z1; +constexpr uint16_t MEASURE_Z2 = START_COMMAND | POWER_ADC | CHANNEL_Z2; + +constexpr uint8_t RAW_READ_TO_12BIT_VALUE_SHIFT = 3;//16 bits read, zero-padded, but the MSB of the 16 is where the "BUSY" signal is asserted, so only need to shift by 3 instead of 4 + + +uint32_t msraw=0x80000000; +int16_t xraw=0, yraw=0, zraw=0; +constexpr uint8_t rotation = 1; + +SPISettings spiSettingsTouch(2000000,MSBFIRST,SPI_MODE0); + +int16_t touch_besttwoavg( int16_t x , int16_t y , int16_t z ) { + int16_t da, db, dc; + int16_t reta = 0; + if ( x > y ) da = x - y; else da = y - x; + if ( x > z ) db = x - z; else db = z - x; + if ( z > y ) dc = z - y; else dc = y - z; + + if ( da <= db && da <= dc ) reta = (x + y) >> 1; + else if ( db <= da && db <= dc ) reta = (x + z) >> 1; + else reta = (y + z) >> 1; // else if ( dc <= da && dc <= db ) reta = (x + y) >> 1; + + return (reta); +} + +uint16_t touchReadChannel(uint8_t channel_command){ + //We assume that SPI.beginTransaction has already been called, and CS is LOW + SPI.transfer(channel_command);//Throw away any bytes here + const uint16_t tmpH = SPI.transfer(0) & 0x7F;//Leading 0 (during "busy" signal), followed by bits 11-5 + const uint16_t tmpL = SPI.transfer(0);//Bits 4-0, followed by 0s + return tmpH << 5 | tmpL >> 3; +} + +void touch_update(){ + uint32_t now = millis(); + if (now - msraw < MSEC_THRESHOLD){ + return; + } + + SPI.beginTransaction(spiSettingsTouch); + digitalWrite(PIN_TOUCH_CS, LOW); + + int16_t z1 = touchReadChannel(MEASURE_Z1);//~0 when not pressed, increases with pressure + int32_t z = z1; + int16_t z2 = touchReadChannel(MEASURE_Z2);//~4095 when not pressed, decreases with pressure + z += (4095 - z2); + //Serial.print(F("z1:"));Serial.print(z1);Serial.print(F(" z2:"));Serial.print(z2);Serial.print(F(" z:"));Serial.println(z); + + zraw = z; + if (zraw < Z_THRESHOLD) {//Don't bother reading x/y if we're not being touched + digitalWrite(PIN_TOUCH_CS, HIGH); + SPI.endTransaction(); + return; + } + + // make 3 x-y measurements + int16_t data[6]; + data[0] = touchReadChannel(MEASURE_X); + data[1] = touchReadChannel(MEASURE_Y); + data[2] = touchReadChannel(MEASURE_X); + data[3] = touchReadChannel(MEASURE_Y); + data[4] = touchReadChannel(MEASURE_X); + data[5] = touchReadChannel(MEASURE_Y & ~POWER_ADC_REF);//Turn off sensor + + digitalWrite(PIN_TOUCH_CS, HIGH); + SPI.endTransaction(); + + int16_t x = touch_besttwoavg( data[0], data[2], data[4] ); + int16_t y = touch_besttwoavg( data[1], data[3], data[5] ); + + msraw = now; // good read completed, set wait + switch (rotation) { + case 0: + xraw = 4095 - y; + yraw = x; + break; + case 1: + xraw = x; + yraw = y; + break; + case 2: + xraw = y; + yraw = 4095 - x; + break; + default: // 3 + xraw = 4095 - x; + yraw = 4095 - y; + } +} + +void initTouch(){ + pinMode(PIN_TOUCH_CS, OUTPUT); + digitalWrite(PIN_TOUCH_CS, HIGH); +} + +bool readTouch(Point *const touch_point_out){ + touch_update(); + //Serial.print(F("readTouch found zraw of "));Serial.println(zraw); + if (zraw >= Z_THRESHOLD) { + touch_point_out->x = xraw; + touch_point_out->y = yraw; + //Serial.print(ts_point.x); Serial.print(",");Serial.println(ts_point.y); + return true; + } + return false; +} + +void scaleTouch(Point *const touch_point_in_out){ + touch_point_in_out->x = ((long)(touch_point_in_out->x - globalSettings.touchOffsetX) * 10L)/ (long)globalSettings.touchSlopeX; + touch_point_in_out->y = ((long)(touch_point_in_out->y - globalSettings.touchOffsetY) * 10L)/ (long)globalSettings.touchSlopeY; + + //Serial.print(p->x); Serial.print(",");Serial.println(p->y); +} \ No newline at end of file diff --git a/touch.h b/touch.h new file mode 100644 index 0000000..94ef80b --- /dev/null +++ b/touch.h @@ -0,0 +1,11 @@ +#pragma once + +#include "point.h" + +void initTouch(); + +//Returns true if touched, false otherwise +bool readTouch(Point *const touch_point_out); + +//Applies the touch calibration the point passed in +void scaleTouch(Point *const touch_point_in_out); \ No newline at end of file diff --git a/tuner.cpp b/tuner.cpp new file mode 100644 index 0000000..9e99ea6 --- /dev/null +++ b/tuner.cpp @@ -0,0 +1,217 @@ +#include "tuner.h" + +#include + +#include "nano_gui.h" +#include "pin_definitions.h" +#include "si5351.h" + +static const uint32_t THRESHOLD_USB_LSB = 10000000L; + +void saveVFOs() +{ + SaveSettingsToEeprom(); +} + + +void switchVFO(Vfo_e new_vfo){ + ritDisable();//If we are in RIT mode, we need to disable it before setting the active VFO so that the correct VFO gets it's frequency restored + + globalSettings.activeVfo = new_vfo; + setFrequency(GetActiveVfoFreq()); + saveVFOs(); +} + +/** + * Select the properly tx harmonic filters + * The four harmonic filters use only three relays + * the four LPFs cover 30-21 Mhz, 18 - 14 Mhz, 7-10 MHz and 3.5 to 5 Mhz + * Briefly, it works like this, + * - When KT1 is OFF, the 'off' position routes the PA output through the 30 MHz LPF + * - When KT1 is ON, it routes the PA output to KT2. Which is why you will see that + * the KT1 is on for the three other cases. + * - When the KT1 is ON and KT2 is off, the off position of KT2 routes the PA output + * to 18 MHz LPF (That also works for 14 Mhz) + * - When KT1 is On, KT2 is On, it routes the PA output to KT3 + * - KT3, when switched on selects the 7-10 Mhz filter + * - KT3 when switched off selects the 3.5-5 Mhz filter + * See the circuit to understand this + */ + +void setTXFilters(unsigned long freq){ + + if (freq > 21000000L){ // the default filter is with 35 MHz cut-off + digitalWrite(PIN_TX_LPF_A, 0); + digitalWrite(PIN_TX_LPF_B, 0); + digitalWrite(PIN_TX_LPF_C, 0); + } + else if (freq >= 14000000L){ //thrown the KT1 relay on, the 30 MHz LPF is bypassed and the 14-18 MHz LPF is allowd to go through + digitalWrite(PIN_TX_LPF_A, 1); + digitalWrite(PIN_TX_LPF_B, 0); + digitalWrite(PIN_TX_LPF_C, 0); + } + else if (freq > 7000000L){ + digitalWrite(PIN_TX_LPF_A, 0); + digitalWrite(PIN_TX_LPF_B, 1); + digitalWrite(PIN_TX_LPF_C, 0); + } + else { + digitalWrite(PIN_TX_LPF_A, 0); + digitalWrite(PIN_TX_LPF_B, 0); + digitalWrite(PIN_TX_LPF_C, 1); + } +} + +/** + * This is the most frequently called function that configures the + * radio to a particular frequeny, sideband and sets up the transmit filters + * + * The transmit filter relays are powered up only during the tx so they dont + * draw any current during rx. + * + * The carrier oscillator of the detector/modulator is permanently fixed at + * uppper sideband. The sideband selection is done by placing the second oscillator + * either 12 Mhz below or above the 45 Mhz signal thereby inverting the sidebands + * through mixing of the second local oscillator. + */ + +void setFrequency(const unsigned long freq, + const bool transmit){ + static const unsigned long FIRST_IF = 45005000UL; + + setTXFilters(freq); + + //Nominal values for the oscillators + uint32_t local_osc_freq = FIRST_IF + freq; + uint32_t ssb_osc_freq = FIRST_IF;//will be changed depending on sideband + uint32_t bfo_osc_freq = globalSettings.usbCarrierFreq; + + if(TuningMode_e::TUNE_CW == globalSettings.tuningMode){ + if(transmit){ + //We don't do any mixing or converting when transmitting + local_osc_freq = freq; + ssb_osc_freq = 0; + bfo_osc_freq = 0; + } + else{ + //We offset when receiving CW so that it's audible + if(VfoMode_e::VFO_MODE_USB == GetActiveVfoMode()){ + local_osc_freq -= globalSettings.cwSideToneFreq; + ssb_osc_freq += globalSettings.usbCarrierFreq; + } + else{ + local_osc_freq += globalSettings.cwSideToneFreq; + ssb_osc_freq -= globalSettings.usbCarrierFreq; + } + } + } + else{//SSB mode + if(VfoMode_e::VFO_MODE_USB == GetActiveVfoMode()){ + ssb_osc_freq += globalSettings.usbCarrierFreq; + } + else{ + ssb_osc_freq -= globalSettings.usbCarrierFreq; + } + } + + si5351bx_setfreq(2, local_osc_freq); + si5351bx_setfreq(1, ssb_osc_freq); + si5351bx_setfreq(0, bfo_osc_freq); + + SetActiveVfoFreq(freq); +} + +/** + * startTx is called by the PTT, cw keyer and CAT protocol to + * put the uBitx in tx mode. It takes care of rit settings, sideband settings + * Note: In cw mode, doesnt key the radio, only puts it in tx mode + * CW offest is calculated as lower than the operating frequency when in LSB mode, and vice versa in USB mode + */ + +void startTx(TuningMode_e tx_mode){ + globalSettings.tuningMode = tx_mode; + + if (globalSettings.ritOn){ + //save the current as the rx frequency + uint32_t rit_tx_freq = globalSettings.ritFrequency; + globalSettings.ritFrequency = GetActiveVfoFreq(); + setFrequency(rit_tx_freq,true); + } + else{ + if(globalSettings.splitOn){ + if(Vfo_e::VFO_B == globalSettings.activeVfo){ + globalSettings.activeVfo = Vfo_e::VFO_A; + } + else{ + globalSettings.activeVfo = Vfo_e::VFO_B; + } + } + setFrequency(GetActiveVfoFreq(),true); + } + + digitalWrite(PIN_TX_RXn, 1);//turn on the tx + globalSettings.txActive = true; + drawTx(); +} + +void stopTx(){ + digitalWrite(PIN_TX_RXn, 0);//turn off the tx + globalSettings.txActive = false; + + if(globalSettings.ritOn){ + uint32_t rit_rx_freq = globalSettings.ritFrequency; + globalSettings.ritFrequency = GetActiveVfoFreq(); + setFrequency(rit_rx_freq); + } + else{ + if(globalSettings.splitOn){ + if(Vfo_e::VFO_B == globalSettings.activeVfo){ + globalSettings.activeVfo = Vfo_e::VFO_A; + } + else{ + globalSettings.activeVfo = Vfo_e::VFO_B; + } + } + setFrequency(GetActiveVfoFreq()); + } + drawTx(); +} + +/** + * ritEnable is called with a frequency parameter that determines + * what the tx frequency will be + */ +void ritEnable(unsigned long freq){ + globalSettings.ritOn = true; + //save the non-rit frequency back into the VFO memory + //as RIT is a temporary shift, this is not saved to EEPROM + globalSettings.ritFrequency = freq; +} + +// this is called by the RIT menu routine +void ritDisable(){ + if(globalSettings.ritOn){ + globalSettings.ritOn = false; + setFrequency(globalSettings.ritFrequency); + } +} + +bool autoSelectSidebandChanged(const uint32_t old_frequency) +{ + const uint32_t new_freq = GetActiveVfoFreq(); + //Transition from below to above the traditional threshold for USB + if(old_frequency < THRESHOLD_USB_LSB && new_freq >= THRESHOLD_USB_LSB){ + SetActiveVfoMode(VfoMode_e::VFO_MODE_USB); + setFrequency(new_freq);//Refresh tuning to activate the new sideband mode + return true; + } + + //Transition from above to below the traditional threshold for USB + if(old_frequency >= THRESHOLD_USB_LSB && new_freq < THRESHOLD_USB_LSB){ + SetActiveVfoMode(VfoMode_e::VFO_MODE_LSB); + setFrequency(new_freq);//Refresh tuning to activate the new sideband mode + return true; + } + + return false; +} diff --git a/tuner.h b/tuner.h new file mode 100644 index 0000000..cce5e97 --- /dev/null +++ b/tuner.h @@ -0,0 +1,15 @@ +#pragma once + +#include "settings.h" + +void saveVFOs(); +void setFrequency(const unsigned long freq, const bool transmit = false); +void startTx(TuningMode_e tx_mode); +void stopTx(); +void ritEnable(unsigned long f); +void ritDisable(); +void checkCAT(); +void cwKeyer(void); +void switchVFO(Vfo_e vfoSelect); + +bool autoSelectSidebandChanged(const uint32_t old_frequency); //if the current frequency defaults to a different sideband mode, updates to that sideband mode and returns true. Else, returns false diff --git a/ubitx.h b/ubitx.h deleted file mode 100644 index 4304650..0000000 --- a/ubitx.h +++ /dev/null @@ -1,225 +0,0 @@ - -/* The ubitx is powered by an arduino nano. The pin assignment is as folows - * - */ - -#define ENC_A (A0) // Tuning encoder interface -#define ENC_B (A1) // Tuning encoder interface -#define FBUTTON (A2) // Tuning encoder interface -#define PTT (A3) // Sense it for ssb and as a straight key for cw operation -#define ANALOG_KEYER (A6) // This is used as keyer. The analog port has 4.7K pull up resistor. Details are in the circuit description on www.hfsignals.com -#define ANALOG_SPARE (A7) // Not used yet - -#define TX_RX (7) // Pin from the Nano to the radio to switch to TX (HIGH) and RX(LOW) -#define CW_TONE (6) // Generates a square wave sidetone while sending the CW. -#define TX_LPF_A (5) // The 30 MHz LPF is permanently connected in the output of the PA... -#define TX_LPF_B (4) // ...Alternatively, either 3.5 MHz, 7 MHz or 14 Mhz LPFs are... -#define TX_LPF_C (3) // ...switched inline depending upon the TX frequency -#define CW_KEY (2) // Pin goes high during CW keydown to transmit the carrier. - // ... The CW_KEY is needed in addition to the TX/RX key as the... - // ...key can be up within a tx period - - -/** pin assignments -14 T_IRQ 2 std changed -13 T_DOUT (parallel to SOD/MOSI, pin 9 of display) -12 T_DIN (parallel to SDI/MISO, pin 6 of display) -11 T_CS 9 (we need to specify this) -10 T_CLK (parallel to SCK, pin 7 of display) -9 SDO(MSIO) 12 12 (spi) -8 LED A0 8 (not needed, permanently on +3.3v) (resistor from 5v, -7 SCK 13 13 (spi) -6 SDI 11 11 (spi) -5 D/C A3 7 (changable) -4 RESET A4 9 (not needed, permanently +5v) -3 CS A5 10 (changable) -2 GND GND -1 VCC VCC - -The model is called tjctm24028-spi -it uses an ILI9341 display controller and an XPT2046 touch controller. -*/ - -#define TFT_DC 9 -#define TFT_CS 10 -#define CS_PIN 8 //this is the pin to select the touch controller on spi interface -// MOSI=11, MISO=12, SCK=13 - -//XPT2046_Touchscreen ts(CS_PIN); - -//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); - -/** - * The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory. - * We have to be very careful with variables that are declared inside the functions as they are - * created in a memory region called the stack. The stack has just a few bytes of space on the Arduino - * if you declare large strings inside functions, they can easily exceed the capacity of the stack - * and mess up your programs. - * We circumvent this by declaring a few global buffers as kitchen counters where we can - * slice and dice our strings. These strings are mostly used to control the display or handle - * the input and output from the USB port. We must keep a count of the bytes used while reading - * the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable. - */ -extern char c[30], b[30]; -extern char printBuff[2][20]; //mirrors what is showing on the two lines of the display -extern int count; //to generally count ticks, loops, etc - -/** - * The second set of 16 pins on the Raduino's bottom connector are have the three clock outputs and the digital lines to control the rig. - * This assignment is as follows : - * Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - * GND +5V CLK0 GND GND CLK1 GND GND CLK2 GND D2 D3 D4 D5 D6 D7 - * These too are flexible with what you may do with them, for the Raduino, we use them to : - * - TX_RX line : Switches between Transmit and Receive after sensing the PTT or the morse keyer - * - CW_KEY line : turns on the carrier for CW - */ - - -/** - * These are the indices where these user changable settinngs are stored in the EEPROM - */ -#define MASTER_CAL 0 -#define LSB_CAL 4 -#define USB_CAL 8 -#define SIDE_TONE 12 -//these are ids of the vfos as well as their offset into the eeprom storage, don't change these 'magic' values -#define VFO_A 16 -#define VFO_B 20 -#define CW_SIDETONE 24 -#define CW_SPEED 28 -// the screen calibration parameters : int slope_x=104, slope_y=137, offset_x=28, offset_y=29; -#define SLOPE_X 32 -#define SLOPE_Y 36 -#define OFFSET_X 40 -#define OFFSET_Y 44 -#define CW_DELAYTIME 48 - -//These are defines for the new features back-ported from KD8CEC's software -//these start from beyond 256 as Ian, KD8CEC has kept the first 256 bytes free for the base version -#define VFO_A_MODE 256 // 2: LSB, 3: USB -#define VFO_B_MODE 257 - -//values that are stroed for the VFO modes -#define VFO_MODE_LSB 2 -#define VFO_MODE_USB 3 - -// handkey, iambic a, iambic b : 0,1,2f -#define CW_KEY_TYPE 358 - -/** - * The uBITX is an upconnversion transceiver. The first IF is at 45 MHz. - * The first IF frequency is not exactly at 45 Mhz but about 5 khz lower, - * this shift is due to the loading on the 45 Mhz crystal filter by the matching - * L-network used on it's either sides. - * The first oscillator works between 48 Mhz and 75 MHz. The signal is subtracted - * from the first oscillator to arriive at 45 Mhz IF. Thus, it is inverted : LSB becomes USB - * and USB becomes LSB. - * The second IF of 11.059 Mhz has a ladder crystal filter. If a second oscillator is used at - * 56 Mhz (appox), the signal is subtracted FROM the oscillator, inverting a second time, and arrives - * at the 11.059 Mhz ladder filter thus doouble inversion, keeps the sidebands as they originally were. - * If the second oscillator is at 33 Mhz, the oscilaltor is subtracated from the signal, - * thus keeping the signal's sidebands inverted. The USB will become LSB. - * We use this technique to switch sidebands. This is to avoid placing the lsbCarrier close to - * 11 MHz where its fifth harmonic beats with the arduino's 16 Mhz oscillator's fourth harmonic - */ - -#define INIT_USB_FREQ (11059200l) -// limits the tuning and working range of the ubitx between 3 MHz and 30 MHz -#define LOWEST_FREQ (100000l) -#define HIGHEST_FREQ (30000000l) - -//we directly generate the CW by programmin the Si5351 to the cw tx frequency, hence, both are different modes -//these are the parameter passed to startTx -#define TX_SSB 0 -#define TX_CW 1 - -extern char ritOn; -extern char vfoActive; -extern unsigned long vfoA, vfoB, sideTone, usbCarrier; -extern char isUsbVfoA, isUsbVfoB; -extern unsigned long frequency, ritRxFrequency, ritTxFrequency; //frequency is the current frequency on the dial -extern unsigned long firstIF; - -// if cwMode is flipped on, the rx frequency is tuned down by sidetone hz instead of being zerobeat -extern int cwMode; - - -//these are variables that control the keyer behaviour -extern int cwSpeed; //this is actuall the dot period in milliseconds -extern int32_t calibration; -extern int cwDelayTime; -extern bool Iambic_Key; - -#define IAMBICB 0x10 // 0 for Iambic A, 1 for Iambic B -extern unsigned char keyerControl; -//during CAT commands, we will freeeze the display until CAT is disengaged -extern unsigned char doingCAT; - - -/** - * Raduino needs to keep track of current state of the transceiver. These are a few variables that do it - */ -extern boolean txCAT; //turned on if the transmitting due to a CAT command -extern char inTx; //it is set to 1 if in transmit mode (whatever the reason : cw, ptt or cat) -extern int splitOn; //working split, uses VFO B as the transmit frequency -extern char keyDown; //in cw mode, denotes the carrier is being transmitted -extern char isUSB; //upper sideband was selected, this is reset to the default for the - //frequency when it crosses the frequency border of 10 MHz -extern byte menuOn; //set to 1 when the menu is being displayed, if a menu item sets it to zero, the menu is exited -extern unsigned long cwTimeout; //milliseconds to go before the cw transmit line is released and the radio goes back to rx mode -extern unsigned long dbgCount; //not used now -extern unsigned char txFilter ; //which of the four transmit filters are in use -extern boolean modeCalibrate;//this mode of menus shows extended menus to calibrate the oscillators and choose the proper - //beat frequency - -/* these are functions implemented in the main file named as ubitx_xxx.ino */ -void active_delay(int delay_by); -void saveVFOs(); -void setFrequency(unsigned long f); -void startTx(byte txMode); -void stopTx(); -void ritEnable(unsigned long f); -void ritDisable(); -void checkCAT(); -void cwKeyer(void); -void switchVFO(int vfoSelect); - -int enc_read(void); // returns the number of ticks in a short interval, +ve in clockwise, -ve in anti-clockwise -int btnDown(); //returns true if the encoder button is pressed - -/* these functions are called universally to update the display */ -void updateDisplay(); //updates just the VFO frequency to show what is in 'frequency' variable -void redrawVFOs(); //redraws only the changed digits of the vfo -void guiUpdate(); //repaints the entire screen. Slow!! -void drawCommandbar(char *text); -void drawTx(); -//getValueByKnob() provides a reusable dialog box to get a value from the encoder, the prefix and postfix -//are useful to concatanate the values with text like "Set Freq to " x " KHz" -int getValueByKnob(int minimum, int maximum, int step_size, int initial, char* prefix, char *postfix); - -//functions of the setup menu. implemented in seteup.cpp -void doSetup2(); //main setup function, displays the setup menu, calls various dialog boxes -void setupBFO(); -void setupFreq(); - - - -//displays a nice dialog box with a title and instructions as footnotes -void displayDialog(char *title, char *instructions); -void printCarrierFreq(unsigned long freq); //used to display the frequency in the command area (ex: fast tuning) - - -//main functions to check if any button is pressed and other user interface events -void doCommands(); //does the commands with encoder to jump from button to button -void checkTouch(); //does the commands with a touch on the buttons - - - - - - - -/* these are functiosn implemented in ubitx_si5351.cpp */ -void si5351bx_setfreq(uint8_t clknum, uint32_t fout); -void initOscillators(); -void si5351_set_calibration(int32_t cal); //calibration is a small value that is nudged to make up for the inaccuracies of the reference 25 MHz crystal frequency diff --git a/ubitx_cat.cpp b/ubitx_cat.cpp index c15bc26..b5fd491 100644 --- a/ubitx_cat.cpp +++ b/ubitx_cat.cpp @@ -1,68 +1,157 @@ #include -#include "ubitx.h" #include "nano_gui.h" +#include "scratch_space.h" +#include "settings.h" +#include "tuner.h" /** * The CAT protocol is used by many radios to provide remote control to comptuers through * the serial port. - * + * * This is very much a work in progress. Parts of this code have been liberally * borrowed from other GPLicensed works like hamlib. - * - * WARNING : This is an unstable version and it has worked with fldigi, - * it gives time out error with WSJTX 1.8.0 + * + * WARNING : This is an unstable version and it has worked with fldigi, + * it gives time out error with WSJTX 1.8.0 */ -static unsigned long rxBufferArriveTime = 0; -static byte rxBufferCheckCount = 0; -#define CAT_RECEIVE_TIMEOUT 500 -static byte cat[5]; -static byte insideCat = 0; -static byte useOpenRadioControl = 0; +static const uint8_t FT817_MESSAGE_SIZE = 5; + +static const uint8_t ACK = 0x00; +static const uint8_t RACK = 0xF0;//Re-Acknowledge sent when the state requests is already active + +//Data is ordered parameters 1-4, then command code last +enum CatDataIndex_e : uint8_t { + P1 = 0, + P2 = 1, + P3 = 2, + P4 = 3, + CMD = 4 +}; + +enum Ft817Command_e : uint8_t { + //Listed in the order presented by FT-817ND_OM_ENG_E13771011.pdf + OffBit = 0x80, + LockOn = 0x00, + LockOff = LockOn | OffBit, + PttOn = 0x08, + PttOff = PttOn | OffBit, + SetFrequency = 0x01,//P1-P4 are BCD, 0x01 0x42 0x34 0x56 = 14.23456MHz + OperatingMode = 0x07,//See OperatingMode_e for P1 decode + ClarOn = 0x05, + ClarOff = ClarOn | OffBit, + ClarFrequency = 0xF5,//P1 is sign/direction (0x00 = +, - otherwise), P3-P4 are BCD, 0x12 0x34 = 12.34kHz + VfoToggle = 0x81, + SplitOn = 0x02, + SplitOff = SplitOn | OffBit, + RepeaterMode = 0x09,//See RepeaterMode_e for P1 decode + RepeaterOffset = 0xF9,//P1-P4 are BCD + CtcssDcsMode = 0x0A,//See CtcssDcsMode_e for P1 decode + CtcssTone = 0x0B,//P1-P2 are BCD, 0x08 0x85 = 88.5MHz + DcsTone = 0x0C,//P1-P2 are BCD, 0x00 0x23 = code 023 + ReadRxStatus = 0xE7,//Returns ReadRxStatus_t + ReadTxStatus = 0xF7,//Returns ReadTxStatus_t + ReadFreqAndMode = 0x03,//Returns current frequency (BCD, 4 bytes), then mode (OperatingMode_e) + PowerOn = 0x0F, + PowerOff = PowerOn | OffBit, + //Unofficial commands + ReadEeprom = 0xBB, +}; + +enum OperatingMode_e : uint8_t { + LSB = 0x00, + USB = 0x01, + CW = 0x02, + CWR = 0x03,//CW-reverse aka LSB CW + AM = 0x04, + FM = 0x08, + DIG = 0x0A, + PKT = 0x0C, +}; + +enum RepeaterMode_e : uint8_t { + ShiftMinus = 0x09, + ShiftPlus = 0x49, + Simplex = 0x89, +}; + +enum CtcssDcsMode_e : uint8_t { + DcsOn = 0x0A, + CtcssOn = 0x2A, + EncoderOn = 0x4A, + Off = 0x8A, +}; + +struct ReadRxStatus_t { + //Bitfields are not defined by the standard to be portable, which is unfortunate + uint8_t Smeter : 4;//0x00 = S0, 0x09 = S9, etc. + uint8_t Dummy : 1; + uint8_t DiscriminatorCenteringOff : 1; + uint8_t CodeUnmatched : 1; + uint8_t SquelchSuppressionActive : 1; +}; + +struct ReadTxStatus_t { + //Bitfields are not defined by the standard to be portable, which is unfortunate + uint8_t PowerOutputMeter : 4; + uint8_t Dummy : 1; + uint8_t SplitOff : 1; + uint8_t HighSwrDetected : 1; + uint8_t PttOff : 1; +}; + +//Values based on http://www.ka7oei.com/ft817_memmap.html +//hamlib likes to read addresses 0x0065 (read as 0x0064) and 0x007A, but including support for some others +enum Ft817Eeprom_e : uint16_t { + VfoAndBankSelect = 0x0055, + TuningModes = 0x0057, + KeyerStatus = 0x0058, + BandSelect = 0x0059, + BeepVolume = 0x005C, + CwPitch = 0x005E, + CwWeight = 0x005F, + CwDelay = 0x0060, + SidetoneVolume = 0x0061, + CwSpeed = 0x0062, + VoxGain = 0x0063, + CatBaudRate = 0x0064, + SsbMicVolume = 0x0067, + AmMicVolume = 0x0068, + FmMicVolume = 0x0069, + TxPower = 0x0079, + AntennaSelectAndSplit = 0x007A, + VfoAPhantomMode = 0x01E9, +}; //for broken protocol -#define CAT_RECEIVE_TIMEOUT 500 +static const uint16_t CAT_RECEIVE_TIMEOUT_MS = 500; -#define CAT_MODE_LSB 0x00 -#define CAT_MODE_USB 0x01 -#define CAT_MODE_CW 0x02 -#define CAT_MODE_CWR 0x03 -#define CAT_MODE_AM 0x04 -#define CAT_MODE_FM 0x08 -#define CAT_MODE_DIG 0x0A -#define CAT_MODE_PKT 0x0C -#define CAT_MODE_FMN 0x88 - -#define ACK 0 - -unsigned int skipTimeCount = 0; - -byte setHighNibble(byte b,byte v) { +uint8_t setHighNibble(uint8_t b, uint8_t v) { // Clear the high nibble b &= 0x0f; // Set the high nibble return b | ((v & 0x0f) << 4); } -byte setLowNibble(byte b,byte v) { +uint8_t setLowNibble(uint8_t b, uint8_t v) { // Clear the low nibble b &= 0xf0; // Set the low nibble return b | (v & 0x0f); } -byte getHighNibble(byte b) { +uint8_t getHighNibble(uint8_t b) { return (b >> 4) & 0x0f; } -byte getLowNibble(byte b) { +uint8_t getLowNibble(uint8_t b) { return b & 0x0f; } // Takes a number and produces the requested number of decimal digits, staring -// from the least significant digit. +// from the least significant digit. // -void getDecimalDigits(unsigned long number,byte* result,int digits) { +void getDecimalDigits(unsigned long number, uint8_t* result,int digits) { for (int i = 0; i < digits; i++) { // "Mask off" (in a decimal sense) the LSD and return it result[i] = number % 10; @@ -73,84 +162,46 @@ void getDecimalDigits(unsigned long number,byte* result,int digits) { // Takes a frequency and writes it into the CAT command buffer in BCD form. // -void writeFreq(unsigned long freq,byte* cmd) { +void writeFreq(unsigned long freq, uint8_t* cmd) { // Convert the frequency to a set of decimal digits. We are taking 9 digits // so that we can get up to 999 MHz. But the protocol doesn't care about the // LSD (1's place), so we ignore that digit. - byte digits[9]; + uint8_t digits[9]; getDecimalDigits(freq,digits,9); - // Start from the LSB and get each nibble - cmd[3] = setLowNibble(cmd[3],digits[1]); - cmd[3] = setHighNibble(cmd[3],digits[2]); - cmd[2] = setLowNibble(cmd[2],digits[3]); - cmd[2] = setHighNibble(cmd[2],digits[4]); - cmd[1] = setLowNibble(cmd[1],digits[5]); - cmd[1] = setHighNibble(cmd[1],digits[6]); - cmd[0] = setLowNibble(cmd[0],digits[7]); - cmd[0] = setHighNibble(cmd[0],digits[8]); + // Start from the LSB and get each nibble + cmd[P4] = setLowNibble(cmd[P4],digits[1]); + cmd[P4] = setHighNibble(cmd[P4],digits[2]); + cmd[P3] = setLowNibble(cmd[P3],digits[3]); + cmd[P3] = setHighNibble(cmd[P3],digits[4]); + cmd[P2] = setLowNibble(cmd[P2],digits[5]); + cmd[P2] = setHighNibble(cmd[P2],digits[6]); + cmd[P1] = setLowNibble(cmd[P1],digits[7]); + cmd[P1] = setHighNibble(cmd[P1],digits[8]); } -// This function takes a frquency that is encoded using 4 bytes of BCD +// This function takes a frquency that is encoded using 4 uint8_ts of BCD // representation and turns it into an long measured in Hz. // // [12][34][56][78] = 123.45678? Mhz // -unsigned long readFreq(byte* cmd) { +uint32_t readFreq(uint8_t* cmd) { // Pull off each of the digits - byte d7 = getHighNibble(cmd[0]); - byte d6 = getLowNibble(cmd[0]); - byte d5 = getHighNibble(cmd[1]); - byte d4 = getLowNibble(cmd[1]); - byte d3 = getHighNibble(cmd[2]); - byte d2 = getLowNibble(cmd[2]); - byte d1 = getHighNibble(cmd[3]); - byte d0 = getLowNibble(cmd[3]); - return - (unsigned long)d7 * 100000000L + - (unsigned long)d6 * 10000000L + - (unsigned long)d5 * 1000000L + - (unsigned long)d4 * 100000L + - (unsigned long)d3 * 10000L + - (unsigned long)d2 * 1000L + - (unsigned long)d1 * 100L + - (unsigned long)d0 * 10L; + unsigned long ret = 0; + for(uint8_t i = 0; i < 4; ++i){ + const uint8_t d1 = getHighNibble(cmd[i]); + const uint8_t d0 = getLowNibble(cmd[i]); + ret *= 100; + ret += 10*d1 + d0; + } + + return ret*10; } -//void ReadEEPRom_FT817(byte fromType) -void catReadEEPRom(void) +void catGetEeprom(const uint16_t read_address, uint8_t* response) { - //for remove warnings - byte temp0 = cat[0]; - byte temp1 = cat[1]; -/* - itoa((int) cat[0], b, 16); - strcat(b, ":"); - itoa((int) cat[1], c, 16); - strcat(b, c); - printLine2(b); -*/ - - cat[0] = 0; - cat[1] = 0; - //for remove warnings[1] = 0; - - switch (temp1) + switch (read_address) { - case 0x45 : // - if (temp0 == 0x03) - { - cat[0] = 0x00; - cat[1] = 0xD0; - } - break; - case 0x47 : // - if (temp0 == 0x03) - { - cat[0] = 0xDC; - cat[1] = 0xE0; - } - break; - case 0x55 : + case Ft817Eeprom_e::VfoAndBankSelect: //0 : VFO A/B 0 = VFO-A, 1 = VFO-B //1 : MTQMB Select 0 = (Not MTQMB), 1 = MTQMB ("Memory Tune Quick Memory Bank") //2 : QMB Select 0 = (Not QMB), 1 = QMB ("Quick Memory Bank") @@ -159,299 +210,211 @@ void catReadEEPRom(void) //5 : Memory/MTUNE select 0 = Memory, 1 = MTUNE //6 : //7 : MEM/VFO Select 0 = Memory, 1 = VFO (A or B - see bit 0) - cat[0] = 0x80 + (vfoActive == VFO_B ? 1 : 0); - cat[1] = 0x00; + *response = 0x80 //always report VFO mode + | ((VFO_B == globalSettings.activeVfo) ? 0x01 : 0x00); break; - case 0x57 : // - //0 : 1-0 AGC Mode 00 = Auto, 01 = Fast, 10 = Slow, 11 = Off - //2 DSP On/Off 0 = Off, 1 = On (Display format) - //4 PBT On/Off 0 = Off, 1 = On (Passband Tuning) - //5 NB On/Off 0 = Off, 1 = On (Noise Blanker) - //6 Lock On/Off 0 = Off, 1 = On (Dial Lock) - //7 FST (Fast Tuning) On/Off 0 = Off, 1 = On (Fast tuning) - - cat[0] = 0xC0; - cat[1] = 0x40; - break; - case 0x59 : // band select VFO A Band Select 0000 = 160 M, 0001 = 75 M, 0010 = 40 M, 0011 = 30 M, 0100 = 20 M, 0101 = 17 M, 0110 = 15 M, 0111 = 12 M, 1000 = 10 M, 1001 = 6 M, 1010 = FM BCB, 1011 = Air, 1100 = 2 M, 1101 = UHF, 1110 = (Phantom) - //http://www.ka7oei.com/ft817_memmap.html - //CAT_BUFF[0] = 0xC2; - //CAT_BUFF[1] = 0x82; - break; - case 0x5C : //Beep Volume (0-100) (#13) - cat[0] = 0xB2; - cat[1] = 0x42; - break; - case 0x5E : + case Ft817Eeprom_e::CwPitch: //3-0 : CW Pitch (300-1000 Hz) (#20) From 0 to E (HEX) with 0 = 300 Hz and each step representing 50 Hz //5-4 : Lock Mode (#32) 00 = Dial, 01 = Freq, 10 = Panel //7-6 : Op Filter (#38) 00 = Off, 01 = SSB, 10 = CW - //CAT_BUFF[0] = 0x08; - cat[0] = (sideTone - 300)/50; - cat[1] = 0x25; - break; - case 0x61 : //Sidetone (Volume) (#44) - cat[0] = sideTone % 50; - cat[1] = 0x08; + *response = (globalSettings.cwSideToneFreq - 300)/50; break; - case 0x5F : // - //4-0 CW Weight (1.:2.5-1:4.5) (#22) From 0 to 14 (HEX) with 0 = 1:2.5, incrementing in 0.1 weight steps - //5 420 ARS (#2) 0 = Off, 1 = On - //6 144 ARS (#1) 0 = Off, 1 = On - //7 Sql/RF-G (#45) 0 = Off, 1 = On - cat[0] = 0x32; - cat[1] = 0x08; + case Ft817Eeprom_e::SidetoneVolume: + //Sidetone (Volume) (#44) 0-100 + *response = globalSettings.cwSideToneFreq / 100; break; - case 0x60 : //CW Delay (10-2500 ms) (#17) From 1 to 250 (decimal) with each step representing 10 ms - cat[0] = cwDelayTime; - cat[1] = 0x32; + case Ft817Eeprom_e::CwDelay: + //CW Delay (10-2500 ms) (#17) From 1 to 250 (decimal) with each step representing 10 ms + *response = globalSettings.cwActiveTimeoutMs / 10; break; - case 0x62 : // + case Ft817Eeprom_e::CwSpeed: //5-0 CW Speed (4-60 WPM) (#21) From 0 to 38 (HEX) with 0 = 4 WPM and 38 = 60 WPM (1 WPM steps) //7-6 Batt-Chg (6/8/10 Hours (#11) 00 = 6 Hours, 01 = 8 Hours, 10 = 10 Hours - //CAT_BUFF[0] = 0x08; - cat[0] = 1200 / cwSpeed - 4; - cat[1] = 0xB2; + *response = (1200 / globalSettings.cwDitDurationMs) - 4; break; - case 0x63 : // - //6-0 VOX Gain (#51) Contains 1-100 (decimal) as displayed - //7 Disable AM/FM Dial (#4) 0 = Enable, 1 = Disable - cat[0] = 0xB2; - cat[1] = 0xA5; + case Ft817Eeprom_e::CatBaudRate: + //4-0 : VOX Delay (#50) 0 = 100 Ms with each step representing 100 Ms. 24 = 2500 Ms + //5 : Emergency (#28) 0 = Off, 1 = On + //7-6 : CAT Rate (4800, 9600, 38400) (#14) 00 = 4800, 01 = 9600, 10 = 38400 Baud + *response = 0xA5; break; - case 0x64 : // + case Ft817Eeprom_e::VfoAPhantomMode: + //2-0 : 000 = LSB, 001 = USB, 010 = CW, 011 = CWR, 100 = AM, 101 = FM, 110 = DIG, 111 = PKT + //7-3 : ? + if (VfoMode_e::VFO_MODE_USB == GetActiveVfoMode()){ + *response = OperatingMode_e::USB; + } + else{ + *response = OperatingMode_e::LSB; + } break; - case 0x67 : //6-0 SSB Mic (#46) Contains 0-100 (decimal) as displayed - cat[0] = 0xB2; - cat[1] = 0xB2; - break; case 0x69 : //FM Mic (#29) Contains 0-100 (decimal) as displayed - case 0x78 : - if (isUSB) - cat[0] = CAT_MODE_USB; - else - cat[0] = CAT_MODE_LSB; - - if (cat[0] != 0) cat[0] = 1 << 5; + case Ft817Eeprom_e::AntennaSelectAndSplit: + //0 : HF Antenna Select 0 = Front, 1 = Rear + //1 : 6 M Antenna Select 0 = Front, 1 = Rear + //2 : FM BCB Antenna Select 0 = Front, 1 = Rear + //3 : Air Antenna Select 0 = Front, 1 = Rear + //4 : 2 M Antenna Select 0 = Front, 1 = Rear + //5 : UHF Antenna Select 0 = Front, 1 = Rear + //6 : ? ? + //7 : SPL On/Off 0 = Off, 1 = On + *response = (globalSettings.splitOn ? 0xFF : 0x7F); break; - case 0x79 : // - //1-0 TX Power (All bands) 00 = High, 01 = L3, 10 = L2, 11 = L1 - //3 PRI On/Off 0 = Off, 1 = On - //DW On/Off 0 = Off, 1 = On - //SCN (Scan) Mode 00 = No scan, 10 = Scan up, 11 = Scan down - //ART On/Off 0 = Off, 1 = On - cat[0] = 0x00; - cat[1] = 0x00; + } +} + +//Maps some of the fixed memory layout of the FT817's EEPROM +void catReadEEPRom(uint8_t* cmd, uint8_t* response) +{ + const uint16_t read_address = cmd[P1] << 8 | cmd[P2]; + + catGetEeprom(read_address,response); + catGetEeprom(read_address+1,response+1); +} + +void processCatCommand(uint8_t* cmd) { + //A response of a single byte, 0x00, is an ACK, so default to that + uint8_t response[FT817_MESSAGE_SIZE] = {ACK}; + uint8_t response_length = 1; + + switch(cmd[CMD]){ + case Ft817Command_e::SetFrequency: + { + uint32_t f = readFreq(cmd); + setFrequency(f); break; - case 0x7A : //SPLIT - //7A 0 HF Antenna Select 0 = Front, 1 = Rear - //7A 1 6 M Antenna Select 0 = Front, 1 = Rear - //7A 2 FM BCB Antenna Select 0 = Front, 1 = Rear - //7A 3 Air Antenna Select 0 = Front, 1 = Rear - //7A 4 2 M Antenna Select 0 = Front, 1 = Rear - //7A 5 UHF Antenna Select 0 = Front, 1 = Rear - //7A 6 ? ? - //7A 7 SPL On/Off 0 = Off, 1 = On - - cat[0] = (splitOn ? 0xFF : 0x7F); + } + + case Ft817Command_e::SplitOn: + if(globalSettings.splitOn){ + response[0] = RACK; + } + globalSettings.splitOn = true; break; - case 0xB3 : // - cat[0] = 0x00; - cat[1] = 0x4D; + case Ft817Command_e::SplitOff: + if(!globalSettings.splitOn){ + response[0] = RACK; + } + globalSettings.splitOn = false; break; - } + case Ft817Command_e::ReadFreqAndMode: + //First 4 bytes are the frequency + writeFreq(GetActiveVfoFreq(),response);//bytes 0-3 + //Last byte is the mode + if (VfoMode_e::VFO_MODE_USB == GetActiveVfoMode()){ + response[4] = OperatingMode_e::USB; + } + else{ + response[4] = OperatingMode_e::LSB; + } + response_length = 5; + break; - // sent the data - Serial.write(cat, 2); -} + case Ft817Command_e::OperatingMode: + if(OperatingMode_e::LSB == cmd[P1] || OperatingMode_e::CWR == cmd[P1]){ + SetActiveVfoMode(VfoMode_e::VFO_MODE_LSB); + } + else{ + SetActiveVfoMode(VfoMode_e::VFO_MODE_USB); + } -void processCATCommand2(byte* cmd) { - byte response[5]; - unsigned long f; - - switch(cmd[4]){ -/* case 0x00: - response[0]=0; - Serial.write(response, 1); - break; -*/ - case 0x01: - //set frequency - f = readFreq(cmd); - setFrequency(f); - updateDisplay(); - response[0]=0; - Serial.write(response, 1); - //sprintf(b, "set:%ld", f); - //printLine2(b); - break; - - case 0x02: - //split on - splitOn = 1; - break; - case 0x82: - //split off - splitOn = 0; - break; - - case 0x03: - writeFreq(frequency,response); // Put the frequency into the buffer - if (isUSB) - response[4] = 0x01; //USB - else - response[4] = 0x00; //LSB - Serial.write(response,5); - //printLine2("cat:getfreq"); - break; - - case 0x07: // set mode - if (cmd[0] == 0x00 || cmd[0] == 0x03) - isUSB = 0; - else - isUSB = 1; - response[0] = 0x00; - Serial.write(response, 1); - setFrequency(frequency); - //printLine2("cat: mode changed"); - //updateDisplay(); - break; - - case 0x08: // PTT On - if (!inTx) { - response[0] = 0; - txCAT = true; - startTx(TX_SSB); - updateDisplay(); - } else { - response[0] = 0xf0; - } - Serial.write(response,1); - updateDisplay(); - break; - - case 0x88 : //PTT OFF - if (inTx) { - stopTx(); - txCAT = false; - } - response[0] = 0; - Serial.write(response,1); - updateDisplay(); - break; - - case 0x81: - //toggle the VFOs - response[0] = 0; - if (vfoActive == VFO_A) - switchVFO(VFO_B); - else - switchVFO(VFO_A); - //menuVfoToggle(1); // '1' forces it to change the VFO - Serial.write(response,1); - updateDisplay(); - break; - - case 0xBB: //Read FT-817 EEPROM Data (for comfirtable) - catReadEEPRom(); - break; - - case 0xe7 : - // get receiver status, we have hardcoded this as - //as we dont' support ctcss, etc. - response[0] = 0x09; - Serial.write(response,1); - break; - - case 0xf7: + setFrequency(GetActiveVfoFreq());//Refresh frequency to get new mode to take effect + break; + + case Ft817Command_e::PttOn: + if (!globalSettings.txActive) { + globalSettings.txCatActive = true; + startTx(globalSettings.tuningMode); + } + else { + response[0] = RACK; + } + break; + + case Ft817Command_e::PttOff: + if (globalSettings.txActive) { + stopTx(); + } + else{ + response[0] = RACK; + } + globalSettings.txCatActive = false; + break; + + case Ft817Command_e::VfoToggle: + if (Vfo_e::VFO_A == globalSettings.activeVfo){ + globalSettings.activeVfo = Vfo_e::VFO_B; + } + else{ + globalSettings.activeVfo = Vfo_e::VFO_A; + } + break; + + case Ft817Command_e::ReadEeprom: + catReadEEPRom(cmd,response); + response_length = 2; + break; + + case Ft817Command_e::ReadRxStatus: + //We don't have visibility into these values, so just hard code stuff + ReadRxStatus_t reply_status; + reply_status.Dummy = 0; + reply_status.Smeter = 9;//S9 + reply_status.SquelchSuppressionActive = 0; + reply_status.DiscriminatorCenteringOff = 1; + reply_status.CodeUnmatched = 0; + response[0] = *(uint8_t*)&reply_status; + break; + + case Ft817Command_e::ReadTxStatus: { - boolean isHighSWR = false; - boolean isSplitOn = false; - - /* - Inverted -> *ptt = ((p->tx_status & 0x80) == 0); <-- souce code in ft817.c (hamlib) - */ - response[0] = ((inTx ? 0 : 1) << 7) + - ((isHighSWR ? 1 : 0) << 6) + //hi swr off / on - ((isSplitOn ? 1 : 0) << 5) + //Split on / off - (0 << 4) + //dummy data - 0x08; //P0 meter data - - Serial.write(response, 1); + //We don't have visibility into some of these values, so just hard code stuff + ReadTxStatus_t reply_status; + reply_status.Dummy = 0; + reply_status.HighSwrDetected = 0; + reply_status.PowerOutputMeter = 0xF; + reply_status.PttOff = !globalSettings.txActive; + reply_status.SplitOff = globalSettings.splitOn;//Yaesu's documentation says that 1 = split off, but as of 2020-05-04 hamlib reads (*split = (p->tx_status & 0x20) ? RIG_SPLIT_ON : RIG_SPLIT_OFF), so do what hamlib wants + + response[0] = *(uint8_t*)&reply_status; + break; } - break; - - default: - //somehow, get this to print the four bytes - ultoa(*((unsigned long *)cmd), c, 16); - /*itoa(cmd[4], b, 16); - strcat(b, ">"); - strcat(b, c); - printLine2(b);*/ - response[0] = 0x00; - Serial.write(response[0]); + + default: + //Do something? + break; } - insideCat = false; + Serial.write(response, response_length); } -int catCount = 0; void checkCAT(){ - byte i; + static uint8_t rx_buffer[FT817_MESSAGE_SIZE]; + static uint8_t current_index = 0; + static uint32_t timeout = 0; //Check Serial Port Buffer if (Serial.available() == 0) { //Set Buffer Clear status - rxBufferCheckCount = 0; + if(timeout < millis()){ + current_index = 0; + timeout = 0; + } return; } - else if (Serial.available() < 5) { //First Arrived - if (rxBufferCheckCount == 0){ - rxBufferCheckCount = Serial.available(); - rxBufferArriveTime = millis() + CAT_RECEIVE_TIMEOUT; //Set time for timeout + else{ + if(0 == current_index){ + timeout = millis() + CAT_RECEIVE_TIMEOUT_MS; } - else if (rxBufferArriveTime < millis()){ //Clear Buffer - for (i = 0; i < Serial.available(); i++) - rxBufferCheckCount = Serial.read(); - rxBufferCheckCount = 0; + rx_buffer[current_index] = Serial.read(); + ++current_index; + if(current_index < FT817_MESSAGE_SIZE){ + return; } - else if (rxBufferCheckCount < Serial.available()){ // Increase buffer count, slow arrive - rxBufferCheckCount = Serial.available(); - rxBufferArriveTime = millis() + CAT_RECEIVE_TIMEOUT; //Set time for timeout - } - return; } - - //Arived CAT DATA - for (i = 0; i < 5; i++) - cat[i] = Serial.read(); - - - //this code is not re-entrant. - if (insideCat == 1) - return; - insideCat = 1; - -/** - * This routine is enabled to debug the cat protocol -**/ - catCount++; - -/* - if (cat[4] != 0xf7 && cat[4] != 0xbb && cat[4] != 0x03){ - sprintf(b, "%d %02x %02x%02x%02x%02x", catCount, cat[4],cat[0], cat[1], cat[2], cat[3]); - printLine2(b); - } -*/ - -/* - if (!doingCAT){ - doingCAT = 1; - displayText("CAT on", 100,120,100,40, ILI9341_ORANGE, ILI9341_BLACK, ILI9341_WHITE); - } -*/ - processCATCommand2(cat); - insideCat = 0; + processCatCommand(rx_buffer); + current_index = 0; + timeout = 0; } diff --git a/ubitx_ui.cpp b/ubitx_ui.cpp deleted file mode 100644 index 1f27322..0000000 --- a/ubitx_ui.cpp +++ /dev/null @@ -1,869 +0,0 @@ -#include -#include -#include "morse.h" -#include "ubitx.h" -#include "nano_gui.h" - -/** - * The user interface of the ubitx consists of the encoder, the push-button on top of it - * and the 16x2 LCD display. - * The upper line of the display is constantly used to display frequency and status - * of the radio. Occasionally, it is used to provide a two-line information that is - * quickly cleared up. - */ - -#define BUTTON_SELECTED 1 - -struct Button { - int x, y, w, h; - char *text; - char *morse; -}; - -#define MAX_BUTTONS 17 -const struct Button btn_set[MAX_BUTTONS] PROGMEM = { -//const struct Button btn_set [] = { - {0, 10, 159, 36, "VFOA", "A"}, - {160, 10, 159, 36, "VFOB", "B"}, - - {0, 80, 60, 36, "RIT", "R"}, - {64, 80, 60, 36, "USB", "U"}, - {128, 80, 60, 36, "LSB", "L"}, - {192, 80, 60, 36, "CW", "M"}, - {256, 80, 60, 36, "SPL", "S"}, - - {0, 120, 60, 36, "80", "8"}, - {64, 120, 60, 36, "40", "4"}, - {128, 120, 60, 36, "30", "3"}, - {192, 120, 60, 36, "20", "2"}, - {256, 120, 60, 36, "17", "7"}, - - {0, 160, 60, 36, "15", "5"}, - {64, 160, 60, 36, "10", "1"}, - {128, 160, 60, 36, "WPM", "W"}, - {192, 160, 60, 36, "TON", "T"}, - {256, 160, 60, 36, "FRQ", "F"}, -}; - -#define MAX_KEYS 17 -const struct Button keypad[MAX_KEYS] PROGMEM = { - {0, 80, 60, 36, "1", "1"}, - {64, 80, 60, 36, "2", "2"}, - {128, 80, 60, 36, "3", "3"}, - {192, 80, 60, 36, "", ""}, - {256, 80, 60, 36, "OK", "K"}, - - {0, 120, 60, 36, "4", "4"}, - {64, 120, 60, 36, "5", "5"}, - {128, 120, 60, 36, "6", "6"}, - {192, 120, 60, 36, "0", "0"}, - {256, 120, 60, 36, "<-", "B"}, - - {0, 160, 60, 36, "7", "7"}, - {64, 160, 60, 36, "8", "8"}, - {128, 160, 60, 36, "9", "9"}, - {192, 160, 60, 36, "", ""}, - {256, 160, 60, 36, "Can", "C"}, -}; - -boolean getButton(char *text, struct Button *b){ - for (int i = 0; i < MAX_BUTTONS; i++){ - memcpy_P(b, btn_set + i, sizeof(struct Button)); - if (!strcmp(text, b->text)){ - return true; - } - } - return false; -} - - -/* - * This formats the frequency given in f - */ -void formatFreq(long f, char *buff) { - // tks Jack Purdum W8TEE - // replaced fsprint commmands by str commands for code size reduction - - memset(buff, 0, 10); - memset(b, 0, sizeof(b)); - - ultoa(f, b, DEC); - - //one mhz digit if less than 10 M, two digits if more - if (f < 10000000l){ - buff[0] = ' '; - strncat(buff, b, 4); - strcat(buff, "."); - strncat(buff, &b[4], 2); - } - else { - strncat(buff, b, 5); - strcat(buff, "."); - strncat(buff, &b[5], 2); - } -} - -void drawCommandbar(char *text){ - displayFillrect(30,45,280, 32, DISPLAY_NAVY); - displayRawText(text, 30, 45, DISPLAY_WHITE, DISPLAY_NAVY); -} - -/** A generic control to read variable values -*/ -int getValueByKnob(int minimum, int maximum, int step_size, int initial, char* prefix, char *postfix) -{ - int knob = 0; - int knob_value; - - while (btnDown()) - active_delay(100); - - active_delay(200); - knob_value = initial; - - strcpy(b, prefix); - itoa(knob_value, c, 10); - strcat(b, c); - strcat(b, postfix); - drawCommandbar(b); - - while(!btnDown() && digitalRead(PTT) == HIGH){ - - knob = enc_read(); - if (knob != 0){ - if (knob_value > minimum && knob < 0) - knob_value -= step_size; - if (knob_value < maximum && knob > 0) - knob_value += step_size; - - strcpy(b, prefix); - itoa(knob_value, c, 10); - strcat(b, c); - strcat(b, postfix); - drawCommandbar(b); - } - checkCAT(); - } - displayFillrect(30,41,280, 32, DISPLAY_NAVY); - return knob_value; -} - -void printCarrierFreq(unsigned long freq){ - - memset(c, 0, sizeof(c)); - memset(b, 0, sizeof(b)); - - ultoa(freq, b, DEC); - - strncat(c, b, 2); - strcat(c, "."); - strncat(c, &b[2], 3); - strcat(c, "."); - strncat(c, &b[5], 1); - displayText(c, 110, 100, 100, 30, DISPLAY_CYAN, DISPLAY_NAVY, DISPLAY_NAVY); -} - -void displayDialog(char *title, char *instructions){ - displayClear(DISPLAY_BLACK); - displayRect(10,10,300,220, DISPLAY_WHITE); - displayHline(20,45,280,DISPLAY_WHITE); - displayRect(12,12,296,216, DISPLAY_WHITE); - displayRawText(title, 20, 20, DISPLAY_CYAN, DISPLAY_NAVY); - displayRawText(instructions, 20, 200, DISPLAY_CYAN, DISPLAY_NAVY); -} - - - - -char vfoDisplay[12]; -void displayVFO(int vfo){ - int x, y; - int displayColor, displayBorder; - Button b; - - if (vfo == VFO_A){ - getButton("VFOA", &b); - if (splitOn){ - if (vfoActive == VFO_A) - strcpy(c, "R:"); - else - strcpy(c, "T:"); - } - else - strcpy(c, "A:"); - if (vfoActive == VFO_A){ - formatFreq(frequency, c+2); - displayColor = DISPLAY_WHITE; - displayBorder = DISPLAY_BLACK; - }else{ - formatFreq(vfoA, c+2); - displayColor = DISPLAY_GREEN; - displayBorder = DISPLAY_BLACK; - } - } - - if (vfo == VFO_B){ - getButton("VFOB", &b); - - if (splitOn){ - if (vfoActive == VFO_B) - strcpy(c, "R:"); - else - strcpy(c, "T:"); - } - else - strcpy(c, "B:"); - if (vfoActive == VFO_B){ - formatFreq(frequency, c+2); - displayColor = DISPLAY_WHITE; - displayBorder = DISPLAY_WHITE; - } else { - displayColor = DISPLAY_GREEN; - displayBorder = DISPLAY_BLACK; - formatFreq(vfoB, c+2); - } - } - - if (vfoDisplay[0] == 0){ - displayFillrect(b.x, b.y, b.w, b.h, DISPLAY_BLACK); - if (vfoActive == vfo) - displayRect(b.x, b.y, b.w , b.h, DISPLAY_WHITE); - else - displayRect(b.x, b.y, b.w , b.h, DISPLAY_NAVY); - } - x = b.x + 6; - y = b.y + 3; - - char *text = c; - - for (int i = 0; i <= strlen(c); i++){ - char digit = c[i]; - if (digit != vfoDisplay[i]){ - - displayFillrect(x, y, 15, b.h-6, DISPLAY_BLACK); - //checkCAT(); - - displayChar(x, y + TEXT_LINE_HEIGHT + 3, digit, displayColor, DISPLAY_BLACK); - checkCAT(); - } - if (digit == ':' || digit == '.') - x += 7; - else - x += 16; - text++; - }//end of the while loop of the characters to be printed - - strcpy(vfoDisplay, c); -} - -void btnDraw(struct Button *b){ - if (!strcmp(b->text, "VFOA")){ - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_A); - } - else if(!strcmp(b->text, "VFOB")){ - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_B); - } - else if ((!strcmp(b->text, "RIT") && ritOn == 1) || - (!strcmp(b->text, "USB") && isUSB == 1) || - (!strcmp(b->text, "LSB") && isUSB == 0) || - (!strcmp(b->text, "SPL") && splitOn == 1)) - displayText(b->text, b->x, b->y, b->w, b->h, DISPLAY_BLACK, DISPLAY_ORANGE, DISPLAY_DARKGREY); - else if (!strcmp(b->text, "CW") && cwMode == 1) - displayText(b->text, b->x, b->y, b->w, b->h, DISPLAY_BLACK, DISPLAY_ORANGE, DISPLAY_DARKGREY); - else - displayText(b->text, b->x, b->y, b->w, b->h, DISPLAY_GREEN, DISPLAY_BLACK, DISPLAY_DARKGREY); -} - - -void displayRIT(){ - displayFillrect(0,41,320,30, DISPLAY_NAVY); - if (ritOn){ - strcpy(c, "TX:"); - formatFreq(ritTxFrequency, c+3); - if (vfoActive == VFO_A) - displayText(c, 0, 45,159, 30, DISPLAY_WHITE, DISPLAY_NAVY, DISPLAY_NAVY); - else - displayText(c, 160, 45,159, 30, DISPLAY_WHITE, DISPLAY_NAVY, DISPLAY_NAVY); - } - else { - if (vfoActive == VFO_A) - displayText("", 0, 45,159, 30, DISPLAY_WHITE, DISPLAY_NAVY, DISPLAY_NAVY); - else - displayText("", 160, 45,159, 30, DISPLAY_WHITE, DISPLAY_NAVY, DISPLAY_NAVY); - } -} - -void fastTune(){ - int encoder; - - //if the btn is down, wait until it is up - while(btnDown()) - active_delay(50); - active_delay(300); - - displayRawText("Fast tune", 100, 55, DISPLAY_CYAN, DISPLAY_NAVY); - while(1){ - checkCAT(); - - //exit after debouncing the btnDown - if (btnDown()){ - displayFillrect(100, 55, 120, 30, DISPLAY_NAVY); - - //wait until the button is realsed and then return - while(btnDown()) - active_delay(50); - active_delay(300); - return; - } - - encoder = enc_read(); - if (encoder != 0){ - - if (encoder > 0 && frequency < 30000000l) - frequency += 50000l; - else if (encoder < 0 && frequency > 600000l) - frequency -= 50000l; - setFrequency(frequency); - displayVFO(vfoActive); - } - }// end of the event loop -} - -void enterFreq(){ - //force the display to refresh everything - //display all the buttons - int f; - - for (int i = 0; i < MAX_KEYS; i++){ - struct Button b; - memcpy_P(&b, keypad + i, sizeof(struct Button)); - btnDraw(&b); - } - - int cursor_pos = 0; - memset(c, 0, sizeof(c)); - f = frequency / 1000l; - - while(1){ - - checkCAT(); - if(!readTouch()) - continue; - - scaleTouch(&ts_point); - - int total = sizeof(btn_set)/sizeof(struct Button); - for (int i = 0; i < MAX_KEYS; i++){ - struct Button b; - memcpy_P(&b, keypad + i, sizeof(struct Button)); - - int x2 = b.x + b.w; - int y2 = b.y + b.h; - - if (b.x < ts_point.x && ts_point.x < x2 && - b.y < ts_point.y && ts_point.y < y2){ - if (!strcmp(b.text, "OK")){ - long f = atol(c); - if(30000 >= f && f > 100){ - frequency = f * 1000l; - setFrequency(frequency); - if (vfoActive == VFO_A) - vfoA = frequency; - else - vfoB = frequency; - saveVFOs(); - } - guiUpdate(); - return; - } - else if (!strcmp(b.text, "<-")){ - c[cursor_pos] = 0; - if (cursor_pos > 0) - cursor_pos--; - c[cursor_pos] = 0; - } - else if (!strcmp(b.text, "Can")){ - guiUpdate(); - return; - } - else if('0' <= b.text[0] && b.text[0] <= '9'){ - c[cursor_pos++] = b.text[0]; - c[cursor_pos] = 0; - } - } - } // end of the button scanning loop - strcpy(b, c); - strcat(b, " KHz"); - displayText(b, 0, 42, 320, 30, DISPLAY_WHITE, DISPLAY_NAVY, DISPLAY_NAVY); - delay(300); - while(readTouch()) - checkCAT(); - } // end of event loop : while(1) - -} - -void drawCWStatus(){ - displayFillrect(0, 201, 320, 39, DISPLAY_NAVY); - strcpy(b, " cw:"); - int wpm = 1200/cwSpeed; - itoa(wpm,c, 10); - strcat(b, c); - strcat(b, "wpm, "); - itoa(sideTone, c, 10); - strcat(b, c); - strcat(b, "hz"); - displayRawText(b, 0, 201, DISPLAY_CYAN, DISPLAY_NAVY); -} - - -void drawTx(){ - if (inTx) - displayText("TX", 280, 48, 37, 28, DISPLAY_BLACK, DISPLAY_ORANGE, DISPLAY_BLUE); - else - displayFillrect(280, 48, 37, 28, DISPLAY_NAVY); -} -void drawStatusbar(){ - drawCWStatus(); -} - -void guiUpdate(){ - -/* - if (doingCAT) - return; -*/ - // use the current frequency as the VFO frequency for the active VFO - displayClear(DISPLAY_NAVY); - - memset(vfoDisplay, 0, 12); - displayVFO(VFO_A); - checkCAT(); - memset(vfoDisplay, 0, 12); - displayVFO(VFO_B); - - checkCAT(); - displayRIT(); - checkCAT(); - - //force the display to refresh everything - //display all the buttons - for (int i = 0; i < MAX_BUTTONS; i++){ - struct Button b; - memcpy_P(&b, btn_set + i, sizeof(struct Button)); - btnDraw(&b); - checkCAT(); - } - drawStatusbar(); - checkCAT(); -} - - - -// this builds up the top line of the display with frequency and mode -void updateDisplay() { - displayVFO(vfoActive); -} - -int enc_prev_state = 3; - -/** - * The A7 And A6 are purely analog lines on the Arduino Nano - * These need to be pulled up externally using two 10 K resistors - * - * There are excellent pages on the Internet about how these encoders work - * and how they should be used. We have elected to use the simplest way - * to use these encoders without the complexity of interrupts etc to - * keep it understandable. - * - * The enc_state returns a two-bit number such that each bit reflects the current - * value of each of the two phases of the encoder - * - * The enc_read returns the number of net pulses counted over 50 msecs. - * If the puluses are -ve, they were anti-clockwise, if they are +ve, the - * were in the clockwise directions. Higher the pulses, greater the speed - * at which the enccoder was spun - */ - -byte enc_state (void) { - //Serial.print(digitalRead(ENC_A)); Serial.print(":");Serial.println(digitalRead(ENC_B)); - return (digitalRead(ENC_A) == 1 ? 1 : 0) + (digitalRead(ENC_B) == 1 ? 2: 0); -} - -int enc_read(void) { - int result = 0; - byte newState; - int enc_speed = 0; - - long stop_by = millis() + 200; - - while (millis() < stop_by) { // check if the previous state was stable - newState = enc_state(); // Get current state - -// if (newState != enc_prev_state) -// active_delay(20); - - if (enc_state() != newState || newState == enc_prev_state) - continue; - //these transitions point to the encoder being rotated anti-clockwise - if ((enc_prev_state == 0 && newState == 2) || - (enc_prev_state == 2 && newState == 3) || - (enc_prev_state == 3 && newState == 1) || - (enc_prev_state == 1 && newState == 0)){ - result--; - } - //these transitions point o the enccoder being rotated clockwise - if ((enc_prev_state == 0 && newState == 1) || - (enc_prev_state == 1 && newState == 3) || - (enc_prev_state == 3 && newState == 2) || - (enc_prev_state == 2 && newState == 0)){ - result++; - } - enc_prev_state = newState; // Record state for next pulse interpretation - enc_speed++; - active_delay(1); - } - //if (result) - // Serial.println(result); - return(result); -} - -void ritToggle(struct Button *b){ - if (ritOn == 0){ - ritEnable(frequency); - } - else - ritDisable(); - btnDraw(b); - displayRIT(); -} - -void splitToggle(struct Button *b){ - - if (splitOn) - splitOn = 0; - else - splitOn = 1; - - btnDraw(b); - - //disable rit as well - ritDisable(); - - struct Button b2; - getButton("RIT", &b2); - btnDraw(&b2); - - displayRIT(); - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_A); - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_B); -} - -void vfoReset(){ - Button b; - if (vfoActive = VFO_A) - vfoB = vfoA; - else - vfoA = vfoB; - - if (splitOn){ - getButton("SPL", &b); - splitToggle(&b); - } - - if (ritOn){ - getButton("RIT", &b); - ritToggle(&b); - } - - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_A); - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_B); - - saveVFOs(); -} - -void cwToggle(struct Button *b){ - if (cwMode == 0){ - cwMode = 1; - } - else - cwMode = 0; - - setFrequency(frequency); - btnDraw(b); -} - -void sidebandToggle(struct Button *b){ - if (!strcmp(b->text, "LSB")) - isUSB = 0; - else - isUSB = 1; - - struct Button e; - getButton("USB", &e); - btnDraw(&e); - getButton("LSB", &e); - btnDraw(&e); - - saveVFOs(); -} - - -void redrawVFOs(){ - - struct Button b; - ritDisable(); - getButton("RIT", &b); - btnDraw(&b); - displayRIT(); - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_A); - memset(vfoDisplay, 0, sizeof(vfoDisplay)); - displayVFO(VFO_B); - - //draw the lsb/usb buttons, the sidebands might have changed - getButton("LSB", &b); - btnDraw(&b); - getButton("USB", &b); - btnDraw(&b); -} - - -void switchBand(long bandfreq){ - long offset; - -// Serial.println(frequency); -// Serial.println(bandfreq); - if (3500000l <= frequency && frequency <= 4000000l) - offset = frequency - 3500000l; - else if (24800000l <= frequency && frequency <= 25000000l) - offset = frequency - 24800000l; - else - offset = frequency % 1000000l; - -// Serial.println(offset); - - setFrequency(bandfreq + offset); - updateDisplay(); - saveVFOs(); -} - -int setCwSpeed(){ - int knob = 0; - int wpm; - - wpm = 1200/cwSpeed; - - wpm = getValueByKnob(1, 100, 1, wpm, "CW: ", " WPM"); - - cwSpeed = 1200/wpm; - - EEPROM.put(CW_SPEED, cwSpeed); - active_delay(500); - drawStatusbar(); -// printLine2(""); -// updateDisplay(); -} - -void setCwTone(){ - int knob = 0; - int prev_sideTone; - - tone(CW_TONE, sideTone); - - //disable all clock 1 and clock 2 - while (digitalRead(PTT) == HIGH && !btnDown()) - { - knob = enc_read(); - - if (knob > 0 && sideTone < 2000) - sideTone += 10; - else if (knob < 0 && sideTone > 100 ) - sideTone -= 10; - else - continue; //don't update the frequency or the display - - tone(CW_TONE, sideTone); - itoa(sideTone, c, 10); - strcpy(b, "CW Tone: "); - strcat(b, c); - strcat(b, " Hz"); - drawCommandbar(b); - //printLine2(b); - - checkCAT(); - active_delay(20); - } - noTone(CW_TONE); - //save the setting - EEPROM.put(CW_SIDETONE, sideTone); - - displayFillrect(30,41,280, 32, DISPLAY_NAVY); - drawStatusbar(); -// printLine2(""); -// updateDisplay(); -} - -void doCommand(struct Button *b){ - - if (!strcmp(b->text, "RIT")) - ritToggle(b); - else if (!strcmp(b->text, "LSB")) - sidebandToggle(b); - else if (!strcmp(b->text, "USB")) - sidebandToggle(b); - else if (!strcmp(b->text, "CW")) - cwToggle(b); - else if (!strcmp(b->text, "SPL")) - splitToggle(b); - else if (!strcmp(b->text, "VFOA")){ - if (vfoActive == VFO_A) - fastTune(); - else - switchVFO(VFO_A); - } - else if (!strcmp(b->text, "VFOB")){ - if (vfoActive == VFO_B) - fastTune(); - else - switchVFO(VFO_B); - } - else if (!strcmp(b->text, "A=B")) - vfoReset(); - else if (!strcmp(b->text, "80")) - switchBand(3500000l); - else if (!strcmp(b->text, "40")) - switchBand(7000000l); - else if (!strcmp(b->text, "30")) - switchBand(10000000l); - else if (!strcmp(b->text, "20")) - switchBand(14000000l); - else if (!strcmp(b->text, "17")) - switchBand(18000000l); - else if (!strcmp(b->text, "15")) - switchBand(21000000l); - else if (!strcmp(b->text, "13")) - switchBand(24800000l); - else if (!strcmp(b->text, "10")) - switchBand(28000000l); - else if (!strcmp(b->text, "FRQ")) - enterFreq(); - else if (!strcmp(b->text, "WPM")) - setCwSpeed(); - else if (!strcmp(b->text, "TON")) - setCwTone(); -} - -void checkTouch(){ - - if (!readTouch()) - return; - - while(readTouch()) - checkCAT(); - scaleTouch(&ts_point); - - /* //debug code - Serial.print(ts_point.x); Serial.print(' ');Serial.println(ts_point.y); - */ - int total = sizeof(btn_set)/sizeof(struct Button); - for (int i = 0; i < MAX_BUTTONS; i++){ - struct Button b; - memcpy_P(&b, btn_set + i, sizeof(struct Button)); - - int x2 = b.x + b.w; - int y2 = b.y + b.h; - - if (b.x < ts_point.x && ts_point.x < x2 && - b.y < ts_point.y && ts_point.y < y2) - doCommand(&b); - } -} - -//returns true if the button is pressed -int btnDown(){ - if (digitalRead(FBUTTON) == HIGH) - return 0; - else - return 1; -} - - -void drawFocus(int ibtn, int color){ - struct Button b; - - memcpy_P(&b, btn_set + ibtn, sizeof(struct Button)); - displayRect(b.x, b.y, b.w, b.h, color); -} - -void doCommands(){ - int select=0, i, prevButton, btnState; - - //wait for the button to be raised up - while(btnDown()) - active_delay(50); - active_delay(50); //debounce - - menuOn = 2; - - while (menuOn){ - - //check if the knob's button was pressed - btnState = btnDown(); - if (btnState){ - struct Button b; - memcpy_P(&b, btn_set + select/10, sizeof(struct Button)); - - doCommand(&b); - - //unfocus the buttons - drawFocus(select, DISPLAY_BLUE); - if (vfoActive == VFO_A) - drawFocus(0, DISPLAY_WHITE); - else - drawFocus(1, DISPLAY_WHITE); - - //wait for the button to be up and debounce - while(btnDown()) - active_delay(100); - active_delay(500); - return; - } - - i = enc_read(); - - if (i == 0){ - active_delay(50); - continue; - } - - if (i > 0){ - if (select + i < MAX_BUTTONS * 10) - select += i; - } - if (i < 0 && select + i >= 0) - select += i; //caught ya, i is already -ve here, so you add it - - if (prevButton == select / 10) - continue; - - //we are on a new button - drawFocus(prevButton, DISPLAY_BLUE); - drawFocus(select/10, DISPLAY_WHITE); - prevButton = select/10; - } -// guiUpdate(); - - //debounce the button - while(btnDown()) - active_delay(50); - active_delay(50); - - checkCAT(); -} - diff --git a/ubitx_v6.3.1_code.ino b/ubitx_v6.3.1_code.ino deleted file mode 100644 index 4d162c4..0000000 --- a/ubitx_v6.3.1_code.ino +++ /dev/null @@ -1,843 +0,0 @@ - /** - * This source file is under General Public License version 3. - * - * This verision uses a built-in Si5351 library - * Most source code are meant to be understood by the compilers and the computers. - * Code that has to be hackable needs to be well understood and properly documented. - * Donald Knuth coined the term Literate Programming to indicate code that is written be - * easily read and understood. - * - * The Raduino is a small board that includes the Arduin Nano, a TFT display and - * an Si5351a frequency synthesizer. This board is manufactured by HF Signals Electronics Pvt Ltd - * - * To learn more about Arduino you may visit www.arduino.cc. - * - * The Arduino works by starts executing the code in a function called setup() and then it - * repeatedly keeps calling loop() forever. All the initialization code is kept in setup() - * and code to continuously sense the tuning knob, the function button, transmit/receive, - * etc is all in the loop() function. If you wish to study the code top down, then scroll - * to the bottom of this file and read your way up. - * - * Below are the libraries to be included for building the Raduino - * The EEPROM library is used to store settings like the frequency memory, caliberation data, etc. - * - * The main chip which generates upto three oscillators of various frequencies in the - * Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet - * from www.silabs.com although, strictly speaking it is not a requirment to understand this code. - * Instead, you can look up the Si5351 library written by xxx, yyy. You can download and - * install it from www.url.com to complile this file. - * The Wire.h library is used to talk to the Si5351 and we also declare an instance of - * Si5351 object to control the clocks. - */ -#include -#include -#include "ubitx.h" -#include "nano_gui.h" - -/** - The main chip which generates upto three oscillators of various frequencies in the - Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet - from www.silabs.com although, strictly speaking it is not a requirment to understand this code. - - We no longer use the standard SI5351 library because of its huge overhead due to many unused - features consuming a lot of program space. Instead of depending on an external library we now use - Jerry Gaffke's, KE7ER, lightweight standalone mimimalist "si5351bx" routines (see further down the - code). Here are some defines and declarations used by Jerry's routines: -*/ - - -/** - * We need to carefully pick assignment of pin for various purposes. - * There are two sets of completely programmable pins on the Raduino. - * First, on the top of the board, in line with the LCD connector is an 8-pin connector - * that is largely meant for analog inputs and front-panel control. It has a regulated 5v output, - * ground and six pins. Each of these six pins can be individually programmed - * either as an analog input, a digital input or a digital output. - * The pins are assigned as follows (left to right, display facing you): - * Pin 1 (Violet), A7, SPARE - * Pin 2 (Blue), A6, KEYER (DATA) - * Pin 3 (Green), +5v - * Pin 4 (Yellow), Gnd - * Pin 5 (Orange), A3, PTT - * Pin 6 (Red), A2, F BUTTON - * Pin 7 (Brown), A1, ENC B - * Pin 8 (Black), A0, ENC A - *Note: A5, A4 are wired to the Si5351 as I2C interface - * * - * Though, this can be assigned anyway, for this application of the Arduino, we will make the following - * assignment - * A2 will connect to the PTT line, which is the usually a part of the mic connector - * A3 is connected to a push button that can momentarily ground this line. This will be used for RIT/Bandswitching, etc. - * A6 is to implement a keyer, it is reserved and not yet implemented - * A7 is connected to a center pin of good quality 100K or 10K linear potentiometer with the two other ends connected to - * ground and +5v lines available on the connector. This implments the tuning mechanism - */ - -#define ENC_A (A0) -#define ENC_B (A1) -#define FBUTTON (A2) -#define PTT (A3) -#define ANALOG_KEYER (A6) -#define ANALOG_SPARE (A7) - - -/** pin assignments -14 T_IRQ 2 std changed -13 T_DOUT (parallel to SOD/MOSI, pin 9 of display) -12 T_DIN (parallel to SDI/MISO, pin 6 of display) -11 T_CS 9 (we need to specify this) -10 T_CLK (parallel to SCK, pin 7 of display) -9 SDO(MSIO) 12 12 (spi) -8 LED A0 8 (not needed, permanently on +3.3v) (resistor from 5v, -7 SCK 13 13 (spi) -6 SDI 11 11 (spi) -5 D/C A3 7 (changable) -4 RESET A4 9 (not needed, permanently +5v) -3 CS A5 10 (changable) -2 GND GND -1 VCC VCC - -The model is called tjctm24028-spi -it uses an ILI9341 display controller and an XPT2046 touch controller. -*/ - -#define TFT_DC 9 -#define TFT_CS 10 - -//#define TIRQ_PIN 2 -#define CS_PIN 8 - -// MOSI=11, MISO=12, SCK=13 - -//XPT2046_Touchscreen ts(CS_PIN); - -//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC); - -/** - * The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory. - * We have to be very careful with variables that are declared inside the functions as they are - * created in a memory region called the stack. The stack has just a few bytes of space on the Arduino - * if you declare large strings inside functions, they can easily exceed the capacity of the stack - * and mess up your programs. - * We circumvent this by declaring a few global buffers as kitchen counters where we can - * slice and dice our strings. These strings are mostly used to control the display or handle - * the input and output from the USB port. We must keep a count of the bytes used while reading - * the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable. - */ -char c[30], b[30]; -char printBuff[2][20]; //mirrors what is showing on the two lines of the display -int count = 0; //to generally count ticks, loops, etc - -/** - * The second set of 16 pins on the Raduino's bottom connector are have the three clock outputs and the digital lines to control the rig. - * This assignment is as follows : - * Pin 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - * GND +5V CLK0 GND GND CLK1 GND GND CLK2 GND D2 D3 D4 D5 D6 D7 - * These too are flexible with what you may do with them, for the Raduino, we use them to : - * - TX_RX line : Switches between Transmit and Receive after sensing the PTT or the morse keyer - * - CW_KEY line : turns on the carrier for CW - */ - -#define TX_RX (7) -#define CW_TONE (6) -#define TX_LPF_A (5) -#define TX_LPF_B (4) -#define TX_LPF_C (3) -#define CW_KEY (2) - -/** - * These are the indices where these user changable settinngs are stored in the EEPROM - */ -#define MASTER_CAL 0 -#define LSB_CAL 4 -#define USB_CAL 8 -#define SIDE_TONE 12 -//these are ids of the vfos as well as their offset into the eeprom storage, don't change these 'magic' values -#define VFO_A 16 -#define VFO_B 20 -#define CW_SIDETONE 24 -#define CW_SPEED 28 -// the screen calibration parameters : int slope_x=104, slope_y=137, offset_x=28, offset_y=29; -#define SLOPE_X 32 -#define SLOPE_Y 36 -#define OFFSET_X 40 -#define OFFSET_Y 44 -#define CW_DELAYTIME 48 - -//These are defines for the new features back-ported from KD8CEC's software -//these start from beyond 256 as Ian, KD8CEC has kept the first 256 bytes free for the base version -#define VFO_A_MODE 256 // 2: LSB, 3: USB -#define VFO_B_MODE 257 - -//values that are stroed for the VFO modes -#define VFO_MODE_LSB 2 -#define VFO_MODE_USB 3 - -// handkey, iambic a, iambic b : 0,1,2f -#define CW_KEY_TYPE 358 - -/** - * The uBITX is an upconnversion transceiver. The first IF is at 45 MHz. - * The first IF frequency is not exactly at 45 Mhz but about 5 khz lower, - * this shift is due to the loading on the 45 Mhz crystal filter by the matching - * L-network used on it's either sides. - * The first oscillator works between 48 Mhz and 75 MHz. The signal is subtracted - * from the first oscillator to arriive at 45 Mhz IF. Thus, it is inverted : LSB becomes USB - * and USB becomes LSB. - * The second IF of 12 Mhz has a ladder crystal filter. If a second oscillator is used at - * 57 Mhz, the signal is subtracted FROM the oscillator, inverting a second time, and arrives - * at the 12 Mhz ladder filter thus doouble inversion, keeps the sidebands as they originally were. - * If the second oscillator is at 33 Mhz, the oscilaltor is subtracated from the signal, - * thus keeping the signal's sidebands inverted. The USB will become LSB. - * We use this technique to switch sidebands. This is to avoid placing the lsbCarrier close to - * 12 MHz where its fifth harmonic beats with the arduino's 16 Mhz oscillator's fourth harmonic - */ - - -#define INIT_USB_FREQ (11059200l) -// limits the tuning and working range of the ubitx between 3 MHz and 30 MHz -#define LOWEST_FREQ (100000l) -#define HIGHEST_FREQ (30000000l) - -//we directly generate the CW by programmin the Si5351 to the cw tx frequency, hence, both are different modes -//these are the parameter passed to startTx -#define TX_SSB 0 -#define TX_CW 1 - -char ritOn = 0; -char vfoActive = VFO_A; -int8_t meter_reading = 0; // a -1 on meter makes it invisible -unsigned long vfoA=7150000L, vfoB=14200000L, sideTone=800, usbCarrier; -char isUsbVfoA=0, isUsbVfoB=1; -unsigned long frequency, ritRxFrequency, ritTxFrequency; //frequency is the current frequency on the dial -unsigned long firstIF = 45005000L; - -// if cwMode is flipped on, the rx frequency is tuned down by sidetone hz instead of being zerobeat -int cwMode = 0; - - -//these are variables that control the keyer behaviour -int cwSpeed = 100; //this is actuall the dot period in milliseconds -extern int32_t calibration; -int cwDelayTime = 60; -bool Iambic_Key = true; -#define IAMBICB 0x10 // 0 for Iambic A, 1 for Iambic B -unsigned char keyerControl = IAMBICB; -//during CAT commands, we will freeeze the display until CAT is disengaged -unsigned char doingCAT = 0; - - -/** - * Raduino needs to keep track of current state of the transceiver. These are a few variables that do it - */ -boolean txCAT = false; //turned on if the transmitting due to a CAT command -char inTx = 0; //it is set to 1 if in transmit mode (whatever the reason : cw, ptt or cat) -int splitOn = 0; //working split, uses VFO B as the transmit frequency -char keyDown = 0; //in cw mode, denotes the carrier is being transmitted -char isUSB = 0; //upper sideband was selected, this is reset to the default for the - //frequency when it crosses the frequency border of 10 MHz -byte menuOn = 0; //set to 1 when the menu is being displayed, if a menu item sets it to zero, the menu is exited -unsigned long cwTimeout = 0; //milliseconds to go before the cw transmit line is released and the radio goes back to rx mode -unsigned long dbgCount = 0; //not used now -unsigned char txFilter = 0; //which of the four transmit filters are in use -boolean modeCalibrate = false;//this mode of menus shows extended menus to calibrate the oscillators and choose the proper - //beat frequency - -/** - * Below are the basic functions that control the uBitx. Understanding the functions before - * you start hacking around - */ - -/** - * Our own delay. During any delay, the raduino should still be processing a few times. - */ - -void active_delay(int delay_by){ - unsigned long timeStart = millis(); - while (millis() - timeStart <= (unsigned long)delay_by) { - delay(10); - //Background Work - checkCAT(); - } -} - -void saveVFOs(){ - - if (vfoActive == VFO_A) - EEPROM.put(VFO_A, frequency); - else - EEPROM.put(VFO_A, vfoA); - - if (isUsbVfoA) - EEPROM.put(VFO_A_MODE, VFO_MODE_USB); - else - EEPROM.put(VFO_A_MODE, VFO_MODE_LSB); - - if (vfoActive == VFO_B) - EEPROM.put(VFO_B, frequency); - else - EEPROM.put(VFO_B, vfoB); - - if (isUsbVfoB) - EEPROM.put(VFO_B_MODE, VFO_MODE_USB); - else - EEPROM.put(VFO_B_MODE, VFO_MODE_LSB); -} - -/** - * Select the properly tx harmonic filters - * The four harmonic filters use only three relays - * the four LPFs cover 30-21 Mhz, 18 - 14 Mhz, 7-10 MHz and 3.5 to 5 Mhz - * Briefly, it works like this, - * - When KT1 is OFF, the 'off' position routes the PA output through the 30 MHz LPF - * - When KT1 is ON, it routes the PA output to KT2. Which is why you will see that - * the KT1 is on for the three other cases. - * - When the KT1 is ON and KT2 is off, the off position of KT2 routes the PA output - * to 18 MHz LPF (That also works for 14 Mhz) - * - When KT1 is On, KT2 is On, it routes the PA output to KT3 - * - KT3, when switched on selects the 7-10 Mhz filter - * - KT3 when switched off selects the 3.5-5 Mhz filter - * See the circuit to understand this - */ - -void setTXFilters(unsigned long freq){ - - if (freq > 21000000L){ // the default filter is with 35 MHz cut-off - digitalWrite(TX_LPF_A, 0); - digitalWrite(TX_LPF_B, 0); - digitalWrite(TX_LPF_C, 0); - } - else if (freq >= 14000000L){ //thrown the KT1 relay on, the 30 MHz LPF is bypassed and the 14-18 MHz LPF is allowd to go through - digitalWrite(TX_LPF_A, 1); - digitalWrite(TX_LPF_B, 0); - digitalWrite(TX_LPF_C, 0); - } - else if (freq > 7000000L){ - digitalWrite(TX_LPF_A, 0); - digitalWrite(TX_LPF_B, 1); - digitalWrite(TX_LPF_C, 0); - } - else { - digitalWrite(TX_LPF_A, 0); - digitalWrite(TX_LPF_B, 0); - digitalWrite(TX_LPF_C, 1); - } -} - - -void setTXFilters_v5(unsigned long freq){ - - if (freq > 21000000L){ // the default filter is with 35 MHz cut-off - digitalWrite(TX_LPF_A, 0); - digitalWrite(TX_LPF_B, 0); - digitalWrite(TX_LPF_C, 0); - } - else if (freq >= 14000000L){ //thrown the KT1 relay on, the 30 MHz LPF is bypassed and the 14-18 MHz LPF is allowd to go through - digitalWrite(TX_LPF_A, 1); - digitalWrite(TX_LPF_B, 0); - digitalWrite(TX_LPF_C, 0); - } - else if (freq > 7000000L){ - digitalWrite(TX_LPF_A, 0); - digitalWrite(TX_LPF_B, 1); - digitalWrite(TX_LPF_C, 0); - } - else { - digitalWrite(TX_LPF_A, 0); - digitalWrite(TX_LPF_B, 0); - digitalWrite(TX_LPF_C, 1); - } -} - - -/** - * This is the most frequently called function that configures the - * radio to a particular frequeny, sideband and sets up the transmit filters - * - * The transmit filter relays are powered up only during the tx so they dont - * draw any current during rx. - * - * The carrier oscillator of the detector/modulator is permanently fixed at - * uppper sideband. The sideband selection is done by placing the second oscillator - * either 12 Mhz below or above the 45 Mhz signal thereby inverting the sidebands - * through mixing of the second local oscillator. - */ - -void setFrequency(unsigned long f){ - uint64_t osc_f, firstOscillator, secondOscillator; - - setTXFilters(f); - -/* - if (isUSB){ - si5351bx_setfreq(2, firstIF + f); - si5351bx_setfreq(1, firstIF + usbCarrier); - } - else{ - si5351bx_setfreq(2, firstIF + f); - si5351bx_setfreq(1, firstIF - usbCarrier); - } -*/ - //alternative to reduce the intermod spur - if (isUSB){ - if (cwMode) - si5351bx_setfreq(2, firstIF + f + sideTone); - else - si5351bx_setfreq(2, firstIF + f); - si5351bx_setfreq(1, firstIF + usbCarrier); - } - else{ - if (cwMode) - si5351bx_setfreq(2, firstIF + f + sideTone); - else - si5351bx_setfreq(2, firstIF + f); - si5351bx_setfreq(1, firstIF - usbCarrier); - } - - frequency = f; -} - -/** - * startTx is called by the PTT, cw keyer and CAT protocol to - * put the uBitx in tx mode. It takes care of rit settings, sideband settings - * Note: In cw mode, doesnt key the radio, only puts it in tx mode - * CW offest is calculated as lower than the operating frequency when in LSB mode, and vice versa in USB mode - */ - -void startTx(byte txMode){ - unsigned long tx_freq = 0; - - digitalWrite(TX_RX, 1); - inTx = 1; - - if (ritOn){ - //save the current as the rx frequency - ritRxFrequency = frequency; - setFrequency(ritTxFrequency); - } - else - { - if (splitOn == 1) { - if (vfoActive == VFO_B) { - vfoActive = VFO_A; - isUSB = isUsbVfoA; - frequency = vfoA; - } - else if (vfoActive == VFO_A){ - vfoActive = VFO_B; - frequency = vfoB; - isUSB = isUsbVfoB; - } - } - setFrequency(frequency); - } - - if (txMode == TX_CW){ - digitalWrite(TX_RX, 0); - - //turn off the second local oscillator and the bfo - si5351bx_setfreq(0, 0); - si5351bx_setfreq(1, 0); - - //shif the first oscillator to the tx frequency directly - //the key up and key down will toggle the carrier unbalancing - //the exact cw frequency is the tuned frequency + sidetone - if (isUSB) - si5351bx_setfreq(2, frequency + sideTone); - else - si5351bx_setfreq(2, frequency - sideTone); - - delay(20); - digitalWrite(TX_RX, 1); - } - drawTx(); - //updateDisplay(); -} - -void stopTx(){ - inTx = 0; - - digitalWrite(TX_RX, 0); //turn off the tx - si5351bx_setfreq(0, usbCarrier); //set back the cardrier oscillator anyway, cw tx switches it off - - if (ritOn) - setFrequency(ritRxFrequency); - else{ - if (splitOn == 1) { - //vfo Change - if (vfoActive == VFO_B){ - vfoActive = VFO_A; - frequency = vfoA; - isUSB = isUsbVfoA; - } - else if (vfoActive == VFO_A){ - vfoActive = VFO_B; - frequency = vfoB; - isUSB = isUsbVfoB; - } - } - setFrequency(frequency); - } - //updateDisplay(); - drawTx(); -} - -/** - * ritEnable is called with a frequency parameter that determines - * what the tx frequency will be - */ -void ritEnable(unsigned long f){ - ritOn = 1; - //save the non-rit frequency back into the VFO memory - //as RIT is a temporary shift, this is not saved to EEPROM - ritTxFrequency = f; -} - -// this is called by the RIT menu routine -void ritDisable(){ - if (ritOn){ - ritOn = 0; - setFrequency(ritTxFrequency); - updateDisplay(); - } -} - -/** - * Basic User Interface Routines. These check the front panel for any activity - */ - -/** - * The PTT is checked only if we are not already in a cw transmit session - * If the PTT is pressed, we shift to the ritbase if the rit was on - * flip the T/R line to T and update the display to denote transmission - */ - -void checkPTT(){ - //we don't check for ptt when transmitting cw - if (cwTimeout > 0) - return; - - if (digitalRead(PTT) == 0 && inTx == 0){ - startTx(TX_SSB); - active_delay(50); //debounce the PTT - } - - if (digitalRead(PTT) == 1 && inTx == 1) - stopTx(); -} - -//check if the encoder button was pressed -void checkButton(){ - int i, t1, t2, knob, new_knob; - - //only if the button is pressed - if (!btnDown()) - return; - active_delay(50); - if (!btnDown()) //debounce - return; - - //disengage any CAT work - doingCAT = 0; - - int downTime = 0; - while(btnDown()){ - active_delay(10); - downTime++; - if (downTime > 300){ - doSetup2(); - return; - } - } - active_delay(100); - - - doCommands(); - //wait for the button to go up again - while(btnDown()) - active_delay(10); - active_delay(50);//debounce -} - -void switchVFO(int vfoSelect){ - if (vfoSelect == VFO_A){ - if (vfoActive == VFO_B){ - vfoB = frequency; - isUsbVfoB = isUSB; - EEPROM.put(VFO_B, frequency); - if (isUsbVfoB) - EEPROM.put(VFO_B_MODE, VFO_MODE_USB); - else - EEPROM.put(VFO_B_MODE, VFO_MODE_LSB); - } - vfoActive = VFO_A; -// printLine2("Selected VFO A "); - frequency = vfoA; - isUSB = isUsbVfoA; - } - else { - if (vfoActive == VFO_A){ - vfoA = frequency; - isUsbVfoA = isUSB; - EEPROM.put(VFO_A, frequency); - if (isUsbVfoA) - EEPROM.put(VFO_A_MODE, VFO_MODE_USB); - else - EEPROM.put(VFO_A_MODE, VFO_MODE_LSB); - } - vfoActive = VFO_B; -// printLine2("Selected VFO B "); - frequency = vfoB; - isUSB = isUsbVfoB; - } - - setFrequency(frequency); - redrawVFOs(); - saveVFOs(); -} - -/** - * The tuning jumps by 50 Hz on each step when you tune slowly - * As you spin the encoder faster, the jump size also increases - * This way, you can quickly move to another band by just spinning the - * tuning knob - */ - -void doTuning(){ - int s; - static unsigned long prev_freq; - static unsigned long nextFrequencyUpdate = 0; - - unsigned long now = millis(); - - if (now >= nextFrequencyUpdate && prev_freq != frequency){ - updateDisplay(); - nextFrequencyUpdate = now + 500; - prev_freq = frequency; - } - - s = enc_read(); - if (!s) - return; - - doingCAT = 0; // go back to manual mode if you were doing CAT - prev_freq = frequency; - - - if (s > 10) - frequency += 200l * s; - else if (s > 5) - frequency += 100l * s; - else if (s > 0) - frequency += 50l * s; - else if (s < -10) - frequency += 200l * s; - else if (s < -5) - frequency += 100l * s; - else if (s < 0) - frequency += 50l * s; - - if (prev_freq < 10000000l && frequency > 10000000l) - isUSB = true; - - if (prev_freq > 10000000l && frequency < 10000000l) - isUSB = false; - - setFrequency(frequency); -} - - -/** - * RIT only steps back and forth by 100 hz at a time - */ -void doRIT(){ - unsigned long newFreq; - - int knob = enc_read(); - unsigned long old_freq = frequency; - - if (knob < 0) - frequency -= 100l; - else if (knob > 0) - frequency += 100; - - if (old_freq != frequency){ - setFrequency(frequency); - updateDisplay(); - } -} - -/** - * The settings are read from EEPROM. The first time around, the values may not be - * present or out of range, in this case, some intelligent defaults are copied into the - * variables. - */ -void initSettings(){ - byte x; - //read the settings from the eeprom and restore them - //if the readings are off, then set defaults - EEPROM.get(MASTER_CAL, calibration); - EEPROM.get(USB_CAL, usbCarrier); - EEPROM.get(VFO_A, vfoA); - EEPROM.get(VFO_B, vfoB); - EEPROM.get(CW_SIDETONE, sideTone); - EEPROM.get(CW_SPEED, cwSpeed); - EEPROM.get(CW_DELAYTIME, cwDelayTime); - -// the screen calibration parameters : int slope_x=104, slope_y=137, offset_x=28, offset_y=29; - - if (usbCarrier > 11060000l || usbCarrier < 11048000l) - usbCarrier = 11052000l; - if (vfoA > 35000000l || 3500000l > vfoA) - vfoA = 7150000l; - if (vfoB > 35000000l || 3500000l > vfoB) - vfoB = 14150000l; - if (sideTone < 100 || 2000 < sideTone) - sideTone = 800; - if (cwSpeed < 10 || 1000 < cwSpeed) - cwSpeed = 100; - if (cwDelayTime < 10 || cwDelayTime > 100) - cwDelayTime = 50; - - /* - * The VFO modes are read in as either 2 (USB) or 3(LSB), 0, the default - * is taken as 'uninitialized - */ - - EEPROM.get(VFO_A_MODE, x); - - switch(x){ - case VFO_MODE_USB: - isUsbVfoA = 1; - break; - case VFO_MODE_LSB: - isUsbVfoA = 0; - break; - default: - if (vfoA > 10000000l) - isUsbVfoA = 1; - else - isUsbVfoA = 0; - } - - EEPROM.get(VFO_B_MODE, x); - switch(x){ - case VFO_MODE_USB: - isUsbVfoB = 1; - break; - case VFO_MODE_LSB: - isUsbVfoB = 0; - break; - default: - if (vfoA > 10000000l) - isUsbVfoB = 1; - else - isUsbVfoB = 0; - } - - //set the current mode - isUSB = isUsbVfoA; - - /* - * The keyer type splits into two variables - */ - EEPROM.get(CW_KEY_TYPE, x); - - if (x == 0) - Iambic_Key = false; - else if (x == 1){ - Iambic_Key = true; - keyerControl &= ~IAMBICB; - } - else if (x == 2){ - Iambic_Key = true; - keyerControl |= IAMBICB; - } - -} - -void initPorts(){ - - analogReference(DEFAULT); - - //?? - pinMode(ENC_A, INPUT_PULLUP); - pinMode(ENC_B, INPUT_PULLUP); - pinMode(FBUTTON, INPUT_PULLUP); - - //configure the function button to use the external pull-up -// pinMode(FBUTTON, INPUT); -// digitalWrite(FBUTTON, HIGH); - - pinMode(PTT, INPUT_PULLUP); -// pinMode(ANALOG_KEYER, INPUT_PULLUP); - - pinMode(CW_TONE, OUTPUT); - digitalWrite(CW_TONE, 0); - - pinMode(TX_RX,OUTPUT); - digitalWrite(TX_RX, 0); - - pinMode(TX_LPF_A, OUTPUT); - pinMode(TX_LPF_B, OUTPUT); - pinMode(TX_LPF_C, OUTPUT); - digitalWrite(TX_LPF_A, 0); - digitalWrite(TX_LPF_B, 0); - digitalWrite(TX_LPF_C, 0); - - pinMode(CW_KEY, OUTPUT); - digitalWrite(CW_KEY, 0); -} - -void setup() -{ - Serial.begin(38400); - Serial.flush(); - - displayInit(); - initSettings(); - initPorts(); - initOscillators(); - frequency = vfoA; - setFrequency(vfoA); - - if (btnDown()){ - setupTouch(); - isUSB = 1; - setFrequency(10000000l); - setupFreq(); - isUSB = 0; - setFrequency(7100000l); - setupBFO(); - } - guiUpdate(); - -} - - -/** - * The loop checks for keydown, ptt, function button and tuning. - */ - -byte flasher = 0; -boolean wastouched = false; - -void loop(){ - - if (cwMode) - cwKeyer(); - else if (!txCAT) - checkPTT(); - - checkButton(); - //tune only when not tranmsitting - if (!inTx){ - if (ritOn) - doRIT(); - else - doTuning(); - checkTouch(); - } - - checkCAT(); -} diff --git a/ubitxv6.ino b/ubitxv6.ino new file mode 100644 index 0000000..38f4da4 --- /dev/null +++ b/ubitxv6.ino @@ -0,0 +1,185 @@ + /** + * This source file is under General Public License version 3. + * + * This verision uses a built-in Si5351 library + * Most source code are meant to be understood by the compilers and the computers. + * Code that has to be hackable needs to be well understood and properly documented. + * Donald Knuth coined the term Literate Programming to indicate code that is written be + * easily read and understood. + * + * The Raduino is a small board that includes the Arduin Nano, a TFT display and + * an Si5351a frequency synthesizer. This board is manufactured by HF Signals Electronics Pvt Ltd + * + * To learn more about Arduino you may visit www.arduino.cc. + * + * The Arduino works by starts executing the code in a function called setup() and then it + * repeatedly keeps calling loop() forever. All the initialization code is kept in setup() + * and code to continuously sense the tuning knob, the function button, transmit/receive, + * etc is all in the loop() function. If you wish to study the code top down, then scroll + * to the bottom of this file and read your way up. + * + * Below are the libraries to be included for building the Raduino + * The EEPROM library is used to store settings like the frequency memory, caliberation data, etc. + * + * The main chip which generates upto three oscillators of various frequencies in the + * Raduino is the Si5351a. To learn more about Si5351a you can download the datasheet + * from www.silabs.com although, strictly speaking it is not a requirment to understand this code. + * Instead, you can look up the Si5351 library written by xxx, yyy. You can download and + * install it from www.url.com to complile this file. + * The Wire.h library is used to talk to the Si5351 and we also declare an instance of + * Si5351 object to control the clocks. + */ +#include +#include "encoder.h" +#include "menu.h" +#include "menu_main.h" +#include "morse.h" +#include "pin_definitions.h" +#include "push_button.h" +#include "nano_gui.h" +#include "settings.h" +#include "setup.h" +#include "si5351.h" +#include "touch.h" +#include "tuner.h" +#include "ui_touch.h" + +/** + * The Arduino, unlike C/C++ on a regular computer with gigabytes of RAM, has very little memory. + * We have to be very careful with variables that are declared inside the functions as they are + * created in a memory region called the stack. The stack has just a few bytes of space on the Arduino + * if you declare large strings inside functions, they can easily exceed the capacity of the stack + * and mess up your programs. + * We circumvent this by declaring a few global buffers as kitchen counters where we can + * slice and dice our strings. These strings are mostly used to control the display or handle + * the input and output from the USB port. We must keep a count of the bytes used while reading + * the serial port as we can easily run out of buffer space. This is done in the serial_in_count variable. + */ +char b[128]; +char c[30]; + +//during CAT commands, we will freeeze the display until CAT is disengaged +unsigned char doingCAT = 0; + +/** + * Basic User Interface Routines. These check the front panel for any activity + */ + +/** + * The PTT is checked only if we are not already in a cw transmit session + * If the PTT is pressed, we shift to the ritbase if the rit was on + * flip the T/R line to T and update the display to denote transmission + */ + +void checkPTT(){ + //we don't check for ptt when transmitting cw + if (globalSettings.cwExpirationTimeMs > 0){ + return; + } + + if(digitalRead(PIN_PTT) == 0 && !globalSettings.txActive){ + startTx(TuningMode_e::TUNE_SSB); + delay(50); //debounce the PTT + } + + if (digitalRead(PIN_PTT) == 1 && globalSettings.txActive) + stopTx(); +} + +/** + * The settings are read from EEPROM. The first time around, the values may not be + * present or out of range, in this case, some intelligent defaults are copied into the + * variables. + */ +void initSettings(){ + LoadDefaultSettings(); + LoadSettingsFromEeprom(); +} + +void initPorts(){ + + analogReference(DEFAULT); + + //?? + pinMode(PIN_ENC_A, INPUT_PULLUP); + pinMode(PIN_ENC_B, INPUT_PULLUP); + pinMode(PIN_ENC_PUSH_BUTTON, INPUT_PULLUP); + enc_setup(); + + //configure the function button to use the external pull-up +// pinMode(PIN_ENC_PUSH_BUTTON, INPUT); +// digitalWrite(PIN_ENC_PUSH_BUTTON, HIGH); + + pinMode(PIN_PTT, INPUT_PULLUP); +// pinMode(PIN_ANALOG_KEYER, INPUT_PULLUP); + + pinMode(PIN_CW_TONE, OUTPUT); + digitalWrite(PIN_CW_TONE, 0); + + pinMode(PIN_TX_RXn,OUTPUT); + digitalWrite(PIN_TX_RXn, 0); + + pinMode(PIN_TX_LPF_A, OUTPUT); + pinMode(PIN_TX_LPF_B, OUTPUT); + pinMode(PIN_TX_LPF_C, OUTPUT); + digitalWrite(PIN_TX_LPF_A, 0); + digitalWrite(PIN_TX_LPF_B, 0); + digitalWrite(PIN_TX_LPF_C, 0); + + pinMode(PIN_CW_KEY, OUTPUT); + digitalWrite(PIN_CW_KEY, 0); +} + +void setup() +{ + Serial.begin(38400); + Serial.flush(); + + initSettings(); + displayInit(); + initTouch(); + initPorts(); + initOscillators(); + setFrequency(globalSettings.vfoA.frequency); + + //Run initial calibration routine if button is pressed during power up + if(ButtonPress_e::NotPressed != CheckTunerButton()){ + LoadDefaultSettings(); + setupTouch(); + SetActiveVfoMode(VfoMode_e::VFO_MODE_USB); + setFrequency(10000000L); + runLocalOscSetting(); + SetActiveVfoMode(VfoMode_e::VFO_MODE_LSB); + setFrequency(7100000L); + runBfoSetting(); + } + + rootMenu->initMenu(); +} + + +/** + * The loop checks for keydown, ptt, function button and tuning. + */ + +void loop(){ + if(TuningMode_e::TUNE_CW == globalSettings.tuningMode){ + cwKeyer(); + } + else if(!globalSettings.txCatActive){ + checkPTT(); + } + + checkCAT(); + + if(globalSettings.txActive){ + //Don't run menus when transmitting + return; + } + + ButtonPress_e tuner_button = CheckTunerButton(); + Point touch_point; + ButtonPress_e touch_button = checkTouch(&touch_point); + int16_t knob = enc_read(); + runActiveMenu(tuner_button,touch_button,touch_point,knob); +} diff --git a/ui_touch.cpp b/ui_touch.cpp new file mode 100644 index 0000000..67e07d3 --- /dev/null +++ b/ui_touch.cpp @@ -0,0 +1,31 @@ +#include "ui_touch.h" + +#include //delay + +#include "button_timing.h" +#include "touch.h" + +ButtonPress_e checkTouch(Point *const touch_point_out){ + if (!readTouch(touch_point_out)){ + return ButtonPress_e::NotPressed; + } + delay(DEBOUNCE_DELAY_MS); + if (!readTouch(touch_point_out)){//debounce + return ButtonPress_e::NotPressed; + } + + uint16_t down_time = 0; + while(readTouch(touch_point_out) && (down_time < LONG_PRESS_TIME_MS)){ + delay(LONG_PRESS_POLL_TIME_MS); + down_time += LONG_PRESS_POLL_TIME_MS; + } + + scaleTouch(touch_point_out); + + if(down_time < LONG_PRESS_TIME_MS){ + return ButtonPress_e::ShortPress; + } + else{ + return ButtonPress_e::LongPress; + } +} diff --git a/ui_touch.h b/ui_touch.h new file mode 100644 index 0000000..386adac --- /dev/null +++ b/ui_touch.h @@ -0,0 +1,6 @@ +#pragma once + +#include "menu.h" +#include "point.h" + +ButtonPress_e checkTouch(Point *const touch_point_out); diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..de6022f --- /dev/null +++ b/utils.cpp @@ -0,0 +1,44 @@ +#include "utils.h" + +#include +#include //ultoa +#include //memset, strlen +#include //F() + +/* + * Formats the frequency given + */ +void formatFreq(uint32_t freq, char* buff, uint16_t buff_size, uint8_t fixed_width) +{ + memset(buff, 0, buff_size); + + ultoa(freq, buff, 10); + uint8_t num_digits = strlen(buff); + const uint8_t num_spacers = (num_digits-1) / 3; + const uint8_t num_leading_digits_raw = num_digits % 3; + const uint8_t num_leading_digits = (0 == num_leading_digits_raw) ? 3 : num_leading_digits_raw; + + if(0 < fixed_width){ + while(0 < fixed_width - num_digits - num_spacers){ + if(0 == fixed_width % 4){ + buff[0] = '\x81';//separator size + } + else{ + buff[0] = '\x80';//digit size + } + --fixed_width; + ++buff; + } + } + + ultoa(freq, buff, 10); + buff += num_leading_digits; + num_digits -= num_leading_digits; + for(int i = num_digits-1; i >= 0; --i){ + buff[i + (i/3 + 1)] = buff[i]; + } + for(unsigned int i = 0; i < num_spacers; ++i){ + memcpy_P(buff,F("."),1); + buff += 4; + } +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..ec1607b --- /dev/null +++ b/utils.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +#define LIMIT(val,min,max) ((val) < (min)) ? (min) : (((max) < (val)) ? (max) : (val)) + +void formatFreq(uint32_t freq, char* buff_out, uint16_t buff_size, uint8_t fixed_width = 0); diff --git a/version.cpp b/version.cpp new file mode 100644 index 0000000..316e0bf --- /dev/null +++ b/version.cpp @@ -0,0 +1,4 @@ +#include "version.h" + +const char VERSION_STRING_PRIVATE [] PROGMEM = "R1.5.1"; +const char* const VERSION_STRING = VERSION_STRING_PRIVATE; \ No newline at end of file diff --git a/version.h b/version.h new file mode 100644 index 0000000..9ce2cef --- /dev/null +++ b/version.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * VERSION_STRING is a PROGMEM string, so extract it before use, e.g. + * strncpy_P(char_buffer_out,VERSION_STRING,size_of_char_buffer_out); + */ +extern const char* const VERSION_STRING; \ No newline at end of file