IoT

lpicp: Turn any board running Linux into a PIC programmer

This is probably not what you think it is. It is not an application that controls ICD2, PicKit2 or any form of custom LPT PIC programmer for a PC (you can use MPLAB X for that). What lpicp does is provide a user space application and kernel driver to perform the functionality of a PIC programmer on embedded systems running Linux. After deploying these modules onto such a system (and assuming the hardware is layed out properly), you can run something like:

lpicp -x write -d /dev/icsp0 -f my-pic-program.hex

and have your embedded system program a Microchip PIC microcontroller via an ICSP bus.

Implementing the ICSP protocol and bus

Microchip has devised a simple 4 wire bus to allow for programming the PIC devices. In reality, the majority of the physical protocol is over 2 wires – pgc (clock) and pgd (data). The bus master (the programmer) clocks out 4 command bits followed by 16 data bits. The commands are specified in the flash programming spec for each device but they can be divided into three main groups:

  • Read
  • Write
  • Execute instruction

To implement a high level operation like reading a device requires a mixture of commands, spanning across all the three groups; simplisticly you’d have to execute instructions to prepare the PIC, write the address pointer and read data. You really must read the flash programming spec to understand the specifics, but the jist is the same for all high level operations (erase/read/write).

Since no hardware controller exists for operating the bus transactions, we are required to bit bang it. The hardware protocol itself is very simple (although very inconsistent across operations and devices). I had the option of implementing the bus protocol entirely in userspace and using the platform independent GPIO infrastructure but this does not achieve the desired bus timing (despite the fact that the PICs are very lax in their timing, especially in maximum values, I still wanted the bus to look as clean as possible).

To do this, I offloaded all bus transactions to a platform driver (mc_icsp) using callbacks to the platform (which toggles the GPIOs). All timing is configured when registering the platform driver and user space interacts with it via /dev using 4 ioctls:

  • transmit: send 4 bit command and write 16 bits of data
  • receive: send 4 bit command and read 16 bits of data
  • send command only: send only the 4 bit command with a configurable waveform
  • send data only: send only 16 bits of data

Userland

The user space application, lpicp, takes care of receiving the command from the user, parsing Intel HEX records and performing the higher levels ICSP protocol. This is very device specific so there is framework in place to allow for adding devices without ugly ifs() and #ifdefs.

The application is built using cmake, which outputs liblpicp.a and an lpicp executable. The executable adds a command line layer, but you can just as well link to liblpicp.a to integrate PIC programming into your application. The lpicp executable can be run with the following arguments:

  • -x, –exec: r, read | w, write | e, erase | devid
  • -d, –dev: ICSP device name (e.g. /dev/icsp0)
  • -f, –file: Path to Intel HEX file
  • -o, –offset: Read from offset, Write to offset
  • -s, –size: Size for operation, in bytes
  • -v, –verbose: Verbose operation

The application uses libGIS‘s ihex.c module to parse the Intel HEX record file outputted by PIC compilers. The files contain data records to be written to program, configuration and eeprom memory. The application knows where to burn each record according to its address.

Getting the source

This project is really in its infancy and will probably only ever serve as reference. I was contracted to implement this for PIC18F452 and PIC18F4520 and this is what works. Browse the README @ github to understand the limitations of the current version and build instructions.

You can get the lpicp code @ the lpicp github repo and the driver can be found here.

U-Boot / Linux bringup by example

This post describes the steps required for setting up an Ubuntu based compilation and debugging environment for U-Boot and Linux 2.6 w/a JFFS2 filesystem on a custom PPC board, using an Abatron BDI3000. It mostly discusses the environment and doesn’t dive too much as to how to customize the packages for your specific board (this is the hard part, but also extremely board specific). While you will probably not follow this guide from start to finish, it serves as a reference for what is the minimal amount of work required to build U-Boot and Linux for a PPC8xx board and a supplement to the DENX DULG, which is a must read.

It assumes that Ubuntu is already installed on the station, but nothing else. On the host side we mostly need the ELDK – a single package that contains all the cross tools needed (compiler, linker, debugger, tools to generate a file system, etc.).

A basic U-Boot/Linux target requires four things:

  • A U-Boot binary (u-boot.bin) generated by customizing the U-Boot sources for your board, compiled using a cross-compiler (ppc_8xx-gcc in our case) and burned to the correct offset in the flash
  • Linux image (uImage) generated by compiling the untouched Linux sources
  • A Flat Device Tree BLOB (.dtb file), generated by compiling (using a specialized compiler) a textual DTS file which describes busses, offsets and devices of the target board
  • A Root file system containing all the files we’d like to appear on the target once it’s up, packed into a single file. This file is generated by packaging up a directory from the ELDK which contains all the tools compiled for the architecture using the appropriate file system maker (in our case, mkfs.jffs2).

Step 1: Getting the sources

In this step we install git (the source control system), eldk (the development environment), U-Boot (the bootloader) and Linux (the OS). Start by getting eldk 4.2 for PPC (iso) from here (The file is under /pub/eldk/4.2/ppc-linux-x86/ppc-2008-04-01.iso). Keep in mind that this may be very slow as the file is 1.9 GB; I used GetRight on windows to pull the file from all the mirrors concurrently.

Create a working directory in your home directory:

mkdir ~/dev
cd ~/dev

Get git:

sudo apt-get install git-core

Get U-Boot sources:

git clone git://git.denx.de/u-boot.git u-boot/

Get Linux sources:

git clone git://git.denx.de/linux-2.6-denx.git linux-2.6-denx/

Step 2: Installing the ELDK

Create an installation directory under your working directory (e.g. ~/dev/eldk)

mkdir eldk
cd eldk

Create a mount point for the ELDK installation CD and mount it (prepend the path of the iso to the file):

sudo mkdir /mnt/eldk-inst
sudo mount -o loop,exec ppc-2008-04-01.iso /mnt/eldk-inst/

Install the eldk for the ppc8xx architecture

/mnt/eldk-inst/install ppc_8xx

Add eldk to path by appending the following in ~/.bashrc using a text editor of choice (make sure to show hidden files). After this is done, make sure to re-open a terminal window for the next steps:
PATH=$PATH:~/dev/eldk/usr/bin:~/dev/eldk/bin
export PATH

Test this by running the following in your dev directory:

ppc_8xx-gcc

You should see the standard gcc “no input files” error.

Step 3: Preparing a JFFS2 filesystem

The ELDK contains a basic yet functional file system that we can use. It is packaged as a ramdisk image and we need to extract it so that we can build a JFFS2 image. Strip the prepared ramdisk of its 64 byte header, revealing a gzipped file. Unzip it to receive ramdisk_image

cd ~/dev/eldk/ppc_8xx/images
dd if=uRamdisk bs=64 skip=1 of=ramdisk.gz

Mount the image and give everyone access/execute permissions to it

sudo mkdir /mnt/rootfs-inst
sudo mount -o loop ramdisk_image /mnt/rootfs-inst
sudo chmod -R 777 /mnt/rootfs-inst/

Generate the JFFS2 image assuming a sector size of 128KB (0×20000), in this case

mkfs.jffs2 -b -r /mnt/rootfs-inst -e 0x20000 -o image.jffs2

Step 4: Finishing up environment installation

The development station requires a TFTP server for two reasons: the BDI3000 TFTPs a configuration file each time it loads and U-Boot will fetch the Linux images via TFTP once it is burnt to flash. We must therefore install a TFTP server and set up its directories.

First, we’ll install atftpd:

sudo apt-get install atftpd
sudo gedit /etc/default/atftpd (modify to USE_INETD=FALSE)
sudo invoke-rc.d atftpd start
sudo chmod 777 /srv/tftp

We then create three directories @ /srv/tftp:

mkdir /srv/tftpd/u-boot
mkdir /srv/tftpd/linux
mkdir /srv/tftpd/bdi3000

Configure the BDI’s server IP address. See BDI documentation for more info how to do this.

Step 5: Compiling U-Boot to receive a burnable u-boot.bin

Customize U-Boot for your board. For a custom board with SDRAM and 8MB Flash, I had to:

  • Create a directory under /board/myboard and have a Makefile, config.mk, u-boot.lds and myboard.c. You can copy these files from the standard ep8xx board
  • myboard.c implemented the following functions:
    • checkboard: Just printed a banner
    • initdram: Initialize the SDRAM
    • boardpoweroff: Reboot hook (didn’t implement anything here)
    • ftboardsetup: Fixed up the FTD BLOB passed to Linux with information gathered by U-Boot
  • Add a header file under include/configs/myboard.h with all the preprocessor directives. Again, you can reference the standard ep8xx board, but make sure to define CONFIGOFLIBFDT and CONFIGOFBOARDSETUP. Otherwise Linux will not boot because it doesn’t receive the FDT BLOB
  • Add the board to the main Makefile (you should be able to easily understand using ep8xx as an example) Clean, just to make sure

    make distclean make clean

And then compile it

make my_board
make

Step 6: Compiling Linux

Start by installing ncurses, required for menuconfig

sudo apt-get libncurses5-dev

At this point you will have to customize Linux. For PPC this mostly means creating a compilable device tree file (with a .dts extension) which describes the offsets of the core peripherals and such (instead of instantiating drivers in code). This is not trivial and is yet another syntax/structure to learn, but at least you have examples in the form of dts files for existing boards. In addition, you will also have to create a default configuration in arch/powerpc/configs, a custom platform in arch/powerpc/platforms (which will, at least, initialize I/O pins) and modify arch/powerpc/platforms/Kconfig + arch/powerpc/platforms/Makefile to support your new platform. Simply go by example of existing boards and check out this PDF.

Build Linux and the FDT BLOB (DTB file)

make ARCH=powerpc CROSS_COMPILE=ppc_8xx- my_board_defconfig
make ARCH=powerpc CROSS_COMPILE=ppc_8xx- uImage
make ARCH=powerpc CROSS_COMPILE=ppc_8xx- my_board.dtb

Step 7: Burning and configuring U-Boot

The following sections assume the following offsets: Flash starts at 0xFF800000 which is where the Linux kernel resides (until 0xFF9DFFFF), the DTB file resides at 0xFF9E0000 until 0xFF9FFFFF, the JFFS2 filesystem image resides at 0xFFA00000. Finally, U-Boot resides at 0xFFF00000.

Set up your BDI with a configuration script that will allow you to attach to the board and erase its flash. Define the sectors on which u-boot will reside so that the erase command will know what to delete. Copy u-boot.bin (from ~/dev/u-boot) to /srv/tftp/u-boot and then telnet to the bdi.

reset
erase
prog 0xfff00000

Open a serial connection to the target. Setup u-boot to auto-load the kernel at 0xFF800000 and pass the DTB @ 0xFF9E0000.

setenv ethaddr 00:e0:6f:00:00:01
setenv ipaddr [target-ip-address]
setenv serverip [your-pc-ip-address]
setenv bootargs root=/dev/mtdblock2 rw rootfstype=jffs2
setenv bootcmd bootm 0xff800000 - 0xff9e0000
setenv bootdelay 3
saveenv

Step 8: Burning Linux, the DTB and the Root filesystem

Copy image.jffs2 created earlier and uImage, my_board.dtb from arch/powerpc/boot to the /srv/tftp/linux directory. Using the U-Boot console, download the images to RAM via tftp. After each download, note the size printed by u-boot in hex: Bytes transferred = 1284822 (139ad6 hex). You will need this for the next step.

tftp 0x100000 /linux/image.jffs2
tftpboot 0x400000 /linux/uImage
tftpboot 0x800000 /linux/my_board.dtb

Burn the images to the flash

erase 0xffa00000 0xffcfffff
erase 0xff800000 0xff9fffff
cp.b 0x400000 0xff800000 [linux-size-in-hex, e.g. 139ad6]
cp.b 0x800000 0xff9e0000 [dtb-size-in-hex]
cp.b 0x100000 0xffa00000 [jffs2-size-in-hex]

Power cycle the board – Linux should be up. Easy!

Rotary encoder based cooking timer

Continuing my experimentations with cooking timer user interfaces, I stumbled upon a cute little rotary encoder on Sparkfun. It immediately looked like a good match for the project. The thing is that all cooking timers I’ve seen have either a simplistic interface requiring many clicks to set the time (like clicking the “minutes” button 50 times) or a complex keypad with way too many buttons.

At first I thought I’d need 2 axes (axis 1 would increment by 1 minute and the other axis by 15 minutes) but fiddling around with the trackball showed me that it was not necessary; that I’d just need one axis with tactile feedback (that is, a clicky switch rather than a smooth flowing motion of the controller) and some selection mechanism to switch between 1 and 15 minutes.

The Sparkfun rotary encoder fit the bill – 12 steps for full rotation and clickable! Perfect. I wired up the encoder on the breadboard, wrote the software and then fabricated two one layer PCBs.

The Hardware

The schematic is fairly straightforward – a PIC18F2525 sits in the middle connected to the rotary encoder (gray code and button), 3 indication LEDs and a buzzer (link to the schematic at the end of the article). The main board connects to the seven segment display mezzanine via two connectors. This board is not, in any way, efficient power wise – it sucks about as much energy as humanly possible.

4 digit Seven segment display

Another Sparkfun gem is the integrated 4 digit seven segment display. The 4 digits are internally connected and there’s even a colon in there – we save a LOT of nets and do not need to flip the 3rd digit to create a colon as there is a dedicated colon segment. The display isn’t huge, measuring in at 4 centimeters across, but will suffice for this test project. Hooking this up to the uC is very standard so I won’t dive into details. The only thing worth noting is that we need to wire up the colon anode to VCC and the cathode to the uC for controlling, holding it at logic high. When we want to light it up we sink the current on the uC by setting the cathode to logic low. You could and should ramp up/PWM this line so as to have consistent brightness with the multiplexed digits (i don’t do it in this project).

Clickable rotary encoder

The rotary encoder from Sparkfun gives two inputs: rotation (CW/CCW) and button click. The rotation interfaces takes the form of a standard gray code output: two channels connected to the uC input pins (w/external or internal pull ups) and ground. The difference between this encoder and the trackball from my previous project is that this is a purely mechanical interface, as opposed to a digital interface with the ALPS trackball. This means that while we received a perfectly filtered gray code signal from the IC on the ALPS, here we receive gray code directly from the mechanical rotation. Each rotation simply connects the channels to ground shifted by a quarter phase. In 99% of the cases we receive a good signal, but those extra 1% bounces need handling. We will see the waveforms in a bit.

The integrated button is as simple as it gets – just like any other button pulled high and shoved into the uC input.

The Software

Rotary encoder state machine

In my previous trackball project I needed to receive an event whenever the trackball moved. This was achieved very easily by simply analyzing the gray code and detecting valid gray code transitions. The difference here is that we don’t want to react whenever a bit change occurs, rather when a complete, valid cycle occurs. This cycle indicates that the user has performed one “click” on the control (there are 12 such clicks in a complete rotation). By detecting such valid cycles we filter out two unwanted events:

  • Mechanical bounces (see rightmost waveform below)
  • The user overshooting and performing an extra “half” click (see middle waveform below)

The software needs to look for two certain valid 4 step flows: a CW click and a CCW click, as shown in the leftmost waveform.

Normally, both channels are pulled up and sit there at logic 1. The CW flow starts by the first channel going to 0 followed by the second channel. The first channel then goes back up to 1 and so does the second. Any deviation of this flow should be filtered out, but mechanical bounces need not cause the motion to be ignored. To fulfill these requirements, I implement this using a two 3 bit “shift register” variables and a simple state machine.

The 3 bit shift registers hold the last 3 polled values of each channel. The polling rate must be fast enough such that 3 polls indicate a stable logic value (you can calculate the polling time required by measuring the shortest bit cycle you can and then diving this by at least 5 – for example if the channel bit is 1ms long when you turn the knob as fast as you can, you should set your polling to at most 200uS). The shift register value is then fed into a pattern matcher – this simply compares the value to either 111 (stable 1) or 000 (stable 0) at any given time. If a match occurs, a bit is appended to the value of the shift register – 0 for channel 0 and 1 for channel 1 (for example, if channel 1 has detected a transition to 0, the new value will be 1000, uniquely identifying an event that channel 1 has transitioned to 0). This new value is then fed into a state machine looking for a 4 consecutive values indicating the 4 step valid flow.

Let’s understand this by looking at how I detect the CW flow above. At first both channels are at 1 so the shift register value is always 111 for both channels. Since we want to detect a stable transition to 0, we set the search pattern for both channels to 000. 3 consecutive polls equal to 0 satisfy the requirements of debouncing mechanical spikes. Once channel 0 goes to 0 for more than 3 polling cycles, the pattern match occurs and the value is fed to the appending mechanism which appends 0 (for channel 0). This value is then fed into the state machine which sees that it indicates the first step of the 4 step flow (channel 0 went down to zero).

The pattern matching for channel 0 is then set to 111 (we want to know when the channel goes back to 1) and the state machine is set to expect the next step: 1000 (channel 1 goes down to 0). In a valid flow channel 1 goes down to zero, the pattern match occurs, 1 is appended to the shift register and the value is fed to the state machine. The search pattern for channel 1 is then changed to 111 and the state machine is set to expect the next step: channel 1 goes back up to 1.

This flow continues until the entire flow occurs correctly. If at any point something is done in the wrong order, the entire state machine and pattern matching resets. This mechanism filters out any “half cycles” performed by the user. Only when the user clicks the rotary to the next notch will the above flow occur. This state machine is written in quadenc.c: quadenc_isr().

Seven segment display

Operating the 4 digit SSD is exactly the same as operating 4 separate seven segments – you select a digit via a transistor, illuminate the desired segments, pause, select next digit and repeat. There are tons of tutorials on seven segments out there so I won’t dive into details. The only thing worth mentioning here is that in my implementation I do two obvious things:

  1. Whenever I want to display a number I modify a RAM array containing 4 bytes – each byte representing a digit and each bit representing the appropriate segment (the position into the byte is the position of the segment in the I/O port). Doing this does not affect the value displayed to the user – it’s all done in RAM – see sseg.c: sseg_setDisplay().
  2. A timer interrupt (shared with the rotary encoder polling mechanism) is set to occur ever X interval (a few milliseconds is fine). When the interrupt is raised I load the next byte (of the 4 bytes modified by the application) into the port. Can’t get any shorter than that. See sseg.c: sseg_isr().

The user interface is a standard state machine driven user interface. The video below has a short walkthrough of the interface. As such, I’ll focus on the button event handling mechanism because this is somewhat reusable over projects.

Just like for every mechanical button, software debouncing is required. This is done by polling the input pin every X ms and shifting this into a 3 bit shift register (no interrupt is used). Once a stable pattern is detected (either 000 for button down, 111 for button up) we can handle this event. However, our timer exports a few cases for the user:

  • Single click: pausing an active timer or switching between 1 and 15 minute intervals
  • Double click: Reseting the timer
  • Long press: Start a timer
  • Press and turn: Select a timer

This requires some code to handle, done in a state machine separate from the user state machine (see ui.c: ui_checkButtonEvent()). The outputted events from this button handling machine are fed into user interface to actually perform the desired event.

Video demonstration and waveform explanation

The following video gives an overview of the timer user interface (interesting unto itself) and then at just over five minutes in shows waveforms for working with the rotary encoder.

Full source can be found on the project Github page. Related Downloads:

Thing UI based on trackballs gestures

I set out to use a trackball to control an alarm clock. It seemed to me like an interesting experiment in UI and another small lesson in electronics. I envisioned using the two axes for controlling hours and minutes (separately and simultaneously) and the tracking speed to specify increments (faster scrolling would mean larger incremcents).

Finding a trackball was not easy. In fact I could only find two – a small clicky trackball from RS (sold also by digikey and mouser – feels pretty crappy) and a beautiful ALPS trackball that was sold by an Aussie company that seemingly unloaded its stock to Roman Black (I got mine directly from him after I mailed him cash). The trackball was surprisingly accurate – no hardware or software filtering was required to receive pure gray code.

After hooking up the trackball I understood that I could not rely directly on its movement to control numeric values with precision as it was too unpredictable. Using a simple “N trackball ticks equal 1 value” did not translate into an acceptable user experience as it proved too difficult to stop at the value we wanted (often going too far and then having to scroll back to correct or conversely, having too great of a dead zone). This would probably work well enough for applications which do not require pin point accuracy like controlling contrast, brightness, movement, etc. However, for setting a precise time this was not good enough.

I decided to decode the trackball gestures instead of numeric value (number of ticks). This would mean defining a set of hand movements against the trackball that would translate into an operation on the clock. To simplify, I decided to control a cooking timer and not an alarm clock as I had already felt at this point that the trackball was not a suitable interface but still wanted to explore detecting gestures. I defined two simple movements – a slow track and a fast track; slow offsetting the timer by one minute and fast by 15. For example, if a user would like to set 50 minutes he would fast track 3 times and then slow track 5 times (on one axis).

Below is an overview of detecting slow/fast tracks with the ALPS trackball. I was able to achieve > 95% success in detecting these two simple gestures (that is less than 1 in 20 movements are detected incorrectly) but found that the trackball is not comfortable enough to operate a timer (currently working on something that hopefully is, details will follow when complete) and decided not to hone this mechanism.

The hardware layer: connecting everything up

The hardware prototype was standard. I used a PIC18F2525 running at 40MHz (10MHz crystal w/PLL – of course you do not need this much horsepower for this task), 4 seven segment displays (multiplexed), a MAX232 for serial and the ALPS trackball. You can read more detail about the ALPS hardware at Roman Black’s excellent page and more about quadrature encoders on google (there are many articles about rotary/quadrature encoders and there’s nothing I can add on this matter). The only worthy thing to report is that I connected the trackball quadrature encoder outputs (2 for each axis) directly to the PIC’s I/O port.

The low level software: Decoding the quadrature encoding

This portion of software needs to perform decoding of the gray code outputted by both axes of the trackball and indicate to the higher layers how many ticks occurred for each axis in each direction. Since, as explained earlier, the gray code is perfect at the PIC’s inputs – there is no special debouncing or filtering required at this stage. There are a few ways of going about this:

  • Polling the inputs in a while() loop: Very simple to implement but should your application be busy doing something like populating an LCD while a change occurred, it might skip an encoder transition and you will receive false counts.
  • Polling the inputs periodically, using a TMR interrupt: By setting up a timer interrupt, you could perform the same polling as using a while() loop, only there would be no chance of missing an encoder transition assuming your application does not mask interrupts for more than a few microseconds. The downside to this is that your application will always spend cycles looking for encoder transitions even when you don’t want it to.
  • Using IO interrupts: Setting up the PIC so that it will generate an interrupt on a change of one of the 4 trackball outputs. I could not do this with the 18F2525 because it did not have 4 free interrupt on change pins.
  • Using a dedicated hardware decoder/counter: For example, LSI offers a hardware quadrature decoder (you would need one for each axis). Some PICs even offer a quadrature decoder peripheral (QEI) – e.g. 18F2331.

I opted for using the TMR polling mechanism as it was the simplest to code and efficient in terms of cost and availability. It is not trivial to guess the polling frequency and there is no “right answer”. On one hand you want to poll as infrequently as possible to preserve cycles but on the other – you might miss transitions and have false reports (you would move the trackball in one direction yet still receive some counts indicating it has moved in the other). I found that there was an extremely small error rate (probably up to the mechanics of my finger slightly moving in the wrong direction as I started to spin the trackball) – at most 2 counts – when I had properly configured the polling rate.

The reason for there being no “right answer” is because this depends on your trackball resolution (256 ticks for one full rotation for the ALPS) and, more importantly, how fast you think the user can spin the trackball (this could be up to the trackball electronics, as well). I found that I could not spin the ALPS faster than about 320uS for one encoder full phase (that is, the bit length of a single encoder output was no less than 320uS). Since the second output will transition in the middle of this bit, we need to poll at least twice in 320uS (or about 160uS). I opted to poll at twice that rate and set up my TMR to 80uS with excellent results. When I used incorrect values (say 250uS) it seemed that for every movement in one direction there is about 10%-30% movement in the other. This happens due to missing transitions and incorrectly reporting the direction as a result.

Once we’ve settled on a way of when to read the quadrature encoder, we need to make sense of the inputs. I’ve adopted Roman Black’s method of comparing bit A of the previous state with bit B of the current state – whether they are equal or not defines the direction. This requires shifting and comparing the inputted bytes but it’s fairly straightforward. The low level software would simply increment or decrement a counter accordingly. This counter was read periodically (using a delay) by the high level portion of the software.

Note: While this project only uses one axis, I thought about how to separate the X and Y axes. It occurred to me that usually a user would only want to operate one axis at a time but would probably fail to move the trackball in only one axis. To solve this “axis bleeding” I devised a theory to check for the ratio between X and Y axis counts during a given high level period. If they were close to 1:1 they would register to both axes, otherwise only the axis with the larger amount of counts would be registered, while the other would register as 0. This would mean that if the user moved the trackball up (intentionally) but also inadvertently to one side, this would be filtered out. However, if the user moved the trackball diagonally – this would indicate that he would like to modify both axes at the same time. Using the count ratio this would be possible – the ratio allowed between X and Y counts would determine the width of allowed diagonal movement.

The high level software: detecting gestures

I wanted to see how exactly a short pulse and fast track looked in terms of counts vs. time to see how I can differentiate between them. I outputted the counts I received to the serial and then plotted a graph. It is very easy to see that slow tracks take longer and contain generally up to 20 counts per high level polling period while the fast tracks are very short with a high amount of ticks per period. I also wanted to have only one event per track – regardless of how long it took. Continuous movement is not feasible with a finger driven trackball.

Using this information a devised a simple state machine.

  • The state machine would sit in Idle state looking at how many counts occurred in the last period. If the counts were 0, this would forever be the case. Once counts are received (indicating that the low level software has detected trackball movement), the values are compared against a threshold – if below, we would move into debounce state; If above, we would move directly into fast track state.
  • When in Debounce state, we wait for the next readout – if it is zero, we would go back to idle and declare no event (thus effectively debouncing small unintentional movements); If it is still not zero but below the high threshold, we would move into slow track state. If it is above, we go into the fast track state.
  • When in Slow track state, we will wait for readouts to occur – if one of them is above the threshold we would move into fast track state. If they are all below the threshold we wait until we receive a zero count (indicating the trackball stopped) and then declare a “slow track” event.
  • Fast track state is very similar – wait until a zero count arrives and then declare “fast track” event.

The state machine is depicted as follows:

The code

You can see the code for this project here. You may also notice that I included a “consecutive medium hits” counter – this accounts for cases that the user performed a fast track that occurred exactly during the polling period. This would cause the large number of counts to be split over two polling periods and not pass the fast track threshold.

Of course, this is a very primitive and non-optimized implementation I prepared over a lazy Saturday. Should your implementation benefit from a finger driven trackball, you may find the ideas in this article useful.

The Dot Factory: An LCD Font and Image Generator

The Dot Factory is a small open source tool (MIT licensed) intended to generate the required C language information to store many fonts and images, as efficiently as possible, on a microcontroller. These fonts are then uploaded via the LCD driver (see the Drivers and Modules page for a few) to the actual dot matrix LCD. It is written in C# for Visual Studio 2008 and has been tested on Windows XP, 2003, Vista, 7 and Linux via Mono.

Working with dot matrix LCDs with microcontrollers, while not difficult, is tedious. The actual LCD controller allows us to upload simple visual data (dot on or dot off) into the LCD’s dot matrix, but not much else. It is up to our software to decide what to upload when we want to draw lines, circles and more importantly – text.

While there are software graphic libraries that allow us to generate a character “on the fly” using vector graphics (the character is described as a series of drawing commands that allow scaling and decoration) – these are much too complex and large to integrate in a microcontroller environment. Consequently, we must store the exact appearance of a character as a series of 1s and 0s, equivalent to a “dot on” “dot off” on the LCD, and upload this as a bitmap when we want to display text. While it is possible to generate this manually, it is desired to have a tool to do our grunt work by converting windows fonts (given a size and decoration) into a series of bitmaps.

Using The Dot Factory

TDF is comprised of two panes – the input pane on the left (what you want to generate) and the output pane on the right (the generated output, in C code). The input pane can accept either a font of your choice (for writing text to the LCD) or an image. When generating a font, you have the option of either generating all the available letters (by selecting “All” in the Insert Text box and clicking the plus button) or by typing in which letters, numbers or symbols you are actually using in your application (for example: 0123abcd). If you are writing a simple application that has only a few sentences, you can type them wholly in this box without fear of duplicating letters – TDF takes care of that by discarding any duplicates. This way only the letters you use will take up space.

Once you have completed setting up what it is you’d like to generate (be it an image or font), select the output method in the output pane. If you are using the LCD drivers on this website, you want it to generate an MSb first output, otherwise images will come out wrong. If you have a compiler that supports the “0b” binary specifier, you can select “binary” rather than “hex”. This will allow you to visually see the pixels you will set and allow for manual touch up by the user without having to calculate hex and experimentation. Click generate and your C code will be outputted to the text box below. Copy paste this into your application (it is recommended to put this in a separate module, not your LCD driver module, for organizational reasons).

Note that 5×7 and 5×8 fonts cannot be generated using this tool. While some TTF fonts can render characters this small they are usually distorted to the point of uselessness. You can download a ready made five by seven font here. I ripped this font from text file a while ago, so apologies to the uncredited author.

What does it generate?
For font generation, three entities are generated.

  • The character bitmap array: This holds the actual characters as a bitmap (only the characters selected in the input pane). Each byte represents a single vertical page sent to the LCD. All vertical padding is removed from the characters
  • The character descriptor array: Allows O(1) mapping between a character’s ASCII value and required meta information about the character – namely its width in bits and its offset into the character bitmap array. When the LCD driver needs to find where character X is located in the bitmap array, it will jump to index [X - font.startCharacter] in the descriptor array. The startCharacter is the first character (that is, the character with the lowest ASCII value) used for this font. By defining a startCharacter we can reduce the number of elements in the descriptor array.
  • The font information: This element is essentially the font descriptor for this font. It holds information regarding this font like the name of the character bitmap and descriptor arrays, the font start character and how many pixels wide a space character is for this font. The LCD driver will replace the space character with empty pixels (this saves both processing time, space in the character bitmap array and space in the character descriptor array – since the space is the first ASCII character and is commonly used).

The generated structures are generated with documentation, but you may want to see a sample bitmapDb header file for detailed info on the character descriptor array and font information structures. For image generation, only the image’s bitmap array and size descriptors are generated. Note that the height value is pixels (bits) and width values are in pages.

Revision history

I usually release versions according to user demand. If you think TDF can use whatever feature, drop me a line. When enough users request something I invest the time to add it.

  • Version 0.1.4 (2jun12): Support for “Column Major” mode where each byte represents a column rather than a row – contribution by Paul Ryland
  • Version 0.1.3 (31mar12): Linux support with Mono – contribution by mru00
  • Version 0.1.2 (29may11): Fixed width/height being swapped. Added support for configuring image descriptor format (bits/bytes). Thanks geo for the heads up and suggestion
  • Version 0.1.1 (25may11): Added support for multiple descriptor arrays with a double lookup. Before this version TheDotFactory could generate Unicode characters but the lookup tables were usually too huge to be of any use. Using this feature, a double lookup is employed, allowing for fast lookups for characters residing at disparate ranges. See the video for an explanation (will be posted in the next few days). In addition to this, added support for specifying character ranges instead of inputing the actual characters. For example, <<100-120>> will generate characters for ASCII characters 100 to 120. Thanks a bunch to Archis Bhave for inputs and testing. Source is now distributed via github.
  • Version 0.1.0 (15dec10): Added support to format the generated variable names (thanks SpiralBrain), added end character indication to font information (thanks Nick Jensen), added the ability to save to clipboard from File menu and added the ability to save the source/header to file via file menu (don’t remember who, but someone wondered why this wasn’t in. I personally think all fonts should be in a single module and so I opted for copy/paste, but to each his own)
  • Version 0.0.9 (06jun10): Fixed a bug that prevents the space character from being generated (broken in 0.0.8 – thanks Thomas Kibalo)
  • Version 0.0.8 (29may10): Fixed two unreported crashes (clicking generate without entering any text and when a newline existed in generated text), added the ability to copy the outputted text by using a context menu Version 0.0.7 (28may10): Added ability to select whether character descriptor array is to be created and which character will be used to visualize the font (thanks Christian Treczoks), syntax coloring automatically disabled when generating large amounts of text (will be fixed properly next version), properly handled bitmaps with no black pixels in them (displays error instead of a crash), some minor cosmetics
  • Version 0.0.6 (03mar10): Bug fix for image generation (tried to save a temporary file for debugging in a custom directory) – thanks to Nir Shemeshfor pointhing this out!
  • Version 0.0.5 (23dec09): Added support for rotation (90 degree increments), space character generation, width (bit/byte) selection of character width and font height, optional generation of character height/width and font height, structures are now generated with documention, input text and font is persisted throughout invokations of the application, persistent preset management – add, edit, delete output configuration presets
  • Version 0.0.4 (31jul09): Added a space to end of comments in the char descriptor array to prevent preprocessor misinterpreting ‘\’ as a newline
  • Version 0.0.3 (30jul09): Added output configuration: hex/binary, MSb First/LSb first, configurable padding removal, comment control, flip X/Y and more
  • Version 0.0.2 (28jul09): Vista support
  • Version 0.0.1 (25jul09): Initial release (flip not supported, output format not supported, not tested on Vista)

Download

To run this executable, you must have the .NET framework installed. The stable binary has been in the wild for a month at least with no major bugs reported. Non stable binary contains new features and bug fixes (see revision history).

Up to date source can be found at Github.