ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
tm1651.cpp
Go to the documentation of this file.
1// This Esphome TM1651 component for use with Mini Battery Displays (7 LED levels)
2// and removes the Esphome dependency on the TM1651 Arduino library.
3// It was largely based on the work of others as set out below.
4// @mrtoy-me July 2025
5// ==============================================================================================
6// Original Arduino TM1651 library:
7// Author:Fred.Chu
8// Date:14 August, 2014
9// Applicable Module: Battery Display v1.0
10// This library is free software; you can redistribute it and/or
11// modify it under the terms of the GNU Lesser General Public
12// License as published by the Free Software Foundation; either
13// version 2.1 of the License, or (at your option) any later version.
14// This library is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the GNU
17// Lesser General Public License for more details.
18// Modified record:
19// Author: Detlef Giessmann Germany
20// Mail: mydiyp@web.de
21// Demo for the new 7 LED Battery-Display 2017
22// IDE: Arduino-1.6.5
23// Type: OPEN-SMART CX10*4RY68 4Color
24// Date: 01.05.2017
25// ==============================================================================================
26// Esphome component using arduino TM1651 library:
27// MIT License
28// Copyright (c) 2019 freekode
29// ==============================================================================================
30// Library and command-line (python) program to control mini battery displays on Raspberry Pi:
31// MIT License
32// Copyright (c) 2020 Koen Vervloese
33// ==============================================================================================
34// MIT License
35// Permission is hereby granted, free of charge, to any person obtaining a copy
36// of this software and associated documentation files (the "Software"), to deal
37// in the Software without restriction, including without limitation the rights
38// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39// copies of the Software, and to permit persons to whom the Software is
40// furnished to do so, subject to the following conditions:
41// The above copyright notice and this permission notice shall be included in all
42// copies or substantial portions of the Software.
43// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49// SOFTWARE.
50
51#include "tm1651.h"
52#include "esphome/core/log.h"
53
54namespace esphome::tm1651 {
55
56static const char *const TAG = "tm1651.display";
57
58static const bool LINE_HIGH = true;
59static const bool LINE_LOW = false;
60
61// TM1651 maximum frequency is 500 kHz (duty ratio 50%) = 2 microseconds / cycle
62static const uint8_t CLOCK_CYCLE = 8;
63
64static const uint8_t HALF_CLOCK_CYCLE = CLOCK_CYCLE / 2;
65static const uint8_t QUARTER_CLOCK_CYCLE = CLOCK_CYCLE / 4;
66
67static const uint8_t ADDR_FIXED = 0x44; // fixed address mode
68static const uint8_t ADDR_START = 0xC0; // address of the display register
69
70static const uint8_t DISPLAY_OFF = 0x80;
71static const uint8_t DISPLAY_ON = 0x88;
72
73static const uint8_t MAX_DISPLAY_LEVELS = 7;
74
75static const uint8_t PERCENT100 = 100;
76static const uint8_t PERCENT50 = 50;
77
78static const uint8_t TM1651_BRIGHTNESS_DARKEST = 0;
79static const uint8_t TM1651_BRIGHTNESS_TYPICAL = 2;
80static const uint8_t TM1651_BRIGHTNESS_BRIGHTEST = 7;
81
82static const uint8_t TM1651_LEVEL_TAB[] = {0b00000000, 0b00000001, 0b00000011, 0b00000111,
83 0b00001111, 0b00011111, 0b00111111, 0b01111111};
84
85// public
86
88 this->clk_pin_->setup();
90
91 this->dio_pin_->setup();
93
94 this->brightness_ = TM1651_BRIGHTNESS_TYPICAL;
95
96 // clear display
97 this->display_level_();
98 this->update_brightness_(DISPLAY_ON);
99}
100
102 ESP_LOGCONFIG(TAG, "Battery Display");
103 LOG_PIN(" CLK: ", clk_pin_);
104 LOG_PIN(" DIO: ", dio_pin_);
105}
106
107void TM1651Display::set_brightness(uint8_t new_brightness) {
108 this->brightness_ = this->remap_brightness_(new_brightness);
109 if (this->display_on_) {
110 this->update_brightness_(DISPLAY_ON);
111 }
112}
113
114void TM1651Display::set_level(uint8_t new_level) {
115 if (new_level > MAX_DISPLAY_LEVELS)
116 new_level = MAX_DISPLAY_LEVELS;
117 this->level_ = new_level;
118 if (this->display_on_) {
119 this->display_level_();
120 }
121}
122
123void TM1651Display::set_level_percent(uint8_t percentage) {
124 this->level_ = this->calculate_level_(percentage);
125 if (this->display_on_) {
126 this->display_level_();
127 }
128}
129
131 this->display_on_ = false;
132 this->update_brightness_(DISPLAY_OFF);
133}
134
136 this->display_on_ = true;
137 // display level as it could have been changed when display turned off
138 this->display_level_();
139 this->update_brightness_(DISPLAY_ON);
140}
141
142// protected
143
144uint8_t TM1651Display::calculate_level_(uint8_t percentage) {
145 if (percentage > PERCENT100)
146 percentage = PERCENT100;
147 // scale 0-100% to 0-7 display levels
148 // use integer arithmetic with rounding
149 uint16_t initial_scaling = (percentage * MAX_DISPLAY_LEVELS) + PERCENT50;
150 return (uint8_t) (initial_scaling / PERCENT100);
151}
152
154 this->start_();
155 this->write_byte_(ADDR_FIXED);
156 this->stop_();
157
158 this->start_();
159 this->write_byte_(ADDR_START);
160 this->write_byte_(TM1651_LEVEL_TAB[this->level_]);
161 this->stop_();
162}
163
164uint8_t TM1651Display::remap_brightness_(uint8_t new_brightness) {
165 if (new_brightness <= 1)
166 return TM1651_BRIGHTNESS_DARKEST;
167 if (new_brightness == 2)
168 return TM1651_BRIGHTNESS_TYPICAL;
169
170 // new_brightness >= 3
171 return TM1651_BRIGHTNESS_BRIGHTEST;
172}
173
174void TM1651Display::update_brightness_(uint8_t on_off_control) {
175 this->start_();
176 this->write_byte_(on_off_control | this->brightness_);
177 this->stop_();
178}
179
180// low level functions
181
182bool TM1651Display::write_byte_(uint8_t data) {
183 // data bit written to DIO when CLK is low
184 for (uint8_t i = 0; i < 8; i++) {
185 this->half_cycle_clock_low_((bool) (data & 0x01));
187 data >>= 1;
188 }
189
190 // start 9th cycle, setting DIO high and look for ack
191 this->half_cycle_clock_low_(LINE_HIGH);
192 return this->half_cycle_clock_high_ack_();
193}
194
196 // first half cycle, clock low and write data bit
197 this->clk_pin_->digital_write(LINE_LOW);
198 delayMicroseconds(QUARTER_CLOCK_CYCLE);
199
200 this->dio_pin_->digital_write(data_bit);
201 delayMicroseconds(QUARTER_CLOCK_CYCLE);
202}
203
205 // second half cycle, clock high
206 this->clk_pin_->digital_write(LINE_HIGH);
207 delayMicroseconds(HALF_CLOCK_CYCLE);
208}
209
211 // second half cycle, clock high and check for ack
212 this->clk_pin_->digital_write(LINE_HIGH);
213 delayMicroseconds(QUARTER_CLOCK_CYCLE);
214
216 // valid ack on DIO is low
217 bool ack = (!this->dio_pin_->digital_read());
218
220
221 // ack should be set DIO low by now
222 // if its not, set DIO low before the next cycle
223 if (!ack) {
224 this->dio_pin_->digital_write(LINE_LOW);
225 }
226 delayMicroseconds(QUARTER_CLOCK_CYCLE);
227
228 // begin next cycle
229 this->clk_pin_->digital_write(LINE_LOW);
230
231 return ack;
232}
233
235 // start data transmission
236 this->delineate_transmission_(LINE_HIGH);
237}
238
240 // stop data transmission
241 this->delineate_transmission_(LINE_LOW);
242}
243
245 // delineate data transmission
246 // DIO changes its value while CLK is high
247
248 this->dio_pin_->digital_write(dio_state);
249 delayMicroseconds(HALF_CLOCK_CYCLE);
250
251 this->clk_pin_->digital_write(LINE_HIGH);
252 delayMicroseconds(QUARTER_CLOCK_CYCLE);
253
254 this->dio_pin_->digital_write(!dio_state);
255 delayMicroseconds(QUARTER_CLOCK_CYCLE);
256}
257
258} // namespace esphome::tm1651
virtual void pin_mode(gpio::Flags flags)=0
virtual void setup()=0
virtual void digital_write(bool value)=0
virtual bool digital_read()=0
InternalGPIOPin * clk_pin_
Definition tm1651.h:51
void update_brightness_(uint8_t on_off_control)
Definition tm1651.cpp:174
void set_level_percent(uint8_t percentage)
Definition tm1651.cpp:123
void set_brightness(uint8_t new_brightness)
Definition tm1651.cpp:107
void delineate_transmission_(bool dio_state)
Definition tm1651.cpp:244
uint8_t calculate_level_(uint8_t percentage)
Definition tm1651.cpp:144
bool write_byte_(uint8_t data)
Definition tm1651.cpp:182
uint8_t remap_brightness_(uint8_t new_brightness)
Definition tm1651.cpp:164
void half_cycle_clock_low_(bool data_bit)
Definition tm1651.cpp:195
InternalGPIOPin * dio_pin_
Definition tm1651.h:52
void set_level(uint8_t new_level)
Definition tm1651.cpp:114
void dump_config() override
Definition tm1651.cpp:101
@ FLAG_OUTPUT
Definition gpio.h:28
@ FLAG_INPUT
Definition gpio.h:27
void IRAM_ATTR HOT delayMicroseconds(uint32_t us)
Definition hal.cpp:48
uint8_t ack