AceSegment  0.11.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 <AceCommon.h> // incrementMod()
30 #include "../hw/ClockInterface.h" // ClockInterface
31 #include "../LedModule.h"
32 
33 class ScanningModuleTest_isAnyDigitDirty;
34 class ScanningModuleTest_isBrightnessDirty;
35 
36 namespace ace_segment {
37 
76 template <
77  typename T_LM,
78  uint8_t T_DIGITS,
79  uint8_t T_SUBFIELDS = 1,
80  typename T_CI = ClockInterface>
81 class ScanningModule : public LedModule {
82 
83  public:
94  explicit ScanningModule(
95  const T_LM& ledMatrix,
96  uint8_t framesPerSecond
97  ):
98  LedModule(mPatterns, T_DIGITS),
99  mLedMatrix(ledMatrix),
100  mFramesPerSecond(framesPerSecond)
101  {}
102 
109  void begin() {
111 
112  // Set up durations for the renderFieldWhenReady() polling function.
113  mMicrosPerField = (uint32_t) 1000000UL / getFieldsPerSecond();
114  mLastRenderFieldMicros = T_CI::micros();
115 
116  // Initialize variables needed for multiplexing.
117  mCurrentDigit = 0;
118  mPrevDigit = T_DIGITS - 1;
119  mCurrentSubField = 0;
120  mPattern = 0;
121 
122  // Set initial patterns and global brightness.
123  mIsDigitBrightnessDirty = false;
124  mLedMatrix.clear();
125  if (T_SUBFIELDS > 1) {
126  setBrightness(T_SUBFIELDS / 2); // half brightness
127  }
128  }
129 
130 
132  void end() {
133  LedModule::end();
134  }
135 
136  //-----------------------------------------------------------------------
137  // Additional brightness control. ScanningModule allows brightness to be
138  // defined on a per-digit basis.
139  //-----------------------------------------------------------------------
140 
164  void setBrightnessAt(uint8_t pos, uint8_t brightness) {
165  if (pos >= T_DIGITS) return;
166  mBrightnesses[pos] = (brightness >= T_SUBFIELDS)
167  ? T_SUBFIELDS : brightness;
168  mIsDigitBrightnessDirty = true;
169  }
170 
171  //-----------------------------------------------------------------------
172  // Methods related to rendering.
173  //-----------------------------------------------------------------------
174 
176  uint16_t getFramesPerSecond() const { return mFramesPerSecond; }
177 
179  uint16_t getFieldsPerSecond() const {
180  return mFramesPerSecond * getFieldsPerFrame();
181  }
182 
184  uint16_t getFieldsPerFrame() const { return T_DIGITS * T_SUBFIELDS; }
185 
190  uint16_t getMicrosPerField() const { return mMicrosPerField; }
191 
201  uint16_t now = T_CI::micros();
202  uint16_t elapsedMicros = now - mLastRenderFieldMicros;
203  if (elapsedMicros >= mMicrosPerField) {
204  renderFieldNow();
205  mLastRenderFieldMicros = now;
206  return true;
207  } else {
208  return false;
209  }
210  }
211 
221  void renderFieldNow() {
222  updateBrightness();
223  if (T_SUBFIELDS > 1) {
224  displayCurrentFieldModulated();
225  } else {
226  displayCurrentFieldPlain();
227  }
228  }
229 
230  private:
231  friend class ::ScanningModuleTest_isAnyDigitDirty;
232  friend class ::ScanningModuleTest_isBrightnessDirty;
233 
234  // disable copy-constructor and assignment operator
235  ScanningModule(const ScanningModule&) = delete;
236  ScanningModule& operator=(const ScanningModule&) = delete;
237 
239  void displayCurrentFieldPlain() {
240  const uint8_t pattern = mPatterns[mCurrentDigit];
241  mLedMatrix.draw(mCurrentDigit, pattern);
242  mPrevDigit = mCurrentDigit;
243  ace_common::incrementMod(mCurrentDigit, T_DIGITS);
244  }
245 
247  void displayCurrentFieldModulated() {
248  // Calculate the maximum subfield duration for current digit.
249  const uint8_t brightness = mBrightnesses[mCurrentDigit];
250 
251  // Implement pulse width modulation PWM, using the following boundaries:
252  //
253  // * If brightness == 0, then turn the digit OFF 100% of the time.
254  // * If brightness >= T_SUBFIELDS, turn the digit ON 100% of the time.
255  //
256  // The mCurrentSubField is incremented modulo T_SUBFIELDS, so will always
257  // be in the range of [0, T_SUBFIELDS-1]. The brightness will always be <=
258  // T_SUBFIELDS, with the value of T_SUBFIELDS being 100% bright. So if we
259  // turn on the LED when (mCurrentSubField < brightness), we get the
260  // desired outcome.
261  const uint8_t pattern = (mCurrentSubField < brightness)
262  ? mPatterns[mCurrentDigit]
263  : 0;
264 
265  if (pattern != mPattern || mCurrentDigit != mPrevDigit) {
266  mLedMatrix.draw(mCurrentDigit, pattern);
267  mPattern = pattern;
268  }
269 
270  mCurrentSubField++;
271  mPrevDigit = mCurrentDigit;
272  if (mCurrentSubField >= T_SUBFIELDS) {
273  ace_common::incrementMod(mCurrentDigit, T_DIGITS);
274  mCurrentSubField = 0;
275  }
276  }
277 
282  void updateBrightness() {
283  if (isBrightnessDirty()) {
284  for (uint8_t i = 0; i < T_DIGITS; i++) {
286  }
287 
288  // Clear the global brightness dirty flag.
290  }
291  }
292 
293  private:
294  // The ordering of the fields below partially motivated to save memory on
295  // 32-bit processors.
296 
298  const T_LM& mLedMatrix;
299 
301  uint8_t mPatterns[T_DIGITS];
302 
304  uint8_t mBrightnesses[T_DIGITS];
305 
306  //-----------------------------------------------------------------------
307  // Variables needed by renderFieldWhenReady() to render frames and fields at
308  // a certain rate per second.
309  //-----------------------------------------------------------------------
310 
312  uint16_t mMicrosPerField;
313 
315  uint16_t mLastRenderFieldMicros;
316 
318  uint8_t const mFramesPerSecond;
319 
320  //-----------------------------------------------------------------------
321  // Variables needed to keep track of the multiplexing of the digits,
322  // and PWM of a single digit.
323  //-----------------------------------------------------------------------
324 
326  bool mIsDigitBrightnessDirty;
327 
333  uint8_t mCurrentDigit;
334 
342  uint8_t mPrevDigit;
343 
348  uint8_t mCurrentSubField;
349 
355  uint8_t mPattern;
356 };
357 
358 }
359 
360 #endif
ace_segment::ScanningModule::renderFieldNow
void renderFieldNow()
Render the current field immediately.
Definition: ScanningModule.h:221
ace_segment::ScanningModule::end
void end()
A no-op end() function for consistency with other classes.
Definition: ScanningModule.h:132
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:164
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:81
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:109
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:179
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:94
ace_segment::ScanningModule::getFieldsPerFrame
uint16_t getFieldsPerFrame() const
Total fields per frame across all digits.
Definition: ScanningModule.h:184
ace_segment::ScanningModule::getFramesPerSecond
uint16_t getFramesPerSecond() const
Return the requested frames per second.
Definition: ScanningModule.h:176
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:200
ace_segment::ScanningModule::getMicrosPerField
uint16_t getMicrosPerField() const
Return micros per field.
Definition: ScanningModule.h:190