Jump to content
43oh

GPIO abstraction layer


Recommended Posts

[EDIT: This code concept has evolved into "fabooh" find it here on github https://github.com/RickKimball/msp430_code/tree/master/fabooh ]

 

I've been experimenting with msp430-gcc uniarch trying out different approaches to implement a fast GPIO abstraction layer that hides some of the bitmask gymnastics we all seem to do when programming the msp430. I think I've come up with a reasonable approach and I'm hoping for some feedback from all of you.

 

Normally to access the gpio ports on the msp430 with 'C', you use a bit ANDing (P1OUT & BIT4) and ORing ( P1OUT | BIT1 ) style of programming to read and write the bits. In the Arduino world, they hide all the bit magic using digitalRead and digitalWrite. The downside, at least on the Arduino, is those instructions aren't very efficient. However, reading code written for the Arduino API does seem somewhat more understandable than a bunch of ANDs and ORs.

 

I want to make my code more readable, but I didn't want to sacrifice efficiency. After rejecting a few different methods, such as macros and functions with port tables, I came up with a C++ template that can be used to hide most of the bit mask programming and still manages to be reasonably efficient.

 

Here is an example of toggling some leds on and off using a timer:

/**
* gpiotest.cpp - experimental c++ template based gpio abstraction
*
* author: rick@kimballsoftware.com
*
* 2012-01-14 - this version is about 168 bytes using msp430-gcc -Os
*/

#include 
#include 
#include "gpio.h"

GPIO<0> LED1; // P1.0
GPIO<6> LED2; // P1.6

/**
* millis - access to milliseconds since startup
*
* Note: because the value is stored in a 16 bit int,
* it rolls over every 65536 milliseconds or 65.536 seconds
*/

static uint16_t millis;

int main(void) {
WDTCTL = WDT_MDLY_0_5; // Configure Watchdog as .5 second interval timer

// use default ~1.024 MHz DCO clock

digitalWrite(LED1,HIGH);
digitalWrite(LED2,LOW);

pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);

IE1 |= WDTIE; // Enable WDT interrupt

while(1) {
// Go to sleep until the Watchdog wakes us
_BIS_SR(LPM0_bits + GIE);

// The WDT_INTERVAL_ISR brings the CPU out of sleep mode
// and we end up here, every 8 seconds or so
if ( millis % 8192 == 0 ) {
volatile int i=0;

i=i;
}
}

return 0;
}

/**
* WDT_INTERVAL_ISR -Watchdog Timer ISR handler
*
* Note: assumes MCLK is 1.024MHz and we are using the
* WatchDog Interval timer mode of WDT_MDLY_0_5
*/
#ifdef __GNUC__
__attribute__( (interrupt (WDT_VECTOR)) )
#else
#pragma vector = WDT_VECTOR
__interrupt
#endif
void WDT_INTERVAL_ISR(void) {
static uint8_t _500uSCount= 0; // count each .5mSec tick

// every 2 ticks update the millis counter
if ( (++_500uSCount % 2) == 0) {
millis++;

if ( millis % 64 == 0) { // toggle every 64 mSec, uses power of 2 value for smaller code
LED1.toggle(); // our 2 leds are out of sync
LED2.toggle(); // one is on when the other is off

_BIC_SR_IRQ(LPM0_bits); // leave the ISR and exit sleep mode
}
}
}

To use the code you include "gpio.h" .. this declares the GPIO template and provides accessor functions for reading / writing and configuring the ports and pins.

 

To use the launchpad leds, just create an instance of the GPIO object for each pin:

GPIO<0> LED1; // P1.0
GPIO<6> LED2; // P1.6
This gives you an object that allows you to manipulate the pin values. You can do things like LED1.setHigh() or LED1.setLow() to toggle that pin HIGH and LOW. LED1.setHigh() is doing a P1OUT |= BIT0; under the covers. LED1.setLow does a P1OUT &= ~BIT0.

 

To use the pin as output you need to configure it. Normally you would do a P1SEL |= BIT0; to make it an output pin. With the gpio.h you can just use LED1.configureAsOutput(). As an example of how we can be more Arduino like, I provided a macro so you can also use "pinMode(LED1, OUTPUT)" instead.

 

Because we are using c++ templates, the compiler is very efficient at optimizing the code. In the example, I use two 'C' statements to set the pin modes. However the compilers optimizes it down to a single msp430 instruction.

 pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
c04c: f2 d0 41 00 bis.b #65, &0x0022 ;#0x0041
c050: 22 00
In fact, the example code is only 168 bytes of msp430 object code. This seems very reasonable to me for readable C code. Using C++ templates with structures has another advantage over traditional C++ classes done Arduino style. if you don't use a function you don't incur any overhead. The compiler only generates object code for the functions you use. This allows you to create all the various helper methods you might want and not end up with giant code bloat.

 

I've concentrated on making the experiment work with the msp430g2553 and msp430g2231. However it should be fine with any of the 'G' series of chips. You need to look at the way the GPIO structure is laid out and make sure it maps to the special memory areas for your chip.

 

This isn't a finished work, it is just a starting point for those hoping for some GPIO abstraction that that still yields efficient code. Take a look at the gpio.h and the sample code and tell me what you think. It should compile with both CCS and msp430-gcc ( although I haven't tried CCS )

 

You can find the latest version of the code here:

 

https://gist.github.com/1597239

 

It has the following files:

 

gpiotest.cpp - is the sample program

gpio.h - the GPIO template declaration

 

[Edited 1/28/2012] updated code with more implementation methods

 

-rick

Link to post
Share on other sites
I've been experimenting with msp430-gcc uniarch trying out different approaches to implement a fast GPIO abstraction layer that hides some of the bitmask gymnastics we all seem to do when programming the msp430. I think I've come up with a reasonable approach and I'm hoping for some feedback from all of you.

The only thing to watch out for is that the structures you're using don't have a volatile qualifier on the peripheral registers, which is why this happens:

Because we are using c++ templates, the compiler is very efficient at optimizing the code. In the example, I use two 'C' statements to set the pin modes. However the compilers optimizes it down to a single msp430 instruction.

 

	pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
   c04c:	f2 d0 41 00 	bis.b	#65,	&0x0022	;#0x0041
   c050:	22 00 

Because they're not volatile, the compiler is free to optimize the bejeezus out of the code, possibly reordering and eliminating some assignments if it sees a subsequent assignment to the same variable with no intervening read. With some registers, putting both bit sets into a single operation would result in very wrong behavior. E.g., a sequence I wrote earlier today:

      UCB0CTL1 |= UCSWRST;
     UCB0CTL1 &= ~UCSWRST;

might very well be completely eliminated, resulting in corrupted I2C reads.

 

I suspect this could result in some very difficult to understand bugs. You can avoid the problem by adding volatile qualifiers in the structure declarations, though of course you'll lose the corresponding performance. However, the readability/simplicity would remain.

Link to post
Share on other sites

Thanks Peter, this is just the kind of feedback I was looking for.

 

What was I thinking? Of course those memory mapped registers need to be marked as volatile,

the hardware is updating them and the value can change at any moment. I updated the gpio.h

and changed the sample so that it performed some serial toggling of the same pin:

 

...
  LED1.setHigh();
  LED1.setLow();
  LED1.setHigh();
...

 

msp430-gcc seems to generate the proper code even with -Os turn on:

       inline void setHigh() _INLINE_ {
               reinterpret_cast(PORT_BASE_ADDR)->_OUT |= PIN_MASK();
   c03e:       d2 d3 21 00     bis.b   #1,     &0x0021 ;r3 As==01
       }

       inline void setLow() _INLINE_ {
               reinterpret_cast(PORT_BASE_ADDR)->_OUT &= ~(PIN_MASK());
   c042:       f2 f0 fe ff     and.b   #-2,    &0x0021 ;#0xfffe
   c046:       21 00 
       inline pin_value getValue() _INLINE_ {
               return ( reinterpret_cast(PORT_BASE_ADDR)->_OUT & PIN_MASK() ) ? HIGH : LOW;
       }

       inline void setHigh() _INLINE_ {
               reinterpret_cast(PORT_BASE_ADDR)->_OUT |= PIN_MASK();
   c048:       d2 d3 21 00     bis.b   #1,     &0x0021 ;r3 As==01
       }

       inline void setLow() _INLINE_ {
               reinterpret_cast(PORT_BASE_ADDR)->_OUT &= ~(PIN_MASK());
   c04c:       f2 f0 bf ff     and.b   #-65,   &0x0021 ;#0xffbf
   c050:       21 00 

 

-rick

Link to post
Share on other sites
  • 2 weeks later...

I looked at EasyMSP before bothering with this code. Unfortunately the version I tried with msp430-gcc generated really bloated code. I could have modified the code to use macros to get the code size down, however then I would have lost the advantage of typesafe code checking I get for free with templates. However, I agree, it would be great to have a framework that works well with both msp430-gcc and CCS and isn't limited to 16k.

 

There seems to be different approaches to makes it easier to write code for the msp430. If you live in the TI CCS world you can use GRACE to generate application specific code. This is a nice approach however it doesn't really help those who want to use gcc. Then there is the framework approach of MSP430Ware. Again nice if you are in the CCS world but it adds the additional requirement of a larger msp430F5xxx type device. It doesn't really do anything for people playing with the G series chips. Both of these approaches work if you are happy staying in CCS land and a 16k code limit.

 

People migrating over from the Arduino world want to just be able to get a lower cost Arduino. For that group of people, they just want to make minimal changes to their sketches and have them work on the lower cost msp430 devices. Sometimes that works sometimes it doesn't. Arduinos generally assume a 1 or 2 clock cycle for GPIO toggling. Using an msp430 the best you can do is 4-5 cycles per GPIO toggle. However, you can write msp430 code that uses its peripherals and you might end up being faster than the Arduino. So making the migrating Arduino crowd happy isn't always going to end with a good result unless they put some effort in to learning the msp430.

 

If you want to implement a "standard" library, you end up with many unanswered questions about what would be the best type of framework or code generator to build. I haven't got a general answer to that question. For my projects, I want a framework that works well with msp430-gcc with the G series of chips. Occasionally, I'm interested in working with larger msp430f5xxx chips. I want the framework to produce small fast code while still being readable. Unless CCS is released for free with unlimited code size, msp430-gcc is going to be the only free option that makes any sense. So you can see my motivation for experimenting with this style of coding that works in both msp430-gcc and CCS. I hope others find it useful.

 

-rick

Link to post
Share on other sites

I like the intention behind both of these projects to simplify the IO. When I moved from Arduino to MSP430, it took me a while to get this into my fingers, and lowering the hurdle is good. Can it be done in such a way that the interface as seen from the developer is the same for Rickta59's and EasyMSP's libraries? So that the function calls are the same, but we just use different header files?

 

Personally my interest is in MSPGCC and the value-line series, as I'd like to use something like this within the Inventortown IDE, and also would love to be able to point to some good tutorials on doing simple IO. My long term ambition is to make a graphical representation of it, though that's still in my dreams :D ...

 

I'd love to get good libraries for all the other ones also, such as servo, serial, reading the length of pulse-in, shifting pulses out or in a pin (shiftout), i2c, spi and timers also. Maybe also something to simplify interrupts. And something for configuring the clock in a simpler way (I still find all the different options confusing). I have some servo-implementations, i2c etc on my site, but they are quite basic and need some work to be improved and generalized. Some of this is in EasyMSP, but I'm focussing on MSPGCC. But if we at least had some common interfaces across the different IDEs/compilers, that would help.

Link to post
Share on other sites
  • 11 months later...

Just compiled the example on Energia, dumping the rest of the framework just using main.cpp, and this example compiles in as little as:

 

 

Binary sketch size: 211 bytes (of a 16,384 byte maximum)
 
 
Very impressive, good job Rick. I will be putting this to use myself. Just for comparison. A "bare" main.cpp compiles to 107 bytes. 
Link to post
Share on other sites
  • 1 month later...

Sorry to be such a noob, but I don't know how to get this to work:

 

 

 

"C:/ti/ccsv5/tools/compiler/msp430_4.1.4/bin/cl430" -vmsp --abi=eabi -g --include_path="C:/ti/ccsv5/ccs_base/msp430/include" --include_path="C:/ti/ccsv5/tools/compiler/msp430_4.1.4/include" --advice:power=all --define=__MSP430G2553__ --diag_warning=225 --display_error_number --diag_wrap=off --printf_support=minimal --preproc_with_compile --preproc_dependency="blink.pp"  "../blink.c"
"..\gpio.h", line 80: error #78-D: this declaration has no storage class or type specifier
"..\gpio.h", line 80: error #66: expected a ";"
 

 

 
Line 80:

 

template<uint8_t PINNUM>
struct PORTINFO {
  static int const BASE_ADDR = PINNUM < 8 ? P1BASEADDR

 

 

My code:

 

#include <msp430.h>
#include <stdint.h>
#include "gpio.h"

........

GPIO<5> ASSY_MISPLACED_SW;
 

 

Link to post
Share on other sites

needs to be in a .cpp file is it?

 

To be honest, I've left this approach behind and gone with a new approach.

 

Still very much in an alpha state though:

 

https://github.com/RickKimball/msp430_code/tree/master/fabooh

 

I've also stopped trying to make it work with CCS. You will have to do

that yourself.

 

-rick

Link to post
Share on other sites

Thank you. I didn't know it was supposed to be in a .cpp file. Now at least it compiles so if I find any runtime problems with CCS I will report them here. I only know Java well and a tiny bit of C, so this is a bit steep to me, but it's a much better abstraction than having to bit juggling, even if you manage abstract a lot of the math.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...