In this C2000 Programming Model Guide, the two basic approaches to programming on the C2000 will be over viewed using the C2000 Launchpad. Some code examples will be shown, as well as a code execution speed test comparing the two methods. All the code is written in Code Composer Studio (CCS) v5.5, with the latest version of ControlSuite released at the time of this article being posted.
TI provides all the documentation required for programming the C2000, which can be found here.
The Texas Instruments ControlSuite can be downloaded free of charge and includes support for two programming methods: Direct Register Access Model and Software Driver Model. There is also a third method, using Assembly language but that will not be covered here. Both of these models can be used independently or a combination of both can be used. Each model has advantages and disadvantages, which are summarised below.
|Direct Register Access Model||Smaller code footprint, Faster code execution||Statements are obscure, A detailed knowledge of each register is required|
|Software Driver Model||Larger code footprint, Generally slower execution time||Code is much more over viewable and understandable|
Direct Register Access Model
The direct register access model writes values directly to the individual peripherals registers, if you have programmed the MSP430G Launchpad it uses a similar method. All of the peripheral registers are defined in the corresponding header file contained in f2802x0_header/i/include, the image below shows this in CCS.
The individual header files define the location of each register with respect to other registers within a peripheral, as well as the bit fields within each register. This is all implemented using structures, the header files only define the structures they do not declare them. A C source file is used to declare the structures with the physical memory of the device F2802x_GlobalVariablesDefs.c. To use the direct register access model in an application, the file DSP28x_Project.h must be included in each source file were the register accesses are made. An example of a direct register access statement can be found in the code snippet below, this particular call clears the ADC interrupt flag for ADC interrupt 1.
AdcRegs.ADCINTFLGCLR.bit.ADCINT1 = 1;
Software Driver Model
The software driver model uses an API provided by the peripheral driver library, which is used by applications to control the peripherals. Before a peripheral can be used, the driver header file needs to be included and a handle to that peripheral initialised. The code snippet below shows an example of this, illustrating the header file for the PWM peripheral, then the initialisation and finally two API function examples.
/*** Include for the pwm.h header file ***/
/*** Initialise handles for ePWM1 and ePWM2 ***/
myPwm1 = PWM_init((void *)PWM_ePWM1_BASE_ADDR, sizeof(PWM_Obj));
myPwm2 = PWM_init((void *)PWM_ePWM2_BASE_ADDR, sizeof(PWM_Obj));
/*** Examples of API function calls using handles ***/
Writing An Application Using Both Models
To write an application that includes both the direct register access model and the software driver model, TI recommends the following:
- Link driverlib.lib into your application (see below image)
- Include DSP28x_Project.h in files you wish to use the direct register access model
- Add /controlsuite/device_support/f2802x0/version/ to your projects include path (see below image)
- Include driver header files from f2802x0_common/include in any source file that makes calls to that driver
Simple Code Execution Time Test Using An Oscilloscope
A simple test was devised to illustrate the main differences between these two models, this test is also useful for testing control and timing applications. It has been used in a previous tutorial to access code execution time, which can be found here.
The C2000 clock is set to 60MHz for this test. The code uses a timer which generates an interrupt every 100uS, when this interrupt is generated GPIO pin 19 is set high, the interrupt is then cleared. There is also a continuous while loop running in the main program function, this has a statement/function which sets the GPIO pin 19 low. So the sequence of events is as follows:
Every 100uS the interrupt handler is called -> GPIO pin 19 is set high -> The interrupt flag is cleared -> The program returns to the while loop and GPIO pin 19 is taken low
There is also one more piece of additional code added, a variable called control. This variable is quite important as in between the interrupt calls, the code inside the while loop will still be executed. Therefore if the GPIO pin low statement/function is partly way through executing when the interrupt is called, it will then return to this point and cause multiple timing traces on the oscilloscope. This is due to the fact that there is an indeterminate time when the interrupt is called and what code is being executed at that time. So using the if statement inside the while loop which uses the control variable, reduces these unknowns and ensures the code is far more predictable.
The code snippet below shows the code used in the main function, this code also demonstrates the equivalent direct register access statement to the software driver function for the GPIO pin 19 operation.
*** All the set-up code is here before the while loop and just sets
*** up the timer, interrupt etc. Then the direct register commands and
*** TI Api commands can be run alternately and commented out as required
if (Control == 1)
/*** Set the pin low using the direct register command ***/
// GpioDataRegs.GPACLEAR.bit.GPIO19 = 1;
/*** Set the pin low using the TI API ***/
GPIO_setLow (myGpio, GPIO_Number_19);
Control = 0;
/*** Timer0 interrupt handler ***/
interrupt void cpu_timer0_isr(void)
/*** Set the pin high using the direct register command ***/
// GpioDataRegs.GPASET.bit.GPIO19 = 1;
/*** Set the pin high using the TI API ***/
GPIO_setHigh (myGpio, GPIO_Number_19);
Control = 1;
/*** Clear the interrupt flag ***/
PieCtrlRegs.PIEACK.bit.ACK1 = 1;
The next four images show screen captures from the oscilloscope, for the direct register access model and the software driver model. It should be noted there is still a small variation in the timing as multiple traces are visible, hence two images are shown for each model allowing the variation to be calculated.
Direct Register Access lowest Pulse Width
Direct Register Access Highest Pulse Width
Software Driver Lowest Pulse Width
Software Driver Highest Pulse Width
What these oscilloscope traces show is the direct register access model clearly executes at a faster rate, the lowest and highest execution times are listed below for each model.
Direct Register Access Model:
- Lowest = 2.92uS which equates to 176 Clock Cycles
- Highest = 3.4uS which equates to 204 Clock Cycles
Software Driver Model:
- Lowest = 6.64uS which equates to 399 Clock Cycles
- Highest = 7.08uS which equates to 425 Clock Cycles
How the models are used is dependant on the application: (1) If code size and execution time is not an issue it’s perfectly ok to use the software driver model. (2) If code size is not an issue but certain aspects where speed of execution and timing are critical, then using both methods could be applicable. (3) If code size and execution time are both critical, then the direct register access model is probably the best choice (Assembly language would be even better).
I take great care when writing all the tutorials and articles, ensuring all the code is fully tested to avoid issues for my readers. All this takes time and a great deal of work, so please support the site by using the Adfly links etc. If you have found this useful or have any problems implementing, please feel free to leave a comment and I will do my best to help.