AceSegment  0.8.0
A framework for rendering seven segment LED displays using the TM1637, MAX7219, HT16K33, or 74HC595 controller chips
Tm1637Module.h
1 /*
2 MIT License
3 
4 Copyright (c) 2021 Brian T. Park
5 
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12 
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23 */
24 
25 #ifndef ACE_SEGMENT_TM1637_MODULE_H
26 #define ACE_SEGMENT_TM1637_MODULE_H
27 
28 #include <Arduino.h>
29 #include <AceCommon.h> // incrementMod()
30 #include "../LedModule.h"
31 
32 class Tm1637ModuleTest_flushIncremental;
33 class Tm1637ModuleTest_flush;
34 
35 namespace ace_segment {
36 
47 static const uint16_t kDefaultTm1637DelayMicros = 100;
48 
62 extern const uint8_t kDigitRemapArray6Tm1637[6];
63 
64 namespace internal {
65 
74 constexpr uint8_t initialDirtyBits(uint8_t numBits) {
75  return ((uint16_t) 0x1 << numBits) - 1;
76 }
77 
78 } // namespace internal
79 
90 template <typename T_TMII, uint8_t T_DIGITS>
91 class Tm1637Module : public LedModule {
92  public:
93 
101  explicit Tm1637Module(
102  const T_TMII& tmiInterface,
103  const uint8_t* remapArray = nullptr
104  ) :
105  LedModule(T_DIGITS),
106  mTmiInterface(tmiInterface),
107  mRemapArray(remapArray)
108  {}
109 
110  //-----------------------------------------------------------------------
111  // Initialization and termination.
112  //-----------------------------------------------------------------------
113 
123  void begin() {
124  memset(mPatterns, 0, T_DIGITS);
125  mBrightness = kBrightnessCmd | kBrightnessLevelOn | 0x7;
126 
127  // Initially, we want to set all the dirty bits for digits and brightness,
128  // so that they are sent to the LED module upon the first flush() or
129  // flushIncremental(). The number of dirty bits required is `T_DIGITS +
130  // 1` because we use an extra bit for the brightness.
131  mIsDirty = internal::initialDirtyBits(T_DIGITS + 1);
132 
133  mFlushStage = 0;
134  }
135 
137  void end() {}
138 
139  //-----------------------------------------------------------------------
140  // Implement the LedModule interface
141  //-----------------------------------------------------------------------
142 
144  uint8_t getNumDigits() const { return T_DIGITS; }
145 
146  void setPatternAt(uint8_t pos, uint8_t pattern) override {
147  mPatterns[pos] = pattern;
148  setDirtyBit(pos);
149  }
150 
151  uint8_t getPatternAt(uint8_t pos) override {
152  return mPatterns[pos];
153  }
154 
155  void setBrightness(uint8_t brightness) override {
156  mBrightness = (mBrightness & ~0x7) | (brightness & 0x7);
157  setDirtyBit(kBrightnessDirtyBit);
158  }
159 
160  //-----------------------------------------------------------------------
161  // Additional brightness control supported by the TM1637 chip.
162  //-----------------------------------------------------------------------
163 
168  void setDisplayOn(bool on = true) {
169  if (on) {
170  mBrightness |= kBrightnessLevelOn;
171  } else {
172  mBrightness &= ~kBrightnessLevelOn;
173  }
174  setDirtyBit(kBrightnessDirtyBit);
175  }
176 
177  //-----------------------------------------------------------------------
178  // Methods related to rendering.
179  //-----------------------------------------------------------------------
180 
186  void flush() {
187  if (! mIsDirty) return;
188 
189  // Update the brightness first
190  mTmiInterface.startCondition();
191  mTmiInterface.sendByte(mBrightness);
192  mTmiInterface.stopCondition();
193 
194  // Update the digits using auto incrementing mode.
195  mTmiInterface.startCondition();
196  mTmiInterface.sendByte(kDataCmdAutoAddress);
197  mTmiInterface.stopCondition();
198 
199  mTmiInterface.startCondition();
200  mTmiInterface.sendByte(kAddressCmd);
201  for (uint8_t chipPos = 0; chipPos < T_DIGITS; ++chipPos) {
202  // Remap the logical position used by the controller to the actual
203  // position. For example, if the controller digit 0 appears at physical
204  // digit 2, we need to display the segment pattern given by logical
205  // position 2 when sending the byte to controller digit 0.
206  uint8_t physicalPos = remapLogicalToPhysical(chipPos);
207  uint8_t effectivePattern = mPatterns[physicalPos];
208  mTmiInterface.sendByte(effectivePattern);
209  }
210  mTmiInterface.stopCondition();
211 
212  mIsDirty = 0x0;
213  }
214 
249  if (mFlushStage == T_DIGITS) {
250  // Update brightness.
251  if (isDirtyBit(T_DIGITS)) {
252  mTmiInterface.startCondition();
253  mTmiInterface.sendByte(mBrightness);
254  mTmiInterface.stopCondition();
255  clearDirtyBit(T_DIGITS);
256  }
257  } else {
258  // Remap the logical position used by the controller to the actual
259  // position. For example, if the controller digit 0 appears at physical
260  // digit 2, we need to display the segment pattern given by logical
261  // position 2 when sending the byte to controller digit 0.
262  const uint8_t chipPos = mFlushStage;
263  const uint8_t physicalPos = remapLogicalToPhysical(chipPos);
264  if (isDirtyBit(physicalPos)) {
265  // Update changed digit.
266  mTmiInterface.startCondition();
267  mTmiInterface.sendByte(kDataCmdFixedAddress);
268  mTmiInterface.stopCondition();
269 
270  mTmiInterface.startCondition();
271  mTmiInterface.sendByte(kAddressCmd | chipPos);
272  mTmiInterface.sendByte(mPatterns[physicalPos]);
273  mTmiInterface.stopCondition();
274  clearDirtyBit(physicalPos);
275  }
276  }
277 
278  // An extra dirty bit is used for the brightness so use `T_DIGITS + 1`.
279  ace_common::incrementMod(mFlushStage, (uint8_t) (T_DIGITS + 1));
280  }
281 
282  private:
283  void setDirtyBit(uint8_t bit) {
284  mIsDirty |= (0x1 << bit);
285  }
286 
287  void clearDirtyBit(uint8_t bit) {
288  mIsDirty &= ~(0x1 << bit);
289  }
290 
291  bool isDirtyBit(uint8_t bit) const {
292  return mIsDirty & (0x1 << bit);
293  }
294 
296  uint8_t remapLogicalToPhysical(uint8_t pos) const {
297  return mRemapArray ? mRemapArray[pos] : pos;
298  }
299 
300  private:
301  // Give access to mIsDirty and mFlushStage
302  friend class ::Tm1637ModuleTest_flushIncremental;
303  friend class ::Tm1637ModuleTest_flush;
304 
305  // These come from the TM1637 controller chip datasheet.
306  static uint8_t const kDataCmdWriteDisplay = 0b01000000;
307  static uint8_t const kDataCmdReadKeys = 0b01000010;
308  static uint8_t const kDataCmdAutoAddress = 0b01000000;
309  static uint8_t const kDataCmdFixedAddress = 0b01000100;
310  static uint8_t const kAddressCmd = 0b11000000;
311  static uint8_t const kBrightnessCmd = 0b10000000;
312  static uint8_t const kBrightnessLevelOn = 0b00001000;
313 
314  // Use the bit at position 'T_DIGITS' as the dirty bit for brightness.
315  // A TM1637 can have a maximum of 6 T_DIGITS, so we are safe.
316  static uint8_t const kBrightnessDirtyBit = T_DIGITS;
317 
318  // The ordering of these fields is partially determined to save memory on
319  // 32-bit processors.
320 
321  // TM1637 interface object. Copied by value instead of reference to avoid an
322  // extra level of indirection.
323  const T_TMII mTmiInterface;
324 
325  const uint8_t* const mRemapArray;
326  uint8_t mPatterns[T_DIGITS]; // maps to dirty bits [0, T_DIGITS-1]
327  uint8_t mBrightness; // maps to dirty bit at T_DIGITS
328  uint8_t mIsDirty; // bit array
329  uint8_t mFlushStage; // [0, T_DIGITS], with T_DIGITS for brightness update
330 };
331 
332 } // ace_segment
333 
334 #endif
ace_segment::LedModule
General interface that represents a generic seven-segment LED module with multiple digits.
Definition: LedModule.h:44
ace_segment::Tm1637Module::flushIncremental
void flushIncremental()
Update only a single digit or the brightness.
Definition: Tm1637Module.h:248
ace_segment::Tm1637Module::setDisplayOn
void setDisplayOn(bool on=true)
Turn off the entire display.
Definition: Tm1637Module.h:168
ace_segment::Tm1637Module::setPatternAt
void setPatternAt(uint8_t pos, uint8_t pattern) override
Set the led digit pattern at position pos.
Definition: Tm1637Module.h:146
ace_segment::Tm1637Module::begin
void begin()
Initialize the module.
Definition: Tm1637Module.h:123
ace_segment::Tm1637Module::end
void end()
Signal end of usage.
Definition: Tm1637Module.h:137
ace_segment::Tm1637Module::getPatternAt
uint8_t getPatternAt(uint8_t pos) override
Get the led digit pattern at position pos.
Definition: Tm1637Module.h:151
ace_segment::Tm1637Module::flush
void flush()
Send segment patterns of all digits plus the brightness to the display, if any of the digits or brigh...
Definition: Tm1637Module.h:186
ace_segment::Tm1637Module::setBrightness
void setBrightness(uint8_t brightness) override
Set global brightness of all digits.
Definition: Tm1637Module.h:155
ace_segment::Tm1637Module
An implementation of LedModule using the TM1637 chip.
Definition: Tm1637Module.h:91
ace_segment::Tm1637Module::getNumDigits
uint8_t getNumDigits() const
Return the number of digits supported by this display instance.
Definition: Tm1637Module.h:144
ace_segment::Tm1637Module::Tm1637Module
Tm1637Module(const T_TMII &tmiInterface, const uint8_t *remapArray=nullptr)
Constructor.
Definition: Tm1637Module.h:101