Skip to contentSkip to author details
 

Entity Framework Composable Queries using LinqKit

Written by Jeff Tyler
 EntityFramework  C#  LinqKit  SQL  Queries  Composable

One of the biggest drawbacks to SQL is the inability to reuse pieces of a query. Technically you can create views but then you wind up joining views to views consuming views and performance goes down the drain.

What if there was away to write a query once and then reuse that query anywhere that you needed that data and have the query injected into each of the queries. Changing one query suddenly changes all of them. Which maintains the DRY principal and once you have a query figured out nobody else has to rewrite the same thing.

With Entity Framework this seems like a simple solution. You write a query in a method and call the method inside of Entity Framework. Seems to make logical since, but when you run it you get an error informing you that there is an unknown method in the query. So now what...

Enter LinqKit https://github.com/scottksmith95/LINQKit...
LinqKit is a project that intercepts an expression and modifies it from something that entity framework will throw an error on to something that it can use.
it allows you to compose queries and reuse them so that you can apply the DRY prinicpal to your queries

For this example we are going to have a parent
each parent has a collection of children
each child has a collection of schools that are the schools that the child has attened

public class Parent  
{
   public int Id{get;set;}
   public string name{get;set;}
   public virtual ICollection<Child> Children {get;set;}
}
public class Child  
{
    public int Id{get;set;}
    public int ParentId {get;set;}
    public int Age{get;set;}
    [ForeignKey(nameof(ParentId))]
    public Parent Parent{get;set;}

    // many to many
    public ICollection<School> School{get;set;}
}

public School  
{
    public Id{get;set;}
    public ICollection<Child> Children{get;set;}
}

So we have a parent with a list of children
Now we want to get the children that are still less then 18
The query would like like

var minors = Db.Parents.Select(parent=> new {parent = parent, child = parent.Children.Where(child=> child.Age < 18});  

For each of the Parents we project to an anonymous object that holds the parent and a collection of their children that are less than 18.

Here is the same query with LinqKit

public static class ParentCompositions  
{
    public static Expression<Func<Parent,IQueryable<Child>>> Minors = 
    parent=>parent.Children.Where(child=> child.< 18);
}

Db.Parents.AsExpandable().Select(parent=>new {parent = parent, child = ParentCompositions.Minors.Invoke(parent)}  

As you can see we the compositon class now has a field that holds an expression representation of the same query we used earlier. The query calls AsExpandable on on the IQueryable Parents. AsExpandable is the method the LinqKit uses to intercept the query. Then in the select method we call Invoke which lets LinqKit know that it needs to intercept it. The SQL generated between the two examples will be exactly the same.

The cool thing about it is now anytime we need minor children for a parent, all we have to do is call the composition.

Now most of the time we don't get away with querying only one or two tables. So Lets get all of the parents and there children that are in high school and still a minor

public class Compositions  
{
     public class ChildSchoolQueryResult
     {
       public Child Child {get;set;}
       public School School {get;set;}
     }

     public class ParentsWithChildrenInSchoolQueryResult
     {
        public Parent Parent {get;set;}
        IQueryable<ChildSchoolQueryResult> ChildrenSchools{get;set}
     }
     public static Expression<Func<Child, ChildSchool>> LastSchoolAttended = 
        child => new ChildSchool { 
              Child = child,
              School = child.Schools.FirstOrDefault(school => school.Id == 
                                             child.Schools.Max(s2=>s2.Id)
                 };

     public static Expression<Func<Parent, ParentsWithChildrenInSchoolQueryResult>> 
     SchoolDependants= p=> 
            new ParentsWithChildrenInSchoolQueryResult{
               parent = p,
               ChildrenInSchool = 
               LastSchoolAttended.Invoke(ParentCompositions.Minors.Invoke(p).AsExpandable)
};

var HighSchoolDependants = Db.Parents.Select(parent=>  
    new { 
    Parent = parent, 
    children = p.children.where(c=> c.Age <= 18)
        .Select(child=> 
             new {
                     Child = child,
                     School = child.Schools.FirstOfDefault(
                       school => school.id == child.schools.Max(school2=>school2.Id)})
                }
       });
    }
}).Where(p=>p.Children.Any(p=>p.School.Name == "High School");
// LinqKit
var HighSchoolDependants = Db.Parents.Select(parent => Compositions.SchoolDependants.Invoke(parent))  
.where(p=>p.ChildrenSchools.Any(s=>School.Name == "High School"));
}

This is a good example of the two styles and their pros and cons.
Pros
Change the Composition changes the result everywhere
Complicated queries only get wrote once

Cons
Thought must be taken to create compositions correctly

Without LinqKit
Pros
No extra library
All logic is in one place

Cons
Logic has to be duplicated
Complicated Queries must be recopied


FPGA for Fun #1 (Part 2) - Driving the MAX 7219 LED Display Module

Written by Michael Earls
 FPGA  electronics

In a previous post, I covered the wiring diagrams, finite state machines, and LED segment encoding for the MAX7219 display module wired up to the Mojo FPGA.

In this post, I will cover the Lucid HDL code used to implement the state machines.

All of this Lucid HDL (and the resulting Verilog) can be found on my GitHub page:

FPGA for Fun on GitHub

Blog posts in this series:

The first thing to do after creating the project is define the constraints file to setup our pinouts. See the wiring diagram for details.

NET "max7219_load" LOC = P40 | IOSTANDARD = LVTTL;  
NET "max7219_data" LOC = P50 | IOSTANDARD = LVTTL;  
NET "max7219_clock" LOC = P51 | IOSTANDARD = LVTTL;

The MAX7219 Component

After adding an SPI Master component to the project from within the Mojo IDE, we need to add a new max7219 component to handle our operation states.

Here are the states that we'll be implementing:

Max7219 Component FSM

module max7219 (  
    input clk,  // clock
    input rst,  // reset
    input addr_in[8],
    input din[8],
    input start,
    output cs,
    output dout,
    output sck,
    output busy
  ) {

addr_in is used for our address data (from the MAX7219 Register Map), din is used for the data values coming in, and cs, dout, and sck are all used to manage the SPI interface with the MAX7219. We use busy to indicate that we're not in the IDLE state. start is used to trigger our state transfer from IDLE to TRANSFER_ADDR.

Let's continue with the Lucid code.

.clk (clk) {  
  .rst(rst) { 
      fsm state(#INIT(IDLE)) = {IDLE, TRANSFER_ADDR, TRANSFER_DATA};
      spi_master spi(.miso(0));
      dff data[8];
      dff addr[8];
      dff load_state;
    }
  }

We're initializing the components that we'll use in this module with the clk and rst values coming from the Mojo.

We define our finite state machine (fsm) and initialize it to a default state of IDLE. We then initialize an spi_master component to manage our SPI clock and data transfers. We're using d-flip-flops (dff) to hold the values that we send to the address and data lines because we need to store those values between clock cycles. We're also using a d-flip-flip to store the current state of our load (cs) pin.

  sig data_out[8];  
  sig mosi;
  counter count;

  always  {
    sck = spi.sck;
    // synchronize our counter with the spi clock so we can keep up with where we are
    count.clk = spi.sck;
    count.rst = 0;

    data_out = 8b0;
    spi.start = 0;
    mosi = 0;
    busy = state.q != state.IDLE;  // busy when not idle
    dout = 0;

In lines 1-2, we define a few signals to deal with the output data (what we'll actually send to the MAX7219 display) and the MOSI (Master Out Slave In). We also define a counter to use to keep up with how many SPI clock cycles have passed. This will let us manage the load pin and toggle it at the right time to latch the input data.

It's important to notice that in line 8, we set the clock of the counter to our SPI component's sck value. This ensures that our counter only increments in sync with the SPI data clock. It will help us keep up with how much data has been sent to the MAX7219.

  case (state.q) {  
      state.IDLE:
        load_state.d = 1;
        if (start) { // if we should start a new transfer
          // save our data and address values to memory
          addr.d = addr_in;
          data.d = din;
          count.rst = 1;

          // Toggle the load pin (makes the 7219 start listening for data)
          load_state.d = 0;

          state.d = state.TRANSFER_ADDR;
        }
      state.TRANSFER_ADDR:
        spi.start = 1;
        data_out = addr.q;
        dout = spi.mosi;
        if (count.value == 8){
          state.d = state.TRANSFER_DATA;
        }
      state.TRANSFER_DATA:  
        spi.start = 1;
        data_out = data.q;
        dout = spi.mosi;
        if (count.value == 16){
          // latch the data by pulsing the load pin (cs)
          load_state.d = 1;
          count.rst = 1;
          state.d = state.IDLE;
        }
    }

    cs = load_state.q;
    spi.data_in = data_out;

The last section of code in this component is the state management case statement. During the IDLE state, we first set the load pin high. The load pin stays high until we wish to write data to the MAX7219 (see the Timing Diagram). Then, we check to see if the start value is high. If so, we set the d-flip-flop values for address and data to the values coming in to the component. We then reset the counter and set the load pin low (to start reading into the MAX7219). After all of this, we transfer to the next state.

In the TRANSFER_ADDR state, we start the SPI component, set our data_out signal to equal the value of the addr flip-flop and set the dout value to the spi component's mosi value. We repeat the process 8 times total so that the SPI component will send the 8 bits we need for our address value. After the 8 bits are sent, we change to the next state.

In the TRANSFER_DATA state, we start the SPI component and set the data_out signal to our data value stored in the data flip-flop. We set the dout to the SPI mosi value. We repeat this process 8 times to ensure that we send all 8 bits of the data to the MAX7219. After sending the 8th bit (our counter is now at 16), we pulse the load pin high to latch the address and data into the MAX7219. We then reset the counter and switch to the IDLE state.

In line 34, we set the cs output to the value of our load flip-flop. Line 35 shows how we send the value from our data_out signal to the SPI data input. This is a bit confusing because we're mixing in with out. Think of it like this, we're outputting data from this component into another component. That component has an input that we're matching to our output. Because we're always connecting the dout output to the SPI component's mosi output (lines 18 and 25), our MAX7219 is always receiving its data from the SPI component. It's also managing the serial clock, so everything is in sync.

That's everything for the max7219 component. By using this component, we can control the MAX7219 chip from our Mojo FPGA.

Main Module

The next HDL to cover is the main component (mojo_top). This is a bit more complicated, because it uses the component we just covered to display "DEADBEEF" on the display.

The flow of this is basically just state management to switch from states based on this state diagram:

Main FSM

module mojo_top (  
    input clk,              // 50MHz clock
    input rst_n,            // reset button (active low)
    output led [8],         // 8 user controllable LEDs
    input cclk,             // configuration clock, AVR ready when high
    output spi_miso,        // AVR SPI MISO
    input spi_ss,           // AVR SPI Slave Select
    input spi_mosi,         // AVR SPI MOSI
    input spi_sck,          // AVR SPI Clock
    output spi_channel [4], // AVR general purpose pins (used by default to select ADC channel)
    input avr_tx,           // AVR TX (FPGA RX)
    output avr_rx,          // AVR RX (FPGA TX)
    input avr_rx_busy,      // AVR RX buffer full
    output max7219_load,
    output max7219_data,
    output max7219_clock
  ) {

  sig rst;                  // reset signal

  .clk(clk) {
    // The reset conditioner is used to synchronize the reset signal to the FPGA
    // clock. This ensures the entire FPGA comes out of reset at the same time.
    reset_conditioner reset_cond;

    .rst(rst) {
      max7219 max;
      fsm state(#INIT(IDLE)) = {IDLE, SEND_SHUTDOWN, SEND_RESET, SEND_NO_DECODE, SEND_ALL_DIGITS, SEND_TEST_ON, SEND_TEST_OFF, SEND_WORD, HALT};
      dff segments[8][8];
      dff segment_index[3];
    }
  }

  sig max_addr[8];
  sig max_data[8];

In line 29, you can see where we use a two-dimensional d-flip-flop to hold the segment data. The first dimension is the segment to display the data on (1-8 on the MAX7219), the second dimension is used to store the binary values representing the actual segment display (defined below as constants). We use the segment_index to keep up with what segment we're currently displaying.

Segment Encodings

  const C0 = b01111110;  
  const C1 = b00110000;
  const C2 = b01101101;
  const C3 = b01111001;
  const C4 = b00110011;
  const C5 = b01011011;
  const C6 = b01011111;
  const C7 = b01110000;
  const C8 = b01111111;
  const C9 = b01111011;
  const A = b01110111;
  const B = b00011111;
  const C = b01001110;
  const D = b00111101;
  const E = b01001111;
  const F = b01000111;
  const O = b00011101; // O
  const R = b00000101; // R
  const MINUS = b01000000; // -
  const BLANK = b00000000; // BLANK

These are the binary values for the segments on the MAX7219. I added O and R so that I could display the word "Error".

always { 
segments.d[7] = D; segments.d[6] = E; segments.d[5] = A; segments.d[4] = D; segments.d[3] = B; segments.d[2] = E; segments.d[1] = E; segments.d[0] = F;

If you are a software engineer like I am, you may be tempted to read this like source code. Hardware Description Languages (like Lucid, Verilog, and VHDL) are not programming languages. We need to have some way of expressing when things take place in relation to other things. Everything in the always block runs all of the time, as the word suggests. That's why we have to manage these states like we do, because this "code" is "executing" every time the clock cycles on the FPGA (the Mojo has a 50 MHz clock). It is truly parallel processing. That's one of the great advantages of FPGAs over microcontrollers. It's also why it's more complicated to manage.

The segments are numbered from right to left, so we need to display the characters in reverse order. We do this starting on line 2. Basically, we need to set the data stored in the segments flip-flop to the binary values of the character we wish to display. So, in line 2, the 7th segment (which will be segment 8 on the MAX7219 module) is set to the values stored in the D constant (b00111101). We'll loop through all 8 of these values in the SEND_WORD state and send the value of the flip-flop as the data to the max component.

The rest is fairly self-explanatory. We just transition from one state to another, sending in the appropriate address and data values as required by that state to get the desired result. We're not using the SEND_TEST_ON or SEND_TEST_OFF states, so those can be ignored.

Main Module State Machine Implementation

    reset_cond.in = ~rst_n; // input raw inverted reset signal  
    rst = reset_cond.out;   // conditioned reset

    led = 8h00;             // turn LEDs off
    spi_miso = bz;          // not using SPI
    spi_channel = bzzzz;    // not using flags
    avr_rx = bz;            // not using serial port
    max_addr = 8b0;
    max_data = 8b0;
    max.start = 0;

    case(state.q) {
      state.IDLE:
        segment_index.d = 0;
        state.d = state.SEND_SHUTDOWN;
      state.SEND_SHUTDOWN:
        max.start = 1;
        max_addr = h0C;
        max_data = h00;
        if(max.busy != 1) {
          state.d = state.SEND_RESET;
        }
      state.SEND_RESET:
        max.start = 1;
        max_addr = h0C;
        max_data = h01;
        if(max.busy != 1) {
          state.d = state.SEND_NO_DECODE;
        }
      state.SEND_TEST_ON:
        max.start = 1;
        max_addr = h0F;
        max_data = h01;
        if(max.busy != 1) {
          state.d = state.SEND_NO_DECODE;
        }
      state.SEND_TEST_OFF:
        max.start = 1;
        max_addr = h0F;
        max_data = h00;
        if(max.busy != 1) {
          state.d = state.SEND_NO_DECODE;
        }
      state.SEND_NO_DECODE:
        max.start = 1;
        max_addr = h09;
        max_data = h00;
        if(max.busy != 1) {
          state.d = state.SEND_ALL_DIGITS;
        }
      state.SEND_ALL_DIGITS:
        max.start = 1;
        max_addr = h0B;
        max_data = h07;
        if(max.busy != 1) {
          state.d = state.SEND_WORD;
        }
      state.SEND_WORD:
        if(segment_index.q < 8)
        {
          max.start = h01;
          max_addr = segment_index.q + 1;
          max_data = segments.q[segment_index.q];
          if(max.busy != 1) {
            segment_index.d = segment_index.q + 1;
          }
        } 
        else {
          segment_index.d = 0;
          state.d = state.HALT;      
        }
      state.HALT:
        max_addr = 8b0;
        max_data = 8b0;
    }

    max.addr_in = max_addr;
    max.din = max_data;
    max7219_clock = max.sck;
    max7219_data = max.dout;
    max7219_load = max.cs;
  }

}

On line 59, we check to see if our segment index is less than 8. If so, we set the address to the value of the segment index +1 on line 62 (to account for the fact that index 0 should write to segment 1, index 1 to segment 2, etc.) We wait for the max component to finish by keeping these values until the busy flag goes low (line 64). When that happens, we simply increment our segment index by 1.

On line 69, we handle the case when the index is equal to 8 (well, technically, it's no longer less than 8, but it's 8 in our case). Then, we simply transition to the HALT state, which does nothing but send a no-op (data 0 at address 0).

Starting on line 79, we need to set the output values going to the actual module to the values that we generate in the max component. We set the clock, data, and load pins accordingly.

That's it! This will display "DEADBEEF" on the MAX7219 display.

DEADBEEF

Video:


FPGA for Fun #1 (Part 1) - Driving the MAX 7219 LED Display Module

Written by Michael Earls
 FPGA  electronics

I recently ordered a few LED display modules based on the MAX7219 chip. When they arrived, I immediately began putting together projects to use them. The first project was to wire one up to my Raspberry Pi and use C to bitbang some numbers to display. I wrote about this in a previous post.

The Lucid HDL "code" can be found on GitHub - FPGA for Fun on GitHub

Blog posts in this series:

This time, I'd like to talk about how I wired the MAX7219 LED display module up to my Mojo FPGA.

The Module

MAX 7219 LED Display Module

Here is a module on eBay similar to the ones that I purchased:

Red MAX7219 8-Digit LED Display SPI Control Module Digital Tube For Arduino

If this link is dead (or the sale has ended), search for the above text on eBay and you should find one.

Wiring the MAX7219 to the Mojo

First, I started by wiring the Max 7219 to the Mojo.
MAX7219 Mojo wiring diagram

I wired RAW from the Mojo to VCC, GND to GND, Pin 50 to Data In (DIN), Pin 51 to CLK, and Pin 40 to CS (Load).

MAX7219 Datasheet (PDF)

Timing Considerations

The timing diagram for the MAX7219 module is fairly straight forward. It only covers four of the interface pins. We won’t be using the data out pin (DOUT), so we can ignore that one.

We’re interested in the behavior of the Load, Clock, and the data in pins and how they interact.

MAX7219 Timing Diagram

Since we are working with an FPGA clock that runs too fast for our display module, we’ll need to use an SPI component to manage our communication and synchronize with that clock instead of the FPGA. In addition, we’ll use a d-flip-flop to manage the state of the load pin between clock cycles.

After creating a new Mojo project, I added the SPI Master component to it (it's one of the available components in the Mojo IDE).

The load pin must be low to clock data in, so that’s our first step when sending data. Once the load pin is low, we start our clock pulses to the CLK pin. As you can see in the timing diagram, we need to send one bit of data at roughly the same time as our clock pulse. The Max 7219 uses a 16-bit word for each message that it processes, so we need to keep up with how many clock cycles have passed since we started (we'll use a d-flip-flop to do this in the implementation).

Once we get to 16 clock pulses on the CLK pin, the data is then latched into either the digit or control registers by pulsing LOAD/CS.

LOAD/CS must go high concurrently with or after the 16th rising clock edge, but before the next rising clock edge or data will be lost. This is very important to remember and was the source of a great deal of trouble for me when first building this project. I spent a few hours debugging this (I don't yet have the wave capture utility setup in my Mojo IDE and the logic probe that I ordered hasn't arrived from Hong Kong, yet).

Finite State Machines

I've found that planning my logic out in terms of finite state machines really helps me get my head around the problem. The Max7219 component is driven by three main states: Idle, Transfer Address, and Transfer Data. It requires a little setup to synchronize the clocks and manage the state of the load pin.

MAX7219 Component FSM

MAX7219 Component FSM

We need to make an assumption about the overall state of the component; there is an SPI Clock counter with its clock input set to the SCK output of the SPI component.

IDLE
  • Set Load (CS) to 1
  • If Start == 1
    • Save incoming address and data to memory
    • Reset the SPI clock counter (helps manage two clock domains)
    • Set Load to 0 (tells the MAX7219 to start listening)
    • Transition to next state
TRANSFER_ADDR
  • Start the SPI component
  • Send the address we have in memory to the SPI component’s input
  • Set the data out to the value of the SPI.MOSI
  • If the SPI clock counter == 8
    • Transfer to the next state.
TRANSFER_DATA
  • Start the SPI component
  • Send the data we have in memory to the SPI component’s input
  • Set the data out to the value of the SPI.MOSI
  • If the SPI clock counter == 16
    • Latch the data by pulsing the load pin high
    • Reset the SPI clock counter
    • Transition back to the IDLE state to await more input

Register Map

There are 13 registers on the Max7219 module including a no-op.

Max7219 Register Map

For example, if I wanted to turn on all of the segments on the display (test mode), I would send 0x0F as the first 8 bits of the message (the address), and then 0x01 as the next 8 bits (the data). I would then pulse the Load pin to latch the message into the Max7219.

Let’s go over the order of operations that I’ll use in my test component:

  • Shutdown = 0
  • Shutdown = 1
  • Decode Mode = 0 (display segments instead of BCD data)
  • Scan Limit = 7 (use all 8 digits)
  • Send 8 characters
  • Halt (send all zeros - no-op)

Main Component (Mojo Top) FSM

The main component is a little more complicated because it is the one that drives the MAX7219 component. It keeps up with the characters that are being sent, as well as the states required to initialize the MAX7219 module.

This particular component was written as an initial test to see if I could get data to display. It does not handle any sort of number conversion or true display driver functionality. It merely outputs “DEADBEEF” to the display. The segment index is simply a d-flip-flop that I use to keep up with which segment we're sending the data for. Since there is more than one segment, we have to send them one at a time. We use the index to determine which one to send.

Main Component FSM

IDLE
  • Reset segment index to 0
  • Transition to next state
SEND_SHUTDOWN
  • Start the Max7219 component
  • Set the max7219 address to 0C (Shutdown)
  • Set the max7219 data to 00
  • When max is complete, transition to next state

Remember that all actions shown are simultaneous and happen for each clock cycle. That’s why we have to wait until the Max7219 component has completed sending its message and is no longer busy before moving on to the next state. Otherwise, we’d overload the module and nothing would be displayed. It’s a careful balancing act of clock, data, and load.

SEND_RESET
  • Start the Max7219 component
  • Set the max7219 address to 0C (Shutdown)
  • Set the max7219 data to 01 (Normal operation)
  • When max is complete, transition to next state
SEND_NO_DECODE
  • Start the Max7219 component
  • Set the max7219 address to 09 (Decode Mode)
  • Set the max7219 data to 00
  • When max is complete, transition to next state
SEND_ALL_DIGITS
  • Start the Max7219 component
  • Set the max7219 address to 0B (Scan Limit)
  • Set the max7219 data to 07 (use all 8 digits)
  • When max is complete, transition to next state
SEND_WORD
  • Start the Max7219 component
  • If segment index < 8
    • Set the max7219 address to the segment index +1 (to account for a zero-based index and a 1 based segment count)
    • Set the max7219 data to segment array with the index of segment index (segments.q[segment_index.q])
    • When max is complete, increment the segment index by 1
  • If segment index = 8
    • Reset segment index to 0
    • Transition to the next state (HALT)
HALT
  • Set the max7219 address to 0
  • Set the max7219 data to 0

Segment Lines

Programming the 7-segments is done by sending an 8-bit number representing the state of the segments in order from A-G with the decimal point being the most significant bit.

7-Segment Layout

I coded the 16 hexadecimal digits and added a few characters so that the word “Error” could be displayed, as well as a minus sign and a blank space.

0 = 01111110
1 = 00110000
2 = 01101101
3 = 01111001
4 = 00110011
5 = 01011011
6 = 01011111
7 = 01110000
8 = 01111111
9 = 01111011
A = 01110111
B = 00011111
C = 01001110
D = 00111101
E = 01001111
F = 01000111
O = 00011101
R = 00000101
MINUS = 01000000
BLANK = 00000000

That's the basic state workflow for the module on the FPGA. I had a great time figuring this out and I hope this has helped you understand how you might wire a MAX7219 module to your FPGA.

In the next post, we'll take a look at the implementation in the Lucid Hardware Description Language. Lucid is the HDL used by the Mojo. It is a simpler HDL than Verilog or VHDL, but transpiles to Verilog for synthesis onto the hardware.

Video:

Read Part 2


Driving the Max 7219 7-Segment Display Module from ASP.NET Core on the Raspberry Pi

Written by Michael Earls
 programming  electronics  raspberry pi  C#  .NET Core

In a previous blog post, I discussed how to create a dotnet core 2.0 WebApi application to run on the Raspberry Pi and trigger an LED. This post will build upon that by outlining the steps I took to connect a Max 7219 7-Segment Display Module to the Pi and extend my WebApi to display numbers on the display.

I recently purchased a Max 7219 7-Segment Display Module on eBay. It came in the mail today and I got right to work interfacing it with my Raspberry Pi. I used the excellent instructions found in this post on Plingboot:

Driving the Max7219 with the Raspberry Pi.

I had to convert the C++ code to C# to get it to work with dotnet core, but that didn't take too long. I also added a hex decoder and the ability to use multiple digits.

I hooked up my Pi using the following pin configuration:

  • Pin 11 (Gpio 17) - Data (DIN)
  • Pin 15 (Gpio 22) - Clock (CLK)
  • Pin 16 (Gpio 23) - Load (CS)
  • Pin 1 (3.3v) - 220 Ohm resistor, then to VCC
  • Pin 6 (GND) - GND

I used the same Cake code and foundation WebApi code and added a new Controller for the Max7219. Here is the code:

using System;  
using System.Collections.Generic;  
using System.Linq;  
using Microsoft.AspNetCore.Mvc;  
using Unosquare.RaspberryIO;  
using Unosquare.RaspberryIO.Gpio;

namespace WebApi.Controllers  
{
    [Route("api/[controller]")]
    public class Max7219Controller : Controller
    {
        private GpioPin DataPin = Pi.Gpio.Pin00; // GPIO 17 (WiringPi pin num 0)  header pin 11
        private GpioPin ClockPin = Pi.Gpio.Pin03; // GPIO 22 (WiringPi pin num 3)   header pin 15
        private GpioPin LoadPin = Pi.Gpio.Pin04; // GPIO 23 (WiringPi pin num 4)   header pin 16

        private Dictionary<char, int> Characters = new Dictionary<char, int> 
        {
            {'-', 0b01000000},
            {'0', 0b01111110},
            {'1', 0b00110000},
            {'2', 0b01101101},
            {'3', 0b01111001},
            {'4', 0b00110011},
            {'5', 0b01011011},
            {'6', 0b01011111},
            {'7', 0b01110000},
            {'8', 0b01111111},
            {'9', 0b01111011},
            {'A', 0b01110111},
            {'B', 0b00011111},
            {'C', 0b01001110},
            {'D', 0b00111101},
            {'E', 0b01001111},
            {'F', 0b01000111},
            {'O', 0b00011101},
            {'R', 0b00000101},
            {' ', 0b00000000}
        };
        private int DECODE_MODE = 0x09;
        private int INTENSITY = 0x0a;
        private int SCAN_LIMIT = 0x0b;
        private int SHUTDOWN = 0x0c;
        private int DISPLAY_TEST = 0x0f;

        public Max7219Controller()
        {
            //We need 3 output pins to control the Max7219: Data, Clock and Load
            DataPin.PinMode = GpioPinDriveMode.Output;
            ClockPin.PinMode = GpioPinDriveMode.Output;
            LoadPin.PinMode = GpioPinDriveMode.Output;
        }

        // GET api/values
        [HttpGet]
        public IEnumerable<string> Get()
        {
            Max7219Send(DISPLAY_TEST, 1);
            Max7219Send(DECODE_MODE, 0);   // Set BCD decode mode off
            Max7219Send(INTENSITY, 1);     // set brightness 0 to 15
            Max7219Send(SHUTDOWN, 1);      // come out of shutdown mode    / turn on the digits
            return new string[] { "Test mode. Call Max7219/{number} to show a number on the display" };
        }

        [HttpGet("{value}")]
        public string Get(int value)
        {
            /* 
            BCD decode mode off : data bits correspond to the segments (A-G and DP) of the seven segment display.
            BCD mode on :  0 to 15 =  0 to 9, -, E, H, L, P, and ' '
            */

            Max7219Send(DECODE_MODE, 1);   // Set BCD decode mode on
            Max7219Send(INTENSITY, 1);     // set brightness 0 to 15
            Max7219Send(SHUTDOWN, 1);      // come out of shutdown mode    / turn on the digits
            Max7219Send(1,value);

            /*
            var pin = Pi.Gpio.Pin00;
            pin.PinMode = GpioPinDriveMode.Output;
            pin.Write(value);
            return $"pin {pin.BcmPinNumber} set to {value}";
            */

            return $"sent {value} to Max7219";
        }


        /*
        GetFormatted() will set the value based on the first argument passed in. It will either display the hex or the decimal/integer value.
        http://servername/api/max7219/hex/10
        will display an "A"
         */
        [HttpGet("{format}/{value}")]
        public string GetFormatted(string format, int value)
        {
            Max7219Send(DISPLAY_TEST, 0);  // Disable test mode
            Max7219Send(INTENSITY, 0x01);     // set brightness 0 to 15
            Max7219Send(SHUTDOWN, 0x01);      // come out of shutdown mode    / turn on the digits
            Max7219Send(SCAN_LIMIT, 0x07);    // use all digits
            /* 
            BCD decode mode off : data bits correspond to the segments (A-G and DP) of the seven segment display.
            BCD mode on :  0 to 15 =  0 to 9, -, E, H, L, P, and ' '
            */

            Max7219Send(DECODE_MODE, 0);   // Set BCD decode mode off

            var outValue = format.ToLower() == "hex" ? value.ToString("X") : value.ToString();

            if (outValue.Length > 8)
            {
                // send error message
                outValue = "ERROR";
            }

            // make sure we have enough characters to work with
            outValue = outValue.PadLeft(8, ' ');
            var values = outValue.Reverse().Take(8).ToArray();

            for (int i = 0; i < 8; i++)
            {
                var outChar = values[i];
                Max7219Send(i + 1, Characters[outChar]);
            }

            return $"sent {outValue.Trim()} to Max7219 in {format} format.";
        }

        private void Max7219Send(int regNumber, int dataOut)
        {
            LoadPin.Write(GpioPinValue.High); // set LOAD High to start
            Send16Bits((regNumber << 8) + dataOut);   // send 16 bits ( reg number + dataout )
            LoadPin.Write(GpioPinValue.Low); // LOAD Low to latch
            LoadPin.Write(GpioPinValue.High); // set LOAD high to finish
        }

        private void Send16Bits(int output)
        {
            for(short i = 16; i > 0; i--)
            {
                int mask = 1 << (i - 1); // calculate bitmask

                ClockPin.Write(GpioPinValue.Low);

                // send one bit on the data pin
                if (Convert.ToBoolean(output & mask))
                {
                    DataPin.Write(GpioPinValue.High);
                }
                else
                {
                    DataPin.Write(GpioPinValue.Low);
                }

                ClockPin.Write(GpioPinValue.High);
            }
        }
    }
}

This code will allow you to use either decimal(integer) or hexadecimal values depending on how you call the method.

http://servername/api/max7219/hex/42

will display "2A" on the display, while

http://servername/api/max7219/decimal/42

will display "42".

Also, if the output data is more than 8 characters in length, it will display "Error" (line 113).
ERROR ERROR on the Max7219 8-character 7-segment display

Update - November 29, 2017 - Here are the hexadecimal digits for versions of C# prior to 7 that do not support the binary literals:

private Dictionary<char, int> Characters = new Dictionary<char, int>  
{
    {'0', 0x7e},
    {'1', 0x30},
    {'2', 0x6d},
    {'3', 0x79},
    {'4', 0x33},
    {'5', 0x5b},
    {'6', 0x5f},
    {'7', 0x70},
    {'8', 0x7f},
    {'9', 0x7b},
    {'A', 0x77},
    {'B', 0x1f},
    {'C', 0x4e},
    {'D', 0x3d},
    {'E', 0x4f},
    {'F', 0x47},
    {'O', 0x1d},
    {'R', 0x05},
    {'-', 0x40},
    {' ', 0x00}
};

Is the feedback loop the key to a learning computer?

Written by Michael Earls
 music  electronics  machine learning

When I was in my early 20's, I setup my Korg Poly-Six synthesizer so that the audio output fed into a Radio Shack (Realistic) Reverb Machine with the output of that going back into the synthesizer input (a modification that someone had added to the synthesizer before I bought it) for a complete feedback loop. I got some very interesting sounds as the synthesizer processed its own signals after a delay set by the knobs on the reverb machine.

I would spend hours experimenting getting the right sound. This was around the same time that I was attending technical school where I studied computer programming. I used to sit and think about how a computer could be "taught" how to play music if there was just some way I could emulate the feedback loop from my synthesizer using a computer. I felt that the key to artificial intelligence was in that feedback loop. Fr each iteration of the loop, you would introduce new information that would be processed and added to the previous iterations. After all, this is how my new musical sounds were being generated.

That was my amateur version of AI. Today, there are real scientists and engineers working on machine learning using "feedback loops" of some sort. I have no idea how it really works, nor do I have the patience and education required to enter this field of study, but I can't help but be interested in how I would apply machine learning to music.

I realize that loops are the core currency in a computer system, but I was thinking more abstractly.


Running a .NET Core 2.0 WebApi app on the Raspberry Pi (Raspbian)

Written by Michael Earls
 .NET Core  .NET  WebAPI  raspberry pi  programming

I wanted to create a web api app that answered calls on my Raspberry Pi. The first step I learned was how to install and configure .NET core 2.0 on the Raspberry Pi.

Note - .NET Core only runs on the Raspberry Pi 2 or 3. It will not run on the Raspberry Pi Zero or Zero W.

To get this working, I followed these great instructions from Jeremy Lindsay:

Running a .NET Core 2 app on Raspbian Jessie, and deploying to the Pi with Cake

Once I had this template installed, I simply created a new .NET Core web api app:

Note - Make sure you install the .NET core 2.0 SDK before attempting any of this. .NET Downloads

dotnet new webapi -n WebApiApp

I added the following line of code to the Program.cs file. This will ensure that the Pi answers to all host names (so I can access it from other computers other than localhost).

WebHost.CreateDefaultBuilder(args)  
    .UseStartup()
    .UseUrls("http://*:5000") // add this line
    .Build();

I then copied the following files and folders from the directory created in the sample from the article above to my new WebApiApp folder:

  • build.cake
  • tools
  • publish

I opened up build.cake and changed the correct app name to the name of the project. Here are my configuration settings:

///////////////////////////////////////////////////////////////////////  
// ARGUMENTS (WITH DEFAULT PARAMETERS FOR LINUX (Ubuntu 16.04, Raspbian Jessie, etc)
///////////////////////////////////////////////////////////////////////
var runtime = Argument("runtime", "linux-arm");  
var destinationIp = Argument("destinationPi", "192.168.1.113");  
var destinationDirectory = Argument("destinationDirectory", @"/home/pi/DotNetConsoleApps/WebApiApp");  
var username = Argument("username", "pi");  
var sessionname = Argument("sessionname", "Raspberry Pi");  
var executableName = Argument("executableName", "WebApiApp");

I ensured that the /home/pi/DotNetConsoleApps/WebApiApp directory existed on the Raspberry Pi and then ran the build script from the Powershell terminal:

../build

This used cake to clean, build, and deploy my web api app onto my Raspberry Pi.

Once this was complete, I switched to my PuTTY terminal and typed in ./WebApiApp to start the Web Api app. It started listening on port 5000. I was able to access the Web Api from a browser on my PC by using the IP address of the Raspberry Pi:

http://192.168.1.113:5000/api/values

This displayed the default project api controller output of the template app created in the earlier step.

I can't believe how easy this was. .NET core is really improving the development workflow immensely over previous versions of .NET. This was so much better than what came before.

Update: I was able to get an LED wired up to the WebApi by using this great article from Carlos Mendible:

Note - When you use the Gpio library, you will need to start your WbApiApp using sudo ./WebApiApp due to the need to access IO pins

Toggle Raspberry Pi GPIO Pins with ASP.NET Core 2.0

I wired the LED to physical pin 11 on the Pi. Here's my controller (not much different from his sample):

using System.Collections.Generic;  
using Microsoft.AspNetCore.Mvc;  
using Unosquare.RaspberryIO;  
using Unosquare.RaspberryIO.Gpio;

namespace WebApi.Controllers  
{
    [Route("api/[controller]")]
    public class GpioController : Controller
    {
        // GET api/values
        [HttpGet]
        public IEnumerable Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        [HttpGet("{value}")]
        public string Get(bool value)
        {
            var pin = Pi.Gpio.Pin00;
            pin.PinMode = GpioPinDriveMode.Output;
            pin.Write(value);
            return $"pin {pin.BcmPinNumber} set to {value}";
        }
    }
}

Raspberry Pi with LED wired to WebApi (not pictured) The state of the LED after sending a true to the gpio api method.


The Hand-MIDI Interface Project - Adafruit Feather 32u4 with Magnetometer

Written by Michael Earls
 electronics  music  diy  programming

A few months ago, I had an idea to create a new musical instrument that mounted on a glove. I used a cycling glove that I had from that time that I bought the Trek bike and rode it twice (long story).

I bought the following parts from Adafruit:

When the parts arrived, I started working on my "instrument". The idea was that I'd mount a Raspberry Pi to the back of my hand and wire up the 9-DOF sensor to one hand and the accelerometer to the other. I quickly learned that the Raspberry Pi is too big to mount on the back of a hand and ended up going a different direction. So, I ordered the following parts to try again:

I also ordered the following from ebay:

Once these new parts arrived, I got busy with my project. I first started by soldering some 4-conductor twisted pair phone wire to the I2C interface of the 9-DOF sensor. I made the wire about 8 inches long and then terminated it with one of the RJ-11 connectors.

9-DOF Sensor with RJ-11 connector 9-DOF Sensor with RJ-11 Connector

I then connected the Cat-3 keystone jack to the Feather's I2C port so that the wires would match how I connected them to the 9-DOF sensor. This would allow me to "plug-and-play" my I2C sensors. I originally tried to sew the sensor onto the palm of the glove using the conductive thread, but I never could get it to solder onto the sensor properly, so I ended up eliminating the conductive thread altogether.

Adafruit Feather with Cat-3 Keystone Jack Adafruit Feather with Cat-3 Keystone Jack

Ultimately, I decided to forego the glove completely until I could prove my concept.

Once I had the hardware wired up, it was time to write the sketch for the Feather. The Adafruit Feather that I bought is based on the Arduino 32u4 processor, so I opened up the Arduino IDE and started modifying the sample code for the 9-DOF sensor (and merged in sample code from the OLED display, as well).

I was able to get my custom cerkit.com logo on the display and that made me happy.

cerkit.com Logo on the Adafruit OLED Display Featherwing

Here is the code that displays the output of the 9-DOF sensor to the serial monitor of the Arduino IDE:

#include <Adafruit_SSD1306.h>

/*********************************************************************
  This is an example for our Monochrome OLEDs based on SSD1306 drivers

  Pick one up today in the adafruit shop!
  ------> http://www.adafruit.com/category/63_98

  This example is for a 128x32 size display using I2C to communicate
  3 pins are required to interface (2 I2C and one reset)

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada  for Adafruit Industries.
  BSD license, check license.txt for more information
  All text above, and the splash screen must be included in any redistribution
*********************************************************************/

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM9DS0.h>

using namespace std;

#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

#define BUTTON_A 9
#define BUTTON_B 6
#define BUTTON_C 5

#define CHANNEL 1

bool _invertText = false;

/* Assign a unique base ID for this sensor */
Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(1000);  // Use I2C, ID #1000  
char _xBuf [10] = { 0 };  
char _yBuf [10] = { 0 };  
char _zBuf [10] = { 0 };  
char _lsmData [128] = { 0 };


/***************************************************************************************************
   Splash screen image generated by LCD Assistant
   http://en.radzio.dxp.pl/bitmap_converter/
   See https://learn.adafruit.com/monochrome-oled-breakouts/arduino-library-and-examples
   for more details
 **************************************************************************************************/

static const unsigned char PROGMEM cerkit_splash [] = {  
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x7F, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x8F, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x02, 0x1F, 0x07, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x04, 0x1F, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x08, 0x1F, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x10, 0x1F, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x02, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x38, 0x1F, 0x0F, 0xE4, 0x00, 0x00, 0x00, 0x02, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x3C, 0x0F, 0x1F, 0xE6, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x4E, 0x03, 0x3F, 0x8E, 0x0E, 0x07, 0x02, 0xC2, 0x31, 0xC1, 0xF8, 0x00, 0x07, 0x07, 0x07, 0x60,
  0x47, 0x00, 0xFF, 0x1E, 0x11, 0x08, 0x83, 0x22, 0x40, 0x40, 0x40, 0x00, 0x08, 0x88, 0x84, 0x90,
  0xC3, 0xC0, 0xFC, 0x3F, 0x21, 0x10, 0x42, 0x22, 0x80, 0x40, 0x40, 0x00, 0x10, 0x90, 0x44, 0x90,
  0x81, 0xFE, 0xE0, 0xFF, 0x20, 0x10, 0x42, 0x02, 0x80, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x7F, 0x03, 0xFF, 0x20, 0x1F, 0xC2, 0x03, 0x00, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x07, 0x1F, 0xFF, 0x20, 0x10, 0x02, 0x02, 0x80, 0x40, 0x40, 0x00, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x00, 0xFF, 0xFF, 0x20, 0x10, 0x02, 0x02, 0x40, 0x40, 0x4C, 0x10, 0x10, 0x10, 0x44, 0x90,
  0x80, 0x00, 0xFF, 0xFF, 0x11, 0x08, 0x42, 0x02, 0x20, 0x40, 0x48, 0x38, 0x08, 0x88, 0x84, 0x90,
  0xC0, 0x07, 0x1F, 0xFF, 0x0E, 0x07, 0x82, 0x02, 0x10, 0x40, 0x30, 0x10, 0x07, 0x07, 0x04, 0x90,
  0x40, 0x3F, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x41, 0xFE, 0xC0, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x43, 0xE0, 0xFC, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x2F, 0x80, 0xFF, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x1E, 0x00, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x1C, 0x00, 0xFF, 0xC8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x0C, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x02, 0x00, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x80, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x70, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#if (SSD1306_LCDHEIGHT != 32)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

void setup()  
{
  Serial.begin(9600);
  Serial.println(F("cerkit.com warbly hand controller")); Serial.println("");

  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
  // init done

  // Show image buffer on the display hardware.
  // Since the buffer is initialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.
  display.display();
  delay(2000);

  display.clearDisplay();
  display.display();

  showCustomSplashScreen();

  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(BUTTON_C, INPUT_PULLUP);

  /* Initialize the sensor */
  lsm.begin();

  /* Display some basic information on this sensor */
  displaySensorDetails();

  /* Setup the sensor gain and integration time */
  configureSensor();

  Serial.println(F("Found LSM9DS0 9DOF"));
}


void loop() {  
  performSensorSweep();
}

void showCustomSplashScreen()  
{
  // compensate for original image being inverted
  display.invertDisplay(true);
  display.drawBitmap(0, 0, cerkit_splash, 128, 32, WHITE);
  display.display();
  delay(2000);

  // put the display back to normal
  display.invertDisplay(false);

  display.clearDisplay();
  display.display();
}

void updateScreen(char* msg, int x, int y)  
{
  display.setCursor(x, y);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.print(msg);
  display.display();
}

void printMessage(char* msg)  
{
  printMessage(msg, 2);
}

void printMessage(char* msg, int textSize)  
{
  display.clearDisplay();
  display.setCursor(0, 0);
  display.setTextSize(textSize);
  if (_invertText)
  {
    display.setTextColor(BLACK, WHITE); // 'inverted' text
  }
  else
  {
    display.setTextColor(WHITE);
  }

  display.print(msg);
  display.display();
}

void performSensorSweep()  
{
  /* Get a new sensor event */
  sensors_event_t accel, mag, gyro, temp;

  lsm.getEvent(&accel, &mag, &gyro, &temp);

  // print out accelerometer data
  Serial.print("Accel X: "); Serial.print(accel.acceleration.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(accel.acceleration.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(accel.acceleration.z);     Serial.println("  \tm/s^2");
  Serial.println("**********************\n");

  // print out magnetometer data
  Serial.print("Magn. X: "); Serial.print(mag.magnetic.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(mag.magnetic.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(mag.magnetic.z);     Serial.println("  \tgauss");
  Serial.println("**********************\n");

  // print out gyroscopic data
  Serial.print("Gyro  X: "); Serial.print(gyro.gyro.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(gyro.gyro.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(gyro.gyro.z);     Serial.println("  \tdps");

  Serial.println("**********************\n");

  delay(250);
}

/**************************************************************************/
/*
    Displays some basic information on this sensor from the unified
    sensor API sensor_t type (see Adafruit_Sensor for more information)
*/
/**************************************************************************/
void displaySensorDetails(void)  
{
  sensor_t accel, mag, gyro, temp;

  lsm.getSensor(&accel, &mag, &gyro, &temp);

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(accel.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(accel.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(accel.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(accel.max_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Min Value:    ")); Serial.print(accel.min_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Resolution:   ")); Serial.print(accel.resolution); Serial.println(F(" m/s^2"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(mag.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(mag.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(mag.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(mag.max_value); Serial.println(F(" uT"));
  Serial.print  (F("Min Value:    ")); Serial.print(mag.min_value); Serial.println(F(" uT"));
  Serial.print  (F("Resolution:   ")); Serial.print(mag.resolution); Serial.println(F(" uT"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(gyro.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(gyro.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(gyro.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(gyro.max_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Min Value:    ")); Serial.print(gyro.min_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Resolution:   ")); Serial.print(gyro.resolution); Serial.println(F(" rad/s"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(temp.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(temp.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(temp.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(temp.max_value); Serial.println(F(" C"));
  Serial.print  (F("Min Value:    ")); Serial.print(temp.min_value); Serial.println(F(" C"));
  Serial.print  (F("Resolution:   ")); Serial.print(temp.resolution); Serial.println(F(" C"));
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  delay(500);
}

/**************************************************************************/
/*
    Configures the gain and integration time for the TSL2561
*/
/**************************************************************************/
void configureSensor(void)  
{
  // 1.) Set the accelerometer range
  lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_2G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_4G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_6G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_8G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_16G);

  // 2.) Set the magnetometer sensitivity
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_2GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_4GAUSS);
  lsm.setupMag(lsm.LSM9DS0_MAGGAIN_8GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_12GAUSS);

  // 3.) Setup the gyroscope
  lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_245DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_500DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_2000DPS);
}

Here's a screenshot of the serial monitor output:

Serial Monitor Output for the 9-DSO Sensor on the Adafruit Feather

Once I had this data, I could start to interface with MIDI to send note and other data to my computer.

This is where things started to fall apart. I'm not sure what exactly went wrong, but nothing sounded the way I intended. I tried to code a note threshold for a particular range of values of the magnetometer. The idea was that as the sensor got closer to a magnet that was fixed to my desk, it would raise or lower the note being played by my music software. It worked...sort of, but not at the level of detail that I imagined. I looked around for ways to come up with the correct combination of notes to data mapping, but I never really got anything that satisfied me.

After a few hours of working on the code, I gave up and put the project away. As a matter of fact, I'm writing this many months after I stopped working on it. I was so frustrated by my failure to get it working to match my imagination that I didn't bother documenting my efforts.

Update: November 19, 2017 - I decided to complete this project and get it up and running. After examining the code, I realized that I was calling the note on and note off MIDI messages in the wrong way. After a bit of twiddling with my timing, I was able to get it to work (mostly). It still stops sending the messages after a few seconds, but I was able to get it to run long enough to create a short video of the results.

#include <frequencyToNote.h>  
#include <MIDIUSB.h>
#include <pitchToFrequency.h>
#include <pitchToNote.h>

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM9DS0.h>

// Simple tutorial on how to receive and send MIDI messages.
// Here, when receiving any message on channel 4, the Arduino
// will blink a led and play back a note for 1 second.

/* Assign a unique base ID for this sensor */   
Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(1000);  // Use I2C, ID #1000

static const unsigned ledPin = 13;      // LED pin on Arduino Uno

void setup()  
{
    pinMode(ledPin, OUTPUT);

    lsm.begin();

    /* Display some basic information on this sensor */
    displaySensorDetails();

    /* Setup the sensor gain and integration time */
    configureSensor();

    Serial.println(F("Found LSM9DS0 9DOF"));
}

void loop()  
{
    performSensorSweep();
}

void performSensorSweep()  
{
  /* Get a new sensor event */ 
  sensors_event_t accel, mag, gyro, temp;

  lsm.getEvent(&accel, &mag, &gyro, &temp);

  // print out accelerometer data
  Serial.print("Accel X: "); Serial.print(accel.acceleration.x); Serial.print(" ");
  Serial.print("  \tY: "); Serial.print(accel.acceleration.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(accel.acceleration.z);     Serial.println("  \tm/s^2");

  // send a MIDI note...
  int zAccelNote = map(gyro.gyro.z, -128, 80, 20, 127);
  int xAccelVelocity = map(gyro.gyro.x, -144, 144, 64, 127);

  // print out magnetometer data
  Serial.print("Magn. X: "); Serial.print(mag.magnetic.x); Serial.print(" ");

  // send a MIDI note...
  int zNote = map(mag.magnetic.z, -3.03, 0.28, 20, 100);
  //int xVelocity = map(mag.magnetic.x, -6.0, 6.0, 64, 127);
  int xVelocity = 127;

  digitalWrite(ledPin, HIGH);
  noteOn(3, zNote, xVelocity);
  yield;
  digitalWrite(ledPin, LOW);

  Serial.print("  \tY: "); Serial.print(mag.magnetic.y);       Serial.print(" ");
  Serial.print("  \tZ: "); Serial.print(mag.magnetic.z);     Serial.println("  \tgauss");

  Serial.println("**********************\n");

// set acceleration
//int accelDelay = map(accel.acceleration.x, 
 delay(5);
}

// First parameter is the event type (0x09 = note on, 0x08 = note off).
// Second parameter is note-on/note-off, combined with the channel.
// Channel can be anything between 0-15. Typically reported to the user as 1-16.
// Third parameter is the note number (48 = middle C).
// Fourth parameter is the velocity (64 = normal, 127 = fastest).

void noteOn(byte channel, byte pitch, byte velocity) {  
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
}

void noteOff(byte channel, byte pitch, byte velocity) {  
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
}

/**************************************************************************/
/*
    Displays some basic information on this sensor from the unified
    sensor API sensor_t type (see Adafruit_Sensor for more information)
*/
/**************************************************************************/
void displaySensorDetails(void)  
{
  sensor_t accel, mag, gyro, temp;

  lsm.getSensor(&accel, &mag, &gyro, &temp);

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(accel.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(accel.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(accel.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(accel.max_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Min Value:    ")); Serial.print(accel.min_value); Serial.println(F(" m/s^2"));
  Serial.print  (F("Resolution:   ")); Serial.print(accel.resolution); Serial.println(F(" m/s^2"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(mag.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(mag.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(mag.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(mag.max_value); Serial.println(F(" uT"));
  Serial.print  (F("Min Value:    ")); Serial.print(mag.min_value); Serial.println(F(" uT"));
  Serial.print  (F("Resolution:   ")); Serial.print(mag.resolution); Serial.println(F(" uT"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(gyro.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(gyro.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(gyro.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(gyro.max_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Min Value:    ")); Serial.print(gyro.min_value); Serial.println(F(" rad/s"));
  Serial.print  (F("Resolution:   ")); Serial.print(gyro.resolution); Serial.println(F(" rad/s"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  Serial.println(F("------------------------------------"));
  Serial.print  (F("Sensor:       ")); Serial.println(temp.name);
  Serial.print  (F("Driver Ver:   ")); Serial.println(temp.version);
  Serial.print  (F("Unique ID:    ")); Serial.println(temp.sensor_id);
  Serial.print  (F("Max Value:    ")); Serial.print(temp.max_value); Serial.println(F(" C"));
  Serial.print  (F("Min Value:    ")); Serial.print(temp.min_value); Serial.println(F(" C"));
  Serial.print  (F("Resolution:   ")); Serial.print(temp.resolution); Serial.println(F(" C"));  
  Serial.println(F("------------------------------------"));
  Serial.println(F(""));

  delay(500);
}

// First parameter is the event type (0x0B = control change).
// Second parameter is the event type, combined with the channel.
// Third parameter is the control number number (0-119).
// Fourth parameter is the control value (0-127).

void controlChange(byte channel, byte control, byte value) {  
  midiEventPacket_t event = {0x0B, 0xB0 | channel, control, value};
  MidiUSB.sendMIDI(event);
}

/**************************************************************************/
/*
    Configures the gain and integration time for the TSL2561
*/
/**************************************************************************/
void configureSensor(void)  
{
  // 1.) Set the accelerometer range
  lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_2G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_4G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_6G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_8G);
  //lsm.setupAccel(lsm.LSM9DS0_ACCELRANGE_16G);

  // 2.) Set the magnetometer sensitivity
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_2GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_4GAUSS);
  lsm.setupMag(lsm.LSM9DS0_MAGGAIN_8GAUSS);
  //lsm.setupMag(lsm.LSM9DS0_MAGGAIN_12GAUSS);

  // 3.) Setup the gyroscope
  lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_245DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_500DPS);
  //lsm.setupGyro(lsm.LSM9DS0_GYROSCALE_2000DPS);
}

Overall, this was a fun project, and I enjoyed getting to hear the sounds it created.


Armchair Engineering - Ignorance is bliss

Written by Michael Earls
 FPGA  electronics

I'm totally into my FPGA. Embedded Micro is supposed to be announcing a new FPGA based on the new Artix 7 FPGA chip that will allow me to run the new Vivado design suite from Xilinx. I'm currently using the very outdated ISE tool (it's like using Microsoft Front Page to do modern web development). ISE works (with modifications to the binaries to make it work on Windows 10), but I'd like to try out some new (to me) stuff like System Verilog (which is unsupported in ISE).

I love the idea of an FPGA and I'm about to dip my feet into writing my own CPU based on the Basic CPU example project on Embedded Micro's website. I'd like to expand the instruction set to larger than 4-bits. It's probably going to be completely bizarre to anyone who actually really knows what they're doing (real-life engineers), but I don't care, I'm doing it for fun.

I thought about having a register that would write its data directly to a MIDI interface so I could have instructions that would send MIDI signals. I'd also like to figure out the best way to interface with the CPU so I can load programs into it without uploading a new bin file each time. The example requires me to use a custom-built Assembler to make the Lucid code required for running my programs on the CPU. It would be cool to have a loader on the PC that could upload the program to the Mojo using the USB connection and the on-board microprocessor.

Update: November 21, 2017 - It seems that what I'm looking for is already out there. I was already aware of the OpenCores website, but I also found the PicoBlaze and MicroBlaze microcontroller cores from Xilinx for the FPGA. I'm going to start working with the PicoBlaze and see what I can do to extend it to interact with external peripherals. That's going to save me a lot of time trying to figure out how to extend the CPU sample from Embedded Micro.

I also thought about adding a 12-bit VGA connector and seeing if I could learn how to interface with it using a custom CPU. The main issue I'm running into is I am quickly hitting my wall of interest. The best way to describe it is that if the concepts are too deep (i.e. - they require an advanced degree of some sort to make sense of them), then I tend to veer away from the topic altogether and move on to something a bit more amateur. However, I keep getting drawn back to CPU design, even though it's a very complicated topic.

I've been watching Ben Eater's awesome series about his breadboard computer on YouTube and learning a lot about how computers work at the most basic level (logic gates, timer-driven clocks, and data buses).

Another topic I've been exploring is how to build a "pocket computer" or similar homebrew computer based on the Zilog Z-80 processor from the 80's (70's?). I have a big interest in this particular processor because it's relatively simple as far as CPU's go.

I'd like to take all of this knowledge and apply it to my custom CPU project. I think it would be cool to come up with an amalgamation of Ben Eater's breadboard computer and the Mojo CPU such that I can control and interact with the FPGA CPU using buttons, switches, 7-segment displays, and possibly even LCD displays.

It has taken me over a year to get comfortable enough with electronics to feel somewhat confident in my abilities to pull some of this off. It's a slow road because it is only a hobby, after all, and my day job takes priority. If there's a topic that I need to study at home to help me on the job, I tend to learn those things first, then spend my extra time learning the electronics and FPGA knowledge.

When I first got into this, it was so that I could play with making my own music "synthesizers" using CMOS logic chips, but that rapidly advanced to the idea of "chips on a chip" that the FPGA represents. I can never just do anything the easy way. I like to take my problems and abstract them away until they are almost unrecognizable, then find the solution that covers the most ground. It serves me well at work when solving large-scale enterprise computing problems, but it makes hobbies a bit more complicated than they should be.

Ultimately, I see myself doing a lot more playing around with the FPGA in the future and less time with actual silicon chips and discreet components. I might make plug-in boards for my FPGA to accomplish some task like making a VGA interface, but even that may not be necessary when Embedded Micro releases their HDMI shield for the Mojo.

When it comes down to it, I think that the fact that I don't really know what I'm doing when it comes to FPGA coding is a good thing. It's that kind of spirit that allowed the microprocessor and home computer movement to sprout from the garages and basements of amateurs back when room-sized computers ruled the computing world and IBM thought the home computer was a toy and (luckily) ignored it. I'm not going to cause any real damage and I might actually stumble upon something fun along the way. Sure, I'm doing this the hard way, but I think it's too late for me to go to a university and get a degree in electrical or electronics engineering. Besides, I think this is a lot more fun...