Я являюсь обладателем замечательного устройства — GPS логгера Holux M-241. Штука весьма удобная и полезная в путешествиях. С помощью логгера я пишу GPS трек поездки, по которому потом можно посмотреть свой путь в деталях, а также привязать снятые фотографии к GPS координатам. А еще у него есть небольшой экран который показывает дополнительную информацию — часы, текущую скорость, высоту и направление, одометр и многое другое. Вот тут я когда то написал небольшой обзор.
При всех достоинствах железки я стал из нее вырастать. Мне не хватает нескольких небольших, но полезных плюшек: несколько одометров, показ вертикальной скорости, замер параметров участка пути. Вроде мелочи, но фирма Holux посчитала это недостаточно полезным для реализации в прошивке. Так же мне не нравятся кое какие параметры железяки, а некоторые вещи за 10 лет уже морально устарели…
В какой то момент я осознал, что могу сам сделать логгер с такими фичами как мне нужно. Благо все необходимые компоненты достаточно дешевы и доступны. Свою реализацию я начал делать на основе Arduino. Под катом дневник постройки, где я постарался расписать свои технические решения.
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <gfxfont.h>
#include <fonts/FreeMono12pt7b.h>
#include <fonts/FreeMono18pt7b.h>
...
#include <fonts/FreeSerifItalic24pt7b.h>
#include <fonts/FreeSerifItalic9pt7b.h>
#include <fonts/TomThumb.h>
struct font_and_name
{
const char * PROGMEM name;
GFXfont * font;
};
#define FONT(name) {#name, &name}
const font_and_name fonts[] = {
// FONT(FreeMono12pt7b),
FONT(FreeMono18pt7b),
/*
FONT(FreeMono24pt7b),
FONT(FreeMono9pt7b),
FONT(FreeMonoBold12pt7b),
...
FONT(FreeSerifItalic9pt7b),
FONT(TomThumb)*/
};
const unsigned int fonts_count = sizeof(fonts) / sizeof(font_and_name);
unsigned int current_font = 0;
extern Adafruit_SSD1306 display;
void RunFontTest()
{
display.clearDisplay();
display.setCursor(0,30);
display.setFont(fonts[current_font].font);
display.print("12:34:56");
display.setCursor(0,6);
display.setFont(&TomThumb);
display.print(fonts[current_font].name);
display.display();
}
void SwitchToNextFont()
{
current_font = ++current_font % fonts_count;
}
// This font consists only of digits and ':' to display current time.
// The font is very based on FreeSans18pt7b.h
//TODO: 25 pixel height is too much for displaying time. Create another 22px font
const uint8_t TimeFontBitmaps[] PROGMEM = {
/*
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x20, 0x3F, 0xFC, 0xE3, 0xF1,
0xF8, 0xFC, 0x7E, 0x3F, 0x1F, 0x8E, 0x82, 0x41, 0x00, 0x01, 0xC3, 0x80,
...
0x03, 0x00, 0xC0, 0x60, 0x18, 0x06, 0x03, 0x00, 0xC0, 0x30, 0x18, 0x06,
0x01, 0x80, 0xC0, 0x30, 0x00, */0x07, 0xE0, 0x0F, 0xF8, 0x1F, 0xFC, 0x3C,
0x3C, 0x78, 0x1E, 0x70, 0x0E, 0x70, 0x0E, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
0x07, 0xE0, 0x07, 0xE0, 0x0F, 0x70, 0x0E, 0x70, 0x0E, 0x78, 0x1E, 0x3C,
0x3C, 0x1F, 0xF8, 0x1F, 0xF0, 0x07, 0xE0, 0x03, 0x03, 0x07, 0x0F, 0x3F,
0xFF, 0xFF, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xE0, 0x1F, 0xF8,
0x3F, 0xFC, 0x7C, 0x3E, 0x70, 0x0F, 0xF0, 0x0F, 0xE0, 0x07, 0xE0, 0x07,
0x00, 0x07, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0xF8,
0x03, 0xF0, 0x07, 0xC0, 0x1F, 0x00, 0x3C, 0x00, 0x38, 0x00, 0x70, 0x00,
0x60, 0x00, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0x07, 0xFE, 0x07, 0xFF, 0x87, 0x83, 0xC3, 0x80, 0xF3, 0x80, 0x39, 0xC0,
0x1C, 0xE0, 0x0E, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x7F, 0x00, 0x3F, 0x00,
0x1F, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x03, 0xF0, 0x01,
0xF8, 0x00, 0xFE, 0x00, 0x77, 0x00, 0x73, 0xE0, 0xF8, 0xFF, 0xF8, 0x3F,
0xF8, 0x07, 0xF0, 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x78, 0x00, 0xF8,
0x00, 0xF8, 0x01, 0xF8, 0x03, 0xB8, 0x03, 0x38, 0x07, 0x38, 0x0E, 0x38,
0x1C, 0x38, 0x18, 0x38, 0x38, 0x38, 0x70, 0x38, 0x60, 0x38, 0xE0, 0x38,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x1F, 0xFF, 0x0F, 0xFF, 0x8F, 0xFF,
0xC7, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x39,
0xF0, 0x3F, 0xFE, 0x1F, 0xFF, 0x8F, 0x83, 0xE7, 0x00, 0xF0, 0x00, 0x3C,
0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xFC, 0x00,
0xEF, 0x00, 0x73, 0xC0, 0xF0, 0xFF, 0xF8, 0x3F, 0xF8, 0x07, 0xE0, 0x00,
0x03, 0xE0, 0x0F, 0xF8, 0x1F, 0xFC, 0x3C, 0x1E, 0x38, 0x0E, 0x70, 0x0E,
0x70, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xE3, 0xE0, 0xEF, 0xF8, 0xFF, 0xFC,
0xFC, 0x3E, 0xF0, 0x0E, 0xF0, 0x0F, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07,
0x60, 0x07, 0x70, 0x0F, 0x70, 0x0E, 0x3C, 0x3E, 0x3F, 0xFC, 0x1F, 0xF8,
0x07, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0E,
0x00, 0x1C, 0x00, 0x18, 0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00, 0xE0,
0x00, 0xC0, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x03, 0x80, 0x07, 0x00,
0x07, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0C, 0x00,
0x1C, 0x00, 0x1C, 0x00, 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0xFF, 0x87, 0x83,
0xC7, 0x80, 0xF3, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x78, 0x0F, 0x1E,
0x0F, 0x07, 0xFF, 0x01, 0xFF, 0x03, 0xFF, 0xE3, 0xE0, 0xF9, 0xC0, 0x1D,
0xC0, 0x0F, 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0xF7, 0x00,
0x73, 0xE0, 0xF8, 0xFF, 0xF8, 0x3F, 0xF8, 0x07, 0xF0, 0x00, 0x07, 0xE0,
0x1F, 0xF8, 0x3F, 0xFC, 0x7C, 0x3C, 0x70, 0x0E, 0xF0, 0x0E, 0xE0, 0x06,
0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0x70, 0x0F, 0x78, 0x3F,
0x3F, 0xFF, 0x1F, 0xF7, 0x07, 0xC7, 0x00, 0x07, 0x00, 0x06, 0x00, 0x0E,
0x70, 0x0E, 0x70, 0x1C, 0x78, 0x3C, 0x3F, 0xF8, 0x1F, 0xF0, 0x07, 0xC0,
0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x80 /*, 0xFF, 0xF0, 0x00, 0x00,
0x00, 0x07, 0xFF, 0xB6, 0xD6, 0x00, 0x00, 0x80, 0x03, 0xC0, 0x07, 0xE0,
0x0F, 0xC0, 0x3F, 0x80, 0x7E, 0x00, 0xFC, 0x01, 0xF0, 0x00, 0xE0, 0x00,
...
0x38, 0x38, 0xF8, 0xF0, 0xE0, 0x38, 0x00, 0xFC, 0x03, 0xFC, 0x1F, 0x3E,
0x3C, 0x1F, 0xE0, 0x1F, 0x80, 0x1E, 0x00
*/
};
//TODO Recalc offset numbers
const GFXglyph TimeFontGlyphs[] PROGMEM =
{
{ 449-449, 16, 25, 19, 2, -24 }, // 0x30 '0'
{ 499-449, 8, 25, 19, 4, -24 }, // 0x31 '1'
{ 524-449, 16, 25, 19, 2, -24 }, // 0x32 '2'
{ 574-449, 17, 25, 19, 1, -24 }, // 0x33 '3'
{ 628-449, 16, 25, 19, 1, -24 }, // 0x34 '4'
{ 678-449, 17, 25, 19, 1, -24 }, // 0x35 '5'
{ 732-449, 16, 25, 19, 2, -24 }, // 0x36 '6'
{ 782-449, 16, 25, 19, 2, -24 }, // 0x37 '7'
{ 832-449, 17, 25, 19, 1, -24 }, // 0x38 '8'
{ 886-449, 16, 25, 19, 1, -24 }, // 0x39 '9'
{ 936-449, 3, 19, 7, 2, -20 }, // 0x3A ':'
};
const GFXfont TimeFont PROGMEM = {
(uint8_t *)TimeFontBitmaps,
(GFXglyph *)TimeFontGlyphs,
0x30, 0x3A, 20 };
// A simple 8x12 font (slightly modifier Courier New)
const uint8_t Monospace8x12Bitmaps[] PROGMEM = {
0x1e, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x1e, //0
0x18, 0x68, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x7f, //1
0x3e, 0x41, 0x41, 0x01, 0x02, 0x0c, 0x10, 0x20, 0x41, 0x7f, //2
0x3e, 0x41, 0x01, 0x01, 0x0e, 0x02, 0x01, 0x01, 0x41, 0x3e, //3
0x02, 0x06, 0x0a, 0x12, 0x12, 0x22, 0x3f, 0x02, 0x02, 0x0f, //4
0x7f, 0x41, 0x40, 0x40, 0x7e, 0x01, 0x01, 0x01, 0x41, 0x3e, //5
0x1e, 0x21, 0x40, 0x40, 0x5e, 0x61, 0x41, 0x41, 0x41, 0x3e, //6
0x7f, 0x41, 0x01, 0x02, 0x02, 0x04, 0x04, 0x04, 0x08, 0x08, //7
0x1e, 0x21, 0x21, 0x21, 0x1e, 0x21, 0x21, 0x21, 0x21, 0x1e, //8
0x1e, 0x21, 0x21, 0x21, 0x23, 0x1d, 0x01, 0x01, 0x22, 0x1c, //9
0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, //:
};
const GFXglyph Monospace8x12Glyphs[] PROGMEM =
{
{ 0, 8, 10, 8, 0, -11 }, // 0x30 '0'
{ 10, 8, 10, 8, 0, -11 }, // 0x31 '1'
{ 20, 8, 10, 8, 0, -11 }, // 0x32 '2'
{ 30, 8, 10, 8, 0, -11 }, // 0x33 '3'
{ 40, 8, 10, 8, 0, -11 }, // 0x34 '4'
{ 50, 8, 10, 8, 0, -11 }, // 0x35 '5'
{ 60, 8, 10, 8, 0, -11 }, // 0x36 '6'
{ 70, 8, 10, 8, 0, -11 }, // 0x37 '7'
{ 80, 8, 10, 8, 0, -11 }, // 0x38 '8'
{ 90, 8, 10, 8, 0, -11 }, // 0x39 '9'
{ 100, 8, 10, 8, 0, -11 }, // 0x3A ':'
};
const GFXfont Monospace8x12Font PROGMEM = {
(uint8_t *)Monospace8x12Bitmaps,
(GFXglyph *)Monospace8x12Glyphs,
0x30, 0x3A, 12 };
class Screen
{
Screen * nextScreen;
public:
Screen();
virtual ~Screen() {}
virtual void drawScreen() = 0;
virtual void drawHeader();
virtual void onSelButton();
virtual void onOkButton();
virtual PROGMEM const char * getSelButtonText();
virtual PROGMEM const char * getOkButtonText();
Screen * addScreen(Screen * screen);
};
class Screen
{
Screen * nextScreen;
…
};
class ParentScreen : public Screen
{
Screen * childScreen;
…
};
Screen * screenStack[3];
int screenIdx = 0;
void setCurrentScreen(Screen * screen)
{
screenStack[screenIdx] = screen;
}
Screen * getCurrentScreen()
{
return screenStack[screenIdx];
}
void enterChildScreen(Screen * screen)
{
screenIdx++; //TODO limit this
screenStack[screenIdx] = screen;
}
void backToParentScreen()
{
if(screenIdx)
screenIdx--;
}
Screen * createCurrentTimeScreen()
{
TimeZoneScreen * tzScreen = new TimeZoneScreen(1, 30);
tzScreen = tzScreen->addScreen(new TimeZoneScreen(2, 45));
tzScreen = tzScreen->addScreen(new TimeZoneScreen(-3, 30));
// TODO Add real timezones here
CurrentTimeScreen * screen = new CurrentTimeScreen();
screen->addChildScreen(tzScreen);
return screen;
}
enum State
{
IDLE_DISPLAY_OFF,
IDLE,
MESSAGE_BOX,
BUTTON_PRESSED,
};
size_t print(const __FlashStringHelper *);
size_t print(const char[]);
class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
display.print(F(“String in flash memory”));
const char text[] PROGMEM = "String in flash memory";
display.print(F(text));
#define USE_PGM_STRING(x) reinterpret_cast<const __FlashStringHelper *>(x)
void Screen::drawHeader()
{
display.setFont(NULL);
display.setCursor(20, 0);
display.print('\x1e');
display.print(USE_PGM_STRING(getSelButtonText()));
display.setCursor(80, 0);
display.print('\x1e');
display.print(USE_PGM_STRING(getOkButtonText()));
}
К сожалению, не доступен сервер mySQL