NFS root on SheevaPlug with Debian

I broke out my SheevaPlug the other day and decided to tinker with it a bit… recently I had some new projects ideas where it would be of use.  I also wanted to configure it with an NFS root to avoid flash wear issues and a way to easily back up the device.  This is an attempt at documenting the process in the hope it may help someone else with all the issues I ran into.  YMMV, damnum absque injuria, etc…

The plug had previously been sitting in my closet for some time, in fact it’s been so long that the Ubuntu Jaunty 9.04 install it came with was very outdated by this point.  Unfortunately the idea of upgrading to any more recent Ubuntu release will not work as they are compiled for newer ARM architectures; the CPU in the SheevaPlug is only an ARMv5 and 9.04 was the last version to support this core.  Ubuntu 9.10 was built for v6 and 10.04 and later target the Cortex ARMv7 core.

However we’re still in luck as Debian has an ARM port which supports Marvell’s Kirkwood platform.  And thanks to some wonderful work by Martin we have an easy to follow process for installing Squeeze on the SheevaPlug.  There’s a few caveats and issues to contend with and you will probably have to upgrade U-Boot (Marvell shipped their own variant which added support for plug computers but newer versions support it directly now).  This is all explained very well on Martin’s Debian installation page.  I recommend choosing the option for loading the installer via TFTP as you will need it later if you want to use NFS root.  I also initially installed Debian on an SD card, for some reason trying a USB drive did not work.

Once the installation is done, boot into your new Squeeze install and prepare an NFS mount on another box somewhere which will serve your rootfs.  I perused the DisklessUbuntuHowTo doc to get a basic idea of how it works, you’ll be doing something similar except the initrd update process will be different – this will be explained below.  Once your NFS share is created on the server side, mount it on the SheevaPlug and copy the files over (obviously replacing the IP’s as necessary):

mount -t nfs 192.168.1.2:/nfsroot /mnt
cp -ax /. /mnt/.
cp -ax /boot/. /mnt/boot/.

The Ubuntu HOW-TO also lists copying /dev but this will not be needed as it is mounted via udev.  On my install /boot was a separate partition so make sure you include that as well.  Edit a few files in the new rootfs on the server-side to reflect a few things – /nfsroot/etc/network/interfaces to assign a static IP and /nfsroot/etc/fstab to replace your rootfs device with /dev/nfs.

You’ll also want to copy over the uImage and uInitrd files from /nfsroot/boot over to your TFTP folder as they’ll be needed to boot the plug.

Now comes the fun part, creating a new initrd.  Unfortunately the one installed by Debian did not support a NFS root so we need to build a new one.  Do this on the server-side as the new initrd will be copied to the TFTP folder as well.  The steps are relatively straight-forward but I recommend you read through the details first to get an better understanding.  The steps I outline are taken from a post on editing initrd’s and another on Debian SheevaPlug installation.

The process consists of the following: copy the kirkwood initrd image, extract it, update the initramfs.conf, re-compress, then rebuild it adding a special header for u-Boot.

~$ mkdir tmp

~$ cd tmp

~/tmp$ cp /home/exports/sheeva_squeeze_nfs_root/boot/initrd.img-2.6.32-5-kirkwood ./initrd.img-2.6.32-5-kirkwood.gz

~/tmp$ gunzip initrd.img-2.6.32-5-kirkwood.gz 

~/tmp$ mkdir initrd

~/tmp$ cd initrd/

~/tmp/initrd$ cpio -id < ../initrd.img-2.6.32-5-kirkwood
25329 blocks

~/tmp/initrd$ ls
bin  conf  etc  init  lib  sbin  scripts

At this point you’ll have the initrd extracted.  Edit the conf/initramfs.conf file and search for BOOT=local, change it to BOOT=nfs.

~/tmp/initrd$ vim conf/initramfs.conf

~/tmp/initrd$ find . | cpio --create --format='newc' > ../initrd.img-2.6.32-5-kirkwood-nfs
25329 blocks

~/tmp/initrd$ cd ..

~/tmp$ ls
initrd  initrd.img-2.6.32-5-kirkwood  initrd.img-2.6.32-5-kirkwood-nfs

~/tmp$ gzip -c initrd.img-2.6.32-5-kirkwood-nfs > initrd.img-2.6.32-5-kirkwood-nfs.gz

~/tmp$ ls
initrd  initrd.img-2.6.32-5-kirkwood  initrd.img-2.6.32-5-kirkwood-nfs  initrd.img-2.6.32-5-kirkwood-nfs.gz

~/tmp$ mkimage -A arm -O linux -T ramdisk -C gzip -a 0x00000000 -e 0x00000000 -n 'Debian ramdisk' -d initrd.img-2.6.32-5-kirkwood-nfs.gz initrd.img-2.6.32-5-kirkwood-nfs
Image Name:   Debian ramdisk
Created:      Thu Oct 20 20:43:31 2011
Image Type:   ARM Linux RAMDisk Image (gzip compressed)
Data Size:    5473115 Bytes = 5344.84 kB = 5.22 MB
Load Address: 0x00000000
Entry Point:  0x00000000

~/tmp$ ls
initrd  initrd.img-2.6.32-5-kirkwood  initrd.img-2.6.32-5-kirkwood-nfs  initrd.img-2.6.32-5-kirkwood-nfs.gz

~/tmp$ cp initrd.img-2.6.32-5-kirkwood-nfs /var/lib/tftpboot/uImage-nfsroot

Now that you have the new initrd created you’ll need to update your uBoot environment variables.  This can be quite complex in-and-of itself.  The previous u-Boot environment variables I had did not work with this new setup, my attempts at abstracting away most of the complexity into many variables did not seem to work, there were many instances where variables referenced in commands and other variables were not getting set properly.  I basically re-worked it from the ground up using sample material at DENX’s (developers of u-Boot) website.  Their manual has a lot of information, you’ll probably want to reference the CommandLineParsing, UBootCmdGroupEnvironment, and LinuxNfsRoot pages primarily.  The working copy of my u-Boot environment is listed below:

addip=setenv bootargs ${bootargs} ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}:${hostname}:${netdev}:off panic=1
addmisc=setenv bootargs ${bootargs}
addtty=setenv bootargs ${bootargs} console=ttyS0,${baudrate}
baudrate=115200
boot_mmc=setenv bootargs $(bootargs_console); run bootcmd_mmc; bootm ${kernel_addr_r} ${fdt_addr_r}
boot_nfs=tftpboot ${kernel_addr_r} ${bootfile}; tftpboot ${fdt_addr_r} ${fdt_file}; run nfsargs addip addtty; bootm ${kernel_addr_r} ${fdt_addr_r}
bootargs=root=/dev/nfs rw nfsroot=192.168.1.2:/home/exports/sheeva_squeeze_nfs_root ip=192.168.1.11:192.168.1.2:192.168.1.1:255.255.255.0:sheeva:eth0:off panic=1 console=ttyS0,115200
bootargs_console=console=ttyS0,115200 mtdparts=orion_nand:0x00100000@0x00000000(uBoot)ro,0x00400000@0x00100000(uImage),0x1fb00000@0x00500000(rootfs)
bootargs_nfs=$(bootargs_console) root=/dev/nfs rw nfsroot=$(serverip):$(rootpath) ip=$(ipaddr):$(serverip):$(gateway):$(netmask):sheeva:eth0:none
bootcmd=run boot_nfs
bootcmd_mmc=mmc init; ext2load mmc 0:1 0x00800000 /uImage; ext2load mmc 0:1 0x01100000 /uInitrd
bootcmd_nfs=tftpboot ${kernel_addr_r} ${bootfile}; tftpboot ${fdt_addr_r} ${fdt_file}
bootdelay=3
bootfile=uImage.sheeva.squeeze
console=console=ttyS0,115200 mtdparts=nand_mtd:0x00100000@0x00000000(uBoot)ro,0x00400000@0x00100000(uImage),0x1fb00000@0x00500000(rootfs)
ethact=egiga0
ethaddr=00:50:43:xx:xx:xx
fdt_addr=0x01100000
fdt_addr_r=0x01100000
fdt_file=uInitrd-nfsroot
fileaddr=800000
filesize=539CDD
gatewayip=192.168.1.1
hostname=sheeva
ipaddr=192.168.1.11
kernel_addr_r=0x00800000
netdev=eth0
netmask=255.255.255.0
nfsargs=setenv bootargs root=/dev/nfs rw nfsroot=${serverip}:${rootpath}
rootpath=/home/exports/sheeva_squeeze_nfs_root
serverip=192.168.1.2
stderr=serial
stdin=serial
stdout=serial
x_bootargs=console=ttyS0,115200 mtdparts=orion_nand:512k(uboot),4m@1m(kernel),507m@5m(rootfs) rw
x_bootargs_root=ubi.mtd=2 root=ubi0:rootfs rootfstype=ubifs
x_bootcmd_kernel=nand read 0x6400000 0x100000 0x400000
x_bootcmd_sata=ide reset;
x_bootcmd_usb=usb start;

By default it’s set to boot from NFS via the bootcmd=run boot_nfs line.  If you’re having issues getting NFS root to work, you may want to remove the panic=1 from the addip variable, this way it can drop you to an initrd prompt.  The boot_mmc variable is adapted from Martin’s example.  You may need to modify these slightly if your SheevaPlug is different, specifically the bootargs_console variable – mtdparts reflects the flash partition setup on my plug, update if yours differs (I think these are the factory default settings), /proc/mtd will have this information in it if you boot again from the Debian install.  Also I changed it to orion_nand, I believe it used to be orion_mtd initially.

That’s basically it.   Hope this is of use to someone!

More sensor interfacing with the Bus Pirate

After interfacing the TMP102 temperature sensor recently with the Bus Pirate, the next step on my agenda was to integrate this with the Web Platform along with a HH10D humidity sensor.  Unfortunately I realised a bit late (entirely my fault rushing an order) that although the HH10D was an I2C sensor, the humidity was actually read through a frequency output pin; the I2C EEPROM was only for calibration data.  Bus Pirate to the rescue once again!

This, like the TMP102 is an 3.3V sensor and needs pullup resistors on the I2C bus to read the EEPROM.  I wasted spent a number of hours with this getting nothing but garbage data with the first sensor I tried (the calibration values were completely wrong), on the second try with a backup sensor I had much better luck, getting good results almost immediately.

We’ll be doing a similar setup to the TMP102: using I2C and turning on the power supplies but in this case the AUX pin should be set to HIGH-Z for input/reading.

HiZ> m
1. HiZ
2. 1-WIRE
3. UART
4. I2C
5. SPI
6. 2WIRE
7. 3WIRE
8. KEYB
9. LCD
x. exit(without change)

(1)> 4
Set speed:
 1. ~5KHz
 2. ~50KHz
 3. ~100KHz
 4. ~400KHz

(1)> 3
Ready
I2C> c
a/A/@ controls AUX pin
I2C> @
AUX INPUT/HI-Z, READ: 0
I2C> W
Power supplies ON
I2C> (1)
Searching I2C address space. Found devices at:
0xA2(0x51 W) 0xA3(0x51 R)

Good.  Now if you take a look at the datasheet for the M24C02 EEPROM to do a random address read you’ll need to send a start bit, device select (read only; R/W set to 0), address, then another start bit, device select again (but with R/W set to 1), then read commands.  The HH10D datasheet mentions there are 2 calibration factors we need to read: sensitivity and offset and each are 2 byte integers, making a total of 4 bytes.  These start at address EEPROM address 10.  Using the BP, we do the following:

I2C> [0xa2 0x0a [0xa3 r:4
I2C START BIT
WRITE: 0xA2 ACK
WRITE: 0x0A ACK
I2C START BIT
WRITE: 0xA3 ACK
READ: 0x01  ACK 0x8B  ACK 0x1E  ACK 0x32
I2C>

This is in big-endian format so the sensitivity value (at address 0x10) reads 0x018B and the offset is 0x1E32.  We’ll also do a frequency read, the first as baseline with my AC running nearby and the second breathing on the sensor:

I2C> f
AUX Frequency:  autorange 7,169 Hz
I2C> f
AUX Frequency:  autorange 6,790 Hz

The formula in the datasheet states the RH = (offset-freq) * sensitivity/4096.  Plugging these in we get 54% and 90.6% RH which seems to match the other humidity sensor in my room.  Success!

Next step was getting this working on my Web Platform.  I have some very bare-bones code working with the TMP102 already, just need to work on the HH10D a bit more and I’ll post my example up here.

Hard drive rotary input device part 2

Been working on a few projects lately and have a number of things I’m hoping to post soon, one of them I got around to briefly documenting is the re-implementation of a hard drive rotary input device – I’m working on hooking up a laptop hard drive to an Arduino/AVR to be used as an input device similar to a rotary encoder only super-sized.  I originally put together a circuit last year which never really worked but now I’ve gotten around to doing it right.  The circuit I implemented was similar to the original Instructable with a few modifications, I tried using the LM324 op-amp called for but didn’t quite work the way I wanted (running from +5V Vcc gave an output ~3.5V instead of being saturated to the full 5V) and tried using a LM339 comparator instead with better success.  The signals can be seen for comparison on my scope in the video.  Right now this just proof-of-concept generating the correct inputs signals to the microcontroller.  Implementation of firmware, a simple circuit to the AVR and brief demo of a more sophisticated output display will follow shortly. Details of the circuit are on the YouTube page and listed below.

The schematic I followed was similar to the one in the Instructable however I removed the 1k series resistors to the non-inverting inputs of the ‘324 and replaced the 10k feedback with 1M’s. The outputs of the LM324 were fed to the LEDs with a series current-limiting resistor. The 339 was similar except it had a 10k pull-up resistor on the output. Both were running off of a single-sided +5V supply and the inverting inputs were tied to ground. The hard drive I used had 4 pins as shown, 3 pins each to separate coils and one common pin which was also tied to ground.

Barometer OneWire sensor added

I finally got around to ordering and building a 1-wire barometer kit from Hobby Boards…  it was a fairly straightforward assembly process when you actually follow the construction tips and don’t accidentally mix up the location of the two SMT IC chips…  Oops!  Thankfully they were only 8-pin SOIC’s so it wasn’t too terribly difficult to fix.  Next time I suppose should stop watching Storm Chasers while soldering!

It took a minor hack on the DS2438 library I’ve been using to add a function to read data from the ADC without converting to a humidity value with temperature compensation to get working, but seems to be pretty good so far; my reading are within 0.02 inHg from what I’ve seen so far, though only testing over the full 3-4″ range will evaluate the precision correctly.  I’ve updated the patch on my Google Code repository to reflect the new readAD() function.

There are some minor bugs I’ve noticed, namely the DS1820 temperature sensors can report invalid readings when the Arduino is powered on.  It only seems to happen when it’s reset, a cold power-on rarely causes it and usually an additional reset or two fixes the issue.  The DallasTemperature library is coded to use parasitic power for the sensors and it’s possible I may have wired them to use +5V; I’m thinking this could possibly cause issues but will need to check.  The lightning counter code currently calculates the derivative between readings and only uploads the differences rather then the absolute value, occasionally this will give an incorrect reading as well which results in a large negative value being transmitted.  These two issues can skew the scaling on the graphs which can be annoying.

I’m also looking into better integration with Pachube, I’ve seen a nice example which use EEML XML format with the API v2 which allows for better flexibility, it could handle the invalid sensor readings better by omitting them from the feed update.  I also came across a Perl library which I may use to write a small command-line client for updating or manually changing sensor readings…  perhaps a simple hack to fix the min/max graph scaling problem.

Although I consider this project something of a success I will eventually want to develop a new version from the ground up using sensors similar to the weather sensors SparkFun carries…  it will probably be similar to the USB weather board they built (using different I2C/SPI sensors) but I want to integrate a LCD and ethernet directly on the board.  Plus it will give me an excuse to finally make a custom PCB! 🙂

Images of the build:

Pachube datastream:

Arduino OneWire/Pachube project uploaded

After uploading my radarwatch project to Google Code yesterday, I went ahead and created another one for my Arduino projects. Right now it consists of a basic weather station which reads in data from OneWire sensors and upload it in real-time to a live Pachube feed via an Ethernet shield. The sensors I’m using are from Hobby Boards and include temperature, humidity, solar, and lightning detection. I’d like to thank the developer ‘gfb’ for writing and open-sourcing an OneWire Arduino library for additional 1-wire IC’s beyond the standard DS1820 temperature sensor covered in the Arduino DallasTemperature library.

The code is pretty basic now and probably somewhat buggy.  I need to patch 2 of the OneWire libraries, one to fix a bug reading in DS2423 counters (lightning detection) and another patch to add functionality to read current from the DS2438 smart battery monitor (used as a solar sensor here).

Sensor data is displayed on a small 16×2 LCD display, echoed out to the USB serial port, uploaded to Pachube in real-time and also available via a webserver running on the Ethernet shield.  I’m not a huge fan of the sample tutorial code I used from Pachube, it seems kinda hackish.  I’ve seen better examples on there built around a nice library but unfortunately they all seem to need Processing, which I didn’t feel like using here.  Maybe if I get time I’ll put together a nice Pachube stand-alone Arduino library.

There are a few kinks to work out still and further development, such as getting the solar sensor close to the window to track daylight hours without compromising the accuracy of the thermal readings (old apartment, window insulation not so good).  I’ll try to post some pictures of the setup and maybe a schematic up soon.

If you’re curious, you can view the feed here: http://www.pachube.com/feeds/9598

Wiki entry : http://code.google.com/p/kern3l/wiki/OneWireProject

Sample graphs: (these are real-time live data)

  • Temperature
  • Humidity
  • Lightning strikes (frequently produces false positives)

Sample images of the hardware:

SheevaPlug Basics + MultiBoot

I thought documenting my notes and what I’ve leared about the SheevaPlug so far may be useful so I’m putting together a basic HOW-TO.  The documentation from GlobalScale Technology is a great place to start (docs on their site no longer seem to be there, check wiki link posted below).

Firstly, it will likely come pre-installed with Ubuntu Jaunty Beta, although possibly Gentoo.  Both can run on the plug in addition to FreeBSD.  Wiki explains more.  It also has a good entry on configuring the plug to boot from an SD card rather then the onboard NAND flash.

The forums are a very useful resource as well:

Configuring the USB serial console (Linux & Windows).  My Ubuntu desktop failed to create a /dev/ttyUSB1 on it’s own when plugged in, a simple ‘modprobe ftdi_sio product=0x9e8f vendor=0x9e88’ resolved that.

Give care when flashing partitions or images and make sure you don’t overwrite /dev/mtd0 or /dev/mtdblock0 (1st MB of NAND), it holds the uBoot boot image, the plug cannot boot without it.  Next partition is the Linux kernel image (~1-4mb), and finally a root filesystem image (remainder of the 512MB).

I somehow managed to brick mine at one point, after a fsck and reboot I got a “Verifying Checksum … Bad Data CRC” error and it would not boot.  Playing with it a bit, I managed to get it to boot by tftp’ing a kernel image and mount a root filesystem via NFS.  GST’s documentation and the wiki provide plenty of guidance.  I also had issues getting it to re-flash the kernel image to /dev/mtd1 because the kernel image provided by GST was slightly too big for the default size of mtd1.  Since then I’ve read they have released a new kernel image that fits, but at the time I simply increased the size of mtd1 (to 4mb rather then the default 1mb) by editing the mtdparts setting in uBoot.

It also occured to me that coming up with a makeshift bootmenu might be useful for debugging:

set console 'console=ttyS0,115200 mtdparts=nand_mtd:0x00100000@0x00000000(uBoot)ro,0x00400000@0x00100000(uImage),0x1fb00000@0x00500000(rootfs)'

set boottftp 'tftpboot 0x2000000 $(image_name)'
set bootargs_nfs 'root=/dev/nfs rw'
set boot_nfs 'run boottftp;setenv bootargs $(console) $(bootargs_nfs) nfsroot=$(serverip):$(rootpath) ip=$(ipaddr):$(serverip)$(bootargs_end);bootm 0x2000000'

set bootnand 'nand read.e 0x2000000 0x00100000 0x00500000'
set bootargs_nand 'root=/dev/mtdblock2 rw'
set boot_nand 'run bootnand;setenv bootargs $(console) $(bootargs_nand) ip=$(ipaddr):$(serverip)$(bootargs_end);bootm 0x2000000'

set bootargs_sd 'root=/dev/mmcblk0p1 rw'
set boot_sd 'run bootnand;setenv bootargs $(console) $(bootargs_sd) ip=$(ipaddr):$(serverip)$(bootargs_end);bootm 0x2000000'

set bootcmd 'run boot_sd;'

As I mentioned, the mtdparts I provided are configured for a 4mb mtd1 partition, you may need to modify this.  This thread explains mtdparts better.  All of the other variables such as IP, SERVERIP, etc need to be configured in addition to these listed here.  You will also see it’s set to boot from SD card by default, it can be changed to ‘run boot_nand’ or ‘run boot_nfs’ if you like.  Also, the uBoot quick reference page might be use of to you as well.

DIY Radar & SheevaPlug

So I’ve decided to disable my weekly Twitter updates after seeing how annoying it would be to read.  I’ve been working on a few things lately, first is a “DIY radar” weather warning system with the help of NOAA WSR-88D radar data, libGD, and Perl.  I recall some very basic image processing algorithms and code I tinkered with in BASIC when I was much younger, but it was very simplistic and this was ages ago.  It’s fun to catch up on some old programming interests.  I’ll throw a post about it together once the code is a bit more complete.

My Linux-based SheevaPlug (embedded Linux in a wall-wart) has been occupying some time as well, tinkering with the Jaunty install and NFS and SSH.  I’m pretty impressed with the capabilities of this so far, despite the fact I managed to partially brick it for awhile. I eventually want to build a Tweet-A-Watt and use my plug for interfacing.  Wiring my 1-wire weather station to the plug makes sense as well.