AceSegment  0.2.0
An adjustable, configurable, and extensible framework for rendering seven segment LED displays.
Renderer.cpp
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 #include <Arduino.h>
26 #include "StyledDigit.h"
27 #include "Hardware.h"
28 #include "Renderer.h"
29 #include "Util.h"
30 
31 // Set to 1 to use the reciprocal of pulse frames to avoid a long division
32 // calculation every frame.
33 #define ACE_SEGMENT_USE_INVERSE_PULSE_FRAMES 1
34 
35 namespace ace_segment {
36 
38  uint16_t nowMicros = mHardware->micros();
39 
40  // Extract driver specific info.
41  mIsPulseEnabled = mDriver->isBrightnessSupported();
42  mFieldsPerFrame = mDriver->getFieldsPerFrame();
43 
44  // Counters for frames and fields.
45  mCurrentField = 0;
46 
47  // Set up durations for polling.
48  mMicrosPerField = 1000000UL / getFieldsPerSecond();
49  mLastRenderFieldMicros = nowMicros;
50 
51  // Reset statistics
52  mStats.reset();
53 
54  // Set up for blinking slow.
55  mFramesPerBlinkSlow = (uint32_t) mFramesPerSecond
56  * mBlinkSlowDurationMillis / 1000;
57  mCurrentBlinkSlowFrame = 0;
58 
59  // Set up for blinking fast.
60  mFramesPerBlinkFast = (uint32_t) mFramesPerSecond
61  * mBlinkFastDurationMillis / 1000;
62  mCurrentBlinkFastFrame = 0;
63 
64  // Set up for pulsing slow.
65  mFramesPerPulseSlow = (uint32_t) mFramesPerSecond
66  * mPulseSlowDurationMillis / 1000;
67  mFramesPerPulseSlowInverse = (uint32_t) 65536 * 1000
68  / mFramesPerSecond / mPulseSlowDurationMillis;
69  mCurrentPulseSlowFrame = 0;
70 
71  // Set up for pulsing fast.
72  mFramesPerPulseFast = (uint32_t) mFramesPerSecond
73  * mPulseFastDurationMillis / 1000;
74  mFramesPerPulseFastInverse = (uint32_t) 65536 * 1000
75  / mFramesPerSecond / mPulseFastDurationMillis;
76  mCurrentPulseFastFrame = 0;
77 }
78 
79 void Renderer::writePatternAt(uint8_t digit, uint8_t pattern, uint8_t style) {
80  if (digit >= mNumDigits) return;
81  StyledDigit& styledDigit = mStyledDigits[digit];
82  styledDigit.pattern = pattern;
83  styledDigit.style = style;
84 }
85 
86 void Renderer::writePatternAt(uint8_t digit, uint8_t pattern) {
87  if (digit >= mNumDigits) return;
88  StyledDigit& styledDigit = mStyledDigits[digit];
89  styledDigit.pattern = pattern;
90 }
91 
92 void Renderer::writeStyleAt(uint8_t digit, uint8_t style) {
93  if (digit >= mNumDigits) return;
94  StyledDigit& styledDigit = mStyledDigits[digit];
95  styledDigit.style = style;
96 }
97 
98 void Renderer::writeDecimalPointAt(uint8_t digit, bool state) {
99  if (digit >= mNumDigits) return;
100  StyledDigit& styledDigit = mStyledDigits[digit];
101  if (state) {
102  styledDigit.setDecimalPoint();
103  } else {
104  styledDigit.clearDecimalPoint();
105  }
106 }
107 
109  uint16_t now = mHardware->micros();
110  uint16_t elapsedMicros = now - mLastRenderFieldMicros;
111  if (elapsedMicros >= mMicrosPerField) {
112  renderField();
113  mLastRenderFieldMicros = now;
114  }
115 }
116 
118  uint16_t now = mHardware->micros();
119  if (mCurrentField == 0) {
120  updateFrame();
121  }
122  mDriver->displayCurrentField();
123  Util::incrementMod(mCurrentField, mFieldsPerFrame);
124 
125  uint16_t duration = mHardware->micros() - now;
126  mStats.update(duration);
127 }
128 
132  if (mStatsResetInterval > 0 &&
133  mStats.getCount() >= mStatsResetInterval) {
134  mStats.reset();
135  }
136 }
137 
139  noInterrupts();
140  TimingStats stats = mStats;
141  interrupts();
142  return stats;
143 }
144 
146  for (uint8_t digit = 0; digit < mNumDigits; digit++) {
147  StyledDigit& styledDigit = mStyledDigits[digit];
148  StyledDigit::StyleType style = styledDigit.style;
149  uint8_t brightness = calcBrightness(style, mBrightness,
150  mBlinkSlowState, mBlinkFastState, mIsPulseEnabled,
151  mPulseSlowFraction, mPulseFastFraction);
152  mDriver->setPattern(digit, styledDigit.pattern, brightness);
153  }
154 }
155 
156 uint8_t Renderer::calcBrightness(uint8_t style, uint8_t brightness,
157  uint8_t blinkSlowState, uint8_t blinkFastState, bool isPulseEnabled,
158  uint8_t pulseSlowFraction, uint8_t pulseFastFraction) {
159 
160  switch (style) {
161  case StyledDigit::kStyleNormal:
162  return brightness;
163  case StyledDigit::kStyleBlinkSlow:
164  return (blinkSlowState == kBlinkStateOff) ? 0 : brightness;
165  case StyledDigit::kStyleBlinkFast:
166  return (blinkFastState == kBlinkStateOff) ? 0 : brightness;
167  case StyledDigit::kStylePulseSlow:
168  if (isPulseEnabled) {
169  return ((uint16_t) pulseSlowFraction * brightness) / 256;
170  } else {
171  return brightness;
172  }
173  case StyledDigit::kStylePulseFast:
174  if (isPulseEnabled) {
175  return ((uint16_t) pulseFastFraction * brightness) / 256;
176  } else {
177  return brightness;
178  }
179  default:
180  return brightness;
181  }
182 }
183 
185  calcBlinkStateForFrame(mFramesPerBlinkSlow, mCurrentBlinkSlowFrame,
186  mBlinkSlowState);
187  calcBlinkStateForFrame(mFramesPerBlinkFast, mCurrentBlinkFastFrame,
188  mBlinkFastState);
189 
190  if (mIsPulseEnabled) {
191 #if ACE_SEGMENT_USE_INVERSE_PULSE_FRAMES == 1
192  calcPulseFractionForFrameUsingInverse(mFramesPerPulseSlowInverse,
193  mFramesPerPulseSlow, mCurrentPulseSlowFrame, mPulseSlowFraction);
194  calcPulseFractionForFrameUsingInverse(mFramesPerPulseFastInverse,
195  mFramesPerPulseFast, mCurrentPulseFastFrame, mPulseFastFraction);
196 #else
197  calcPulseFractionForFrame(mFramesPerPulseSlow, mCurrentPulseSlowFrame,
198  mPulseSlowFraction);
199  calcPulseFractionForFrame(mFramesPerPulseFast, mCurrentPulseFastFrame,
200  mPulseFastFraction);
201 #endif
202  }
203 }
204 
205 void Renderer::calcBlinkStateForFrame(uint16_t framesPerBlink,
206  uint16_t& currentFrame, uint8_t& blinkState) {
207  uint16_t middleOfBlink = framesPerBlink / 2;
208  if (currentFrame < middleOfBlink) {
209  blinkState = kBlinkStateOn;
210  } else {
211  blinkState = kBlinkStateOff;
212  }
213  Util::incrementMod(currentFrame, framesPerBlink);
214 }
215 
216 void Renderer::calcPulseFractionForFrame(uint16_t framesPerPulse,
217  uint16_t& currentFrame, uint8_t& pulseFraction) {
218  uint16_t middleOfPulse = framesPerPulse / 2;
219  uint16_t fraction;
220  if (currentFrame < middleOfPulse) {
221  // TODO: rewrite to avoid expensive division operation
222  fraction = 256 * (uint32_t) currentFrame / middleOfPulse;
223  } else if (currentFrame < framesPerPulse) {
224  uint16_t reverse = (framesPerPulse - currentFrame - 1);
225  // TODO: rewrite to avoid expensive division operation
226  fraction = 256 * (uint32_t) reverse / middleOfPulse;
227  } else {
228  fraction = 0;
229  }
230  if (fraction > 255) fraction = 255;
231  pulseFraction = fraction;
232  Util::incrementMod(currentFrame, framesPerPulse);
233 }
234 
236  uint16_t framesPerPulseInverse, uint16_t framesPerPulse,
237  uint16_t& currentFrame, uint8_t& pulseFraction) {
238  uint16_t middleOfPulse = framesPerPulse / 2;
239  uint16_t fraction;
240  if (currentFrame < middleOfPulse) {
241  fraction = (uint32_t) framesPerPulseInverse * currentFrame / (256/2);
242  } else if (currentFrame < framesPerPulse) {
243  uint16_t reverse = (framesPerPulse - currentFrame - 1);
244  fraction = (uint32_t) framesPerPulseInverse * reverse / (256/2);
245  } else {
246  fraction = 0;
247  }
248  if (fraction > 255) fraction = 255;
249  pulseFraction = fraction;
250  Util::incrementMod(currentFrame, framesPerPulse);
251 }
252 
253 }
void renderFieldWhenReady()
Display one field of a frame when the time is right.
Definition: Renderer.cpp:108
TimingStats getTimingStats()
Return stats.
Definition: Renderer.cpp:138
virtual void displayCurrentField()=0
Display the current field of the frame.
Data structure that keeps track of the state of each digit (its segment bit pattern and its style)...
Definition: StyledDigit.h:36
void writePatternAt(uint8_t digit, uint8_t pattern, uint8_t style)
Write the pattern and style for a given digit.
Definition: Renderer.cpp:79
void renderField()
Render the current field immediately.
Definition: Renderer.cpp:117
static void calcPulseFractionForFrameUsingInverse(uint16_t framesPerPulseInverse, uint16_t framesPerPulse, uint16_t &currentFrame, uint8_t &pulseFraction)
Calculate the pulse fraction using the reciprocal of the framesPerPulse, which avoid a long division...
Definition: Renderer.cpp:235
void writeStyleAt(uint8_t digit, uint8_t style)
Write the style for a given digit, leaving pattern unchanged.
Definition: Renderer.cpp:92
virtual unsigned long micros()
Get the current micros.
Definition: Hardware.h:59
virtual void configure()
Configure the driver with the parameters given by the various setXxx() methods.
Definition: Renderer.cpp:37
virtual bool isBrightnessSupported()=0
Returns true if the driver supports brightness.
void updateFrame()
Perform things that need to be done each frame.
Definition: Renderer.cpp:129
void setPattern(uint8_t digit, SegmentPatternType pattern, uint8_t brightness=DimmingDigit::kOn)
Set the pattern for a given digit.
Definition: Driver.cpp:43
void calcBlinkAndPulseForFrame()
Calculate the blink and pulse states for current frame.
Definition: Renderer.cpp:184
void renderStyledDigits()
Translate the StyledDigits to DimmingDigits for the Driver.
Definition: Renderer.cpp:145
static void calcPulseFractionForFrame(uint16_t framesPerPulse, uint16_t &currentFrame, uint8_t &pulseFraction)
Calculate the pulse fraction.
Definition: Renderer.cpp:216
uint16_t getCount()
Number of times update() was called since last reset.
Definition: TimingStats.h:58
uint16_t getFieldsPerSecond()
Return the fields per second.
Definition: Renderer.h:94
void writeDecimalPointAt(uint8_t digit, bool state=true)
Write the decimal point for the digit.
Definition: Renderer.cpp:98
static uint8_t calcBrightness(uint8_t style, uint8_t brightness, uint8_t blinkSlowState, uint8_t blinkFastState, bool isPulseEnabled, uint8_t pulseSlowFraction, uint8_t pulseFastFraction)
Calculate the effective brightness of a digit with the given style.
Definition: Renderer.cpp:156
static void calcBlinkStateForFrame(uint16_t framesPerBlink, uint16_t &currentFrame, uint8_t &blinkState)
Calculate the blink state.
Definition: Renderer.cpp:205
static void incrementMod(T &i, T m)
Increment i modulo m, avoiding expensive % operator on some 8-bit processors like AVR...
Definition: Util.h:39
virtual uint16_t getFieldsPerFrame()=0
Return number of fields per frame.