Last-Fi

by Pezi 4. July 2013 04:55

Introduction

This article will introduce Last-Fi – an F# powered internet radio player that uses a Raspberry Pi and Last.Fm services.  The motivation behind this work was to both build something fun and useful for the Raspberry Pi that interfaces with various pieces of hardware, but can also show off some features that F# is great at within a hardware context.

The result is Last-Fi – a highly asynchronous and stable music player.  It is based around Last.Fm, but because it uses MPD to play music it only requires a few minor adjustments that will allow it to work with any music source, whilst retaining the ability to obtain artist information from Last.Fm.  In terms of the hardware, you are able to control various aspects of Last.Fi with a controller from the 1980’s Nintendo Entertainment System.  Various information is displayed on a 2x16 back-lit LCD display.  In addition to this, the program also hosts a ServiceStack webservice, and uses FunScript, an F# to JavaScript compiler, in order to host a website that shows various information and allows direct control from any device on the same network.

I presented a talk on this work at SkillsMatter in London (here's the video), and you can find the slide deck here.  The code is all open source and you can find it on GitHub here (disclaimer: this code is highly subject to change and is largely a toy project of mine)

Last-Fi illustrates an extremely powerful programming language doing some of what it does best, the entire stack from the HTTP Last.FM API, to the hardware programming and even the JavaScript is written in F# code.  Not only that, it is almost all concurrent and completely immutable – I have yet to experience a single problem with any of the software.  The entire project is less than 800 lines of F#.  If you are new to F# be sure to check out www.fsharp.org and you can find instructions on how to get the Pi running with F# in a previous post I made here.

 

 

Hardware Elements

Last-Fi features two main hardware elements;

LCD Screen

Any standard 2x16 LCD screen compatible with the Hitachi HD44780 LCD controller is compatible with Last.Fi. The screen is used in 4-bit mode with no other additional circuitry and thus requires at least 7-8 IO pins from the Pi.  The screen I am using also has a backlight, but due to running out of pins and not wanting to make the circuitry more complex, the backlight is simply connected to always be on at full brightness. A nice addition to control the backlight would be some form of hardware pulse width modulation circuit.  The contrast of the screen is simply always at full contrast, this is just because having a potentiometer for this purpose was difficult to fit in the case, and in this instance the screen I am using looks best at full contrast anyway.

In terms of the software, you can read more about how the screens are controlled in a previous post.  The code has evolved a fair bit since then but the basic principles are the same.  A F# MailboxProcessor (henceforth known as an Agent) is employed within a standard .NET type to enable async execution and manage state whilst providing safe synchronization mechanics.   The agent accepts two messages, ScrollText and TemporaryText.  The former will cause the screen to display and scroll where necessary the two lines of text supplied in the message.  The scrolling happens all asynchronously in an isolated environment that has no affect on the rest of the system.  The latter message will cause the supplied text to appear on the LCD temporarily, and after a timeout period will revert back to scrolling whatever state was present before.

This code illustrates how we can create a self-contained asynchronous and isolated system that is packaged in a re-useable object.  It demonstrates how we can propagate state using an F# Agent, and how to use TryReceive() to provide both scrolling and temporary text timing, whilst providing the ability to quickly override messages.  This is useful in various situations, such as holding “up” on the NES pad will allow the volume to repeatedly increase without waiting for the previous temporary text that shows the volume to time out.

 

NES Pad

The Nintendo pad is essentially just an 8 bit parallel to serial shift register. Indeed, if you look inside one you will find a single chip which is exactly that.  The pad is used to skip tracks, adjust the volume, start / play / pause the music.  In order to use the NES pad, its internal shift register is hooked up to some of the Pi’s pins, and then at some specified polling interval, the state of the buttons are shifted in where something can happen to them.  Once again this is a very concurrent operation where a requirement is to poll at certain intervals, and therefore another Agent is used packaged up in a .NET type thus providing the reusable, isolated asynchronous object similar to the LCD.

For the NES object to be of any use, it requires some state.  It needs to know what the button state was previously in order to determine what has changed in the current iteration.  Once again the Agent forms the basis of this by propagating a state through each loop cycle that contains both the “current” state of the buttons and a list of buttons that were pressed in the past.

With this information, the object is able to raise .NET events such as ButtonDown and ButtonUp that a subscriber can listen to.  Using F#’s powerful active patterns the caller can also quite easily indicate it is interested in a more complicated pattern of buttons. In conjunction with this another F# feature is used – first class .NET events.  This means you can create an event in code and the register it with some pattern match based on button history and state.  In this way is is simple to create your own event that fires when Start and Select have both been pressed for 3 seconds, or to fire when the button history matches the Konami Code, for example.

    let startSelectEvent = Event<unit>()
    startSelectEvent.Publish.Add(fun _ -> printfn "start select!!")

    let konamiCodeEvent = Event<unit>()
    konamiCodeEvent.Publish.Add(fun _ -> printfn "Konami Code!!!"
                                         let aux = async {
                                            for i = 1 to 5 do
                                                // this will cause the message to flash 5 times (exciting!!)
                                                lcd.TemporaryText("Konami Code!","") 
                                                do! Async.Sleep(750) 
                                                lcd.TemporaryText("","")
                                                do! Async.Sleep(750) 
                                            return () }
                                         Async.Start aux )

    nes.AddCustomEvent((fun data ->
                        match data with
                        | NES.ButtonDown NES.Button.START length1 
                          & NES.ButtonDown NES.Button.SELECT length2 when length2 > 3000.0 && length1 > 3000.0 -> true
                        | _ -> false), startSelectEvent)

    nes.AddCustomEvent((function
                        | NES.ButtonSequence
                           [NES.Button.UP;NES.Button.UP;NES.Button.DOWN;NES.Button.DOWN;
                            NES.Button.LEFT;NES.Button.RIGHT;NES.Button.LEFT;NES.Button.RIGHT;
                            NES.Button.B;NES.Button.A] () -> true
                        | _ -> false),konamiCodeEvent)

Other Software

MPC / MPD

This is the Linux daemon used to actually play  music.  MPC is a command line interface to MPD.  Critically, MPC supports a command called “idle” which will block the current thread until something in the the daemon happens, at which point the program returns some text to indicate a change has happened.  The core Player object (discussed shortly) uses this in order to tell the LCD what to display, and when to load new tracks. However, ideally this operation wouldn’t actually block and this is where F#’s async workflows once again come in very useful – it’s trivial to wrap the MPC idle command in a async workflow and then be able to use it elsewhere without blocking.

Last.Fm API

In order to use the Last.FM API, HTTP POST messages are manually crafted and encrypted.  Thanks to F#’s extremely succinct record types and higher-order functions, this is able to be achieved in a very small amount of code. 

Player

This object is what connects to Last.Fm via the API, retrieves and queues track URLs to MPC, and provides the music interface to the rest of the program.  Once again, this is a F# agent running an async loop propagating state.  You might think this is overkill, but this object has to respond to commands both from the webservice, and from the NES pad, so the Agent nicely handles any sync problem.

Core

The core program itself uses ServiceStack to host a webservice that allows operations via an instance of the Player object.   The program initializes the various objects, hooks up various events between the objects and then sits there doing nothing perpetually Smile

Website

As mentioned before, the website is created by using a F# to JavaScript compiler, FunScript.  FunsScript is also a mini web server which means I can avoid using XSP or another ASP.NET equivalent.  FunScript also makes use of F# type providers to bring in TypeScript definition files, which essentially annotate populate JavaScript libraries with type information.  Using this you can basically bring any JavaScript library directly into the F# programming language. Pretty cool I say!

F# meets the Raspberry Pi

by Pezi 2. March 2013 22:58

I got a Pi for my birthday! A great excuse to get back into electronics.

After unsuccessfully struggling to get the F# compiler to build under the stable version of mono for Debian Wheezy, I realised that F# programs work just fine if you build normally from a windows computer, throw in FSharp.Core.dll in the /bin/ and copy it over. So I have a setup now where I work with VS2012 / Sublime and sync the executable and libraries with WinScp (or indeed the Scp plugin for FAR Manager).

Next up is to get access to the hardware. I built this C library as a shared object, dumped it in with the other binaries of my project and it worked through P/Invoke with no hassle at all ! 

I've quite a bit of electronics experience, so after I did some basic tests that the various I/O pins could be set to output and switch from High to Low, I skipped the traditional "hello world" of hardware (blinking an LED) and figured I'd try something a little bit more ambitious. I have a little LCD screen laying around in my old gear, it's a standard LCD driven by the Hitachi HD4480 LCD Controller. You can use these in 8-bit or 4-bit mode, with the 8 bit mode needing 4 more I/O pins.  I'm using the 4 bit mode because I don't really have that many pins and it's pretty easy (although a little fiddly) to use it in 4-bit mode.  

I'm using a total of 7 I/O pins,  E (enable), RS (switch between command and character mode), RW (I'm not actually using this right now) , and then DB4 through DB7 which are the 4 input bits (they are the higher nibble of the full 8 bits). This is the circuit :

In this schematic the Pi pins relate to the actual physical pin numbers, however in the code a mapping is needed over to what the Pi internally calls its GPIO pins.  I created an enum for this purposes, only containing the pins I am using for now

type GPIOPins =
    | Pin_11 = 17u
    | Pin_12 = 18u
    | Pin_13 = 27u
    | Pin_15 = 22u
    | Pin_16 = 23u
    | Pin_18 = 24u
    | Pin_22 = 25u

For example the physical pin 11 maps to GPIO number 17.  Infact when I first hooked this circuit up and wrote all the code to perform the LCD initilization I couldn't get it to work. Thankfully I happen to have a 16 channel logic analyzer in my scope so I hooked up all the inputs, set it to a single sweep triggering on the rising edge of the Enable pin over 500ms and noticed that the RW pin was always high - strange (I neglected to take a picture of the waveforms for this post :( ).  Turns out that the Pi user manual is WRONG, I have got a slightly later revision of the board where pin 13 is mapped to 27, not 21!

The next bit of code imports a couple of the functions from the C library and creates a couple of mini functions around them

[<DllImportAttribute("libbcm2835.so", EntryPoint = "bcm2835_init")>]
extern bool bcm2835_init()

[<DllImport("libbcm2835.so", EntryPoint = "bcm2835_gpio_fsel")>]
extern void bcm2835_gpio_fsel(GPIOPins pin, bool mode_out);

[<DllImport("libbcm2835.so", EntryPoint = "bcm2835_gpio_write")>]
extern void bcm2835_gpio_write(GPIOPins pin, bool value);

let fsel pin value = bcm2835_gpio_fsel(pin,value)                        
let write pin value = bcm2835_gpio_write(pin,value)            
let wait (ms:int) = System.Threading.Thread.Sleep(ms)

To use the LCD you write some bits to the data pins, then bring the Enable pin high for a few us then pull it low again ("pulse"). The LCD then does something depending on the input bits. In order to prepare it for 4-bit use I first have to send it a few 0x03 (0011) packets as per the spec indicates. Then I can switch it into 4-bit mode (0x2). From this point on, I can use all the 8-bit commands from the spec. Because I'm running in 4 bit mode I have to send the high nibble first, pulse, then send the low nibble. I wrapped some of the LCD functionality up in a F# record type (note: all this code is just a first stab, everything with hardware is inherently to do with mutating state so I won't be using a lot of the real functional features of the language just yet, but I'll see what I can do about that later)

type LCDCommands =
    | AllLow =          0b00000000
    | Clear =           0b00000001
    | Home =            0b00000010   
    | FourBit =         0b00100000   
    | TwoLine =         0b00001100    
    | DisplayOn =       0b00001100
    | CursorOn =        0b00000001
    | AutoIncCursor =   0b00000110    
    | Line2 =           0xC0
        
type LCD = { E : GPIOPins; RW : GPIOPins; RS : GPIOPins; 
             DB4 : GPIOPins; DB5 : GPIOPins; DB6 : GPIOPins; DB7 : GPIOPins; }
    with 
    member lcd.Pulse() = // toggles enable 
        write lcd.E true; wait 1
        write lcd.E false; wait 1
    member lcd.WriteNibble(value) = // write the lower four bits to the data pins and pulses
        write lcd.DB7 (value >>> 3 &&& 0x1 = 0x1)
        write lcd.DB6 (value >>> 2 &&& 0x1 = 0x1)
        write lcd.DB5 (value >>> 1 &&& 0x1 = 0x1)
        write lcd.DB4 (value &&& 0x1 = 0x1)
        lcd.Pulse()
        wait 1
    member lcd.WriteByte(value) =
        lcd.WriteNibble(value >>> 4) // write high nibble first
        lcd.WriteNibble(value)
    member lcd.Command = int >> lcd.WriteByte

I have captured some of the LCD commands in another enum - some of these have to be OR'd together as per the spec. I've just encoded the ones I'm going to use. The there's the pulse which toggles enable with a tiny delay. Because I'm in 4 bit mode I'll always be writing nibbles with a pulse at the end - the WriteByte function simply writes the high nibble first then the low nibble as the spec indicates. The last function is just a wrapper so I can directly use the LCDCommand enum.

member lcd.Initialize() = // I am only using the (annoyingly fiddly) 4 bit mode
        // assume 1000ms or so has passed since program start up
        // make sure pins are set to output
        fsel lcd.E   true; fsel lcd.RW  true
        fsel lcd.RS  true; fsel lcd.DB4 true
        fsel lcd.DB5 true; fsel lcd.DB6 true
        fsel lcd.DB7 true
        // zero them all out
        lcd.Command LCDCommands.AllLow
        // to start with we are only writing special wakeup nibbles
        lcd.WriteNibble(0x3); wait 5 // as per spec, first call has a 5ms wait
        lcd.WriteNibble(0x3); wait 1
        lcd.WriteNibble(0x3); wait 1
        // now set into 4 bit mode and send 8 bits in 2 nibbles from now on
        lcd.WriteNibble(0x2)
        lcd.Command(LCDCommands.FourBit ||| LCDCommands.TwoLine)     // set 5x8 mode 2 lines
        lcd.Command(LCDCommands.DisplayOn ||| LCDCommands.CursorOn)  // switch it on
        lcd.Command(LCDCommands.AutoIncCursor)

This is the startup sequence - set all the pins to Output, zero them all out, and then follow the startup sequence as per the spec. initially I have to just use nibbles, until the wake-up sequence is complete, then I can set it to 4-bit mode and use full byte commands. Once the display is in 4-bit mode I switch it to 5x8 mode with 2 lines and switch the screen on with a flashing cursor and so on.

member lcd.WriteText(text:string,clear) = 
        if clear then lcd.Command(LCDCommands.Clear)
        write lcd.RS true; wait 1
        Encoding.ASCII.GetBytes(text) |> Seq.iter(int >> lcd.WriteByte)
        write lcd.RS false; wait 1

Lastly a function to output some text. To do this you have to set the LCD into character output mode by pulling RS high; then you can send ASCII codes and the LCD will print them.

Pulling this all together I wrote that classic silly number-guessing game you write when learning to program, with the output on the LCD:

[<EntryPoint>]
let main argv = 
    try
        match bcm2835_init() with
        | true ->
            let lcd = { E = GPIOPins.Pin_11; RW = GPIOPins.Pin_12; RS = GPIOPins.Pin_13; 
                        DB4 = GPIOPins.Pin_15; DB5 = GPIOPins.Pin_16; DB6 = GPIOPins.Pin_18; DB7 = GPIOPins.Pin_22 }
            
            wait 1000
            lcd.Initialize()
            
            let rec loop number attempts =                
                 try
                    let guess = Console.ReadLine() |> Int32.Parse
                    if guess = number then                     
                        lcd.WriteText("CORRECT!!",true)
                        lcd.WriteByte(0xC0); 
                        lcd.WriteText("YOU WIN!",false)
                    elif attempts + 1 > 5 then
                        lcd.WriteText("WRONG!!",true)
                        lcd.WriteByte(0xC0);
                        lcd.WriteText("YOU LOSE!!",false)
                    else
                        lcd.WriteText("WRONG!! ",true)
                        lcd.WriteText((if number < guess then "< " else "> ") + guess.ToString(),false)
                        lcd.WriteByte(0xC0); wait 2
                        lcd.WriteText("GUESS AGAIN!",false)
                        loop number (attempts + 1 )
                 with
                 | _ -> printfn "Number not reconigsed. Try again"
                        loop number attempts
                        
            lcd.WriteText("Guess a number",true)
            lcd.WriteByte(0xC0); wait 2
            lcd.WriteText("0 <---> 50",false)
            loop (System.Random(DateTime.Now.Millisecond).Next(51)) 0

            
        | false -> printfn "failed to init"
    with
    | ex -> printfn "exception thrown : %s" <| ex.ToString()
    
    Console.Read()

Here's a pic of it working ..

Cool! This was just a silly project to test everything is working properly - I can take over the world now.