2024-11-20 13:31:21 +00:00
|
|
|
/*
|
|
|
|
xdsp_13_tm1640.ino - TM1640B LED display controller support for Tasmota
|
|
|
|
|
|
|
|
Copyright (C) 2024 Stefan Oskamp
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program 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 General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY
|
|
|
|
#ifdef USE_DISPLAY_TM1640
|
|
|
|
/*********************************************************************************************\
|
|
|
|
This driver enables the display of the current time, numbers (both integers and floats) and
|
|
|
|
basic text on the IoTTimer clock.
|
|
|
|
|
|
|
|
Template {"NAME":"IoTTimer Lo","GPIO":[32,33,0,34,3872,1312,0,0,10944,10912,640,480,608,4768],"FLAG":0,"BASE":18}
|
|
|
|
|
|
|
|
In addition, it is possible to set brightness (seven levels plus off) and clear the display.
|
|
|
|
|
|
|
|
To use, compile Tasmota with USE_DISPLAY, USE_DISPLAY_TM1640 and USE_IOTTIMER, or build the tasmota-display
|
|
|
|
firmware.
|
|
|
|
|
|
|
|
Either use following template:
|
|
|
|
Template {"NAME":"IoTTimer Lo","GPIO":[32,33,0,34,3872,1312,0,0,10944,10912,640,480,608,4768],"FLAG":0,"BASE":18}
|
|
|
|
|
|
|
|
or configure manually:
|
|
|
|
For the IoTTimer clock assign the pins as follows from Tasmota's GUI:
|
|
|
|
|
|
|
|
GPIO12 --> "TM1640 DIN"
|
|
|
|
GPIO13 --> "TM1640 CLK"
|
|
|
|
|
|
|
|
Once the GPIO configuration is saved and the ESP8266/ESP32 module restarts, set the Display
|
|
|
|
Model to 13 and Display Mode to 0 using the command "Backlog DisplayModel 13 ; DisplayMode 0;"
|
|
|
|
Before using it, set the Display Type to 1 (for IOTTIMER) using the "DisplayType 1" command.
|
|
|
|
|
|
|
|
After the ESP8266 restarts again, turn ON the display with the command "Power 1"
|
|
|
|
|
|
|
|
Now, the following "Display" commands can be used:
|
|
|
|
|
|
|
|
|
|
|
|
DisplayClear
|
|
|
|
|
|
|
|
Clears the display, command: "DisplayClear"
|
|
|
|
|
|
|
|
|
|
|
|
DisplayFloat num
|
|
|
|
|
|
|
|
Clears and then displays float (with decimal point) command e.g., "DisplayFloat 12.3"
|
|
|
|
See function description below for more details.
|
|
|
|
|
|
|
|
|
|
|
|
DisplayFloatNC num
|
|
|
|
|
|
|
|
Same as DisplayFloatNC
|
|
|
|
|
|
|
|
|
|
|
|
DisplayClock 1|2|3|4
|
|
|
|
|
|
|
|
Displays a clock.
|
|
|
|
Commands "DisplayClock 1" // 12 hr format
|
|
|
|
"DisplayClock 2" // 24 hr format
|
|
|
|
"DisplayClock 3" // 12-hour without seconds
|
|
|
|
"DisplayClock 4" // 24-hour without seconds
|
|
|
|
|
|
|
|
|
|
|
|
In addition, if you compile using USE_DISPLAY_MODES1TO5, setting DisplayMode to 1 shows the time,
|
|
|
|
setting it to 2 shows the date and setting it to 3 alternates between time and date (using "DisplayRefresh [1..7]"
|
|
|
|
for the time and seconds you want to show the time before displaying the date)
|
|
|
|
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
#define XDSP_13 13
|
|
|
|
|
|
|
|
#define TM1640_CMD_DATA_AUTO 0x40
|
|
|
|
#define TM1640_CMD_DATA_FIXED 0x44
|
|
|
|
#define TM1640_CMD_DISPLAY 0x80
|
|
|
|
#define TM1640_CMD_ADDRESS 0xC0
|
|
|
|
|
|
|
|
#define TM1640_CLOCK_DELAY 1 // uSec
|
|
|
|
|
|
|
|
#define LEVEL_MIN 0
|
|
|
|
#define LEVEL_MAX 100
|
|
|
|
|
|
|
|
enum tm1640_display_options_types {
|
|
|
|
TM1640_DEFAULT,
|
|
|
|
TM1640_IOTTIMER // IOTTIMER WiFi clock
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct Tm1640_t {
|
|
|
|
int8_t clock_pin;
|
|
|
|
int8_t data_pin;
|
|
|
|
bool show_clock;
|
|
|
|
bool clock_24;
|
|
|
|
bool clock_seconds;
|
|
|
|
} Tm1640_t;
|
|
|
|
|
|
|
|
Tm1640_t* Tm1640 = nullptr;
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* TM1640 low level functions
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
void TM1640Start (void) {
|
|
|
|
digitalWrite(Tm1640->data_pin, LOW);
|
|
|
|
digitalWrite(Tm1640->clock_pin, LOW);
|
|
|
|
delayMicroseconds(TM1640_CLOCK_DELAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1640Stop (void) {
|
|
|
|
digitalWrite(Tm1640->clock_pin, HIGH);
|
|
|
|
digitalWrite(Tm1640->data_pin, HIGH);
|
|
|
|
delayMicroseconds(TM1640_CLOCK_DELAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1640Send(uint8_t data) {
|
|
|
|
for (uint32_t i = 0; i < 8; i++) { // 8 bits
|
|
|
|
digitalWrite(Tm1640->data_pin, data & 1 ? HIGH : LOW);
|
|
|
|
delayMicroseconds(TM1640_CLOCK_DELAY);
|
|
|
|
data >>= 1;
|
|
|
|
digitalWrite(Tm1640->clock_pin, HIGH);
|
|
|
|
delayMicroseconds(TM1640_CLOCK_DELAY);
|
|
|
|
digitalWrite(Tm1640->clock_pin, LOW);
|
|
|
|
delayMicroseconds(TM1640_CLOCK_DELAY);
|
|
|
|
}
|
|
|
|
digitalWrite(Tm1640->data_pin, LOW);
|
|
|
|
delayMicroseconds(TM1640_CLOCK_DELAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1640SendData(uint8_t address, uint8_t data) {
|
|
|
|
// First, send data command using FIXED addressing:
|
|
|
|
TM1640Start();
|
|
|
|
TM1640Send(TM1640_CMD_DATA_FIXED);
|
|
|
|
TM1640Stop();
|
|
|
|
// Then, send address and one data byte:
|
|
|
|
TM1640Start();
|
|
|
|
TM1640Send(TM1640_CMD_ADDRESS | address);
|
|
|
|
TM1640Send(data);
|
|
|
|
TM1640Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1640SendDataArray(uint8_t address, uint8_t *data, uint8_t count) {
|
|
|
|
// First, send data command using AUTO addressing:
|
|
|
|
TM1640Start();
|
|
|
|
TM1640Send(TM1640_CMD_DATA_AUTO);
|
|
|
|
TM1640Stop();
|
|
|
|
// Then, send address and all data bytes:
|
|
|
|
TM1640Start();
|
|
|
|
TM1640Send(TM1640_CMD_ADDRESS | address);
|
|
|
|
while (count-- > 0) {
|
|
|
|
TM1640Send(*data++);
|
|
|
|
}
|
|
|
|
TM1640Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
void TM1640SetBrightness(uint8_t level) {
|
|
|
|
// level can be 0 to 8.
|
|
|
|
// 0 means off
|
|
|
|
//
|
|
|
|
// Other levels are mapped to TM1640 levels 0 ... 7
|
|
|
|
// The mapping to the PWM level is non-linear, according to the data sheet
|
|
|
|
// level | TM1640 | PWM
|
|
|
|
// 1 | 0 | 1/16
|
|
|
|
// 2 | 1 | 2/16
|
|
|
|
// 3 | 2 | 4/16
|
|
|
|
// 4 | 3 | 10/16
|
|
|
|
// 5 | 4 | 11/16
|
|
|
|
// 6 | 5 | 12/16
|
|
|
|
// 7 | 6 | 13/16
|
|
|
|
// 8 | 7 | 14/16
|
|
|
|
uint8_t cmd = TM1640_CMD_DISPLAY | (level > 0 ? 0x8 : 0) | ((level - 1) % 8);
|
|
|
|
TM1640Start();
|
|
|
|
TM1640Send (cmd);
|
|
|
|
TM1640Stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Init function
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
void TM1640Init(void) {
|
|
|
|
if (PinUsed(GPIO_TM1640CLK) && PinUsed(GPIO_TM1640DIN)) {
|
|
|
|
Tm1640 = (Tm1640_t*)calloc(sizeof(Tm1640_t), 1); // Need calloc to reset registers to 0/false
|
|
|
|
if (nullptr == Tm1640) { return; }
|
|
|
|
|
|
|
|
Tm1640->clock_pin = Pin(GPIO_TM1640CLK);
|
|
|
|
Tm1640->data_pin = Pin(GPIO_TM1640DIN);
|
|
|
|
|
|
|
|
pinMode(Tm1640->data_pin, OUTPUT);
|
|
|
|
pinMode(Tm1640->clock_pin, OUTPUT);
|
|
|
|
digitalWrite(Tm1640->clock_pin, HIGH);
|
|
|
|
digitalWrite(Tm1640->data_pin, HIGH);
|
|
|
|
|
|
|
|
Settings->display_model = XDSP_13;
|
|
|
|
|
|
|
|
#ifdef USE_IOTTIMER
|
|
|
|
Settings->display_options.type = TM1640_IOTTIMER;
|
|
|
|
|
|
|
|
Settings->display_cols[0] = 9; // 4 (left) + 2 (lower right) + 3 (upper right).
|
|
|
|
Settings->display_rows = 1;
|
|
|
|
Settings->display_width = Settings->display_cols[0];
|
|
|
|
Settings->display_height = Settings->display_rows;
|
|
|
|
|
|
|
|
Tm1640->clock_24 = true;
|
|
|
|
Tm1640->clock_seconds = true;
|
|
|
|
|
|
|
|
IoTTimerDim();
|
|
|
|
IoTTimerClearDisplay();
|
|
|
|
#endif // USE_IOTTIMER
|
|
|
|
|
|
|
|
AddLog(LOG_LEVEL_INFO, PSTR("DSP: TM1640 with %d digits (type %d)"), Settings->display_width, Settings->display_options.type);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* IotTimer
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
#ifdef USE_IOTTIMER
|
|
|
|
|
|
|
|
/*
|
|
|
|
(specifically for its use in the IOTTIMER WiFi clock)
|
|
|
|
|
|
|
|
The WiFi LED clock called IOTTIMER has the following characteristics:
|
|
|
|
- Controlled by an ESP-12F
|
|
|
|
- Display with four 35 mm (1 12/32 in), two 21 mm (26/32 in), and three 12 mm (~1/2 in),
|
|
|
|
seven-segment LED digits, plus special symbols (alarm, AM/PM)
|
|
|
|
- TM1640B LED controller
|
|
|
|
- R8010 RTC with CR1220 battery
|
|
|
|
- Temperature sensor M1601
|
|
|
|
- Ambient light sensor (analog voltage)
|
|
|
|
- Buzzer
|
|
|
|
- Three buttons on the backside
|
|
|
|
- USB C port for power supply (only)
|
|
|
|
|
|
|
|
The TM1640B chip is a controller for a sixteen-digit seven-segment (plus dot) LED display.
|
|
|
|
It is also sometimes used to control a 16 x 8 LED matrix. The controller is controlled
|
|
|
|
through a proprietary two-wire serial interface bearing some similarities with I2C. The
|
|
|
|
two wires are called CLK and DIN. We use two GPIO pins and one-microsecond sleeps to
|
|
|
|
implement the required timing.
|
|
|
|
|
|
|
|
The wiring of the LEDs in the IOTTIMER clock has been optimized for a simple routing of the
|
|
|
|
traces on the display board. The enumeration of the digit segments is non-standard, but
|
|
|
|
consistent across all digits. The bigger digits have two LEDs per segment, controlled by
|
|
|
|
separate digit lines of the LED controller. From the software perspective, they appear as
|
|
|
|
two layers of four digits each.
|
|
|
|
|
|
|
|
The brightness of the LEDs can be controlled in seven steps (plus off). In theory, the
|
|
|
|
brightness of the segments with two LEDs could be set in fifteen levels (plus off).
|
|
|
|
To keep things simple and to avoid brightness gradients within segments, both LEDs of a
|
|
|
|
segment will always be set to the same level.
|
|
|
|
|
|
|
|
The intention of this display driver (together with the drivers for the other components)
|
|
|
|
is to be able to use the IOTTIMER as an alarm clock that can be fully integrated in your
|
|
|
|
home automation using Tasmota and rules.
|
|
|
|
|
|
|
|
This driver is not a generic TM1640B driver as use cases of the TM1640B in different
|
|
|
|
devices will differ significantly.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#define IOTTIMER_DIGITS 16
|
|
|
|
#define IOTTIMER_DOT_BIT 2
|
|
|
|
|
|
|
|
static unsigned char IoTTimerDisplay[IOTTIMER_DIGITS];
|
|
|
|
|
|
|
|
// Wiring of the LEDs (per digit):
|
|
|
|
//
|
|
|
|
// Seg# Bit Hex
|
|
|
|
// 07 06 40
|
|
|
|
// 08 01 07 00 80 01
|
|
|
|
// 02 01 02
|
|
|
|
// 06 04 05 03 20 08
|
|
|
|
// 05 03 04 02 10 04
|
|
|
|
//
|
|
|
|
// Font as per wiring:
|
|
|
|
static const byte IoTTimerFont[128] {
|
|
|
|
//0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F
|
|
|
|
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, // 0x10
|
|
|
|
// SP ! " # $ % & ' ( ) * + , - . /
|
|
|
|
0x00, 0xA0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x01, 0xF0, 0x59, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, // 0x20
|
|
|
|
//0 1 2 3 4 5 6 7 8 9 : ; < = > ?
|
|
|
|
0xF9, 0x09, 0x73, 0x5B, 0x8B, 0xDA, 0xFA, 0x49, 0xFB, 0xDB, 0x00, 0x00, 0x00, 0x12, 0x00, 0x63, // 0x30
|
|
|
|
// @ A B C D E F G H I J K L M N O
|
|
|
|
0x00, 0xEB, 0xBA, 0xF0, 0x3B, 0xF2, 0xE2, 0xFA, 0xAB, 0x09, 0x19, 0x00, 0xB0, 0x00, 0xE9, 0xF9, // 0x40
|
|
|
|
// P Q R S T U V W X Y Z [ \ ] ^(°) _
|
|
|
|
0xE3, 0xAB, 0x22, 0xDA, 0xB2, 0xB9, 0x00, 0x00, 0x00, 0x4B, 0x00, 0xF0, 0x00, 0x59, 0xC3, 0x10, // 0x50
|
|
|
|
// `=° a b c d e f g h i j k l m n o
|
|
|
|
0x01, 0x7B, 0xBA, 0x32, 0x3B, 0xF3, 0xE2, 0xDB, 0xAA, 0x08, 0x19, 0x00, 0x09, 0x00, 0x2A, 0x3A, // 0x60
|
|
|
|
// p q r s t u v w x y z { | } ~ DEL
|
|
|
|
0xE3, 0xAB, 0x22, 0xDA, 0xB2, 0x38, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x0B, 0x09, 0xA2, 0x00, 0x00 // 0x70
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void IoTTimerDim(void)
|
|
|
|
{
|
|
|
|
TM1640SetBrightness (changeUIntScale(GetDisplayDimmer(), 0, 100, 0, 8));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void IoTTimerDisplayOn (void)
|
|
|
|
{
|
|
|
|
IoTTimerDim();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void IoTTimerDisplayOff (void)
|
|
|
|
{
|
|
|
|
TM1640SetBrightness (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void IoTTimerDisplayOnOff(void)
|
|
|
|
{
|
|
|
|
if (disp_power) {
|
|
|
|
IoTTimerDisplayOn();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
IoTTimerDisplayOff();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void IoTTimerClearDisplay (void)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < IOTTIMER_DIGITS; i++) {
|
|
|
|
IoTTimerDisplay[i] = 0;
|
|
|
|
}
|
|
|
|
TM1640SendDataArray(0, IoTTimerDisplay, IOTTIMER_DIGITS);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Init function
|
|
|
|
\*********************************************************************************************/
|
|
|
|
void IoTTimerInit(uint8_t mode)
|
|
|
|
{
|
|
|
|
switch(mode) {
|
|
|
|
case DISPLAY_INIT_MODE:
|
|
|
|
IoTTimerDim();
|
|
|
|
IoTTimerClearDisplay();
|
|
|
|
break;
|
|
|
|
case DISPLAY_INIT_PARTIAL:
|
|
|
|
case DISPLAY_INIT_FULL:
|
|
|
|
IoTTimerDim();
|
|
|
|
IoTTimerClearDisplay();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void IoTTimerDrawStringAt(uint32_t x, uint32_t y, const char *str, uint32_t color = 0, uint32_t flag = 0);
|
|
|
|
void IoTTimerDrawStringAt(uint32_t x, uint32_t y, const char *str, uint32_t color, uint32_t flag) {
|
|
|
|
// displaytext [x] = 0123456789
|
|
|
|
// Considers display as 1111223334 where 1111 is large white display,
|
|
|
|
// 22 is small white display,
|
|
|
|
// 333 is green display,
|
|
|
|
// 4 is "1" = pm, "2" = alarm, "3" = both
|
|
|
|
// Following also works - notice scattered dot(.), colon(:), minus(-) or plus(+)
|
|
|
|
// displaytext 12:34:56.7.8.9ab - Show all lights including "pm" (=a) and "alarm" (=b)
|
|
|
|
// displaytext 12:34:56 - Show time
|
|
|
|
// displaytext 05-11-24 - Show date
|
|
|
|
// displaytext [x6]12.3 - Show value in green leds
|
|
|
|
// displaytext [ztS] - Clear display and show current time with seconds
|
|
|
|
|
|
|
|
bool alarm = false;
|
|
|
|
bool pm = false;
|
|
|
|
bool dot_left_up = false;
|
|
|
|
bool dot_left_dn = false;
|
|
|
|
bool dot_right_dn = false;
|
|
|
|
bool dot_right_up_left = false;
|
|
|
|
bool dot_right_up_right = false;
|
|
|
|
bool dash_left = false;
|
|
|
|
bool dash_right = false;
|
|
|
|
|
|
|
|
char chr;
|
|
|
|
uint32_t idx = x;
|
|
|
|
const char *pos = str;
|
|
|
|
while (*pos) {
|
|
|
|
chr = *pos & 0x7F; // We only support 0 to 127
|
|
|
|
switch (idx) {
|
|
|
|
case 0:
|
|
|
|
IoTTimerDisplay[12] = IoTTimerDisplay[13] = IoTTimerFont[chr]; // col 0
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
IoTTimerDisplay[14] = IoTTimerDisplay[15] = IoTTimerFont[chr]; // col 1
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
if (('.' == chr) || (':' == chr) || ('-' == chr) || ('+' == chr)) {
|
|
|
|
if ('.' == chr) {
|
|
|
|
dot_left_dn = true;
|
|
|
|
}
|
|
|
|
else if (':' == chr) {
|
|
|
|
dot_left_up = true;
|
|
|
|
dot_left_dn = true;
|
|
|
|
}
|
|
|
|
else if ('-' == chr) {
|
|
|
|
dash_left = true;
|
|
|
|
dash_right = true;
|
|
|
|
}
|
|
|
|
else if ('+' == chr) {
|
|
|
|
dot_left_up = true;
|
|
|
|
dot_left_dn = true;
|
|
|
|
dash_left = true;
|
|
|
|
dash_right = true;
|
|
|
|
}
|
|
|
|
idx--;
|
|
|
|
} else {
|
|
|
|
IoTTimerDisplay[4] = IoTTimerDisplay[5] = IoTTimerFont[chr]; // col 2
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
IoTTimerDisplay[11] = IoTTimerDisplay[1] = IoTTimerFont[chr]; // col 3
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
if (('.' == chr) || (':' == chr) || ('-' == chr)) {
|
|
|
|
idx--; // Skip
|
|
|
|
} else {
|
|
|
|
IoTTimerDisplay[6] = IoTTimerFont[chr]; // col 4
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 5:
|
|
|
|
IoTTimerDisplay[7] = IoTTimerFont[chr]; // col 5
|
|
|
|
break;
|
|
|
|
case 6:
|
|
|
|
if ('.' == chr) {
|
|
|
|
dot_right_dn = true;
|
|
|
|
idx--;
|
|
|
|
} else {
|
|
|
|
// Upper right (green)
|
|
|
|
IoTTimerDisplay[9] = IoTTimerFont[chr]; // col 6
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 7:
|
|
|
|
if ('.' == chr) {
|
|
|
|
dot_right_up_left = true;
|
|
|
|
idx--;
|
|
|
|
} else {
|
|
|
|
IoTTimerDisplay[10] = IoTTimerFont[chr]; // col 7
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 8:
|
|
|
|
if ('.' == chr) {
|
|
|
|
dot_right_up_right = true;
|
|
|
|
idx--;
|
|
|
|
} else {
|
|
|
|
IoTTimerDisplay[8] = IoTTimerFont[chr]; // col 8
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 9: // col 9
|
|
|
|
if (chr & 0x01) { // 1, A, a
|
|
|
|
pm = true;
|
|
|
|
}
|
|
|
|
if (chr & 0x02) { // 2, B, b
|
|
|
|
alarm = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
idx++;
|
|
|
|
pos++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dots and dash
|
|
|
|
if (alarm) {
|
|
|
|
IoTTimerDisplay[12] |= 1 << IOTTIMER_DOT_BIT; // Alarm symbol
|
|
|
|
}
|
|
|
|
if (pm) {
|
|
|
|
IoTTimerDisplay[13] |= 1 << IOTTIMER_DOT_BIT; // PM
|
|
|
|
}
|
|
|
|
if (dot_left_up) {
|
|
|
|
IoTTimerDisplay[14] |= 1 << IOTTIMER_DOT_BIT; // Upper dot
|
|
|
|
}
|
|
|
|
if (dot_left_dn) {
|
|
|
|
IoTTimerDisplay[4] |= 1 << IOTTIMER_DOT_BIT; // Lower dot
|
|
|
|
}
|
|
|
|
if (dot_right_dn) {
|
|
|
|
IoTTimerDisplay[7] |= 1 << IOTTIMER_DOT_BIT; // Blue dot
|
|
|
|
}
|
|
|
|
if (dot_right_up_left) {
|
|
|
|
IoTTimerDisplay[10] |= 1 << IOTTIMER_DOT_BIT; // Green dot left
|
|
|
|
}
|
|
|
|
if (dot_right_up_right) {
|
|
|
|
IoTTimerDisplay[8] |= 1 << IOTTIMER_DOT_BIT; // Green dot right
|
|
|
|
}
|
|
|
|
if (dash_left) {
|
|
|
|
IoTTimerDisplay[5] |= 1 << IOTTIMER_DOT_BIT;
|
|
|
|
}
|
|
|
|
if (dash_right) {
|
|
|
|
IoTTimerDisplay[15] |= 1 << IOTTIMER_DOT_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
TM1640SendDataArray(0, IoTTimerDisplay, IOTTIMER_DIGITS);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Displays floating point number in the upper right sub-display of the IOTTIMER.
|
|
|
|
* Format is always "[n]n[.]n" (negative number is "-n[.]n")
|
|
|
|
\*********************************************************************************************/
|
|
|
|
void IoTTimerShowFloat(float f) {
|
|
|
|
/*
|
|
|
|
char buffer[16];
|
|
|
|
ext_snprintf_P(buffer, sizeof(buffer),PSTR("%1_f"), &f);
|
|
|
|
IoTTimerDrawStringAt(6, 0, buffer);
|
|
|
|
*/
|
|
|
|
bool negative = false;
|
|
|
|
float threshold = 99.95;
|
|
|
|
if (f < 0.0) {
|
|
|
|
f = -f;
|
|
|
|
negative = true;
|
|
|
|
threshold = 9.95;
|
|
|
|
}
|
|
|
|
uint8_t precision = 0;
|
|
|
|
if (f < threshold) {
|
|
|
|
f *= 10.0;
|
|
|
|
precision++;
|
|
|
|
}
|
|
|
|
uint32_t n = (uint32_t) (f + 0.5);
|
|
|
|
char buffer[5] = { 0 };
|
|
|
|
if (negative) {
|
|
|
|
if (n > 99) {
|
|
|
|
n = 99;
|
|
|
|
}
|
|
|
|
buffer[0] = '-';
|
|
|
|
} else {
|
|
|
|
if (n > 999) {
|
|
|
|
n = 999;
|
|
|
|
}
|
|
|
|
if (n / 100 != 0) {
|
|
|
|
buffer[0] = '0' + n / 100;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buffer[1] = '0' + n % 100 / 10;
|
|
|
|
uint32_t idx = 2;
|
|
|
|
if (precision == 1) {
|
|
|
|
buffer[2] = '.';
|
|
|
|
idx++;
|
|
|
|
}
|
|
|
|
buffer[idx] = '0' + n % 10;
|
|
|
|
IoTTimerDrawStringAt(6, 0, buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Update the temperature in the upper right corner.
|
|
|
|
\*********************************************************************************************/
|
|
|
|
void IoTTimerUpdateTemperature(void) {
|
|
|
|
IoTTimerShowFloat(ConvertTempToFahrenheit(TasmotaGlobal.temperature_celsius));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Adjust the brightness based on the photo diode voltage.
|
|
|
|
\*********************************************************************************************/
|
|
|
|
void IoTTimerAdjustBrightness(void) {
|
|
|
|
// Max ADC value is 3400 [lx], but that is only reached in direct sun light.
|
|
|
|
// 20 is already quite bright.
|
|
|
|
// Illuminance value of 0 should map to level 1 (level 0 is off)
|
|
|
|
static float filteredLevel = 1.0;
|
|
|
|
float level = (float) AdcGetLux(0) / (20.0 / 7.0) + 1.0;
|
|
|
|
if (level > 8.0) level = 8.0;
|
|
|
|
if (level < 1.0) level = 1.0;
|
|
|
|
filteredLevel = 0.9 * filteredLevel + 0.1 * (float) level;
|
|
|
|
|
|
|
|
TM1640SetBrightness ((int) (filteredLevel + 0.5));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Show the current time
|
|
|
|
\*********************************************************************************************/
|
|
|
|
void IoTTimerShowTime(void) {
|
|
|
|
uint8_t hour = RtcTime.hour;
|
|
|
|
uint8_t min = RtcTime.minute;
|
|
|
|
uint8_t sec = RtcTime.second;
|
|
|
|
uint8_t symbol = 0;
|
|
|
|
|
|
|
|
if (!Tm1640->clock_24) {
|
|
|
|
if (hour > 12) {
|
|
|
|
hour -= 12;
|
|
|
|
}
|
|
|
|
if (hour == 0) {
|
|
|
|
hour = 12;
|
|
|
|
}
|
|
|
|
symbol |= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buffer[16];
|
|
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("%2d:%02d"), hour, min);
|
|
|
|
if (Tm1640->clock_seconds) {
|
|
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("%s:%02d"), buffer, sec);
|
2024-11-20 13:49:15 +00:00
|
|
|
} else {
|
|
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("%s "), buffer); // Erase seconds in case toggling between date/time
|
2024-11-20 13:31:21 +00:00
|
|
|
}
|
|
|
|
IoTTimerDrawStringAt(0, 0, buffer);
|
|
|
|
|
|
|
|
if (Settings->timer[0].arm) {
|
|
|
|
symbol |= 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), symbol);
|
|
|
|
IoTTimerDrawStringAt(9, 0, buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Show the current date
|
|
|
|
\*********************************************************************************************/
|
|
|
|
void IoTTimerShowDate(void)
|
|
|
|
{
|
|
|
|
uint8_t left = RtcTime.day_of_month;
|
|
|
|
uint8_t middle = RtcTime.month;
|
|
|
|
uint8_t right = RtcTime.year % 100;
|
|
|
|
|
|
|
|
if (!Tm1640->clock_24) {
|
|
|
|
// Use U.S. date format.
|
|
|
|
left = RtcTime.month;
|
|
|
|
middle = RtcTime.day_of_month;
|
|
|
|
}
|
|
|
|
|
|
|
|
char buffer[16];
|
|
|
|
snprintf_P(buffer, sizeof(buffer), PSTR("%02d-%02d-%02d"), left, middle, right);
|
|
|
|
IoTTimerDrawStringAt(0, 0, buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void IoTTimerRefresh(void) { // Every second
|
2024-11-22 21:17:26 +00:00
|
|
|
if (!disp_power) { return; }
|
|
|
|
|
2024-11-20 13:31:21 +00:00
|
|
|
// Update temperature display content:
|
|
|
|
IoTTimerUpdateTemperature();
|
|
|
|
|
|
|
|
// Auto-adjust brightness:
|
|
|
|
IoTTimerAdjustBrightness();
|
|
|
|
|
|
|
|
if (Settings->display_mode) { // Mode 0 is User text
|
|
|
|
switch (Settings->display_mode) {
|
|
|
|
case 1: // Time
|
|
|
|
IoTTimerShowTime();
|
|
|
|
break;
|
|
|
|
case 2: // Date
|
|
|
|
IoTTimerShowDate();
|
|
|
|
break;
|
|
|
|
case 3: // Time/Date
|
|
|
|
if (TasmotaGlobal.uptime % Settings->display_refresh)
|
|
|
|
{
|
|
|
|
IoTTimerShowTime();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
IoTTimerShowDate();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
case 5:
|
|
|
|
// not in use
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_DISPLAY_MODES1TO5
|
|
|
|
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Displays a clock.
|
|
|
|
* Command: DisplayClock 1 // 12-hour format
|
|
|
|
* DisplayClock 2 // 24-hour format
|
|
|
|
* DisplayClock 3 // 12-hour without seconds
|
|
|
|
* DisplayClock 4 // 24-hour without seconds
|
|
|
|
\*********************************************************************************************/
|
|
|
|
void CmndIoTTimerClock(void) {
|
|
|
|
uint16_t val = XdrvMailbox.payload;
|
|
|
|
|
|
|
|
if (ArgC() == 0)
|
|
|
|
val = 0;
|
|
|
|
|
|
|
|
if ((val < 1) || (val > 4))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (val == 1) {
|
|
|
|
Tm1640->show_clock = true;
|
|
|
|
Tm1640->clock_24 = false;
|
|
|
|
Tm1640->clock_seconds = true;
|
|
|
|
}
|
|
|
|
else if (val == 2) {
|
|
|
|
Tm1640->show_clock = true;
|
|
|
|
Tm1640->clock_24 = true;
|
|
|
|
Tm1640->clock_seconds = true;
|
|
|
|
}
|
|
|
|
else if (val == 3) {
|
|
|
|
Tm1640->show_clock = true;
|
|
|
|
Tm1640->clock_24 = false;
|
|
|
|
Tm1640->clock_seconds = false;
|
|
|
|
}
|
|
|
|
else if (val == 4) {
|
|
|
|
Tm1640->show_clock = true;
|
|
|
|
Tm1640->clock_24 = true;
|
|
|
|
Tm1640->clock_seconds = false;
|
|
|
|
} else {
|
|
|
|
Tm1640->show_clock = false;
|
|
|
|
Tm1640->clock_24 = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
IoTTimerClearDisplay();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif // USE_IOTTIMER
|
|
|
|
|
|
|
|
/*********************************************************************************************\
|
|
|
|
* Interface
|
|
|
|
\*********************************************************************************************/
|
|
|
|
|
|
|
|
bool Xdsp13(uint32_t function) {
|
|
|
|
bool result = false;
|
|
|
|
|
|
|
|
if (FUNC_DISPLAY_INIT_DRIVER == function) {
|
|
|
|
TM1640Init();
|
|
|
|
}
|
|
|
|
else if (Tm1640 && (XDSP_13 == Settings->display_model)) {
|
|
|
|
|
|
|
|
#ifdef USE_IOTTIMER
|
|
|
|
|
|
|
|
switch (function) {
|
|
|
|
case FUNC_DISPLAY_INIT:
|
|
|
|
IoTTimerInit(dsp_init);
|
|
|
|
break;
|
|
|
|
#ifdef USE_DISPLAY_MODES1TO5
|
|
|
|
case FUNC_DISPLAY_EVERY_SECOND:
|
|
|
|
IoTTimerRefresh();
|
|
|
|
break;
|
|
|
|
#endif // USE_DISPLAY_MODES1TO5
|
|
|
|
case FUNC_DISPLAY_MODEL:
|
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
case FUNC_DISPLAY_CLOCK:
|
|
|
|
CmndIoTTimerClock();
|
|
|
|
break;
|
|
|
|
case FUNC_DISPLAY_CLEAR:
|
|
|
|
IoTTimerClearDisplay();
|
|
|
|
break;
|
|
|
|
case FUNC_DISPLAY_NUMBER:
|
|
|
|
case FUNC_DISPLAY_NUMBERNC:
|
|
|
|
case FUNC_DISPLAY_FLOAT:
|
|
|
|
case FUNC_DISPLAY_FLOATNC:
|
|
|
|
IoTTimerShowFloat(CharToFloat(XdrvMailbox.data));
|
|
|
|
break;
|
|
|
|
case FUNC_DISPLAY_DIM:
|
|
|
|
IoTTimerDim();
|
|
|
|
break;
|
|
|
|
case FUNC_DISPLAY_POWER:
|
|
|
|
IoTTimerDisplayOnOff();
|
|
|
|
break;
|
|
|
|
case FUNC_DISPLAY_DRAW_STRING:
|
|
|
|
IoTTimerDrawStringAt(dsp_x, dsp_y, dsp_str, dsp_color, dsp_flag);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_IOTTIMER
|
|
|
|
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // USE_DISPLAY_TM1640
|
|
|
|
#endif // USE_DISPLAY
|