AceSegment  0.12.0
A library for rendering seven segment LED displays using the TM1637, TM1638, MAX7219, HT16K33, or 74HC595 controller chips
ScanningModule.h
1 /*
2 MIT License
3 
4 Copyright (c) 2018 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_SCANNING_MODULE_H
26 #define ACE_SEGMENT_SCANNING_MODULE_H
27 
28 #include <stdint.h>
29 #include <string.h> // memset()
30 #include <AceCommon.h> // incrementMod()
31 #include "../hw/ClockInterface.h" // ClockInterface
32 #include "../LedModule.h"
33 
34 class ScanningModuleTest_isAnyDigitDirty;
35 class ScanningModuleTest_isBrightnessDirty;
36 
37 namespace ace_segment {
38 
77 template <
78  typename T_LM,
79  uint8_t T_DIGITS,
80  uint8_t T_SUBFIELDS = 1,
81  typename T_CI = ClockInterface>
82 class ScanningModule : public LedModule {
83 
84  public:
95  explicit ScanningModule(
96  const T_LM& ledMatrix,
97  uint8_t framesPerSecond
98  ):
99  LedModule(mPatterns, T_DIGITS),
100  mLedMatrix(ledMatrix),
101  mFramesPerSecond(framesPerSecond)
102  {}
103 
110  void begin() {
112  memset(mPatterns, 0, T_DIGITS);
113 
114  // Set up durations for the renderFieldWhenReady() polling function.
115  mMicrosPerField = (uint32_t) 1000000UL / getFieldsPerSecond();
116  mLastRenderFieldMicros = T_CI::micros();
117 
118  // Initialize variables needed for multiplexing.
119  mCurrentDigit = 0;
120  mPrevDigit = T_DIGITS - 1;
121  mCurrentSubField = 0;
122  mPattern = 0;
123 
124  // Set initial patterns and global brightness.
125  mIsDigitBrightnessDirty = false;
126  mLedMatrix.clear();
127  if (T_SUBFIELDS > 1) {
128  setBrightness(T_SUBFIELDS / 2); // half brightness
129  }
130  }
131 
132 
134  void end() {
135  LedModule::end();
136  }
137 
138  //-----------------------------------------------------------------------
139  // Additional brightness control. ScanningModule allows brightness to be
140  // defined on a per-digit basis.
141  //-----------------------------------------------------------------------
142 
166  void setBrightnessAt(uint8_t pos, uint8_t brightness) {
167  if (pos >= T_DIGITS) return;
168  mBrightnesses[pos] = (brightness >= T_SUBFIELDS)
169  ? T_SUBFIELDS : brightness;
170  mIsDigitBrightnessDirty = true;
171  }
172 
173  //-----------------------------------------------------------------------
174  // Methods related to rendering.
175  //-----------------------------------------------------------------------
176 
178  uint16_t getFramesPerSecond() const { return mFramesPerSecond; }
179 
181  uint16_t getFieldsPerSecond() const {
182  return mFramesPerSecond * getFieldsPerFrame();
183  }
184 
186  uint16_t getFieldsPerFrame() const { return T_DIGITS * T_SUBFIELDS; }
187 
192  uint16_t getMicrosPerField() const { return mMicrosPerField; }
193 
203  uint16_t now = T_CI::micros();
204  uint16_t elapsedMicros = now - mLastRenderFieldMicros;
205  if (elapsedMicros >= mMicrosPerField) {
206  renderFieldNow();
207  mLastRenderFieldMicros = now;
208  return true;
209  } else {
210  return false;
211  }
212  }
213 
223  void renderFieldNow() {
224  updateBrightness();
225  if (T_SUBFIELDS > 1) {
226  displayCurrentFieldModulated();
227  } else {
228  displayCurrentFieldPlain();
229  }
230  }
231 
232  private:
233  friend class ::ScanningModuleTest_isAnyDigitDirty;
234  friend class ::ScanningModuleTest_isBrightnessDirty;
235 
236  // disable copy-constructor and assignment operator
237  ScanningModule(const ScanningModule&) = delete;
238  ScanningModule& operator=(const ScanningModule&) = delete;
239 
241  void displayCurrentFieldPlain() {
242  const uint8_t pattern = mPatterns[mCurrentDigit];
243  mLedMatrix.draw(mCurrentDigit, pattern);
244  mPrevDigit = mCurrentDigit;
245  ace_common::incrementMod(mCurrentDigit, T_DIGITS);
246  }
247 
249  void displayCurrentFieldModulated() {
250  // Calculate the maximum subfield duration for current digit.
251  const uint8_t brightness = mBrightnesses[mCurrentDigit];
252 
253  // Implement pulse width modulation PWM, using the following boundaries:
254  //
255  // * If brightness == 0, then turn the digit OFF 100% of the time.
256  // * If brightness >= T_SUBFIELDS, turn the digit ON 100% of the time.
257  //
258  // The mCurrentSubField is incremented modulo T_SUBFIELDS, so will always
259  // be in the range of [0, T_SUBFIELDS-1]. The brightness will always be <=
260  // T_SUBFIELDS, with the value of T_SUBFIELDS being 100% bright. So if we
261  // turn on the LED when (mCurrentSubField < brightness), we get the
262  // desired outcome.
263  const uint8_t pattern = (mCurrentSubField < brightness)
264  ? mPatterns[mCurrentDigit]
265  : 0;
266 
267  if (pattern != mPattern || mCurrentDigit != mPrevDigit) {
268  mLedMatrix.draw(mCurrentDigit, pattern);
269  mPattern = pattern;
270  }
271 
272  mCurrentSubField++;
273  mPrevDigit = mCurrentDigit;
274  if (mCurrentSubField >= T_SUBFIELDS) {
275  ace_common::incrementMod(mCurrentDigit, T_DIGITS);
276  mCurrentSubField = 0;
277  }
278  }
279 
284  void updateBrightness() {
285  if (isBrightnessDirty()) {
286  for (uint8_t i = 0; i < T_DIGITS; i++) {
288  }
289 
290  // Clear the global brightness dirty flag.
292  }
293  }
294 
295  private:
296  // The ordering of the fields below partially motivated to save memory on
297  // 32-bit processors.
298 
300  const T_LM& mLedMatrix;
301 
303  uint8_t mPatterns[T_DIGITS];
304 
306  uint8_t mBrightnesses[T_DIGITS];
307 
308  //-----------------------------------------------------------------------
309  // Variables needed by renderFieldWhenReady() to render frames and fields at
310  // a certain rate per second.
311  //-----------------------------------------------------------------------
312 
314  uint16_t mMicrosPerField;
315 
317  uint16_t mLastRenderFieldMicros;
318 
320  uint8_t const mFramesPerSecond;
321 
322  //-----------------------------------------------------------------------
323  // Variables needed to keep track of the multiplexing of the digits,
324  // and PWM of a single digit.
325  //-----------------------------------------------------------------------
326 
328  bool mIsDigitBrightnessDirty;
329 
335  uint8_t mCurrentDigit;
336 
344  uint8_t mPrevDigit;
345 
350  uint8_t mCurrentSubField;
351 
357  uint8_t mPattern;
358 };
359 
360 }
361 
362 #endif
ace_segment::ScanningModule::renderFieldNow
void renderFieldNow()
Render the current field immediately.
Definition: ScanningModule.h:223
ace_segment::ScanningModule::end
void end()
A no-op end() function for consistency with other classes.
Definition: ScanningModule.h:134
ace_segment::LedModule
General interface that represents a generic seven-segment LED module with multiple digits.
Definition: LedModule.h:44
ace_segment::ScanningModule::setBrightnessAt
void setBrightnessAt(uint8_t pos, uint8_t brightness)
Set the brightness for a given pos, leaving pattern unchanged.
Definition: ScanningModule.h:166
ace_segment::LedModule::begin
void begin()
Subclasses should call this from its own begin().
Definition: LedModule.h:93
ace_segment::ScanningModule
An implementation of LedModule for display modules which do not have hardware controller chips,...
Definition: ScanningModule.h:82
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::ScanningModule::begin
void begin()
Configure the driver with the parameters given by in the constructor.
Definition: ScanningModule.h:110
ace_segment::LedModule::end
void end()
Subclasses should call this from its own end().
Definition: LedModule.h:108
ace_segment::ScanningModule::getFieldsPerSecond
uint16_t getFieldsPerSecond() const
Return the fields per second.
Definition: ScanningModule.h:181
ace_segment::LedModule::getBrightness
uint8_t getBrightness() const
Get the current brightness.
Definition: LedModule.h:87
ace_segment::ScanningModule::ScanningModule
ScanningModule(const T_LM &ledMatrix, uint8_t framesPerSecond)
Constructor.
Definition: ScanningModule.h:95
ace_segment::ScanningModule::getFieldsPerFrame
uint16_t getFieldsPerFrame() const
Total fields per frame across all digits.
Definition: ScanningModule.h:186
ace_segment::ScanningModule::getFramesPerSecond
uint16_t getFramesPerSecond() const
Return the requested frames per second.
Definition: ScanningModule.h:178
ace_segment::LedModule::isBrightnessDirty
bool isBrightnessDirty() const
Check if the brightness level is dirty.
Definition: LedModule.h:136
ace_segment::ScanningModule::renderFieldWhenReady
bool renderFieldWhenReady()
Display one field of a frame when the time is right.
Definition: ScanningModule.h:202
ace_segment::ScanningModule::getMicrosPerField
uint16_t getMicrosPerField() const
Return micros per field.
Definition: ScanningModule.h:192