Skip to contentSkip to author details

programming

A 29-post collection

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}
};

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.


ASP.NET Core, JWT Tokens, and the User.Identity.Name property - A Discovery

Written by Michael Earls
 ASP.NET  programming  .NET Core

I've been working on creating a token-based auth system and I wanted to write about a discovery that I made. I've been following the excellent ASP.NET Core Token Authentication Guide.

I was able to get everything up and running as suggested in the guide, but when I accessed the User.Identity.Name property, it was null. I was hoping to find the Name of the user there, but it wasn't. After some exploration, I was able to determine the solution. You simply add the following code in Startup.cs. I added this to the TokenValidationParameters area as outlined in the Guide.

var tokenValidationParameters = new TokenValidationParameters
{
    // Ensure that User.Identity.Name is set correctly after login
    NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",

    ... Existing code here ...

};

If you want to use a different claim as your User.Identity.Name, then use that claim name instead of the XmlSoap schema above. We're actually not using username, we're using an Id number that identifies the user.


Who am I, really?

Written by Michael Earls
 michael  programming

Due to various circumstances, I recently found myself in the job market again. There was a particular job that emerged that I got very excited about. They cold contacted me through LinkedIn, not knowing that I was even looking for a job.

I had to drive two hours for the first interview, but the job was for working remotely with a little travel. Once in the interview, I noticed that they were asking questions based off of my LinkedIn profile, not off of the resume that I had so carefully crafted to be my own representation of myself.

Honestly, I'm was glad they were using LinkedIn and not my resume because I hate resumes. After working in this industry for over twenty years, the resume that I'm most comfortable with has grown to over six pages long. It's my understanding that it's not supposed to exceed two pages.

So, when I became aware that I was going to have to sell myself again, I created a new resume that was going to be my short version. I even named it "Michael Earls resume 2 page.docx". It's terrible. I still have not found the best way to squeeze over twenty years of knowledge, experience, and wisdom onto just two pages.

During the interview, they asked some good questions about my experience and asked about the projects I had worked on. Remember, this is a job that I really wanted to get as it had to do with industrial automation and that was a field I have been interested in for a very long time.

I finished the interview and felt pretty good about it. Days went by and I never heard anything back. So, I sent an email asking if they had any news. They told me that they were still making a decision.

After a few weeks, I finally just gave up all hope and continued with the other job prospects as normal. Then, out of the blue, I got a call from them asking me to drive up for another interview, this time with the president of the company and other executives. I agreed.

This time, however, I was asked to perform one of those in-depth personality profiles that companies pay for. They were serious and I knew it. I get nervous taking these personality profiles because they make me feel like I'm being analyzed (because I am).

When I got to the interview, it was intense. They were armed with 10 reports about my personality and asked me very pointed questions about who I was and how I would respond to certain scenarios.

During both interviews, I specifically remember telling them that I viewed the work that I do as a creative endeavor. I told them that I see programming as an art form and the act of writing code is like painting a picture.

When asked what my greatest gift was, I said "I make order out of chaos".

Utter bullshit.

I never heard back from them after the second interview, even when I sent an email asking if they had made a decision because I had received another job offer. Personally, I think it was unprofessional to not even tell me that I had not been selected, but each company handles things in their own way.

Going back through the interview, I can remember times when they kept trying to get me back on track. All I had to do was confirm to them that I was the same person from my LinkedIn profile. Nowhere in my career have I ever "painted a picture with code".

I am a software engineer, not an artist. But why did I say those things? What led me to spout off so much untruth?

After some self reflection, I finally realized why I had told them that and why I have had that view of myself since the beginning.

It started in 7th grade.

When I was growing up, I used to draw a lot. I even got in trouble in school for drawing during class to the point where most of my artwork ended up in the trash can (the evil teachers tore it up before placing it there).

I was a huge Iron Maiden fan in Junior High. I loved heavy metal music and I used to draw demons, skeletons, skulls, flames, you get the idea. Iron Maiden's "mascot" is a skeleton with rotting flesh falling from his bones.

I used to emulate that style in my drawings and I drew a lot of it. I also drew post-apocalyptic scenes (much like you see now in The Walking Dead TV show).

One day, there was an announcement that students were encouraged to join the art club. Just show up to the art room after school with some of your drawings to get in. When I knocked on the door, the teacher opened the door, looked at my drawings and said "this is Satanic, you can't be in the art club".

I was deflated. I don't think I've drawn another picture since that day. Yet, somehow, I never stopped thinking of myself as an artist. I originally wanted to go to art school and be a 3-D animator, but that was too expensive and very unrealistic coming from the financial demographic that I come from.

I've somehow been lying to myself all these years that I am an artist, yet nothing that I do is art. It's all engineering and science.

I can imagine that I must come across as a dishonest person when I discuss how I view myself because I have been so clueless for so long.

Now I know who I am, I'm a software engineer and I write code for a living. I don't "paint with code". I solve problems. I am a problem solver and I'm a damn good one.


Pong on the Mojo Revisited

Written by Michael Earls
 FPGA  electronics  programming

In my previous post, I briefly discussed the implementation of a pseudo-pong game on my Mojo FPGA developer board.

I was able to implement the VHDL version that I linked to later in the post and it works much better.

The biggest challenge I had with it was converting the two-button input to a potentiometer input. It turns out all I had to do was convert an array to an integer as input to the paddle's left position.

Here is a link to the article describing the implementation that I used:

FPGA Pong From FPGACenter.com.

The VHDL was a big challenge for me because I had to learn it as I went along. It works just like Verilog, but the syntax is so different.

Also, I modified my User Constraints File to make the RGB an array for compatibility with this particular implementation.

Here are the new custom constraints:

NET "hsync" LOC = P50 | IOSTANDARD = LVTTL;  
NET "vsync" LOC = P51 | IOSTANDARD = LVTTL;  
NET "rgb<2>" LOC = P41 | IOSTANDARD = LVTTL;  
NET "rgb<1>" LOC = P40 | IOSTANDARD = LVTTL;  
NET "rgb<0>" LOC = P35 | IOSTANDARD = LVTTL;

Here is the code containing my changes (in the vga_control and img_gen modules):

vga_control:

library IEEE;  
use IEEE.STD_LOGIC_1164.ALL;  
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity vga_control is  
     Port ( clk : in STD_LOGIC;
                start : in STD_LOGIC;
                reset : in STD_LOGIC;
                paddle : IN STD_LOGIC_VECTOR(9 downto 0);
                rgb : out STD_LOGIC_VECTOR (2 downto 0);
                h_s : out STD_LOGIC;
                v_s : out STD_LOGIC);
end vga_control;

architecture Behavioral of vga_control is

COMPONENT img_gen  
       PORT( clk : IN std_logic;
                     x_control : IN std_logic_vector(9 downto 0);
                     paddle : IN std_logic_vector(9 downto 0);
                     y_control : IN std_logic_vector(9 downto 0);
                     video_on : IN std_logic; 
                     rgb : OUT std_logic_vector(2 downto 0) );
END COMPONENT;

COMPONENT sync_mod  
       PORT( clk : IN std_logic;
                     reset : IN std_logic;
                     start : IN std_logic; 
                     y_control : OUT std_logic_vector(9 downto 0);
                     x_control : OUT std_logic_vector(9 downto 0);
                     h_s : OUT std_logic;
                     v_s : OUT std_logic;
                     video_on : OUT std_logic );
END COMPONENT;

signal x,y:std_logic_vector(9 downto 0);  
signal video:std_logic;

begin  
          U1: img_gen PORT MAP( clk =>clk , x_control => x, paddle => paddle , 
                                                         y_control => y, video_on =>video , rgb => rgb );

           U2: sync_mod PORT MAP( clk => clk, reset => reset, start => start, y_control => y, x_control =>x ,
                                                             h_s => h_s , v_s => v_s, video_on =>video );
end Behavioral;

My changes are on lines 9, 20, and 41. This sends the output from the potentiometer hooked up to the ADC on the Mojo to the img_gen module where it's converted to position data for the paddle control.

Here's the modified img_gen code:

library IEEE;  
use IEEE.STD_LOGIC_1164.ALL;  
use IEEE.STD_LOGIC_ARITH.ALL;  
use IEEE.STD_LOGIC_UNSIGNED.ALL;


entity img_gen is  
     Port ( clk : in STD_LOGIC;
                x_control : in STD_LOGIC_VECTOR(9 downto 0);
                paddle : in STD_LOGIC_VECTOR(9 downto 0);
                y_control : in STD_LOGIC_VECTOR(9 downto 0);
                video_on : in STD_LOGIC;
                rgb : out STD_LOGIC_VECTOR(2 downto 0));
end img_gen;

architecture Behavioral of img_gen is

--wall
constant wall_l:integer :=10;--the distance between wall and left side of screen  
constant wall_t:integer :=10;--the distance between wall and top side of screen  
constant wall_k:integer :=10;--wall thickness  
signal wall_on:std_logic;  
signal rgb_wall:std_logic_vector(2 downto 0); 

--bar
signal bar_l, bar_l_next: integer:=100;  
constant bar_t:integer :=420;--the distance between bar and top side of screen  
constant bar_k:integer :=10;--bar thickness  
constant bar_w:integer:=120;--bar width  
constant bar_v:integer:=10;--velocity of the bar  
signal bar_on:std_logic;  
signal rgb_bar:std_logic_vector(2 downto 0); 

--ball
signal ball_l,ball_l_next:integer :=100;--the distance between ball and left side of screen  
signal ball_t,ball_t_next:integer :=100; --the distance between ball and top side of screen  
constant ball_w:integer :=20;--ball Height  
constant ball_u:integer :=20;--ball width  
constant x_v,y_v:integer:=3;-- horizontal and vertical speeds of the ball  
signal ball_on:std_logic;  
signal rgb_ball:std_logic_vector(2 downto 0); 

--refreshing(1/60)
signal refresh_reg,refresh_next:integer;  
constant refresh_constant:integer:=830000;  
signal refresh_tick:std_logic;

--ball animation
signal xv_reg,xv_next:integer:=3;--variable of the horizontal speed  
signal yv_reg,yv_next:integer:=3;--variable of the vertical speed

--x,y pixel cursor
signal x,y:integer range 0 to 650;

--mux
signal vdbt:std_logic_vector(3 downto 0);

--buffer
signal rgb_reg,rgb_next:std_logic_vector(2 downto 0);

begin

--x,y pixel cursor
x <= conv_integer(x_control);  
y <= conv_integer(y_control );

--refreshing
process(clk)  
begin  
     if clk'event and clk='1' then
          refresh_reg<=refresh_next; 
     end if;
end process;  
refresh_next <= 0 when refresh_reg= refresh_constant else  
refresh_reg+1;  
refresh_tick <= '1' when refresh_reg = 0 else  
                           '0';
--register part
process(clk)  
begin  
     if clk'event and clk='1' then
         ball_l <= ball_l_next;
         ball_t <= ball_t_next;
         xv_reg <= xv_next;
         yv_reg <= yv_next;
         bar_l <= bar_l_next;
      end if;
end process;

--bar animation
process(refresh_tick,paddle)  
begin

    if refresh_tick= '1' then
       bar_l_next <= conv_integer(paddle);
    end if;
end process;

--ball animation
process(refresh_tick,ball_l,ball_t,xv_reg,yv_reg)  
begin  
     ball_l_next <=ball_l;
     ball_t_next <=ball_t;
     xv_next <= xv_reg;
     yv_next <= yv_reg;
     if refresh_tick = '1' then
        if ball_t > 400 and ball_l > (bar_l -ball_u) and ball_l < (bar_l +120) then --the ball hits the bar
           yv_next <= -y_v ;
       elsif ball_t < 35 then--The ball hits the wall
           yv_next <= y_v;
       end if;
       if ball_l < 10 then --The ball hits the left side of the screen
          xv_next <= x_v;
       elsif ball_l> 600 then 
          xv_next <= -x_v ; --The ball hits the right side of the screen
       end if; 
       ball_l_next <= ball_l + xv_reg;
       ball_t_next <= ball_t + yv_reg; 
    end if;
end process;

--wall object
wall_on <= '1' when x > wall_l and x < (640-wall_l) and y> wall_t and y < (wall_t+ wall_k) else  
                      '0'; 
rgb_wall <= "000";--Black  
--bar object
bar_on <= '1' when x > bar_l and x < (bar_l+bar_w) and y> bar_t and y < (bar_t+ bar_k) else  
                    '0'; 
rgb_bar <= "001";--blue

--ball object
ball_on <= '1' when x > ball_l and x < (ball_l+ball_u) and y> ball_t and y < (ball_t+ ball_w) else  
                     '0'; 
rgb_ball <= "010"; --Green

--buffer
process(clk)  
begin  
     if clk'event and clk = '1' then
         rgb_reg <= rgb_next;
     end if;
end process;

--mux
vdbt<=video_on & wall_on & bar_on &ball_on;  
with vdbt select  
     rgb_next <= "100" when "1000",--Background of the screen is red 
     rgb_wall when "1100",
     rgb_wall when "1101",
     rgb_bar when "1010",
     rgb_bar when "1011",
     rgb_ball when "1001",
      "000" when others;
--output
rgb <= rgb_reg;

end Behavioral;

My changes are on lines 10 and 95 to make the potentiometer control the paddle.


Creating PONG on the Mojo FPGA

Written by Michael Earls
 FPGA  electronics  programming

I recently found an old VGA monitor on the side of the road and it inspired me to see if I could get my Mojo FPGA board to output a VGA signal. I took the monitor apart and wired its VGA cable directly to my Mojo.

I read up on the VGA specification and learned that getting it to work was as easy as adding resistors to the Red, Green, and Blue outputs.

VGA Connector

I decided to try my hand at implementing the PONG game that is outlined on fpga4fun.com.

There were some differences between that FPGA that they were using and my Mojo board, so I had to go through some trial-and-error.

For instance, the Verilog code that they have on the site depends on a 25MHz clock and my Mojo uses a 50MHz clock. I tried various ways of dividing the clock without success.

I finally found a great example using a 50MHz clock, but it wasn't quite right because it used 8-bits to encode the RGB values, 3 bits for red and green, and 2 bits for blue. I couldn't get this to work, so I found the code that it was based on and found 1 bit per color implementation. I was finally able to get a signal.

The rest of the time was spent implementing the Pong code from the original article. However, the article used a mouse input. I wanted to use a regular potentiometer (dial) to make it more like the "good old days". So, I wired up a pot to the Mojo and altered the Verilog to use it.

Honestly, my code turned out to be spaghetti code because I haven't quite learned how to modularize Verilog to my satisfaction.

I was able to get the paddle working and responding to movement of the potentiometer, but for some reason, the code for the ball just didn't work in my implementation. I'm guessing it has to do with the timing. I still haven't been able to get the ball working, but I was pretty happy that the paddle works.

The Mojo wired to the VGA cable and the paddle The Mojo wired to the VGA cable and the paddle "controller"

The pong paddle The paddle "controller" - a simple 10k potentiometer

Here is the Verilog definition for the Pong module:

`timescale 1ns / 1ps  
module pong(clk, red, green, blue, hsync, vsync, pot_sample);

// Input clk, 50 MHz Oscillator
input clk;  
input [9:0]pot_sample;

// VGA outputs
output red;  
output green;  
output blue;  
output hsync;  
output vsync;     

reg [9:0] hcount;     // VGA horizontal counter  
reg [9:0] vcount;     // VGA vertical counter  
reg [2:0] data;          // RGB data

wire hcount_ov;  
wire vcount_ov;  
wire inDisplayArea;  
wire hsync;  
wire vsync;  
reg  vga_clk;

// VGA mode parameters
parameter hsync_end   = 10'd95,  
   hdat_begin  = 10'd143,
   hdat_end  = 10'd783,
   hpixel_end  = 10'd799,
   vsync_end  = 10'd1,
   vdat_begin  = 10'd34,
   vdat_end  = 10'd514,
   vline_end  = 10'd524;


always @(posedge clk)  
begin  
 vga_clk = ~vga_clk;
end

always @(posedge vga_clk)  
begin  
 if (hcount_ov)
  hcount <= 10'd0;
 else
  hcount <= hcount + 10'd1;
end

assign hcount_ov = (hcount == hpixel_end);

always @(posedge vga_clk)  
begin  
 if (hcount_ov)
 begin
  if (vcount_ov)
   vcount <= 10'd0;
  else
   vcount <= vcount + 10'd1;
 end
end  
assign  vcount_ov = (vcount == vline_end);

////////////////////////////////////////////////////////////////
// Pong
////////////////////////////////////////////////////////////////
// Handle paddle position
reg [9:0] PaddlePosition;

always @(posedge vga_clk) begin

        PaddlePosition <= hdat_begin + pot_sample;

    if(0)
    begin
      if(0)
      begin
         if(~&PaddlePosition)        // make sure the value doesn't overflow
            PaddlePosition <= PaddlePosition + 1;
      end 
      else
      begin
         if(|PaddlePosition)        // make sure the value doesn't underflow
            PaddlePosition <= PaddlePosition - 1;
      end
    end
end

wire paddle = (hcount >= PaddlePosition + 8) && (hcount <= PaddlePosition+120) && ((vcount <= vdat_end - 15) && (vcount > vdat_end - 25));

// Draw a border around the screen
wire border = ((hcount >=  hdat_begin) && (hcount <  hdat_begin + 10)) || ((hcount >  hdat_end - 10) && (hcount <=  hdat_end))  
    || ((vcount >= vdat_begin) && (vcount < vdat_begin + 10)) || ((vcount > vdat_end - 10) && (vcount <= vdat_end));

//////////////////////////////////////////////////////////////////
reg [9:0] ballX;  
reg [9:0] ballY;  
reg ball_inX, ball_inY;

always @(posedge vga_clk)  
if(ball_inX==0) ball_inX <= ((hcount>ballX) && (hcount<=ballX+16)) & ball_inY; else ball_inX <= 0;//!(hcount==ballX+16);

always @(posedge vga_clk)  
if(ball_inY==0) ball_inY <= ((vcount>ballY) && (vcount<=ballY+16)); else ball_inY <= 0;//!(vcount==ballY+16);

wire ball = ball_inX & ball_inY;

/////////////////////////////////////////////////////////////////
wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself

reg ResetCollision;  
always @(posedge vga_clk) ResetCollision <= (vcount==0) & (hcount==0);  // active only once for every video frame

reg CollisionX1, CollisionX2, CollisionY1, CollisionY2;  
always @(posedge vga_clk) if(ResetCollision) CollisionX1<=0; else if(BouncingObject & (hcount==ballX   ) & (vcount==ballY+ 8)) CollisionX1<=1;  
always @(posedge vga_clk) if(ResetCollision) CollisionX2<=0; else if(BouncingObject & (hcount==ballX+16) & (vcount==ballY+ 8)) CollisionX2<=1;  
always @(posedge vga_clk) if(ResetCollision) CollisionY1<=0; else if(BouncingObject & (hcount==ballX+ 8) & (vcount==ballY   )) CollisionY1<=1;  
always @(posedge vga_clk) if(ResetCollision) CollisionY2<=0; else if(BouncingObject & (hcount==ballX+ 8) & (vcount==ballY+16)) CollisionY2<=1;

/////////////////////////////////////////////////////////////////
wire UpdateBallPosition = ResetCollision;  // update the ball position at the same time that we reset the collision detectors

reg ball_dirX, ball_dirY;  
always @(posedge vga_clk)  
if(UpdateBallPosition)  
begin  
    if(~(CollisionX1 & CollisionX2))        // if collision on both X-sides, don't move in the X direction
    begin
        ballX <= ballX + (ball_dirX ? -1 : 1);
        if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0;
    end

    if(~(CollisionY1 & CollisionY2))        // if collision on both Y-sides, don't move in the Y direction
    begin
        ballY <= ballY + (ball_dirY ? -1 : 1);
        if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0;
    end
end 

/////////////////////////////////////////////////////////////////

// display area calculation
assign inDisplayArea = ((hcount >= hdat_begin) && (hcount < hdat_end))  
     && ((vcount >= vdat_begin) && (vcount < vdat_end));

assign hsync = (hcount > hsync_end);  
assign vsync = (vcount > vsync_end);

reg vga_R, vga_G, vga_B;

always @(posedge vga_clk)  
begin  
  vga_R <= BouncingObject | ball;
  vga_G <= BouncingObject | ball;
  vga_B <= BouncingObject | ball;
end

assign red = (inDisplayArea) ?  vga_R : 0;  
assign green = (inDisplayArea) ?  vga_G : 0;  
assign blue = (inDisplayArea) ?  vga_B : 0;      


// generate "image"
always @(posedge vga_clk)  
begin  
  data <= (vcount[2:0] ^ hcount[2:0]); 
end

endmodule

Line #6 is the direct input of the potentiometer from the Mojo's on-board Analog to Digital Converter (ADC). Here is the top file showing how it's all wired together:

module mojo_top(  
    // 50MHz clock input
     input clk,
    // Input from reset button (active low)
    input rst_n,
    // cclk input from AVR, high when AVR is ready
    input cclk,
    // Outputs to the 8 onboard LEDs
    output[7:0]led,
    // AVR SPI connections
    output spi_miso,
    input spi_ss,
    input spi_mosi,
    input spi_sck,
    // AVR ADC channel select
    output [3:0] spi_channel,
    // Serial connections
    input avr_tx, // AVR Tx => FPGA Rx
    output avr_rx, // AVR Rx => FPGA Tx
    input avr_rx_busy, // AVR Rx buffer full
     output  red,
    output  green,
    output  blue,
    output hsync, vsync
    );

wire rst = ~rst_n; // make reset active high

// these signals should be high-z when not used
assign spi_miso = 1'bz;  
assign avr_rx = 1'bz;  
assign spi_channel = 4'bzzzz;

assign led = 8'b0;

wire [3:0] channel;  
wire new_sample;  
wire [9:0] sample;  
wire [3:0] sample_channel;  
wire [9:0] pot_sample;

avr_interface avr_interface (  
 .clk(clk),
 .rst(rst),
 .cclk(cclk),
 .spi_miso(spi_miso),
 .spi_mosi(spi_mosi),
 .spi_sck(spi_sck),
 .spi_ss(spi_ss),
 .spi_channel(spi_channel),
 .tx(avr_rx),
 .rx(avr_tx),
 .channel(channel),
 .new_sample(new_sample),
 .sample(sample),
 .sample_channel(sample_channel),
 .tx_data(8'h00),
 .new_tx_data(1'b0),
 .tx_busy(),
 .tx_block(avr_rx_busy),
 .rx_data(),
 .new_rx_data()
);

 input_capture input_capture (
 .clk(clk),
 .rst(rst),
 .channel(channel),
 .new_sample(new_sample),
 .sample(sample),
 .sample_channel(sample_channel),
 .sample_out(pot_sample)
);

pong pongBoard(  
    .clk(clk), 
    .red(red),
    .green(green),
    .blue(blue),
    .hsync(hsync),
    .vsync(vsync),
    .pot_sample(pot_sample)
);

endmodule

And, finally, here are the custom user constraints that I used to wire the output pins up to the VGA connector:

NET "hsync" LOC = P50 | IOSTANDARD = LVTTL;  
NET "vsync" LOC = P51 | IOSTANDARD = LVTTL;  
NET "red" LOC = P41 | IOSTANDARD = LVTTL;  
NET "green" LOC = P40 | IOSTANDARD = LVTTL;  
NET "blue" LOC = P35 | IOSTANDARD = LVTTL;

I'd really like to get the ball working with this. My scoring will be different than traditional Pong as I am not yet ready to tackle the "AI" of the computer player. I'd like the player to score 1 point each time the ball hits the paddle and lose a point each time the ball hits the bottom wall. If the ball hits the bottom wall while the score is 0, then the game is over. I'll have to add in a reset button, but that shouldn't be much trouble.

I just found another implementation of Pong in VHDL that I may try later. It divides the 50MHz clock into a 25MHz clock using a mod 2 counter. I discuss my implementation of that code in another post.

I haven't written any VHDL, yet, so I'm still trying to wrap my head around it. It was hard enough switching from Mojo's Lucid language to Verilog as Lucid made things a bit easier. However, I felt that since there were a lot more people out there using Verilog that I would be better off learning that. Now I'm learning that there are even more people using VHDL.

Lucid, Verilog, and VHDL are a far cry from C#, C, and JavaScript; three languages I'm very familiar with. This has been a very beneficial lesson in parallel "programming".


Moon Phase Calculator for the Pebble Watch (C Source)

Written by Michael Earls
 programming  development

A few years ago, I bought a Pebble smartwatch. Mostly I bought it because it was cheap, but I also wanted to write my own apps for it. I wrote a few apps.

Recently, Pebble was bought by FitBit and ceased production of the Pebble smartwatch. However, they are making great progress to ensuring that the watch continues to be viable into the future.

Anyway, as part of the transition, I wanted to release my source code for my moon phase app. What sets this moon phase calculator apart from my first implementation is the ability for it to run when not connected to the phone. I implemented it as a C app.

The moon phase calculation itself was originally written in JavaScript and I had to convert it to C. Sadly, I can't remember where I got the source, so I can't give attribution.

The code is available on my GitHub Page:

pebble-moon-phase on GitHub

Here is the actual calculation for anyone searching for a moon phase calculator in C:

#include "math.h"

#define        PI  3.1415926535897932384626433832795
#define        RAD (PI/180.0)
#define         SMALL_FLOAT    (1e-12)

static double Julian(int year,int month,double day)  
{
    /*
      Returns the number of julian days for the specified day.
      */

    int a,b=0,c,e;
    if (month < 3) {
    year--;
    month += 12;
    }
    if (year > 1582 || (year  1582 && month>10) ||
    (year  1582 && month==10 && day > 15)) {
    a=year/100;
    b=2-a+a/4;
    }
    c = 365.25*year;
    e = 30.6001*(month+1);
    return b+c+e+day+1720994.5;
}

static double sun_position(double j)  
{
    double n,x,e,l,dl,v;
    //double m2;
    int i;

    n=360/365.2422*j;
    i=n/360;
    n=n-i*360.0;
    x=n-3.762863;
    if (x<0) x += 360;
    x *= RAD;
    e=x;
    do {
    dl=e-.016718*sin(e)-x;
    e=e-dl/(1-.016718*cos(e));
    } while (fabs(dl)>=SMALL_FLOAT);
    v=360/PI*atan(1.01686011182*tan(e/2));
    l=v+282.596403;
    i=l/360;
    l=l-i*360.0;
    return l;
}

static double moon_position(double j, double ls)  
{

    double ms,l,mm,n,ev,sms,ae,ec;//,z,x;//,lm,bm,ec;
    //double d;
    //double ds, as, dm;
    int i;

    /* ls = sun_position(j) */
    ms = 0.985647332099*j - 3.762863;
    if (ms < 0) ms += 360.0;
    l = 13.176396*j + 64.975464;
    i = l/360;
    l = l - i*360.0;
    if (l < 0) l += 360.0;
    mm = l-0.1114041*j-349.383063;
    i = mm/360;
    mm -= i*360.0;
    n = 151.950429 - 0.0529539*j;
    i = n/360;
    n -= i*360.0;
    ev = 1.2739*sin((2*(l-ls)-mm)*RAD);
    sms = sin(ms*RAD);
    ae = 0.1858*sms;
    mm += ev-ae- 0.37*sms;
    ec = 6.2886*sin(mm*RAD);
    l += ev+ec-ae+ 0.214*sin(2*mm*RAD);
    l= 0.6583*sin(2*(l-ls)*RAD)+l;
    return l;
}

static double moon_phase2(int year,int month,int day, double hour, int* ip)  
{
    /*
      Calculates more accurately than Moon_phase , the phase of the moon at
      the given epoch.
      returns the moon phase as a real number (0-1)
      */

    double j= Julian(year,month,(double)day+hour/24.0)-2444238.5;
    double ls = sun_position(j);
    double lm = moon_position(j, ls);

    double t = lm - ls;
    if (t < 0) t += 360;
    *ip = (int)((t + 22.5)/45) & 0x7;
    return (1.0 - cos((lm - ls)*RAD))/2;
}