Jump to content
madias

howto: porting libraries - some help needed

Recommended Posts

Hello,

I'm trying to collect some standard procedures porting arduino(mostly avr, sam is easier) libraries to energia/lm4f. 

Maybe all forum members could help together, creating a little howto:

 

Porting libraries to Energia/Stellaris LM4F

Version 2014-05-20

 

1. First, it is necessary to examine the files to the following:

 

1.1

#if ARDUINO >= 100

#include <Arduino.h> 

#else

#include <WProgram.h> 

#endif

 

and change it to:

#include <Energia.h> 

(or let #include <Arduino.h> as only line)

 

1.2 change #include <pins_arduino.h> to #include <pins_energia.h>

 

2. If PROGMEM was used:

remove all PROGMEM and replace pgm_read_byte with *

and better add a static const

 

or insert this line on top: (Untested)

#define PROGMEM

#define pgm_read_byte(addr) (*(const unsigned char *)(addr))

 

or insert:(Untested)

#define pgm_read_word(data) *(data)

#define pgm_read_byte(data) *(data)

#define PROGMEM

 

3. __delay_cycles macro

3.1 easy way: use delayMicroseconds(xxx) where xxx is to try&error

3.2 use SysCtlDelay(xxx); -> Thread: http://forum.stellarisiti.com/topic/1980-delay-cycles/

 

4. Pin related macros/stuff

 

4.1 insert:(Untested)

#define cbi(reg, mask) GPIOPinWrite(reg, mask, 0)

#define sbi(reg, mask) GPIOPinWrite(reg, mask, mask)

#define pulse_high(reg, bitmask) { sbi(reg, bitmask); cbi(reg, bitmask); }

#define pulse_low(reg, bitmask) { cbi(reg, bitmask); sbi(reg, bitmask); }

 

4.2 Conversation: (now I need your help for converting!)

Arduino / AVR:

#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) )
#define digitalPinToTimer(P) ( pgm_read_byte( digital_pin_to_timer_PGM + (P) ) )
#define analogInPinToBit(P) (P)
#define portOutputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_output_PGM + (P))) )
#define portInputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_input_PGM + (P))) )
#define portModeRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_mode_PGM + (P))) )

 

So your help is desired, I'll update this thread as soon as I get new information.

Maybe we can build together a conversation.h file, so we can include it to every library (for testing)

 

Regards 

Matthias

Share this post


Link to post
Share on other sites

0. If want to maintain backwards compatibility (one library to run on both Arduino and LM4F)

#if defined(__AVR__)
//Leave the AVR arduino code as is
#else
//alternative code for LM4F
#endif

1.1

Can just leave those include statements as is, Arduino.h includes Energia.h

 

 

2. 

#if !defined(__AVR__)

#if !defined(PROGMEM)
#define PROGMEM

#define memcpy_P(dest, src, len) memcpy((dest), (src), (len))
#endif

#if (13 == ENERGIA)
// Energia version 13 pgm_read_byte and pgm_read_dword do not work under some circumstances.
#undef pgm_read_byte
#undef pgm_read_dword
#endif

#ifndef pgm_read_byte
#define pgm_read_byte(x) (*(const unsigned char *)(x))
#endif

#ifndef pgm_read_dword
#define pgm_read_dword(x) (*(const unsigned long *)(x))
#endif

#endif

4.2

digitalPinToPort, digitalPinToBitMask, digitalPinToTimer - these all exist in Energia.h 

(Not sure if need any conversion, depends how the result is used)

 

portOutputRegister - consider/compare to Energia portDATARegister

 

 

But, if you want to take better advantage of the capabilities of the Stellaris/Tiva processors you could do something like this.

#define portMaskedOutputRegister(port, mask) \
  ((volatile uint8_t *) (((uint32_t)portBASERegister(port)) + (GPIO_O_DATA + (((uint32_t)mask) << 2))))

The mask you supply will be applied before writing to the port in question, so one does not have to do read the

port, twiddle the bit(s) of interest, write it back.  Can just write it, and only the bits indicated by the mask will be changed.

So 

oepin     = digitalPinToBitMask(oe);
oeport    = portOutputRegister(digitalPinToPort(oe));

*oeport    |= oepin;
*oeport    &= ~oepin;
// can become
oepin     = digitalPinToBitMask(oe);
oeport    = portMaskedOutputRegister(digitalPinToPort(oe), oepin);

*oeport    = 0xFF;
*oeport    = 0;

Note that in the bit set example, you could use oepin in place of 0xFF, however if you are writing 1's to several ports

in nearby code, the compiler may be able to keep the one value in a register, rather than having to load a bunch of

different port masks.  (Also, the original AVR code will still work, it just takes more instructions.)

 

If you define a mask which selects several pins, you can output to them all together with a single write, e.g.

*rowport = row;  // Add bit shifts as needed if the pins for row do not start at pin 0

Rather than having to split row up one bit at a time.

 

Edited August 2014 - add parenthesis around parameters in PROGMEM, add test to be sure PROGMEM not already defined.

[Edited November 2014 - updated definitions of pgm_read_byte.  Replace versions from Energia 13 which don't work right.]

Share this post


Link to post
Share on other sites

Addition/caveat to my above post

The #defines for memcpy_P, etc. need extensive testing, and/or review by somebody who really knows all the ins/outs/and gotchas of macros.

(i.e., it should work in a simple case, but I am not sure that it will always work right, e.g. if the call to memcpy_P is generated by a macro and

has fancy macro arguments.)

Other options might be to create an inline function called memcpy_P that calls memcpy, or to make memcpy_P an alias for memcpy.

 

Additional - for the #define PROGMEM may be well to protect them inside #ifndef, i.e.

 

#if !defined(PROGMEM)

#define PROGMEM

#endif

etc.

 

So that if something else provides the fix (e.g. if Energia incorporated similar fixes) we wouldn't wind up with multiple define errors.

Share this post


Link to post
Share on other sites

3.3 For very short delays (e.g. one cycle) use __NOP() 
 

Don’t muck about trying to be clever: for a one-cycle delay just use __NOP(), the ARM CMSIS standard spelling for an inline function that emits the NOP instruction. Where it has an effect, it’s a one-cycle effect. Where it doesn’t, other instructions don’t behave any better.


This item may need some refinement (e.g. add the code for the NOP function, if it is not part of Energia.)

Share this post


Link to post
Share on other sites

2.1 If avr/pgmspace.h is included (e.g., uses more than just PROGMEM)

#include "avr/pgmspace.h"

AVR chips have separate address spaces for program memory and data memory, while ARM chips do not separate them.  pgmspace.h defines various access structures and libraries.  There are a few pgmspace.h replacement files for machines like the ARM.

 

For example, the one from Teensy 3

https://github.com/PaulStoffregen/cores/blob/master/teensy3/avr/pgmspace.h

 

There are various places you can put the file.  I do not know where the "best" place to put pgmspace.h would be.  The following directories work (if you want to make it generally available, without having to specially include it in a particular project).  (Would be nice if it was included as part of Energia.)

 

For Tiva/Stellaris

[Energia Dir]/hardware/lm4f/cores/lm4f/avr
or
[Energia Dir]/hardware/lm4f/tools/lm4f/arm-none-eabi/include/avr

For MSP430
[Energia Dir]/hardware/msp430/core/msp430/avr
or
[Energia Dir]/hardware/tools/msp430/msp430/include/avr

Share this post


Link to post
Share on other sites

5. Working on multiple platforms.  There are various macros to help select appropriate code for the current platform.

 

Environment/library
ENERGIA - Energia environment (Tiva/CC3200/MSP430/C2000 )
CORE_TEENSY - TeensyDuino (AVR/ )

MAPLE_IDE - libmaple (STM32)

MPIDE - chipKIT

ARDUINO - (AVR/SAM/x86/...) - also defined by other environments, like ENERGIA.

 

Processor family:
__MSP430_CPU__ or __MSP430_HEADER_VERSION__ - MSP430

[???] - MSP432
__arm__ - Should be true for any ARM processor (e.g. Tiva, Stellaris, CC3200, SAM/Arduino Due, STM32, Freescale, ... )
__AVR__ - Atmel AVR processors (original Arduino, Teensy before version 3, etc.)

__ARDUINO_X86__ - Intel x86/Galileo

 

Board: (Arduino 1.5.7 BETA)

ARDUINO_SAM_DUE - Arduino Due

ARDUINO_AVR_xxx (YUN, LEONARDO, MEGA, ....) - Various AVR Arduinos

 

see for example

Embededxcode: Manage code for multiple platforms

 

Additional examples and information:

http://forum.stellarisiti.com/topic/1965-architecture-for-conditional-compilation-in-energia/

http://forum.43oh.com/topic/5670-one-sketch-multiple-architectures/

 

[Edit: Oct 1 2014: Added Board, added x86 processor]

[Edit: Need to add MSP432]

Share this post


Link to post
Share on other sites

6. Analog pins

 

6.1 Analog pin names on CC3200

 

Arduino standard analog pin names (A0, A1, etc.) are not defined in Energia.  The following code can be included as a workaround.

// Check to be sure CC3200 CPU
#if defined(__CC3200R1M1RGC__)// Do not conflict in case A0 does get fixed
#if !defined(A0)
static const uint8_t A0 = 23;
static const uint8_t A1 = 2;
static const uint8_t A2 = 6;
static const uint8_t A3 = 24;
#endif#endif

For further information, see

https://github.com/energia/Energia/issues/483

 

Caution: Note that analog input pins on the CC3200 can not handle as much voltage as Digital pins.  Be careful to keep the input in range when a pin is set to Analog input.

 

[Edit: 1 Nov 2014 - changed to Energia pin numbers, rather than PIN_ macros ]

 

6.2 Analog read range

 

On arduino, analogRead returns a 10 bit unsigned value (0 - 1023).  Some Arduinos can read more bits in an analog to digital conversion.  For instance the Arduino Due has 12 bit ADCs, so it can read values (0-4095).  Although the Due normally returns 10 bits,  analogReadResolution(number of bits) lets code select how many bits to read.  

 

Unfortunately the API does not appear to provide an official indicator of how many bits can be read.  However it happens that Arduino Due does define 

#define ADC_RESOLUTION 12

Other platforms vary in how many bits ADCs output.  Unlike the Arduino, many other wiring platforms return the full ADC range from analogRead, and there is little standardization in indicating how many bits to expect.  (On Energia, some devices return 10 bits, while others return 12 bits.)

 

On the MSP430 Energia uses various symbols to indicate ADC resolution.  It could be convenient to extend ADC_RESOLUTION by defining it on other platforms.

#if defined(__MSP430_HAS_ADC10__) || defined(__MSP430_HAS_ADC10_B__)
//#define ADC_RESOLUTION 10
#elif defined(__MSP430_HAS_ADC12_PLUS__) || defined(__MSP430_HAS_ADC12_B__)
#define ADC_RESOLUTION 12
#endif

On Tiva/Stellaris and Maple, ADC has 12 bits.

 

[This is a work in progress - more later]

Share this post


Link to post
Share on other sites

Use caution with calls inside class constructors.

 

Although some arduino libraries call pinMode, or other library functions inside class constructors, this may not be prudent portable programming practice, and may lead to unpredictable results.  If a global object of the class is created, it can not rely on the order in which other objects are initialized, so it can not rely on them being in working state.  Likewise hardware may not have been initialized.

 

One workaround is to place pinMode, etc. in a "begin()" method, and require that it be called before the class is used.

 

http://forum.43oh.com/topic/6011-tiva-delaymicroseconds-unreliable/?p=52601

http://forum.stellarisiti.com/topic/2165-strange-behavior-pinmodedigitalwrite-within-library/

Share this post


Link to post
Share on other sites

Following up on this, I need some help with the definitions needed to port the OneWire library for Arduino to Energia for the MSP430. The hardware abstraction layer in the library has this:

 

// Platform specific I/O definitions


#if defined(__AVR__)
#define PIN_TO_BASEREG(pin)             (portInputRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint8_t
#define IO_REG_ASM asm("r30")
#define DIRECT_READ(base, mask)         (((*(base)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask)   ((*((base)+1)) &= ~(mask))
#define DIRECT_MODE_OUTPUT(base, mask)  ((*((base)+1)) |= (mask))
#define DIRECT_WRITE_LOW(base, mask)    ((*((base)+2)) &= ~(mask))
#define DIRECT_WRITE_HIGH(base, mask)   ((*((base)+2)) |= (mask))


#elif defined(__MK20DX128__)
#define PIN_TO_BASEREG(pin)             (portOutputRegister(pin))
#define PIN_TO_BITMASK(pin)             (1)
#define IO_REG_TYPE uint8_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask)         (*((base)+512))
#define DIRECT_MODE_INPUT(base, mask)   (*((base)+640) = 0)
#define DIRECT_MODE_OUTPUT(base, mask)  (*((base)+640) = 1)
#define DIRECT_WRITE_LOW(base, mask)    (*((base)+256) = 1)
#define DIRECT_WRITE_HIGH(base, mask)   (*((base)+128) = 1)


#elif defined(__SAM3X8E__)
// Arduino 1.5.1 may have a bug in delayMicroseconds() on Arduino Due.
// http://arduino.cc/forum/index.php/topic,141030.msg1076268.html#msg1076268
// If you have trouble with OneWire on Arduino Due, please check the
// status of delayMicroseconds() before reporting a bug in OneWire!
#define PIN_TO_BASEREG(pin)             (&(digitalPinToPort(pin)->PIO_PER))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask)         (((*((base)+15)) & (mask)) ? 1 : 0)
#define DIRECT_MODE_INPUT(base, mask)   ((*((base)+5)) = (mask))
#define DIRECT_MODE_OUTPUT(base, mask)  ((*((base)+4)) = (mask))
#define DIRECT_WRITE_LOW(base, mask)    ((*((base)+13)) = (mask))
#define DIRECT_WRITE_HIGH(base, mask)   ((*((base)+12)) = (mask))
#ifndef PROGMEM
#define PROGMEM
#endif
#ifndef pgm_read_byte
#define pgm_read_byte(addr) (*(const uint8_t *)(addr))
#endif


#elif defined(__PIC32MX__)
#define PIN_TO_BASEREG(pin)             (portModeRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint32_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask)         (((*(base+4)) & (mask)) ? 1 : 0)  //PORTX + 0x10
#define DIRECT_MODE_INPUT(base, mask)   ((*(base+2)) = (mask))            //TRISXSET + 0x08
#define DIRECT_MODE_OUTPUT(base, mask)  ((*(base+1)) = (mask))            //TRISXCLR + 0x04
#define DIRECT_WRITE_LOW(base, mask)    ((*(base+8+1)) = (mask))          //LATXCLR  + 0x24
#define DIRECT_WRITE_HIGH(base, mask)   ((*(base+8+2)) = (mask))          //LATXSET + 0x28


#else
#error "Please define I/O register types here"
#endif

Looking around some forums I found that these definitions might work for the Tiva / Stellaris boards, though i haven't had a chance to test yet:

#elif defined(__TM4C129XNCZAD__) || defined(__TM4C1294NCPDT__) || defined(__LM4F120H5QR__) || defined(__TM4C123GH6PM__)
#define portMaskedOutputRegister(base, mask) ((volatile uint8_t *) (base + (GPIO_O_DATA + (((uint32_t)mask) << 2))))
#define PIN_TO_BASEREG(pin)             (&((uint32_t)portBASERegister(digitalPinToPort(pin))))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define IO_REG_TYPE uint8_t
#define IO_REG_ASM
#define DIRECT_READ(base, mask)
#define DIRECT_MODE_INPUT(base, mask)
#define DIRECT_MODE_OUTPUT(base, mask)
#define DIRECT_WRITE_LOW(base, mask)  (portMaskedOutputRegister(base, mask) = 0)
#define DIRECT_WRITE_HIGH(base, mask) (portMaskedOutputRegister(base, mask) = 0xFF)

I would like to know what definitions are needed for the MSP430:

 

#elif defined(__MSP430__)
I can't use GPIO_O_DATA in defining "portMaskedOutputRegister", it's not defined for the MSP430...
 

Share this post


Link to post
Share on other sites
Look in the energia header files (Energia.h) for the MSP430, should find definitions of many of the macros.

 

Probably don't want to try to replicate portMaskedOutputRegister - it uses special features of 

the LM4F output ports (not present on many other ARMs, not sure if present on MSP430).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×