Merge pull request #68 from lancaster-university/updating-dynamic-pwm

DynamicPwm behaviour modifications, and added a new Servo API to MicroBitPin
This commit is contained in:
James Devine 2016-01-12 21:14:29 +00:00
commit c568dd7cf0
5 changed files with 504 additions and 191 deletions

View file

@ -4,7 +4,7 @@
#define MICROBIT_DYNAMIC_PWM_H
#define NO_PWMS 3
#define MICROBIT_DISPLAY_PWM_PERIOD 1000
#define MICROBIT_DEFAULT_PWM_PERIOD 20000
enum PwmPersistence
{
@ -15,28 +15,30 @@ enum PwmPersistence
/**
* Class definition for DynamicPwm.
*
* This class addresses a few issues found in the underlying libraries.
* This class addresses a few issues found in the underlying libraries.
* This provides the ability for a neat, clean swap between PWM channels.
*/
class DynamicPwm : public PwmOut
{
private:
private:
static DynamicPwm* pwms[NO_PWMS];
static uint8_t lastUsed;
static uint16_t sharedPeriod;
uint8_t flags;
float lastValue;
/**
* An internal constructor used when allocating a new DynamicPwm representation
* @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.)
* @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.)
* @param period the frequency of the pwm channel in us.
*/
DynamicPwm(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT, int period = MICROBIT_DISPLAY_PWM_PERIOD);
public:
DynamicPwm(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT);
public:
/**
* Redirects the pwm channel to point at a different pin.
* @param pin the new pin to direct PWM at.
@ -48,12 +50,12 @@ class DynamicPwm : public PwmOut
* @endcode
*/
void redirect(PinName pin);
/**
* Retrieves a pointer to the first available free pwm channel - or the first one that can be reallocated.
* @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.)
* @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.)
* @param period the frequency of the pwm channel in us.
*
@ -62,8 +64,8 @@ class DynamicPwm : public PwmOut
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* @endcode
*/
static DynamicPwm* allocate(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT, int period = MICROBIT_DISPLAY_PWM_PERIOD);
static DynamicPwm* allocate(PinName pin, PwmPersistence persistence = PWM_PERSISTENCE_TRANSIENT);
/**
* Frees this DynamicPwm instance if the pointer is valid.
*
@ -74,31 +76,95 @@ class DynamicPwm : public PwmOut
* @endcode
*/
void release();
/**
* A lightweight wrapper around the super class' write in order to capture the value
*
* @param value the duty cycle percentage in floating point format.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate();
* pwm->write(0.5);
* @endcode
*/
int write(float value);
/**
* Retreives the pin name associated with this DynamicPwm instance.
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->getPinName(); // equal to n
* pwm->getPinName();
* @endcode
*/
PinName getPinName();
/**
* Retreives the last value that has been written to this pwm channel.
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->getPeriod();
* @endcode
*/
int getValue();
/**
* Retreives the current period in use by the entire PWM module in microseconds.
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->getPeriod();
* @endcode
*/
int getPeriodUs();
/**
* Retreives the current period in use by the entire PWM module in milliseconds.
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->getPeriod();
* @endcode
*/
int getPeriod();
/**
* Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS.
*
* @param period the desired period in microseconds.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->setPeriodUs(1000); // period now is 1ms
* @endcode
*
* @note The display uses the pwm module, if you change this value the display may flicker.
*/
void setPeriodUs(int period);
int setPeriodUs(int period);
/**
* Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS.
*
* @param period the desired period in milliseconds.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->setPeriod(1); // period now is 1ms
* @endcode
*/
int setPeriod(int period);
};
#endif

View file

@ -3,40 +3,44 @@
#include "mbed.h"
#include "MicroBitComponent.h"
// Status Field flags...
#define IO_STATUS_DIGITAL_IN 0x01 // Pin is configured as a digital input, with no pull up.
#define IO_STATUS_DIGITAL_OUT 0x02 // Pin is configured as a digital output
#define IO_STATUS_ANALOG_IN 0x04 // Pin is Analog in
#define IO_STATUS_ANALOG_OUT 0x08 // Pin is Analog out (not currently possible)
#define IO_STATUS_TOUCH_IN 0x10 // Pin is a makey-makey style touch sensor
#define IO_STATUS_EVENTBUS_ENABLED 0x80 // Pin is will generate events on change
// Status Field flags...
#define IO_STATUS_DIGITAL_IN 0x01 // Pin is configured as a digital input, with no pull up.
#define IO_STATUS_DIGITAL_OUT 0x02 // Pin is configured as a digital output
#define IO_STATUS_ANALOG_IN 0x04 // Pin is Analog in
#define IO_STATUS_ANALOG_OUT 0x08 // Pin is Analog out
#define IO_STATUS_TOUCH_IN 0x10 // Pin is a makey-makey style touch sensor
#define IO_STATUS_EVENTBUS_ENABLED 0x80 // Pin is will generate events on change
//#defines for each edge connector pin
#define MICROBIT_PIN_P0 P0_3 //P0 is the left most pad (ANALOG/DIGITAL) used to be P0_3 on green board
#define MICROBIT_PIN_P1 P0_2 //P1 is the middle pad (ANALOG/DIGITAL)
#define MICROBIT_PIN_P2 P0_1 //P2 is the right most pad (ANALOG/DIGITAL) used to be P0_1 on green board
#define MICROBIT_PIN_P3 P0_4 //COL1 (ANALOG/DIGITAL)
#define MICROBIT_PIN_P4 P0_17 //BTN_A
#define MICROBIT_PIN_P5 P0_5 //COL2 (ANALOG/DIGITAL)
#define MICROBIT_PIN_P6 P0_12 //COL9
#define MICROBIT_PIN_P7 P0_11 //COL8
#define MICROBIT_PIN_P8 P0_18 //PIN 18
#define MICROBIT_PIN_P9 P0_10 //COL7
#define MICROBIT_PIN_P10 P0_6 //COL3 (ANALOG/DIGITAL)
#define MICROBIT_PIN_P11 P0_26 //BTN_B
#define MICROBIT_PIN_P12 P0_20 //PIN 20
#define MICROBIT_PIN_P13 P0_23 //SCK
#define MICROBIT_PIN_P14 P0_22 //MISO
#define MICROBIT_PIN_P15 P0_21 //MOSI
#define MICROBIT_PIN_P16 P0_16 //PIN 16
#define MICROBIT_PIN_P19 P0_0 //SCL
#define MICROBIT_PIN_P20 P0_30 //SDA
#define MICROBIT_PIN_P0 P0_3 //P0 is the left most pad (ANALOG/DIGITAL) used to be P0_3 on green board
#define MICROBIT_PIN_P1 P0_2 //P1 is the middle pad (ANALOG/DIGITAL)
#define MICROBIT_PIN_P2 P0_1 //P2 is the right most pad (ANALOG/DIGITAL) used to be P0_1 on green board
#define MICROBIT_PIN_P3 P0_4 //COL1 (ANALOG/DIGITAL)
#define MICROBIT_PIN_P4 P0_17 //BTN_A
#define MICROBIT_PIN_P5 P0_5 //COL2 (ANALOG/DIGITAL)
#define MICROBIT_PIN_P6 P0_12 //COL9
#define MICROBIT_PIN_P7 P0_11 //COL8
#define MICROBIT_PIN_P8 P0_18 //PIN 18
#define MICROBIT_PIN_P9 P0_10 //COL7
#define MICROBIT_PIN_P10 P0_6 //COL3 (ANALOG/DIGITAL)
#define MICROBIT_PIN_P11 P0_26 //BTN_B
#define MICROBIT_PIN_P12 P0_20 //PIN 20
#define MICROBIT_PIN_P13 P0_23 //SCK
#define MICROBIT_PIN_P14 P0_22 //MISO
#define MICROBIT_PIN_P15 P0_21 //MOSI
#define MICROBIT_PIN_P16 P0_16 //PIN 16
#define MICROBIT_PIN_P19 P0_0 //SCL
#define MICROBIT_PIN_P20 P0_30 //SDA
#define MICROBIT_PIN_MAX_OUTPUT 1023
#define MICROBIT_PIN_MAX_OUTPUT 1023
#define MICROBIT_PIN_MAX_SERVO_RANGE 180
#define MICROBIT_PIN_DEFAULT_SERVO_RANGE 1000
#define MICROBIT_PIN_DEFAULT_SERVO_CENTER MICROBIT_PIN_DEFAULT_SERVO_RANGE + MICROBIT_PIN_DEFAULT_SERVO_RANGE/2
/**
* Pin capabilities enum.
* Pin capabilities enum.
* Used to determine the capabilities of each Pin as some can only be digital, or can be both digital and analogue.
*/
enum PinCapability{
@ -45,7 +49,7 @@ enum PinCapability{
PIN_CAPABILITY_TOUCH = 0x04,
PIN_CAPABILITY_AD = PIN_CAPABILITY_DIGITAL | PIN_CAPABILITY_ANALOG,
PIN_CAPABILITY_ALL = PIN_CAPABILITY_DIGITAL | PIN_CAPABILITY_ANALOG | PIN_CAPABILITY_TOUCH
};
/**
@ -56,56 +60,62 @@ enum PinCapability{
class MicroBitPin : public MicroBitComponent
{
/**
* Unique, enumerated ID for this component.
* Unique, enumerated ID for this component.
* Used to track asynchronous events in the event bus.
*/
void *pin; // The mBed object looking after this pin at any point in time (may change!).
PinCapability capability;
/**
* Disconnect any attached mBed IO from this pin.
* Used only when pin changes mode (i.e. Input/Output/Analog/Digital)
*/
void disconnect();
/**
* Performs a check to ensure that the current Pin is in control of a
* DynamicPwm instance, and if it's not, allocates a new DynamicPwm instance.
*/
int obtainAnalogChannel();
public:
PinName name; // mBed pin name of this pin.
/**
* Constructor.
* Constructor.
* Create a Button representation with the given ID.
* @param id the ID of the new Pin object.
* @param name the pin name for this MicroBitPin instance to represent
* @param capability the capability of this pin.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* @endcode
*/
MicroBitPin(int id, PinName name, PinCapability capability);
/**
* Configures this IO pin as a digital output (if necessary) and sets the pin to 'value'.
* @param value 0 (LO) or 1 (HI)
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED
* if the given pin does not have digital capability.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* P0.setDigitalValue(1); // P0 is now HI
* @endcode
*/
int setDigitalValue(int value);
/**
* Configures this IO pin as a digital input (if necessary) and tests its current value.
* @return 1 if this input is high, 0 if input is LO, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* P0.getDigitalValue(); // P0 is either 0 or 1;
* @endcode
@ -120,13 +130,28 @@ class MicroBitPin : public MicroBitComponent
*/
int setAnalogValue(int value);
/**
* Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms,
* and sets the duty cycle between 0.05 and 0.1 (i.e. 5% or 10%) based on the value given to this method.
*
* A value of 180 sets the duty cycle to be 10%, and a value of 0 sets the duty cycle to be 5% by default.
*
* This range can be modified to fine tune, and also tolerate different servos.
*
* @param value the level to set on the output pin, in the range 0 - 180
* @param range which gives the span of possible values the i.e. lower and upper bounds center ± range/2 (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_RANGE)
* @param center the center point from which to calculate the lower and upper bounds (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_CENTER)
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED
* if the given pin does not have analog capability.
*/
int setServoValue(int value, int range = MICROBIT_PIN_DEFAULT_SERVO_RANGE, int center = MICROBIT_PIN_DEFAULT_SERVO_CENTER);
/**
* Configures this IO pin as an analogue input (if necessary and possible).
* @return the current analogue level on the pin, in the range 0 - 1024, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* P0.getAnalogValue(); // P0 is a value in the range of 0 - 1024
* @endcode
@ -160,35 +185,61 @@ class MicroBitPin : public MicroBitComponent
/**
* Configures this IO pin as a makey makey style touch sensor (if necessary) and tests its current debounced state.
* @return 1 if pin is touched, 0 if not, or MICROBIT_NOT_SUPPORTED if this pin does not support touch capability.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL);
* if(P0.isTouched())
* {
* uBit.display.clear();
* }
* }
* @endcode
*/
int isTouched();
/**
* Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms,
* and sets the pulse width, based on the value it is given
*
* @param pulseWidth the desired pulse width in microseconds.
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED
* if the given pin does not have analog capability.
*/
int setServoPulseUs(int pulseWidth);
/**
* Configures the PWM period of the analog output to the given value.
*
* @param period The new period for the analog output in milliseconds.
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
*/
int setAnalogPeriod(int period);
/**
* Configures the PWM period of the analog output to the given value.
*
* @param period The new period for the analog output in microseconds.
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
*/
int setAnalogPeriodUs(int period);
/**
* Retrieves the PWM period of the analog output.
*
* @return the period on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
int getAnalogPeriodUs();
/**
* Retrieves the PWM period of the analog output.
*
* @return the period on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
int getAnalogPeriod();
};
#endif

View file

@ -4,6 +4,8 @@ DynamicPwm* DynamicPwm::pwms[NO_PWMS] = { 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
* #HACK #BODGE # YUCK #MBED_SHOULD_DO_THIS
@ -11,9 +13,9 @@ uint8_t DynamicPwm::lastUsed = NO_PWMS+1; //set it to out of range i.e. 4 so we
* @param pin The pin to start running PWM on
* @param oldPin The pin to stop running PWM on
* @param channel_number The GPIOTE channel being used to drive this PWM channel
*/
*/
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)
@ -37,21 +39,20 @@ void gpiote_reinit(PinName pin, PinName oldPin, uint8_t channel_number)
__NOP();
__NOP();
__NOP();
NRF_TIMER2->CC[channel_number] = 0;
NRF_TIMER2->CC[channel_number] = 0;
}
/**
* An internal constructor used when allocating a new DynamicPwm representation
* @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.)
* @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.)
* @param period the frequency of the pwm channel in us.
*/
DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence, int period) : PwmOut(pin)
DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence) : PwmOut(pin)
{
this->flags = persistence;
this->setPeriodUs(period);
}
/**
@ -65,24 +66,23 @@ DynamicPwm::DynamicPwm(PinName pin, PwmPersistence persistence, int period) : Pw
* @endcode
*/
void DynamicPwm::redirect(PinName pin)
{
gpiote_reinit(pin, _pwm.pin, (uint8_t)_pwm.pwm);
{
gpiote_reinit(pin, _pwm.pin, (uint8_t)_pwm.pwm);
this->_pwm.pin = pin;
}
/**
* Retrieves a pointer to the first available free pwm channel - or the first one that can be reallocated.
* @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.)
* @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.)
* @param period the frequency of the pwm channel in us.
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* @endcode
*/
DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence, int period)
DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence)
{
//try to find a blank spot first
for(int i = 0; i < NO_PWMS; i++)
@ -90,31 +90,35 @@ DynamicPwm* DynamicPwm::allocate(PinName pin, PwmPersistence persistence, int pe
if(pwms[i] == NULL)
{
lastUsed = i;
pwms[i] = new DynamicPwm(pin, persistence, period);
pwms[i] = new DynamicPwm(pin, persistence);
return pwms[i];
}
}
}
//no blank spot.. try to find a transient PWM
for(int i = 0; i < NO_PWMS; i++)
int channelIterator = (lastUsed + 1 > NO_PWMS - 1) ? 0 : lastUsed + 1;
while(channelIterator != lastUsed)
{
if(pwms[i]->flags & PWM_PERSISTENCE_TRANSIENT && i != lastUsed)
if(pwms[channelIterator]->flags & PWM_PERSISTENCE_TRANSIENT)
{
lastUsed = i;
pwms[i]->flags = persistence;
pwms[i]->redirect(pin);
return pwms[i];
}
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;
}
@ -132,9 +136,9 @@ void DynamicPwm::release()
{
//free the pwm instance.
NRF_GPIOTE->CONFIG[(uint8_t) _pwm.pwm] = 0;
pwmout_free(&_pwm);
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)
@ -144,6 +148,30 @@ void DynamicPwm::release()
}
}
/**
* A lightweight wrapper around the super class' write in order to capture the value
*
* @param value the duty cycle percentage in floating point format.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate();
* pwm->write(0.5);
* @endcode
*/
int DynamicPwm::write(float value){
if(value < 0)
return MICROBIT_INVALID_PARAMETER;
PwmOut::write(value);
lastValue = value;
return MICROBIT_OK;
}
/**
* Retreives the pin name associated with this DynamicPwm instance.
*
@ -155,21 +183,92 @@ void DynamicPwm::release()
*/
PinName DynamicPwm::getPinName()
{
return _pwm.pin;
return _pwm.pin;
}
/**
* Retreives the last value that has been written to this DynamicPwm instance.
* The value is in the range 0-1024
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->getValue();
* @endcode
*/
int DynamicPwm::getValue()
{
return (float)lastValue * float(MICROBIT_PIN_MAX_OUTPUT);
}
/**
* Retreives the current period in use by the entire PWM module
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->getPeriod();
* @endcode
*/
int DynamicPwm::getPeriodUs()
{
return sharedPeriod;
}
/**
* Retreives the current period in use by the entire PWM module
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->getPeriod();
* @endcode
*/
int DynamicPwm::getPeriod()
{
return getPeriodUs() / 1000;
}
/**
* Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS.
*
* @param period the desired period in microseconds.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->setPeriodUs(1000); // period now is 1ms
* @endcode
*
* @note The display uses the pwm module, if you change this value the display may flicker.
*/
void DynamicPwm::setPeriodUs(int period)
int DynamicPwm::setPeriodUs(int 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;
return MICROBIT_OK;
}
/**
* Sets the period used by the WHOLE PWM module. Any changes to the period will AFFECT ALL CHANNELS.
*
* @param period the desired period in microseconds.
*
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range
*
* Example:
* @code
* DynamicPwm* pwm = DynamicPwm::allocate(PinName n);
* pwm->setPeriod(1); // period now is 1ms
* @endcode
*/
int DynamicPwm::setPeriod(int period)
{
return setPeriodUs(period * 1000);
}

View file

@ -2,29 +2,29 @@
#include "MicroBitPin.h"
/**
* Constructor.
* Constructor.
* Create a Button representation with the given ID.
* @param id the ID of the new Pin object.
* @param name the pin name for this MicroBitPin instance to represent
* @param capability the capability of this pin, can it only be digital? can it only be analog? can it be both?
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* @endcode
*/
MicroBitPin::MicroBitPin(int id, PinName name, PinCapability capability)
{
//set mandatory attributes
MicroBitPin::MicroBitPin(int id, PinName name, PinCapability capability)
{
//set mandatory attributes
this->id = id;
this->name = name;
this->capability = capability;
// Power up in a disconnected, low power state.
// If we're unused, this is how it will stay...
this->status = 0x00;
this->pin = NULL;
}
/**
@ -45,16 +45,16 @@ void MicroBitPin::disconnect()
NRF_ADC->ENABLE = ADC_ENABLE_ENABLE_Disabled; // forcibly disable the ADC - BUG in mbed....
delete ((AnalogIn *)pin);
}
if (status & IO_STATUS_ANALOG_OUT)
{
if(((DynamicPwm *)pin)->getPinName() == name)
((DynamicPwm *)pin)->release();
}
((DynamicPwm *)pin)->release();
}
if (status & IO_STATUS_TOUCH_IN)
delete ((MicroBitButton *)pin);
this->pin = NULL;
this->status = status & IO_STATUS_EVENTBUS_ENABLED; //retain event bus status
}
@ -62,9 +62,9 @@ void MicroBitPin::disconnect()
/**
* Configures this IO pin as a digital output (if necessary) and sets the pin to 'value'.
* @param value 0 (LO) or 1 (HI)
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* P0.setDigitalValue(1); // P0 is now HI
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED
@ -80,14 +80,14 @@ int MicroBitPin::setDigitalValue(int value)
// Ensure we have a valid value.
if (value < 0 || value > 1)
return MICROBIT_INVALID_PARAMETER;
// Move into a Digital input state if necessary.
if (!(status & IO_STATUS_DIGITAL_OUT)){
disconnect();
disconnect();
pin = new DigitalOut(name);
status |= IO_STATUS_DIGITAL_OUT;
}
// Write the value.
((DigitalOut *)pin)->write(value);
@ -97,9 +97,9 @@ int MicroBitPin::setDigitalValue(int value)
/**
* Configures this IO pin as a digital input (if necessary) and tests its current value.
* @return 1 if this input is high, 0 if input is LO, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* P0.getDigitalValue(); // P0 is either 0 or 1;
* @endcode
@ -109,20 +109,32 @@ int MicroBitPin::getDigitalValue()
//check if this pin has a digital mode...
if(!(PIN_CAPABILITY_DIGITAL & capability))
return MICROBIT_NOT_SUPPORTED;
// Move into a Digital input state if necessary.
if (!(status & IO_STATUS_DIGITAL_IN)){
disconnect();
pin = new DigitalIn(name,PullDown);
disconnect();
pin = new DigitalIn(name,PullDown);
status |= IO_STATUS_DIGITAL_IN;
}
return ((DigitalIn *)pin)->read();
}
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);
status |= IO_STATUS_ANALOG_OUT;
}
return MICROBIT_OK;
}
/**
* Configures this IO pin as an analog/pwm output, and change the output value to the given level.
* @param value the level to set on the output pin, in the range 0 - 1024
* @param value the level to set on the output pin, in the range 0 - 1023
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED
* if the given pin does not have analog capability.
*/
@ -131,34 +143,65 @@ int MicroBitPin::setAnalogValue(int value)
//check if this pin has an analogue mode...
if(!(PIN_CAPABILITY_ANALOG & capability))
return MICROBIT_NOT_SUPPORTED;
//sanitise the brightness level
//sanitise the level value
if(value < 0 || value > MICROBIT_PIN_MAX_OUTPUT)
return MICROBIT_INVALID_PARAMETER;
float level = (float)value / float(MICROBIT_PIN_MAX_OUTPUT);
// 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);
status |= IO_STATUS_ANALOG_OUT;
}
//perform a write with an extra check! :)
if(((DynamicPwm *)pin)->getPinName() == name)
((DynamicPwm *)pin)->write(level);
//obtain use of the DynamicPwm instance, if it has changed / configure if we do not have one
if(obtainAnalogChannel() == MICROBIT_OK)
return ((DynamicPwm *)pin)->write(level);
return MICROBIT_OK;
}
/**
* Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms,
* and sets the duty cycle between 0.05 and 0.1 (i.e. 5% or 10%) based on the value given to this method.
*
* A value of 180 sets the duty cycle to be 10%, and a value of 0 sets the duty cycle to be 5% by default.
*
* This range can be modified to fine tune, and also tolerate different servos.
*
* @param value the level to set on the output pin, in the range 0 - 180
* @param range which gives the span of possible values the i.e. lower and upper bounds center ± range/2 (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_RANGE)
* @param center the center point from which to calculate the lower and upper bounds (Defaults to: MICROBIT_PIN_DEFAULT_SERVO_CENTER)
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED
* if the given pin does not have analog capability.
*/
int MicroBitPin::setServoValue(int value, int range, int center)
{
//check if this pin has an analogue mode...
if(!(PIN_CAPABILITY_ANALOG & capability))
return MICROBIT_NOT_SUPPORTED;
//sanitise the servo level
if(value < 0 || range < 1 || center < 1)
return MICROBIT_INVALID_PARAMETER;
//clip - just in case
if(value > MICROBIT_PIN_MAX_SERVO_RANGE)
value = MICROBIT_PIN_MAX_SERVO_RANGE;
//calculate the lower bound based on the midpoint
int lower = (center - (range / 2)) * 1000;
value = value * 1000;
//add the percentage of the range based on the value between 0 and 180
int scaled = lower + (range * (value / MICROBIT_PIN_MAX_SERVO_RANGE));
return setServoPulseUs(scaled / 1000);
}
/**
* Configures this IO pin as an analogue input (if necessary and possible).
* @return the current analogue level on the pin, in the range 0 - 1024, or MICROBIT_NOT_SUPPORTED if the given pin does not have analog capability.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_BOTH);
* P0.getAnalogValue(); // P0 is a value in the range of 0 - 1024
* @endcode
@ -168,14 +211,14 @@ int MicroBitPin::getAnalogValue()
//check if this pin has an analogue mode...
if(!(PIN_CAPABILITY_ANALOG & capability))
return MICROBIT_NOT_SUPPORTED;
// Move into an analogue input state if necessary.
if (!(status & IO_STATUS_ANALOG_IN)){
disconnect();
disconnect();
pin = new AnalogIn(name);
status |= IO_STATUS_ANALOG_IN;
}
//perform a read!
return ((AnalogIn *)pin)->read_u16();
}
@ -219,14 +262,14 @@ int MicroBitPin::isAnalog()
/**
* Configures this IO pin as a makey makey style touch sensor (if necessary) and tests its current debounced state.
* @return 1 if pin is touched, 0 if not, or MICROBIT_NOT_SUPPORTED if this pin does not support touch capability.
*
*
* Example:
* @code
* @code
* MicroBitPin P0(MICROBIT_ID_IO_P0, MICROBIT_PIN_P0, PIN_CAPABILITY_ALL);
* if(P0.isTouched())
* {
* uBit.display.clear();
* }
* }
* @endcode
*/
int MicroBitPin::isTouched()
@ -234,22 +277,53 @@ int MicroBitPin::isTouched()
//check if this pin has a touch mode...
if(!(PIN_CAPABILITY_TOUCH & capability))
return MICROBIT_NOT_SUPPORTED;
// Move into a touch input state if necessary.
if (!(status & IO_STATUS_TOUCH_IN)){
disconnect();
pin = new MicroBitButton(id, name);
disconnect();
pin = new MicroBitButton(id, name);
status |= IO_STATUS_TOUCH_IN;
}
return ((MicroBitButton *)pin)->isPressed();
}
/**
* Configures this IO pin as an analog/pwm output if it isn't already, configures the period to be 20ms,
* and sets the pulse width, based on the value it is given
*
* @param pulseWidth the desired pulse width in microseconds.
* @return MICROBIT_OK on success, MICROBIT_INVALID_PARAMETER if value is out of range, or MICROBIT_NOT_SUPPORTED
* if the given pin does not have analog capability.
*/
int MicroBitPin::setServoPulseUs(int pulseWidth)
{
//check if this pin has an analogue mode...
if(!(PIN_CAPABILITY_ANALOG & capability))
return MICROBIT_NOT_SUPPORTED;
//sanitise the pulse width
if(pulseWidth < 0)
return MICROBIT_INVALID_PARAMETER;
//Check we still have the control over the DynamicPwm instance
if(obtainAnalogChannel() == MICROBIT_OK)
{
//check if the period is set to 20ms
if(((DynamicPwm *)pin)->getPeriodUs() != MICROBIT_DEFAULT_PWM_PERIOD)
((DynamicPwm *)pin)->setPeriodUs(MICROBIT_DEFAULT_PWM_PERIOD);
((DynamicPwm *)pin)->pulsewidth_us(pulseWidth);
}
return MICROBIT_OK;
}
/**
* Configures the PWM period of the analog output to the given value.
*
* @param period The new period for the analog output in microseconds.
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
int MicroBitPin::setAnalogPeriodUs(int period)
@ -257,18 +331,42 @@ int MicroBitPin::setAnalogPeriodUs(int period)
if (!(status & IO_STATUS_ANALOG_OUT))
return MICROBIT_NOT_SUPPORTED;
((DynamicPwm *)pin)->setPeriodUs(period);
return MICROBIT_OK;
return ((DynamicPwm *)pin)->setPeriodUs(period);
}
/**
* Configures the PWM period of the analog output to the given value.
*
* @param period The new period for the analog output in microseconds.
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* @return MICROBIT_OK on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
*/
int MicroBitPin::setAnalogPeriod(int period)
{
return setAnalogPeriodUs(period*1000);
}
/**
* Retrieves the PWM period of the analog output.
*
* @return the period on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
int MicroBitPin::getAnalogPeriodUs()
{
if (!(status & IO_STATUS_ANALOG_OUT))
return MICROBIT_NOT_SUPPORTED;
return ((DynamicPwm *)pin)->getPeriodUs();
}
/**
* Retrieves the PWM period of the analog output.
*
* @return the period on success, or MICROBIT_NOT_SUPPORTED if the
* given pin is not configured as an analog output.
*/
int MicroBitPin::getAnalogPeriod()
{
return getAnalogPeriodUs()/1000;
}

View file

@ -14,7 +14,7 @@
#include "ble.h"
/*
/*
* Return to our predefined compiler settings.
*/
#if !defined(__arm)
@ -37,8 +37,8 @@ const char* MICROBIT_BLE_SOFTWARE_VERSION = NULL;
/*
* Many of the mbed interfaces we need to use only support callbacks to plain C functions, rather than C++ methods.
* So, we maintain a pointer to the MicroBitBLEManager that's in use. Ths way, we can still access resources on the micro:bit
* whilst keeping the code modular.
* So, we maintain a pointer to the MicroBitBLEManager that's in use. Ths way, we can still access resources on the micro:bit
* whilst keeping the code modular.
*/
static MicroBitBLEManager *manager = NULL;
@ -73,15 +73,15 @@ static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager
}
/**
* Constructor.
* Constructor.
*
* Configure and manage the micro:bit's Bluetooth Low Energy (BLE) stack.
* Note that the BLE stack *cannot* be brought up in a static context.
* (the software simply hangs or corrupts itself).
* Hence, we bring it up in an explicit init() method, rather than in the constructor.
*/
MicroBitBLEManager::MicroBitBLEManager()
{
MicroBitBLEManager::MicroBitBLEManager()
{
manager = this;
this->ble = NULL;
this->pairingStatus = 0;
@ -93,53 +93,53 @@ MicroBitBLEManager::MicroBitBLEManager()
* this callback to restart advertising.
*/
void MicroBitBLEManager::onDisconnectionCallback()
{
{
if(ble)
ble->startAdvertising();
ble->startAdvertising();
}
/**
* Post constructor initialisation method.
* After *MUCH* pain, it's noted that the BLE stack can't be brought up in a
* After *MUCH* pain, it's noted that the BLE stack can't be brought up in a
* static context, so we bring it up here rather than in the constructor.
* n.b. This method *must* be called in main() or later, not before.
*
* Example:
* @code
* @code
* uBit.init();
* @endcode
*/
void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumber)
{
{
ManagedString BLEName("BBC micro:bit");
this->deviceName = deviceName;
// Start the BLE stack.
// Start the BLE stack.
ble = new BLEDevice();
ble->init();
// automatically restart advertising after a device disconnects.
ble->onDisconnection(bleDisconnectionCallback);
// configure the stack to hold on to CPU during critical timing events.
// mbed-classic performs __disabe_irq calls in its timers, which can cause MIC failures
// on secure BLE channels.
// configure the stack to hold on to CPU during critical timing events.
// mbed-classic performs __disabe_irq calls in its timers, which can cause MIC failures
// on secure BLE channels.
ble_common_opt_radio_cpu_mutex_t opt;
opt.enable = 1;
sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt);
sd_ble_opt_set(BLE_COMMON_OPT_RADIO_CPU_MUTEX, (const ble_opt_t *)&opt);
#if CONFIG_ENABLED(MICROBIT_BLE_PRIVATE_ADDRESSES)
// Configure for private addresses, so kids' behaviour can't be easily tracked.
ble->setAddress(Gap::ADDR_TYPE_RANDOM_PRIVATE_RESOLVABLE, NULL);
#endif
#endif
// Setup our security requirements.
ble->securityManager().onPasskeyDisplay(passkeyDisplayCallback);
ble->securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback);
ble->securityManager().init(MICROBIT_BLE_ENABLE_BONDING, MICROBIT_BLE_REQUIRE_MITM, SecurityManager::IO_CAPS_DISPLAY_ONLY);
// Bring up any configured auxiliary services.
// Bring up any configured auxiliary services.
#if CONFIG_ENABLED(MICROBIT_BLE_DFU_SERVICE)
new MicroBitDFUService(*ble);
#endif
@ -150,29 +150,29 @@ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumb
#if CONFIG_ENABLED(MICROBIT_BLE_EVENT_SERVICE)
new MicroBitEventService(*ble);
#endif
#if CONFIG_ENABLED(MICROBIT_BLE_LED_SERVICE)
#endif
#if CONFIG_ENABLED(MICROBIT_BLE_LED_SERVICE)
new MicroBitLEDService(*ble);
#endif
#if CONFIG_ENABLED(MICROBIT_BLE_ACCELEROMETER_SERVICE)
#if CONFIG_ENABLED(MICROBIT_BLE_ACCELEROMETER_SERVICE)
new MicroBitAccelerometerService(*ble);
#endif
#if CONFIG_ENABLED(MICROBIT_BLE_MAGNETOMETER_SERVICE)
#if CONFIG_ENABLED(MICROBIT_BLE_MAGNETOMETER_SERVICE)
new MicroBitMagnetometerService(*ble);
#endif
#if CONFIG_ENABLED(MICROBIT_BLE_BUTTON_SERVICE)
#if CONFIG_ENABLED(MICROBIT_BLE_BUTTON_SERVICE)
new MicroBitButtonService(*ble);
#endif
#if CONFIG_ENABLED(MICROBIT_BLE_IO_PIN_SERVICE)
#if CONFIG_ENABLED(MICROBIT_BLE_IO_PIN_SERVICE)
new MicroBitIOPinService(*ble);
#endif
#if CONFIG_ENABLED(MICROBIT_BLE_TEMPERATURE_SERVICE)
#if CONFIG_ENABLED(MICROBIT_BLE_TEMPERATURE_SERVICE)
new MicroBitTemperatureService(*ble);
#endif
@ -189,7 +189,7 @@ void MicroBitBLEManager::init(ManagedString deviceName, ManagedString serialNumb
ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length());
ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
ble->setAdvertisingInterval(200);
ble->startAdvertising();
ble->startAdvertising();
}
/**
@ -219,7 +219,7 @@ void MicroBitBLEManager::pairingComplete(bool success)
* of the micro:bit in cases where BLE is disabled during normal operation.
*/
void MicroBitBLEManager::pairingMode(MicroBitDisplay &display)
{
{
ManagedString namePrefix("BBC micro:bit [");
ManagedString namePostfix("]");
ManagedString BLEName = namePrefix + deviceName + namePostfix;
@ -237,7 +237,7 @@ void MicroBitBLEManager::pairingMode(MicroBitDisplay &display)
ble->accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, (uint8_t *)BLEName.toCharArray(), BLEName.length());
ble->setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
ble->setAdvertisingInterval(200);
ble->startAdvertising();
ble->startAdvertising();
// Stop any running animations on the display
display.stopAnimation();
@ -336,6 +336,5 @@ void MicroBitBLEManager::showNameHistogram(MicroBitDisplay &display)
for (int j=0; j<h+1; j++)
display.image.setPixelValue(MICROBIT_DFU_HISTOGRAM_WIDTH-i-1, MICROBIT_DFU_HISTOGRAM_HEIGHT-j-1, 255);
}
}
}