led.cpp
4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include <ion/led.h>
#include <ion/display.h>
#include "device.h"
#include "led.h"
#include "regs/regs.h"
// Public Ion::LED methods
static KDColor sLedColor = KDColorBlack;
KDColor Ion::LED::getColor() {
return sLedColor;
}
void Ion::LED::setColor(KDColor c) {
sLedColor = c;
/* Active all RGB colors */
TIM3.CCMR()->setOC2M(TIM<Register16>::CCMR::OCM::PWM1);
TIM3.CCMR()->setOC4M(TIM<Register16>::CCMR::OCM::PWM1);
TIM3.CCMR()->setOC3M(TIM<Register16>::CCMR::OCM::PWM1);
/* Set the PWM duty cycles to display the right color */
constexpr float maxColorValue = (float)((1 << 8) -1);
Device::setPeriodAndDutyCycles(Device::Mode::PWM, c.red()/maxColorValue, c.green()/maxColorValue, c.blue()/maxColorValue);
}
void Ion::LED::setBlinking(uint16_t period, float dutyCycle) {
/* We want to use the PWM at a slow rate to display a seeable blink.
* Consequently, we do not use PWM to display the right color anymore but to
* blink. We cannot use the PWM to display the exact color so we 'project the
* color on 3 bits' : all colors have 2 states - active or not. */
TIM3.CCMR()->setOC2M(sLedColor.red() > 0 ? TIM<Register16>::CCMR::OCM::PWM1 : TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC4M(sLedColor.green() > 0 ? TIM<Register16>::CCMR::OCM::PWM1 : TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM<Register16>::CCMR::OCM::PWM1 : TIM<Register16>::CCMR::OCM::ForceInactive);
Device::setPeriodAndDutyCycles(Device::Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period);
}
// Private Ion::Device::LED methods
namespace Ion {
namespace LED {
namespace Device {
void init() {
initGPIO();
initTimer();
}
void shutdown() {
shutdownTimer();
shutdownGPIO();
}
void initGPIO() {
/* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer,
* which is an alternate function. More precisely, we will use AF2, which maps
* PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */
for(const GPIOPin & g : RGBPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction);
g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2);
}
}
void shutdownGPIO() {
for(const GPIOPin & g : RGBPins) {
g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog);
g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None);
}
}
void initTimer() {
// Output preload enable for channels 2-4
TIM3.CCMR()->setOC2PE(true);
TIM3.CCMR()->setOC3PE(true);
TIM3.CCMR()->setOC4PE(true);
// Auto-reload preload enable
TIM3.CR1()->setARPE(true);
// Enable Capture/Compare for channel 2 to 4
TIM3.CCER()->setCC2E(true);
TIM3.CCER()->setCC3E(true);
TIM3.CCER()->setCC4E(true);
TIM3.BDTR()->setMOE(true);
TIM3.CR1()->setCEN(true);
}
void shutdownTimer() {
TIM3.CCMR()->setOC2M(TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC4M(TIM<Register16>::CCMR::OCM::ForceInactive);
TIM3.CCMR()->setOC3M(TIM<Register16>::CCMR::OCM::ForceInactive);
}
/* Pulse width modulation mode allows you to generate a signal with a
* frequency determined by the value of the TIMx_ARR register and a duty cycle
* determined by the value of the TIMx_CCRx register. */
void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period) {
switch (mode) {
case Mode::PWM:
/* Let's set the prescaler to 1. Increasing the prescaler would slow down
* the modulation, which can be useful when debugging or when we want an
* actual blinking. */
TIM3.PSC()->set(1);
TIM3.ARR()->set(PWMPeriod);
period = PWMPeriod;
break;
case Mode::Blink:
int systemClockFreq = 96;
/* We still want to do PWM, but at a rate slow enough to blink. Ideally,
* we want to pre-scale the period to be able to set it in milliseconds;
* however, as the prescaler is cap by 2^16-1, we divide it by a factor
* and correct the period consequently. */
int factor = 2;
// TODO: explain the 2 ?
TIM3.PSC()->set(systemClockFreq*1000/factor);
period *= factor;
TIM3.ARR()->set(period);
break;
}
TIM3.CCR2()->set(dutyCycleRed*period);
TIM3.CCR3()->set(dutyCycleBlue*period);
TIM3.CCR4()->set(dutyCycleGreen*period);
}
}
}
}