Initial commit

This commit is contained in:
Dirk Jahnke 2019-05-29 15:44:54 +02:00
commit 831b514c0a
10 changed files with 654 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.pio
.clang_complete
.gcc-flags.json

67
.travis.yml Normal file
View File

@ -0,0 +1,67 @@
# Continuous Integration (CI) is the practice, in software
# engineering, of merging all developer working copies with a shared mainline
# several times a day < https://docs.platformio.org/page/ci/index.html >
#
# Documentation:
#
# * Travis CI Embedded Builds with PlatformIO
# < https://docs.travis-ci.com/user/integration/platformio/ >
#
# * PlatformIO integration with Travis CI
# < https://docs.platformio.org/page/ci/travis.html >
#
# * User Guide for `platformio ci` command
# < https://docs.platformio.org/page/userguide/cmd_ci.html >
#
#
# Please choose one of the following templates (proposed below) and uncomment
# it (remove "# " before each line) or use own configuration according to the
# Travis CI documentation (see above).
#
#
# Template #1: General project. Test it using existing `platformio.ini`.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio run
#
# Template #2: The project is intended to be used as a library with examples.
#
# language: python
# python:
# - "2.7"
#
# sudo: false
# cache:
# directories:
# - "~/.platformio"
#
# env:
# - PLATFORMIO_CI_SRC=path/to/test/file.c
# - PLATFORMIO_CI_SRC=examples/file.ino
# - PLATFORMIO_CI_SRC=path/to/test/directory
#
# install:
# - pip install -U platformio
# - platformio update
#
# script:
# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# 7-Segment FastClock / Counter Display
This is a controller for a LED driven 7-segment clock/counter display. An ESP8266 offers WiFi access for configuration/setting purpose and the ability to retrieve the current time through NTP.
But it might as well be used as a fast clock display for model railroads.
Prerequisites:
* WS-2812 based adressable LED chain forming the 7-segment display
* number of LEDs per segment is configurable
## Links / References
- example of 3d printable segment frames

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

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

20
platformio.ini Normal file
View File

@ -0,0 +1,20 @@
;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:esp12e]
platform = espressif8266
board = d1_mini
framework = arduino
upload_port = /dev/cu.wchusbserial1420
[lib_deps]
library =
WifiManager,
Adafruit NeoPixel

228
src/SevenSegmentClock.cpp Normal file
View File

@ -0,0 +1,228 @@
#include "SevenSegmentClock.h"
static const uint16_t PixelCount = 4*7*3+3;
#define colorSaturation 63
// Seven Segment Layout: 3 LEDs per segment
// order of segments:
// b
// ---
// a| |c
// --- d
// e| |g
// ---
// f
#define SegmentsPerDigit 7
#define LedsPerSegment 3
#define LedsPerDigit (SegmentsPerDigit * LedsPerSegment)
#define SeperatorLeds 3 /* num of leds as seperation between hours/mins */
#define SegOffset_a 0
#define SegOffset_b LedsPerSegment
#define SegOffset_c LedsPerSegment*2
#define SegOffset_d LedsPerSegment*3
#define SegOffset_e LedsPerSegment*4
#define SegOffset_f LedsPerSegment*5
#define SegOffset_g LedsPerSegment*6
static const uint8_t digitOffset[] = { 0, LedsPerDigit, 2*LedsPerDigit+SeperatorLeds, 3*LedsPerDigit+SeperatorLeds };
#define Seg_a 0x01
#define Seg_b 0x02
#define Seg_c 0x04
#define Seg_d 0x08
#define Seg_e 0x10
#define Seg_f 0x20
#define Seg_g 0x40
#define decimalPointLed (2*LedsPerDigit)
#define clockSeperatorLed1 (2*LedsPerDigit+1)
#define clockSeperatorLed2 (2*LedsPerDigit+2)
#define firstCharacterMapped 32u /* first char to be mapped is "space" */
#define lastCharacterMapped (sizeof(charMapping) + firstCharacterMapped)
static const unsigned char PROGMEM charMapping[] = {
/* 0x20, space */ 0,
/* ! */ 0,
/* " */ 0,
/* # */ 0,
/* $ */ 0,
/* % */ 0,
/* & */ 0,
/* ' */ 0,
/* ( */ Seg_a + Seg_b + Seg_e + Seg_f,
/* ) */ Seg_b + Seg_c + Seg_f + Seg_g,
/* * */ 0,
/* + */ 0,
/* - */ Seg_d,
/* . */ 0,
/* / */ Seg_e,
/* 0 */ Seg_a + Seg_b + Seg_c + Seg_e + Seg_f + Seg_g,
/* 1 */ Seg_c + Seg_g,
/* 2 */ Seg_b + Seg_c + Seg_d + Seg_e + Seg_f,
/* 3 */ Seg_b + Seg_c + Seg_d + Seg_f + Seg_g,
/* 4 */ Seg_a + Seg_c + Seg_d + Seg_g,
/* 5 */ Seg_a + Seg_b + Seg_d + Seg_f + Seg_g,
/* 6 */ Seg_a + Seg_d + Seg_e + Seg_f + Seg_g,
/* 7 */ Seg_b + Seg_c + Seg_g,
/* 8 */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_e + Seg_f + Seg_g,
/* 9 */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_g,
/* : */ 0,
/* ; */ 0,
/* < */ 0,
/* = */ Seg_d + Seg_e,
/* > */ 0,
/* ? */ 0,
/* @ */ 0,
/* A */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_e + Seg_g,
/* B */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_e + Seg_f + Seg_g,
/* C */ Seg_a + Seg_b + Seg_e + Seg_f,
/* D */ Seg_a + Seg_b + Seg_c + Seg_e + Seg_f + Seg_g,
/* E */ Seg_a + Seg_b + Seg_d + Seg_e + Seg_f,
/* F */ Seg_a + Seg_b + Seg_d + Seg_e,
/* G */ Seg_a + Seg_b + Seg_d + Seg_e + Seg_f + Seg_g,
/* h */ Seg_a + Seg_d + Seg_e + Seg_g,
/* I */ Seg_a + Seg_e,
/* J */ Seg_b + Seg_c + Seg_f + Seg_g,
/* K */ Seg_a + Seg_c + Seg_d + Seg_e + Seg_g,
/* L */ Seg_a + Seg_e + Seg_f,
/* m */ Seg_d + Seg_e + Seg_g,
/* n */ Seg_d + Seg_e + Seg_g,
/* o */ Seg_d + Seg_e + Seg_f + Seg_g,
/* P */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_e,
/* q */ Seg_a + Seg_b + Seg_c + Seg_d + Seg_g,
/* r */ Seg_d + Seg_e,
/* S */ Seg_a + Seg_b + Seg_d + Seg_f + Seg_g,
/* t */ Seg_a + Seg_d + Seg_e + Seg_f,
/* U */ Seg_a + Seg_c + Seg_e + Seg_f + Seg_g,
/* v */ Seg_e + Seg_f + Seg_g,
/* w */ Seg_e + Seg_f + Seg_g,
/* X */ Seg_a + Seg_c + Seg_d + Seg_e + Seg_g,
/* Y */ Seg_a + Seg_c + Seg_d + Seg_g,
/* Z */ Seg_b + Seg_c + Seg_d + Seg_e + Seg_f,
/* [ */ Seg_a + Seg_b + Seg_e + Seg_f,
/* \ */ Seg_a + Seg_d + Seg_g,
/* ] */ Seg_b + Seg_c + Seg_f + Seg_g,
/* ^ */ Seg_a + Seg_b + Seg_c,
/* _ */ Seg_e,
/* 3 hor. bars */ Seg_b + Seg_d + Seg_e,
/* 2 hor. bars, top */ Seg_b + Seg_d,
/* 1 hor. bar, top */ Seg_b,
/* || */ Seg_a + Seg_c + Seg_e + Seg_g
};
void SevenSegmentClock::displaySegment(unsigned int ledAddress, uint32_t color) {
//Serial.print("displaySegment led="); Serial.print(ledAddress); Serial.print(" color=0x"); Serial.println(color, HEX);
for (int i=0; i<LedsPerSegment; i++) {
strip->setPixelColor(ledAddress + i, color);
}
}
void SevenSegmentClock::displayDigit(unsigned int digitNum, char charToDisplay) {
unsigned int c = charToDisplay;
uint32_t color;
//Serial.print("displayDigit: digitNum="); Serial.print(digitNum); Serial.print(" char=0x"); Serial.println(charToDisplay, HEX);
if (digitNum < 0 || digitNum > 3) {
Serial.print("SevenSegmentClock::displayDigit: Invalid digit num ");
Serial.println(digitNum);
return;
}
int offset = digitOffset[digitNum];
//Serial.print("1st LED address="); Serial.println(offset);
if (c < firstCharacterMapped || c > lastCharacterMapped) {
Serial.print("ERROR: SevenSegmentClock::displayDigit - Cannot display character 0x");
Serial.print(c, HEX);
Serial.print(" at digit position ");
Serial.println(digitNum);
return;
}
c -= firstCharacterMapped;
//Serial.print("Check char mapping at index="); Serial.println(c);
unsigned char mapping = pgm_read_byte(charMapping + c);
//Serial.print("Char mapping="); Serial.println(mapping, HEX);
color = (mapping & Seg_a) ? currentColor : black;
displaySegment(offset + SegOffset_a, color);
color = (mapping & Seg_b) ? currentColor : black;
displaySegment(offset + SegOffset_b, color);
color = (mapping & Seg_c) ? currentColor : black;
displaySegment(offset + SegOffset_c, color);
color = (mapping & Seg_d) ? currentColor : black;
displaySegment(offset + SegOffset_d, color);
color = (mapping & Seg_e) ? currentColor : black;
displaySegment(offset + SegOffset_e, color);
color = (mapping & Seg_f) ? currentColor : black;
displaySegment(offset + SegOffset_f, color);
color = (mapping & Seg_g) ? currentColor : black;
displaySegment(offset + SegOffset_g, color);
}
void SevenSegmentClock::displaySeperator(char seperatorCharacter) {
//Serial.print("displaySeperator: seperator="); Serial.println(seperatorCharacter);
switch (seperatorCharacter) {
case '.':
case ',':
strip->setPixelColor(decimalPointLed, currentColor);
strip->setPixelColor(clockSeperatorLed1, black);
strip->setPixelColor(clockSeperatorLed2, black);
break;
case ':':
strip->setPixelColor(decimalPointLed, black);
strip->setPixelColor(clockSeperatorLed1, currentColor);
strip->setPixelColor(clockSeperatorLed2, currentColor);
break;
default:
strip->setPixelColor(decimalPointLed, black);
strip->setPixelColor(clockSeperatorLed1, black);
strip->setPixelColor(clockSeperatorLed2, black);
Serial.print("SevenSegmentClock::displaySeperator: Unknown character to be displayed: ");
Serial.println(seperatorCharacter);
break;
}
}
void SevenSegmentClock::displayTime(int hour, int minute) {
char displayText[4];
clockHour = hour;
clockMinute = minute;
Serial.print("SevenSegmentClock: new time "); Serial.print(clockHour); Serial.print(":"); Serial.println(clockMinute);
displayText[0] = (hour > 9) ? '0' + (hour/10) : ' ';
displayText[1] = '0' + hour % 10;
displayText[2] = '0' + minute / 10;
displayText[3] = '0' + minute % 10;
displayDigit(0, displayText[0]);
displayDigit(1, displayText[1]);
displayDigit(2, displayText[2]);
displayDigit(3, displayText[3]);
displaySeperator(':');
strip->show();
Serial.print("Shown: "); Serial.print(displayText[0]); Serial.print(displayText[1]); Serial.print(':'); Serial.print(displayText[2]); Serial.println(displayText[3]);
};
uint32_t SevenSegmentClock::red, SevenSegmentClock::green, SevenSegmentClock::blue, SevenSegmentClock::white, SevenSegmentClock::black;
uint8_t SevenSegmentClock::LedDataPin;
Adafruit_NeoPixel *SevenSegmentClock::strip;
void SevenSegmentClock::begin(void) {
Serial.println("Init Neopixels ...");
Serial.print("LED pin="); Serial.println(LedDataPin);
Serial.print("Pixels="); Serial.println(PixelCount);
SevenSegmentClock::strip = new Adafruit_NeoPixel(PixelCount, LedDataPin, NEO_GRB + NEO_KHZ800);
strip->begin();
SevenSegmentClock::red = strip->Color(colorSaturation, 0, 0);
SevenSegmentClock::green = strip->Color(0, colorSaturation, 0);
SevenSegmentClock::blue = strip->Color(0, 0, colorSaturation);
SevenSegmentClock::white = strip->Color(colorSaturation, colorSaturation, colorSaturation);
SevenSegmentClock::black = strip->Color(0, 0, 0);
SevenSegmentClock::currentColor = SevenSegmentClock::white;
// strip->show();
// boot animation
uint32_t colors[] = { red, green, blue, white };
unsigned int colorIndex = 0;
for (int i=0; i<PixelCount; ++i) {
strip->setPixelColor(i, colors[colorIndex++]);
if (colorIndex > sizeof(colors)) colorIndex = 0;
}
strip->show();
delay(2000);
}

33
src/SevenSegmentClock.h Normal file
View File

@ -0,0 +1,33 @@
#ifndef sevenSegmentClock_h_included
#define sevenSegmentClock_h_included
#include <Adafruit_NeoPixel.h>
// avoid flickering of the display:
#define TIME_BETWEEN_DISPLAY_UPDATES_ms 300
#define BLINK_ON_OFF_TIME_ms 1000
#define defaultLedDataPin 2
class SevenSegmentClock {
public:
SevenSegmentClock() { LedDataPin=defaultLedDataPin; init(); };
SevenSegmentClock(uint8_t dataPin) { LedDataPin=dataPin; init(); };
void begin(void);
void displayTime(int hour, int minute);
//void setClockSpeed(int _msPerModelSecond) { msPerModelSecond = _msPerModelSecond; setClockSpeed("x"); };
void setClockHalted(bool halted) { clockHalted = halted; };
static uint32_t red, green, blue, white, black;
enum ClockDisplayStatus { Off, Booting, Halted, StandardClock, FastClock };
void displayDigit(unsigned int digitNum, char c);
void displaySeperator(char seperatorCharacter);
private:
void init(void) { displayStatus = Off; clockHour=12; clockMinute=34; setClockHalted(true); };
static uint8_t LedDataPin;
static Adafruit_NeoPixel *strip;
ClockDisplayStatus displayStatus;
int clockHour;
int clockMinute;
bool clockHalted;
uint32_t currentColor;
void displaySegment(unsigned int ledAddress, uint32_t color);
};
#endif

191
src/main.cpp Normal file
View File

@ -0,0 +1,191 @@
#include <Arduino.h>
#include <FS.h> //this needs to be first, or it all crashes and burns...
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
//needed for library
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include "SevenSegmentClock.h"
#define USE_CONFIG false
static const char *appName = "FastclockClient7Seg";
#define MAX_CLOCK_NAME_LEN 16
#define DEFAULT_CLOCK_CHANNEL 1
SevenSegmentClock sevenSegmentClock;
char static_ip[16] = "10.0.1.56";
char static_gw[16] = "10.0.1.1";
char static_sn[16] = "255.255.255.0";
char clockName[MAX_CLOCK_NAME_LEN+1] = "fastclk";
uint8_t clockChannel = DEFAULT_CLOCK_CHANNEL;
//flag for saving data
bool shouldSaveConfig = false;
//callback notifying us of the need to save config
void saveConfigCallback () {
Serial.println("Should save config");
shouldSaveConfig = true;
}
void setupWifiConnection() {
WiFiManager wifiManager;
//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);
//set static ip
IPAddress _ip,_gw,_sn;
_ip.fromString(static_ip);
_gw.fromString(static_gw);
_sn.fromString(static_sn);
//wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn);
//add all your parameters here
//**wifiManager.addParameter(&custom_mqtt_server);
//**wifiManager.addParameter(&custom_mqtt_port);
//wifiManager.addParameter(&custom_blynk_token);
//reset settings - for testing
//wifiManager.resetSettings();
//set minimu quality of signal so it ignores AP's under that quality
//defaults to 8%
wifiManager.setMinimumSignalQuality();
Serial.println("Starting autoConnect ...");
if (!wifiManager.autoConnect("FastclockClient7Seg", "password")) {
Serial.println("failed to connect and hit timeout");
delay(3000);
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(5000);
}
//if you get here you have connected to the WiFi
Serial.println("connected...yeey :)");
//save the custom parameters to FS
#if USE_CONFIG
if (shouldSaveConfig) {
Serial.println("saving config");
DynamicJsonDocument jsonBuffer(2048);
JsonObject json = jsonBuffer.createObject();
//**json["mqtt_server"] = mqtt_server;
//**json["mqtt_port"] = mqtt_port;
//json["blynk_token"] = blynk_token;
json["ip"] = WiFi.localIP().toString();
json["gateway"] = WiFi.gatewayIP().toString();
json["subnet"] = WiFi.subnetMask().toString();
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile) {
Serial.println("failed to open config file for writing");
}
serializeJsonPretty(json, Serial);
serializeJson(json, configFile);
configFile.close();
//end save
}
#endif
Serial.print("local ip: "); Serial.println(WiFi.localIP());
Serial.print("gateway: "); Serial.println(WiFi.gatewayIP());
Serial.print("subnet: "); Serial.println(WiFi.subnetMask());
}
void setup() {
// if coming from deep sleep, we just go to sleep again
// put your setup code here, to run once:
Serial.begin(115200);
Serial.print("Starting *** "); Serial.println(appName);
Serial.println(ESP.getResetReason());
//clean FS, for testing
//SPIFFS.format();
//read configuration from FS json
Serial.println("mounting FS...");
if (SPIFFS.begin()) {
Serial.println("mounted file system");
#if USE_CONFIG
if (SPIFFS.exists("/config.json")) {
//file exists, reading and loading
Serial.println("reading config file");
File configFile = SPIFFS.open("/config.json", "r");
if (configFile) {
Serial.println("opened config file");
size_t size = configFile.size();
// Allocate a buffer to store contents of the file.
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonDocument jsonBuffer(2048);
JsonObject json = jsonBuffer.createObject();
DeserializationError error = deserializeJson(jsonBuffer, json);
serializeJson(json, Serial);
if (!error) {
Serial.println("\nparsed json");
//**strcpy(mqtt_server, json["mqtt_server"]);
//**strcpy(mqtt_port, json["mqtt_port"]);
//strcpy(blynk_token, json["blynk_token"]);
if (json["ip"]) {
Serial.print("setting custom ip from config: ");
//**strcpy(static_ip, json["ip"]);
//**strcpy(static_gw, json["gateway"]);
//**strcpy(static_sn, json["subnet"]);
Serial.println(static_ip);
/* Serial.println("converting ip");
IPAddress ip = ipFromCharArray(static_ip);
Serial.println(ip);*/
} else {
Serial.println("no custom ip in config");
}
} else {
Serial.println("failed to load json config");
}
}
}
#endif
} else {
Serial.println("failed to mount FS");
}
//end read
Serial.print("static ip: "); Serial.println(static_ip);
// setupWifiConnection();
/*
radio.setClockChannel(clockChannel);
radio.setClockName(clockName);
radio.begin();
fastclock.begin();
pinMode(POWER_OFF_PIN, INPUT);
*/
sevenSegmentClock.begin();
}
int hours = 0, minutes = 0;
void loop() {
sevenSegmentClock.displayTime(hours, minutes++);
if (minutes > 99) { minutes = 0; }
if (minutes % 5 == 0) hours++;
if (hours > 99) hours = 0;
delay(1000);
}

11
test/README Normal file
View File

@ -0,0 +1,11 @@
This directory is intended for PIO 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 PIO Unit Testing:
- https://docs.platformio.org/page/plus/unit-testing.html