Aeolus Development

Newlib Port and Basic Microcontroller Support

ARMStick 100

ARMStick 101

ARMStick 102

In System Programming (ISP)

Documentation and Application Notes

Downloads

Other Resources

Warranty and Returns

Contacts

Product Feedback and Suggestions

Consulting

Whats New

Privacy Policy

Store

By Robert Adsett

Sunday, January 01, 2006

Note: This library of routine only provides the adaptation of newlib to the LPC2000. See the download page for pointer to the required newlib sources and build instructions.

Contributions are welcome.

* Marks items that are new or changed since release 3.

Most changes since release 3 are changes to the UART support.  Support added to polling version for checking to see if any characters are waiting to be read.  Also check both TEMT and THRE flags on transmission now. UART1 device driver contributed by Cyril Holweck Added (Thanks)

Some comment corrections.

Microcontroller Support

In addition to straight newlib functionality this port provides support for setting up and controlling aspects of the LPC2000. In addition it provides a timer that can be used to wait for a specific amount of time.

Register Support

lpc210x.h provides declarations of the peripheral registers in the 2104/5/6. lpc210x.ld is a linker script that provides the actual definitions.

lpc2119.h provides declarations of the peripheral registers in the 2119/2129/2194/2292/2294. lpc2119.ld is a linker script that provides the actual definitions.

Enums

Several enums are used to control the microcontroller. These enums are typedef'd to make their use in parameters clearer.


VPB_param -- used to set the ratio between the CPU clock and the VP bus clock. Possible values are:

  • VPB_DIV1 -- VPB clock = CPU clock
  • VPB_DIV2 -- VPB clock = CPU clock / 2
  • VPB_DIV4 -- VPB clock = CPU clock / 4

MAM_CONTROL -- used to control the memory access module. Possible values are:

  • MAM_disabled -- MAM disabled.
  • MAM_part_enable -- MAM partially enabled.
  • MAM_full_enable -- MAM fully enabled.

Typedefs

PROCESSOR_STATUS - Holds the processor status.  Not the current status but usually the value returned from DisableInterrupts. Should be treated as a magic cookie and not manipulated.

INT_MASK - A set of bits for masking the processors interrupt status.

INTERRUPT_SOURCE - Interrupt source, a number identifying the interrupt source.  Each variant header file (I.E. "lpc2119.h") will define its own set of values that are valid.

INT_PRIORITY - The priority to assign to a given interrupt source using the VIC

Functions


ActualSpeed

unsigned long ActualSpeed( void);

Returns the actual operating speed of the microcontroller. It takes into account the operation of the PLL. Relies on SetNativeSpeed being called earlier with the correct value.


chars_waiting*

int chars_waiting( int fd);

Returns the number of characters waiting to be read. Returns 0 if no characters are waiting.If the device does not support the call then it will return < 0. In that case errno will be set to indicated that an unsupported call was made.

  • int fd -- number referring to the open file. Generally obtained from a corresponding call to open.

DisableInterrupts

PROCESSOR_STATUS DisableInterrupts( INT_MASK imask);

Disables IRQ/FIQ at the CPU level using the flags passed.  Returns the current state of the processor status register (I.E. before the disabling of interrupts).

  • INT_MASK imask (r0) -- Flag bits to be set in the processor status register.

INTERRUPT_DISABLE_MASK - A constant available from "lpc_sys.h" to pass to DisableInterrupts to turn off both IRQ and FIQ.


RestoreInterrupts

void RestoreInterrupts( PROCESSOR_STATUS stat);

Restores IRQ/FIQ using the processor status passed (presumably saved from a previous call to DisableInterrupts).

  • PROCESSOR_STATUS stat (r0)  -- Processor status register containing previous interrupt state to restore.     

 




GetUs

unsigned long long GetUs( void);

Returns the microseconds since the timer was started.


MinimumAchievableWait

unsigned int MinimumAchievableWait(void);

Returns the minimum number of microseconds that can be expected to be reasonably accurate in a WaitUs call. Values below this will not fail but may wait for a larger or smaller amount of time than requested. Indeed, the actual wait could well be identical for different values of requested wait time used below this.


SetDesiredSpeed

int SetDesiredSpeed( unsigned long desired_speed);

Set the CPU to desired frequency. Relies on an earlier call to SetNativeSpeed to inform the timing routines of the correct oscillator speed. Returns 0 if successful an error otherwise.

  • unsigned long desired_speed -- CPU operating frequency in kHz.

SetMAM

int SetMAM( unsigned int cycle_time, MAM_CONTROL ctrl);

Set up the MAM. Minimal error checking, not much more than a wrapper around the register. Returns 0 if successful,   an error number otherwise.

  • unsigned int cycle_time -- number of cycles to access the flash.
  • MAM_CONTROL ctrl -- Mode to place MAM in. One of:
    • MAM_disabled
    • MAM_part_enable
    • MAM_full_enable

SetNativeSpeed

int SetNativeSpeed( unsigned long speed);

Set the oscillator frequency of the external oscillator. This is used to inform the routines that deal with CPU frequencies what the starting point is. Any error here will be multiplied later. Note: There is no way to determine or verify this value so we have to trust the caller to get it right. Returns 0 if successful, an error number otherwise.

  • unsigned long speed -- external oscillator/crystal frequency in kHz.

StartClock

int StartClock( void);

Starts up the clock used for internal timing. Attempts to match the desired clock speed (CLOCK_SPEED) and initializes timing_scale_factor to a compensating scale. Returns 0 if successful, otherwise an error number. Note: Should be called only after all clocks have been set up. Otherwise time scale will not be correct.


VICInit

int VICInit( void (*defadd)(void));

Initializes the VIC (Vectored Interrupt Controller). Does anything required for initial setup. In particular it ensures that the default vector is set up correctly and all interrupt sources are disabled. Returns 0 if successful, otherwise an error.

  • void (*defadd)(void) -- Address of the default interrupt service routine, must be non-zero.

VICSetup

int VICSetup( INTERRUPT_SOURCE interrupt_source, INT_PRIORITY priority, void (*service)(), unsigned int FIQ);

Sets up an interrupt source in the Vectored Interrupt Controller. Although it checks against some errors there is no  check against the interrupt source since the allowable range varies from variant to variant. Returns 0 if successful, otherwise an error.

  • INTERRUPT_SOURCE interrupt_source -- Source of the interrupt to setup. This is the interrupt number assigned to the peripheral in hardware.
  • INT_PRIORITY priority -- The priority of the interrupt if assigned to the IRQ. This also ends up being the index to its vector. Not used if the interrupt is assigned to the FIQ.  If the interrupt is assigned the lowest priority the service routine passed in (if not 0) will replace the current default vector routine.
  • void (*service)() -- A pointer to the service routine to use for this vector. Should be 0 for FIQ, may be 0 for non-vectored IRQs.
  • unsigned int FIQ -- Set to non-zero if interrupt will be serviced as an FIQ.

VPBControl

int VPBControl( VPB_param p);

Control the clock divider on the peripheral bus. Returns 0 if successful, otherwise an error number.

  • VPB_parm p -- requested VPB to CPU freq rate.

VPBRate

unsigned long VPBRate( void);

Finds and returns the rate of the clock on the peripheral bus (in Hz).


UsToCounts

unsigned int UsToCounts( unsigned int us);

Converts to internal units in counts from uS. Other modules use this counter for a timebase so this needs to be available to them. Returns number of counts corresponding to us. Saturates on overflow so for large time periods it is possible to get a result lower than requested.

  • unsigned int us -- microseconds to convert to counts.

WaitUs

void WaitUs( unsigned int wait_time);

Wait for 'wait_time' us (microseconds) Will break wait into multiple waits if needed to avoid saturation.

  • unsigned int wait_time -- microseconds to convert to counts.

Device driver structure

Structure used to define a device driver.

struct device_table_entry {
   const char *name;  /* Device name.   */
   
        /* Device open method.*/
   int (*open)(struct _reent *r,const char *name, int flags, int mode);
   
        /* Device close method.*/
   int (*close)(struct _reent *r,int file);
   
        /* Device read method.*/
   _ssize_t (*read)(struct _reent *r,int file, void *ptr, size_t len);

        /* Device write method.*/
   _ssize_t (*write)(struct _reent *r,int file, const void *ptr, size_t len);
   
        /* Device ioctl method (controls device operating parameters. */
   int (*ioctl)(struct _reent *r, int file, unsigned long request, void *ptr);
   };
name
The name of the device. Used by open to find the device to open.
open
Implements open for this device. The parameters are the same as those passed to open_r. open_r (and through it open) breaks down the name passed to it and uses the device portion of the name to search for a matching device. When a matching device is found the non-device portion of the name is passed to this function. For many devices this portion will be an empty string. For some devices this will be the name of a sub-device or file. It is the responsibility of the driver to check and parse this name. The format of the name passed to open should be of the form "device_name/device_specific_sub_dev_or_file". The portion up to the / is used to match against name. The / and everything after it is optional and the portion after the / will be passed on to the device driver. Some devices may require it, some may require that there be only the device_name. On success this routine should return a non-negative number that can be used by other calls to refer to the specific file or device opened (drivers that only implement a single device will typically return 0 on success). open combines the return value with the driver index to form an unique file number newlib uses to refer to the open device/file. On failure returns a negative number and sets errno to indicate the error source.
close
The inverse of open. Takes similar parameters to close_r. close_r first breaks out the driver index from the file number passed to it and passes the remainder to this function. This function should perform any cleanup needed. For some devices drivers this will be an empty operation.
read
Reads up to len bytes from an open file/device. Bytes are read into buffer pointed to by ptr. Returns the number of bytes read or a negative number in the case of an error (errno will be set). May return 0 or block if there are no bytes to read.
write
Writes up to len bytes to an open file/device. Bytes to be written are taken from the buffer pointed to by ptr. Returns a negative number and sets errno in the case of an error, otherwise returns the number of bytes actually written.
ioctl
I/O control. See ioctl_r for a full description. Device specific control. Returns 0 if successful. Otherwise the return will be negative and errno will be set. This is the only entry that may be a null pointer. Set this to 0 if the device does not support ioctl calls, then any attempts to call ioctl for that device will result in an error return and errno will be set to ENOSYS.

List of devices. This list must be provided by the application program. Newlib adaptation uses this to determine what devices are available and how to control them. List is terminated by a null pointer entry. Note: Entries 0, 1, 2 are reserved to address stdin, stdout and stderr. These must be able to work from a standard open w/o needing further setup if they are to be transparent, alternatively they must be setup before any I/O is done. They must also not require the use of a sub-device/file to open successfully.

extern const struct device_table_entry *device_table[];

Included Device Drivers

com1
Serial port driver for Uart 0. Set baud rate before using any I/O. Supports simple polled I/O. Use ioctl to set serial line characteristics.
com1_int
Serial port driver for Uart 0. Set baud rate before using any I/O. Supports simple interrupt driven I/O. Use ioctl to set serial line characteristics.
com2 *
Serial port driver for Uart 1. Set baud rate before using any I/O. Supports simple polled I/O. Use ioctl to set serial line characteristics.
com2_int *
Serial port driver for Uart 1. Set baud rate before using any I/O. Supports simple interrupt driven I/O. Use ioctl to set serial line characteristics.
sys
System device. Originally held some control functionality. Now essentially a dummy that discards anything sent to it.

Error Codes Specific to this port

ELPC_CANT
Can't perform requested operation.
ELPC_OOR
Argument out of range.
ELPC_INTERNAL
Internal error encountered.

IO Control

Control over the characteristics of device is provided through a facility somewhat similar to that provided by Unix and Linux. Unlike that facility it takes a fixed number of arguments with the last argument being a pointer to an input/output structure that varies based on the call being made. The only action defined at the moment is for setting serial line characteristics.


_ioctl_r

int _ioctl_r( struct _reent *r, int fd, unsigned long request, void *ptr);

Support function. Provides a version with explicit re-entrancy variable. If you would normally use open_r directly then this is the appropriate call, otherwise the companion call ioctl is probably better suited. Device specific control. Returns 0 if successful, otherwise errno will be set to indicate the source of the error.

  • struct _reent *r -- re-entrancy structure, used by newlib to support multiple threads of operation.
  • int fd -- number referring to the open file. Generally obtained from a corresponding call to open.
  • unsigned long request -- Number to indicate the request being made of the driver.
  • void *ptr -- pointer to data that is either used by or set by the driver. Varies by request.


ioctl

int ioctl( int fd, unsigned long request, void *ptr);

Support function. Device specific control. A shell to convert requests into a re-entrant form. Returns 0 if successful, otherwise errno will be set to indicate the source of the error.

  • int fd -- number referring to the open file. Generally obtained from a corresponding call to open.
  • unsigned long request -- Number to indicate the request being made of the driver.
  • void *ptr -- pointer to data that is either used by or set by the driver. Varies by request.

Request definitions. These define the requested actions.

UART_SETUP
Set up baud rate parity etc. Pass pointer to a serial_param structure that contains the information on how to set up the serial port.

structure to pass via ptr with ioctl request

struct serial_param {
    unsigned long baud;
    unsigned int length;
    unsigned int parity;
    unsigned int stop;
    };

baud is the required baud rate in Hz. The driver will set the baud rate based on this value and before returning will set baud to the actual baud rate that the driver is set to.

Possible stop bit settings, assign to stop field of serial_param.

UART_STOP_BITS_2
Provide 2 stop bits
UART_STOP_BITS_1
Provide 1 stop bit

Possible parity values, assign to parity field of serial_param.

UART_PARITY_NONE
Set to parity None
UART_PARITY_ODD
Set to parity Odd
UART_PARITY_EVEN
Set to parity Even
UART_PARITY_STICK1
Set to parity stuck on
UART_PARITY_STICK0
Set to parity stuck off

Possible word length values, assign to length field of serial_param.

UART_WORD_LEN_5
5 bit serial byte.
UART_WORD_LEN_6
6 bit serial byte.
UART_WORD_LEN_7
7 bit serial byte.
UART_WORD_LEN_8
8 bit serial byte.
UART_SETUP

Interrupt Control

The assembly include file interrupt.inc provides support for writing short interrupt shells in assembly. Two macros are provided, one (InterruptEntry) to setup the beginning of the interrupt service routine and one (InterruptExit) to exit from the ISR cleanly. Note that these macros do not provide the acknowledgement needed to finish a routine.


InterruptEntry

InterruptEntry

Assembly macro. Called at the beginning of an ISR it saves the working registers r0, r1, r2, r3, r12 and r14 and the saved processor status register. It does not re-enable any interrupts or change stacks so the stack on exit from the macro will be the stack appropriate to the exception. It is the responsibility of the user to ensure that the stack is large enough. The registers saved are sufficient that a call to a C function can be made safely after exit from the macro. If the user is writing code that makes use of other registers these additional registers must be preserved as is normal for the ARM procedure call standard. Use the companion macro InterruptExit at the end of the ISR.


InterruptExit

InterruptExit

Assembly macro. Called at the end of the ISR to restore the registers saved by InterruptEntry. It disables all interrupts before restoring the processor to the state it was in before the interrupt.

Example Interrupt Shell

An example of the use of the interrupt macros taken from "uart0_ishell.c".

The include of "interrupt.inc" provides the macros for entry and exit.

Two external references are made. One to "VicVectAddrRead" is used to signal the end of the ISR to the VIC hardware. The other (to "Uart0Service") is the C routine that does most of the actual work responding to the interrupt.

Finally the shell itself ("Uart0InterruptShell") is made public so that it can be used to setup the VIC.

The shell is very straightforward.

  • The macro InterruptEntry starts the ISR, saving the critical registers.
  • A standard call is then made to Uart0Service to deal with the interrupt (this will be safe since any registers used in addition to the ones saved in the InterruptEntry macro will be saved by the standard C code).
  • A write to the VIC signal that the interrupt has been dealt with and frees it up for the next interrupt.
  • Finally the saved state from the entry is restored and the routine exits using the macro InterruptExit.
.include "interrupt.inc"

	.extern	Uart0Service
	.extern	VICVectAddrRead
	.global Uart0InterruptShell

Uart0InterruptShell:
	InterruptEntry

	bl 	Uart0Service		/*  Call routine that does the 	*/
					/* actual work			*/

	ldr	r0, =VICVectAddrRead	/*  Let VIC know we are done.	*/
	str	r0,[r0]	InterruptExit
	

Linker Files and Startup

The startup file "crt0.s" makes use of some information defined in the linker control file to set the environment for the compiled code. This allows for straightforward customization of the environment as needed while keeping a common startup for multiple projects.

The values most likely to be modified are the following:

__ram_size__
The amount of ram available. This is used to determine where to place the various stacks. The stacks for the exceptions modes are placed at the top of memory, immediately below them is placed the main stack.
__STACK_SIZE_FIQ__
The size of the stack for FIQ exception mode. Make sure there is enough room here for all the stack space needed by the interrupt including room for any saved registers.
__STACK_SIZE_IRQ__
The size of the stack for IRQ exception mode. Make sure there is enough room here for all the stack space needed by the interrupt including room for any saved registers.
__STACK_SIZE_SUPERVISOR__
The size of the stack for supervisor mode.
__STACK_SIZE_ABORT__
The size of the stack for the abort exception mode. This can be quite small if the abort exception does something simple like loop infinitely.
__STACK_SIZE_UNDEFINED__
The size of the stack for the undefined exception mode. This can be quite small if the undefined exception does something simple like loop infinitely.

The linker files have a number of exception defaults provided like the following:

PROVIDE( undefined_instruction_exception = endless_loop);
PROVIDE( software_interrupt_exception = endless_loop);
PROVIDE( interrupt_exception = endless_loop);

These can be overridden in the main code simple by providing a routine of the same public name. In addition they can be overridden in the startup code (interrupt_exception often is to make efficient use of the VIC).

The linker file also provides definitions for the variant specific registers, using sequences similar to:

/*  Provide address definitions for any peripheral registers */
/* used.       */

/* WD */
PROVIDE( WDMOD = 0xE0000000);

Again, if desired, these can be overridden in the main code. That is probably not useful though. The main purpose for this is to (with help from the variant's header file) provide a simple transparent way to use the peripheral registers on the micro.

Linking to the Library

If you use the -llibrary option to request the use of a library in linking remember that GNU expands that to liblibrary.a. So -lnewlib-lpc becomes a search for libnewlib-lpc.a.

Note (added 2005/05/01)

If you are using a newlib library with stubs already provided (this is likely) you need to ensure that the newlib-lpc stubs get linked in instead. This can be accomplished in several ways.

  • explicitly link in the modules needed. This works but is ugly to maintain.
  • modify the existing newlib to remove the stubs (backup first). This is probably the second best method.
  • rebuild newlib without the stubs to begin with.

ToDo

  • Add a few more device drivers.
  • Expand documentation.
  • Add support for UART1's modem control lines.

Home