diff --git a/.gitignore b/.gitignore index 259148f..89cc49c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,32 +1,5 @@ -# Prerequisites -*.d - -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..e80666b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/include/OzOLED.cpp b/include/OzOLED.cpp new file mode 100644 index 0000000..4d85a76 --- /dev/null +++ b/include/OzOLED.cpp @@ -0,0 +1,827 @@ +/* + OzOLED.cpp - 0.96' I2C 128x64 OLED Driver Library + 2014 Copyright (c) OscarLiang.net All right reserved. + + Author: Oscar Liang + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + +*/ + + + + +#include "OzOLED.h" +#include +#include + + +// 8x8 Font ASCII 32 - 127 Implemented +// Users can modify this to support more characters(glyphs) +// BasicFont is placed in code memory. + +// This font be freely used without any restriction(It is placed in public domain) +const byte BasicFont[][8] PROGMEM = { + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x5F,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x07,0x00,0x07,0x00,0x00,0x00}, + {0x00,0x14,0x7F,0x14,0x7F,0x14,0x00,0x00}, + {0x00,0x24,0x2A,0x7F,0x2A,0x12,0x00,0x00}, + {0x00,0x23,0x13,0x08,0x64,0x62,0x00,0x00}, + {0x00,0x36,0x49,0x55,0x22,0x50,0x00,0x00}, + {0x00,0x00,0x05,0x03,0x00,0x00,0x00,0x00}, + {0x00,0x1C,0x22,0x41,0x00,0x00,0x00,0x00}, + {0x00,0x41,0x22,0x1C,0x00,0x00,0x00,0x00}, + {0x00,0x08,0x2A,0x1C,0x2A,0x08,0x00,0x00}, + {0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00}, + {0x00,0xA0,0x60,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x08,0x08,0x08,0x08,0x08,0x00,0x00}, + {0x00,0x60,0x60,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x20,0x10,0x08,0x04,0x02,0x00,0x00}, + {0x00,0x3E,0x51,0x49,0x45,0x3E,0x00,0x00}, + {0x00,0x00,0x42,0x7F,0x40,0x00,0x00,0x00}, + {0x00,0x62,0x51,0x49,0x49,0x46,0x00,0x00}, + {0x00,0x22,0x41,0x49,0x49,0x36,0x00,0x00}, + {0x00,0x18,0x14,0x12,0x7F,0x10,0x00,0x00}, + {0x00,0x27,0x45,0x45,0x45,0x39,0x00,0x00}, + {0x00,0x3C,0x4A,0x49,0x49,0x30,0x00,0x00}, + {0x00,0x01,0x71,0x09,0x05,0x03,0x00,0x00}, + {0x00,0x36,0x49,0x49,0x49,0x36,0x00,0x00}, + {0x00,0x06,0x49,0x49,0x29,0x1E,0x00,0x00}, + {0x00,0x00,0x36,0x36,0x00,0x00,0x00,0x00}, + {0x00,0x00,0xAC,0x6C,0x00,0x00,0x00,0x00}, + {0x00,0x08,0x14,0x22,0x41,0x00,0x00,0x00}, + {0x00,0x14,0x14,0x14,0x14,0x14,0x00,0x00}, + {0x00,0x41,0x22,0x14,0x08,0x00,0x00,0x00}, + {0x00,0x02,0x01,0x51,0x09,0x06,0x00,0x00}, + {0x00,0x32,0x49,0x79,0x41,0x3E,0x00,0x00}, + {0x00,0x7E,0x09,0x09,0x09,0x7E,0x00,0x00}, + {0x00,0x7F,0x49,0x49,0x49,0x36,0x00,0x00}, + {0x00,0x3E,0x41,0x41,0x41,0x22,0x00,0x00}, + {0x00,0x7F,0x41,0x41,0x22,0x1C,0x00,0x00}, + {0x00,0x7F,0x49,0x49,0x49,0x41,0x00,0x00}, + {0x00,0x7F,0x09,0x09,0x09,0x01,0x00,0x00}, + {0x00,0x3E,0x41,0x41,0x51,0x72,0x00,0x00}, + {0x00,0x7F,0x08,0x08,0x08,0x7F,0x00,0x00}, + {0x00,0x41,0x7F,0x41,0x00,0x00,0x00,0x00}, + {0x00,0x20,0x40,0x41,0x3F,0x01,0x00,0x00}, + {0x00,0x7F,0x08,0x14,0x22,0x41,0x00,0x00}, + {0x00,0x7F,0x40,0x40,0x40,0x40,0x00,0x00}, + {0x00,0x7F,0x02,0x0C,0x02,0x7F,0x00,0x00}, + {0x00,0x7F,0x04,0x08,0x10,0x7F,0x00,0x00}, + {0x00,0x3E,0x41,0x41,0x41,0x3E,0x00,0x00}, + {0x00,0x7F,0x09,0x09,0x09,0x06,0x00,0x00}, + {0x00,0x3E,0x41,0x51,0x21,0x5E,0x00,0x00}, + {0x00,0x7F,0x09,0x19,0x29,0x46,0x00,0x00}, + {0x00,0x26,0x49,0x49,0x49,0x32,0x00,0x00}, + {0x00,0x01,0x01,0x7F,0x01,0x01,0x00,0x00}, + {0x00,0x3F,0x40,0x40,0x40,0x3F,0x00,0x00}, + {0x00,0x1F,0x20,0x40,0x20,0x1F,0x00,0x00}, + {0x00,0x3F,0x40,0x38,0x40,0x3F,0x00,0x00}, + {0x00,0x63,0x14,0x08,0x14,0x63,0x00,0x00}, + {0x00,0x03,0x04,0x78,0x04,0x03,0x00,0x00}, + {0x00,0x61,0x51,0x49,0x45,0x43,0x00,0x00}, + {0x00,0x7F,0x41,0x41,0x00,0x00,0x00,0x00}, + {0x00,0x02,0x04,0x08,0x10,0x20,0x00,0x00}, + {0x00,0x41,0x41,0x7F,0x00,0x00,0x00,0x00}, + {0x00,0x04,0x02,0x01,0x02,0x04,0x00,0x00}, + {0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00}, + {0x00,0x01,0x02,0x04,0x00,0x00,0x00,0x00}, + {0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00}, + {0x00,0x7F,0x48,0x44,0x44,0x38,0x00,0x00}, + {0x00,0x38,0x44,0x44,0x28,0x00,0x00,0x00}, + {0x00,0x38,0x44,0x44,0x48,0x7F,0x00,0x00}, + {0x00,0x38,0x54,0x54,0x54,0x18,0x00,0x00}, + {0x00,0x08,0x7E,0x09,0x02,0x00,0x00,0x00}, + {0x00,0x18,0xA4,0xA4,0xA4,0x7C,0x00,0x00}, + {0x00,0x7F,0x08,0x04,0x04,0x78,0x00,0x00}, + {0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x80,0x84,0x7D,0x00,0x00,0x00,0x00}, + {0x00,0x7F,0x10,0x28,0x44,0x00,0x00,0x00}, + {0x00,0x41,0x7F,0x40,0x00,0x00,0x00,0x00}, + {0x00,0x7C,0x04,0x18,0x04,0x78,0x00,0x00}, + {0x00,0x7C,0x08,0x04,0x7C,0x00,0x00,0x00}, + {0x00,0x38,0x44,0x44,0x38,0x00,0x00,0x00}, + {0x00,0xFC,0x24,0x24,0x18,0x00,0x00,0x00}, + {0x00,0x18,0x24,0x24,0xFC,0x00,0x00,0x00}, + {0x00,0x00,0x7C,0x08,0x04,0x00,0x00,0x00}, + {0x00,0x48,0x54,0x54,0x24,0x00,0x00,0x00}, + {0x00,0x04,0x7F,0x44,0x00,0x00,0x00,0x00}, + {0x00,0x3C,0x40,0x40,0x7C,0x00,0x00,0x00}, + {0x00,0x1C,0x20,0x40,0x20,0x1C,0x00,0x00}, + {0x00,0x3C,0x40,0x30,0x40,0x3C,0x00,0x00}, + {0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00}, + {0x00,0x1C,0xA0,0xA0,0x7C,0x00,0x00,0x00}, + {0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00}, + {0x00,0x08,0x36,0x41,0x00,0x00,0x00,0x00}, + {0x00,0x00,0x7F,0x00,0x00,0x00,0x00,0x00}, + {0x00,0x41,0x36,0x08,0x00,0x00,0x00,0x00}, + {0x00,0x02,0x01,0x01,0x02,0x01,0x00,0x00}, + {0x00,0x02,0x05,0x05,0x02,0x00,0x00,0x00} +}; + + +// Big numbers font, from 0 to 9 - 96 bytes each. +const byte bigNumbers [][96] PROGMEM = { +{0x00, 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x0F, 0x0F, 0x0F, +0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x03, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xF0, +0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, +0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x07, 0x07, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC1, 0xC0, 0xC0, 0xC0, +0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, +0x03, 0x03, 0x83, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, +0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0xC1, 0xC0, 0xC0, 0xC0, +0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE1, 0xFF, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x81, 0x83, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x87, +0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, +0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, +0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x07, +0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, +0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC1, 0x81, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x81, 0x83, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x87, +0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, +0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, +0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC1, 0x81, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x87, +0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, +0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xE1, +0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE1, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0x87, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x87, +0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, +0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0xE0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, +0xF0, 0xF0, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, +0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x07, +0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0F, 0x0F, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x00}, + +{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x3C, 0x7E, 0x7E, 0x7E, 0x7E, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xF8, 0xF8, 0xF8, 0xF8, 0xF0, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} +}; + + +// SSD1306 Commandset +// ------------------ +// Fundamental Commands +#define ASA_DISPLAY_ALL_ON_RESUME 0xA4 +// Addressing Setting Commands +#define ASA_MEMORY_ADDR_MODE 0x20 +// Hardware Configuration Commands +#define ASA_SET_START_LINE 0x40 +#define ASA_SET_SEGMENT_REMAP 0xA0 +#define ASA_SET_MULTIPLEX_RATIO 0xA8 +#define ASA_COM_SCAN_DIR_DEC 0xC8 +#define ASA_SET_DISPLAY_OFFSET 0xD3 +#define ASA_SET_COM_PINS 0xDA +#define ASA_CHARGE_PUMP 0x8D +// Timing & Driving Scheme Setting Commands +#define ASA_SET_DISPLAY_CLOCK_DIV_RATIO 0xD5 +#define ASA_SET_PRECHARGE_PERIOD 0xD9 +#define ASA_SET_VCOM_DESELECT 0xDB + + + +// ====================== LOW LEVEL ========================= + +void OzOLED::sendCommand(byte command){ + Wire.beginTransmission(OLED_ADDRESS); // begin transmitting + Wire.write(OzOLED_COMMAND_MODE);//data mode + Wire.write(command); + Wire.endTransmission(); // stop transmitting +} + + +void OzOLED::sendData(byte data){ + + Wire.beginTransmission(OLED_ADDRESS); // begin transmitting + Wire.write(OzOLED_DATA_MODE);//data mode + Wire.write(data); + Wire.endTransmission(); // stop transmitting + +} + +void OzOLED::printChar(char C, byte X, byte Y){ + + if ( X < 128 ) + setCursorXY(X, Y); + + //Ignore unused ASCII characters. Modified the range to support multilingual characters. + if(C < 32 || C > 127) + C='*'; //star - indicate characters that can't be displayed + + + for(byte i=0; i<8; i++) { + + //read bytes from code memory + sendData(pgm_read_byte(&BasicFont[C-32][i])); //font array starts at 0, ASCII starts at 32. Hence the translation + + } +} + +void OzOLED::printString(const char *String, byte X, byte Y, byte numChar){ + + if ( X < 128 ) + setCursorXY(X, Y); + + + byte count=0; + while(String[count] && count 0) { + + char_buffer[i++] = long_num % 10; + long_num /= 10; + + } + + f += i; + for(; i > 0; i--) { + + printChar('0'+ char_buffer[i - 1]); + + } + + return f; + +} + +byte OzOLED::printNumber16(long long_num, byte X, byte Y){ + + if ( X < 128 ) + setCursorXY(X, Y); + + + byte char_buffer[10] = ""; + byte i = 0; + byte f = 0; // number of characters + + if (long_num < 0) { + + f++; + printChar16('-', X, Y); + long_num = -long_num; + + } + else if (long_num == 0) { + + f++; + printChar16('0', X, Y); + return f; + + } + + while (long_num > 0) { + + char_buffer[i++] = long_num % 10; + long_num /= 10; + + } + + f += i; + for(; i > 0; i--) { + + printChar16('0'+ char_buffer[i - 1], X, Y); + X += 2; + + } + + return f; + +} + + +byte OzOLED::printNumber(float float_num, byte prec, byte X, byte Y){ + + if ( X < 128 ) + setCursorXY(X, Y); + +// prec - 6 maximum + + byte num_int = 0; + byte num_frac = 0; + byte num_extra = 0; + + long d = float_num; // get the integer part + float f = float_num - d; // get the fractional part + + + if (d == 0 && f < 0.0){ + + printChar('-'); + num_extra++; + printChar('0'); + num_extra++; + f *= -1; + + } + else if (d < 0 && f < 0.0){ + + num_int = printNumber(d); // count how many digits in integer part + f *= -1; + + } + else{ + + num_int = printNumber(d); // count how many digits in integer part + + } + + // only when fractional part > 0, we show decimal point + if (f > 0.0){ + + printChar('.'); + num_extra++; + + long f_shift = 1; + + if (num_int + prec > 8) + prec = 8 - num_int; + + for (byte j=0; j 127) //Ignore unused ASCII characters. + C='*'; //star - indicate characters that can't be displayed + unsigned int m = 0; + unsigned char n[8]; + + for ( byte i=0; i<8; i++) { + m = EnlardeByte2Word(pgm_read_byte(&BasicFont[C-32][i])); + sendData(lowByte(m)); + sendData(lowByte(m)); + n[i] = highByte(m); + } + setCursorXY(X, Y+1); + + for(byte i=0; i<8; i++) { + sendData(n[i]); //font array starts at 0, ASCII starts at 32. Hence the translation + sendData(n[i]); //font array starts at 0, ASCII starts at 32. Hence the translation + } +} + +void OzOLED::printString16(const char *String, byte X, byte Y, byte numChar){ + byte count=0; + while(String[count] && count 58) + sendData(0); + else + sendData(pgm_read_byte(&bigNumbers[number[count]-48][i])); + + + if(column >= 23){ + column = 0; + setCursorXY(X, ++Y); + } + else + column++; + + } + + count++; + + X = X + 3; + Y = Y - 4; + + + } + + + +} + + +void OzOLED::drawBitmap(const byte *bitmaparray, byte X, byte Y, byte width, byte height){ + +// max width = 16 +// max height = 8 + + setCursorXY( X, Y ); + + byte column = 0; + for(int i=0; i>4)&0x0F)); //set column higher address + sendCommand(0xB0 + Y); //set page address + +} + + +void OzOLED::clearDisplay() { + + + for(byte page=0; page<8; page++) { + + setCursorXY(0, page); + for(byte column=0; column<128; column++){ //clear all columns + sendData(0); + } + + } + + setCursorXY(0,0); + +} + +/* +void OzOLED::clearPage(byte page) { + // clear page and set cursor at beginning of that page + + setCursorXY(0, page); + for(byte column=0; column<128; column++){ //clear all columns + sendData(0x00); + } + +} +*/ + + +void OzOLED::setInverseDisplay(){ + + sendCommand(OzOLED_CMD_INVERSE_DISPLAY); + +} + +void OzOLED::setNormalDisplay(){ + + sendCommand(OzOLED_CMD_NORMAL_DISPLAY); + +} + +void OzOLED::setPowerOff(){ + + sendCommand(OzOLED_CMD_DISPLAY_OFF); + +} + +void OzOLED::setPowerOn(){ + + sendCommand(OzOLED_CMD_DISPLAY_ON); + +} + +void OzOLED::setBrightness(byte Brightness){ + + sendCommand(OzOLED_CMD_SET_BRIGHTNESS); + sendCommand(Brightness); + +} + +void OzOLED::setPageMode(){ + addressingMode = PAGE_ADDRESSING; + sendCommand(0x20); //set addressing mode + sendCommand(PAGE_ADDRESSING); //set page addressing mode +} + +void OzOLED::setHorizontalMode(){ + addressingMode = HORIZONTAL_ADDRESSING; + sendCommand(0x20); //set addressing mode + sendCommand(HORIZONTAL_ADDRESSING); //set page addressing mode +} + + +// startscrollright +// Activate a right handed scroll for rows start through stop +// Hint, the display is 16 rows tall. To scroll the whole display, run: +// scrollRight(0x00, 0x0F) - start - stop +void OzOLED::scrollRight(byte start, byte end, byte speed){ + + sendCommand(OzOLED_RIGHT_SCROLL); //Horizontal Scroll Setup + sendCommand(0x00); // dummy byte + sendCommand(start); // start page address + sendCommand(speed); // set time interval between each scroll + sendCommand(end); // end page address + + sendCommand(0x01); + sendCommand(0xFF); + + sendCommand(0x2f); //active scrolling + +} + + +// startscrollleft +// Activate a right handed scroll for rows start through stop +// Hint, the display is 16 rows tall. To scroll the whole display, run: +// display.scrollright(0x00, 0x0F) - start - stop +void OzOLED::scrollLeft(byte start, byte end, byte speed){ + + sendCommand(OzOLED_LEFT_SCROLL); //Horizontal Scroll Setup + sendCommand(0x00); // dummy byte + sendCommand(start); // start page address + sendCommand(speed); // set time interval between each scroll + sendCommand(end); // end page address + + sendCommand(0x01); + sendCommand(0xFF); + + sendCommand(0x2f); //active scrolling + +} + +// startscrolldiagright +// Activate a diagonal scroll for rows start through stop +// Hint, the display is 16 rows tall. To scroll the whole display, run: +// display.scrollright(0x00, 0x0F) +void OzOLED::scrollDiagRight(){ + + sendCommand(OzOLED_SET_VERTICAL_SCROLL_AREA); + sendCommand(0X00); + sendCommand(OzOLED_Max_Y); + sendCommand(OzOLED_VERTICAL_RIGHT_SCROLL); //Vertical and Horizontal Scroll Setup + sendCommand(0X00); //dummy byte + sendCommand(0x00); //define page0 as startpage address + sendCommand(0X00); //set time interval between each scroll ste as 6 frames + sendCommand(0x07); //define page7 as endpage address + sendCommand(0X01); //set vertical scrolling offset as 1 row + sendCommand(OzOLED_CMD_ACTIVATE_SCROLL); //active scrolling + +} + +void OzOLED::scrollDiagLeft(){ + + sendCommand(OzOLED_SET_VERTICAL_SCROLL_AREA); + sendCommand(0X00); + sendCommand(OzOLED_Max_Y); + sendCommand(OzOLED_VERTICAL_LEFT_SCROLL); //Vertical and Horizontal Scroll Setup + sendCommand(0X00); //dummy byte + sendCommand(0x00); //define page0 as startpage address + sendCommand(0X00); //set time interval between each scroll ste as 6 frames + sendCommand(0x07); //define page7 as endpage address + sendCommand(0X01); //set vertical scrolling offset as 1 row + sendCommand(OzOLED_CMD_ACTIVATE_SCROLL); //active scrolling + +} + + +void OzOLED::setActivateScroll(byte direction, byte startPage, byte endPage, byte scrollSpeed){ + + +/* +This function is still not complete, we need more testing also. +Use the following defines for 'direction' : + + Scroll_Left + Scroll_Right + +For Scroll_vericle, still need to debug more... + +Use the following defines for 'scrollSpeed' : + + Scroll_2Frames + Scroll_3Frames + Scroll_4Frames + Scroll_5Frames + Scroll_25Frames + Scroll_64Frames + Scroll_128Frames + Scroll_256Frames + +*/ + + + if(direction == Scroll_Right) { + + //Scroll Right + sendCommand(0x26); + + } + else { + + //Scroll Left + sendCommand(0x27); + + } + /* + else if (direction == Scroll_Up ){ + + //Scroll Up + sendCommand(0x29); + + } + else{ + + //Scroll Down + sendCommand(0x2A); + + } + */ + sendCommand(0x00);//dummy byte + sendCommand(startPage); + sendCommand(scrollSpeed); + sendCommand(endPage); // for verticle scrolling, use 0x29 as command, endPage should = start page = 0 + + /* + if(direction == Scroll_Up) { + + sendCommand(0x01); + + } + */ + + sendCommand(OzOLED_CMD_ACTIVATE_SCROLL); + +} + +void OzOLED::setDeactivateScroll(){ + + sendCommand(OzOLED_CMD_DEACTIVATE_SCROLL); + +} + + + + +OzOLED OzOled; // Preinstantiate Objects diff --git a/include/OzOLED.h b/include/OzOLED.h new file mode 100644 index 0000000..a29b16e --- /dev/null +++ b/include/OzOLED.h @@ -0,0 +1,110 @@ +/* + OzOLED.h - 0.96' I2C 128x64 OLED Driver Library + 2014 Copyright (c) OscarLiang.net All right reserved. + + Author: Oscar Liang + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + +*/ + +#ifndef OzOLED_data_H +#define OzOLED_data_H + +#include + +#define OzOLED_Max_X 128 //128 Pixels +#define OzOLED_Max_Y 64 //64 Pixels + +#define OLED_ADDRESS 0x3C +#define I2C_400KHZ 1 // 0 to use default 100Khz, 1 for 400Khz + +// registers +#define OzOLED_COMMAND_MODE 0x80 +#define OzOLED_DATA_MODE 0x40 + +// commands +#define OzOLED_CMD_DISPLAY_OFF 0xAE +#define OzOLED_CMD_DISPLAY_ON 0xAF +#define OzOLED_CMD_NORMAL_DISPLAY 0xA6 +#define OzOLED_CMD_INVERSE_DISPLAY 0xA7 +#define OzOLED_CMD_SET_BRIGHTNESS 0x81 + +#define OzOLED_RIGHT_SCROLL 0x26 +#define OzOLED_LEFT_SCROLL 0x27 +#define OzOLED_SET_VERTICAL_SCROLL_AREA 0xA3 +#define OzOLED_VERTICAL_RIGHT_SCROLL 0x29 +#define OzOLED_VERTICAL_LEFT_SCROLL 0x2A +#define OzOLED_CMD_ACTIVATE_SCROLL 0x2F +#define OzOLED_CMD_DEACTIVATE_SCROLL 0x2E + +#define HORIZONTAL_ADDRESSING 0x00 +#define PAGE_ADDRESSING 0x02 + +#define Scroll_Left 0x00 +#define Scroll_Right 0x01 +#define Scroll_Up 0x02 +#define Scroll_Down 0x03 + +#define Scroll_2Frames 0x07 +#define Scroll_3Frames 0x04 +#define Scroll_4Frames 0x05 +#define Scroll_5Frames 0x00 +#define Scroll_25Frames 0x06 +#define Scroll_64Frames 0x01 +#define Scroll_128Frames 0x02 +#define Scroll_256Frames 0x03 + + +class OzOLED { + +public: + + byte addressingMode; + + void sendCommand(byte command); + void sendData(byte Data); + + void printChar(char c, byte X=255, byte Y=255); + void printString(const char *String, byte X=255, byte Y=255, byte numChar=255); + byte printNumber(long n, byte X=255, byte Y=255); + byte printNumber(float float_num, byte prec=6, byte Y=255, byte numChar=255); + void printBigNumber(const char *number, byte column=0, byte page=0, byte numChar=255); + void drawBitmap(const byte *bitmaparray, byte X, byte Y, byte width, byte height); + void printChar16(char c, byte X=255, byte Y=255); // font 16x16 + void printString16(const char *String, byte X, byte Y, byte numChar=255); // font 16x16 + byte printNumber16(long n, byte X=255, byte Y=255); + void init(); + + void setCursorXY(byte Column, byte Row); + void clearDisplay(); + //void clearPage(byte page); + + void setNormalDisplay(); + void setInverseDisplay(); + void setPowerOff(); + void setPowerOn(); + void setPageMode(); + void setHorizontalMode(); + void setBrightness(byte Brightness); + + void scrollRight(byte start, byte end, byte speed); + void scrollLeft(byte start, byte end, byte speed); + void scrollDiagRight(); + void scrollDiagLeft(); + void setActivateScroll(byte direction, byte startPage, byte endPage, byte scrollSpeed); + void setDeactivateScroll(); + +}; + +extern OzOLED OzOled; // OzOLED object + +#endif diff --git a/include/README b/include/README new file mode 100644 index 0000000..194dcd4 --- /dev/null +++ b/include/README @@ -0,0 +1,39 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the usual convention is to give header files names that end with `.h'. +It is most portable to use only letters, digits, dashes, and underscores in +header file names, and at most one dot. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/include/buildTime.h b/include/buildTime.h new file mode 100644 index 0000000..6afb5a9 --- /dev/null +++ b/include/buildTime.h @@ -0,0 +1,63 @@ +// source http://qaru.site/questions/186859/how-to-use-date-and-time-predefined-macros-in-as-two-integers-then-stringify + +#ifndef BUILD_DEFS_H +#define BUILD_DEFS_H +// Example of __DATE__ string: "Jul 27 2012" +// 01234567890 + +#define BUILD_YEAR_CH0 (__DATE__[7]-'0') +#define BUILD_YEAR_CH1 (__DATE__[8]-'0') +#define BUILD_YEAR_CH2 (__DATE__[9]-'0') +#define BUILD_YEAR_CH3 (__DATE__[10]-'0') +#define BUILD_YEAR (BUILD_YEAR_CH0*1000+BUILD_YEAR_CH1*100 + BUILD_YEAR_CH2*10+BUILD_YEAR_CH3) + +#define BUILD_MONTH_IS_JAN (__DATE__[0] == 'J' && __DATE__[1] == 'a' && __DATE__[2] == 'n') +#define BUILD_MONTH_IS_FEB (__DATE__[0] == 'F') +#define BUILD_MONTH_IS_MAR (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'r') +#define BUILD_MONTH_IS_APR (__DATE__[0] == 'A' && __DATE__[1] == 'p') +#define BUILD_MONTH_IS_MAY (__DATE__[0] == 'M' && __DATE__[1] == 'a' && __DATE__[2] == 'y') +#define BUILD_MONTH_IS_JUN (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'n') +#define BUILD_MONTH_IS_JUL (__DATE__[0] == 'J' && __DATE__[1] == 'u' && __DATE__[2] == 'l') +#define BUILD_MONTH_IS_AUG (__DATE__[0] == 'A' && __DATE__[1] == 'u') +#define BUILD_MONTH_IS_SEP (__DATE__[0] == 'S') +#define BUILD_MONTH_IS_OCT (__DATE__[0] == 'O') +#define BUILD_MONTH_IS_NOV (__DATE__[0] == 'N') +#define BUILD_MONTH_IS_DEC (__DATE__[0] == 'D') + +#define BUILD_MONTH \ + ( \ + (BUILD_MONTH_IS_JAN) ? 1 : \ + (BUILD_MONTH_IS_FEB) ? 2 : \ + (BUILD_MONTH_IS_MAR) ? 3 : \ + (BUILD_MONTH_IS_APR) ? 4 : \ + (BUILD_MONTH_IS_MAY) ? 5 : \ + (BUILD_MONTH_IS_JUN) ? 6 : \ + (BUILD_MONTH_IS_JUL) ? 7 : \ + (BUILD_MONTH_IS_AUG) ? 8 : \ + (BUILD_MONTH_IS_SEP) ? 9 : \ + (BUILD_MONTH_IS_OCT) ? 10 : \ + (BUILD_MONTH_IS_NOV) ? 11 : \ + (BUILD_MONTH_IS_DEC) ? 12 : \ + /* error default */ '?' \ + ) + +#define BUILD_DAY_CH0 (((__DATE__[4] >= '0') ? (__DATE__[4]) : '0')-'0') +#define BUILD_DAY_CH1 (__DATE__[5]-'0') +#define BUILD_DAY (BUILD_DAY_CH0*10+BUILD_DAY_CH1) + +// Example of __TIME__ string: "21:06:19" +// 01234567 + +#define BUILD_HOUR_CH0 (__TIME__[0]-'0') +#define BUILD_HOUR_CH1 (__TIME__[1]-'0') +#define BUILD_HOUR (BUILD_HOUR_CH0*10+BUILD_HOUR_CH1) + +#define BUILD_MIN_CH0 (__TIME__[3]-'0') +#define BUILD_MIN_CH1 (__TIME__[4]-'0') +#define BUILD_MIN (BUILD_MIN_CH0*10+BUILD_MIN_CH1) + +#define BUILD_SEC_CH0 (__TIME__[6]-'0') +#define BUILD_SEC_CH1 (__TIME__[7]-'0') +#define BUILD_SEC (BUILD_SEC_CH0*10+BUILD_SEC_CH1) + +#endif // BUILD_DEFS_H diff --git a/include/dhtnew.cpp b/include/dhtnew.cpp new file mode 100644 index 0000000..7f6ca20 --- /dev/null +++ b/include/dhtnew.cpp @@ -0,0 +1,311 @@ +// +// FILE: dhtnew.cpp +// AUTHOR: Rob.Tillaart@gmail.com +// VERSION: 0.4.1 +// PURPOSE: DHT Temperature & Humidity Sensor library for Arduino +// URL: https://github.com/RobTillaart/DHTNEW +// +// HISTORY: +// 0.1.0 2017-07-24 initial version based upon DHTStable +// 0.1.1 2017-07-29 add begin() to determine type once and for all instead of every call + refactor +// 0.1.2 2018-01-08 improved begin() + refactor() +// 0.1.3 2018-01-08 removed begin() + moved detection to read() function +// 0.1.4 2018-04-03 add get-/setDisableIRQ(bool b) +// 0.1.5 2019-01-20 fix negative temperature DHT22 - issue #120 +// 0.1.6 2020-04-09 #pragma once, readme.md, own repo +// 0.1.7 2020-05-01 prevent premature read; add waitForReading flag (Kudo's to Mr-HaleYa), +// 0.2.0 2020-05-02 made temperature and humidity private (Kudo's to Mr-HaleYa), +// 0.2.1 2020-05-27 Fix #11 - Adjust bit timing threshold +// 0.2.2 2020-06-08 added ERROR_SENSOR_NOT_READY and differentiate timeout errors +// 0.3.0 2020-06-12 added getReadDelay & setReadDelay to tune reading interval +// removed get/setDisableIRQ; adjusted wakeup timing; refactor +// 0.3.1 2020-07-08 added powerUp() powerDown(); +// 0.3.2 2020-07-17 fix #23 added get/setSuppressError(); overrulable DHTLIB_INVALID_VALUE +// 0.3.3 2020-08-18 fix #29, create explicit delay between pulling line HIGH and +// waiting for LOW in handshake to trigger the sensor. +// On fast ESP32 this fails because the capacity / voltage of the long wire +// cannot rise fast enough to be read back as HIGH. +// 0.3.4 2020-09-23 Added **waitFor(state, timeout)** to follow timing from datasheet. +// Restored disableIRQ flag as problems occured on AVR. The default of +// this flag on AVR is false so interrupts are allowed. +// This need some investigation +// Fix wake up timing for DHT11 as it does not behave according datasheet. +// fix wakeupDelay bug in setType(); +// 0.4.0 2020-11-10 added DHTLIB_WAITING_FOR_READ as return value of read (minor break of interface) +// 0.4.1 2020-11-11 getType() attempts to detect sensor type + +#include "dhtnew.h" + +// these defines are not for user to adjust +#define DHTLIB_DHT11_WAKEUP 18 +#define DHTLIB_DHT_WAKEUP 1 + +// READ_DELAY for blocking read +// datasheet: DHT11 = 1000 and DHT22 = 2000 +// use setReadDelay() to overrule (at own risk) +// as individual sensors can be read faster. +// see example DHTnew_setReadDelay.ino +#define DHTLIB_DHT11_READ_DELAY 1000 +#define DHTLIB_DHT22_READ_DELAY 2000 + +///////////////////////////////////////////////////// +// +// PUBLIC +// +DHTNEW::DHTNEW(uint8_t pin) +{ + _dataPin = pin; + // Data-bus's free status is high voltage level. + pinMode(_dataPin, OUTPUT); + digitalWrite(_dataPin, HIGH); + _readDelay = 0; + #if defined(__AVR__) + _disableIRQ = false; + #endif +}; + +uint8_t DHTNEW::getType() +{ + if (_type == 0) read(); + return _type; +} + +void DHTNEW::setType(uint8_t type) +{ + if ((type == 0) || (type == 11)) + { + _type = type; + _wakeupDelay = DHTLIB_DHT11_WAKEUP; + } + if (type == 22) + { + _type = type; + _wakeupDelay = DHTLIB_DHT_WAKEUP; + } +} + +// return values: +// DHTLIB_OK +// DHTLIB_WAITING_FOR_READ +// DHTLIB_ERROR_CHECKSUM +// DHTLIB_ERROR_BIT_SHIFT +// DHTLIB_ERROR_SENSOR_NOT_READY +// DHTLIB_ERROR_TIMEOUT_A +// DHTLIB_ERROR_TIMEOUT_B +// DHTLIB_ERROR_TIMEOUT_C +// DHTLIB_ERROR_TIMEOUT_D +int DHTNEW::read() +{ + if (_readDelay == 0) + { + _readDelay = DHTLIB_DHT22_READ_DELAY; + if (_type == 11) _readDelay = DHTLIB_DHT11_READ_DELAY; + } + if (_type != 0) + { + while (millis() - _lastRead < _readDelay) + { + if (!_waitForRead) return DHTLIB_WAITING_FOR_READ; + yield(); + } + return _read(); + } + + _type = 22; + _wakeupDelay = DHTLIB_DHT_WAKEUP; + int rv = _read(); + if (rv == DHTLIB_OK) return rv; + + _type = 11; + _wakeupDelay = DHTLIB_DHT11_WAKEUP; + rv = _read(); + if (rv == DHTLIB_OK) return rv; + + _type = 0; // retry next time + return rv; +} + +// return values: +// DHTLIB_OK +// DHTLIB_ERROR_CHECKSUM +// DHTLIB_ERROR_BIT_SHIFT +// DHTLIB_ERROR_SENSOR_NOT_READY +// DHTLIB_ERROR_TIMEOUT_A +// DHTLIB_ERROR_TIMEOUT_B +// DHTLIB_ERROR_TIMEOUT_C +// DHTLIB_ERROR_TIMEOUT_D +int DHTNEW::_read() +{ + // READ VALUES + int rv = _readSensor(); + interrupts(); + + // Data-bus's free status is high voltage level. + pinMode(_dataPin, OUTPUT); + digitalWrite(_dataPin, HIGH); + _lastRead = millis(); + + if (rv != DHTLIB_OK) + { + if (_suppressError == false) + { + _humidity = DHTLIB_INVALID_VALUE; + _temperature = DHTLIB_INVALID_VALUE; + } + return rv; // propagate error value + } + + if (_type == 22) // DHT22, DHT33, DHT44, compatible + { + _humidity = (_bits[0] * 256 + _bits[1]) * 0.1; + _temperature = ((_bits[2] & 0x7F) * 256 + _bits[3]) * 0.1; + } + else // if (_type == 11) // DHT11, DH12, compatible + { + _humidity = _bits[0] + _bits[1] * 0.1; + _temperature = _bits[2] + _bits[3] * 0.1; + } + + if (_bits[2] & 0x80) // negative temperature + { + _temperature = -_temperature; + } + _humidity = constrain(_humidity + _humOffset, 0, 100); + _temperature += _tempOffset; + + // TEST CHECKSUM + uint8_t sum = _bits[0] + _bits[1] + _bits[2] + _bits[3]; + if (_bits[4] != sum) + { + return DHTLIB_ERROR_CHECKSUM; + } + return DHTLIB_OK; +} + +void DHTNEW::powerUp() +{ + digitalWrite(_dataPin, HIGH); + // do a dummy read to sync the sensor + read(); +}; + +void DHTNEW::powerDown() +{ + digitalWrite(_dataPin, LOW); +} + + +///////////////////////////////////////////////////// +// +// PRIVATE +// + +// return values: +// DHTLIB_OK +// DHTLIB_ERROR_CHECKSUM +// DHTLIB_ERROR_BIT_SHIFT +// DHTLIB_ERROR_SENSOR_NOT_READY +// DHTLIB_ERROR_TIMEOUT_A +// DHTLIB_ERROR_TIMEOUT_B +// DHTLIB_ERROR_TIMEOUT_C +// DHTLIB_ERROR_TIMEOUT_D +int DHTNEW::_readSensor() +{ + // INIT BUFFERVAR TO RECEIVE DATA + uint8_t mask = 0x80; + uint8_t idx = 0; + + // EMPTY BUFFER + for (uint8_t i = 0; i < 5; i++) _bits[i] = 0; + + // HANDLE PENDING IRQ + yield(); + + // REQUEST SAMPLE - SEND WAKEUP TO SENSOR + pinMode(_dataPin, OUTPUT); + digitalWrite(_dataPin, LOW); + // add 10% extra for timing inaccuracies in sensor. + delayMicroseconds(_wakeupDelay * 1100UL); + + // HOST GIVES CONTROL TO SENSOR + digitalWrite(_dataPin, HIGH); + delayMicroseconds(2); + pinMode(_dataPin, INPUT_PULLUP); + + // DISABLE INTERRUPTS when clock in the bits + if (_disableIRQ) { noInterrupts(); } + + // DHT22 + // SENSOR PULLS LOW after 20-40 us => if stays HIGH ==> device not ready + // timeout is 20 us less due to delay() above + // DHT11 + // SENSOR PULLS LOW after 6000-10000 us + uint32_t WAITFORSENSOR = 50; + if (_type == 11) WAITFORSENSOR = 15000UL; + if (_waitFor(LOW, WAITFORSENSOR)) return DHTLIB_ERROR_SENSOR_NOT_READY; + + // SENSOR STAYS LOW for ~80 us => or TIMEOUT + if (_waitFor(HIGH, 90)) return DHTLIB_ERROR_TIMEOUT_A; + + // SENSOR STAYS HIGH for ~80 us => or TIMEOUT + if (_waitFor(LOW, 90)) return DHTLIB_ERROR_TIMEOUT_B; + + // SENSOR HAS NOW SEND ACKNOWLEDGE ON WAKEUP + // NOW IT SENDS THE BITS + + // READ THE OUTPUT - 40 BITS => 5 BYTES + for (uint8_t i = 40; i != 0; i--) + { + // EACH BIT START WITH ~50 us LOW + if (_waitFor(HIGH, 70)) return DHTLIB_ERROR_TIMEOUT_C; + + // DURATION OF HIGH DETERMINES 0 or 1 + // 26-28 us ==> 0 + // 70 us ==> 1 + uint32_t t = micros(); + if (_waitFor(LOW, 90)) return DHTLIB_ERROR_TIMEOUT_D; + if ((micros() - t) > DHTLIB_BIT_THRESHOLD) + { + _bits[idx] |= mask; + } + + // PREPARE FOR NEXT BIT + mask >>= 1; + if (mask == 0) // next byte? + { + mask = 0x80; + idx++; + } + } + // After 40 bits the sensor pulls the line LOW for 50 us + // No functional need to wait for this one + // if (_waitFor(HIGH, 60)) return DHTLIB_ERROR_TIMEOUT_E; + + // CATCH RIGHTSHIFT BUG ESP (only 1 single bit shift) + // humidity is max 1000 = 0x03E8 for DHT22 and 0x6400 for DHT11 + // so most significant bit may never be set. + if (_bits[0] & 0x80) return DHTLIB_ERROR_BIT_SHIFT; + + return DHTLIB_OK; +} + + +// returns true if timeout has passed. +// returns false if timeout is not reached and state is seen. +bool DHTNEW::_waitFor(uint8_t state, uint32_t timeout) +{ + uint32_t start = micros(); + uint8_t count = 2; + while ((micros() - start) < timeout) + { + // delayMicroseconds(1); // less # reads ==> minimizes # glitch reads + if (digitalRead(_dataPin) == state) + { + count--; + if (count == 0) return false; // requested state seen count times + } + } + return true; +} + +// -- END OF FILE -- diff --git a/include/dhtnew.h b/include/dhtnew.h new file mode 100644 index 0000000..43cbe19 --- /dev/null +++ b/include/dhtnew.h @@ -0,0 +1,112 @@ +#pragma once +// +// FILE: dhtnew.h +// AUTHOR: Rob Tillaart +// VERSION: 0.4.1 +// PURPOSE: DHT Temperature & Humidity Sensor library for Arduino +// URL: https://github.com/RobTillaart/DHTNEW +// +// HISTORY: +// see dhtnew.cpp file + +// DHT PIN layout from left to right +// ================================= +// FRONT : DESCRIPTION +// pin 1 : VCC +// pin 2 : DATA +// pin 3 : Not Connected +// pin 4 : GND + +#include "Arduino.h" + +#define DHTNEW_LIB_VERSION "0.4.0" + +#define DHTLIB_OK 0 +#define DHTLIB_ERROR_CHECKSUM -1 +#define DHTLIB_ERROR_TIMEOUT_A -2 +#define DHTLIB_ERROR_BIT_SHIFT -3 +#define DHTLIB_ERROR_SENSOR_NOT_READY -4 +#define DHTLIB_ERROR_TIMEOUT_C -5 +#define DHTLIB_ERROR_TIMEOUT_D -6 +#define DHTLIB_ERROR_TIMEOUT_B -7 +#define DHTLIB_WAITING_FOR_READ -8 + +#ifndef DHTLIB_INVALID_VALUE +#define DHTLIB_INVALID_VALUE -999 +#endif + + +// bits are timing based (datasheet) +// 26-28us ==> 0 +// 70 us ==> 1 +// See https://github.com/RobTillaart/DHTNew/issues/11 +#ifndef DHTLIB_BIT_THRESHOLD +#define DHTLIB_BIT_THRESHOLD 50 +#endif + + +class DHTNEW +{ +public: + + DHTNEW(uint8_t pin); + + // 0 = unknown, 11 or 22 + uint8_t getType(); + void setType(uint8_t type = 0); + int read(); + + // lastRead is in MilliSeconds since start sketch + uint32_t lastRead() { return _lastRead; }; + + // preferred interface + float getHumidity() { return _humidity; }; + float getTemperature() { return _temperature; }; + + // adding offsets works well in normal range + // might introduce under- or overflow at the ends of the sensor range + void setHumOffset(float offset) { _humOffset = offset; }; + void setTempOffset(float offset) { _tempOffset = offset; }; + float getHumOffset() { return _humOffset; }; + float getTempOffset() { return _tempOffset; }; + + bool getDisableIRQ() { return _disableIRQ; }; + void setDisableIRQ(bool b ) { _disableIRQ = b; }; + + bool getWaitForReading() { return _waitForRead; }; + void setWaitForReading(bool b ) { _waitForRead = b; }; + + // set readDelay to 0 will reset to datasheet values + uint16_t getReadDelay() { return _readDelay; }; + void setReadDelay(uint16_t rd = 0) { _readDelay = rd; }; + + // minimal support for low power applications. + // after powerUp one must wait up to two seconds. + void powerUp(); + void powerDown(); + + // suppress error values of -999 => check return value of read() instead + bool getSuppressError() { return _suppressError; }; + void setSuppressError(bool b) { _suppressError = b; }; + +private: + uint8_t _dataPin = 0; + uint8_t _wakeupDelay = 0; + uint8_t _type = 0; + float _humOffset = 0.0; + float _tempOffset = 0.0; + float _humidity; + float _temperature; + uint32_t _lastRead = 0; + bool _disableIRQ = true; + bool _waitForRead = false; + bool _suppressError = false; + uint16_t _readDelay = 0; + + uint8_t _bits[5]; // buffer to receive data + int _read(); + int _readSensor(); + bool _waitFor(uint8_t state, uint32_t timeout); +}; + +// -- END OF FILE -- diff --git a/include/iarduino_NeoPixel.cpp b/include/iarduino_NeoPixel.cpp new file mode 100644 index 0000000..582daca --- /dev/null +++ b/include/iarduino_NeoPixel.cpp @@ -0,0 +1,101 @@ +#include "iarduino_NeoPixel.h" + iarduino_NeoPixel::iarduino_NeoPixel(uint8_t i, uint16_t j){ + pinOutput = i; // Сохраняем номер выхода, к которому подключены светодиоды + lenLED = j; // Сохраняем количество подключённых светодиодов + portOutput = portOutputRegister(digitalPinToPort(pinOutput)); // Получаем указатель на адрес регистра выходных значений порта на котором присутствует выход pinOutput. Функция digitalPinToPort возвращает номер, соответствующий порту на котором находится вывод pinOutput, а функция portOutputRegister возвращает указатель на адрес регистра выходных значений этого порта + pinMask = digitalPinToBitMask(pinOutput); // Получаем маску вывода, это байт в котором установлен только один бит. Порядковый номер этого бита, соответствует номеру вывода pinOutput (от 0 до 7) на порту portOutput +} + +bool iarduino_NeoPixel::begin(void){ + pinMode (pinOutput, OUTPUT); // Конфигурируем вывод pinOutput как выход + digitalWrite (pinOutput, LOW); // Устанавливаем на выходе pinOutput уровень логического «0» + lenRGB = lenLED*3; // Получаем размер массива цветов светодиодов + if((arrRGB = (uint8_t *) malloc(lenRGB))){ // Выделяем область памяти, размером lenRGB байт, под массив для хранения цветов светодиодов, адрес первой ячейки памяти массива передаём указателю arrRGB + memset (arrRGB, 0, lenRGB); // Сбрасываем в 0 все байты массива по указателю arrRGB + }else {lenRGB=0; return false;} // Если память выделить не удалось, то указываем размер массива равным 0 байт и возвращаем false + return true; // Возвращаем флаг успешной инициализации +} + +void iarduino_NeoPixel::setColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t j){ + if(lenRGB){ // Если была успешно вызвана функция begin, то ... + if(j<127){ r=map(j, 0,126,0, r); g=map(j, 0,126,0, g); b=map(j, 0,126,0, b);}else // Если требуется затемнить цвет, то ... + if(j>127){ r=map(j,128,255,r,255); g=map(j,128,255,g,255); b=map(j,128,255,b,255);} // Если требуется осветлить цвет, то ... + if(n == NeoPixelAll){ // Если требуется установить цвета для всех светодиодов, то ... + for(uint16_t i=0; i n){ // Если требуется установть цвет только одному светодиоду и количество светодиодов больше чем номер нужного светодиода, то ... + arrRGB[n*3+0] = g; // Устанавливаем цвет Green (зелёный) + arrRGB[n*3+1] = r; // Устанавливаем цвет Red (красный) + arrRGB[n*3+2] = b; // Устанавливаем цвет Blue (синий) + } + } +} + +void iarduino_NeoPixel::write(){ + uint8_t bitLow = *portOutput & ~pinMask; // Получаем значение, запись которого в регистр по адресу portOutput, приведёт к установке логического «0» на вывхде pinOutput, не затрагивая уровни других выводов + uint8_t bitHigh = *portOutput | pinMask; // Получаем значение, запись которого в регистр по адресу portOutput, приведёт к установке логической «1» на вывхде pinOutput, не затрагивая уровни других выводов + uint8_t byte = 0; + uint8_t bit = 8; + + if(lenRGB){ // Передаём данные, если количество светодиодов больше 0 и была успешно вызвана функция begin + +// Время передачи одного бита = 1250 нс (20 м.т.) +// ______ +// | 375нс| 875нс (14мт) +// | (6мт)|________________ передача логического «0» +// _______________ +// | 875 нс | 375нс +// | (14 м.т.) |_______ передача логической «1» +// +// HHHHHH XXXXXXXX LLLLLL уровни машинный тактов (м.т.) 1 м.т. = 62,5 нс +// 123456 78901234 567890 + + noInterrupts(); // Запрещаем исполнение прерываний во время вывода данных + + asm volatile( + // Команды Assembler + "getbyte: \n\t" // Метака getbyte. + "ldi %[bit] , 8 \n\t" // 1 м.т. => T=01 bit = 8 Указываем что в передаваемом байте есть 8 не переданных битов + "ld %[byte] , %a[arr]+ \n\t" // 2 м.т. => T=03 byte = *arr++ Получаем 1 байт для передачи, копируя его из СОЗУ в регистр byte. Адрес ячейки СОЗУ берётся из указателя arr, значение которого мы увеличиваем после получения байта + "sbiw %[len] , 1 \n\t" // 2 м.т. => T=05 len-- Уменьшаем значение регистра len (счётчик количества переданных байт) на 1 + "brne sendbyte \n\t" // 2 м.т. => T=07 if(len>0){goto sendbyte} Если полученный байт не за пределами счетчика len, то переходим к блоку sendbyte для передачи этого байта + "rjmp exitsend \n\t" // 2 м.т. goto exitsend Выходим из Ассемблерной вставки + + "sendbyte: \n\t" // (для 0/1) (для 0/1) Метка sendbyte. + "st %a[port], %[high] \n\t" // 2 м.т. => T=02 port = high Устанавливаем на выводе pinOutput уровень логической «1» (записывая в порт port значение регистра high) + "nop \n\t" // 1 м.т. => T=03 Пропускаем один машинный такт (м.т.) + "nop \n\t" // 1 м.т. => T=04 Пропускаем один машинный такт (м.т.) + "nop \n\t" // 1 м.т. => T=05 Пропускаем один машинный такт (м.т.) + "sbrs %[byte] , 7 \n\t" // 1/2 м.т. => T=06/07 if(byte & b10000000){jump} Если установлен 7 бит в регистре byte, то следующая строка пропускается. Команда выполняется за 2 м.т. если условие соблюдается, иначе за 1 м.т. + "st %a[port], %[low] \n\t" // 2 м.т. => T=08 port = high Устанавливаем на выводе pinOutput уровень логического «0» (записывая в порт port значение регистра low) + "rol %[byte] \n\t" // 1 м.т. => T=09/08 byte <<= 1 Сдвигаем влево все биты находящиеся в byte, (т.к. при передаче бита мы сверяемся со значением старшего бита) + "nop \n\t" // 1 м.т. => T=10/09 Пропускаем один машинный такт (м.т.) + "nop \n\t" // 1 м.т. => T=11/10 Пропускаем один машинный такт (м.т.) + "nop \n\t" // 1 м.т. => T=12/11 Пропускаем один машинный такт (м.т.) + "nop \n\t" // 1 м.т. => T=13/12 Пропускаем один машинный такт (м.т.) + "nop \n\t" // 1 м.т. => T=14/13 Пропускаем один машинный такт (м.т.) + "nop \n\t" // 1 м.т. => T=15/14 Пропускаем один машинный такт (м.т.) + "st %a[port], %[low] \n\t" // 2 м.т. => T=17/16 port = low Устанавливаем на выводе pinOutput уровень логического «0» (записывая в порт port значение регистра low) + "dec %[bit] \n\t" // 1 м.т. => T=01 bit-- Уменьшаем значение регистра bit (счетчик не переданных битов байта) на 1 + "breq getbyte \n\t" // 1/2 м.т. => T=03 if(bit==0){goto} Если значение bit равно 0 (все биты переданы), то переходим к метке getbyte (для получения следующего байта) + "rjmp sendbyte \n\t" // 2 м.т. => T=05 goto sendbyte Переходим к передачи следующего бита текущего байта + + "exitsend: \n" + :// Список выходных параметров + [port] "+e" (portOutput), // +e один из двухбайтных регистров указателей x,y,z Адрес порта для передачи данных + [byte] "+r" (byte), // +r любой однобайтный регистр от r0 до r31 Передаваемый байт данных + [bit] "+r" (bit) // +r любой однобайтный регистр от r0 до r31 Количество еще не переданных бит из byte + :// Список входных параметров + [low] "r" (bitLow), // r любой однобайтный регистр от r0 до r31 Значение которое требуется записать в порт, чтоб на выводе pinOutput установился логический «0» + [high] "r" (bitHigh), // r любой однобайтный регистр от r0 до r31 Значение которое требуется записать в порт, чтоб на выводе pinOutput установилась логическая «1» + [arr] "e" (arrRGB), // e один из двухбайтных регистров указателей x,y,z Адрес первой ячейки блока памяти в котором находится массив данных + [len] "w" (lenRGB+1) // w один из верхних двухбайтных регистров r24,r26,r28,r30 Счетчик количества переданных байтов + ); + interrupts(); // Возобновляем исполнение прерываний (если таковые были) + } +} + +void iarduino_NeoPixel::setColor(uint16_t n, uint32_t rgb, uint8_t j){setColor(n, (rgb&0xFF0000)>>16, (rgb&0x00FF00)>>8, (rgb&0x0000FF), j);} diff --git a/include/iarduino_NeoPixel.h b/include/iarduino_NeoPixel.h new file mode 100644 index 0000000..f64b282 --- /dev/null +++ b/include/iarduino_NeoPixel.h @@ -0,0 +1,39 @@ +// Библиотека для работы с адресными светодиодами WS2812B: http://iarduino.ru/shop/Expansion-payments/neopixel-trema-modul.html +// Версия: 1.0.0 +// Последнюю версию библиотеки Вы можете скачать по ссылке: http://iarduino.ru/file/296.html +// Подробное описание функции бибилиотеки доступно по ссылке: http://wiki.iarduino.ru/page/adresnye-svetodiody-moduli-neopixel/ +// Библиотека является собственностью интернет магазина iarduino.ru и может свободно использоваться и распространяться! +// При публикации устройств или скетчей с использованием данной библиотеки, как целиком, так и её частей, +// в том числе и в некоммерческих целях, просим Вас опубликовать ссылку: http://iarduino.ru +// Автор библиотеки: Панькин Павел +// Если у Вас возникли технические вопросы, напишите нам: shop@iarduino.ru + +#ifndef iarduino_NeoPixel_h +#define iarduino_NeoPixel_h + +#if defined(ARDUINO) && (ARDUINO >= 100) +#include +#else +#include +#endif + +#define NeoPixelAll 65535L // Константа указывающая, что цвет применяется ко всем светодиодам + +class iarduino_NeoPixel{ + public: iarduino_NeoPixel (uint8_t, uint16_t=0); // Подключение (№ вывода к которому подключены светодиоды, количество подключённых светодиодов) + void setColor (uint16_t, uint8_t, uint8_t, uint8_t, uint8_t=127); // Указание цвета (№ светодиода, R,G,B, яркость) + void setColor (uint16_t, uint32_t, uint8_t=127); // Указание цвета (№ светодиода, RGB, яркость) + bool begin (void); // Инициализация работы со светодиодами + void write (void); // Запись данных в светодиоды + uint16_t count (void){return lenRGB? lenLED:0;} // Получение количества светодиодов + uint8_t * getPointer (void){return lenRGB? arrRGB:0;} // Получение указателя на массив с цветами для светодиодов + private: + volatile uint8_t * portOutput; // Объявляем указатель на адрес регистра выходных значений порта. В этом регистре находится один байт, биты которого и являются логическими уровнями на выходе порта + uint8_t * arrRGB; // Объявляем указатель на массив с цветами для светодиодов + uint8_t pinOutput; // Определяем переменную для хранения номера вывода используемого как выход для подключения светодиодов. + uint8_t pinMask; // Объявляем переменную для хранения маски вывода. Она хранит один байт в котором установлен только один бит. Порядковый номер этого бита, соответствует номеру вывода на порту (от 0 до 7) + uint16_t lenRGB=0; // Определяем количество байт в массиве с цветами светодиодов (будет равно количеству светодиодов * RGB) + uint16_t lenLED; // Объявляем количество свтодиодов +}; + +#endif diff --git a/include/iarduino_RTC.cpp b/include/iarduino_RTC.cpp new file mode 100644 index 0000000..959c79b --- /dev/null +++ b/include/iarduino_RTC.cpp @@ -0,0 +1,134 @@ +#include "iarduino_RTC.h" + +// Вывод даты и времени +char* iarduino_RTC::gettime(String i){char j[i.length()+1]; i.toCharArray(j, i.length()); j[i.length()]=0; return gettime(j);} +char* iarduino_RTC::gettime(const char* i){ + uint8_t j, k, n; bool f; // Объявляем локальные переменные + if(valRequest > millis()){valRequest=0;} // Избавляемся от переполнения millis() +// Получаем текущее время: + if(valPeriod == 0) {funcReadTime();}else // Если минимальный период опроса модуля == 0 минут, то получаем время из регистров модуля, иначе ... + if(valRequest == 0 || (valPeriod+valRequest <= millis())) {funcReadTime();}else // Если время последнего опроса модуля превысило минимальный период опроса модуля, то получаем время из регистров модуля, иначе ... + {funcCalculationTime();} // Получаем время без обращения к модулю (время рассчитывая исходя из millis и valRequest) + funcSetMoreTime(); // Преобразуем переменные не читаемые из модуля +// Вычисляем размер блока памяти под строку с ответом: + n=strlen(i)+1; // Определяем размер равный введённой строке (i) + 1 символ конца строки + for(j=0; j0){n++;} if(k>9){n++;} if(k>11){n++;} // Увеличиваем размер (n) в соостветствии со значениём найденного служебного символа + }}} +// Выделяем блок памяти под строку с ответом: + free(charReturn); // Освобождаем ранее созданный блок памяти + charReturn = (char*) malloc(n); // Выделяем новый блок памяти размером n байт +// Заполняем выделенный блок памяти: + n=0; // Определяем позицию в блоке памяти для следующего подставляемого значения + for(j=0; j6 ){i=6; } charReturn[n]=f?32:i+48; break; + /* 2 знака */ case 1: if(i>99){i=99;} charReturn[n]=f?32:i/10+48; charReturn[n+1]=f?32:i%10+48; break; + /* AM / PM */ case 2: if(i>6 ){i=6; } charReturn[n]=f?32:charMidday[i]; charReturn[n+1]=f?32:charMidday[i+1]; break; + /* дн / мес */ case 3: if(i>54){i=54;} charReturn[n]=f?32:charDayMon[i]; charReturn[n+1]=f?32:charDayMon[i+1]; charReturn[n+2]=f?32:charDayMon[i+2]; break; + /* 4 знака */ case 4: if(i>99){i=99;} charReturn[n]=f?32:(valCentury-1)/10+48; charReturn[n+1]=f?32:(valCentury-1)%10+48; charReturn[n+2]=f?32:i/10+48; charReturn[n+3]=f?32:i%10+48; break; + } +} + +// Установка даты и времени +void iarduino_RTC::settime(int i1, int i2, int i3, int i4, int i5, int i6, int i7){ // (сек, мин, час, день, мес, год, день_недели) + funcWriteTime(i1, i2, i3, i4, i5, i6, i7); // Записываем дату и время в регистры модуля + funcReadTime(); // Читаем дату и время из регистров модуля + funcSetMoreTime(); // Корректируем переменные не читаемые из модуля (hours, midday) +} + +// Установка даты и времени от начала эпохи Unix +void iarduino_RTC::settimeUnix(uint32_t i){ // (сек) + uint32_t j; uint8_t k; bool f=true; // Объявляем временные переменные. + seconds = i % 60; // Получаем текущее значение секунд. (остаток от деления секунд прошедших с начала эпохи Unix на количество секунд в минуте) + i = (i-seconds) / 60; // Получаем количество минут прошедших с начала эпохи Unix. + minutes = i % 60; // Получаем текущее значение минут. (остаток от деления минут прошедших с начала эпохи Unix на количество минут в часе) + i = (i-minutes) / 60; // Получаем количество часов прошедших с начала эпохи Unix. + Hours = i % 24; // Получаем текущее значение часов. (остаток от деления часов прошедших с начала эпохи Unix на количество часов в дне) + i = (i-Hours) / 24; // Получаем количество дней прошедших с начала эпохи Unix. + j = 0; while((((j+1)*365)+((j+2)/4))<=i){j++;} // Получаем количество лет прошедших с начала эпохи Unix. + weekday = (i+4) % 7; // Получаем текущий день недели. (0-воскресенье, 1-понедельник, ... , 6-суббота) + i = i - (j*365) - ((j+1)/4); // Получаем количество дней прошедших в текущем году. + valCentury = ((1970+j)/100)+1; // Получаем текущий век. + year = (1970+j)%100; // Получаем две последние цифры текущего года. + k = ((1970+j)%4)==0?29:28; // Получаем количество дней в феврале текущего года. + month = 0; while(f){month++; j=month; j=(((j+1)%2)^(j<8))?31:(j==2?k:30); if(i>=j){i-=j;}else{f=false;}} + day = i+1; // Получаем текущий день. + // Устанавливаем время: + settime(seconds, minutes, Hours, day, month, year, weekday); +} + +// Чтение даты и времени в переменные из регистров модуля: +void iarduino_RTC::funcReadTime(void){ // (без параметров) + seconds = arrCalculationTime[0] = funcConvertCodeToNum(objClass -> funcReadTimeIndex(0)); // Получаем секунды + minutes = arrCalculationTime[1] = funcConvertCodeToNum(objClass -> funcReadTimeIndex(1)); // Получаем минуты + Hours = arrCalculationTime[2] = funcConvertCodeToNum(objClass -> funcReadTimeIndex(2)); // Получаем часы + day = arrCalculationTime[3] = funcConvertCodeToNum(objClass -> funcReadTimeIndex(3)); // Получаем день + month = arrCalculationTime[4] = funcConvertCodeToNum(objClass -> funcReadTimeIndex(4)); // Получаем месяц + year = arrCalculationTime[5] = funcConvertCodeToNum(objClass -> funcReadTimeIndex(5)); // Получаем год + weekday = arrCalculationTime[6] = funcConvertCodeToNum(objClass -> funcReadTimeIndex(6))-1; // Получаем день недели (в регистре значение от 1 до 7, а в переменной от 0 до 6) + Unix = funcCalculationUnix(); // Получаем количество секунд (прошедших с начала эпохи Unix) + valRequest = millis(); // Сохраняем время данного запроса +} + +// Запись даты и времени в регистры модуля: +void iarduino_RTC::funcWriteTime(int i1, int i2, int i3, int i4, int i5, int i6, int i7){ // + if(i1<=60 && i1>=0){objClass -> funcWriteTimeIndex(0, funcConvertNumToCode(i1 ));} // Сохраняем секунды + if(i2<=60 && i2>=0){objClass -> funcWriteTimeIndex(1, funcConvertNumToCode(i2 ));} // Сохраняем минуты + if(i3<=23 && i3>=0){objClass -> funcWriteTimeIndex(2, funcConvertNumToCode(i3 ));} // Сохраняем часы + if(i4<=31 && i4>=1){objClass -> funcWriteTimeIndex(3, funcConvertNumToCode(i4 ));} // Сохраняем день + if(i5<=12 && i5>=1){objClass -> funcWriteTimeIndex(4, funcConvertNumToCode(i5 ));} // Сохраняем месяц + if(i6<=99 && i6>=0){objClass -> funcWriteTimeIndex(5, funcConvertNumToCode(i6 ));} // Сохраняем год + if(i7<= 6 && i7>=0){objClass -> funcWriteTimeIndex(6, funcConvertNumToCode(i7+1));} // Сохраняем день недели (в регистре значение от 1 до 7, а в переменной от 0 до 6) +} + +// Расчёт времени без обращения к модулю: +void iarduino_RTC::funcCalculationTime(void){ // (без параметров) + uint32_t i=(millis()-valRequest)/1000; // Определяем количество секунд (прошедших после последнего обращения к модулю) + uint8_t j=30 + ( (arrCalculationTime[4] + (arrCalculationTime[4]>7?1:0)) % 2 ); // Определяем количество дней в месяце (31, 30, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + if(arrCalculationTime[4]==2){j=28+((((uint16_t)valCentury-1)*100+arrCalculationTime[5])%4?0:1);}// Если текущий месяц - февраль, то меняем на ... (31, 28/29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + i+=arrCalculationTime[0]; seconds = i%60; i/=60; // Добавляем к прошедним секундам (i) посление прочитанные секунды (arrCalculationTime[0]), результатом будет остаток, а значение i превращаем в минуты + i+=arrCalculationTime[1]; minutes = i%60; i/=60; // Добавляем к прошедним минутам (i) посление прочитанные минуты (arrCalculationTime[1]), результатом будет остаток, а значение i превращаем в часы + i+=arrCalculationTime[2]; Hours = i%24; i/=24; // Добавляем к прошедним часам (i) посление прочитанные часы (arrCalculationTime[2]), результатом будет остаток, а значение i превращаем в дни + weekday = arrCalculationTime[6]+i; if(weekday>6){weekday=0;} // День недели увеличится на (i) от прочитанного дня недели (arrCalculationTime[6]) + i+=arrCalculationTime[3]; day = i%(j+1); i/=(j+1); day+=i; // Добавляем к прошедним дням (i) посление прочитанные дни (arrCalculationTime[3]), результатом будет остаток, а значение i превращаем в месяцы + i+=arrCalculationTime[4]; month = i%13; i/=13; month+=i; // Добавляем к прошедним месяцам (i) посление прочитанные месяцы (arrCalculationTime[4]), результатом будет остаток, а значение i превращаем в годы + i+=arrCalculationTime[5]; year = i%100; // Добавляем к прошедним годам (i) посление прочитанные годы (arrCalculationTime[5]), результатом будет остаток, а значение i превращаем в дни + Unix = funcCalculationUnix(); // Получаем количество секунд прошедших с начала эпохи Unix +} + +// Расчёт количества cекунд прошедших с начала эпохи Unix: +uint32_t iarduino_RTC::funcCalculationUnix(void){ // (без параметров) + uint32_t i; // Объявляем переменную для хранения результата. (рассчёты производятся из значений переменных: seconds, minutes, Hours, day, month, year и valCentury). + uint32_t j = (uint32_t)(valCentury-1) * 100 + year; // Определяем текущий год с учётом века. (valCentury - век, year - последние два знака текущего года). + i = j - 1970; // Определяем количество прошедших лет (с 01.01.1970 г.) + i = i * 365 + ((i+1)/4); // Определяем количество дней в прошедших годах ((i+1)/4) - количество прошедших високосных лет (без учёта текущего года). + i += (month-1)*30 + ( (month + (month<9?0:1) )/2 ); // Добавляем количество дней в прошедших месяцах ((month+(month<9?0:1))/2) - количество прошедших месяцев текущего года, содержащих 31 день. + i -= month>2? (j%4==0?1:2) : 0; // Вычитаем 1 или 2 дня из февраля текущего года ((month>2) - если февраль уже прошел, j%4==0 - если текущий год високосный) + i += day-1; // Добавляем количество прошедших дней этого месяца + i *= 86400; // Получаем количество секунд прошедших дней + i += (uint32_t)Hours * 3600 + (uint32_t)minutes * 60 + seconds; // Добавляем количество секунд текущего дня + return i; // Возвращаем результат +} diff --git a/include/iarduino_RTC.h b/include/iarduino_RTC.h new file mode 100644 index 0000000..94203ad --- /dev/null +++ b/include/iarduino_RTC.h @@ -0,0 +1,98 @@ +// Библиотека для работы с часами реального времени: (на чипе DS1302) http://iarduino.ru/shop/Expansion-payments/rtc-modul-ds1302.html +// (на чипе DS1307) http://iarduino.ru/shop/Expansion-payments/chasy-realnogo-vremeni-rtc-trema-modul.html +// (на чипе DS3231) http://iarduino.ru/shop/Expansion-payments/chasy-realnogo-vremeni-ds3231.html +// Версия: 1.3.4 +// Последнюю версию библиотеки Вы можете скачать по ссылке: http://iarduino.ru/file/235.html +// Подробное описание функции бибилиотеки доступно по ссылке: http://wiki.iarduino.ru/page/chasy-realnogo-vremeni-rtc-trema-modul/ +// Библиотека является собственностью интернет магазина iarduino.ru и может свободно использоваться и распространяться! +// При публикации устройств или скетчей с использованием данной библиотеки, как целиком, так и её частей, +// в том числе и в некоммерческих целях, просим Вас опубликовать ссылку: http://iarduino.ru +// Автор библиотеки: Панькин Павел +// Если у Вас возникли технические вопросы, напишите нам: shop@iarduino.ru + +#ifndef iarduino_RTC_h // +#define iarduino_RTC_h // + // +#define RTC_UNDEFINED 0 // Модуль часов реального времени не определён + // +#if defined(ARDUINO) && (ARDUINO >= 100) // +#include // +#else // +#include // +#endif // + // +#define RTC_ENABLE_DS3231 // + +class iarduino_RTC_BASE{ // Объявляем полиморфный класс + public: // + virtual void begin (void); // Объявляем функцию инициализации модуля (без параметров) + virtual uint8_t funcReadTimeIndex (uint8_t); // Объявляем функцию чтения 1 значения из регистров даты и времени (0-секунды / 1-минуты / 2-часы / 3-день / 4-месяц / 5-год / 6-день недели) + virtual void funcWriteTimeIndex (uint8_t, uint8_t); // Объявляем функцию записи 1 значения в регистры даты и времени (0-секунды / 1-минуты / 2-часы / 3-день / 4-месяц / 5-год / 6-день недели, значение) +}; // + +#include "iarduino_RTC_DS3231.h" // Подключаем файл iarduino_RTC_DS3231.h + // +class iarduino_RTC{ // + public: // + /** Конструктор класса **/ // + iarduino_RTC(uint8_t i, uint8_t j=SS, uint8_t k=SCK, uint8_t n=MOSI){ // Конструктор основного класса (название [, вывод SS/RST [, вывод SCK/CLK [, вывод MOSI/DAT]]]) + switch(i){ // Тип выбранного модуля + #ifdef RTC_ENABLE_DS1302 // + case RTC_DS1302: objClass = new iarduino_RTC_DS1302(j,k,n); break; // Если используется модуль на базе чипа DS1302, то присваеиваем указателю objClass ссылку на новый объект производного класса iarduino_RTC_DS1302 переопределяя на него все виртуальные функции полиморфного класса iarduino_RTC_BASE + #endif // + #ifdef RTC_ENABLE_DS1307 // + case RTC_DS1307: objClass = new iarduino_RTC_DS1307; break; // Если используется модуль на базе чипа DS1307, то присваеиваем указателю objClass ссылку на новый объект производного класса iarduino_RTC_DS1307 переопределяя на него все виртуальные функции полиморфного класса iarduino_RTC_BASE + #endif // + #ifdef RTC_ENABLE_DS3231 // + case RTC_DS3231: objClass = new iarduino_RTC_DS3231; break; // Если используется модуль на базе чипа DS3231, то присваеиваем указателю objClass ссылку на новый объект производного класса iarduino_RTC_DS3231 переопределяя на него все виртуальные функции полиморфного класса iarduino_RTC_BASE + #endif // + } // + } // + /** Пользовательские функции **/ // + void begin (void) {objClass -> begin(); gettime();} // Определяем функцию инициализации модуля (без параметров) + void period (uint8_t i) {valPeriod=i; valPeriod*=60000;} // Определяем функцию задания минимального периода обращения к модулю (i = период в минутах) + void blinktime (uint8_t i, float j=1) {valBlink=i; valFrequency=uint32_t(1000/j);} // Определяем функцию позволяющую мигать одним из параметров времени (i = 0-нет / 1-сек / 2-мин / 3-час / 4-день / 5-мес / 6-год / 7-день_недели / 8-полдень , j = частота мигания в Гц) + void gettime (void) {gettime("");} // Определяем функцию получения даты и времени из переменных (без параметров) + char* gettime (String); // Объявляем функцию «дублёр» получения даты и времени из переменных (строка с параметрами) + char* gettime (const char*); // Объявляем функцию получения даты и времени ввиде строки (строка с параметрами) + void settime (int, int=-1, int=-1, int=-1, int=-1, int=-1, int=-1); // Объявляем функцию установки даты и времени (сек, мин, час, день, мес, год, день_недели) + uint32_t gettimeUnix (void) {gettime(""); return Unix;} // Определяем функцию получения cекунд прошедших с начала эпохи Unix (без параметров) + void settimeUnix (uint32_t); // Объявляем функцию установки cекунд прошедших с начала эпохи Unix (сек) + // + /** Переменные доступные для пользователя **/ // + uint8_t seconds = 0; // Секунды 0-59 + uint8_t minutes = 0; // Минуты 0-59 + uint8_t hours = 1; // Часы 1-12 + uint8_t Hours = 0; // Часы 0-23 + uint8_t midday = 0; // Полдень 0-1 (0-am, 1-pm) + uint8_t day = 1; // День месяца 1-31 + uint8_t weekday = 0; // День недели 0-6 (0-воскресенье, 1-понедельник, ... , 6-суббота) + uint8_t month = 1; // Месяц 1-12 + uint8_t year = 0; // Год 0-99 (без учёта века) + uint32_t Unix = 0; // Секунды прошедшие с начала эпохи Unix (01.01.1970 00:00:00 GMT) + // + /** Внутренние переменные **/ // + iarduino_RTC_BASE* objClass; // Объявляем указатель на объект полиморфного класса (функции данного класса будут переопределены, т.к. указателю будет присвоена ссылка на производный класс) + char* charReturn = (char*) malloc(1); // Определяем указатель на символьную область памяти в 1 байт (указатель будет ссылаться на строку вывода времени) + const char* charInput = "waAdhHimsyMDY"; // Определяем константу-строку с символами требующими замены (данные символы заменяются функцией gettime на значение времени) + const char* charMidday = "ampmAMPM"; // Определяем константу-строку для вывода полудня (am / pm / AM / PM) + const char* charDayMon = "SunMonTueWedThuFriSatJanFebMarAprMayJunJulAugSepOctNovDec"; // Определяем константу-строку для вывода дня недели или месяца (Mon ... Sun / Jan ... Dec) + uint8_t arrCalculationTime[7]; // Объявляем массив для рассчёта времени без обращения к модулю (для хранения последних, прочитанных из модуля, значений даты и времени) + uint8_t valBlink = 0; // Определяем параметр времени, который должен мигать (1-сек / 2-мин / 3-час / 4-день / 5-мес / 6-год / 7-день_недели / 8-полдень) + uint32_t valFrequency = 1000; // Определяем частоту мигания параметра времени для функции blinktime (по умолчанию 1 Гц) + uint8_t valCentury = 21; // Определяем переменную для хранения текущего века (по умолчанию 21 век) + uint16_t valPeriod = 0; // Определяем минимальный период опроса модуля (в минутах, от 00 до 255) + uint32_t valRequest = 0; // Определяем время последнего чтения регистров времени + private: // + /** Внутренние функции **/ // + void funcReadTime (void); // Объявляем функцию чтения даты и времени из регистров модуля (без параметров) + void funcWriteTime (int, int, int, int, int, int, int); // Объявляем функцию записи даты и времени в регистры модуля (без параметров) + uint8_t funcConvertCodeToNum (uint8_t i) {return (i >> 4)*10 + (i & 0x0F);} // Определяем функцию преобразования двоично-десятичного кода в число (код) + uint8_t funcConvertNumToCode (uint8_t i) {return ((i/10) << 4) + (i%10);} // Определяем функцию преобразования числа в двоично-десятичный код (число) + void funcSetMoreTime (void){hours=(Hours%12)?(Hours%12):12; midday=(Hours<12)?0:1;} // Корректировка переменных не читаемых из модуля (без параметров) + void funcCalculationTime (void); // Объявляем функцию расчёта времени без обращения к модулю (без параметров) + uint32_t funcCalculationUnix (void); // Объявляем функцию расчёта cекунд прошедших с начала эпохи Unix (без параметров) + void funcFillChar (uint8_t, uint8_t, uint8_t, uint8_t); // Объявляем функцию заполнения строки вывода времени (данные, тип данных, позиция для вставки, мигание) +}; // + // +#endif // diff --git a/include/iarduino_RTC_DS3231.h b/include/iarduino_RTC_DS3231.h new file mode 100644 index 0000000..b3d3288 --- /dev/null +++ b/include/iarduino_RTC_DS3231.h @@ -0,0 +1,42 @@ +#ifndef iarduino_RTC_DS3231_h // +#define iarduino_RTC_DS3231_h // +#define RTC_DS3231 3 // Модуль часов реального времени с протоколом передачи данных I2C, памятью 019x8, температурной компенсацией, двумя будильниками и встроенным кварцевым резонатором +#include "iarduino_RTC_I2C.h" // Подключаем файл iarduino_RTC_I2C.h - для работы с шиной I2C + // +class iarduino_RTC_DS3231: public iarduino_RTC_BASE{ // + public: // + /** функции доступные пользователю **/ // +// Инициализация модуля: // + void begin(void){ // (без параметров) +// Инициализация работы с шиной I2C: // + objI2C.begin(100); // (скорость шины в кГц) +// Установка флагов управления и состояния модуля: // + varI=objI2C.readByte(valAddress, 0x02); if( varI & 0b01000000 ){objI2C.writeByte(valAddress, 0x02, (varI&~0b01000000) );} // (если установлен 6 бит в 2 регистре, то сбрасываем его - переводим модуль в 24 часовой режим) + varI=objI2C.readByte(valAddress, 0x0E); if( varI & 0b11011111 ){objI2C.writeByte(valAddress, 0x0E, (varI&~0b11011111) );} // (если установлены 7,6,4,3,2,1 и 0 биты в 14 регистре, то сбрасываем их - разрешаем генератору работать от батарейки, запрещаем выводу SQW работать от батарейки, выводим меандр с частотой 1Гц на вывод SQW, переводим вывод INT/SQW в режим SQW, запрещаем прерывания будильников) + varI=objI2C.readByte(valAddress, 0x0F); if((varI & 0b10000011) || !(varI & 0b00001000)){objI2C.writeByte(valAddress, 0x0F, (varI&~0b10000011)|0b00001000 );} // (если установлены 7,1 и 0 биты или сброшен 3 бит в 15 регистре, то сбрасываем 7,1 и 0 биты, а 3 устанавливаем - сбрасываем флаг остановки генератора, разрешаем меандр с частотой 32768Гц на выводе 32kHz, сбрасываем флаги будильников) + } // + // +// Чтение одного значения из регистров даты и времени модуля: // + uint8_t funcReadTimeIndex(uint8_t i){ // (i = 0-секунды / 1-минуты / 2-часы / 3-день / 4-месяц / 5-год / 6-день недели) + delay(1); // + return objI2C.readByte(valAddress, arrTimeRegAddr[i]) & arrTimeRegMack[i]; // + } // + // +// Запись одного значения в регистры даты и времени модуля: // + void funcWriteTimeIndex(uint8_t i, uint8_t j){ // (i = 0-секунды / 1-минуты / 2-часы / 3-день / 4-месяц / 5-год / 6-день недели, j = значение) + varI=funcReadTimeIndex(i); // Читаем данные из регистра i + j |= ~arrTimeRegMack[i] & varI; // Устанавливаем биты значения j по маске arrTimeRegMack[i] в прочитанные из регистра i + j &= arrTimeRegMack[i] | varI; // Сбрасываем биты значения j по маске arrTimeRegMack[i] в прочитанные из регистра i + objI2C.writeByte(valAddress, arrTimeRegAddr[i], j); // Сохраняем значение j в регистр arrTimeRegAddr[i] + } // + // + private: // + /** Внутренние переменные **/ // + iarduino_I2C objI2C; // Создаём объект для работы с шиной I2C + uint8_t valAddress = 0x68; // Адрес модуля на шине I2C + uint8_t arrTimeRegAddr[7] = {0x00,0x01,0x02,0x04,0x05,0x06,0x03}; // Определяем массив с адресами регистров даты и времени (сек, мин, час, день, месяц, год, день недели) + uint8_t arrTimeRegMack[7] = {0x7F,0x7F,0x3F,0x3F,0x1F,0xFF,0x07}; // Определяем маскировочный массив для регистров даты и времени (при чтении/записи, нужно совершить побитовое «и») + uint8_t varI; // +}; // + // +#endif // diff --git a/include/iarduino_RTC_I2C.h b/include/iarduino_RTC_I2C.h new file mode 100644 index 0000000..6c2cb45 --- /dev/null +++ b/include/iarduino_RTC_I2C.h @@ -0,0 +1,461 @@ +#ifndef iarduino_I2C_h // Разрешаем включить данный код в скетч, только если он не был включён ранее +#define iarduino_I2C_h // Запрещаем повторное включение данного кода в скетч + // +// Определяем тип реализации шины I2C: // + #define iarduino_I2C_SW // Объявляем константу iarduino_I2C_SW - Возможна программная реализация шины I2C. +#if (!defined(pin_SW_SDA) || !defined(pin_SW_SCL)) // Если выводы не определены пользователем, то ... + #undef iarduino_I2C_SW // Отменяем объявление константы iarduino_I2C_SW - Программная реализация шины I2C не возможна (так как выводы не указаны). + #if defined(ESP8266) || defined(ESP32) // Если используются указанные платы, то ... + #include // Подключаем библиотеку Wire. + #define pin_SW_SDA 255 // № вывода SDA не определён + #define pin_SW_SCL 255 // № вывода SCL не определён + #define iarduino_I2C_TW // Объявляем константу iarduino_I2C_TW - Будет использована аппаратная реализация шины I2C под управлением библиотеки Wire. + #elif defined(TwoWire_h) // Проверяем не подключена ли библиотека Wire. + #define pin_SW_SDA 255 // № вывода SDA не определён + #define pin_SW_SCL 255 // № вывода SCL не определён + #define iarduino_I2C_TW // Объявляем константу iarduino_I2C_TW - Будет использована аппаратная реализация шины I2C под управлением библиотеки Wire. + #elif (defined(SDA) && defined(SCL)) // Определяем выводы для шины I2C + #define pin_SW_SDA SDA // № вывода SDA = SDA + #define pin_SW_SCL SCL // № вывода SCL = SCL + #define iarduino_I2C_HW // Объявляем константу iarduino_I2C_HW - Будет использована аппаратная реализация шины I2C. + #elif (defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)) // Определяем выводы для шины I2C + #define pin_SW_SDA PIN_WIRE_SDA // № вывода SDA = PIN_WIRE_SDA + #define pin_SW_SCL PIN_WIRE_SCL // № вывода SCL = PIN_WIRE_SCL + #define iarduino_I2C_HW // Объявляем константу iarduino_I2C_HW - Будет использована аппаратная реализация шины I2C. + #elif (defined(SDA1) && defined(SCL1)) // Определяем выводы для шины I2C-1 + #define pin_SW_SDA SDA1 // № вывода SDA = SDA1 + #define pin_SW_SCL SCL1 // № вывода SCL = SCL1 + #define iarduino_I2C_HW_1 // Объявляем константу iarduino_I2C_HW_1 - Будет использована аппаратная реализация шины I2C-1. + #else // Если выводы определить не удалось, то ... + #define pin_SW_SDA 255 // № вывода SDA не определён - Аппаратная реализация шины I2C не возможна. + #define pin_SW_SCL 255 // № вывода SCL не определён - Аппаратная реализация шины I2C не возможна. + #endif // +#endif // + // +class iarduino_I2C_BASE{ // Определяем полиморфный класс + public: // Доступные методы и функции: +// ОСНОВНЫЕ ФУНКЦИИ: (поддерживаются библиотекой Wire) // + virtual void begin (uint32_t); // Объявляем функцию указания скорости шины I2C. Аргументы: скорость в кГц. + virtual uint8_t readByte (uint8_t, uint8_t ); // Объявляем функцию чтения байта данных из регистра модуля. Аргументы: адрес_модуля, адрес_регистра. (адрес регистра указывает модулю, данные какого регистра требуется отправить мастеру) + virtual bool writeByte (uint8_t, uint8_t, uint8_t); // Объявляем функцию записи байта данных в регистр модуля. Аргументы: адрес_модуля, адрес_регистра, байт_данных. (адрес регистра указывает модулю, в какой регистр требуется сохранить данные) + virtual uint8_t readByte (uint8_t ); // Объявляем функцию чтения байта данных из модуля. Аргументы: адрес_модуля (функция отличается тем, что она не отправляет модулю адрес регистра) + virtual bool writeByte (uint8_t, uint8_t); // Объявляем функцию записи байта данных в модуль. Аргументы: адрес_модуля, байт_данных. (функция отличается тем, что она не отправляет модулю адрес регистра) + virtual bool readBytes (uint8_t, uint8_t, uint8_t*, uint8_t); // Объявляем функцию чтения байтов данных из регистров модуля. Аргументы: адрес_модуля, адрес_первого_регистра, указатель_на_массив, количество_байт. (адрес первого регистра указывает модулю, с какого регистра требуется начать передачу данных мастеру) + virtual bool writeBytes (uint8_t, uint8_t, uint8_t*, uint8_t); // Объявляем функцию записи байтов данных в регистры модуля. Аргументы: адрес_модуля, адрес_первого_регистра, указатель_на_массив, количество_байт. (адрес первого регистра указывает модулю, начиная с какого регистра требуется сохранять данные) + virtual bool readBytes (uint8_t, uint8_t*, uint8_t); // Объявляем функцию чтения байтов данных из модуля. Аргументы: адрес_модуля, указатель_на_массив, количество_байт. (функция отличается тем, что она не отправляет модулю адрес первого регистра, а начинает цикл чтения сразу после отправки адреса модуля.) + virtual bool writeBytes (uint8_t, uint8_t*, uint8_t); // Объявляем функцию записи байтов данных в модуль. Аргументы: адрес_модуля, указатель_на_массив, количество_байт. (функция отличается тем, что после отправки адреса модуля она сразу начинает цикл отправки данных, без передачи адреса первого регистра.) +// ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ: (поддерживаются библиотекой Wire) // + virtual uint8_t getType (void); // Объявляем функцию получения типа реализации шины I2C. Аргументы: отсутствуют. + virtual bool checkAddress(uint8_t); // Объявляем функцию поиска модуля на шине I2C. Аргументы: адрес_модуля. +// ФУНКЦИИ НИЖНЕГО УРОВНЯ: (не поддерживаются библиотекой Wire) // + virtual bool start (void); // Объявляем функцию установки состояния START. Аргументы: отсутствуют. + virtual bool reStart (void); // Объявляем функцию установки состояния RESTART. Аргументы: отсутствуют. + virtual void stop (void); // Объявляем функцию установки состояния STOP. Аргументы: отсутствуют. + virtual bool sendID (uint8_t, bool); // Объявляем функцию передачи адреса модуля. Аргументы: ID-адрес модуля, бит RW (0-запись, 1-чтение). + virtual bool setByte (uint8_t); // Объявляем функцию передачи байта данных. Аргументы: байт для передачи. + virtual uint8_t getByte (bool); // Объявляем функцию получения байта данных. Аргументы: бит подтверждения ACK/NACK + private: // + virtual bool setSCL (bool); // Объявляем функцию установки уровня на линии SCL. Аргументы: логический уровень. + virtual void setSDA (bool); // Объявляем функцию установки уровня на линии SDA. Аргументы: логический уровень. + virtual bool getSDA (void); // Объявляем функцию чтения уровня с линии SDA. Аргументы: отсутствуют. +}; // + // +class iarduino_I2C: public iarduino_I2C_BASE{ // Определяем производный класс + public: // Доступные методы и функции: + // + /** ОСНОВНЫЕ ФУНКЦИИ: **/ // + // +// Функция подготовки шины I2C: // Определяем функцию подготовки шины I2C. + void begin(uint32_t speed){ // Аргумент: скорость шины в кГц. +// _ _ _ _______ _ _ _ // +// SCL: _?_?_/ OUTPUT // +// _ _ _ _ _____ _ _ _ // +// SDA: _?_?_ _/ OUTPUT // +// // + #if defined(iarduino_I2C_TW) // + // Если используется шина I2C под управлением библиотеки Wire: // + Wire.setClock(speed*1000L); // Устанавливаем скорость передачи данных по шине I2C. + Wire.begin(); // Инициируем работу на шине I2C в качестве мастера. + #elif defined(iarduino_I2C_HW) // + // Если используется аппаратная шина I2C: // + pinMode(pin_SDA, INPUT ); digitalWrite(pin_SDA, HIGH); // Определяем вывод pin_SDA как вход и подтягиваем его к Vcc. + pinMode(pin_SCL, INPUT ); digitalWrite(pin_SCL, HIGH); // Определяем вывод pin_SCL как вход и подтягиваем его к Vcc. + TWBR=((F_CPU/(speed*1000L))-16)/2; // Определяем значение регистра скорости связи TWBR: speed = F_CPU / (16 + 2 * TWBR * 4^TWPS). Значит TWBR = (F_CPU/speed - 16) / (2 * 4^TWPS), но так как биты предделителя TWPS мы далее сбросим в 0, то формула упростится до: TWBR = (F_CPU/speed - 16) / 2. Так как speed указана в кГц, а F_CPU в Гц, то умножаем speed на 1000. + if(TWBR<10){TWBR=10;} // Если значение регистра скорости TWBR стало меньше 10 (параметр speed > 400 кГ) то оставляем значение этого регистра равным 10 иначе шина будет работать нестабильно. + TWSR&=(~(_BV(TWPS1)|_BV(TWPS0))); // Определяем значение регистра статуса TWSR: оставляем все его биты не тронутыми, кроме двух битов предделитея TWPS сбрасывая их в 0. + #elif defined(iarduino_I2C_SW) // + // Если используется программная шина I2C: // + port_SDA = digitalPinToPort (pin_SDA); // Определяем номер порта для вывода pin_SDA. + port_SCL = digitalPinToPort (pin_SCL); // Определяем номер порта для вывода pin_SCL. + mask_SDA = digitalPinToBitMask (pin_SDA); // Определяем маску для вывода pin_SDA. в переменной mask_SDA будет установлен только тот бит который соответствует позиции бита управления выводом pin_SDA. + mask_SCL = digitalPinToBitMask (pin_SCL); // Определяем маску для вывода pin_SCL. в переменной mask_SCL будет установлен только тот бит который соответствует позиции бита управления выводом pin_SCL. + mod_SDA = portModeRegister (port_SDA); // Определяем указатель на адрес регистра конфигурации направления работы выводов порта port_SDA. + mod_SCL = portModeRegister (port_SCL); // Определяем указатель на адрес регистра конфигурации направления работы выводов порта port_SCL. + inp_SDA = portInputRegister (port_SDA); // Определяем указатель на адрес регистра входных значений для управления портом port_SDA. + inp_SCL = portInputRegister (port_SCL); // Определяем указатель на адрес регистра входных значений для управления портом port_SCL. + out_SDA = portOutputRegister (port_SDA); // Определяем указатель на адрес регистра выходных значений для управления портом port_SDA. + out_SCL = portOutputRegister (port_SCL); // Определяем указатель на адрес регистра выходных значений для управления портом port_SCL. + setSCL(1); // Переводим вывод SCL в режим входа с подтяжкой к Vcc + setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc + #endif // + } // + // +// Функция чтения одного байта из регистра модуля: // + uint8_t readByte(uint8_t adr, uint8_t reg){ // Определяем функцию чтения одного байта данных из регистра модуля (аргументы: адрес_модуля, адрес_регистра) + uint8_t i=0; readBytes(adr, reg, &i, 1); return i; // Объявляем переменную i и указываем её ссылку для получения 1 байта из регистра reg модуля с адресом adr + } // + // +// Функция чтения одного байта из модуля: // + uint8_t readByte(uint8_t adr){ // Определяем функцию чтения одного байта данных из регистра модуля (аргументы: адрес_модуля) + uint8_t i=0; readBytes(adr, &i, 1); return i; // Объявляем переменную i и указываем её ссылку для получения 1 байта из модуля с адресом adr + } // + // +// Функция записи одного байта в регистр модуля: // + bool writeByte(uint8_t adr, uint8_t reg, uint8_t data){ // Определяем функцию записи одного байта данных в регистр модуля (аргументы: адрес_модуля, адрес_регистра, байт_данных) + return writeBytes(adr, reg, &data, 1); // Запысываем 1 байт по ссылке на переменную data в регистр reg модуля с адресом adr + } // + // +// Функция записи одного байта в модуль: // + bool writeByte(uint8_t adr, uint8_t data){ // Определяем функцию записи одного байта данных в регистр модуля (аргументы: адрес_модуля, байт_данных) + return writeBytes(adr, &data, 1); // Запысываем 1 байт по ссылке на переменную data в модуль с адресом adr + } // + // +// Функция пакетного чтения нескольких байт данных из регистров модуля: // + bool readBytes(uint8_t adr, uint8_t reg, uint8_t *data, uint8_t sum){ // Определяем функцию пакетного чтения нескольких байт данных из регистров модуля (аргументы: адрес_модуля, адрес_первого_регистра, указатель_на_массив, количество_байт) + #if defined(iarduino_I2C_TW) // + // Если используется шина I2C под управлением библиотеки Wire: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это ответ по умолчанию. + Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся. + Wire.write(reg); // Определяем значение первого байта (reg - адреса регистра) который будет отправлен после байта адреса. Функция write() поместит указанный байт в буфер для передачи. + i=Wire.endTransmission(false); if(i){return 0;} // Выполняем инициированную ранее передачу данных (без установки состояния STOP). Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка. + if(!Wire.requestFrom( adr, sum )) {return i;} // Читаем (запрашиваем) sum байт данных от устройства с адресом adr и битом RW=1 => чтение. Функция requestFrom() возвращает количество реально принятых байтов. Так как предыдущая функция не установила состояние STOP, то состояние START данной функции будет расценено как RESTART. + while(Wire.available() && i0){ *data=getByte(sum>1); // Получаем по одному байту из очередного регистра за каждый проход цикла while. Прочитанный байт записываются по указателю data. Аргумент функции getByte имеет значение true на всех проходах цикла кроме последнего + data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму прочитанных байт sum. + #if defined(iarduino_I2C_HW) // Проверить корректность чтения каждого байта можно только на аппаратном уровне + if (sum) { if(TWSR&0xF8!=0x50) { i=0;}} // Если после чтения очередного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x50 значит произошла ошибка при чтении + else { if(TWSR&0xF8!=0x58) { i=0;}} // Если после чтения последного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x58 значит произошла ошибка при чтении + #endif // + }}}}}} stop (); return i==5; // Отправляем команду STOP и возвращаем результат успешности чтения + #endif // + } // + // +// Функция пакетного чтения нескольких байт данных из модуля: // + bool readBytes(uint8_t adr, uint8_t *data, uint8_t sum){ // Определяем функцию пакетного чтения нескольких байт данных из модуля (аргументы: адрес_модуля, указатель_на_массив, количество_байт) + #if defined(iarduino_I2C_TW) // + // Если используется шина I2C под управлением библиотеки Wire: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это ответ по умолчанию. + if(!Wire.requestFrom( adr, sum )) {return i;} // Читаем (запрашиваем) sum байт данных от устройства с адресом adr и битом RW=1 => чтение. Функция requestFrom() возвращает количество реально принятых байтов. + while(Wire.available() && i0){ *data=getByte(sum>1); // Получаем по одному байту из очередного регистра за каждый проход цикла while. Прочитанный байт записываются по указателю data. Аргумент функции getByte имеет значение true на всех проходах цикла кроме последнего + data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму прочитанных байт sum. + #if defined(iarduino_I2C_HW) // Проверить корректность чтения каждого байта можно только на аппаратном уровне + if (sum) { if(TWSR&0xF8!=0x50) { i=0;}} // Если после чтения очередного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x50 значит произошла ошибка при чтении + else { if(TWSR&0xF8!=0x58) { i=0;}} // Если после чтения последного байта пакета значение регистра состояния шины I2C Arduino TWSR с маской 0xF8 не равно 0x58 значит произошла ошибка при чтении + #endif // + }}} stop (); return i==2; // Отправляем команду STOP и возвращаем результат успешности чтения + #endif // + } // + // +// Функция пакетной записи нескольких байт данных в регистр модуля: // + bool writeBytes(uint8_t adr, uint8_t reg, uint8_t *data, uint8_t sum){ // Определяем функцию пакетной записи нескольких байт данных в регистры модуля (аргументы: адрес_модуля, адрес_первого_регистра, указатель_на_массив, количество_байт) + #if defined(iarduino_I2C_TW) // + // Если используется шина I2C под управлением библиотеки Wire: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи + Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся. + Wire.write(reg); // Определяем значение первого байта (reg - адреса регистра) который будет отправлен после байта адреса. Функция write() поместит указанный байт в буфер для передачи. + Wire.write(data, sum); // Определяем значения следующих байт (data - массив данных) который будет отправлен после байта регистра. Функция write() поместит sum элементов массива data в буфер для передачи. + i = Wire.endTransmission(); return i==0; // Выполняем инициированную ранее передачу данных. Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка. В качестве параметра функция endTransmission() может принимать флаг установки стсояния STOP - по умолчанию true. + #else // + // Если шина управляется функциями нижнего уровня данного класса: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи + if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ... + if ( sendID (adr,0) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=0 (запись), то ... + if ( setByte (reg) ) { i=3; // Если модуль ответил ACK на получение адреса регистра reg, то ... + while(sum>0){if(!setByte(*data )) { i=0;} // Передаём один байт из массива по указателю data в очередной регистр за каждый проход цикла while. Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum. + data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum. + }}}} stop (); return i==3; // Отправляем команду STOP и возвращаем результат записи. + #endif // + } // + // +// Функция пакетной записи нескольких байт данных в модуль: // + bool writeBytes(uint8_t adr, uint8_t *data, uint8_t sum){ // Определяем функцию пакетной записи нескольких байт данных в модуль (аргументы: адрес_модуля, указатель_на_массив, количество_байт) + #if defined(iarduino_I2C_TW) // + // Если используется шина I2C под управлением библиотеки Wire: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи + Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся. + Wire.write(data, sum); // Указываем массив данных который будет отправлен после байта адреса. Функция write() поместит sum элементов массива data в буфер для передачи. + i = Wire.endTransmission(); return i==0; // Выполняем инициированную ранее передачу данных. Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка. В качестве параметра функция endTransmission() может принимать флаг установки стсояния STOP - по умолчанию true. + #else // + // Если шина управляется функциями нижнего уровня данного класса: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи + if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ... + if ( sendID (adr,0) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=0 (запись), то ... + while(sum>0){if(!setByte(*data )) { i=0;} // Передаём один байт из массива по указателю data в очередной регистр за каждый проход цикла while. Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum. + data++; sum--; // Увеличиваем адрес указателя data, и уменьшаем сумму переданных байт sum. + }}} stop (); return i==2; // Отправляем команду STOP и возвращаем результат записи. + #endif // + } // + // + /** ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ: **/ // + // +// Функция получения типа реализации шины I2C: // Определяем функцию получения типа шины Ш2С. + uint8_t getType(void){ // Аргументы: отсутсвуют. + #if defined(iarduino_I2C_TW) // + return 4; // Используется аппаратная реализация шины I2C под управлением библиотеки Wire. + #elif defined(iarduino_I2C_HW) // + return 3; // Используется аппаратная реализация шины I2C. + #elif defined(iarduino_I2C_HW_1) // + return 2; // Используется аппаратная реализация шины I2C-1. + #elif defined(iarduino_I2C_SW) // + return 1; // Используется программная реализация шины I2C. + #else // + return 0; // Тип реализации шины I2C не определён. + #endif // + } // + // +// Функция проверки наличия ведомого по его адресу: // + bool checkAddress(uint8_t adr){ // Определяем функцию записи одного байта данных в регистр модуля (аргументы: адрес_регистра, байт_данных) + #if defined(iarduino_I2C_TW) // + // Если используется шина I2C под управлением библиотеки Wire: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат проверки. + Wire.beginTransmission(adr); // Инициируем передачу данных по шине I2C к устройству с адресом adr и битом RW=0 => запись. При этом сама передача не начнётся. + i=Wire.endTransmission(); return i==0; // Выполняем инициированную ранее передачу но без данных (отправится только START, байт адреса, STOP). Функция endTransmission() возвращает: 0-передача успешна / 1 - переполнен буфер для передачи / 2 - получен NACK при передаче адреса / 3 - получен NACK при передаче данных / 4 - другая ошибка. В качестве параметра функция endTransmission() может принимать флаг установки стсояния STOP - по умолчанию true. + #else // + // Если шина управляется функциями нижнего уровня данного класса: // + uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи. + if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ... + if ( sendID (adr,0) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=0 (запись), то ... + }} stop (); return i==2; // Отправляем команду STOP и возвращаем результат записи. + #endif // + } // + // + /** ФУНКЦИИ НИЖНЕГО УРОВНЯ: **/ // + // +// Функция нижнего уровня - установка состояния START: // Определяем функцию установки состояния START. + bool start(void){ // Аргументы: отсутствуют. +// _ _ _ _____:___ _ _ _ // +// SCL: : \___ _ _ _ _ _/ // +// _ _ _ _____: _ _ _ _ _ _ // Спад логического уровня на линии SDA с «1» в «0», при наличии уровня логической «1» на линии SCL +// SDA: :\______ _ _/_ _ _ _ _ _ // +// // + #if defined(iarduino_I2C_HW) // + // Если используется аппаратная шина I2C: // + uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания установки состояния start. + TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTA); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания), TWEN (бит разрешения работы шины), TWSTA (бит условия START). + while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов. + if((TWSR & 0xF8)==0x08){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x08, значит состояние START установилось на шине I2C. + return false; // Если предыдущая строка не вернула true, значит состояние START не установилось на шине I2C, возвращаем false. + #elif defined(iarduino_I2C_SW) // + // Если используется программная шина I2C: // + bool i= setSCL(1); // Устанавливаем «1» на линии SCL. Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false. + setSDA(0); // Устанавливаем «0» на линии SDA + setSCL(0); // Устанавливаем «0» на линии SCL + return i; // Если счетчик i не дошел до 0 при ожидании «1» на линии SCL, то вернётся true. + #else // + // Если тип шины I2C не поддерживается или не определён: // + return false; // Возвращаем false. + #endif // + } // + // +// Функция нижнего уровня - установка состояния RESTART: // Определяем функцию установки состояния RESTART. + bool reStart(void){ // Аргументы: отсутствуют. +// _ _ _ ___:___ _ _ _ // +// SCL: \_ _ _ _ / : \___ _ _ _ _ _/ // +// _ _ _ _ _ ________: _ _ _ _ _ _ // Спад логического уровня на линии SDA с «1» в «0», при наличии уровня логической «1» на линии SCL +// SDA: _ _ _ _ _/ :\______ _ _/_ _ _ _ _ _ // Сигнал рестрат аналогичен сигналу старт и отличается лишь тем, что ему не предшествует сигнал STOP +// // + #if defined(iarduino_I2C_HW) // + // Если используется аппаратная шина I2C: // + uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания установки состояния RESTART. + TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTA); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания), TWEN (бит разрешения работы шины), TWSTA (бит условия START). + while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов. + if((TWSR & 0xF8)==0x10){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x10, значит состояние RESTART установилось на шине I2C. + return false; // Если предыдущая строка не вернула true, значит состояние RESTART не установилось на шине I2C, возвращаем false. + #elif defined(iarduino_I2C_SW) // + // Если используется программная шина I2C: // + return start(); // Программная реализация состояния RESTART идентична программной реализации состояния START. + #else // + // Если тип шины I2C не поддерживается или не определён: // + return false; // Возвращаем false. + #endif // + } // + // +// Функция нижнего уровня - установка состояния STOP: // Определяем функцию установки состояния STOP. + void stop(void){ // Аргументы: отсутствуют. +// _ _ _ ___:____ _ _ _ // +// SCL: \_ _ _ _/ : // +// _ _ _ _ _ :____ _ _ _ // Подъём логического уровня на линии SDA с «0» в «1», при наличии уровня логической «1» на линии SCL +// SDA: _ _ _ _ _\______/: // +// // + #if defined(iarduino_I2C_HW) // + // Если используется аппаратная шина I2C: // + uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания установки состояния STOP + TWCR = _BV(TWINT) | _BV(TWEN) | _BV(TWSTO); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания), TWEN (бит разрешения работы шины), TWSTO (бит условия STOP). + while((!(TWCR & _BV(TWSTO))) && i){i--;} // Ждём сброса бита условия стоп TWSTO в регистре управления TWCR, но не дольше чем i циклов + // if((TWSR & 0xF8)==0xA0){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0xA0, значит состояние STOP установилось на шине I2C. + delayMicroseconds(20); // Данная функция не возвращает результат, но в любом случае делаем задержку меджу завершением текущего пакета и возможным началом следующего + #elif defined(iarduino_I2C_SW) // + // Если используется программная шина I2C: // + setSDA(0); // Устанавливаем «0» на линии SDA + setSCL(1); // Устанавливаем «1» на линии SCL. Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false. + setSDA(1); // Устанавливаем «1» на линии SDA + #endif // + } // + // +// Функция нижнего уровня - передача первого байта АДРЕС+RW: // Определяем функцию передачи первого байта после cигнала START. Это байт адреса модуля с битом RW + bool sendID(uint8_t adr, bool rw){ // Аргументы: ID-адрес модуля, бит RW (0-запись, 1-чтение) +// 1 2 3 4 5 6 7 8 9 // +// SCL: _ _ _ _____/\_/\_/\_/\_/\_/\_/\_/\__/\_____ _ _ _ // +// ________________________ // Передача 7-битного адреса и 1 бита RW. На 9 тактирубщем импульсе модуль отвечает ACK («0» - «я сдесь»), или NACK («1» - «нет») +// SDA: _ _ _ ___/________ADDRESS_______RW>----____ _ _ _ // +// вход // + #if defined(iarduino_I2C_HW) // + // Если используется аппаратная шина I2C: // + uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания получения подтверждения от ведомого ввиде бита ACK. + TWDR = (adr<<1)+rw; // Определяем значение регистра данных TWDR: записываем в него адрес adr сдвигая его на 1 бит влево, при этом младший бит (освободившийся) займёт бит RW (0-запись / 1-чтение) + TWCR = _BV(TWINT) | _BV(TWEN); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания) и TWEN (бит разрешения работы шины). + while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов + if((TWSR & 0xF8)==0x40 && rw){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x40, значит ведомый опознал свой адрес с битом RW=1 и отправил бит подтверждения ACK (в противном случае значение регистра TWSR будет равно 0x48). + if((TWSR & 0xF8)==0x18 && !rw){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x18, значит ведомый опознал свой адрес с битом RW=0 и отправил бит подтверждения ACK (в противном случае значение регистра TWSR будет равно 0x20). + return false; // Если предыдущая строка не вернула true, значит на шине нет ведомых у сказанным адресом, возвращаем false. + #elif defined(iarduino_I2C_SW) // + // Если используется программная шина I2C: // + bool i=true; // Определяем флаг возвращаемый данной функцией. + uint8_t j=7; // Определяем счётчик количества переданных бит адреса. + while(j){ j--; // Передаём 7 бит адреса adr в цикле + setSDA(adr & bit(j)); // Устанавливаем очередной бит адреса на линии SDA + if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false. + setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса) + } setSDA(rw); // Устанавливаем бит RW на линии SDA + if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false. + setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса) + setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc + if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false. + if( getSDA( ) ) {i=false;} // Если на линии SDA установлена «1» значит ведомый ответил NACK + setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса) + return i; // Если линия SCL была занята или модуль ответил NACK, то вернётся false. + #else // + // Если тип шины I2C не поддерживается или не определён: // + return false; // Возвращаем false + #endif // + } // + // +// Функция нижнего уровня - передача одного байта данных: // Определяем функцию передачи одного байта (в любом месте между байтом адреса с битом RW=0 и сигналом STOP). + bool setByte(uint8_t data){ // Аргумент: байт для передачи. +// 1 2 3 4 5 6 7 8 9 // +// SCL: _ _ _ _____/\_/\_/\_/\_/\_/\_/\_/\__/\_____ _ _ _ // +// ________________________ // Передача 1 байта, каждый бит читается модулем по фронту тактирубщих импульсов. На 9 тактирубщем импульсе модуль отвечает ACK («0» - «принял»), или NACK («1» - «нет») +// SDA: _ _ _ ___/__________DATA__________>----____ _ _ _ // +// вход // + #if defined(iarduino_I2C_HW) // + // Если используется аппаратная шина I2C: // + uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания получения подтверждения от ведомого ввиде бита ACK. + TWDR = data; // Определяем значение регистра данных TWDR: записываем в него байт для передачи по шине I2C. + TWCR = _BV(TWINT) | _BV(TWEN); // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания) и TWEN (бит разрешения работы шины). + while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов + if((TWSR & 0xF8)==0x28){return true;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x28, значит ведомый отправил бит подтверждения приёма данных ACK (в противном случае значение регистра TWSR будет равно 0x30). + return false; // Если предыдущая строка не вернула true, значит ведомый не принял отправленный ему байт, возвращаем false. + #elif defined(iarduino_I2C_SW) // + // Если используется программная шина I2C: // + bool i=true; // Определяем флаг возвращаемый данной функцией. + uint8_t j=8; // Определяем счётчик количества переданных байт. + while(j){ j--; // Передаём 8 бит байта data в цикле + setSDA(data & bit(j)); // Устанавливаем очередной бит байта на линии SDA + if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false. + setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса) + } setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc + if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false. + if( getSDA( ) ) {i=false;} // Если на линии SDA установлена «1» значит ведомый ответил NACK + setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса) + return i; // Если счетчик i не дошел до 0 и не был сброшен в 0 при получении NACK, то вернётся true. + #else // + // Если тип шины I2C не поддерживается или не определён: // + return false; // Возвращаем false + #endif // + } // + // +// Функция нижнего уровня - получение одного байта данных: // Определяем функцию получения одного байта (в любом месте между байтом адреса с битом RW=1 и сигналом STOP). + uint8_t getByte(bool ack){ // Аргумент: бит подтверждения ACK/NACK +// 1 2 3 4 5 6 7 8 9 // +// SCL: _ _ _ _____/\_/\_/\_/\_/\_/\_/\_/\__/\_____ _ _ _ // +// __ // Получение 1 байта, каждый бит читается мастером по фронту тактирубщих импульсов. На 9 тактирубщем импульсе мастер отвечает ACK («0» - «давай еще»), или NACK («1» - «хватит») +// SDA: _ _ _ ___/----------DATA-----------<__\____ _ _ _ // +// вход выход // + #if defined(iarduino_I2C_HW) // + // Если используется аппаратная шина I2C: // + uint16_t i=60000L; // Определяем счётчик указывая максимальное количество циклов для ожидания. + TWCR = _BV(TWINT) | _BV(TWEN) | ack< THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..96febe7 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:nanoatmega328] +platform = atmelavr +board = nanoatmega328 +framework = arduino diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..dc9fabb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,636 @@ +#include + +// Подключаем библиотеки: +// Библиотека для работы I2C +#include +//библиотека для работы со внутренней памятью ардуино +#include +// Библиотека для работы с OLED дисплеем +#include "OzOled.h" +// Библиотека для работы с RTC +#include "iarduino_RTC.h" +// Библиотека для работы со светодиодами NeoPixel +#include "iarduino_NeoPixel.h" +// Библиотека для работы с датчиками DHT22 +#include "dhtnew.h" + +// Распиновка +struct { + int pixels = 6; + int dht = 7; + struct { + int up = 2; + int down = 3; + int right = 4; + int left = 5; + } button; + int humi = 13; + int heat = 8; +} pin; + +// Инициализируем объекты устройств +// Объявляем объект часов реального времени +iarduino_RTC rtc(RTC_DS3231); +// Объявляем объект LED указывая (№ вывода Arduino к которому подключён модуль NeoPixel, количество используемых светодиодов) +iarduino_NeoPixel led(pin.pixels, 1); +DHTNEW sensor(pin.dht); + +//#pragma once + +// (Horizontal) byte array of heater icon 16 x 16 px: +const unsigned char bmpHeater[] PROGMEM = { + 0xe0, 0xf8, 0xfe, 0x1c, 0x00, 0xe0, 0xf8, 0xfe, 0x1c, 0x00, 0xe0, 0xf8, 0xfe, 0x1c, 0x00, 0x00, + 0xe3, 0xff, 0xef, 0xe7, 0xe0, 0xe3, 0xff, 0xef, 0xe7, 0xe0, 0xe3, 0xff, 0xef, 0xe7, 0xe0, 0x00, +}; +// (Horizontal) byte array of humidifier icon 16 x 16 px: +const unsigned char bmpHumidifier[] PROGMEM = { + 0x00, 0x54, 0x00, 0xaa, 0x00, 0x54, 0x00, 0xaa, 0x00, 0x54, 0x00, 0xaa, 0x00, 0x54, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x95, 0xc0, 0xea, 0xc0, 0x95, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, +}; + +struct menuItem { + int Len; + char* Text[7]; +}; + +struct Time { + byte Hours; + byte Minutes; +}; + +struct Date { + byte Day; + byte Month; +}; + +// Глобальные переменные +struct { + // Состояние кнопок + struct { + bool Up; + bool Down; + bool Left; + bool Right; + } Button; + // Данные сенсоров + struct { + float Temp; + float Humi; + } Sensor; + // Состояние исполнительных устройств + struct { + bool Heater; + bool Humidifier; + } Actor; + // Пункты меню и положение курсора + struct { + bool Show = false; + int Sect = 0; + int Pos = 1; + int PosOld = 1; + menuItem Item[6] = { + {6, {">>>>> MENU <<<<<", "TIMES...", "DROUGHTS...", "MONSOONS...", "PANIC LIMS...", "SYSTEM..."}}, + {5, {">>>> TIMES <<<<<", "Date/Time", "Sunrize", "Sunset", "Latency (min)"}}, + {6, {">>> DROUGHTS <<<", "Pick date", "Temp. daytime", "Temp. night", "Humi. daytime", "Humi. night"}}, + {6, {">>> MONSOONS <<<", "Pick date", "Temp. daytime", "Temp. night", "Humi. daytime", "Humi. night"}}, + {5, {"> PANIC LIMITS <", "Temp. MAX", "Temp. MIN", "Humi. MAX", "Humi. MIN"}}, + {2, {">>>> SYSTEM <<<<", "Reset"}}, + }; + } Menu; + struct { + struct { + Time Sunrize; + Time Sunset; + byte Latency; + } Times; + struct { + Date Pick; + byte TempDaytime; + byte TempNight; + byte HumiDaytime; + byte HumiNight; + } Droughts; + struct { + Date Pick; + byte TempDaytime; + byte TempNight; + byte HumiDaytime; + byte HumiNight; + } Monsoons; + struct { + byte TempMAX; + byte TempMIN; + byte HumiMAX; + byte HumiMIN; + } Limits; + } Params; +} state; + +void setup() { + Serial.begin(9600); + //вспоминаем данные из EEPROM + EEPROM.get(0, state.Params); + + // Объявляем пины кнопок + pinMode(pin.button.up, INPUT); + pinMode(pin.button.down, INPUT); + pinMode(pin.button.right, INPUT); + pinMode(pin.button.left, INPUT); + // Объявляем пины акторов + pinMode(pin.humi, OUTPUT); + pinMode(pin.heat, OUTPUT); + + // Объявляем пины исполнительных устройств + // Инициализируем адресные светодиоды: + led.begin(); + // Выключаем все светодиоды и ждём завершение инициализации и калибровки (задержка 0,3с): + led.setColor(NeoPixelAll, 254, 0, 0); led.write(); delay(300); + led.setColor(NeoPixelAll, 0, 254, 0); led.write(); delay(300); + led.setColor(NeoPixelAll, 0, 0, 254); led.write(); delay(300); + led.setColor(NeoPixelAll, 0, 0, 0); led.write(); delay(300); + // Инициализируем дисплей на шине I2C + OzOled.init(); + OzOled.clearDisplay(); + // Инициализируем часы и устанавливаем время/дату в вформате (сек(0+), мин(0+), час(0+), число(0+), месяц(0+), год(2000+), день недели(0+)). + rtc.begin(); + // Настройка сенсоров + sensor.setSuppressError(true); +} + +void readButtons(); +void readSensors(); +void actorLeds(); +void screenTime(); +void screenMenu(); +void screenSetRTC(byte MenuSect, byte MenuPos); +uint16_t screenSetScalar(byte MenuSect, byte MenuPos, uint16_t param, uint16_t limitMIN, uint16_t limitMAX); +Date screenSetDate(byte MenuSect, byte MenuPos, Date date); +Time screenSetTime(byte MenuSect, byte MenuPos, Time time); +void actorLeds(); +void OLEDPrintDigits16(uint16_t number, uint8_t signs, uint8_t column, uint8_t line); + + +// Основной цикл +void loop() { + // Опрос кнопок + readButtons(); + // Опрос датчиков + readSensors(); + // Расчет параметров + // Отработка исполнительных + actorLeds(); + + // Отображение и селектор экранов + if (state.Menu.Show) { + screenMenu(); + } else { + screenTime(); + } +} + +// Функция опроса сенсорных кнопок. +void readButtons() { + state.Button.Up = (digitalRead(pin.button.up) == HIGH); + state.Button.Down =(digitalRead(pin.button.down) == HIGH); + state.Button.Right = (digitalRead(pin.button.right) == HIGH); + state.Button.Left = (digitalRead(pin.button.left) == HIGH); +} + +// Функция опроса датчиков температуры и влажности. +void readSensors() { + if (millis() - sensor.lastRead() > 2000) { + sensor.setDisableIRQ(false); + if (sensor.read() != DHTLIB_WAITING_FOR_READ) { + state.Sensor.Temp = sensor.getTemperature(); + state.Sensor.Humi = sensor.getHumidity(); + } + sensor.setDisableIRQ(true); + } +} + +// Экран основного режима +void screenTime() { + // Верхняя строка (текущая температура и влажность) + OzOled.printNumber16(state.Sensor.Temp, 0, 0); OzOled.printString16("C", 4, 0); + OzOled.printNumber16(state.Sensor.Humi, 10, 0); OzOled.printString16("%", 14, 0); + // Мигалка + if ((millis() / 1000) % 2 == 0) { + OzOled.printBigNumber(rtc.gettime("H:i"), 0, 2); + // Состояние нагревателя в верхней строке + if (state.Actor.Heater) { + OzOled.drawBitmap(bmpHeater, 6, 0, 2, 2); + } + // Состояние увлажнителя в верхней строке + if (state.Actor.Humidifier) { + OzOled.drawBitmap(bmpHumidifier, 8, 0, 2, 2); + } + } else { + OzOled.printBigNumber(rtc.gettime("H i"), 0, 2); + // Выключаем иконки акторов + OzOled.printString16(" ", 6, 0); + OzOled.printString16(" ", 8, 0); + } + // Нижняя строка (текущая дата) + OzOled.printString16(rtc.gettime("d-m-y"), 0, 6); + // Вызов меню по кнопке RIGHT + if (state.Button.Right) { + OzOled.clearDisplay(); + state.Menu.Show = true; + } +} + +// Экран режима меню +void screenMenu() { + // Debug section + // Serial.print("MenuSection: "); Serial.print(state.Menu.Sect); Serial.print(" MenuPosition: "); Serial.print(state.Menu.Pos); Serial.println(" "); + + // Отображение заголовка меню + OzOled.printString(state.Menu.Item[state.Menu.Sect].Text[0], 0, 0); + // Отображение пунктов меню + for (int i = 1; i < state.Menu.Item[state.Menu.Sect].Len; i++) { + OzOled.printString(state.Menu.Item[state.Menu.Sect].Text[i], 2, i); + } + // Отображение курсора + if (state.Menu.PosOld != state.Menu.Pos) { + OzOled.printString(" ", 0, state.Menu.PosOld); + } + OzOled.printString("#>", 0, state.Menu.Pos); + // Опрос состояния кнопок + // На позицию вверх + if (state.Button.Up && state.Menu.Pos > 1 && state.Menu.Pos <= state.Menu.Item[state.Menu.Sect].Len-1) { + state.Menu.PosOld = state.Menu.Pos; state.Menu.Pos--; + } + // На позицию вниз + if (state.Button.Down && state.Menu.Pos >= 1 && state.Menu.Pos < state.Menu.Item[state.Menu.Sect].Len-1) { + state.Menu.PosOld = state.Menu.Pos; state.Menu.Pos++; + } + // Переходы вглубь меню и запуск сеттеров + if (state.Button.Right) { + switch (state.Menu.Sect) { + case 0: + state.Menu.Sect = state.Menu.Pos; + state.Menu.Pos = 1; + OzOled.clearDisplay(); + break; + case 1: + switch (state.Menu.Pos) { + case 1: + // Set Date/Time + screenSetRTC(state.Menu.Sect, state.Menu.Pos); + break; + case 2: + // Set Sunrize + state.Params.Times.Sunrize = screenSetTime(state.Menu.Sect, state.Menu.Pos, state.Params.Times.Sunrize); + break; + case 3: + // Set Sunset + state.Params.Times.Sunset = screenSetTime(state.Menu.Sect, state.Menu.Pos, state.Params.Times.Sunset); + break; + case 4: + // Set Latency + state.Params.Times.Latency = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Times.Latency, 0, 99); + break; + } + break; + case 2: + switch (state.Menu.Pos) { + case 1: + // Set Pick date + state.Params.Droughts.Pick = screenSetDate(state.Menu.Sect, state.Menu.Pos, state.Params.Droughts.Pick); + break; + case 2: + // Set Temp. daytime + state.Params.Droughts.TempDaytime = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Droughts.TempDaytime, state.Params.Limits.TempMIN, state.Params.Limits.TempMAX); + break; + case 3: + // Set Temp. night + state.Params.Droughts.TempNight = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Droughts.TempNight, state.Params.Limits.TempMIN, state.Params.Limits.TempMAX); + break; + case 4: + // Set Humi. daytime + state.Params.Droughts.HumiDaytime = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Droughts.HumiDaytime, state.Params.Limits.HumiMIN, state.Params.Limits.HumiMAX); + break; + case 5: + // Set Humi. nights + state.Params.Droughts.HumiNight = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Droughts.HumiNight, state.Params.Limits.HumiMIN, state.Params.Limits.HumiMAX); + break; + } + break; + case 3: + switch (state.Menu.Pos) { + case 1: + // Set Pick date + state.Params.Monsoons.Pick = screenSetDate(state.Menu.Sect, state.Menu.Pos, state.Params.Monsoons.Pick); + break; + case 2: + // Set Temp. daytime + state.Params.Monsoons.TempDaytime = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Monsoons.TempDaytime, state.Params.Limits.TempMIN, state.Params.Limits.TempMAX); + break; + case 3: + // Set Temp. night + state.Params.Monsoons.TempNight = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Monsoons.TempNight, state.Params.Limits.TempMIN, state.Params.Limits.TempMAX); + break; + case 4: + // Set Humi. daytime + state.Params.Monsoons.HumiDaytime = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Monsoons.HumiDaytime, state.Params.Limits.HumiMIN, state.Params.Limits.HumiMAX); + break; + case 5: + // Set Humi. nights + state.Params.Monsoons.HumiNight = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Monsoons.HumiNight, state.Params.Limits.HumiMIN, state.Params.Limits.HumiMAX); + break; + } + break; + case 4: + switch (state.Menu.Pos) { + case 1: + // Temp. MAX + state.Params.Limits.TempMAX = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Limits.TempMAX, state.Params.Limits.TempMIN, 98); + break; + case 2: + // Temp. MIN + state.Params.Limits.TempMIN = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Limits.TempMIN, 0, state.Params.Limits.TempMAX); + break; + case 3: + // Humi. MAX + state.Params.Limits.HumiMAX = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Limits.HumiMAX, state.Params.Limits.HumiMIN, 98); + break; + case 4: + // Humi. MIN + state.Params.Limits.HumiMIN = screenSetScalar(state.Menu.Sect, state.Menu.Pos, state.Params.Limits.HumiMIN, 0, state.Params.Limits.HumiMAX); + break; + } + break; + case 5: + switch (state.Menu.Pos) { + case 1: + // EEPROM reset + for (uint16_t i = 0; i < EEPROM.length(); i++) EEPROM.update(i, 0); + EEPROM.get(0, state.Params); + OzOled.clearDisplay(); + for (uint8_t i=0;i<5;i++) { + OzOled.printString16("RESETING", 0, 2); + OzOled.printString16("EEPROM", 2, 5); + delay(500); + OzOled.clearDisplay(); + } + state.Menu.Show=false; + break; + } + break; + } + } + // Переход вверх по меню, запись в EEPROM и выход + if (state.Button.Left && state.Menu.Sect>0) { + state.Menu.Pos=state.Menu.Sect; + state.Menu.Sect=0; + OzOled.clearDisplay(); + } else if (state.Button.Left) { + state.Menu.Show=false; + EEPROM.put(0, state.Params); + OzOled.clearDisplay(); + } +} + +// Экран установки часов реального времени +void screenSetRTC(byte MenuSect, byte MenuPos) { + OzOled.clearDisplay(); + rtc.gettime(); + struct dateTime { + uint16_t digit; + uint16_t column; + uint16_t line; + }; + dateTime t[5] ={ + {rtc.day, 0, 3}, + {rtc.month, 6, 3}, + {rtc.year, 12, 3}, + {rtc.Hours, 3, 6}, + {rtc.minutes, 9, 6}, + }; + uint8_t pos = 0; + // Отображение заголовка пункта меню + OzOled.printString(state.Menu.Item[MenuSect].Text[MenuPos], 0, 0); + while(true){ + // Опрос кнопок + readButtons(); + // Отображение значения даты и времени + for (int i = 0; i < 5; i++) { + OLEDPrintDigits16(t[i].digit, 2, t[i].column, t[i].line); + } + OzOled.printString16("-", 4, t[0].line); + OzOled.printString16("-", 10, t[1].line); + OzOled.printString16(":", 7, t[3].line); + + // Отображение курсора + OzOled.printString16(" ", t[pos].column, t[pos].line); + delay(300); + + // Сдвигаем курсор вправо + if (state.Button.Right) { + if (pos <4) { + pos++; + } else { + pos=0; + } + } + + // Увеличиваем + if (state.Button.Up && ((pos==0&&t[pos].digit<31)||(pos==1&&t[pos].digit<12)||(pos==2&&t[pos].digit<99)||(pos==3&&t[pos].digit<23)||(pos==4&&t[pos].digit<59))) { + t[pos].digit++; + } + // Уменьшаем + if (state.Button.Down && ((pos<=1&&t[pos].digit>1)||(pos>1&&t[pos].digit>0))) { + t[pos].digit--; + } + + // Сохранить и выйти + if (state.Button.Left) { + break; + } + } + rtc.settime(rtc.seconds, t[4].digit, t[3].digit, t[0].digit, t[1].digit, t[2].digit, rtc.weekday); +} + +// Экран установки скалярных значений +uint16_t screenSetScalar(byte MenuSect, byte MenuPos, uint16_t param, uint16_t limitMIN, uint16_t limitMAX) { + OzOled.clearDisplay(); + if (param>limitMAX) { + param=limitMAX; + } + if (paramlimitMIN) { + param--; + } + + // Сохранить и выйти + if (state.Button.Left) { + return param; + } + } +} + +// Экран установки Даты +Date screenSetDate(byte MenuSect, byte MenuPos, Date date) { + OzOled.clearDisplay(); + rtc.gettime(); + struct dateMonth { + uint8_t digit; + uint8_t column; + uint8_t line; + }; + if (date.Day == 0) { + date.Day=1; + } + if (date.Month == 0) { + date.Month=1; + } + dateMonth t[2] ={ + {date.Day, 2, 4}, + {date.Month, 8, 4}, + }; + uint8_t pos = 0; + // Отображение заголовка пункта меню + OzOled.printString(state.Menu.Item[MenuSect].Text[MenuPos], 0, 0); + while(true){ + // Опрос кнопок + readButtons(); + // Отображение значения + for (int i = 0; i < 2; i++) { + OLEDPrintDigits16(t[i].digit, 2, t[i].column, t[i].line); + } + OzOled.printString16("-", 6, t[0].line); + + // Отображение курсора + OzOled.printString16(" ", 0, t[pos].line+2); + OzOled.printString16("--", t[pos].column, t[pos].line+2); + + // Сдвигаем курсор вправо + if (state.Button.Right) { + if (pos==0) { + pos++; + } else { + pos=0; + } + } + + // Увеличиваем + if (state.Button.Up && ((pos==0&&t[pos].digit<28)||(pos==1&&t[pos].digit<12))) { + t[pos].digit++; + } + // Уменьшаем + if (state.Button.Down && t[pos].digit>1) { + t[pos].digit--; + } + + // Сохранить и выйти + if (state.Button.Left) { + break; + } + } + return Date{t[0].digit, t[1].digit}; +} + + +// Экран установки Времени +Time screenSetTime(byte MenuSect, byte MenuPos, Time time) { + OzOled.clearDisplay(); + rtc.gettime(); + struct timeSlice { + uint8_t digit; + uint8_t column; + uint8_t line; + }; + timeSlice t[2] ={ + {time.Hours, 2, 4}, + {time.Minutes, 8, 4}, + }; + uint8_t pos = 0; + // Отображение заголовка пункта меню + OzOled.printString(state.Menu.Item[MenuSect].Text[MenuPos], 0, 0); + while(true){ + // Опрос кнопок + readButtons(); + // Отображение значения + for (int i = 0; i < 2; i++) { + OLEDPrintDigits16(t[i].digit, 2, t[i].column, t[i].line); + } + OzOled.printString16(":", 6, t[0].line); + + // Отображение курсора + OzOled.printString16(" ", 0, t[pos].line+2); + OzOled.printString16("--", t[pos].column, t[pos].line+2); + + // Сдвигаем курсор вправо + if (state.Button.Right) { + if (pos==0) { + pos++; + } else { + pos=0; + } + } + + // Увеличиваем + if (state.Button.Up && ((pos==0&&t[pos].digit<23)||(pos==1&&t[pos].digit<59))) { + t[pos].digit++; + } + // Уменьшаем + if (state.Button.Down && t[pos].digit>0) { + t[pos].digit--; + } + + // Сохранить и выйти + if (state.Button.Left) { + break; + } + } + return Time{t[0].digit, t[1].digit}; +} + +// Отработка светодиодом в зависимости от влажности +void actorLeds() { + if (state.Sensor.Humi < state.Params.Limits.HumiMIN) { + led.setColor(0, 100, 0, 0); led.write(); + state.Actor.Humidifier=true; + digitalWrite(pin.humi, HIGH); + } else if (state.Sensor.Humi < state.Params.Limits.HumiMAX) { + led.setColor(0, 0, 3, 0); led.write(); + state.Actor.Humidifier=false; + digitalWrite(pin.humi, LOW); + } else { + led.setColor(0, 20, 10, 0); led.write(); + state.Actor.Humidifier=false; + digitalWrite(pin.humi, LOW); + } +} + +// Принтер в форматированные цифры +void OLEDPrintDigits16(uint16_t number, uint8_t signs, uint8_t column, uint8_t line) { + // Определяем кол-во знаков + int n = String(number).length(); + // Валидируем ввод + if (signs>=n) { + for (byte i=0;i