Bare-metal Teensy 3.1 Libraries
(Last modified 20 July 2014)

Updated 20 July 2014
I've added support for a recursive-descent parser, which is a string processing tool commonly used in interpreters and compilers.  It's a cool way to add integer math functions to your embedded programs.  It's lightweight, easily modifiable, and should work on nearly any platform, not just Teensy.

I have received email from people having problems compiling my code from my makefiles.  In all cases, the problem has been caused by not updating the VPATH variable in the project's makefile.

You need to edit the project's makefile (such as spitest.mak), locate the line that sets the value for VPATH, and change the directory path to match your installation.  For example, if you have put sysinit.c and crt0.s in a project folder different from the one I used, edit the directory path assigned to VPATH to point to that directory.  If VPATH is not set correctly, your builds will not be able to find these routines and your links will fail because make can't build sysinit.o or crt0.o.



The Teensy 3.1 (Freescale KL20 MCU) is a lot of fun to play with, darn cheap, and has a bunch of I/O on-board.  Since writing bare-metal libraries from scratch can be a pain, here is a set of basic I/O libraries to help you get started.  These are intended for hobbyists who want to code in C without the Teensyduino or other prebuilt IDE/library suite.  Even if you do use Teensyduino or the like, you might still like a peek under the hood..

Please note that all references to devices in this page use zero-based numbering; the first UART is UART0.  I don't use Teensyduino or any other 'uino tools, so I don't know what their numbering convention is.  But the card that comes with the Teensy 3.1 refers to UARTs 1, 2, and 3, which the Freescale docs call UARTs 0, 1, and 2.  When in doubt, consult the schematic and Freescale header file (mk20d7.h).

This code was edited using Visual Studio C++ 2008 Express Edition and compiled with the CodeSourcery GCC toolset.  You can find details on setting up and using these tools here.

Functions available for each library are defined in an associated header file (xxx.h), which resides in the /include folder in your Teensy 3.1 development tree.

All library files discussed here are built with the above toolset and written to the /library folder in your Teensy 3.1 development tree.  If you need to change any directories, refer to the associated Makefile (xxx.mak) for the library.

You can find a link to a compressed collection of these files at the bottom of this page.

Please note that many of these source files are in a state of flux and contain code that has been commented out or blocked with #if 0 clauses.  This represents code I've expereimented with or have set aside for later.


UART
The UART library code is in a source file named uart.c using Makefile uart.mak.  It compiles to a library named libuart.a.

UARTInit() initializes a UART.  This function takes a UART selector (0-2) and a baudrate.  The baudrate is specified as a uint32_t containing the actual baud; to select 152,000 baud, use a baud rate argument of 152000.  Upon exit, the selected UART will be configured for the baud rate at 8 data bits, one stop bit, no parity.  Additonally, the UART just initialized automatically becomes the active UART.  This means later calls to UART I/O routines, like UARTRead(), will use this UART until you change the active UART.

UARTAssignActiveUART() allows your program to change the active UART.  This lets you quickly change the target of your serial I/O.  This function takes a UART selector (0-2).  Upon exit, it returns the selector of the previously active UART.  Your code can use this information to restore the active UART, if necessary.

UARTWrite() outputs a number of bytes to the active UART.  Note that this routine does NOT use a null-terminated string, it sends a counted block of bytes.  This lets you send binary data through the active UART, should you choose.  This function takes a pointer to the char buffer and a count of the number of bytes to send.  Upon exit, this routine returns the number of characters sent.

UARTAvail() returns the number of characters waiting in the active UART's recieve queue.  Your code can use this function to poll the active UART without having to lock, waiting for chars to arrive.

UARTRead() moves characters from the active UART's receive queue to a buffer.  This function takes a pointer to the destination char buffer and a count of the number of bytes to transfer.  This function locks until the requested number of characters have been transferred.  Upon exit, this function returns the number of chars transferred.

All UART handlers use polling on transmit and interrupt on receive.  You can adjust the size of the receive interrupt queue by editing the uart.c file.


termio
The termio library code is in a source file named termio.c using Makefile termio.mak.  It compiles to a library named libtermio.a.

This library contains a set of character and string I/O routines.  The original source code came from Martin Thomas' website for one of his STM projects.  I've modified some of the code to support multiple UARTs and added a string input routine that supports background operation while waiting for the user to type a string on the active UART..

In general, these routines are similar to the common character and string I/O routines, such as puts() and printf().  However, these routines do not need nor support the newlib functions (notably, sbrk()).  To avoid confusion, the names of these routines generally have an "x" prefix, as in xputs().

xatoi() converts a character string to a 32-bit integer.  It was specifically coded to support the other termio routines, but may be of use to other routines, so it is accessible in the library.  This function takes a pointer to a pointer to a character array, assumed to hold a null-terminated string, and a pointer to a 32-bit variable where the converted number will be stored.  Upon exit, this routine returns non-zero if conversion was successful, else it returns 0.

xitoa() converts a 32-bit integer into a null-terminated string.  Again, it was originally written to support the other termio routines, but might be of use in other programs.  This function takes a 32-bit value, a 32-bit radix, and a 32-bit maximum length.  However, the radix and length values may be negative; these conditions act as formatting instructions.  If you intend to use this xitoa() function, check carefully through the source code so you understand how to set up the arguments properly.

xputc() writes the char passed in its argument to the active UART.

xgetc() reads (with blocking) a character from the active UART and returns that char to the caller.

xavail() returns the number of chars currently in the recieve queue of the active UART.

xputs() writes the null-terminated string pointed to by its argument to the active UART.  This routine supports many of the standard C escape sequence; refer to the source code for details.

xprintf() writes a formatted string to the active UART.  This function emulates much of the printf() functionality, but data formatting is limited to integers only.  Refer to the source code for details on supported formatting.

put_dump() writes a block of data to the active UART, formatted as a traditional memory dump.

get_line() reads (with blocking) a line of input from the active UART and writes those chars as a null-terminated string to the buffer passed in as an argument.  This function also takes a maximum length for the input string.  This function supports backspace for line-editing.

get_line_r() reads a line of input from the active UART and writes those chars as a null-terminated string to the buffer passed in as an argument.  This function also takes a maximum length for the input string.  Unlike get_line(), however, this function also takes as an argument a pointer to an integer variable used to hold an index into the holding buffer.  This routine will copy any available chars into the holding buffer, then return immediately to the caller.  If this function returns a 0, the user on the console has not yet entered a CR to terminate the string.  If this function returns a 1, the user has entered a CR; the string will be terminated with a null but the CR will not appear in the string.


SPI
The SPI library code is in a source file named spi.c using Makefile spi.mak.  It compiles to a library named libspi.a.

The Teensy 3.1 uses the 64-pin variant of the KL20 device, which is the same die used in the 100-pin variant.  This means the Freescale engineers had to drop 36 die signals when they created the 64-pin device.  Unfortunately, one of the signals dropped was SCK for SPI1.

This means SPI0 is a full-function SPI channel, while SPI1 contains only MISO and MOSI.  This crippled SPI can still be used if your target device isn't a true SPI device but only needs a serial stream.

My SPI library tries to compensate for the crippled SPI1 by supporting a bit-banged SPI channel, SPI2.  My SPI2 supports some of the regular SPI features, such as bit fields of 4 through 16 bits, but does not support variable SPI clock frequency.  The SCK frequency for SPI2 is going to be appriximately the core clock divided by 32; for a 64 MHz project, this works out to about 2 MHz.  SPI2 also does not support slave mode.  Feel free to modify the source code to add other features; if you do, please share!

SPIInit() configures the selected SPI channel (0-2) for master mode.  SCK frequency is determned by the value passed in argument sckfreqkhz (in kHz).  SPI transfer size is defined by the number of bits (4-16) passed in argument numbits.  The SPI channel will be configured for CPHA=0, CPOL=0.  SPI transfers will be done by polling the SPI status register; interrupt on complete is not supported.  Upon exit, SPIInit() returns the SCK frequency; this might be less than the requested value, due to system clock and prescaler limits, but will not exceed the requested value.

Note that using SPIInit() to configure SPI2, a bit-banged SPI channel, still requires a non-zero value for sckfreqkhz, but that value will be ignored.

SPIExchange() sends a single value to the selected SPI channel and returns a value from that channel.

SPISend() sends a single value to the selected SPI channel.

Note that for hardware SPI (SPI0 and SPI1), routines SPIExchange() and SPISend() are essentially duplicates; you can simply call SPIExchange() and ignore the returned value.  However, SPI2 pays a speed penalty in SPIExchange() as it bit-bangs in the response.  If you need maximum speed on SPI2 for send-only transfers, use SPISend().


PIT
The PIT library code is in a source file named pit.c using Makefile pit.mak.  It compiles to a library named libpit.a.

The Periodic Interrupt Timer (PIT) allows your code to generate precise timing intervals, firing a selected interrupt on completion.  The KL20 supports four PITs, 0 through 3.

PITInit() configures a PIT channel (0-3) for interrupts based on either a number of microseconds (passed in argument usecs) or in a number of peripheral clock tics (passed in argument tics).  You must pass a non-zero value to only one of these two arguments; pass a 0 to the unused argument.  Argument priority assigns the interrupt priority for the selected PIT; legal values are 0 (highest) to 15.  Argument isshandler assigns the address of a void function that expects a single char argument.  Upon exit, this routine returns the PIT reload value.  Note that the selected PIT will be running upon exit; make sure your ISR code is prepared for an interrupt.

The function pointed to by argument isrhandler must be suppled in your code.  This is the ISR that will process a PIT interrupt.  When control enters your handler function, it will pass in a single char argument, which is the PIT channel number (0-3) that caused the interrupt.  Your code can use this info to decide how to process the interrupt.  Note that your ISR does not need to clear or rearm the PIT; that has already been done.

PITStop() disables the selected PIT.

PITStart() reenables the selected PIT.  The first interval following a call to PITStart() is guaranteed to be a full interval.


SD/SDHC
The SDcard library code is in a source file named sdcard.c using Makefile sdcard.mak.  It compiles to a library named libsdcard.a.

This code is the current version of a body of code constantly undergoing changes and tweaks.  It appears to be reliable but has not been thoroughly tested and I would not be surprised if a bug or two surfaces.  I certainly wouldn't rely on it for a product; consider yourself warned.

SDRegister() provides the connection between the SD card library and your target device's SPI channel.  Note that the SD card library does absolutely NO manipulation of I/O lines!  All SPI transfers and all chip-select toggling must be done by code in your project.

This routine requires three arguments, each a pointer to a function.  Argument pselect points to a function that pulls the SD card's chip-select line low.  Argument pxchg points to a function that writes an eight-bit value to the SD card via SPI and also returns a value read from the card.  Argument pdeselect points to a function that pulls the SD card's chip-select line high.  Upon exit, this routine returns SDCARD_OK if registration was successful, else it returns SDCARD_REGFAIL.

SDInit() initializes the SD card and records the card type in global variable SDType.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.

SDReadBlock() reads 512 bytes of data from the address passed as argument blocknum and saves that data to the buffer pointed to by argument buff.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.

SDWriteBlock() writes 512 bytes of data from the buffer pointed to by buff to the SD card at the address passed as argument blocknum.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.

SDStatus() checks the current status of the SD card and the registration of the SPI information.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.

SDReadOCR() reads the SD card's 4-byte OCR data and writes it to the buffer pointed to by argument buff.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.

SDReadCSD() reads the SD card's 16-byte CSD data and writes it to the buffer pointed to by argument buff.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.

SDReadCID() reads the SD card's 16-byte CID data and writes it to the buffer pointed to by argument buff.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.

SDWriteCSD() writes the 16 bytes of data in the buffer pointed to by argument buff to the SD card's CSD data register.  Upon exit, this routine returns SDCARD_OK if successful, else it returns an appropriate error code.


FAT file system
The FatFS library code is in a source file named ff.c using Makefile ff.mak.  It compiles to a library named libff.a.

This is a slightly customized version of ChaN's FAT32 file system; you can find the original code here: http://elm-chan.org/fsw/ff/00index_e.html

I've talked at length about how much I've used this code and how much of a service ChaN has done for the hobbyist community, so I won't belabor the point here, other than to say it should be a part of every embedded hobbyist's toolkit.

The FatFS library code uses a ffconfig.h file to customize ChaN's code for use in the Teensy.  Feel free to change the settings based on ChaN's excellent comments, then rebuild the library.

Refer to ChaN's documentation and source files for full details on the supported API calls.

Note that FatFS requires one routine for accessing device-specific hardware.  This routine is get_fattime(), used to generate a timestamp for file writes.  I don't yet have RTC code for the Teensy, so I kludged together a bogus substitute, which always returns the timestamp of 11:05:01 on 28 Jul 13.  If you have an external RTC or have enabled support for the KL20 RTC, please substitute your code; and please share!


Recursive-descent parser
The recursive-descent parser (RDP) library code is in a source file named rdp.c using Makefile rdp.mak.  It compiles to a library named librdp.a.

An RDP translates a string of characters into a numerical value using assigned hierarchy of operations.  For example, you can pass the string "1 + 2 * 3" to an RDP and get back the integer value 7.

The code for this RDP handles 32-bit signed integers and includes basic math operations (add, subtract, multiply, divide), modulus, and exponentiation.  It supports unary negationn, logical AND, OR, and XOR, and order of operation by parentheses.  It accepts numeric literals in decimal, hexadecimal (0x), or binary (0b) radices.

Operators are based on the standard C operators, with the exception of exponentiation:

Addition        +
Subtraction     -
Multiplication  *
Division        /
Logical-AND     &
Logical-OR      |
Logical-XOR     ^
Exponentiation  **
Unary negation  -

The library consists of one function call:

        uint32_t        rdp(char  *str, int32_t  *answer);

The matching rdptest program shows how easy it is to add the parser to your project.  Here is a simplification of the rdptest program:

        while (1)
        {
            answer = 0;

            error = rdp(buff, &answer);
                        xprintf("Answer = %d 0x%08x\n\r", answer, answer);
                }

Note that the calling program should always reset the variable holding the result to 0 before calling rdp().  In practice (and in the rdptest program), you would check the error code before assuming the answer is believeable.

This parser is a distillation of the code from my SBasic compiler of years ago.  The SB parser did much more than this simple version, including converting the input string to a series of tokens reordered for later execution by a run-time executive.  I have left parts of the original tokenizer code in rdp.c so you can see a bit of what is involved in making a full tokenizer.  You don't need it to run the RDP routine, but perhaps you'll find a review interesting.


Test cases
I've built a very simple demonstration program for each of the above libraries.  These are not rigorous tests; they simply show examples of how to call some of the routines.  Each test program contains the name of the tested library followed by the word "test".  For example, the SPI test program is spitest.c.


Summary
Here is a link to the collection of library files and the corresponding test files.  Note that this is a large archive (about 20 MB) because it includes the full suite of Freescale Kinetis header files.

I've built quite a few simple projects using the above libraries.  Between the elegance of the Teensy design and the wide range of library support, even projects that normally take a boat-load of code go together quickly.  This all makes the Teensy a fun bare-metal platform.  I'm looking forward to more projects in the coming months.

Please drop me an email if you find these libraries useful or (even better) if you improve or add to them.



Home