Skip to contentSkip to author details

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...

C# Introduction to Expression Trees

Written by Jeff Tyler

Recently I was in a situation where I needed to write my own expression trees to be consumed by Entity Framework. I thought that it would be a good opportunity to pass what I learned along.

Along the way I came up with three realizations that helped me understand what I was doing.

When dealing with Expression Trees you are dynamically building lamda expressions

The power of an Expression Tree is not in it's ability to be used in C# code but in it's ability to write code for another system.

The questions that you are asking when dealing with expression trees are different.

When you write person.Name == "Jeff" you asking if the two are equals. With a lamda your asking what the result of a method is.
With expression trees you are asking how do I build this Lamda in code. So you have to create the parameters and comparison steps separately.

When you consume an expression. You are less concerned with the result of the expression and more concerned with the steps required to get to the end result.

So an explanation of what an expression tree is. An expression tree is used so that you can interpret the code and generate something else. Entity Framework takes an expression tree and uses it to build SQL.
So here is how I finally get my head wrapped around them.

    var person = new Person(){ Name = "Jeff"};
    var isJeff = person.Name == "Jeff";

This is regular code. It is compiled down and when it gets to this line it creates a new person and then sets isJeff to true. This can be used in if statements to change the flow.

var person = new Person { Name = "Jeff" };  
            Func<Person, bool> isJeff = (p) => p.Name == "Jeff";
            var list = new List<Person> { person };
            var jeffs = list.Where(isJeff);
           // calling invoke makes it behave like a regular method.
           var result = isJeff.Invoke(person);

This is a lamda. It creates a function delegate that can be used on any Person to see if they are a Jeff. This can be passed along to LINQ to be used as a filter of a list. This code behaves like a method when it is invoked.

var person = new Person { Name = "Jeff" };  
var param = Expression.Parameter(typeof(Person));  
var property = Expression.Property(param,nameof(Person.Name));  
var constant = Expression.Constant("Jeff");  
var equals = Expression.Equal(property, constant);  

The equals expression gives us three important pieces of information. By knowing it's type we know how to compare it. It is an Equal Expression so we know that it is the same as doing an ==. With equals.Left we can get the property access from the param. It will read param_0.Name.
With equals.right we can see the constant that we added. Which will be "Jeff".

Let's say that we knew an identical object existed in a JavaScript application. we could take this and write something like this

Console.WriteLine(equals.Left + "===" + equals.Right);  

This code would wind up looking like this
param0.Name === "Jeff";
if you happened to have a param
0 variable this would validate it. Not very useful at the moment but it does demonstrate one important thing.

Were going to take it one more step and see if we can't get a more useful condition check.

// this code will only handle a single condition. 
public static string CreateJavaScriptMethod(Expression<Func<Person, bool>> expression)  
        {
            var jsCode = $"function validation({expression.Parameters[0]}) {{";
            var body = expression.Body as BinaryExpression;
            var comparer = "";
            switch (body.NodeType)
            {
                case ExpressionType.Equal:
                    {
                        comparer = "===";
                        break;
                    }
                case ExpressionType.NotEqual:
                    {
                        comparer = "!==";
                        break;
                    }
                case ExpressionType.GreaterThan:
                    {
                        comparer = ">";
                        break;
                    }
                case ExpressionType.LessThan:
                    {
                        comparer = "<";
                        break;
                    }
            }
            jsCode += $"return {body.Left} {comparer} {body.Right}";

            jsCode += "}";

            return jsCode;
        }

This is a method created to handle a simple validation scenario. You write some validation in C# and it generates a javascript method for validation.
The method takes Expression function.
expression.paramters holds all of the paramters for the function. expression.Body is a binary expression. It has a Left, a Right, and it's type will tell you how it is comparing the two values. The switch statement checks the expression type and creates the correct JavaScript equality check.

So now we are going to use the method.
This is the normal use of it.

var script = CreateJavaScriptMethod(p => p.Name == "Jeff");  

This is just a plain old lamda expression that does the same comparison as the one we built. Like you would use for LINQ or Enity Framework. Only this one will give you a JavaScript method.

We can make it a bit more dynamic by building the expression using the expression trees.

Expression<Func<Person, bool>> expression = Expression.Lambda<Func<Person, bool>>(equals, param);

CreateJavaScriptMethod(expression);  

After calling Expression.Lamda on the expression that we created in the beginning it then can be used exactly like the lamda expression.

Emulating a Lunetta circuit with an FPGA is a flawed idea

Written by Michael Earls
 synthesizers  Lunetta  electronics

Last night, I decided to explore an idea that I have been formulating over the past several months, but haven't actually taken the time to explore. I had a thought that I could emulate a Lunetta circuit (a CMOS 40106 integrated circuit with a potentiometer and a capacitor to create an oscillator) using my FPGA and Verilog.

After all, the 40106 is simply a hex inverter (6 inverter circuits on one chip), so the code was this simple:

assign pin1 = ~pin0;

That simply set the value of pin 1 to be the opposite of pin 2 (inverted signal) at every clock cycle. I plugged in the capacitor and potentiometer according to the regular "Lunetta" arrangement (as seen in the schematic below), but it didn't work.

Lunetta circuit

Replace the U1A IC in the schematic with the FPGA and you'll understand how I set it up.

I think I have a fundamental misunderstanding of the nature of CMOS and FPGA's. While you can emulate the functionality of an inverter chip using an FPGA, I don't think it's possible to emulate the physics of the chip itself. The Lunettas work off of the actual physical reaction and timings of the chip itself, not off of a clock cycle such as the one that the FPGA is running under. Emulating a physical feedback circuit will not render the same results when using an FPGA. I looked at the datasheet for the 40106 and saw that it has ~140ns delay. I'm not sure if I can emulate that kind of timing.

I enjoyed the few hours I spent hooking the circuit up and playing around, though, and it got me into Pulse Width Modulation and other audio generation techniques for FPGAs. I'll be exploring more about audio processing in the future, but I've put away the idea of emulating discreet components for their "out of band" uses.

It's fun to play around with this stuff, but when the theory gets too thick, I lose interest and move onto more playful aspects of the technology. I think it's why I have a cursory understanding of a lot of technology, but only a deep understanding of a limited number of topics (like C#, HTML, and other Web Development).

Welcome our newest author on cerkit.com - Jeff Tyler

Written by Michael Earls

We have a new author on the Cerebral Kitchen - Jeff Tyler. Jeff is a professional software developer and will be contributing to the site with more of the same topics you're already used to.

Jeff's first two articles have been posted:

Creating a calendar control with Aurelia part 1

Creating a calendar control with Aurelia part 2

Please welcome Jeff to the site. I look forward to his contributions.

I will still be writing for the site, so look for more from me, as well.

Have a great day.

Michael Earls