Deciding on queue contents

When using queues as a way of passing commands to different parts of the system, it is important to think about what the queue should actually hold, instead of what might be coming across the wire in a physical sense. Even though a queue might be used to hold payloads from a datastream with header and footer information, the actual contents of the queue will usually only contain the parsed payload, rather than the entire message.

Using this approach allows more flexibility in the future to retarget the queue to work over other physical layers.

Since LedCmdExecution() will be operating primarily on the iPWM pointers to interface with the LEDs, it is convenient for the queue to hold a data type that can be used directly by iPWM.   

The iPWM definition from Chapter13/Inc/iPWM.h is as follows:

 typedef void (*iPwmDutyCycleFunc)( float DutyCycle );

typedef struct
{
const iPwmDutyCycleFunc SetDutyCycle;
}iPWM;

This struct only (currently) consists of a single function pointer: iPwmDutyCycleFunciPwmDutyCycleFunc is defined as a constant pointer—after the initialization of the iPWM struct, the pointer can never be changed. This helps guarantee the pointer won't be overwritten, so constantly checking to ensure it isn't NULL won't be necessary. 

Wrapping the function pointer in a struct such as iPWM provides the flexibility of adding additional functions while keeping refactoring to a minimum. We'll be able to pass a single pointer to the iPWM struct to functions, rather than individual function pointers.

If you are creating an interface definition that will be shared with other developers, it is important to be very careful to coordinate and communicate changes among your team!

The DutyCycle argument is defined as float, which makes it easy to keep the interface consistent when interfacing with hardware that has different underlying resolutions. In our implementation, the MCU's timer (TIM) peripherals will be configured to have a 16-bit resolution, but the actual code interfacing to iPWM doesn't need to be concerned with the available resolution; it can simply map the desired output from 0.00 (off)  to 100.00 (on).

For most applications, int32_t would have been preferred over float since it has a consistent representation and is easier to serialize. float is used here to make it easier to see the differences in the data model versus communication. Also, most people tend to think of PWM as a percentage, which maps naturally to float

There are two main considerations when deciding on what data LedCmd contains: 

  •  ledCmdExecutor will be dealing with iPWM directly, so it makes sense to store floats in LedCmd.   
  • We'd also like our LED controller to have different modes of operation, so it will also need a way of passing that information. We'll only have a handful of commands here, so a uint8_t 8-bit unsigned integer is a good fit. Each cmdNum case will be represented by enum (shown later).

This results in the following structure for LedCmd:

typedef struct
{
uint8_t cmdNum;
float red;
float green;
float blue;
}LedCmd;

The LED Cmd Executor's primary interface will be a queue of LedCmds. State changes will be performed by writing new values in the queue.

Since this structure is only 13 bytes, we'll simply pass it by value. Passing by reference (a pointer to the structure) would be faster, but it also complicates the ownership of the data. These trade-offs are discussed in Chapter 9, Intertask Communication.

Now that we have a data model defined, we can look at the remaining components of this application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.14.63