The Arduino library and core source code is (of course) included in the distribution, buried somewhere in the directory structure installed with the application. Somewhere under ...hardware/arduino/cores/arduino. It's not a huge amount of code (less than 4000 lines), and since it is mostly in C and C++, I hope that a fair portion of it will simply stay the way it is even on MSP430. But as a general strategy, I'm going to take each of the Ardunio source modules and copy them into new spaces, after which they will be modified to support the new architecture we're dealing with.
The first thing we must be able to do, of course, is blink the LED(s)! So the first module that I'll attack is wiring_digital.c, which includes pinMode, digitalRead, and digitalWrite; the functions for manipulating "pins." So right away, I've run into one of those important Arduino Abstractions that I mentioned earlier. Pin numbers. The Arduino doesn't need the user to understand "ports", or "registers", or even "bits." You just give a function the number of the pin (as labelled on the board), and a value. The Arduinos are mostly based on 28pin microcontrollers, so they have 14 "digital" pins and 6 "analog" pins. Since the LaunchPad has only 14 pins total, and a maximum of 10 than can do IO, I plan to do something a bit different. I'm simply going to make the "pin numbers" used by the software be the same as the pin numbers of the chip itself! Yes, that'll mean certain pins can't actually do anything useful from the software's point of view. But I think overall clarity will be pretty good...
The original Arduino code uses a set of flash-based tables for translating pin numbers to ports. There's a table for pin to mode (direction), one for the output port, one for the input port, and one for timer info if the pin is shared with a timer PWM. And other stuff. Since the small MSP430s I'm targetting are, well, small, I'll be combining some of those tables and using the fact that the MSP430 architecture has a very regular IO port structure (at least up till p7) to cut down on code size. It's a nice an very general way of handling, the mapping, though it's not clear whether it will end up using less flash space than a string of compare instructions. Oh well; we'll see how things work out.
This immediately gets slightly messy to do in C; pointers to IO registers are all 16bits, even though the registers we're interested in have addresses less than 256. So I ended up implementing these in Assembly language. I have three excuses for using assembly language in C infrastructure, and all three of them get touched by this code:
- mixed size arithmetic. C would rather not admit that sometimes it is useful to add an 8bit quantity to a 16bit quantity in any way other than expanding the 8bit quantity to 16 bits first. Bleh.
- Explicit use of CPU "status" bits. Carry, Zero, etc are useful, but C won't let you access them
- tighter cooperation between co-routines. I have a subroutine that modifies two values in the same registers it was passed as arguments, and puts additional info into the status bits...
So here's what I wound up with for the mapping function. The relevant functions all have the pin number as their first argument, which in mspgcc goes in r15. There may be another argument in r14. r13 is a register "free to be messed with" by subroutines... You'll note that this code is currently very device specific. We'll try to deal with that later.
entry _getportpin
;;; Given a pin# as the first argument in a C function (in R15),
;;; convert this to the port address base in R15 and a bit in R13.
;;; return with Z set if the translation is bad.
mov.b r15, r13 ; get pin
mov.b p2port(r13),r15 ; get port based on pin (extend to word.)
mov.b p2bit(r13),r13 ; get bit
tst r15 ; see if it's valid; leave Z set if not.
ret
;;; pin# to port address translation table (stored in flash)
;;; In the current tables, pin#'s are based on the individual chip pins,
;;; so the first usable "pin" is "2"
p2port: .byte 0,0 ;0 not used. Pin 1 = Vdd
.byte 0x20, 0x20, 0x20, 0x20 , 0x20, 0x20, 0x20, 0x20 ;pin 2-9
.byte 0,0 ; pin 10 = RST, pin 11=TEST
.byte 0x28, 0x28 ; pin 12, 13
.byte 0 ; pin 14 = VSS
;;; pin# to bit (within a port) translation table.
p2bit: .byte 0,0
.byte 1,2,4,8, 16,32,64,128
.byte 0,0
.byte 128,64
.byte 0
This then allows digitalWrite, digitalRead, and pinMode to be written in only a few additional instructions. Note that one of the additional things that needs to be done is to convert the "answer" or setting to/from a boolean (0 or 1) value rather than just 0 or nonzero, to meet the API. Note that the register use here is based on the calling conventions between function as defined by mspgcc; they are slightly different in ccs C (but still nicely documented!), so while the same general technique would work, actual working code will look slightly different...
;;; _digitalWrite(int pin, int value)
;;; actual code to implement the digitalWrite Arduino function (if it
;;; can't be implemented faster inline for the case where args are constant.)
;;; Write a digital value (zero or non-zero) to a particlar pin,
;;; assuming the pin is already set up properly as an output.
entry _digitalWrite ; (uint8_t pin , uint8_t val)
call #_getportpin ;get portbase, bit to r15, r13
jz dwret ;errorn
inc r15 ; adjust to xxOUT
tst.b r14 ;check value
jnz dw_nz
bic.b r13,0(r15)
ret
dw_nz:
bis.b r13,0(r15)
dwret: ret
;;; int _digitalRead(int pin)
;;; actual code to implement the digitalRead Arduino function (if it
;;; can't be implemented faster inline for the case where args are constant.)
;;; return 0 if the pin input is low, 1 if it is high.
entry _digitalRead
call #_getportpin ;get portbase, bit to r15, r13
jz drret ;digital read of invalid pin is 0
mov.b @r15, r15 ;read port (offset from base is 0 for IN)
and.b r13, r15 ;test bit
jz drret ; zero. retval in r15 is also conveniently zero
mov.b #1, r15
drret: ret
;;; void pinMode(int pin)
;;; set up a pin as either input or output
entry pinMode
call #_getportpin ;get portbase, bit to r15, r13
jz pmret ; bad port; do nothing.
add #2, r15 ; adjust base to xxDIR
tst.b r14 ;note INPUT == 0
jz pmINP
bis.b r13, 0(r15) ; set bit for OUTPUT
ret
pmINP: bic.b r13, 0(r15) ;clear bit for INPUT
pmret: ret
But wait! This code is SO much slower than you could do writing bit-twiddle code without these primitives. On a real arduino, we're looking at between 20 and 30 times slower; after all, there are bitset/bitclear single instructions that are no slower than two cycles. Can't the core bit manipulation functions be a bit faster?
Yes, of course they can! Assuming you're willing to twist you mind around some really UGLY C constructs. More in a later posting.
Meanwhile, we can write a blink program:
void main()
{
volatile unsigned int i;
pinMode(2, 1);
while (1) {
_digitalWrite(2, 1);
for (i=50000; i != 0; i--)
; // delay
_digitalWrite(2, 0);
for (i=50000; i != 0; i--)
; // delay
}
}
Load it up and have it start blinking. Ye hah.
Actual source code is attached. The interested can look ahead at the ugly C code if they dare!