Table of Contents
- Compiling and degubbing code for Tiva
- Hardware Abstraction Layer
- Debugging, heap, and display
- Analog Digital Converter and Timers
- Playing Nokia Tunes
- Random Number Generator, Rendering Engine, and the Game
- A real-time operating system
Debugging
To test and debug the SSI code, I connected two boards and made them talk to each other. It mostly worked. However, it turned out that, by default, you can run the OpenOCD-GDB duo only for one board at a time. It's the one that libusb enumerates first. There is a patch that lets OpenOCD choose the device to attach to by its serial number. The patch has not made it to any release yet, but applying it and recompiling the whole thing is relatively straight-forward: clone the source, apply the patch and run the usual autotools combo. You will then need to create a config file for each board that specifies unique port numbers and defines the serial number of the device to attach to:
]==> cat board1.cfg
gdb_port 3333
telnet_port 4444
tcl_port 6666
interface hla
hla_serial 0Exxxxxx
source [find board/ek-tm4c123gxl.cfg]
]==> cat board2.cfg
gdb_port 3334
telnet_port 4445
tcl_port 6667
interface hla
hla_serial 0Exxxxxx
source [find board/ek-tm4c123gxl.cfg]
Separate GDB batch files come handy as well:
]==> cat gdb-board1.conf
target extended-remote :3333
monitor reset init
break main
continue
]==> cat gdb-board2.conf
target extended-remote :3334
monitor reset init
break main
continue
Tweaking the linker script
GCC started generating .init
and .fini
sections that contain no-op
functions:
]==> arm-none-eabi-objdump -d test-06-display.axf
...
Disassembly of section .init:
00007af8 <_init>:
7af8: b5f8 push {r3, r4, r5, r6, r7, lr}
7afa: bf00 nop
Disassembly of section .fini:
00007afc <_fini>:
7afc: b5f8 push {r3, r4, r5, r6, r7, lr}
7afe: bf00 nop
We will discard this code by adding the following to the linker script:
1 /DISCARD/ :
2 {
3 *(.init*)
4 *(.fini*)
5 }
GCC also started generating the stack unwinding code and GDB gets confused in some places if it is not present, so we put it after the code in FLASH:
1 .ARM.exidx :
2 {
3 *(.ARM.exidx*)
4 *(.gnu.linkonce.armexidx*)
5 } > FLASH
See TM4C.ld.
SSI and GPIO
We need both SSI and GPIO to control the Nokia display that we want to use for
the game. Since, in the end, both these systems need to push and receive data,
they fit well the generic interface used for UART. The SSI's initialization
function needs many more parameters than the one for UART, so we pack them all
in a struct. As far as GPIO is concerned, there are two helpers:
IO_gpio_get_state
and IO_gpio_set_state
that just write the appropriate
byte to the IO device. GPIO also comes with a new event type: IO_EVENT_CHANGE
.
1struct IO_ssi_attrs {
2 uint8_t master; //!< 1 for master, 0 for slave
3 uint8_t slave_out; //!< 1 slave output enabled, 0 slave output disabled
4 uint32_t bandwidth; //!< bandwidth in bps
5 uint8_t frame_format; //!< frame format
6 uint8_t freescale_spo; //!< SPO value for freescale frames
7 uint8_t freescale_sph; //!< SPH value for freescale frames
8 uint8_t frame_size; //!< size of the frame in bits
9};
See TM4C_ssi.c and TM4C_gpio.c.
Platforms, the display interface, and fonts
All the devices that are not directly on the board may be connected in many
different ways. To handle all these configurations with the same board, we split
the driver into libtm4c.a
(for the board specific stuff) and
libtm4c_platform_01.a
(for the particular configuration). For now, the only
thing that the platform implements is the display interface. It passes the
appropriate SSI module and GPIOs to the actual display driver. The user sees the
usual IO_io
structure that is initialized with IO_display_init
and can be
written to and synced. write
renders the text to the back-buffer, while sync
sends the back-buffer to the device for rendering. There's also a couple of
specialized functions that have to do only with display devices:
1int32_t IO_display_get_attrs(IO_io *io, IO_display_attrs *attrs);
2int32_t IO_display_clear(IO_io *io);
3int32_t IO_display_put_pixel(IO_io *io, uint16_t x, uint16_t y, uint32_t argb);
4int32_t IO_display_print_bitmap(IO_io *io, uint16_t x, uint16_t y,
5 const IO_bitmap *bitmap);
6int32_t IO_display_set_font(IO_io *io, const IO_font *font);
7int32_t IO_display_cursor_goto(IO_io *io, uint32_t x, uint32_t y);
8int32_t IO_display_cursor_goto_text(IO_io *io, uint32_t line, uint32_t space);
9int32_t IO_display_cursor_move(IO_io *io, int32_t dx, int32_t dy);
10int32_t IO_display_cursor_move_text(IO_io *io, int32_t dline, int32_t dspace);
See IO_display.h.
Platform 01 provides one display device, a PCD8544, the one used in Nokia 5110. It translates and passes the interface calls to the lower-level driver. See pcd8544.c.
If you haven't noticed in the list of the functions above, the display interface
supports multiple fonts. In fact, I wrote a script that rasterizes TrueType
fonts and creates internal IO_font
structures. These can then be used to
render text on a display device. All you need to do is provide a TTF file,
declare the font name and size in CMake, and then reference it in the
font manager. The code comes with DejaVuSans10 and DejaVuSerif10 by
default.
The heap
Malloc comes handy from time to time, so I decided to implement one. It is
extremely prone to fragmentation and never merges chunks, so using free is not
advisable. Still, sometimes you just wish you had one. For instance, when you
need to define a buffer for pixels and don't have a good way to ask for
display parameters at compile time. For alignment reasons, the heap management
code reserves a bit more than 4K for the stack. It then creates a 32 bytes long
guard region protected by the MPU. Everything between the end of the .bss
section and the guard page is handled by IO_malloc
and IO_free
.
1void TM4C_heap_init()
2{
3 uint8_t *stack_start = (uint8_t *)0x20007ff8;
4 uint8_t *stack_end = stack_start-4120;
5 uint8_t *stack_guard = stack_end-32;
6 uint8_t *heap_start = (uint8_t *)&__bss_end_vma;
7
8 MPUCTRL_REG |= (uint32_t)0x05; // enable MPU and the background region
9
10 uint32_t val = (uint32_t)stack_guard;
11 val |= 0x10; // valid
12 val |= 0x07; // highest priority region
13 MPUBASE_REG &= ~0xfffffff7;
14 MPUBASE_REG |= val;
15
16 val = 0;
17 val |= (1 << 28); // disable instruction fetches
18 val |= (4 << 1); // 0x04 == 32bytes
19 val |= 1; // enable the region
20 MPUATTR_REG &= ~0x173fff3f;
21 MPUATTR_REG |= val;
22
23 IO_set_up_heap(heap_start, stack_guard);
24}
See TM4C.c and IO_malloc.c.
A display test
The LCD demo works fine on the breadboard. As you can see, there is a text printed with two kinds of fonts: with and without serifs. Later, the code plays the Game of Life shooting gliders.
Soldering
Since the display works fine, it's safe to do some soldering. We'll use a 9-volt battery as a power source and an LM1086 power regulator to supply 3.3 volts to the microcontroller and other devices.