Using CompactFlash Cards in Your Embedded Linux System
When designing embedded systems, the designer has many choices to make. If you sell thousands of units, you spend lots of time designing around the most inexpensive parts you can find. You design custom circuit boards and search high and low for the best components that you can get for the cheapest price.
If you build only one or two units, the time and cost needed to design the perfect circuit board and find the best parts can eat up the entire budget for your product. In this case, off-the-shelf parts are the best choice.
Most of my projects are for one or two units, so I'm always looking for inexpensive items I can use. The 25¢ MP3 playing chip that can only be bought in lots of 10,000 doesn't help me at all. If there was a $25 chip that I could get in single quantities, I might be interested.
I am an amateur radio operator and a member of the Tucson Amateur Packet Radio organization (TAPR). This organization is dedicated to the support and advancement of amateur radio digital communications by providing publications, meetings and electronic kits. I recently purchased a couple of IDE CompactFlash adapters from them (see Figure 1). This product is a circuit board that allows you to use standard CompactFlash memory cards as an IDE device in a PC.
Figure 1. IDE CompactFlash Card Adapter
If you have a digital camera or a portable MP3 player, you might have seen a CompactFlash card (see Figure 2). My Nikon digital camera uses these cards to store images. The cards contain between 2MB and 192MB of Flash memory. Flash memory is read/write memory that does not lose its contents when the power is disconnected.
Figure 2. A Dimensionally Indicative Photo of a CompactFlash Card
When building an embedded system based on the Linux operating system, the first problem to solve is how to boot the operating system. It might be possible to purchase an ISA or PCI card that has ROM on it and place your code in these ROMs. I haven't tried this because I haven't found affordable boards to do this.
You could use a floppy drive or hard drive in your embedded device, but I've found that anytime you can reduce the number of moving parts, you increase reliability. These drives also draw a lot of current and generate heat, so they could cause other problems.
Using the TAPR CompactFlash adapter would let you boot the operating system off of a CompactFlash card. These cards aren't terribly expensive, and they are very small. They have no moving parts, so they could be a good solution for embedded projects.
For this article, I show how to build a Linux system on the CompactFlash card. When you boot this system, it starts up a web server. Of course, I have bigger plans for the future, but there just isn't enough room in this article.
The CompactFlash adapter has three connectors and one jumper. It has an IDE connector, a CompactFlash card connector and a power connector. The jumper is used to make this device the master or slave IDE device. It is very easy to install in a system. I used a development system to build the software and a target system to test and run the software. I installed the CompactFlash adapter on the secondary IDE connector of my development box. It is configured as the master device on this controller, so Linux will see the CompactFlash adapter as /dev/hdc on my development box. My development box has a hard drive for the /dev/hda device that has Mandrake Linux 7.2 on it. Almost any Linux distribution should work, though. I also have an IDE CD-ROM on my development system that is the /dev/hdb device on the primary IDE controller.
I am using a 32MB CompactFlash card for my device, but if you want to duplicate my results from this article, an 8MB card will work. The CompactFlash will be used when the target system boots, so it would need to be device /dev/hda on the target. It should be connected to the primary IDE controller and configured as a master device. In this application, it is the only IDE device in the target system. You could connect other needed devices.
If your BIOS supports autodetection of drive parameters, it should detect the card. If it doesn't, you'll have to enter parameters for the cylinders, heads and sectors. Check the TAPR web site for some example parameters you can use. Make sure that you do not install or remove a CompactFlash card while the system is powered on. This could cause data corruption. At the very least, it will confuse the operating system if a device disappears or appears with no warning.
My target system will have an NE2000-compatible Ethernet card in it. Make sure you have the proper drivers for the network card you have in your target system. If you installed the driver as a module, you'll need to modify the /etc/init.d/rcS file also.
After reading Bruce Perens' articles (``Building Tiny Linux Systems with Busybox, Parts 1 and 2'') in the first two issues of Embedded Linux Journal, I decided to build my own system. It would be impossible to use one of the full-size major distributions like Red Hat or Mandrake on such a small CompactFlash card. There are some small distributions that might work, such as Tom's Root/Boot disk or the LEM distribution. You also could use BlueCat or Hard Hat Linux. I decided that Bruce had given me enough information to build my own, so I chose that path. It was a bumpy road, but you can learn from my mistakes and build your own system. I recommend that you try it to learn how the system works.
I used the fdisk command to create two partitions on the CompactFlash card. The command you run is fdisk /dev/hdc. I created a partition for the root directory and one for the /var directory. The root directory should be mounted read-only, so none of the files will get changed even if a program goes crazy when your system is running. The /var directory is writable, so any parameters or changeable data can be placed under /var. I created each of these partitions as 16MB partitions, even though only 5MB of space is used on the root partition. If you are using an 8MB CompactFlash card, be sure to make the root partition 5MB in size and the /var partition 3MB in size.
The second thing you will want to do is build a kernel. I chose the 2.2.18 kernel. Version 2.4 has been released, but I didn't need any of its extra features and I already had the source code to version 2.2.18. If your target system is low on memory, you might consider a 2.0 kernel if it has all the drivers you need. My target system is a Pentium, but if yours is a 386 or 486, make sure you build a kernel that will run on your processor. If you use a 386, you'll probably need to enable Math Emulation because this processor doesn't have a built-in math coprocessor.
It is always a good practice to include only the drivers you need in your kernel. Remember, your target device isn't a 30GB hard drive! You can use modules if you like, but you'll need to load them in your code when you need them. Most distributions give you utilities like depmod and others to make sure all the proper modules are loaded in the right order. If you depend on utilities like these, you'll need to make sure they are installed on your embedded device.
The only driver you'll need to support the CompactFlash card is the IDE disk driver. Linux thinks your CompactFlash card is an IDE hard drive. No other drivers are needed to access it.
After building your kernel, you need to build a root filesystem. I won't go into all the details you need to build a filesystem as this is covered in Bruce Perens' article. I added a web server and some networking tools to my filesystem though. Here's the list of software packages I used:
linux-2.2.18.tar.gz--the Linux kernel
busybox-0.48.tar.gz--many Linux utilities
net-tools-1.57.tar.gz--network support applications
lilo-21.6.1.tar.gz--a boot loader for the kernel
thttpd-2.20b.tar.gz--a small web server
ash-0.2--a small /bin/sh compatible shell
I also copied the following library files from /lib on my Mandrake system. These libraries were used by the packages above: ld-linux.so.2, libcrypt.so.1, libnss_files.so.2, libc.so.6, libnsl.so.1 and libpwdb.so.0.
I used two utilities to help me determine what libraries were needed on my embedded system. The ldd command tells which libraries are dynamically linked to an executable. If we were trying to get our system to run on a 2MB CompactFlash card or a floppy disk, we would need to be very careful how we compiled our code to make sure they were as small as possible. I'm using a 32MB card, so there is a little breathing room. I made almost all of my executables dynamic and copied the proper libraries to my system. If disk space is a problem, you might consider a replacement library. There are libraries that are much smaller than the standard libc library included in most distributions.
The other utility I needed to use includes the strace command. The web server software needs to become the user ``nobody'' on your embedded system. The code needs a few extra libraries to read the /etc/passwd file. For some reason, ldd didn't tell me the web server needed these extra libraries. The strace command will run your target program and tell you every file it tries to open. This is how I discovered the additional libraries that were needed.
There are around 200 files in my embedded Linux filesystem. We don't have the space to cover every one in this article. Please refer to Bruce Perens' article for details on most of these files. I will summarize which files belong in each directory of our filesystem. You can download these files from the Embedded Linux Journal web site:
/bin--the bin directory contains user utilities. Most of these come from the BusyBox program.
/boot--the boot directory contains the kernel and the LILO boot loader files.
/dev--the dev directory contains device files needed in our system. The MAKEDEV command can help you create these. Remember that these take up disk space, so only include the ones you will need.
/etc--the etc directory contains configuration files needed for your system. We'll need eight files for our system. I will go into detail about these files a little later.
/lib--shared libraries go in this directory. Also, if you use modules in your kernel, they will go under the modules directory in /lib.
/mnt--this directory is normally empty, but can be used if you need to mount another filesystem.
/proc--this directory is empty. The kernel will put system status files in this directory.
/sbin--system utilities go in this directory. These utilities affect the entire system. In our embedded system, they are from the BusyBox and the net-tools packages.
/tmp--this directory should be a soft link to /var/tmp. /tmp should be a writable directory. When a program needs to write a temporary file, it will do so in the /tmp directory. Our root filesystem is read-only, so programs cannot write to /tmp. We will mount the /var filesystem as read/write, so we can make a tmp directory under /var.
/usr--the /usr directory contains user program files. Most of the files in our /usr directory come from the BusyBox package. I chose to put the web server and its associated files under the /usr directory.
/var--the /var directory normally contains log files and other writable directories. On our system, the /var directory will be mounted read/write. The only files we'll need to have in our /var directory is a tmp subdirectory and a file that contains our networking configuration. The reason this file is in var is so we can change the networking configuration in the field.
The first program run by the kernel after loading is /sbin/init. This program initializes the system and handles the console terminals and system shutdown. The BusyBox application will provide a basic init program for our embedded system. The init program gets its parameters from the /etc/inittab file:
::sysinit:/etc/init.d/rcS ::askfirst:/bin/sh ::ctrlaltdel:/bin/umount -a -rThe first line tells init to run the rcS script immediately. The second line tells it to start a shell on the console when initialization is complete. The third line tells it to unmount any filesystems when the system goes down.
The rcS script is shown in Listing 1. It should mount all the filesystems, start up the network and start the web server. Notice that we look to see if the file /var/etc/network exists. If it does, we source the file. This allows us to change the networking parameters, such as the IP address and gateway address on the fly. If the file doesn't exist, there are default parameters in the script. Using DHCP might be a good idea also, but I didn't implement it in my system. Here are the contents of the /var/etc/network file:
NETWORK_IP=192.168.1.2 NETWORK_MASK=255.255.255.0 NETWORK_BROADCAST=192.168.1.255 NETWORK_GATEWAY=192.168.1.254
Make sure to change the IP addresses to match your network.
There are a few other configuration files that you will need on your system. The /etc/fstab file is needed so the mount command knows where each filesystem should be mounted:
proc /proc proc defaults 0 0 /dev/hda2 /var ext2 defaults 1 1
/etc/thttpd.conf, /etc/nsswitch.conf, /etc/passwd and /etc/group are needed by the web server. When the web server starts, it becomes a non-root user. The nsswitch.conf tells it where to find user and group information. Users are in /etc/passwd, and groups are in /etc/group. You can copy the /etc/passwd and /etc/group file from your system. Only include the users and groups that are needed. The nsswitch.conf file is shown in Listing 2. It tells applications to look in files to find users and passwords.
For this application I chose the tiny-turbo web server. This web server is very small and takes up very little memory. It also allows CGI programs, so you can write web programs. This would let you write a web application as the user interface to your devices. The web server is installed in the /usr/httpd directory. The configuration file is in /etc/thttpd.conf file and is listed as:
dir=/usr/heepd/html chroot cgipat=/cgi-bin/*
Now that the partitions are made, the filesystem created and the kernel built, you need to install a boot loader. The BIOS in PC hardware will load the first sector of the first hard drive when the system boots. You need to have software on your system that will load your Linux kernel from the code in this first sector of the hard drive. I used the LILO boot loader because I've been using this loader for years. It can do so much more than just load a single Linux kernel, but it can do our simple task also.
I had problems making it load the boot-loader software on the CompactFlash card in my development system because the card was addressed at /dev/hdc in my development box. The card will be located at /dev/hda in the target system. Fortunately, there is a way to do it. I found instructions in the LILO mini-HOWTO. See Listing 3 for the contents of the /etc/lilo.conf file. Assuming that you have mounted your CompactFlash card as /mnt/hda1 on your development system, the command to load the boot loader on your CompactFlash card is:
/mnt/hda1/sbin/lilo -r /mnt/hda1 -C etc/lilo.conf
Be very careful with this command. It is possible to replace the boot loader on your development system by mistake. It could cause your development system not to boot properly. It's always a good idea to keep a boot floppy around just in case.
You should now be ready to test your CompactFlash boot device. Shut down your development system and remove the CompactFlash card. Install the card in your target system and turn it on. For testing purposes, it is best to have a display adapter in your target system. If this isn't possible, you can configure the kernel to use a serial port as the console device. If you have a display connected to your target, you should see the kernel messages as the kernel boots. When the kernel is finished booting, it will prompt you to press Enter to start a shell.
Congratulations! You have built a Linux operating system for an embedded device from scratch. You can now test the web server. You can check the web server from another computer. Go to the URL http://126.96.36.199/ in a browser on another computer. Make sure to substitute your IP address in the URL. If you get a welcome screen, it worked. There is a very simple CGI program on the embedded system at http://188.8.131.52/cgi-bin/test.cgi that you can try.
As you can see, installing an embedded Linux system isn't very difficult. Now that the OS is installed and working, you just need to write the code for your embedded system and copy it to the CompactFlash card. This is something that anyone with a little knowledge of Linux can accomplish. Good luck building your embedded system!
Jay Sissom is a systems analyst in the computing department at Indiana University in Bloomington, Indiana. He is very busy finishing an Electrical Engineering degree. Amateur radio and playing the piano are two of his hobbies. He likes building electronic things in what little spare time he has between work and school. You can send him e-mail at email@example.com.