Skip to content

Commit

Permalink
Merge pull request #496 from henrygab/PWM_Conflicts
Browse files Browse the repository at this point in the history
Opt-in PWM conflict avoidance
  • Loading branch information
hathach authored Aug 24, 2020
2 parents 11142b5 + 53819e5 commit 3d7ead8
Show file tree
Hide file tree
Showing 11 changed files with 723 additions and 282 deletions.
140 changes: 136 additions & 4 deletions cores/nRF5/HardwarePWM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,123 @@ HardwarePWM* HwPWMx[] =
#endif
};

HardwarePWM::HardwarePWM(NRF_PWM_Type* pwm)
#if CFG_DEBUG
bool can_stringify_token(uintptr_t token)
{
_pwm = pwm;
uint8_t * t = (uint8_t *)&token;
for (size_t i = 0; i < sizeof(uintptr_t); ++i, ++t)
{
uint8_t x = *t;
if ((x < 0x20) || (x > 0x7E)) return false;
}
return true;
}

void HardwarePWM::DebugOutput(Stream& logger)
{
const size_t count = arrcount(HwPWMx);
logger.printf("HwPWM Debug:");
for (size_t i = 0; i < count; i++) {
HardwarePWM const * pwm = HwPWMx[i];
uintptr_t token = pwm->_owner_token;
logger.printf(" || %d:", i);
if (can_stringify_token(token)) {
uint8_t * t = (uint8_t*)(&token);
static_assert(sizeof(uintptr_t) == 4);
logger.printf(" \"%c%c%c%c\"", t[0], t[1], t[2], t[3] );
} else {
static_assert(sizeof(uintptr_t) == 4);
logger.printf(" %08x", token);
}
for (size_t j = 0; j < MAX_CHANNELS; j++) {
uint32_t r = pwm->_pwm->PSEL.OUT[j]; // only read it once
if ( (r & PWM_PSEL_OUT_CONNECT_Msk) != (PWM_PSEL_OUT_CONNECT_Disconnected << PWM_PSEL_OUT_CONNECT_Pos) ) {
logger.printf(" %02x", r & 0x1F);
} else {
logger.printf(" xx");
}
}
}
logger.printf("\n");
}
#else
void HardwarePWM::DebugOutput(Stream& logger) {}
#endif // CFG_DEBUG

// returns true ONLY when (1) no PWM channel has a pin, and (2) the owner token is nullptr
bool HardwarePWM::takeOwnership(uintptr_t token)
{
bool notInIsr = !isInISR();
if (token == 0) {
if (notInIsr) {
LOG_LV1("HwPWM", "zero / nullptr is not a valid ownership token (attempted use in takeOwnership)");
}
return false; // cannot take ownership with nullptr
}
if (token == this->_owner_token) {
if (notInIsr) {
LOG_LV1("HwPWM", "failing to acquire ownership because already owned by requesting token (cannot take ownership twice)");
}
}
if (this->_owner_token != 0) {
return false;
}
if (this->usedChannelCount() != 0) {
return false;
}
if (this->enabled()) {
return false;
}
// TODO: warn, but do not fail, if taking ownership with IRQs already enabled
// NVIC_GetActive

// Use C++11 atomic CAS operation
uintptr_t newValue = 0U;
return this->_owner_token.compare_exchange_strong(newValue, token);
}
// returns true ONLY when (1) no PWM channel has a pin attached, and (2) the owner token matches
bool HardwarePWM::releaseOwnership(uintptr_t token)
{
bool notInIsr = !isInISR();
if (token == 0) {
if (notInIsr) {
LOG_LV1("HwPWM", "zero / nullptr is not a valid ownership token (attempted use in releaseOwnership)");
}
return false;
}
if (!this->isOwner(token)) {
if (notInIsr) {
LOG_LV1("HwPWM", "attempt to release ownership when not the current owner");
}
return false;
}
if (this->usedChannelCount() != 0) {
if (notInIsr) {
LOG_LV1("HwPWM", "attempt to release ownership when at least on channel is still connected");
}
return false;
}
if (this->enabled()) {
if (notInIsr) {
LOG_LV1("HwPWM", "attempt to release ownership when PWM peripheral is still enabled");
}
return false; // if it's enabled, do not allow ownership to be released, even with no pins in use
}
// TODO: warn, but do not fail, if releasing ownership with IRQs enabled
// NVIC_GetActive

// Use C++11 atomic CAS operation
bool result = this->_owner_token.compare_exchange_strong(token, 0U);
if (!result) {
LOG_LV1("HwPWM", "race condition resulted in failure to acquire ownership");
}
return result;
}

HardwarePWM::HardwarePWM(NRF_PWM_Type* pwm) :
_pwm(pwm)
{
_owner_token = 0U;
arrclr(_seq0);

_max_value = 255;
Expand Down Expand Up @@ -204,17 +318,35 @@ bool HardwarePWM::writePin(uint8_t pin, uint16_t value, bool inverted)
return writeChannel(ch, value, inverted);
}

uint16_t HardwarePWM::readPin(uint8_t pin)
uint16_t HardwarePWM::readPin(uint8_t pin) const
{
int ch = pin2channel(pin);
VERIFY( ch >= 0, 0);

return readChannel(ch);
}

uint16_t HardwarePWM::readChannel(uint8_t ch)
uint16_t HardwarePWM::readChannel(uint8_t ch) const
{
// remove inverted bit
return (_seq0[ch] & 0x7FFF);
}

uint8_t HardwarePWM::usedChannelCount(void) const
{
uint8_t usedChannels = 0;
for(int i=0; i<MAX_CHANNELS; i++)
{
if ( (_pwm->PSEL.OUT[i] & PWM_PSEL_OUT_CONNECT_Msk) != (PWM_PSEL_OUT_CONNECT_Disconnected << PWM_PSEL_OUT_CONNECT_Pos) )
{
usedChannels++;
}
}
return usedChannels;
}

uint8_t HardwarePWM::freeChannelCount(void) const
{
return MAX_CHANNELS - usedChannelCount();
}

31 changes: 26 additions & 5 deletions cores/nRF5/HardwarePWM.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

#include "common_inc.h"
#include "nrf.h"
#include <atomic>

#ifdef NRF52840_XXAA
#define HWPWM_MODULE_NUM 4
Expand All @@ -49,7 +50,8 @@ class HardwarePWM
{
private:
enum { MAX_CHANNELS = 4 }; // Max channel per group
NRF_PWM_Type* _pwm;
NRF_PWM_Type * const _pwm;
std::atomic_uintptr_t _owner_token;

uint16_t _seq0[MAX_CHANNELS];

Expand All @@ -67,10 +69,23 @@ class HardwarePWM

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

// Cooperative ownership sharing

// returns true ONLY when (1) no PWM channel has a pin, and (2) the owner token is nullptr
bool takeOwnership (uintptr_t token);
// returns true ONLY when (1) no PWM channel has a pin attached, and (2) the owner token matches
bool releaseOwnership(uintptr_t token);

// allows caller to verify that they own the peripheral
__INLINE bool isOwner(uintptr_t token) const
{
return this->_owner_token == token;
}

bool addPin (uint8_t pin);
bool removePin (uint8_t pin);

int pin2channel(uint8_t pin)
int pin2channel(uint8_t pin) const
{
pin = g_ADigitalPinMap[pin];
for(int i=0; i<MAX_CHANNELS; i++)
Expand All @@ -80,7 +95,7 @@ class HardwarePWM
return (-1);
}

bool checkPin(uint8_t pin)
bool checkPin(uint8_t pin) const
{
return pin2channel(pin) >= 0;
}
Expand All @@ -94,8 +109,14 @@ class HardwarePWM
bool writeChannel(uint8_t ch , uint16_t value, bool inverted = false);

// Read current set value
uint16_t readPin (uint8_t pin);
uint16_t readChannel (uint8_t ch);
uint16_t readPin (uint8_t pin) const;
uint16_t readChannel (uint8_t ch) const;

// Get count of used / free channels
uint8_t usedChannelCount(void) const;
uint8_t freeChannelCount(void) const;

static void DebugOutput(Stream& logger);
};

extern HardwarePWM HwPWM0;
Expand Down
Loading

0 comments on commit 3d7ead8

Please sign in to comment.