AceSegment  0.11.0
A library for rendering seven segment LED displays using the TM1637, TM1638, 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 
74 template <typename T_TMII, uint8_t T_DIGITS>
75 class Tm1637Module : public LedModule {
76  public:
77 
85  explicit Tm1637Module(
86  const T_TMII& tmiInterface,
87  const uint8_t* remapArray = nullptr
88  ) :
89  LedModule(mPatterns, T_DIGITS),
90  mTmiInterface(tmiInterface),
91  mRemapArray(remapArray)
92  {}
93 
94  //-----------------------------------------------------------------------
95  // Initialization and termination.
96  //-----------------------------------------------------------------------
97 
102  void begin() {
104 
105  memset(mPatterns, 0, T_DIGITS);
106  setDisplayOn(true);
107  setBrightness(0x7);
108  mFlushStage = 0;
109  }
110 
112  void end() {
113  LedModule::end();
114  }
115 
116  //-----------------------------------------------------------------------
117  // Additional brightness control supported by the TM1637 chip.
118  //-----------------------------------------------------------------------
119 
124  void setDisplayOn(bool on = true) {
125  mDisplayOn = on;
126  setBrightness(getBrightness()); // mark the brightness dirty
127  }
128 
129  //-----------------------------------------------------------------------
130  // Methods related to rendering.
131  //-----------------------------------------------------------------------
132 
134  bool isFlushRequired() const {
135  return isAnyDigitDirty() || isBrightnessDirty();
136  }
137 
145  void flush() {
146  // Command1: Update the digits using auto incrementing mode.
147  mTmiInterface.startCondition();
148  mTmiInterface.write(kDataCmdAutoAddress);
149  mTmiInterface.stopCondition();
150 
151  // Command2: Send the LED patterns.
152  mTmiInterface.startCondition();
153  mTmiInterface.write(kAddressCmd);
154  for (uint8_t chipPos = 0; chipPos < T_DIGITS; ++chipPos) {
155  // Remap the logical position used by the controller to the actual
156  // position. For example, if the controller digit 0 appears at physical
157  // digit 2, we need to display the segment pattern given by logical
158  // position 2 when sending the byte to controller digit 0.
159  uint8_t physicalPos = remapLogicalToPhysical(chipPos);
160  uint8_t effectivePattern = mPatterns[physicalPos];
161  mTmiInterface.write(effectivePattern);
162  }
163  mTmiInterface.stopCondition();
164 
165  // Command3: Update the brightness last. This matches the recommendation
166  // given in the Titan Micro TM1637 datasheet. But experimentation shows
167  // that things seems to work even if brightness is sent first, before the
168  // digit patterns.
169  mTmiInterface.startCondition();
170  mTmiInterface.write(kBrightnessCmd
171  | (mDisplayOn ? kBrightnessLevelOn : 0x0)
172  | (getBrightness() & 0xF));
173  mTmiInterface.stopCondition();
174 
177  }
178 
213  if (mFlushStage == T_DIGITS) {
214  // Update brightness.
215  if (isBrightnessDirty()) {
216  mTmiInterface.startCondition();
217  mTmiInterface.write(kBrightnessCmd
218  | (mDisplayOn ? kBrightnessLevelOn : 0x0)
219  | (getBrightness() & 0xF));
220  mTmiInterface.stopCondition();
222  }
223  } else {
224  // Remap the logical position used by the controller to the actual
225  // position. For example, if the controller digit 0 appears at physical
226  // digit 2, we need to display the segment pattern given by logical
227  // position 2 when sending the byte to controller digit 0.
228  const uint8_t chipPos = mFlushStage;
229  const uint8_t physicalPos = remapLogicalToPhysical(chipPos);
230  if (isDigitDirty(physicalPos)) {
231  // Update changed digit.
232  mTmiInterface.startCondition();
233  mTmiInterface.write(kDataCmdFixedAddress);
234  mTmiInterface.stopCondition();
235 
236  mTmiInterface.startCondition();
237  mTmiInterface.write(kAddressCmd | chipPos);
238  mTmiInterface.write(mPatterns[physicalPos]);
239  mTmiInterface.stopCondition();
240  clearDigitDirty(physicalPos);
241  }
242  }
243 
244  // An extra dirty bit is used for the brightness so use `T_DIGITS + 1`.
245  ace_common::incrementMod(mFlushStage, (uint8_t) (T_DIGITS + 1));
246  }
247 
248  private:
250  uint8_t remapLogicalToPhysical(uint8_t pos) const {
251  return mRemapArray ? mRemapArray[pos] : pos;
252  }
253 
254  private:
255  // Give access to mIsDirty and mFlushStage
256  friend class ::Tm1637ModuleTest_flushIncremental;
257  friend class ::Tm1637ModuleTest_flush;
258 
259  // These come from the TM1637 controller chip datasheet.
260  static uint8_t const kDataCmdWriteDisplay = 0b01000000;
261  static uint8_t const kDataCmdReadKeys = 0b01000010;
262  static uint8_t const kDataCmdAutoAddress = 0b01000000;
263  static uint8_t const kDataCmdFixedAddress = 0b01000100;
264  static uint8_t const kAddressCmd = 0b11000000;
265  static uint8_t const kBrightnessCmd = 0b10000000;
266  static uint8_t const kBrightnessLevelOn = 0b00001000;
267 
268  // The ordering of these fields is partially determined to save memory on
269  // 32-bit processors.
270 
271  // TM1637 interface object. Copied by value instead of reference to avoid an
272  // extra level of indirection.
273  const T_TMII mTmiInterface;
274 
275  const uint8_t* const mRemapArray;
276  uint8_t mPatterns[T_DIGITS];
277  bool mDisplayOn;
278  uint8_t mFlushStage; // [0, T_DIGITS], with T_DIGITS for brightness update
279 };
280 
281 } // ace_segment
282 
283 #endif
ace_segment::LedModule::clearDigitsDirty
void clearDigitsDirty()
Clear dirty bits of all digits.
Definition: LedModule.h:126
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:212
ace_segment::LedModule::isAnyDigitDirty
bool isAnyDigitDirty() const
Return true if any digits are dirty.
Definition: LedModule.h:131
ace_segment::Tm1637Module::isFlushRequired
bool isFlushRequired() const
Return true if flushing required.
Definition: Tm1637Module.h:134
ace_segment::Tm1637Module::setDisplayOn
void setDisplayOn(bool on=true)
Turn off the entire display.
Definition: Tm1637Module.h:124
ace_segment::LedModule::clearDigitDirty
void clearDigitDirty(uint8_t pos)
Clear the dirty bit of digit pos.
Definition: LedModule.h:116
ace_segment::Tm1637Module::begin
void begin()
Initialize the module.
Definition: Tm1637Module.h:102
ace_segment::LedModule::begin
void begin()
Subclasses should call this from its own begin().
Definition: LedModule.h:93
ace_segment::LedModule::clearBrightnessDirty
void clearBrightnessDirty()
Clear the dirty bit for brightness.
Definition: LedModule.h:146
ace_segment::LedModule::setBrightness
void setBrightness(uint8_t brightness)
Set global brightness of all digits.
Definition: LedModule.h:81
ace_segment::LedModule::end
void end()
Subclasses should call this from its own end().
Definition: LedModule.h:108
ace_segment::LedModule::getBrightness
uint8_t getBrightness() const
Get the current brightness.
Definition: LedModule.h:87
ace_segment::Tm1637Module::end
void end()
Signal end of usage.
Definition: Tm1637Module.h:112
ace_segment::LedModule::isBrightnessDirty
bool isBrightnessDirty() const
Check if the brightness level is dirty.
Definition: LedModule.h:136
ace_segment::LedModule::isDigitDirty
bool isDigitDirty(uint8_t pos) const
Check the dirty bit of digit pos.
Definition: LedModule.h:121
ace_segment::Tm1637Module::flush
void flush()
Send segment patterns of all digits plus the brightness to the display.
Definition: Tm1637Module.h:145
ace_segment::Tm1637Module
An implementation of LedModule using the TM1637 chip.
Definition: Tm1637Module.h:75
ace_segment::Tm1637Module::Tm1637Module
Tm1637Module(const T_TMII &tmiInterface, const uint8_t *remapArray=nullptr)
Constructor.
Definition: Tm1637Module.h:85