Merge pull request #1 from tiburon-777/master

Init commit
main
Andrey Ivanov 2021-11-08 19:43:30 +03:00 committed by GitHub
commit 1720602fe6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 3056 additions and 32 deletions

37
.gitignore vendored
View File

@ -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

7
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
]
}

827
include/OzOLED.cpp Normal file
View File

@ -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 <Wire.h>
#include <avr/pgmspace.h>
// 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<numChar){
printChar(String[count++]);
}
}
byte OzOLED::printNumber(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++;
printChar('-');
long_num = -long_num;
}
else if (long_num == 0) {
f++;
printChar('0');
return f;
}
while (long_num > 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<prec; j++){
f_shift *= 10;
}
num_frac = printNumber((long)(f*f_shift)); // count how many digits in fractional part
}
return num_int + num_frac + num_extra;
}
unsigned int EnlardeByte2Word(char b)
{
unsigned int d = 0;
for (byte i = 0; i < 8; i++)
{
unsigned int e = (((unsigned int)b) & (1 << i)) << i;
d = d | e | (e << 1);
}
return d;
}
void OzOLED::printChar16(char C, byte X, byte Y){
setCursorXY(X, Y);
if(C < 32 || C > 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<numChar){
printChar16(String[count++], X, Y);
X += 2;
}
}
void OzOLED::printBigNumber(const char *number, byte X, byte Y, byte numChar){
// big number pixels: 24 x 32
// Y - page
byte column = 0;
byte count = 0;
while(number[count] && count<numChar){
setCursorXY(X, Y);
for(byte i=0; i<96; i++) {
// if character is not "0-9" or ':'
if(number[count] < 48 || number[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<width*8*height; i++) {
sendData(pgm_read_byte(&bitmaparray[i]));
if(++column == width*8) {
column = 0;
setCursorXY( X, ++Y );
}
}
}
// =================== High Level ===========================
void OzOLED::init(){
Wire.begin();
// upgrade to 400KHz! (only use when your other i2c device support this speed)
if (I2C_400KHZ){
// save I2C bitrate (default 100Khz)
byte twbrbackup = TWBR;
TWBR = 12;
//TWBR = twbrbackup;
//Serial.println(TWBR, DEC);
//Serial.println(TWSR & 0x3, DEC);
}
setPowerOff(); //display off
delay(10);
setPowerOn(); //display on
delay(10);
setNormalDisplay(); //default Set Normal Display
setPageMode(); // default addressing mode
clearDisplay();
setCursorXY(0,0);
// Additional command
OzOled.setPowerOff();
OzOled.sendCommand(ASA_SET_DISPLAY_CLOCK_DIV_RATIO);
OzOled.sendCommand(0x80);
OzOled.sendCommand(ASA_SET_MULTIPLEX_RATIO);
OzOled.sendCommand(0x3F);
OzOled.sendCommand(ASA_SET_DISPLAY_OFFSET);
OzOled.sendCommand(0x0);
OzOled.sendCommand(ASA_SET_START_LINE | 0x0);
OzOled.sendCommand(ASA_CHARGE_PUMP);
OzOled.sendCommand(0x14);
OzOled.sendCommand(ASA_MEMORY_ADDR_MODE);
OzOled.sendCommand(0x00);
OzOled.sendCommand(ASA_SET_SEGMENT_REMAP | 0x1);
OzOled.sendCommand(ASA_COM_SCAN_DIR_DEC);
OzOled.sendCommand(ASA_SET_COM_PINS);
OzOled.sendCommand(0x12);
OzOled.setBrightness(0xCF);
OzOled.sendCommand(ASA_SET_PRECHARGE_PERIOD);
OzOled.sendCommand(0xF1);
OzOled.sendCommand(ASA_SET_VCOM_DESELECT);
OzOled.sendCommand(0x40);
OzOled.sendCommand(ASA_DISPLAY_ALL_ON_RESUME);
OzOled.setNormalDisplay();
OzOled.setPowerOn();
}
void OzOLED::setCursorXY(byte X, byte Y){
// Y - 1 unit = 1 page (8 pixel rows)
// X - 1 unit = 8 pixel columns
sendCommand(0x00 + (8*X & 0x0F)); //set column lower address
sendCommand(0x10 + ((8*X>>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

110
include/OzOLED.h Normal file
View File

@ -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 <Arduino.h>
#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

39
include/README Normal file
View File

@ -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

63
include/buildTime.h Normal file
View File

@ -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

311
include/dhtnew.cpp Normal file
View File

@ -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 --

112
include/dhtnew.h Normal file
View File

@ -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 --

View File

@ -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<lenLED; i++){ // Проходим по всем светодиодам
arrRGB[i*3+0] = g; // Устанавливаем цвет Green (зелёный)
arrRGB[i*3+1] = r; // Устанавливаем цвет Red (красный)
arrRGB[i*3+2] = b; // Устанавливаем цвет Blue (синий)
}
}else if(lenLED > 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);}

View File

@ -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 <Arduino.h>
#else
#include <WProgram.h>
#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

134
include/iarduino_RTC.cpp Normal file
View File

@ -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; j<strlen(i); j++) { // Проходим по символам полученной строки
for(k=0; k<strlen(charInput); k++) { // Проходим по служебным символам строки charInput
if (i[j]==charInput[k]) { // Если символы обеих строк совпали, то ...
if(k>0){n++;} if(k>9){n++;} if(k>11){n++;} // Увеличиваем размер (n) в соостветствии со значениём найденного служебного символа
}}}
// Выделяем блок памяти под строку с ответом:
free(charReturn); // Освобождаем ранее созданный блок памяти
charReturn = (char*) malloc(n); // Выделяем новый блок памяти размером n байт
// Заполняем выделенный блок памяти:
n=0; // Определяем позицию в блоке памяти для следующего подставляемого значения
for(j=0; j<strlen(i); j++) { // Проходим по символам полученной строки
if(i[j]==charInput[0] /* w */ ) {funcFillChar( weekday ,0,n,7); n+=1;}else // Подставляем день недели от 0 до 6 (один знак: 0-воскресенье, 6-суббота)
if(i[j]==charInput[1] /* a */ ) {funcFillChar( midday*2 ,2,n,8); n+=2;}else // Подставляем полдень am или pm (два знака, в нижнем регистре)
if(i[j]==charInput[2] /* A */ ) {funcFillChar((midday+2)*2 ,2,n,8); n+=2;}else // Подставляем полдень AM или PM (два знака, в верхнем регистре)
if(i[j]==charInput[3] /* d */ ) {funcFillChar( day ,1,n,4); n+=2;}else // Подставляем день месяца от 01 до 31 (два знака)
if(i[j]==charInput[4] /* h */ ) {funcFillChar( hours ,1,n,3); n+=2;}else // Подставляем часы от 01 до 12 (два знака)
if(i[j]==charInput[5] /* H */ ) {funcFillChar( Hours ,1,n,3); n+=2;}else // Подставляем часы от 00 до 23 (два знака)
if(i[j]==charInput[6] /* i */ ) {funcFillChar( minutes ,1,n,2); n+=2;}else // Подставляем минуты от 00 до 59 (два знака)
if(i[j]==charInput[7] /* m */ ) {funcFillChar( month ,1,n,5); n+=2;}else // Подставляем месяц от 01 до 12 (два знака)
if(i[j]==charInput[8] /* s */ ) {funcFillChar( seconds ,1,n,1); n+=2;}else // Подставляем секунды от 00 до 59 (два знака)
if(i[j]==charInput[9] /* y */ ) {funcFillChar( year ,1,n,6); n+=2;}else // Подставляем год от 00 до 99 (два знака)
if(i[j]==charInput[10] /* M */ ) {funcFillChar((month+6)*3 ,3,n,5); n+=3;}else // Подставляем имя месяца от Jan до Dec (три знака: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
if(i[j]==charInput[11] /* D */ ) {funcFillChar( weekday*3 ,3,n,7); n+=3;}else // Подставляем имя деня недели от Mon до Sun (три знака: Mon Tue Wed Thu Fri Sat Sun)
if(i[j]==charInput[12] /* Y */ ) {funcFillChar( year ,4,n,6); n+=4;}else // Подставляем год от 2000 до 2099 (четыре знака)
{charReturn[n]=i[j]; n+=1;} // Если символ полученной строки не совпал со служебными, то подставляем его в блок памяти как есть, без изменений
} charReturn[n]='\0'; return charReturn; // Устанавливаем символ конца строки и возвращаем указатель на блок памяти с результатом
}
// Заполняем значением определённую позицию блока памяти:
void iarduino_RTC::funcFillChar(uint8_t i, uint8_t j, uint8_t n, uint8_t k){ // (данные, тип данных, позиция для вставки, мигание)
bool f=valBlink==k; if((millis()%valFrequency)<(valFrequency/2)){f=false;} // Устанавливаем флаг мигания, если значение valBlink равно значению параметра k
switch (j){
/* 1 знак */ case 0: if(i>6 ){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; // Возвращаем результат
}

98
include/iarduino_RTC.h Normal file
View File

@ -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 <Arduino.h> //
#else //
#include <WProgram.h> //
#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 //

View File

@ -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 //

461
include/iarduino_RTC_I2C.h Normal file
View File

@ -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.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(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() && i<sum){data[i]=Wire.read(); i++;} // Читаем sum принятых байт из буфера для полученных данных в массив по указателю data.
while(Wire.available()){Wire.read();}return i==sum; // Если в буфере для полученных данных есть еще принятые байты, то чистим буфер. Возвращаем true если удалось прочитать sum байт.
#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 на получение адреса регистра i, то ...
if ( reStart () ) { i=4; // Если на шине I2C установилось состояние RESTART, то ...
if ( sendID (adr,1) ) { i=5; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=1 (чтение), то ...
while(sum>0){ *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() && i<sum){data[i]=Wire.read(); i++;} // Читаем sum принятых байт из буфера для полученных данных в массив по указателю data.
while(Wire.available()){Wire.read();}return i==sum; // Если в буфере для полученных данных есть еще принятые байты, то чистим буфер. Возвращаем true если удалось прочитать sum байт.
#else //
// Если шина управляется функциями нижнего уровня данного класса: //
uint8_t i=0; // Предустанавливаем переменную i в значение 0 - это результат записи.
if ( start () ) { i=1; // Если на шине I2C установилось состояние START, то ...
if ( sendID (adr,1) ) { i=2; // Если модуль ответил ACK на получение адреса устройства adr с битом RW=1 (чтение), то ...
while(sum>0){ *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<<TWEA; // Определяем значение регистра управления TWCR: устанавливаем биты TWINT (флаг прерывания) и TWEN (бит разрешения работы шины), а бит TWEA (бит разрешения подтверждения) устанавливаем только если мы собираемся отправить ведомому бит подтверждения получения данных ACK.
while((!(TWCR & _BV(TWINT))) && i){i--;} // Ждём сброса флага прерывания TWINT в регистре управления TWCR, но не дольше чем i циклов
if((TWSR & 0xF8)==0x50 && ack){return TWDR;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x50, значит данные приняты и сохранены в регистре TWDR, а ведомому отправлен бит ACK.
if((TWSR & 0xF8)==0x58 && !ack){return TWDR;} // Проверяем значение регистра статуса TWSR: если его значение с маской 0xF8 равно 0x58, значит данные приняты и сохранены в регистре TWDR, а ведомому отправлен бит NACK.
return 0; // Если предыдущая строка не вернула принятый байт из регистра данных TWDR, значит мы не приняли байт, возвращаем 0.
#elif defined(iarduino_I2C_SW) //
// Если используется программная шина I2C: //
bool i=true; // Определяем флаг возвращаемый данной функцией.
uint8_t j=8; // Определяем счётчик количества полученных битов байта.
uint8_t k=0; // Объявляем переменную для хранения прочитанного байта данных.
setSDA(1); // Переводим вывод SDA в режим входа с подтяжкой к Vcc
while(j){ j--; // Получаем 8 бит байта в цикле
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
if( getSDA( ) ) { k |= (1<<j); } // Заполняем биты байта k в соотсетствии с уровнем на линии SDA
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
} setSDA(!ack); // Устанавливаем бит «ACK/NACK» на линии SDA (ACK-прижимаем, NACK-отпускаем)
if(!setSCL(1) ) {i=false;} // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса). Если ведомый удерживает линию SCL прижатой, то функция ждёт освобождения линии и, если не дождётся, то возвращает false.
setSCL(0); // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса)
return i?k:0; // Если во время ожидания поднятия логического уровня на линии SCL, cчётчик i не дошел до 0, то функция вернёт байт из переменной k.
#else //
// Если тип шины I2C не поддерживается или не определён: //
return 0; // Возвращаем false
#endif //
} //
//
private: //
uint8_t pin_SDA = pin_SW_SDA; // Определяем вывод pin_SDA.
uint8_t pin_SCL = pin_SW_SCL; // Определяем вывод pin_SCL.
uint8_t port_SDA; // Объявляем переменную для хранения номера порта вывода pin_SDA.
uint8_t port_SCL; // Объявляем переменную для хранения номера порта вывода pin_SCL.
uint8_t mask_SDA; // Объявляем переменную для хранения позиции бита управления выводом pin_SDA.
uint8_t mask_SCL; // Объявляем переменную для хранения позиции бита управления выводом pin_SCL.
volatile uint8_t *mod_SDA; // Объявляем указатель на регистр направления работы выводов порта port_SDA.
volatile uint8_t *mod_SCL; // Объявляем указатель на регистр направления работы выводов порта port_SCL.
volatile uint8_t *inp_SDA; // Объявляем указатель на регистр входных значений порта port_SDA.
volatile uint8_t *inp_SCL; // Объявляем указатель на регистр входных значений порта port_SCL.
volatile uint8_t *out_SDA; // Объявляем указатель на регистр выходных значений порта port_SDA.
volatile uint8_t *out_SCL; // Объявляем указатель на регистр выходных значений порта port_SCL.
//
// функция установки уровня на линии SCL: // Определяем функцию установки уровня на линии SCL.
bool setSCL(bool f){ // Аргумент: логический уровень.
uint16_t i=60000L; // Определяем счётчик ожидания освобождения линии SCL.
if(!f) {*mod_SCL |= mask_SCL; *out_SCL &= ~mask_SCL;} // Устанавливаем «0» на линии SCL (Спад тактирубщего импульса) pinMode(pin_SCL, OUTPUT); digitalWrite(pin_SCL, LOW );
else {*mod_SCL &= ~mask_SCL; *out_SCL |= mask_SCL; // Устанавливаем «1» на линии SCL (Фронт тактирующего импульса) pinMode(pin_SCL, INPUT ); digitalWrite(pin_SCL, HIGH);
while((*inp_SCL & mask_SCL)==0 && i){i--;} } // Ждём поднятия логического уровня на линии SCL while(digitalRead(pin_SCL)==0 && i){ цикл выполняется пока на линии 0 или пока i не сброситя d 0}
return i; //
} //
//
// Функция установки уровня на линии SDA: // Определяем функцию установки уровня на линии SDA.
void setSDA(bool f){ // Аргумент: логический уровень.
if(!f) {*mod_SDA |= mask_SDA; *out_SDA &= ~mask_SDA;} // Устанавливаем «0» на линии SDA (если бит RW=«0») pinMode(pin_SDA, OUTPUT); digitalWrite(pin_SDA, LOW );
else {*mod_SDA &= ~mask_SDA; *out_SDA |= mask_SDA;} // Устанавливаем «1» на линии SDA (если бит RW=«1») pinMode(pin_SDA, INPUT ); digitalWrite(pin_SDA, HIGH );
} //
//
// Функция чтения уровня с линии SDA: // Определяем функцию чтения уровня с линии SDA. Перед чтением необходимо вызвать функцию setSDA(1) которая переведёт вывод SDA в режим входа
bool getSDA(void){ return (*inp_SDA & mask_SDA); } // Аргументы: отсутсвуют. digitalRead(pin_SDA);
}; //
//
#endif //

46
lib/README Normal file
View File

@ -0,0 +1,46 @@
This directory is intended for project specific (private) libraries.
PlatformIO will compile them to static libraries and link into executable file.
The source code of each library should be placed in a an own separate directory
("lib/your_library_name/[here are source files]").
For example, see a structure of the following two libraries `Foo` and `Bar`:
|--lib
| |
| |--Bar
| | |--docs
| | |--examples
| | |--src
| | |- Bar.c
| | |- Bar.h
| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
| |
| |--Foo
| | |- Foo.c
| | |- Foo.h
| |
| |- README --> THIS FILE
|
|- platformio.ini
|--src
|- main.c
and a contents of `src/main.c`:
```
#include <Foo.h>
#include <Bar.h>
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

14
platformio.ini Normal file
View File

@ -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

636
src/main.cpp Normal file
View File

@ -0,0 +1,636 @@
#include <Arduino.h>
// Подключаем библиотеки:
// Библиотека для работы I2C
#include <Wire.h>
//библиотека для работы со внутренней памятью ардуино
#include <EEPROM.h>
// Библиотека для работы с 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 (param<limitMIN) {
param=limitMIN;
}
// Отображение заголовка пункта меню
OzOled.printString(state.Menu.Item[MenuSect].Text[MenuPos], 0, 0);
while(true){
// Опрос кнопок
readButtons();
// Отображение параметра
OLEDPrintDigits16(param, 2, 6, 3);
// Увеличиваем
if (state.Button.Up && param<limitMAX) {
param++;
}
// Уменьшаем
if (state.Button.Down && param>limitMIN) {
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<signs-n;i++) {
OzOled.printNumber16(0, column+i*2, line);
}
OzOled.printNumber16(number, column+(signs-n)*2, line);
} else {
for (byte i=0;i<signs;i++) {
OzOled.printString16("#", column+i*2, line);
}
}
}

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PlatformIO Unit Testing and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html