AceSegment  0.4.0
An adjustable, configurable, and extensible framework for rendering seven segment LED displays.
ScanningDisplay.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_DISPLAY_H
26 #define ACE_SEGMENT_SCANNING_DISPLAY_H
27 
28 #include <stdint.h>
29 #include <Arduino.h> // pgm_read_byte()
30 #include <AceCommon.h> // incrementMod()
31 #include "LedDisplay.h"
32 
33 class ScanningDisplayTest_displayCurrentField;
34 
35 namespace ace_segment {
36 
74 template <typename HW, typename LM, uint8_t DIGITS, uint8_t SUBFIELDS = 1>
75 class ScanningDisplay : public LedDisplay {
76 
77  public:
89  explicit ScanningDisplay(
90  const HW& hardware,
91  const LM& ledMatrix,
92  uint8_t framesPerSecond
93  ):
94  LedDisplay(DIGITS),
95  mHardware(hardware),
96  mLedMatrix(ledMatrix),
97  mFramesPerSecond(framesPerSecond)
98  {}
99 
106  void begin() {
107  // Set up durations for the renderFieldWhenReady() polling function.
108  mMicrosPerField = (uint32_t) 1000000UL / getFieldsPerSecond();
109  mLastRenderFieldMicros = mHardware.micros();
110 
111  // Initialize variables needed for multiplexing.
112  mCurrentDigit = 0;
113  mPrevDigit = DIGITS - 1;
114  mCurrentSubField = 0;
115  mPattern = 0;
116 
117  // Set initial patterns and global brightness.
118  mLedMatrix.clear();
119  if (SUBFIELDS > 1) {
120  setGlobalBrightness(SUBFIELDS / 2); // half brightness
121  }
122 
123  // Sleep mode support.
124  mPreparedToSleep = false;
125  }
126 
127 
129  void end() {}
130 
132  uint8_t getNumDigits() const { return DIGITS; }
133 
135  uint16_t getFramesPerSecond() const { return mFramesPerSecond; }
136 
138  uint16_t getFieldsPerSecond() const {
139  return mFramesPerSecond * getFieldsPerFrame();
140  }
141 
143  uint16_t getFieldsPerFrame() const { return DIGITS * SUBFIELDS; }
144 
145  void writePatternAt(uint8_t pos, uint8_t pattern) override {
146  if (pos >= DIGITS) return;
147  mPatterns[pos] = pattern;
148  }
149 
150  void writePatternsAt(uint8_t pos, const uint8_t patterns[],
151  uint8_t len) override {
152  for (uint8_t i = 0; i < len; i++) {
153  if (pos >= DIGITS) break;
154  mPatterns[pos++] = patterns[i];
155  }
156  }
157 
158  void writePatternsAt_P(uint8_t pos, const uint8_t patterns[],
159  uint8_t len) override {
160  for (uint8_t i = 0; i < len; i++) {
161  if (pos >= DIGITS) break;
162  mPatterns[pos++] = pgm_read_byte(patterns + i);
163  }
164  }
165 
186  void setBrightnessAt(uint8_t pos, uint8_t brightness) override {
187  if (SUBFIELDS > 1) {
188  if (pos >= DIGITS) return;
189  mBrightnesses[pos] = (brightness >= SUBFIELDS) ? SUBFIELDS : brightness;
190  }
191  }
192 
193  void writeDecimalPointAt(uint8_t pos, bool state = true) override {
194  if (pos >= DIGITS) return;
195  uint8_t pattern = mPatterns[pos];
196  if (state) {
197  pattern |= 0x80;
198  } else {
199  pattern &= ~0x80;
200  }
201  mPatterns[pos] = pattern;
202  }
203 
210  void setGlobalBrightness(uint8_t brightness) override {
211  if (SUBFIELDS > 1) {
212  for (uint8_t i = 0; i < DIGITS; i++) {
213  setBrightnessAt(i, brightness);
214  }
215  }
216  }
217 
218  void clear() override {
219  for (uint8_t i = 0; i < DIGITS; i++) {
220  mPatterns[i] = 0;
221  }
222  }
223 
233  uint16_t now = mHardware.micros();
234  uint16_t elapsedMicros = now - mLastRenderFieldMicros;
235  if (elapsedMicros >= mMicrosPerField) {
236  renderFieldNow();
237  mLastRenderFieldMicros = now;
238  return true;
239  } else {
240  return false;
241  }
242  }
243 
253  void renderFieldNow() {
254  if (mPreparedToSleep) return;
255 
256  if (SUBFIELDS > 1) {
257  displayCurrentFieldModulated();
258  } else {
259  displayCurrentFieldPlain();
260  }
261  }
262 
267  void prepareToSleep() {
268  mPreparedToSleep = true;
269  mLedMatrix.clear();
270  }
271 
273  void wakeFromSleep() { mPreparedToSleep = false; }
274 
275  private:
276  friend class ::ScanningDisplayTest_displayCurrentField;
277 
278  // disable copy-constructor and assignment operator
279  ScanningDisplay(const ScanningDisplay&) = delete;
280  ScanningDisplay& operator=(const ScanningDisplay&) = delete;
281 
283  void displayCurrentFieldPlain() {
284  const uint8_t pattern = mPatterns[mCurrentDigit];
285  mLedMatrix.draw(mCurrentDigit, pattern);
286  mPrevDigit = mCurrentDigit;
287  ace_common::incrementMod(mCurrentDigit, DIGITS);
288  }
289 
291  void displayCurrentFieldModulated() {
292  // Calculate the maximum subfield duration for current digit.
293  const uint8_t brightness = mBrightnesses[mCurrentDigit];
294 
295  // Implement pulse width modulation PWM, using the following boundaries:
296  //
297  // * If brightness == 0, then turn the digit OFF 100% of the time.
298  // * If brightness >= SUBFIELDS, turn the digit ON 100% of the time.
299  //
300  // The mCurrentSubField is incremented modulo SUBFIELDS, so will always be
301  // in the range of [0, SUBFIELDS-1]. The brightness will always be <=
302  // SUBFIELDS, with the value of SUBFIELDS being 100% bright. So if we turn
303  // on the LED when (mCurrentSubField < brightness), we get the desired
304  // outcome.
305  const uint8_t pattern = (mCurrentSubField < brightness)
306  ? mPatterns[mCurrentDigit]
307  : 0;
308 
309  if (pattern != mPattern || mCurrentDigit != mPrevDigit) {
310  mLedMatrix.draw(mCurrentDigit, pattern);
311  mPattern = pattern;
312  }
313 
314  mCurrentSubField++;
315  mPrevDigit = mCurrentDigit;
316  if (mCurrentSubField >= SUBFIELDS) {
317  ace_common::incrementMod(mCurrentDigit, DIGITS);
318  mCurrentSubField = 0;
319  }
320  }
321 
322  private:
323  // Ordered to save space on 32-bit processors.
324 
326  const HW& mHardware;
327 
329  const LM& mLedMatrix;
330 
332  uint8_t mPatterns[DIGITS];
333 
335  uint8_t mBrightnesses[DIGITS];
336 
337  //-----------------------------------------------------------------------
338  // Variables needed by renderFieldWhenReady() to render frames and fields at
339  // a certain rate per second.
340  //-----------------------------------------------------------------------
341 
343  uint16_t mMicrosPerField;
344 
346  uint16_t mLastRenderFieldMicros;
347 
349  uint8_t const mFramesPerSecond;
350 
351  //-----------------------------------------------------------------------
352  // Variables needed to keep track of the multiplexing of the digits,
353  // and PWM of a single digit.
354  //-----------------------------------------------------------------------
355 
361  uint8_t mCurrentDigit;
362 
370  uint8_t mPrevDigit;
371 
376  uint8_t mCurrentSubField;
377 
383  uint8_t mPattern;
384 
385  //-----------------------------------------------------------------------
386  // Variables to support low power sleeping on the MCU.
387  //-----------------------------------------------------------------------
388 
390  volatile bool mPreparedToSleep;
391 };
392 
393 }
394 
395 #endif
ace_segment::ScanningDisplay::clear
void clear() override
Clear all digits to blank pattern.
Definition: ScanningDisplay.h:218
ace_segment::ScanningDisplay::setBrightnessAt
void setBrightnessAt(uint8_t pos, uint8_t brightness) override
Definition: ScanningDisplay.h:186
ace_segment::ScanningDisplay::renderFieldWhenReady
bool renderFieldWhenReady()
Display one field of a frame when the time is right.
Definition: ScanningDisplay.h:232
ace_segment::LedDisplay
General interface for writing LED segment patterns to the LED display module.
Definition: LedDisplay.h:42
ace_segment::ScanningDisplay::writePatternsAt
void writePatternsAt(uint8_t pos, const uint8_t patterns[], uint8_t len) override
Write the array of patterns of length len, starting at pos.
Definition: ScanningDisplay.h:150
ace_segment::ScanningDisplay::ScanningDisplay
ScanningDisplay(const HW &hardware, const LM &ledMatrix, uint8_t framesPerSecond)
Constructor.
Definition: ScanningDisplay.h:89
ace_segment::ScanningDisplay::wakeFromSleep
void wakeFromSleep()
Wake up from sleep.
Definition: ScanningDisplay.h:273
ace_segment::ScanningDisplay::begin
void begin()
Configure the driver with the parameters given by in the constructor.
Definition: ScanningDisplay.h:106
ace_segment::ScanningDisplay::renderFieldNow
void renderFieldNow()
Render the current field immediately.
Definition: ScanningDisplay.h:253
ace_segment::ScanningDisplay::setGlobalBrightness
void setGlobalBrightness(uint8_t brightness) override
Definition: ScanningDisplay.h:210
ace_segment::ScanningDisplay::getFieldsPerSecond
uint16_t getFieldsPerSecond() const
Return the fields per second.
Definition: ScanningDisplay.h:138
ace_segment::ScanningDisplay::prepareToSleep
void prepareToSleep()
Prepare to go to sleep by clearing the frame, and setting a flag so that it doesn't turn itself back ...
Definition: ScanningDisplay.h:267
ace_segment::ScanningDisplay::getNumDigits
uint8_t getNumDigits() const
Get the number of digits.
Definition: ScanningDisplay.h:132
ace_segment::ScanningDisplay
An implementation of LedDisplay for display modules which do not have hardware controller chips,...
Definition: ScanningDisplay.h:75
ace_segment::ScanningDisplay::writePatternsAt_P
void writePatternsAt_P(uint8_t pos, const uint8_t patterns[], uint8_t len) override
Write the array of patterns of length len, which are stored in flash memory through PROGMEM,...
Definition: ScanningDisplay.h:158
ace_segment::ScanningDisplay::writeDecimalPointAt
void writeDecimalPointAt(uint8_t pos, bool state=true) override
Write the decimal point for the pos.
Definition: ScanningDisplay.h:193
ace_segment::ScanningDisplay::writePatternAt
void writePatternAt(uint8_t pos, uint8_t pattern) override
Write the pattern for a given pos.
Definition: ScanningDisplay.h:145
ace_segment::ScanningDisplay::end
void end()
A no-op end() function for consistency with other classes.
Definition: ScanningDisplay.h:129
ace_segment::ScanningDisplay::getFieldsPerFrame
uint16_t getFieldsPerFrame() const
Total fields per frame across all digits.
Definition: ScanningDisplay.h:143
ace_segment::ScanningDisplay::getFramesPerSecond
uint16_t getFramesPerSecond() const
Return the requested frames per second.
Definition: ScanningDisplay.h:135