Wednesday, September 25, 2013

Spoiled Pi

Background

I built what I called a "Cloud Scanner" interface for my older but functional HP Laserjet 3055 multi-function office printer some time ago.  I built it with a Raspberry Pi and a 2-line Hitachi-compatible LCD and some momentary contact push buttons for navigation.  The idea of the device is that I want to be able to easily and rapidly get paper "into the cloud".  No turning on a connected computer (waiting for boot-up...ok, I have a Mac so this is not too big a deal for me, but God help you Windows folks), no finding the typically crummy proprietary scanning application, no figuring out how to convert the scan that results into the universal electronic paper (PDF), no fumbling around with a transfer program (email, Google Docs, or the like).  No, I just want to walk up, put in my papers to the document feeder, push a button, and have my electronic paper show up in the cloud.  One step.

I built it and all was well, until...

My wife came to me and said the scanner was not working.  She became a fan of my little device and grew to rely on it.  She has a vacation rental business and so does a lot of rental agreement processing.  She sends paper to her email all the time.  So off I go to diagnose.  Turns out...corrupt SD RAM card.  After some Googling, it seems that many RPi users are having the same problem.  Secretly, I kinda knew this.  We had a power failure a day or two prior and I don't think it was coincidence.  This has happened to me in the past and all I had to do was eject the card, mount it in a Linux machine, and run fsck on the volume.  This time...ouch.  The card was totally unrecoverable.

This hurt.  Of course I did not have a backup.  Back to the drawing board...

So this time, I start looking for what the Linux router crowd has been doing for years (with read-only Linux'es and busybox, for example).  The only solution I stumbled on, aside from numerous blogs with varying cookbooks to be performed against Raspbarian, is a distro built by NutCom Services Ltd.  This post chronicles my experience and gives you a mini how-to if you want to build your own.  I use a MacBook for development work, so all instructions will be given in that context.

Re-Baking the Pi

Installing IPE

  1. You'll need a command prompt.  On the Mac, it's terminal.
  2. Download the Industrial Perennial Environment (IPE) and unzip it.
  3. Insert your SD card (must be 1GB or greater) and figure out which volume it mounts up as.  I used:
    $ df -lh
  4. If there is already a formatted volume on the card (usually there is), unmount it:
    $ sudo diskutil umount "/Volumes/NO NAME"
  5. From the directory where the ipe_r1.img file is located, type (NOTE - /dev/disk2 is where my SD card was located...ymmv and don't blame me if you wipe out something important.  This command is destructive and unforgiving!):
    $ sudo dd if=ipe_r1.img of=/dev/disk2 bs=1m
  6. Put the card into the Pi and boot it.  Since I did not have a monitor and keyboard hooked up to mine, I used my router to figure out what IP address was given to the Pi.  Note - The IPE distro only starts up with TELNET for remote access!  It took me a while to figure this out.  I had to hook up a monitor and keyboard to learn that sshd was not started (never fear, keep reading).  Log into the Pi using root with password root.
    $ telnet 192.168.1.122
  7. A very nice command script was written by NutCom that resizes your SD card volume to the max it can be (defaults to 1GB upon install) and then generates an SSH private key, turns on sshd, resets the root password, and turns off telnet.  Run TWICE (there is a reboot in between if memory serves).
    $ firstboot
    $ firstboot
  8. I am always in the habit (and one of the reasons I gravitate to Debian-based distros - apt rocks) of updating software upon a new install.  So this step would be to tell you to run apt-get update and apt-get upgrade.  But I ran into problems with this distro.  There is a script you can run that will set of the Pi volume for read-write, which you obviously need to do to update software.  But what it does not do is change to /boot volume.  Therefore, I got the following errors:
    rm: cannot remove `/boot/bootcode.bin': Read-only file system
    dpkg: error processing raspberrypi-bootloader (--configure):
     subprocess installed post-installation script returned error exit status 1
    dpkg: dependency problems prevent configuration of libraspberrypi0:
     libraspberrypi0 depends on raspberrypi-bootloader (= 1.20130617-1); however:
      Package raspberrypi-bootloader is not configured yet.
    dpkg: error processing libraspberrypi0 (--configure):
     dependency problems - leaving unconfigured
    dpkg: dependency problems prevent configuration of libraspberrypi-dev:
     libraspberrypi-dev depends on libraspberrypi0 (= 1.20130617-1); however:
      Package libraspberrypi0 is not configured yet.
    dpkg: error processing libraspberrypi-dev (--configure):
     dependency problems - leaving unconfigured
    dpkg: dependency problems prevent configuration of libraspberrypi-doc:
     libraspberrypi-doc depends on libraspberrypi0 (= 1.20130617-1); however:
      Package libraspberrypi0 is not configured yet.
    dpkg: error processing libraspberrypi-doc (--configure):
     dependency problems - leaving unconfigured
    dpkg: dependency problems prevent configuration of libraspberrypi-bin:
     libraspberrypi-bin depends on libraspberrypi0 (= 1.20130617-1); however:
      Package libraspberrypi0 is not configured yet.
    dpkg: error processing libraspberrypi-bin (--configure):
     dependency problems - leaving unconfigured
    Errors were encountered while processing:
     raspberrypi-bootloader
     libraspberrypi0
     libraspberrypi-dev
     libraspberrypi-doc
     libraspberrypi-bin
    E: Sub-process /usr/bin/dpkg returned an error code (1)
  9. Set the Pi root volume to read/write.
    $ ipe-rw
  10. Modify the /sbin/ipe-rw file as follows:
    #!/bin/sh
    echo Remounting rootFS for R/W! Use \"ipe-ro\" to lock it again!
    mount / -o remount,rw
    mount /boot -o remount,rw
  11. Modify the /sbin/ipe-ro file as follows:
    #!/bin/sh
    echo Remounting rootFS for R/O!
    mount / -o remount,ro
    mount /boot -o remount,ro
    
  12. Now update the distro (need to re-execute read-write script):
    $ ipe-rw
    $ apt-get update
    $ apt-get upgrade
  13. Base Pi install, immune to SD card corruption (at least so far for me), is now good to go!

Caveat

When updating the Pi, I encountered a situation that I cannot reproduce where DHCP assigned DNS addressing no longer worked (it did at initial install).  It may be that the upgrade process overwrote /etc/resolv.conf and because of the read-only nature of the root volume, it could not get reset.  When I manually updated it with my routers' address, everything was fine.  I intend to ping NutCom after authoring this to see if they have any experience with this.

Base Packages and Dependencies

The following base software is needed by the Cloud Scanner device:

$ apt-get install sane libsane sane-utils libsane-hpaio
$ apt-get install libtiff-tools imagemagick
$ apt-get install python-dev python-pip
$ pip install RPIO
$ pip install RPLCD

The Hardware

I used a HD44780 compatible LCD display and 4 momentary push button switches for the user interface.  I used the onboard power supply on the Pi to power these peripherals, which is just enough (and also contingent on how you are powering the device and what else beside a connection to your scanner you may have plugged into the USB port).

Cloud Scanner Schematic

The Software

I have put my project on github.  Instructions on how to install it are located in the github readme.

There are three options from the main menu: Green button =  Scan now, White button = select an email destination, and Blue button = select a paper size.  The program will scan from the first scanner it finds (which may be a limitation I realize...it's on my list).

One additional thing you will need to do, since the scan program will put all working files in /tmp, is to expand the size of the tmpfs volume.  To do that, edit /etc/default/tmpfs, find the TMP_SIZE parameter, uncomment it, and change it to 200M.

Enhancements

Nothing is ever completely done, is it?
  1. Add a More> submenu to the black button so more options can be added.
  2. Add a scanner selector.
  3. Add an automatic document feeder vs. flatbed option (defaults to ADF but I do have a flatbed-only scanner and it will currently work).
  4. Add more destination types, like to Dropbox, Google Docs, Amazon S3, ftps, etc.
  5. Add a duplex assembly option (for double-sided pages).
  6. Add a web interface to manage the settings.

The Result

For the "enclosure", I was inspired by a project that I saw that used discarded CDs and CD protectors.  Since I have plenty of those, I made one myself.  It worked pretty well.


Tuesday, September 24, 2013

Advanced Arduino Sound Synthesis

I read with interest Jon Thompson's skill builder article Advanced Arduino Sound Synthesis in Make: Magazine.  I have always been interested in electronics, sound, amplification, and music of all kinds, and found myself with some time to explore.  So, I broke out my Atmel chips and o'scope and started experimenting.  I write here about the things that I got hung up on while really trying to understand all of the great information presented in the article.

The first thing I had to deal with was that Jon used the Arduino Nano v3.0 board.  I don't have one of those.  The microcontroller on those boards is the Atmel ATmega328.  I tend to do lots of breadboarding (before Arduino became popular) and have a stock of the older ATmega8 chips.  So, no big deal, they are very similar to the 328 chips.

Since the article is about the Arduino, it is natural that all the source code is based on the Arduino libraries.  I am a command line guy, so I typically use avr-gcc and make tools, along with my trusty home-made USBasp programmer.  I often viewed Arduino as eye-candy that got in the way of the "real programming."  But since Arduino has take over the world, I figured now was the time to see what it was all about.  So I downloaded the Mac version of the IDE, and of course went for the V1.5.4 beta.

The first question I asked myself was "how do I get the Arduino bootloader on a blank ATmega8?"  After some quick research, I realized that you don't have to.  The Arduino IDE has an "Upload Using Programmer" option right off the File menu.  And, the USBasp programmer is supported out of the box (Tools/Programmer).  Nice.

Next problem.  How do you target the build for the ATmega8 on a breadboard?  The closest setting I found was "Arduino NG or older".  So I got listing_2 from the article to generate a series of waveforms, slapped it in the IDE, and viola...errors.

I soon realized that the errors were not due to a targeting problem, but instead because the code was written for the 328.  It referenced registers that the ATmega8 did not have.  But the good news is that the two timers needed for the example (Timer1 and Timer2) worked just fine on the ATmega8.  I just had to correct the registers.  The modified listing_2 follows:

       
# define DEBUG 0

/******** Load AVR timer interrupt macros ********/
#include <avr/interrupt.h>

/******** Sine wave parameters ********/
#define PI2     6.283185 // 2 * PI - saves calculating it later
#define AMP     127      // Multiplication factor for the sine wave
#define OFFSET  128      // Offset shifts wave to just positive values

/******** Lookup table ********/
#define LENGTH  256  // The length of the waveform lookup table
byte wave[LENGTH];   // Storage for the waveform

/******** Waveform parameters ********/
#define SINE     0
#define RAMP     1
#define TRIANGLE 2
#define SQUARE   3
#define RANDOM   4

#if DEBUG
volatile byte timer1_start = 0;
volatile byte timer1_end = 0;
#endif

void setup() {

  /******** Populate the waveform lookup table with a sine wave ********/
  waveform(SINE);                      // Replace sine with the different cases to
                                        // Produce the different waves
#if DEBUG
  // Keep this speed low becuase there are not many cycles left
  Serial.begin(2400);
#endif
  
  /******** Set timer1 for 8-bit fast PWM output ********/
  pinMode(9, OUTPUT);       // Make timer's PWM pin an output
  TCCR1B  = (1 << CS10);    // Set prescaler to 1 - full 8MHz
  TCCR1A |= (1 << COM1A1);  // PWM pin to go low when TCNT1=OCR1A
  TCCR1A |= (1 << WGM10);   // Put timer into 8-bit fast PWM mode
  TCCR1B |= (1 << WGM12); 

  /******** Set up timer 2 to call ISR ********/
  TCCR2 = (1 << CS20);      // Set prescaller to divide by 1
  TIMSK = (1 << OCIE2);     // Set timer to call ISR when TCNT2 = OCR2
  OCR2 = 128;               // sets the frequency of the generated wave
                            // 8Mhz / (OCR2 = 128 * 256)...in this case, 244 Hz
  sei();                    // Enable interrupts to generate waveform!
}

void loop() {  // Nothing to do!
#if DEBUG
  static int diff = 0;
  // This might be negative...ignore it
  diff = timer1_end - timer1_start;
  Serial.println(diff);
  delay(1000);
#endif
}

/******** Called every time TCNT2 = OCR2 ********/
// Question here is...what should be the offset time to set
// TCNT2 given that the timing of my chip is different than the author
// (and maybe even the compiler).
ISR(TIMER2_COMP_vect) {  // Called each time TCNT2 == OCR2
  static byte index=0;    // Points to successive entries in the wavetable
#if DEBUG
  timer1_start = TCNT1L;
#endif
  OCR1AL = wave[index++]; // Update the PWM output
  TCNT2 = 33;  // Timing to compensate for time spent in ISR
#if DEBUG
  timer1_end = TCNT1L;
#endif
}


void waveform(byte w) {
 switch(w) {

   case SINE: 
    for (int i=0; i<LENGTH; i++) 
      {float v = OFFSET+(AMP*sin((PI2/LENGTH)*i));
      wave[i]=int(v);
    }
    break;

  case RAMP:
    for (int i=0; i<LENGTH; i++) {
      wave[i]=i;
    }
    break;

  case TRIANGLE:
    for (int i=0; i<LENGTH; i++) {
      if (i<(LENGTH/2)) {
        wave[i]=i*2;
      } else {
        wave[i]=(LENGTH-1)-(i*2);
      }
    }
    break;
    
  case SQUARE:
    for (int i=0; i<(LENGTH/2); i++) {
      wave[i]=255;
    }
    break;

  case RANDOM:
    randomSeed(2);
    for (int i=0; i<LENGTH; i++) {
      wave[i]=random(256);
    }
      break;
  }
}

After I got the coding errors fixed, the sketch compiled and uploaded easily.  You can see my rig here (the board on the left is like the Nano...that I designed years ago, but it plugs directly into the power rails on the breadboard):



There are a couple of enhancements I made along the way.  Notice in the original interrupt service routine (ISR), Jon makes reference to two lines that are designed to account for time spent in the ISR:

       
/******** Called every time TCNT2 = OCR2A ********/
ISR(TIMER2_COMPA_vect) {  // Called each time TCNT2 == OCR2A
  static byte index=0;    // Points to successive entries in the wavetable
  OCR1AL = wave[index++]; // Update the PWM output
  asm("NOP;NOP");         // Fine tuning
  TCNT2 = 6;              // Timing to compensate for time spent in ISR
}

Notice the NOP and TCNT2 = 6 lines.  I had no idea what I should change these values to.  For one thing, I changed the prescale of Timer2 to 1 instead of 8, so that I could get higher frequencies out of the test (this makes the formula 8MHz / (OCR2 * 256)).  Also, my chip is running at 8MHz vs. 16MHz (for the 328).

I always say, if it offends thee, take it out.  So when I set OCR2 = 128 and took the timing lines out, what I got was the following (126 Hz):


This is clearly not right, and expected, as OCR2 is supposed to set the overflow value for the next value in the waveform table.  But that assumes an instantaneous change.  While the ISR routine does not have much in it, it still takes some clock ticks to execute, but how many?  I am expecting to see 244 Hz (8,000,000 / (128 * 256)).

Through trial and error, I got to a setting of TCNT2 = 33:


But I continued to ask myself...how do I make this more scientific?  I might have expected Jon's value of 6 scaled to account for my slower processor (8 MHz / 16 MHz) and scaled up by the prescale factor difference (8 for his vs 1 for mine).  This would yield 24 plus two ticks for the NOPs, or 26.  33 is off by more than 10%, so how can I figure this out (and how did he...it is not in the article)?

One way is to look at the assembler from the compiler and try to discern the number of clock ticks.  This is beyond what I was willing to do.  But after looking at AVR136 Application Note and studying the code, I realized that I could just time the routine by capturing the timer counter before and after the code executed.  So, if you study my code, you will note a DEGUB symbol that causes that timer difference to be written to the UART0 serial console.  So what was the result?  17.

Hmph!  Where is the other missing time?  I occurred to me that the interrupt service routine must have some overhead associated with it, and it may well be compiler specific.  More Googling...

What I found was this AVR Interrupt Response Time posting by an Atmel engineer.  The answer is 8 to 11 cycles plus any extra time preserving registers (which is a compiler specific optimization).  This jives with 33 - 17 = 16 answer I was looking for.

Here is a final side note.  When I put in the console debugging code, I noticed that I could not get the baud rate specified in the sketch to match the setting I needed to put in to my terminal emulator to see expected results.  I had to specify a baud value 1/2 of that in the sketch.  Having run into this in past projects, I recognized that this must be a clock rate problem in the f_cpu compile directive.  So the Arduino board settings are now coming back to haunt me.  The Arduino NG board runs at 16MHz.  Because I was really into using the Arduino IDE, I figured out how (with much difficulty) to create board files for a breadboarded ATmega8 at different clock rates.  You can see (and use if you want) the result of that work at my github project chuckb/atmega.