microbit-dal: swapped pwm implementation for a more stable one

As per issue #218, pwm has always been unstable resulting in a clicking noise atwhen tones are amplified, and twitchy servo controllers.

This commit when combined with lancaster-university/mbed-classic#03f4fb and lancaster-university/mbed-classic#be51bd will produce a more stable waveform, whilst also maintaining the behaviour of DynamicPwm which has now been absorbed by the PWM driver itself.
This commit is contained in:
James Devine 2017-02-01 19:22:59 +00:00
parent 350aad7a90
commit 15194f2ed2
3 changed files with 28 additions and 204 deletions

View file

@ -29,15 +29,8 @@ DEALINGS IN THE SOFTWARE.
#ifndef MICROBIT_DYNAMIC_PWM_H
#define MICROBIT_DYNAMIC_PWM_H
#define NO_PWMS 3
#define MICROBIT_DEFAULT_PWM_PERIOD 20000
enum PwmPersistence
{
PWM_PERSISTENCE_TRANSIENT = 1,
PWM_PERSISTENCE_PERSISTENT = 2,
};
/**
* Class definition for DynamicPwm.
*
@ -47,13 +40,10 @@ enum PwmPersistence
class DynamicPwm : public PwmOut
{
private:
static DynamicPwm* pwms[NO_PWMS];
static uint8_t lastUsed;
static uint16_t sharedPeriod;
uint8_t flags;
uint32_t period;
float lastValue;
public:
/**
* An internal constructor used when allocating a new DynamicPwm instance.
@ -63,40 +53,7 @@ class DynamicPwm : public PwmOut
* @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.)
* or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.)
*/
DynamicPwm(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT);
public:
/**
* Redirects the pwm channel to point at a different pin.
*
* @param pin the desired pin to output a PWM wave.
*
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->redirect(p0); // pwm is now produced on p0
* @endcode
*/
void redirect(PinName pin);
/**
* Creates a new DynamicPwm instance, or reuses an existing instance that
* has a persistence level of PWM_PERSISTENCE_TRANSIENT.
*
* @param pin the name of the pin for the pwm to target
*
* @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.)
* or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.)
*
* @return a pointer to the first available free pwm channel - or the first one that can be reallocated. If
* no channels are available, NULL is returned.
*
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* @endcode
*/
static DynamicPwm* allocate(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT);
DynamicPwm(PinName pin);
/**
* Frees this DynamicPwm instance for reuse.
@ -123,7 +80,7 @@ class DynamicPwm : public PwmOut
int write(float value);
/**
* Retreives the PinName associated with this DynamicPwm instance.
* Retrieves the PinName associated with this DynamicPwm instance.
*
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
@ -138,7 +95,7 @@ class DynamicPwm : public PwmOut
PinName getPinName();
/**
* Retreives the last value that has been written to this DynamicPwm instance.
* Retrieves the last value that has been written to this DynamicPwm instance.
* in the range 0 - 1023 inclusive.
*
* @code
@ -152,7 +109,7 @@ class DynamicPwm : public PwmOut
int getValue();
/**
* Retreives the current period in use by the entire PWM module in microseconds.
* Retrieves the current period in use by the entire PWM module in microseconds.
*
* Example:
* @code
@ -160,7 +117,7 @@ class DynamicPwm : public PwmOut
* pwm->getPeriod();
* @endcode
*/
int getPeriodUs();
uint32_t getPeriodUs();
/**
* Retreives the current period in use by the entire PWM module in milliseconds.
@ -174,7 +131,7 @@ class DynamicPwm : public PwmOut
* pwm->getPeriod();
* @endcode
*/
int getPeriod();
uint32_t getPeriod();
/**
* Sets the period used by the WHOLE PWM module.
@ -193,7 +150,7 @@ class DynamicPwm : public PwmOut
*
* @note Any changes to the period will AFFECT ALL CHANNELS.
*/
int setPeriodUs(int period);
int setPeriodUs(uint32_t period);
/**
* Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS.
@ -210,7 +167,9 @@ class DynamicPwm : public PwmOut
* pwm->setPeriod(20);
* @endcode
*/
int setPeriod(int period);
int setPeriod(uint32_t period);
~DynamicPwm();
};
#endif

View file

@ -35,52 +35,6 @@ DEALINGS IN THE SOFTWARE.
#include "MicroBitPin.h"
#include "ErrorNo.h"
DynamicPwm* DynamicPwm::pwms[NO_PWMS] = { NULL, NULL, NULL };
uint8_t DynamicPwm::lastUsed = NO_PWMS+1; //set it to out of range i.e. 4 so we know it hasn't been used yet.
uint16_t DynamicPwm::sharedPeriod = 0; //set the shared period to an unknown state
/**
* Reassigns an already operational PWM channel to the given pin.
*
* @param pin The desired pin to begin a PWM wave.
*
* @param oldPin The pin to stop running a PWM wave.
*
* @param channel_number The GPIOTE channel being used to drive this PWM channel
*
* TODO: Merge into mbed, at a later date.
*/
void gpiote_reinit(PinName pin, PinName oldPin, uint8_t channel_number)
{
// Connect GPIO input buffers and configure PWM_OUTPUT_PIN_NUMBER as an output.
NRF_GPIO->PIN_CNF[pin] = (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos)
| (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos)
| (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos)
| (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos)
| (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos);
NRF_GPIO->OUTCLR = (1 << oldPin);
NRF_GPIO->OUTCLR = (1 << pin);
/* Finally configure the channel as the caller expects. If OUTINIT works, the channel is configured properly.
If it does not, the channel output inheritance sets the proper level. */
NRF_GPIOTE->CONFIG[channel_number] = (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |
((uint32_t)pin << GPIOTE_CONFIG_PSEL_Pos) |
((uint32_t)GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |
((uint32_t)GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos); // ((uint32_t)GPIOTE_CONFIG_OUTINIT_High <<
// GPIOTE_CONFIG_OUTINIT_Pos);//
/* Three NOPs are required to make sure configuration is written before setting tasks or getting events */
__NOP();
__NOP();
__NOP();
NRF_TIMER2->CC[channel_number] = 0;
}
/**
* An internal constructor used when allocating a new DynamicPwm instance.
*
@ -89,82 +43,9 @@ void gpiote_reinit(PinName pin, PinName oldPin, uint8_t channel_number)
* @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.)
* or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.)
*/
DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence) : PwmOut(pin)
DynamicPwm::DynamicPwm(PinName pin) : PwmOut(pin)
{
this->flags = persistence;
}
/**
* Redirects the pwm channel to point at a different pin.
*
* @param pin the desired pin to output a PWM wave.
*
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->redirect(p0); // pwm is now produced on p0
* @endcode
*/
void DynamicPwm::redirect(PinName pin)
{
gpiote_reinit(pin, _pwm.pin, (uint8_t)_pwm.pwm);
this->_pwm.pin = pin;
}
/**
* Creates a new DynamicPwm instance, or reuses an existing instance that
* has a persistence level of PWM_PERSISTENCE_TRANSIENT.
*
* @param pin the name of the pin for the pwm to target
*
* @param persistance the level of persistence for this pin PWM_PERSISTENCE_PERSISTENT (can not be replaced until freed, should only be used for system services really.)
* or PWM_PERSISTENCE_TRANSIENT (can be replaced at any point if a channel is required.)
*
* @return a pointer to the first available free pwm channel - or the first one that can be reallocated. If
* no channels are available, NULL is returned.
*
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* @endcode
*/
DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence)
{
//try to find a blank spot first
for(int i = 0; i < NO_PWMS; i++)
{
if(pwms[i] == NULL)
{
lastUsed = i;
pwms[i] = new DynamicPwm(pin, persistence);
return pwms[i];
}
}
//no blank spot.. try to find a transient PWM
int channelIterator = (lastUsed + 1 > NO_PWMS - 1) ? 0 : lastUsed + 1;
while(channelIterator != lastUsed)
{
if(pwms[channelIterator]->flags & PWM_PERSISTENCE_TRANSIENT)
{
lastUsed = channelIterator;
pwms[channelIterator]->flags = persistence;
pwms[channelIterator]->redirect(pin);
return pwms[channelIterator];
}
channelIterator = (channelIterator + 1 > NO_PWMS - 1) ? 0 : channelIterator + 1;
}
//if we haven't found a free one, we must try to allocate the last used...
if(pwms[lastUsed]->flags & PWM_PERSISTENCE_TRANSIENT)
{
pwms[lastUsed]->flags = persistence;
pwms[lastUsed]->redirect(pin);
return pwms[lastUsed];
}
//well if we have no transient channels - we can't give any away! :( return null
return (DynamicPwm*)NULL;
this->period = 0;
}
/**
@ -175,20 +56,10 @@ DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence)
* pwm->release();
* @endcode
*/
void DynamicPwm::release()
DynamicPwm::~DynamicPwm()
{
//free the pwm instance.
NRF_GPIOTE->CONFIG[(uint8_t) _pwm.pwm] = 0;
pwmout_free(&_pwm);
this->flags = PWM_PERSISTENCE_TRANSIENT;
//set the pointer to this object to null...
for(int i =0; i < NO_PWMS; i++)
if(pwms[i] == this)
{
delete pwms[i];
pwms[i] = NULL;
}
}
/**
@ -215,7 +86,7 @@ int DynamicPwm::write(float value){
}
/**
* Retreives the PinName associated with this DynamicPwm instance.
* Retrieves the PinName associated with this DynamicPwm instance.
*
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
@ -233,7 +104,7 @@ PinName DynamicPwm::getPinName()
}
/**
* Retreives the last value that has been written to this DynamicPwm instance.
* Retrieves the last value that has been written to this DynamicPwm instance.
* in the range 0 - 1023 inclusive.
*
* @code
@ -250,7 +121,7 @@ int DynamicPwm::getValue()
}
/**
* Retreives the current period in use by the entire PWM module in microseconds.
* Retrieves the current period in use by the entire PWM module in microseconds.
*
* Example:
* @code
@ -258,13 +129,13 @@ int DynamicPwm::getValue()
* pwm->getPeriod();
* @endcode
*/
int DynamicPwm::getPeriodUs()
uint32_t DynamicPwm::getPeriodUs()
{
return sharedPeriod;
return period;
}
/**
* Retreives the current period in use by the entire PWM module in milliseconds.
* Retrieves the current period in use by the entire PWM module in milliseconds.
*
* Example:
* @code
@ -275,7 +146,7 @@ int DynamicPwm::getPeriodUs()
* pwm->getPeriod();
* @endcode
*/
int DynamicPwm::getPeriod()
uint32_t DynamicPwm::getPeriod()
{
return getPeriodUs() / 1000;
}
@ -297,15 +168,12 @@ int DynamicPwm::getPeriod()
*
* @note Any changes to the period will AFFECT ALL CHANNELS.
*/
int DynamicPwm::setPeriodUs(int period)
int DynamicPwm::setPeriodUs(uint32_t period)
{
if(period < 0)
return MICROBIT_INVALID_PARAMETER;
//#HACK this forces mbed to update the pulse width calculation.
period_us(period);
write(lastValue);
sharedPeriod = period;
this->period = period;
return MICROBIT_OK;
}
@ -325,7 +193,7 @@ int DynamicPwm::setPeriodUs(int period)
* pwm->setPeriod(20);
* @endcode
*/
int DynamicPwm::setPeriod(int period)
int DynamicPwm::setPeriod(uint32_t period)
{
return setPeriodUs(period * 1000);
}

View file

@ -87,10 +87,7 @@ void MicroBitPin::disconnect()
}
if (status & IO_STATUS_ANALOG_OUT)
{
if(((DynamicPwm *)pin)->getPinName() == name)
((DynamicPwm *)pin)->release();
}
delete ((DynamicPwm *)pin);
if (status & IO_STATUS_TOUCH_IN)
delete ((MicroBitButton *)pin);
@ -194,7 +191,7 @@ int MicroBitPin::obtainAnalogChannel()
// Move into an analogue input state if necessary, if we are no longer the focus of a DynamicPWM instance, allocate ourselves again!
if (!(status & IO_STATUS_ANALOG_OUT) || !(((DynamicPwm *)pin)->getPinName() == name)){
disconnect();
pin = (void *)DynamicPwm::allocate(name);
pin = (void *)new DynamicPwm(name);
status |= IO_STATUS_ANALOG_OUT;
}