• Interactivity
  • A tip for using Adafruit USB backpack for LCD interface (USB BackpackPlus)

I really like the Adafruit USB/Serial backpack because it is inexpensive and it can be coupled with inexpensive LCD's, yielding a solution comparable to the MatrixOrbital products at about 1/3 the cost (and no, I'm not associated with Adafruit, I'm just a satisfied customer 🙂 )

There are a few little quirks that bothered me and I found somebody else already addressed almost all of them and was kind enough to create a makefile along with necessary Teensy 1.0 libraries that can be compiled directly with avr-gcc and written to the device via USB connection with avr-dude.

https://github.com/CanyonCasa/BackpackPlus

Most interesting features:

  • Extra pins are now GPIO instead of only GPO. This means there are extended commands available to either read or write a pin, so the USB backpack can also be used for pushbutton or other low-speed inputs.

  • Reorganization of EEPROM (there were some conflicts in the Adafruit code that cause strange behavior)

  • Support for timed display on state.

More detailed information is in the README

There was one little thing that was bothering me about this. When you power on the unit it blinks some configuration data (baud rate). This is a great feature for development, but when you finalize a project this feature detracts from the polish and shine of the user experience, so I wanted a command to disable it when it is no longer needed:

https://github.com/transmogrifox/Adafruit_USB_Backpack_Plus

My code is nothing more than a shameless fork of CanyonCasa USB Backpack Plus with the addition of a control command to disable the config data on startup and an improved switch debounce routine suggested by Jack Gannsle.

Other than that I removed superfluous/redundant stuff that you can get from CanyonCasa's github (like original Adafruit code, EEPROM images, etc). That way the process of hacking the code and burning to your unit is more focused.

Either way I thought I would share this in case anybody is using the USB backpack but wants the added features.

Probably one of the most valuable things CanyonCasa did was to assemble a build system that makes it easy to modify the USB backpack code to taste.

7 days later

I couldn't stop hacking this thing so now I have added an Audio Level Meter function with built-in decay. It can do 2 channels per row.

For a 20x4 display it means you can do level metering on all 8 analog inputs simultaneously.

In my case I can print potentiometer labels on the top and bottom rows while monitoring both audio inputs and outputs on the middle 2 rows.

Backlight display flashes red when level is driven beyond number of rows as clipping indicator.

If you think this is useful, and try it, and find bugs -- please open issues on my github page. Pull requests also welcome.

Sounds great, how do you interface this with Bela though?
I had a quick look at the the code above the other day but I did not see any actual example, perhaps I missed it? I guess you add the BackpackPlus.cpp to your project, but there is no header files?

Also, out of curiosity, how early in the boot can you start this?

    Your comments reveal I need to better clarify what this does. The linked code are some modifications to the Atmel AT90 code that runs in the Adafruit USB backpack to make it more suitable for audio use -- particularly the audio level meter function. You communicate to the character display through a serial port (or USB serial), for example /dev/ttyACM0.

    giuliomoro I had a quick look at the the code above the other day but I did not see any actual example, perhaps I missed it?

    The code referenced makes the Adafruit USB LCD backpack "Bela-ready". Now all the BBB needs to do is send something out the serial port every 50ms or so to keep the bars bouncing.

    giuliomoro Sounds great, how do you interface this with Bela though?

    UART (USB or serial pins) I'm working on the software interface part. At the moment I am using a random number generator to create bouncing levels to emulate audio and writing text on the unused lines as proof-of-concept. No code worth pushing to github yet.

    giuliomoro Also, out of curiosity, how early in the boot can you start this?

    As early as you can get a tty up and get a process running to talk to it. As for the audio level metering, obviously you need a process running that can get audio or ADC inputs. This probably makes more sense when you realize the display interface is just a tty port.

    My plan is for an auto-started Bela project to initiate communication with this, so it will be blank until the system is go. It will be an indication it's booted and running.

    Here is a very basic sketch of how it will be used to monitor levels on Bela.

    //
    // Note: This is not "copy and paste" code 
    // Read this like "pseudocode".  Only for purpose of illustrating the 
    // intended method of application to Bela
    //
    AuxiliaryTask SerialLcdFuncs;
    unsigned int gLcdTimer;
    unsigned char gLevel;
    unsigned char gChannel;
    float gMax0, gMax1;
    .
    .
    .
    void SerialLCD_BG_Task(void*)
    {
        float tmpLevel = 20.0 + 20.0*log10(gMax0);  //This will register any levels from -20 dBFS up to 0 dBFS
                                                                                      //Assumes a 20x4 display
        unsigned char uLev = (unsigned char) (0xFF & lrintf(tmpLevel));
        //Assume there is some object called Serial that was initialized, and is configured to talk to the 
        //serial port to which the USB LCD backpack is connected. (for example /dev/ttyACM0)
        Serial.Write(0xFE); //command mode
        Serial.Write(0xD6); //Audio Level Meter command
        Serial.Write(0x02); //Audio level meter channel (second row, top bar)
        Serial.Write(uLev); //Audio Level Meter Level field
        gMax0 = 0.0; //This might be a little dangerous, but this serves as "pseudo-code" for the general idea
    
       //Repeat calculation for channel 2, only change third line to adapt:
        //Serial.Write(0xFE); //command mode
        //Serial.Write(0xD6); //Audio Level Meter command
        Serial.Write(0x03); //Audio level meter channel (second row, bottom bar)
        //Serial.Write(uLev); //Audio Level Meter Level field
    
      //Read serial port for button press messages (extra I/O)
    
     //Send text if there is stuff to send
    }
    
    .
    .
    .
    bool setup(BelaContext *context, void *userData)
    {
        SerialLcdFuncs= Bela_createAuxiliaryTask(&SerialLCD_BG_Task, 50, "bela-serial-lcd", arg);
        gChannel = 0;
        gLevel = 0;
        gLcdTimer = 0;
        gMax0 = 0.0;
        gMax1 = 0.0;
    }
    
    void render(BelaContext *context, void *userData)
    {
            float ch0 = 0.0;
            float ch1 = 0.0;
            float tmp = 0.0;
    	for(unsigned int n = 0; n < context->audioFrames; n++) {
    
    		ch0 = audioRead(context, n, 0);
    		ch1 = audioRead(context, n, 1);
                    tmp = fabs(ch0);
                    if(tmp > gMax0)
                        gMax0 = tmp;
                   tmp = fabs(ch1);
                    if(tmp > gMax1)
                        gMax1 = tmp;
           
    if(++gLcdTimer >= LCD_WRITE_INTERVAL) { gLcdTimer = 0; Bela_scheduleAuxiliaryTask(SerialLcdFuncs); } } }

    I see, good to know. Incidentally, I find that it is often easier to have something like this as an AuxiliaryTask function, if you are not worried about the drift between the implicit clock given by the number of executions of SerialLCD_BG_Task() and the audio clock (which should be fine in your case).

    void actualTask(void*)
    {
      while(!gShouldStop)
      {
        SerialLCD_BG_Task();
        usleep(50000);
      }
    }

    and then only schedule the task once in setup().

    Thanks for the tip. I will consider this when I implement it into a sketch.

    Nothing bad about your implementation, I would tend to use single-shot task functions and Bela_scheduleAuxiliaryTask for tasks that are tightly coupled with the audio (where you want responsiveness, some sort of deterministic behaviour and do not want jitter to happen), but for tasks that end up running in primary mode anyhow, a while(!gShouldStop){... usleep(...); ...} is often good enough, and less pain (no samples to count!). It also makes you explicitly acknowledge that there is no expected deterministic behaviour
    Just make sure you wait on !gShouldStop and not while(1), or the program will hang upon exit!

      giuliomoro It also makes you explicitly acknowledge that there is no expected deterministic behaviour

      This is a good point.

      There is not a tight dependency on audio synchronization so this one is a good fit for the more simple way to do it...unless somebody was using the GPIO as instrument triggers, but for that my debounce routine alone adds too much latency for that type of application (before it even gets to the UART).

      For the World: Don't use USB backpack GPIO for instrument triggers!

      nice one. I've had a display that looks like yours sitting on my desk for one year now, but it's a 1602F (I assume that's two 16x02) and it only has an I2C interface. But I guess I could try to port your code to it if I find the time. Or perhaps I should first read the datasheet!

      giuliomoro up to date? Where would you set the true/false?

      Oops, forgot to update the comment when I removed the boolean. My first implementation was too specific to my immediate application so I decided to let the specific application decide where to display the meter by using the correct "channel". Then I forgot to update the comment.

      This meter function could also be implemented without special code on the LCD device, so if you take any time to port it you might consider letting the BBB do the work. It's simple enough that porting my code to a different implementation might be more work than reinventing the wheel.