AceSegment  0.12.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 <string.h> // memset()
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  mFlushStage = 0;
108  }
109 
111  void end() {
112  LedModule::end();
113  }
114 
115  //-----------------------------------------------------------------------
116  // Additional brightness control supported by the TM1637 chip.
117  //-----------------------------------------------------------------------
118 
123  void setDisplayOn(bool on = true) {
124  mDisplayOn = on;
125  setBrightness(getBrightness()); // mark the brightness dirty
126  }
127 
128  //-----------------------------------------------------------------------
129  // Methods related to rendering.
130  //-----------------------------------------------------------------------
131 
133  bool isFlushRequired() const {
134  return isAnyDigitDirty() || isBrightnessDirty();
135  }
136 
144  void flush() {
145  // Command1: Update the digits using auto incrementing mode.
146  mTmiInterface.startCondition();
147  mTmiInterface.write(kDataCmdAutoAddress);
148  mTmiInterface.stopCondition();
149 
150  // Command2: Send the LED patterns.
151  mTmiInterface.startCondition();
152  mTmiInterface.write(kAddressCmd);
153  for (uint8_t chipPos = 0; chipPos < T_DIGITS; ++chipPos) {
154  // Remap the logical position used by the controller to the actual
155  // position. For example, if the controller digit 0 appears at physical
156  // digit 2, we need to display the segment pattern given by logical
157  // position 2 when sending the byte to controller digit 0.
158  uint8_t physicalPos = remapLogicalToPhysical(chipPos);
159  uint8_t effectivePattern = mPatterns[physicalPos];
160  mTmiInterface.write(effectivePattern);
161  }
162  mTmiInterface.stopCondition();
163 
164  // Command3: Update the brightness last. This matches the recommendation
165  // given in the Titan Micro TM1637 datasheet. But experimentation shows
166  // that things seems to work even if brightness is sent first, before the
167  // digit patterns.
168  mTmiInterface.startCondition();
169  mTmiInterface.write(kBrightnessCmd
170  | (mDisplayOn ? kBrightnessLevelOn : 0x0)
171  | (getBrightness() & 0xF));
172  mTmiInterface.stopCondition();
173 
176  }
177 
212  if (mFlushStage == T_DIGITS) {
213  // Update brightness.
214  if (isBrightnessDirty()) {
215  mTmiInterface.startCondition();
216  mTmiInterface.write(kBrightnessCmd
217  | (mDisplayOn ? kBrightnessLevelOn : 0x0)
218  | (getBrightness() & 0xF));
219  mTmiInterface.stopCondition();
221  }
222  } else {
223  // Remap the logical position used by the controller to the actual
224  // position. For example, if the controller digit 0 appears at physical
225  // digit 2, we need to display the segment pattern given by logical
226  // position 2 when sending the byte to controller digit 0.
227  const uint8_t chipPos = mFlushStage;
228  const uint8_t physicalPos = remapLogicalToPhysical(chipPos);
229  if (isDigitDirty(physicalPos)) {
230  // Update changed digit.
231  mTmiInterface.startCondition();
232  mTmiInterface.write(kDataCmdFixedAddress);
233  mTmiInterface.stopCondition();
234 
235  mTmiInterface.startCondition();
236  mTmiInterface.write(kAddressCmd | chipPos);
237  mTmiInterface.write(mPatterns[physicalPos]);
238  mTmiInterface.stopCondition();
239  clearDigitDirty(physicalPos);
240  }
241  }
242 
243  // An extra dirty bit is used for the brightness so use `T_DIGITS + 1`.
244  ace_common::incrementMod(mFlushStage, (uint8_t) (T_DIGITS + 1));
245  }
246 
247  //-----------------------------------------------------------------------
248  // Methods related to buttons
249  //-----------------------------------------------------------------------
250 
265  uint8_t readButtons() const {
266  mTmiInterface.startCondition();
267  mTmiInterface.write(kDataCmdReadKeys);
268  uint8_t data = mTmiInterface.read();
269  mTmiInterface.stopCondition();
270  return data;
271  }
272 
273  private:
275  uint8_t remapLogicalToPhysical(uint8_t pos) const {
276  return mRemapArray ? mRemapArray[pos] : pos;
277  }
278 
279  private:
280  // Give access to mIsDirty and mFlushStage
281  friend class ::Tm1637ModuleTest_flushIncremental;
282  friend class ::Tm1637ModuleTest_flush;
283 
284  // These come from the TM1637 controller chip datasheet.
285  static uint8_t const kDataCmdWriteDisplay = 0b01000000;
286  static uint8_t const kDataCmdReadKeys = 0b01000010;
287  static uint8_t const kDataCmdAutoAddress = 0b01000000;
288  static uint8_t const kDataCmdFixedAddress = 0b01000100;
289  static uint8_t const kAddressCmd = 0b11000000;
290  static uint8_t const kBrightnessCmd = 0b10000000;
291  static uint8_t const kBrightnessLevelOn = 0b00001000;
292 
293  // The ordering of these fields is partially determined to save memory on
294  // 32-bit processors.
295 
296  // TM1637 interface object. Copied by value instead of reference to avoid an
297  // extra level of indirection.
298  const T_TMII mTmiInterface;
299 
300  const uint8_t* const mRemapArray;
301  uint8_t mPatterns[T_DIGITS];
302  bool mDisplayOn;
303  uint8_t mFlushStage; // [0, T_DIGITS], with T_DIGITS for brightness update
304 };
305 
306 } // ace_segment
307 
308 #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:211
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:133
ace_segment::Tm1637Module::setDisplayOn
void setDisplayOn(bool on=true)
Turn off the entire display.
Definition: Tm1637Module.h:123
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:111
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::readButtons
uint8_t readButtons() const
Read the 1 byte with key scan with the bits coming out in LSBFIRST order with the following bits: S0 ...
Definition: Tm1637Module.h:265
ace_segment::Tm1637Module::flush
void flush()
Send segment patterns of all digits plus the brightness to the display.
Definition: Tm1637Module.h:144
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