Graham
Graham

Reputation: 338

How can I write hardware independent functions in C for the PIC24

I am working on some code that impliments various features such as a PID controller, signal generator, etc.

My hardware provides various inputs and outputs. Just now I have a load of SWITCH statement to determine the source and destination of my calculations.

For example for the PID controller, there is a switch command every 100ms that decides which input to pass to the pid_calculate function, followed by another switch to decide what to do with the return value. As I have 32 analog inputs, as well as can, lin and serial as possible inputs, the switch statements are HUGE!

I would like to reference to, or a physical example of how something could be coded as a hardware independent (within the confines of the pic) function. I am sure that the answer lies with pointers, but I am new to C and not really sure where to begin with the pointers.

I envisage the function prototype being something like int pid_init(*source,*destination) Where the source is a pointer to the input, eg an ADC buffer, and the destination could be for example the pwm duty cycle register. Just now I have to switch each possible condition then load the data into the registers.

So to clarify, how would I impliment a function that allows it to be input and output independent, and how would I de-reference the pointers(assuming pointers are the correct way to do this)

I hope this makes sense, thanks in advance.

Upvotes: 2

Views: 886

Answers (2)

slebetman
slebetman

Reputation: 113994

My personal favorite is to use macros. They are simple and they always work on all compilers.

Say for example you have some function that does something generic (PID in your case) but operates on hardware specific registers and/or memory locations. Just define the hardware specific part as a macro that you put in a hardware specific header file. Then, when you compile the code you can choose which macro to use (if you notice, that's what PIC compilers do anyway with hardware specific register addresses).


Example:

do_something.c

void do_something () {
    MY_OUTPUT = complex_operation(MY_INPUT);
}

dev_board_16f877.h

#define MY_INPUT  PORTB
#define MY_OUTPUT PORTD

production_board_version_1_0.h (alternative header)

#define MY_INPUT  PORTC
#define MY_OUTPUT PORTD

Better yet, you should even abstract out I/O operations away as functions rather than variable assignments because most other CPUs can't do that. Again, this can simply be done in a hardware specific header file or hardware specific C file that you choose during the compile phase.

BTW, this is also how the Linux kernel does it - separate the algorithm from the hardware specific stuff.

Upvotes: 2

Richard Chambers
Richard Chambers

Reputation: 17623

You can implement a kind of object oriented programming in C so long as you take a bit of care. In the ways that I have done it, I have used function pointers as well as structs with function pointers as members.

For instance to have a kind of data independence, you may have a function that like the quick sort (qsort) function is provided data long with a pointer to a function that operates on the data. Typically with this approach you end up using void pointers of various kinds to allow the use of pointers to any data type.

Another approach that I have used is to have what amounts to an abstract interface for the common operations that the various types of devices support and to then create a struct which provides a function pointer template for those operations. Then I will create an array of these structs and fill the array elements in by using function pointers to the particular function that implements that operation for a particular device.

For instance something like this very simple example is what I have used to provide a standard interface for several different devices. This interface hides the device differences by providing a standard interface.

// I create a struct that specifies what the function
// interface looks like for each of these operations for the
// devices that we are supporting.
typedef struct {
    int  (*pInput)(int iValue);
    char *(*pName) (void);
    int  (*pDance) (int iTune, char *aszName);
} HardwareInterface;

// next I provide the function prototypes for the various devices.
// we have two different devices, Device_01 and Device_02 which
// have a common set of operations with the same interface.
int  Device_01_Input (int iValue);
char *Device_01_Name (void);
int  Device_01_Dance (int iTune, char *aszName);

int  Device_02_Input (int iValue);
char *Device_02_Name (void);
int  Device_02_Dance (int iTune, char *aszName);

// now I define my array of the device operation interfaces.
// this is where I provide the link between a specific operation
// on a specific device.  I will number my devices beginning with
// zero to the number of devices supported minus one.
HardwareInterface HardwareList [] = {
    {Device_01_Input, Device_01_Name, Device_01_Dance},
    {Device_02_Input, Device_02_Name, Device_02_Dance},
};

At this point I can have a function that I call to obtain which of the devices I actually want to use. This function returns an index into the hardware list. So it might go something like this.

int DeviceStdInput (int iValue)
{
    int  i = GetDeviceType ();
    return HardwareList[i].pInput (iValue);
}

Or I might use a handle approach so that I would call a function which provides the handle to a device that is specified in some description which is passed to the function. Then any calls to my standard interface would specify the handle. Underneath, the handle is merely the index into the array of different devices.

{
    int iHandle = GetDeviceHandle ("Device_01");
    int iXvalue = DeviceStdInput (iHandle, iValue);
}

And the function DeviceStdInput() would look like:

int DeviceStdInput (int iHandle, int iValue)
{
    return HardwareList[iHandle].pInput (iValue);
}

You will still need to implement the actual functions used for each of the devices however this provides a way to have a standard interface to several devices with common operations that you can then just use the standard interface within the rest of your code.

I have used this to provide a standard interface for output in which the output devices were a file, a printer, and a web service. The actual sink or output device was selected by the user. The code using the interface did not change. Adding more devices was pretty easy so long as the interface did not change. In some cases I would have devices that did not support a particular operation and that function would just return without doing anything.

Upvotes: 3

Related Questions