Skip to content

Commit 3d7ead8

Browse files
authored
Merge pull request #496 from henrygab/PWM_Conflicts
Opt-in PWM conflict avoidance
2 parents 11142b5 + 53819e5 commit 3d7ead8

File tree

11 files changed

+723
-282
lines changed

11 files changed

+723
-282
lines changed

cores/nRF5/HardwarePWM.cpp

Lines changed: 136 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,123 @@ HardwarePWM* HwPWMx[] =
5353
#endif
5454
};
5555

56-
HardwarePWM::HardwarePWM(NRF_PWM_Type* pwm)
56+
#if CFG_DEBUG
57+
bool can_stringify_token(uintptr_t token)
5758
{
58-
_pwm = pwm;
59+
uint8_t * t = (uint8_t *)&token;
60+
for (size_t i = 0; i < sizeof(uintptr_t); ++i, ++t)
61+
{
62+
uint8_t x = *t;
63+
if ((x < 0x20) || (x > 0x7E)) return false;
64+
}
65+
return true;
66+
}
67+
68+
void HardwarePWM::DebugOutput(Stream& logger)
69+
{
70+
const size_t count = arrcount(HwPWMx);
71+
logger.printf("HwPWM Debug:");
72+
for (size_t i = 0; i < count; i++) {
73+
HardwarePWM const * pwm = HwPWMx[i];
74+
uintptr_t token = pwm->_owner_token;
75+
logger.printf(" || %d:", i);
76+
if (can_stringify_token(token)) {
77+
uint8_t * t = (uint8_t*)(&token);
78+
static_assert(sizeof(uintptr_t) == 4);
79+
logger.printf(" \"%c%c%c%c\"", t[0], t[1], t[2], t[3] );
80+
} else {
81+
static_assert(sizeof(uintptr_t) == 4);
82+
logger.printf(" %08x", token);
83+
}
84+
for (size_t j = 0; j < MAX_CHANNELS; j++) {
85+
uint32_t r = pwm->_pwm->PSEL.OUT[j]; // only read it once
86+
if ( (r & PWM_PSEL_OUT_CONNECT_Msk) != (PWM_PSEL_OUT_CONNECT_Disconnected << PWM_PSEL_OUT_CONNECT_Pos) ) {
87+
logger.printf(" %02x", r & 0x1F);
88+
} else {
89+
logger.printf(" xx");
90+
}
91+
}
92+
}
93+
logger.printf("\n");
94+
}
95+
#else
96+
void HardwarePWM::DebugOutput(Stream& logger) {}
97+
#endif // CFG_DEBUG
98+
99+
// returns true ONLY when (1) no PWM channel has a pin, and (2) the owner token is nullptr
100+
bool HardwarePWM::takeOwnership(uintptr_t token)
101+
{
102+
bool notInIsr = !isInISR();
103+
if (token == 0) {
104+
if (notInIsr) {
105+
LOG_LV1("HwPWM", "zero / nullptr is not a valid ownership token (attempted use in takeOwnership)");
106+
}
107+
return false; // cannot take ownership with nullptr
108+
}
109+
if (token == this->_owner_token) {
110+
if (notInIsr) {
111+
LOG_LV1("HwPWM", "failing to acquire ownership because already owned by requesting token (cannot take ownership twice)");
112+
}
113+
}
114+
if (this->_owner_token != 0) {
115+
return false;
116+
}
117+
if (this->usedChannelCount() != 0) {
118+
return false;
119+
}
120+
if (this->enabled()) {
121+
return false;
122+
}
123+
// TODO: warn, but do not fail, if taking ownership with IRQs already enabled
124+
// NVIC_GetActive
125+
126+
// Use C++11 atomic CAS operation
127+
uintptr_t newValue = 0U;
128+
return this->_owner_token.compare_exchange_strong(newValue, token);
129+
}
130+
// returns true ONLY when (1) no PWM channel has a pin attached, and (2) the owner token matches
131+
bool HardwarePWM::releaseOwnership(uintptr_t token)
132+
{
133+
bool notInIsr = !isInISR();
134+
if (token == 0) {
135+
if (notInIsr) {
136+
LOG_LV1("HwPWM", "zero / nullptr is not a valid ownership token (attempted use in releaseOwnership)");
137+
}
138+
return false;
139+
}
140+
if (!this->isOwner(token)) {
141+
if (notInIsr) {
142+
LOG_LV1("HwPWM", "attempt to release ownership when not the current owner");
143+
}
144+
return false;
145+
}
146+
if (this->usedChannelCount() != 0) {
147+
if (notInIsr) {
148+
LOG_LV1("HwPWM", "attempt to release ownership when at least on channel is still connected");
149+
}
150+
return false;
151+
}
152+
if (this->enabled()) {
153+
if (notInIsr) {
154+
LOG_LV1("HwPWM", "attempt to release ownership when PWM peripheral is still enabled");
155+
}
156+
return false; // if it's enabled, do not allow ownership to be released, even with no pins in use
157+
}
158+
// TODO: warn, but do not fail, if releasing ownership with IRQs enabled
159+
// NVIC_GetActive
160+
161+
// Use C++11 atomic CAS operation
162+
bool result = this->_owner_token.compare_exchange_strong(token, 0U);
163+
if (!result) {
164+
LOG_LV1("HwPWM", "race condition resulted in failure to acquire ownership");
165+
}
166+
return result;
167+
}
168+
169+
HardwarePWM::HardwarePWM(NRF_PWM_Type* pwm) :
170+
_pwm(pwm)
171+
{
172+
_owner_token = 0U;
59173
arrclr(_seq0);
60174

61175
_max_value = 255;
@@ -204,17 +318,35 @@ bool HardwarePWM::writePin(uint8_t pin, uint16_t value, bool inverted)
204318
return writeChannel(ch, value, inverted);
205319
}
206320

207-
uint16_t HardwarePWM::readPin(uint8_t pin)
321+
uint16_t HardwarePWM::readPin(uint8_t pin) const
208322
{
209323
int ch = pin2channel(pin);
210324
VERIFY( ch >= 0, 0);
211325

212326
return readChannel(ch);
213327
}
214328

215-
uint16_t HardwarePWM::readChannel(uint8_t ch)
329+
uint16_t HardwarePWM::readChannel(uint8_t ch) const
216330
{
217331
// remove inverted bit
218332
return (_seq0[ch] & 0x7FFF);
219333
}
220334

335+
uint8_t HardwarePWM::usedChannelCount(void) const
336+
{
337+
uint8_t usedChannels = 0;
338+
for(int i=0; i<MAX_CHANNELS; i++)
339+
{
340+
if ( (_pwm->PSEL.OUT[i] & PWM_PSEL_OUT_CONNECT_Msk) != (PWM_PSEL_OUT_CONNECT_Disconnected << PWM_PSEL_OUT_CONNECT_Pos) )
341+
{
342+
usedChannels++;
343+
}
344+
}
345+
return usedChannels;
346+
}
347+
348+
uint8_t HardwarePWM::freeChannelCount(void) const
349+
{
350+
return MAX_CHANNELS - usedChannelCount();
351+
}
352+

cores/nRF5/HardwarePWM.h

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838

3939
#include "common_inc.h"
4040
#include "nrf.h"
41+
#include <atomic>
4142

4243
#ifdef NRF52840_XXAA
4344
#define HWPWM_MODULE_NUM 4
@@ -49,7 +50,8 @@ class HardwarePWM
4950
{
5051
private:
5152
enum { MAX_CHANNELS = 4 }; // Max channel per group
52-
NRF_PWM_Type* _pwm;
53+
NRF_PWM_Type * const _pwm;
54+
std::atomic_uintptr_t _owner_token;
5355

5456
uint16_t _seq0[MAX_CHANNELS];
5557

@@ -67,10 +69,23 @@ class HardwarePWM
6769

6870
void setClockDiv(uint8_t div); // value is PWM_PRESCALER_PRESCALER_DIV_x, DIV1 is 16Mhz
6971

72+
// Cooperative ownership sharing
73+
74+
// returns true ONLY when (1) no PWM channel has a pin, and (2) the owner token is nullptr
75+
bool takeOwnership (uintptr_t token);
76+
// returns true ONLY when (1) no PWM channel has a pin attached, and (2) the owner token matches
77+
bool releaseOwnership(uintptr_t token);
78+
79+
// allows caller to verify that they own the peripheral
80+
__INLINE bool isOwner(uintptr_t token) const
81+
{
82+
return this->_owner_token == token;
83+
}
84+
7085
bool addPin (uint8_t pin);
7186
bool removePin (uint8_t pin);
7287

73-
int pin2channel(uint8_t pin)
88+
int pin2channel(uint8_t pin) const
7489
{
7590
pin = g_ADigitalPinMap[pin];
7691
for(int i=0; i<MAX_CHANNELS; i++)
@@ -80,7 +95,7 @@ class HardwarePWM
8095
return (-1);
8196
}
8297

83-
bool checkPin(uint8_t pin)
98+
bool checkPin(uint8_t pin) const
8499
{
85100
return pin2channel(pin) >= 0;
86101
}
@@ -94,8 +109,14 @@ class HardwarePWM
94109
bool writeChannel(uint8_t ch , uint16_t value, bool inverted = false);
95110

96111
// Read current set value
97-
uint16_t readPin (uint8_t pin);
98-
uint16_t readChannel (uint8_t ch);
112+
uint16_t readPin (uint8_t pin) const;
113+
uint16_t readChannel (uint8_t ch) const;
114+
115+
// Get count of used / free channels
116+
uint8_t usedChannelCount(void) const;
117+
uint8_t freeChannelCount(void) const;
118+
119+
static void DebugOutput(Stream& logger);
99120
};
100121

101122
extern HardwarePWM HwPWM0;

0 commit comments

Comments
 (0)