LARD, Arduino-style abstraction library for the LPC1227 32-bit processor

As I've mentioned a few times I've recently been playing with LPC 32-bit processors, specifically the LPC1227.

I thought that a good way to learn the architecture would be to write something half-useful like a HAL, you already have CMSIS but that still needs a lot of bit-twiddling and in-depth knowledge of the hardware at the register level so LARD (Lpc ARDuino, yeah I know it's not great) was born.

I've only been on this for a few days but so far it looks promising. Here's an example of a "blink without delay" program

#include "LARD.h" 
#define LED_PIN 13

void LEDoff (void);
void LEDon (void);

swTimer * led_timer;

void LEDon (void) {
  digitalWrite (LED_PIN, HIGH);
  swTimerAttachCallback (led_timer, LEDoff);
}

void LEDoff (void) {
  digitalWrite (LED_PIN, LOW);
  swTimerAttachCallback (led_timer, LEDon);
}

void setup (void) {
  pinMode(LED_PIN, OUTPUT);
  led_timer = swTimerCreate(1000, SWTIMER_TYPE_ASTABLE, LEDon);
  swTimerStart(led_timer);
}

void loop (void) {
}

Or a simpler version

#include "LARD.h" 
#define LED_PIN 13

swTimer * led_timer;

void LEDfunc (void) {
  pinToggle (LED_PIN);
}

void setup (void) {
  pinMode(LED_PIN, OUTPUT);
  led_timer = swTimerCreate(1000, SWTIMER_TYPE_ASTABLE, LEDfunc);
  swTimerStart(led_timer);
}

void loop (void) {
}

I really got these chips for another project so this is a sideline, but it may be worth persevering to get it as compatible as possible.

There's more info and a list of the API functions here

lard.robgray.com

As always, any thoughts are appreciated.

EDIT: The LPC1227 is very similar to the Mega1284, 2 UARTs, 8k RAM, 4 timers, 48 or 64 pins etc, but with a 32-bit ARM core at 30/45MHz.


Rob

I like what you are doing here. "Arduino-like" is a useful goal in itself. It makes porting of libraries from the Arduino world to other architectures relatively straightforward, even if not quite trivial. Expecting the porting process to be completely free is probably misguided -- past a certain point, it becomes a case of rapidly diminishing returns very quickly.

Having said that, the case study of the Leaf Maple concentrates my mind a bit. It was also quite "Arduino-like", but never attracted the critical mass of support to get the most useful Arduino libraries ported across. And so it kind of languishes.

I think the question here then needs to become a bit more pointed. What aspects of "Arduino-like" are important, and what aspects less so? I think perhaps too much effort was directed in the Leaf Maple project to making their system "Arduino-like" in ways which turned out to be less critical than aspects which they put less resources into. For example, they went to great length porting the entire Arduino IDE to the Maple, but for intermediate to advanced users, that is probably a complete waste of effort. Unfortunately, the intermediate to advanced users are probabaly their natural target audience for a project of this kind. As yours would be too for this project, I would think.

So I think you are definitely putting the initial efforts into the right place here. If you can make an abstraction layer that significantly reduces the effort to port many of the Arduino 3rd party libs (which I strongly suspect is primarily what makes Arduino interesting to the intermediate to advanced developers), then you may attract the requisite critical mass.

I'd personally like to see more "Arduino-like" projects get off the ground to leverage all that valuable Arduino 3rd party library development onto other platforms.

Thanks pico, I was beginning to think that nobody cared =(

What aspects of "Arduino-like" are important,

Good question, for the moment I'm concentrating on the standard things like reading/writing pins, UARTs etc as well as adding stuff I think people always use, for example yesterday I added input debouncing, this works in the background (or should do, I haven't hooked up any hardware yet :)) for all 39 IO pins, just call debouncePinRead() to get the current state of the pin.

This should get around the constant dicking around with millis() to see how long the transition has been in place. I'm about to expand that to include a "pushButton" object the raises "onPressed/onReleased" style events so there's no messy "do something when a button is pressed and something else when it's released" code.

I also just added finite state machine support but it was so simple it hardly justifies having it built in so I'm thinking about that.

they went to great length porting the entire Arduino IDE to the Maple,

I suppose I see where they were coming from but the IDE is the last thing I'd want to port. Apart from the fact that I don't have the resources why on earth would you use it when you get the fantastic Eclipse IDE for free?

The IDE is good for it's intended audience but as you say they aren't the people who need to work with 32-bit processors.

If you can make an abstraction layer that significantly reduces the effort to port many of the Arduino 3rd party libs

That's the plan, I should grab some 3rd-party library source code and see how it goes. I still have a lot to do before that though. I suspect any such porting efforts by library authors will be aimed at the Due when it arrives and I don't plan to port every known library, but I will probably do things like LCD etc.

then you may attract the requisite critical mass.

That will be the day, nothing I do attract anything as you can see by the lack of interest in this thread :slight_smile: I suppose I could make an Arduini-compatible board, but I fear there has been too many such boards already and most haven't taken the Arduino world by storm.

I just think that with more resources (on these larger chips) we could do so much better than the messy Arduino API, at least get the function naming more orthogonal, eg delay() and delayMicroseconds(), why isn't it delayMillesecnds()? And what does "interrupts()" do? Yes I know what it does now but it's not descriptive, enableInterrupts() is.


Rob

My interest in 32-bit chips in not for the higher math capabilities, but rather the speed these Arm chips offer. At my level of programming, I can write programs using Analogread, and digitalwrite to make a whole lot of things going on in my sketch. Where I have ran into problems is that at some point, I require too many tasks to be completed in relation to how long the loop() takes to repeat.

What I like about the faster chips is, that I can add more and more tasks and still not worry about being behind on my timing of reads or writes.

I have had some success with a Maple clone but, I find it easier to work with my Arduino boards most of the time.

Graynomad:
That's the plan, I should grab some 3rd-party library source code and see how it goes. I still have a lot to do before that though.

Trying to port over some code from a few libraries from time to time will be a good gauge of your progress, I would think. Some are much simpler than others, of course. The Leaf Maple guys had some of the simpler libs ported more or less from day one, but I remember seeing one thread where there were still people struggling with getting a FAT library for an an SD card to work properly over a year later! Discouraging. The lack of ported libs, and the lack of tools to port the more complex ones over (straightforwardly) really limited the usefulness of the Maple board, even though the hardware design was very promising, I thought. But despite all the raw power, the humble Arduino Mega2560 was a far more capable platform, IMHO, simply because of the wealth of ready-to-go 3rd party software for it.

On a side note, my suspicions for the delay of the Due is that doing exactly what we are discussing here is actually much easier to discuss than do! It will interesting to see what has been ported and what hasn't when it (finally) comes out.

One reason I suspect the hardware side has probably been done and locked down for some time now is the Android variant of the Due that came out during the Google IO conference quite a few weeks ago now. So, in a sense, it would appear the Due hardware already has been released, so one might assume the wait is in finalising the software. Probably better late than half-baked in that regard, so it may actually be a very good sign. Or a very bad one!

I require too many tasks to be completed in relation to how long the loop() takes to repeat.

The LPC1227 that I'm using was chosen because it's perfect for something else I'm working on. This Arduino port is a side effect really. I see the 1227 as a Mega1284 on steroids, and as I think the 1284 is the perfect general-purpose "Arduino" chip I love the 1227 for being the same only better.

struggling with getting a FAT library for an an SD card to work properly over a year later!

Yikes! I won't spend that much time on a library I think. Actually one issue I have is that I'm stuck with C and no C++, that's because you have to pay for C++ and I'm not sure I want to do that yet. I will have to make that decision soon though as the more I write the harder it will be to port my port :slight_smile:

It will interesting to see what has been ported and what hasn't when it (finally) comes out.

Very much so.

my suspicions for the delay of the Due is that doing exactly what we are discussing here is actually much easier to discuss than do!

My thoughts as well, so I'm not going to kill myself trying to replicate what the Arduino guys are presumably doing as we speak. As I say on the linked web page "to create a familiar environment for Arduino programmers" (by which I mean the language and function calls, not the IDE), I want something that is familiar and that hides all the register twiddling that it still necessary, for example here's the code I've written to set up a UART and associated structures in memory

serialConnection * serialCreate (uint8 port, uint32 baudrate, uint8 data_bits, uint8 parity, uint8 stop_bits,
		uint8 rx_buff_size, uint8 tx_buff_size) {

	LPC_UART0_Type * uart;
	uint32 clkDiv;
	serialConnection * s;

	if (port > MAX_SERIAL_CONNECTIONS) {
		SYS_ERROR (ERR_SERIAL_BAD_PORT | 1);
		SYS_ERROR (ERR_SERIAL_INIT_FAILED);
		return (serialConnection *)ERROR;
	}

	/////////////////////////////////////////////////////////////////
	// Create the serial structure
	s = (void*)safeMalloc(sizeof (serialConnection));

	if (s == NULL) {
		SYS_ERROR (ERR_SERIAL_INIT_FAILED | 2);
		return (serialConnection *)ERROR;
	}

	serialConnections [port] = s;

	s->object_id 	 = OBJID_SERIAL_CONNECTION;
	s->not_object_id = ~OBJID_SERIAL_CONNECTION;

	/////////////////////////////////////////////////////////////////
	//
	switch (port) {
		case SERIAL_UART0:

			/////////////////////////////////////////////////////////////////
			// Setup Rx and Tx pins
			pinFunc (1, FUNC_RXD0);
			pinFunc (2, FUNC_TXD0);

			s->rxPin = 1;
			s->txPin = 2;

			/////////////////////////////////////////////////////////////////
			// Disable this UART's interrupt for the duration
			NVIC_DisableIRQ(UART0_IRQn);

			/////////////////////////////////////////////////////////////////
			// Set a pointer to the UART structure. Note that UART0 and UART1 have different
			// structs but for the purposes of this function they are the same because
			// we are not using the RS-485 features of UART0
			uart = LPC_UART0;
			s->uart = uart;

			/////////////////////////////////////////////////////////////////
			// Create Rx and Tx buffers
			s->RxBuffer = FIFObuffer16Create(rx_buff_size);
			if (s->RxBuffer == NULL) {
				free (s);
				SYS_ERROR (ERR_SERIAL_INIT_FAILED | 3);
				return (serialConnection *)ERROR;
			};

			s->TxBuffer = FIFObuffer16Create(tx_buff_size);
			if (s->TxBuffer == NULL) {
				free (s);
				free (s->RxBuffer);
				SYS_ERROR (ERR_SERIAL_INIT_FAILED | 4);
				return (serialConnection *)ERROR;
			}

			/////////////////////////////////////////////////////////////////
			// Release the UART's reset
			LPC_SYSCON->PRESETCTRL |= (0x1 << 2);		// Set UART0_RST_N bit

			/////////////////////////////////////////////////////////////////
			// Enable the UART's AHB clock
			LPC_SYSCON->SYSAHBCLKCTRL |= (0x1 << 12);	// Set UART0 bit

			/////////////////////////////////////////////////////////////////
			// Set the UART's clock divider
			LPC_SYSCON->UART0CLKDIV = 0x1;  			// divide by 1, may be different later

			/////////////////////////////////////////////////////////////////
			// Get the clock divider for later use in baudrate calcs
			// Could use the constant 1 but maybe this will change later
			// so we just re-read the register
			clkDiv = LPC_SYSCON->UART0CLKDIV;
			break;

		case SERIAL_UART1:
			// NOTE: Comments as per the above UART0 code

			pinFunc (8, FUNC_RXD1);
			pinFunc (9, FUNC_TXD1);

			s->rxPin = 8;
			s->txPin = 9;

			NVIC_DisableIRQ(UART1_IRQn);
			uart = (LPC_UART0_Type*) LPC_UART1;
			s->uart = uart;
			s->RxBuffer = FIFObuffer16Create(rx_buff_size);
			if (s->RxBuffer == NULL) {
				free (s);
				SYS_ERROR (ERR_SERIAL_INIT_FAILED | 5);
			}

			s->TxBuffer = FIFObuffer16Create(tx_buff_size);
			if (s->TxBuffer == NULL) {
				free (s);
				free (s->RxBuffer);
				SYS_ERROR (ERR_SERIAL_INIT_FAILED | 6);
			}

			LPC_SYSCON->PRESETCTRL |= (0x1 << 3);		// Set UART1_RST_N bit

			LPC_SYSCON->SYSAHBCLKCTRL |= (0x1 << 13);	// Set UART1 bit

			LPC_SYSCON->UART1CLKDIV = 0x1; 				// divide by 1, may be different later

			clkDiv = LPC_SYSCON->UART1CLKDIV;

			break;

		default:
			free (s);
			SYS_ERROR (ERR_SERIAL_INIT_FAILED | (port << 8) | 7);
			return (serialConnection *)ERROR;

	}

	/////////////////////////////////////////////////////////////////
	//
	// Start with a blank slate in the Line Control Register
	//
	uart->LCR = 0;

	/////////////////////////////////////////////////////////////////
	//
	// Setup data bits
	//
	if (data_bits < 5 || data_bits > 8) {
		SYS_ERROR (ERR_SERIAL_BAD_DATA_BITS | data_bits);
		data_bits = UART_DATA_BITS_8;
	}
	uart->LCR |= data_bits - 5;
	s->data_bits = data_bits;

	/////////////////////////////////////////////////////////////////
	//
	// Setup stop bits
	//
	if ((stop_bits != UART_STOP_BITS_1) && (stop_bits != UART_STOP_BITS_2)) {
		SYS_ERROR (ERR_SERIAL_BAD_STOP_BITS | stop_bits);
		stop_bits = UART_STOP_BITS_2;
	}
	uart->LCR |= stop_bits;
	s->stop_bits = stop_bits;

	//////////////////////////////////////////////////////////////////
	//
	// Setup parity
	//
	if ((parity != UART_PARITY_NONE) &&
		(parity != UART_PARITY_ODD) &&
		(parity != UART_PARITY_EVEN) &&
		(parity != UART_PARITY_FORCE1) &&
		(parity != UART_PARITY_FORCE0)) {

		SYS_ERROR (ERR_SERIAL_BAD_PARITY);
		parity = UART_PARITY_NONE;
	}
	uart->LCR |= parity;
	s->parity = parity;

	//////////////////////////////////////////////////////////////////
	//
	// Setup baudrate
	//
	uint32_t Fdiv;

	Fdiv = ((SystemCoreClock / clkDiv) / 16) / baudrate;

	uart->LCR |= 0x80;  		// DLAB = 1
	uart->DLM = Fdiv / 256;
	uart->DLL = Fdiv % 256;
	uart->LCR &= ~0x80; 		// DLAB = 0

	s->baudrate = baudrate;

	//////////////////////////////////////////////////////////////////
	//
	// Setup Fractional Divide Register
	//
	uart->FDR = 0x10; // set to default value, does nothing

	//////////////////////////////////////////////////////////////////
	//
	// Setup FIFOs
	//
	uart->FCR = (1 << 0) |		// Enable both FIFOs
			    (1 << 1) |		// Reset Rx FIFO
			    (1 << 2) |		// Reset Tx FIFO
			    (3 << 7);		// RX FIFO trigger level = 14 chars

	//////////////////////////////////////////////////////////////////
	//
	//	 Ensure a clean start, no data in either Tx or Rx FIFO.
	//
	while ((uart->LSR & (LSR_THRE | LSR_TEMT)) != (LSR_THRE | LSR_TEMT)) ;
	while (uart->LSR & LSR_RDR) clkDiv = uart->RBR; 

	//////////////////////////////////////////////////////////////////
	//
	// Clear any line status bits
	//
	clkDiv = uart->LSR;

	//////////////////////////////////////////////////////////////////
	//
	// Setup and enable UART interrupts
	//
	uart->IER = IER_RBR | IER_THRE | IER_RX;

	if (port == 0)
		NVIC_EnableIRQ(UART0_IRQn);
	else
		NVIC_EnableIRQ(UART1_IRQn);

	return s;

}

Now a lot of that is overhead I want to various reasons, but there's also a lot of knowledge required to write into various registers. Much easier to write

my_uart = serialCreate(SERIAL_UART0, 115200, UART_DATA_BITS_8, UART_PARITY_ODD, UART_STOP_BITS_1, 10, 10);

or even

my_uart = serialBegin(SERIAL_UART0, 115200);

Which I guess is the point of a HAL after all.


Rob

Just a thought: If you keep C++ classes in mind while writing in straight C, you can write C code that is pretty close to C++ classes just by using structures and systematically named functions (just as you are proposing above for SerialBegin() or perhaps Serial_Begin()).

Make any future porting from C to C++ reasonably "mechanical". But backporting a lib from C++ to C would still be a lot of work. So the lack of a free or inexpensive C++ compiler might be a critical roadblock in getting lots of support for porting libs across. :frowning:

OTOH, "classic" C is still cool, IMHO, and for new lib development would not be an impediment at all! :slight_smile:

Keep us posted. Your serial port code example looks like an excellent example of what a good HAL should do. Not too much, not too little...

Graynomad:
Yikes! I won't spend that much time on a library I think. Actually one issue I have is that I'm stuck with C and no C++, that's because you have to pay for C++ and I'm not sure I want to do that yet. I will have to make that decision soon though as the more I write the harder it will be to port my port :slight_smile:

It may be the compiler you are using you have to pay for C++, but the GNU Compiler Collection (GCC) offers C and C++ (other languages as well, but those are mostly for hosted systems). In fact on the Arduino, you are using GCC under the covers. The LPC 1227 chip uses a Cortex Arm-M0, and I believe the GCC port is well supported. You might need to provide appropriate libraries and ports of libstdc++ and a C library.

I was an early adopter of C++ when it was first released but I'm pretty rusty with it these days, my last 7 years in the job (and now 13 out of the job) I hardly used it, mostly because the product I worked on had to be straight K&R, no fancy bits were allowed because it had to be ported to all systems known to man. From a PC to a Burroughs mainframe (which has 9-bit bytes BTW, it took some work to figure out why the code died on that machine :)).

So I'm fairly happy to stick with C.

If you keep C++ classes in mind while writing in straight C

I hope that's what I'm doing, for example variables that would normally be globals are only accessible with system calls and all "object constructors" like the serial one I showed return a pointer to the object.

As you can also see I am paying a lot of attention to error detecting and handling, something I think Arduino barely pays lip service to.

So the lack of a free or inexpensive C++ compiler might be a critical roadblock in getting lots of support for porting libs across.

Possibly, as mentioned especially for porting of existing libs which I would say are mostly C++.

OTOH, "classic" C is still cool, IMHO

My sediments exactly, C++ is nice but good C can do almost as much. You can still use the same principles just have to work a bit harder.

It may be the compiler you are using you have to pay for C++,

The LPC Xpresso environment does in fact use GCC, it appears to be nobbled until you get the paid-for version. I assume that's just a switch somewhere and you don't have to do a total new download so maybe it can be unswitched :slight_smile:

I think this is a bit short-sighted of the tool chain people, limit the codes size yes, but not the language.


Rob

Graynomad:
I think this is a bit short-sighted of the tool chain people, limit the codes size yes, but not the language.

Agreed.

Hope the project progresses -- please keep us informed. Hope you find that switch! :wink:

Graynomad:
The LPC Xpresso environment does in fact use GCC, it appears to be nobbled until you get the paid-for version. I assume that's just a switch somewhere and you don't have to do a total new download so maybe it can be unswitched :slight_smile:

I think this is a bit short-sighted of the tool chain people, limit the codes size yes, but not the language.

I would imagine the simplest way to disable the C++ bits is just not ship the C++ compiler and libraries.

The GCC bits themselves are covered under the GNU Public License, but not their libraries/debug support, which you would have to recreate. You might want to check out http://www.linaro.org/ if you want to get past LPC's value add, and just get the free software bits.

Or you can strike off on your own, downloading the toolchain source directly and delving into the builds. As a long term GCC developer (powerpc right now, but I've done other processors in the past, but never ARM nor AVR), I can tell you it is a large learning curve. 8)

I would imagine the simplest way to disable the C++ bits is just not ship the C++ compiler and libraries.

Maybe, but that would required a second download which is frowned upon by most people. There are key entry/activation options in the Help menu so I'm assuming it's all there just needs a key.

I can tell you it is a large learning curve.

I can well imagine, I'd rather be writing code that fighting a new toolchain.

I spent years dicking with search paths, makefiles that piped into SED scripts that begat AWK scripts that piped into...etc etc and I'm well over it. I need a turnkey toolchain and the LPCXpresso seems to be it.

So if that means C and not C++ I guess I can live with that.


Rob

Talking to myself I was wondering whether it worths the effort to replicate the Arduino's API with this microntroller (or any other alike).

Arduino has million of users worlwide, beside zillions of money in infraestructre, but it's the worst tool ever to learn embedded systems. The platform teachs you the most aberrants ways of programming. Everything you've tought not to do so, you apply it in here:

-- Extensive use of global variables
-- Functions without arguments
-- A project of 5000 lines in a single file
-- Too slow
-- Debugging through pins or UART
-- and so on

This microcontroller in particular, LPC1227, cannot be compared with ATMega328 in any sense, starting off with the price, nor its capabilities. So the question arises again:

Does it worth the effort to replicate the Arduino's API?

I don't think so. Anyway the newbie is not aware of the core's power, so for him/her it's the same that the core is an ATMEGA328 or a LPC1227, or any other alike. However, for the intermediate and seasoned user, the core matters, and related tools as well.

So, why not to have a clean and better (in every sense) API that reflects the purpose of that hypotetical platform?

-- 32 bits core
-- Step debugging (through GDB)
-- RTOS ready
-- Multifile projects
-- Truly C++ API and code's projects
-- Low power comsumption

What do you guys think?