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:
parent
350aad7a90
commit
15194f2ed2
3 changed files with 28 additions and 204 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue