-
Notifications
You must be signed in to change notification settings - Fork 39
Motate::Pin

To manipulate GPIO (General-Purpose Input/Output) pins, you use Motate::Pin<> objects.
- Creating Motate::Pin (and subclass) objects
-
Motate::Pin<pin_number> Motate::InputPin<pin_number>Motate::OutputPin<pin_number>-
Motate::PWMPin<pin_number> -
Motate::IRQPin<pin_number> - Setting up and handling pin interrupts
- Defining and using
Motate::Pinobjects
To use Motate::Pin objects include the MotatePins.h header, and declare file-global Motate::Pin objects. Then they will then be available for use in functions.
#include "MotatePins.h"
Motate::Pin<9> led1 {kOutput};
Motate::OutputPin<10> led2;
Motate::InputPin<11> button {kPullUp};
void loop() {
if (button) {
led1 = true;
led2 = false;
} else {
led1 = false;
led2 = true;
}
}
Motate::Pin<pin_number> is a type in C++, like int and char. This means that, Motate::Pin<0> and Motate::Pin<1> are distinct types, and you can not cast from one Motate::Pin<pin_number> type to another.
Since Motate::Pin and subclases are all representing specific hardware objects, it is designed to be impossible to change them at run time. This also means it's impossible to create pin objects, pass pin objects as parameters in functions/methods, etc. See Defining and using Motate::Pin objects for more information.
Motate::Pin<pin_number> pin_name {PinMode, PinOptions};
Motate::Pin<pin_number> pin_name {PinMode}; // PinOptions defaults to kNormal
Motate::Pin<pin_number> pin_name; // Not recommended - doesn't fully configure the pin-
pin_numberis the name of a typedef, and must be a compile-time constant integer.- It is recommended to use the
pin_numbertypedef in order to support platforms that have more than 250 pins in the future. Currenty, however, any value that the compiler can convert toconst int8_twill work as apin_number. - It is better to use a
pin_numbervariable placed before the pin declaration or in a header file than to use a#define.
// Both of the following will be compiled away: pin_number kRelayPinNumber = 13; // GOOD #define RELAY_PIN_NUMBER 13 // Not as good -- avoid this // Later, the pin_number can be used as follows: Motate::Pin<kRelayPinNumber> relayPin {kOutput};
- As shown above, the naming convention is to use
kCamelCaseNamePinNumberfor the pin number, andcamelCasePin(with the first letter lowercased) for the pin object itself.
- It is recommended to use the
-
PinModeandPinOptionsare both optional -- but since this is C++ you have to have aPinModevalue to provide aPinOptionsvalue. (PinModeconstantkUnchangedis useful for the case where you're rather not change thePinMode.) -
If
PinModeis either missing orkUnchangedthen the pin registers will not be setup. They can, however, be setup at runtime with a call topin_name.setMode(PinMode)with the samePinModeoptions. -
Note: See the Defining and using
Motate::Pinobjects in projects section for imortant information about where to defineMotate::Pinobjects as well as how to composeMotate::Pinobjects into other objects.
void pin_object.setMode(PinMode)- Configures the pin with the given PinMode.
- The
PinModeconstants that can be used during pin initilization or thesetMode(PinMode)method are as follows:-
kUnchanged- Do not change the regiseters of the pin during initilization. Useful for creating an alias object of a pin that may have already been created elsewhere. -
kOutput- The pin will be set up as an output. Any clocks or peripheral switches that are no longer needed when the pin goes from Input to Output will be disabled automatically. -
kInput- The pin will be set up as an input. Any clocks or peripheral switches that need to be enabled for input will be enabled.
-
void pin_object.setOptions(PinOptions)- Configures the secondary options of the pin.
- The
PinOptionsconstants that can be used as bit masks during pin initilization or thesetOptions(PinOptions)method are as follows:-
kNormal- Do not change anything. -
kTotem- An alias ofkNormal. -
kPullUp- Enable the pull-up resister of the pin (if available).- If the pin doesn't have a pull-up, then this function will do nothing.
- If the pin is configured as an output, then this function will do nothing.
-
void pin_object.operator=(const bool value)
void pin_object.write(const bool value)-
Set the pin high if the provided value is
true, and low if the value isfalse. -
These two operations are equivalent:
pin_object = true; pin_object.write(true);
-
Note: Using objects in an if statement when both
operator=(bool)andoperator bool()are defined can be dangerous, since you could accidentally set the value when you mean to compare against it. For this reason,operator=(bool)returnsvoid, making it impossible to accidentally sayif (pin=true) {...}.
void pin_object.set()- Set the pin to a high value. Equivaluent to
pin_object.write(true).
void pin_object.clear()- Clears the pin to a low value. Equivaluent to
pin_object.write(false).
void pin_object.toggle()- Inverts the value of the pin - high-to-low or low-to-high. Equivaluent to
pin = !pin, except thattoggle()is be optimized to take advantage of any available hardware pin-toggling facilities.
operator bool()
uint32_t pin_object.get()
uint32_t pin_object.getInputValue()
uint32_t pin_object.getOutputValue()- The functions provide various ways to retrieve the pins value.
-
Some architectures make a destinction between input (read) pin value and output (set) pin value. These often depend on the Input or Output status of the pin.
-
operator boolis a syntactic shortcut forget()-- allowing the pin to be used directly in an if statement or other boolean context.- See the note on
operator=(bool)about accidental assignment in if statements being prevented.
if (button) { ... } // Same as: if (button == true) { ... } // Same as: if (button.get()) { ... }
- See the note on
-
get()will both attempt to determine the input/output status of the pin and return eithergetInputValue()orgetOutputValue()appropriately.- On some processors, this may introduce a significant performance penalty for high-performance projects. For these cases, you should either define the pin as a
Motate::OutputPinorMotate::InputPinor callgetInputValue()orgetOutputValue()directly when you can assume that the pin is an input or output. -
Motate::OutputPin<N>, hasget()changed to always be the same asgetOutputValue(). -
Motate::InputPin<N>, hasget()changed to always be the same asgetInputValue().
- On some processors, this may introduce a significant performance penalty for high-performance projects. For these cases, you should either define the pin as a
-
getInputValue()will get the input value of the pin.- The value is only guaranteed to be valid for pins that are currently configured as inputs.
-
getOutputValue()will get the output value of the pin, of the value it was last set to.- The value is only guaranteed to be valid for pins that are currently configured to be outputs.
-
bool pin_object.isNull()-
You can construct a
Motate::Pinobject with with any validint8_tvalue. If a pin specialization with that pin number hasn't been created for the current board and architecture, it will use the default specialization, which is the same as theNullPin, which has apin_numberof -1. -
The default specialization, a.k.a. the
NullPin, has the same interface as a normal pin, but with empty or minimal method definitions to satisfy the compiler. These are designed to completely go away in the optimizer. -
If the pin is a valid pin (IOW, it has a valid specialization),
isNull()will returnfalse. Otherwise,isNull()will returntrue. -
If you put a call to
!pin_object.isNull()in an if statement, the value will be known at compile-time, and it will be the same asif (0)orif (1), which will either be deleted or the contents will be kept and the branch optimized away. For example:Motate::OutputPin<kPinThatDoesntExist> fakePin; Motate::OutputPin<kPinThatDoesExist> realPin; if (!fakePin.isNull()) { // code here will never make it to the final binary } if (!realPin.isNull()) { // code here will *always* make it to the final binary // but the "if" branch will be removed }
Motate::InputPin<pin_number> pin_name;
Motate::InputPin<pin_number> pin_name {PinOptions};-
Motate::InputPinobjects are subclasses ofMotate::Pinthat has the PinMode set tokInput. There are a few notable changes:-
inputpin_object.get()will specifically return thepin_object.getInputValue(). - Functions that set the value have been effectively removed, including:
pin_object.write(),pin_object.set(),pin_object.clear(),pin_object.toggle(), andpin_object.operator=(bool).
-
- It is recommended to use a
Motate::InputPinwhen you know that the pin will always be used as an input.
#Motate::OutputPin<pin_number>
Motate::OutputPin<pin_number> pin_name;
Motate::OutputPin<pin_number> pin_name {PinOptions};-
Motate::OutputPinobjects are subclasses ofMotate::Pinthat has the PinMode set tokOutput. There are a few notable changes:-
outputpin_object.get()will specifically return thepin_object.getOutputValue().
-
- It is recommended to use a
Motate::OutputPinwhen you know that the pin will always be used as an output.
Motate::PWMPin<pin_number> pin_name {const uint32_t frequency};-
Motate::PWMPinobjects are subclasses of bothMotate::PinandMotate::Timerthat provides a convenient interface to configure a PWM output on the pin.-
Motate::PWMPinobjects are always outputs. -
frequencyis the master frequency of the output in hz.
void operator=(const float value) void pwmpin_object.write(const float value)
- The object can be "set" as a floating-point value, from
0.0to1.0, aspwmpin_object = value, orpwmpin_object.write(value), to set the duty cycle. - The value will be scaled linearly from the maximum duty cycle (
value=1.0) to off (value=0.0).
uint16_t pwmpin_object.getTopValue() void pwmpin_object.writeRaw(const uint16_t duty)
- If you wish to avoid floating point math in you project, you can obtain the maximum duty-cycle value with
getTopValue(), and then set the duty cycle withwriteRaw(const uint16_t duty)with an integer value between0for off andgetTopValue()for 100% duty cycle. - The values might be
uint16_toruint32_t, depending on if the resolution of the timer for that pin on that architecture is higher than 16-bit.
bool pwmpin_object.canPWM()- Not all pins are capable of PWM output. Since some architectures might have different pins that are capable of PWM than others, a project that runs on mutliple architectures may wish to adapt to those different capabilities.
canPWM()will return a constant oftrueif the pin is capable of being a PWM, andfalseif it's not. - A project can be compile-time optimized with
pwmpin_object.canPWM()in the same way it is withpin_object.isNull().
-
Motate::IRQPin<pin_number> pin_name;
Motate::IRQPin<pin_number> pin_name {PinOptions};-
Motate::IRQPinobjects are always inputs. They accept the normal compliment ofPinOptions.
void pin_object.setInterrupts(const uint32_t interrupts)-
Turn on or off the interrupts for this pin, if they are supported by the architecture and for this specific pin.
-
Use a bit-mask of the options available to set the interrupts value. For example:
pin_object.setInterrupts(kPinInterruptOnChange|kPinInterruptPriorityHigh);
-
kPinInterruptsOff- Disable interrupts on this pin. -
kPinInterruptOnChange- Enable interrupts on pin level changes. -
kPinInterruptOnRisingEdge- Enable interrupts on low-to-high level. -
kPinInterruptOnFallingEdge- Enable interrupts on high-to-low level. -
kPinInterruptOnLowLevel- Enable interrupts on a low-level of the pin.- Warning: This interrupt will continue to fire as long as it is enabled and the pin has a low level, effectively preventing the processor from running other code.
-
kPinInterruptOnHighLevel- Enable interrupts on a high-level of the pin.- Warning: This interrupt will continue to fire as long as it is enabled and the pin has a high level, effectively preventing the processor from running other code.
-
kPinInterruptOnSoftwareTrigger- This is a special setting to enable the interrupt hardware (timers, etc), but not set a specific hardware interrupt trigger. -
Interrupt priority level options:
kPinInterruptPriorityHighest kPinInterruptPriorityHigh kPinInterruptPriorityMedium kPinInterruptPriorityLow kPinInterruptPriorityLowest
- On architectures that support it, these set the level of priority of the interrupt. Higher priority interrupts will interrupt lower priority interrupts.
- On architectures that don't support priority levels, these options have no effect.
- On most architectures, this changes the level of interrupt priority for all interrupts that share that port.
- On some architectures there are fewer levels, so
kPinInterruptPriorityHighestandkPinInterruptPriorityHighmay indicate the same priority, for example.
MOTATE_PIN_INTERRUPT(pin_number) {
// Insert your code here
}- When an interrupt for
pin_numberis handled, the user-defined function is executed. - Care should be taken that this does not interfere with other code that may be running at a lower priority level, such as normal code (which runs at a level below the "lowest"), since it will literally interrupt that code.
- There is no return value. When this function ends (or returns) then the code that was interrupted will be handled.
- For
HighLevelorLowLevelinterrupts, it is recommended that you callsetInterrupts()to change or disable the interrupts, or this function will start again immediately after it exits as long as the pin is held at the high/low trigger level.
Some architectures have a limited number of pins that can generate pin-change interrupts. For this reason, there is a special subclass of Motate::Pin for those pins that can: Motate::IRQPin.
Since Motate::Pin objects are static stateless objects that provide a convenient interface to hardware registers, it's problematic to define mutiple objects for the same pin.
For this reason, along with performance considerations, Motate::Pin objects are designed to be impossible to be fundamentally changed at run time -- you cannot make a Motate::Pin object represent a different physical pin, for example. This also means it's impossible to create pin objects, pass pin objects as parameters in functions/methods, loop over pin objects by pin number, etc.
If the Motate::Pin object is to be directly used from within one file only, then you can define the object in that file (outside of any function definitions) and then you can use it in functions. You can then refer to those functions from other files as you would normally - by declaring the functions in a header and including that header in any .cpp file that uses those functions.
Motate::Pin<kRelayPinNumber> relayPin {kOutput};
void flipTheRelay() {
relayPin.toggle();
}It's not ideal, but if the Motate::Pin object is to be directly used from within multiple files, then you must define the object as extern (and without the initilizer) in a header file that is included in all the places that that the object will be used. Then, in one compiled file you place the definition without the extern.
// -- File "relay.h"
// In the header file
pin_number kRelayPinNumber = 13; // It's best to have the number be set in one place
extern Motate::Pin<kRelayPinNumber> relayPin;
// ^^ extern is vital here
// Do NOT include the {} intializer here ^^^
// -- File "relay.cpp"
// In exactly ONE .cpp file, we actually define and initialize it
// At the top of the file, with the includes, we pull in the header
#include "relay.h"
// Now we actually define it
// Note that "extern" is missing, and we have the {initializer}
Motate::Pin<kRelayPinNumber> relayPin {kOutput};
// And we can, of course, use the relayPin in this file as well:
void initRelay() {
relayPin.clear();
}
// -- File "controller.cpp"
// Now we can use the pin from any .cpp file that includes "relay.h"
#include "relay.h"
// later...
void turnTheRobot() {
// ...
relayPin.toggle();
// ...
}If one or more Motate::Pin objects are to be part of another object type, care must be taken to ensure that that containing object represents a distinct type. All objects of that type must be given the same care to not be multiply defined as Motate::Pin objects are.
There are three primary usage patterns: Class-as-namespace, Singleton, or Templated Class.
Note: In this case the term "Class" is used, but in the code examples we use structs. In C++, a struct is simply a class where the default access is public, where with a class the default is private. There's little value in using access restrictions in bare-metal projects.
-
Class-as-namespace
- In the Class-as-namespace pattern, all of the members and methods of the class are
static, and no actual objects of the class are ever instantiated. - In this pattern, the class is merely providing a "namespace" to collect the static methods and members into. At no point are objects of the class ever instatiated.
- Warning:** If an all-static class uses other all-static classes or is nested in another class (all-static or not), then you may have issues with the optimizer incorrectly "garbage collecting" methods. Use this method sparingly.
struct SharedButton { static Motate::Pin<kSharedRedButtonPinNumber> redButtonPin {kInput}; static Motate::Pin<kSharedBlueButtonPinNumber> blueButtonPin {kInput}; static bool areBothButtonsPushed() { return (redButtonPin && blueButtonPin); }; }; void main() { if (SharedButton::areBothButtonsPushed()) { //... } }
- In the Class-as-namespace pattern, all of the members and methods of the class are
-
Singleton
- In the Singleton pattern, you construct an object that you will only ever create one of.
-
Note to C++ purists: You don't have to make this type a singleton is the strict sense of making the constructor private and creating a static factory method. In fact, that will be rather difficult to do since
Motate::Pinand many other Motate objects prevent copying. You could with heavy usage ofconstexprand move semantics, but the value is dubious.
-
Note to C++ purists: You don't have to make this type a singleton is the strict sense of making the constructor private and creating a static factory method. In fact, that will be rather difficult to do since
- With this method, the singleton is the only object of that type, and contains a compile-time selected set of
Motate::Pinobjects.
struct SharedButton_t { Motate::InputPin<kSharedRedButtonPinNumber> redButtonPin; Motate::Pin<kSharedBlueButtonPinNumber> blueButtonPin; // NO {initilizer list here}! // The initilizer has to placed in the constructor. // Note that we don't need an initilizer for InputPin redButtonPin. SharedButton_t() : blueButtonPin {kInput} {}; // This constructor has no body. // When the constructor has no body, like this one, we could make it constexpr. bool areBothButtonsPushed() { return (redButtonPin && blueButtonPin); }; }; SharedButton_t shared_button; // We create one and ONLY ONE of these. void main() { if (shared_button.areBothButtonsPushed()) { //... } }
- In the Singleton pattern, you construct an object that you will only ever create one of.
-
Templated Class
- With the Templated Class pattern, you use the pin_numbers as template parameters of the new type.
- This works for templated types other than
Motate::Pinas well. Just use the relevant template parameters of those types as parameters to the new type.
- This works for templated types other than
- All of one templated class with the same parameters should be considered singletons. In the exmple below,
stepper0andstepper1are both singletons that use different pins. - Care should be taken to not use the same pin in two or more different objects - the behavior could become unpredicatable.
- If you wish to intentionally use the same pin in multiple singletons, you should define it outside the singleton class and simply utilize the pin as any other global. For complex cases, create a second templated type that is utilized as a group.
- If you wish to intentially define the same pin in multiple singletons - don't.
template<pin_number step_pin_number, pin_number dir_pin_number, pin_number enable_pin_number> struct stepper_t { Motate::Pin<step_pin_number> step_pin; // NO initilizers here! Motate::Pin<dir_pin_number> dir_pin; Motate::Pin<enable_pin_number> enable_pin; // We need to intialize them in the constructor of the object stepper_t() : step_pin {kOutput}, dir_pin {kOutput}, enable_pin {kOutput} { // intialization here } // No we can create methods that use those pins void enable(bool doEnable) { enable_pin.write(!doEnable); } // functions to step and set direction as well }; // These three variables can be set in a separate header: pin_number kMotor0StepPinNumber = 0; pin_number kMotor0DirPinNumber = 1; pin_number kMotor0EnablePinNumber = 2; // Now we can create a stepper object and specify the pin numbers // as part of the template parameters: stepper_t<kMotor0StepPinNumber, kMotor0DirPinNumber, kMotor0EnablePinNumber> stepper0; // Assuming kMotor1StepPinNumber, kMotor1DirPinNumber, // and kMotor1EnablePinNumber are already setup somewhere else as well. stepper_t<kMotor1StepPinNumber, kMotor1DirPinNumber, kMotor1EnablePinNumber> stepper1; // In a function, we can now use them: stepper0.enable(true); stepper1.enable(false);
- With the Templated Class pattern, you use the pin_numbers as template parameters of the new type.