Coding for the Raspberry Pi Pico on Fedora or a Raspberry Pi

Back in January of this year (2021), the Raspberry Pi Foundation came out with their first microcontroller board calling it the Raspberry Pi Pico.

Raspberry Pi Pico Pinout Diagram

Raspberry Pi Pico Pinout Diagram

The microcontroller chip (RP2040) used on the Pico was designed by the Raspberry Pi foundation and features a dual-core Arm Cortex-M0+ processor with 264KB internal RAM, support for up to 16MB of off-chip Flash and a wide range of flexible I/O options which includes I2C, SPI, and Programmable I/O (PIO). The PIO is something unique in the microcontroller world. The RP2040 was also made available to other microcontroller board manufacturers so we are now seeing boards from Adafruit, Arduino, Sparkfun and others. It will be interesting to see how this chip is used in the future, especially in regards to the programmable I/O (PIO).

Coding (i.e. setting up a development environment) for the Raspberry Pi Pico on a Raspberry Pi is made easy by all the great documentation that is available from the Raspberry Pi Foundation.

Although, the documentation is designed specifically for using a Raspberry Pi and the Rasbian OS, you might find other similar Linux distros mentioned in the documentation (like Debian).  There is a pico-setup script provided but it is only useful on Rasbian.

Programmers have their choice of Python (using MicroPython or CircuitPython), C/C++ (using the SDK) or native assembler (using pioasm). If you are developing in C/C++,  I highly recommend the Pico Project Generator tool for creating new projects.

I happen to like and use the Fedora Linux distro (Fedora 34 at the time of this post). Fedora is a lot different than Raspbian and  some of the software dependencies needed for pico development and debugging are not documented. Actually, some of the pico tools do not compile correctly on Fedora without some modifications. Fedora tends to be on the leading edge and has more recent software development ‘toolchain‘ packages.

There were two specific things I found that did not work on Fedora without modification.  OpenOCD and PicoTool (both documented in the Getting Started Guide) compiled and worked OK on Raspbian but did not compile under Fedora.

To resolve these issues and make something to easily install ‘everything’, I  created a Python script (and other shell based scripts) to download and install the software development kit (SDK),  tools,  documentation and software dependencies for either Fedora or Raspian.  All of these scripts work on either OS.  When installing on Fedora, the scripts make the necessary modifications to compile OpenOCD and PicoTool correctly.

Download the pico-setup.zip file and unzip it to a directory of your choice. Something like:

mkdir /opt/pico
cd /opt/pico
wget https://microcontrollerelectronics.com/sourcecode/pico-setup.zip
unzip pico-setup.zip

The python script (pico-setup.py) and all of the provided shell scripts are  designed to run either on a RedHat (Fedora) system or a Raspberry Pi with Raspbian. The scripts (if necessary) determine which OS is being used at runtime.

Pico-setup.py accepts from one to three parameters (Toolchain, Install, Remove).
The ‘toolchain‘ parameter installs (via APT or DNF depending on the OS) the necessary prerequisite packages for these scripts to work on the underlying OS.  The ‘install’ parameter runs all the provided shells scripts. However, the  shell scripts can be run independently and thus only the ones specifically desired (although some depend on the SDK being installed first and they also depend on the OS toolchain dependencies being installed). The provided README.txt  file provides more documentation on its usage.

 

Connecting a serial port to the WEB via Node.js

A previous post (here) documented how to connect a Python script to a serial port on a microcontroller.  This post documents connecting a serial port to the WEB via Node.js (which is a JavaScript runtime).

Using Node.js and a module for node called serialport, it is very easy to connect a PC to a serial port on a microcontroller.   First install Node.js  (and npm which is the node package manager). Then install the serial port package via:

npm install serialport

NODE.JS to Console Javascript code

Save the following code as serial_port_console.js:

/*
 Node Process for Serial Connection to an Arduino
 Data  Sent and Received is displayed on the console
 From: earl@microcontrollerelectronics.com
*/

const SerialPort = require('serialport')
var stdin        = process.openStdin();
const port       = new SerialPort('/dev/ttyUSB0', {  baudRate: 115200 })

port.on('error', function(err)  { console.log('Error: ', err.message)} )
port.on('data', function (data) { console.log('', data.toString('utf8')) })

stdin.addListener("data", function(data) {
  console.log(data.toString().trim());
  port.write(data);
});

and run it via:

node serial_port_console.js

This is a very simple means to communicate with your serial device just from the console. Data received from the serial device is sent to the console and data typed in on the console is sent to the serial device. Note that the serial port on the PC is hard coded as “/dev/ttyUSB0” and the baud rate is 115200 which should be changed by editing the code to match the connected device.

NODE.JS to WEB Javascript code

Alternatively, use this script for connecting a serial port to the WEB via Node.js:

/*
 Node.js Serial Port Data to Web
 From: earl@microcontrollerelectronics.com
*/

var serport      = "";
var rate         = 115200;
var serports     = [];
var fs           = require('fs');
const SerialPort = require('serialport');

var express = require('express'),
    app    = express(),
    server = require('http').Server(app),
    io     = require('socket.io')(server),
    port   = 8888;

server.listen(port, () => console.log('Server Listening on port' + port))

//app.use(express.static(__dirname));

app.get('*', function(req, res){
  fs.readFile(__dirname + '/index.html','utf8', function (err, data) {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }
    res.writeHead(200, {'Content-Type' : "text/html; charset=utf-8"});
    var result = data.replace("Node Serial Connection","Node Serial Connection " + serports[0]);
    res.end(result);
  });
});

io.on('connection', onConnection);

var connectedSocket = null;
function onConnection(socket) {  
  connectedSocket = socket;
  connectedSocket.on('send', function (data) {
    console.log(data);
    serport.write(data.Data);
  });
 }

if (process.argv.length > 2) {
  console.log(process.argv);
  serports.push(process.argv[2]);
  if (process.argv.length > 3) rate = parseInt(process.argv[3]);
}

SerialPort.list().then(ports => {
  ports.forEach(function(port) {
    if (typeof port['manufacturer'] !== 'undefined') {
      serports.push(port.path);
      console.log(port);
    }
  });
  if (serports.length == 0) {
    console.log("No serial ports found!");
    process.exit();
  }
  serport = new SerialPort(serports[0], {  baudRate: rate })
  serport.on('error', function(err) {
    console.log('Error: ', err.message)
  })
  serport.on('data', function (data) {
    console.log(data.toString('utf8'));
    io.emit('data', { data: data.toString('utf8') });
  })

});

That script also requires an html file to send to the browser:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Node Serial Connection</title>
<script src="/socket.io/socket.io.js"></script>
</head>
<body>
<style>
#text {
  font-family: Arial, Verdana, Helvetica, sans-serif;
  font-size: 14px;
  border: 2px solid blue;
  text-transform: none;
  overflow: auto;
  text-align: left;
  margin-top:4px;
  margin:6px;
  width:97vw;
  border: 2px solid blue;
  height:75vh;
}
.myButton {
        -moz-box-shadow:inset 0px 1px 0px 0px #97c4fe;
        -webkit-box-shadow:inset 0px 1px 0px 0px #97c4fe;
        box-shadow:inset 0px 1px 0px 0px #97c4fe;
        background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #3d94f6), color-stop(1, #1e62d0));
        background:-moz-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
        background:-webkit-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
        background:-o-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
        background:-ms-linear-gradient(top, #3d94f6 5%, #1e62d0 100%);
        background:linear-gradient(to bottom, #3d94f6 5%, #1e62d0 100%);
        filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#3d94f6', endColorstr='#1e62d0',GradientType=0);
        background-color:#3d94f6;
        -moz-border-radius:6px;
        -webkit-border-radius:6px;
        border-radius:6px;
        border:1px solid #337fed;
        display:inline-block;
        cursor:pointer;
        color:#ffffff;
        font-family:Arial;
        font-size:15px;
        font-weight:bold;
        padding:6px 24px;
        text-decoration:none;
        text-shadow:0px 1px 0px #1570cd;
        margin-left:15px;
}
.myButton:hover {
        background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #1e62d0), color-stop(1, #3d94f6));
        background:-moz-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
        background:-webkit-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
        background:-o-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
        background:-ms-linear-gradient(top, #1e62d0 5%, #3d94f6 100%);
        background:linear-gradient(to bottom, #1e62d0 5%, #3d94f6 100%);
        filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#1e62d0', endColorstr='#3d94f6',GradientType=0);
        background-color:#1e62d0;
}
.myButton:active {
        position:relative;
        top:1px;
}
</style>
<div id=text></div>
<div id=tosend>
 <form id=sendform name=sendform>
 <textarea style="margin-top:4px; margin:6px; border:2px solid blue; width:97vw; height:10vh; font-size:20px;"
  id=msg name=msg 
 placeholder="Messages appear in the box above. Type in your message here then click 'Send' or press Enter."></textarea>
 <br /> 
 <input class=myButton type=button name=send  value="Send"  onclick='send_msg("");' />
</form>
</div>
<script>
function send_msg(data) {
  var txt = document.getElementById("msg").value;
  txt = txt + data;
  socket.emit('send', { Data: txt} );
  document.getElementById("msg").value = "";
  document.sendform.msg.focus();
}
function zeroFill(i) { return (i < 10 ? '0' : '') + i }
function now() {
  var d = new Date()
  return d.getFullYear() + '-'
    + zeroFill((d.getMonth() + 1)) + '-'
    + zeroFill(d.getDate())        + ' '
    + zeroFill(d.getHours())       + ':'
    + zeroFill(d.getMinutes())     + ':'
    + zeroFill(d.getSeconds())
}
var text   = document.getElementById('text');
var socket = io.connect('http://localhost:8888');
socket.on('data', function(data) {
//console.log(JSON.stringify(data));
  var msgs  = document.getElementById("text");
  var txt   = msgs.innerHTML;
  var rep   = data.data.replace(/\x0D\x0A/g,"<br />");
  txt       = txt + "<br>" + now() + " " + rep;
  msgs.innerHTML = txt;
  msgs.scrollTop = msgs.scrollHeight;
 });

document.getElementById('msg').addEventListener('keypress', function(event) {
  if (event.keyCode == 13) {  send_msg("\n");  event.preventDefault(); }
});
document.sendform.msg.focus();
</script>
</body>
</html>

Save the Node.Js file as serial_port_web.js and the  html  file as index.html. This script also requires three other npm modules (fs for reading files,  express for the WEB interface and socket.io to maintain a WEB socket connection for the serial data flow).

Install them with:  npm install socket.io fs express

Make sure the microcontroller serial device is connected to the PC, then run the script via:

node serial_port_web.js

The serial_port_web.js script will search for the serial port and use what is found  at a baud rate of 115200 unless it is over ridden with optional parameters on the command line:

node serial_port_web.js  /dev/ttyUSB0 9600

The port is hard coded to 8888 but of course can be changed by editing the code.

When the script is running ,  copy/paste this URL in the browser:

http://localhost:8888

and this screen should appear:

Serial Port to WEB via Node.JsThe large top blue box is where the serial data will be displayed (and a time stamp added). The bottom blue box is where data can be typed to send to the serial device. Note that when Enter is pressed, Enter (or as it is called the Carriage Return or Newline character) is sent to the serial device.  Pressing the Send button (sends the typed in characters without sending Enter.) Depending on what (code) is running on the serial device, sometimes the Enter is needed and sometimes not.

There is a lot more code in the WEB example, than in the console example, however, the WEB example has more features and presents the data in a ‘prettier’ way. Using the WEB example also allows a remote connection to the serial device if the host computer is connected to the internet.

An STM32F103C8T6 based MIDI Controller for MIDI2LR (Updated)

In a previous post (here) ,  a project was introduced for those of you who use Adobe Lightroom. There is a way to use a MIDI hardware ‘box’ to control various Lightroom actions. I am told that once you use the hardware controls you will never want to go back to the software ones.  A Lightroom plugin called MIDI2LR  interfaces various MIDI ‘boxes’ to  Lightroom. The MIDI box sends MIDI Control/Change commands to MIDI2LR which then changes them into Lightroom actions. More info on MID2LR can be found HERE.

This post continues the project by adding multiple rotary encoders (in addition to the buttons). Here is how things are wired (download the Fritzing.org file for this graphic here):

Here is how it  actually looks all wired together:

In this project (so far) there are two STM32F103C8 microcontrollers. One contains the MIDI control code and interfaces via USB to the PC. It also controls the OLED screen and buttons.  Another one controls the five rotary encoders. A previous post (here) documents the STM32F103C8T6 and interfacing with rotary encoders.

Also shown in the picture above are how the STM32F103C8 microcontrollers are programmed. The one on the left has an FTDI serial-to-usb connector and the one on the right has an ST-Link programmer connected.

The two STM32F103C8 microcontrollers are connected together via I2C (two wires).  To add more rotary encoders, just add another STM32F103C8T6 and up to  five encoders.

In the above picture, at the top left, is a simple I2C circuit which was introduced in a previous post here. Using that circuit, multiple groups of rotary encoders can be added (each additional STM32F103C8T6 can control up to 5 more encoders).

Here is the code (using the Arduino IDE) to load into the STM32F103C8T6 controlling the USB connection, buttons and OLED:

// From: earl@microcontrollerelectronics.com
// STM32F103C8T6  MIDI Controller
// for the MIDI2LR ADOBE LIGHTROOM PLUGIN

/* 
 *            +-----------------[USB]-----------------+
  [SS2|PB12] | [31]                            [Gnd] |
 [SCK2|PB13] | [30]                  +---+     [Gnd] |
[MISO2|PB14] | [29]    +-----+       |0 0|     [3V3] |
[MOSI2|PB15] | [28]    |Reset|       |x x|   [Reset] | 
       [PA8] | [27]    +-----+       |1 1|      [ 0] | [PB11|SDA2|RX3] 
   [TX1|PA9] | [26]                  +---+      [ 1] | [PB10|SCL2|TX3]
  [RX1|PA10] | [25]                   ^ ^       [33] | [PB1]
 [USB-|PA11] | [24]            Boot1--+ |       [ 3] | [PB0|A0]
 [USB+|PA12] | [23]            Boot0----+       [ 4] | [PA7|A1|MOSI1]
      [PA15] | [20]                             [ 5] | [PA6|A2|MISO1]
       [PB3] | [19]     +---------------+       [ 6] | [PA5|A3|SCK1]
       [PB4] | [18]     | STM32F103C8T6 |       [ 7] | [PA4|A4|SS1]
       [PB5] | [17]     |   Blue Pill   |       [ 8] | [PA3|A5|RX2]
  [SCL1|PB6] | [16]     +---------------+       [ 9] | [PA2|A6|TX2]
  [SDA1|PB7] | [15]                             [10] | [PA1|A7]
       [PB8] | [32]                             [11] | [PA0|A8]
       [PB9] | [PB9]                            [12] | [PC15]
             | [5V]      +---------------+      [13] | [PC14]
             | [Gnd]     |    ST-Link    |      [14] | [PC13|LED]
             | [3V3]     |3V3 DIO CLK GND|     [Vbat]| 
             +-------------+---+---+---+-------------+
                           |   |   |   |
*/

#include <USBComposite.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306_STM32.h>
#include <Wire_slave.h>

#define OLED_RESET PB4
Adafruit_SSD1306 display(OLED_RESET);

USBMIDI midi;
#define LED PC13
#define KeyDelay 200
#define maxChannel 5

const char ManufacturerName[] = "Generic Chinese BluePill";
const char DeviceName[]       = "STM32F103C8T6 Midi Device";
const char DeviceSerial[]     = "00000000000000000001";
const int  ProductId          = 0x0031;
const int  VendorId           = 0x1EAF;

//Button Pins
const uint8   pins[]    = { PA0,PA1,PA2,PA3,PA4,PA5,PA6,PA7};

//Midi Notes for each button
const uint8_t notes[]   = { 0, 1, 2, 3, 4, 5, 6, 7};

//Midi Notes for each Rotary Encoder
const uint8_t renotes[] = { 8, 9, 10, 11, 12 };

//Mide Notes for each Rotary Encoder Button Push
const uint8_t brenotes[] = { 13,14,15,16,17 };

//Number of Rotary Encoders
#define NUMRE (sizeof(renotes) / sizeof(renotes[0]))

#define Menu        PB8
#define Menu_Select PB9
#define I2C_Address 9

const char *menu_items[] = {"Channel","Midi","Reset"};
const int numMenuItems   = 3;

char textline[80];
char rcvline[80];

int channel     = 0;
int midicmd     = 0;
int menuitem    = -1;
int rcvdata     = 0;
String wiredata = "";

unsigned long timer;
const long interval = 5000;
unsigned long currentmillis;

void setup() {
  Wire1.begin(I2C_Address);
  Wire1.onReceive(receiveEvent);
  USBComposite.setManufacturerString(ManufacturerName);
  USBComposite.setProductString(DeviceName);
  USBComposite.setSerialString(DeviceSerial);
  USBComposite.setVendorId(VendorId);
  USBComposite.setProductId(ProductId);

  midi.begin();
  while (!USBComposite);
  for(int i=0;i<8;++i) pinMode(pins[i],INPUT_PULLUP);
  pinMode(Menu,INPUT_PULLUP);
  pinMode(Menu_Select,INPUT_PULLUP);
  pinMode(LED,OUTPUT);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  timer = millis();
  disptext(0,0,2," MIDI2LR\n\nController\n  Ready!");
}

void disptext(int row, int col, int size, char *text) {
  int i = 0;
  display.clearDisplay();
  display.setTextSize(size);
  display.setTextColor(WHITE);
  display.setCursor(row,col);
  while(text[i] != '\0') {
   if (text[i] == '\n') display.println();
   else display.write(text[i]);
   ++i;
  }
  display.display();
  timer = millis();
}

void loop() {
  currentmillis = millis();

  if ( (currentmillis - timer) > 10000) {
    timer = currentmillis;
    display.clearDisplay();
    display.display();
  }

  if (rcvdata) { 
    wiredata = "";
    wiredata += rcvline[0];
    wiredata += rcvline[1];
    int  i = wiredata.toInt();
    i -= 1;
    char f = rcvline[3];
    
    if ( (i >= 0) && (i <= NUMRE) ) {
      if (f == 'B') {
        sprintf(textline,"%s\nChannel %d\nMidi %s\n%d|%s:%d",
          rcvline,channel,(midicmd ? "CC" : "Note"),channel,(midicmd ? "CC" : "Note"),brenotes[i]
        );
        disptext(0,0,2,textline);
        if (midicmd == 0) {
          midi.sendControlChange(channel, brenotes[i], 127);
          delay(KeyDelay);
          midi.sendControlChange(channel, brenotes[i], 0);
        }
        else {
          midi.sendNoteOn(channel, brenotes[i], 127);
          delay(KeyDelay);
          midi.sendNoteOff(channel, brenotes[i],0);
        }
      }
      else {
        if (f == '+') {
          sprintf(textline,"%s\nChannel %d\nMidi %s\n%d|%s:%d",
            rcvline,channel,(midicmd ? "CC" : "Note"),channel,(midicmd ? "CC" : "Note"),renotes[i]
          );
          disptext(0,0,2,textline);
          midi.sendControlChange(channel, renotes[i], 1);
          delay(KeyDelay);
          midi.sendControlChange(channel, renotes[i], 0);
        }
        else {
          sprintf(textline,"%s\nChannel %d\nMidi %s\n%d|%s:%d",
            rcvline,channel,(midicmd ? "CC" : "Note"),channel,(midicmd ? "CC" : "Note"),renotes[i]
          );
          disptext(0,0,2,textline);
          midi.sendControlChange(channel, renotes[i], 127);
          delay(KeyDelay);
          midi.sendControlChange(channel, renotes[i], 0);
        }
      }
    }
    rcvdata = 0;
  }
  
  if (digitalRead(Menu) == LOW) {
    while(digitalRead(Menu) == LOW) delay(50);
    ++menuitem;
    if (menuitem >= numMenuItems) menuitem = 0;
    sprintf(textline,"Menu Item\n%s",menu_items[menuitem]);
    disptext(0,0,2,textline);
  }

  if (digitalRead(Menu_Select) == LOW) {
    if (menuitem == 0) {
      channel += 1; 
      if (channel >= maxChannel) channel = 0;     
      sprintf(textline,"Select\n%s\nMidi %s\nChannel %d",menu_items[menuitem],(midicmd ? "CC" : "Note"),channel);
    }
    if (menuitem == 1) {
      if (midicmd == 0) midicmd = 1;
      else              midicmd = 0;
      sprintf(textline,"Select\n%s\nMidi %s\nChannel %d",menu_items[menuitem],(midicmd ? "CC" : "Note"),channel);
    }
    if (menuitem == 2) {
      midicmd = 0;
      channel = 0;
      sprintf(textline,"Menu\nReset\nMidi %s\nChannel %d",(midicmd ? "CC" : "Note"),channel);
    }
    while(digitalRead(Menu_Select) == LOW) delay(50);   
    disptext(0,0,2,textline);
  }

  for(int i=0;i<8;++i) {
    if (digitalRead(pins[i]) == LOW) {
      digitalWrite(LED,!digitalRead(LED));
      sprintf(textline,
        "Button %d\nChannel %d\nMidi %s\n%d|%s:%d",
        pins[i],channel,(midicmd ? "CC" : "Note"),channel,(midicmd ? "CC" : "Note"),notes[i]
      );
      disptext(0,0,2,textline);
      if (midicmd == 0) {
         midi.sendControlChange(channel, notes[i], 127);
         delay(KeyDelay);
         midi.sendControlChange(channel, notes[i], 0);
      }
      else {
        midi.sendNoteOn(channel, notes[i], 127);
        delay(KeyDelay);
        midi.sendNoteOff(channel, notes[i],0);
      }
      while(digitalRead(pins[i]) == LOW) delay(50);   
    }
  }
} 

void receiveEvent(int howMany) {
  int c = 0;
  digitalWrite(PC13,!digitalRead(PC13));
  while(Wire1.available()) { rcvline[c] = char(Wire1.read()); ++c; }
  rcvline[c] = '\0';
  rcvdata = 1;
}

Note: The above code is an enhanced version from the previous post (here) were this project was only using buttons.

Here is the code to load into the STM32F103C8T6 which controls the rotary encoders:

//From: earl@microcontrollerelectronics.com
/*
             +-----------------[USB]-----------------+
  [SS2|PB12] | [31]                            [Gnd] |
 [SCK2|PB13] | [30]                  +---+     [Gnd] |
[MISO2|PB14] | [29]    +-----+       |0 0|     [3V3] |
[MOSI2|PB15] | [28]    |Reset|       |x x|   [Reset] | 
       [PA8] | [27]    +-----+       |1 1|      [ 0] | [PB11|SDA2|RX3] 
   [TX1|PA9] | [26]                  +---+      [ 1] | [PB10|SCL2|TX3]
  [RX1|PA10] | [25]                   ^ ^       [33] | [PB1]
 [USB-|PA11] | [24]            Boot1--+ |       [ 3] | [PB0|A0]
 [USB+|PA12] | [23]            Boot0----+       [ 4] | [PA7|A1|MOSI1]
      [PA15] | [20]                             [ 5] | [PA6|A2|MISO1]
       [PB3] | [19]        +-----------+        [ 6] | [PA5|A3|SCK1]
       [PB4] | [18]        | STM32F103 |        [ 7] | [PA4|A4|SS1]
       [PB5] | [17]        | Blue Pill |        [ 8] | [PA3|A5|RX2]
  [SCL1|PB6] | [16]        +-----------+        [ 9] | [PA2|A6|TX2]
  [SDA1|PB7] | [15]                             [10] | [PA1|A7]
       [PB8] | [32]                             [11] | [PA0|A8]
       [PB9] | [PB9]                            [12] | [PC15]
             | [5V]      +---------------+      [13] | [PC14]
             | [Gnd]     |    ST-Link    |      [14] | [PC13|LED]
             | [3V3]     |3V3 DIO CLK GND|     [Vbat]| 
             +-------------+---+---+---+-------------+
                           |   |   |   |

PA9  TX / PA10 RX Serial Upload Serial
PA2  TX / PA3  RX Serial1
PB10 TX / PB11 RX Serial2

*/

#include <Wire.h>
#define I2C_ADDR  9

// Pins are wired CLK,DT,SW,CLK,DT,SW ..

const uint8_t pins[] = {
  PB12,PB13,PB14,
  PA2,PA1,PA0,
  PA5,PA4,PA3,
  PB0,PA7,PA6,
  PB11,PB10,PB1
};

#define NUMPINS (sizeof(pins) / sizeof(pins[0]))
#define NUMRE (NUMPINS / 3)

int state[]     = {0,0,0,0,0};
int laststate[] = {0,0,0,0,0};
int counter[]   = {0,0,0,0,0};
char buf[80];

void setup() { 
  delay(1000);
  Wire.begin();
//  Serial.begin (115200);
  for(int i=0,j=0,k=1,b=2;i<NUMRE;++i,j+=3,k+=3,b+=3) {
    pinMode(pins[j],INPUT);
    pinMode(pins[k],INPUT);
    pinMode(pins[b],INPUT_PULLUP);
  }
  for(int i=0,j=0;i<NUMRE;++i,j+=3) laststate[i] = digitalRead(pins[j]);
} 

void loop() { 
  for(int i=0,j=0,k=1,b=2;i<NUMRE;++i,j+=3,k+=3,b+=3) {
    state[i] = digitalRead(pins[j]);
    if (laststate[i] != state[i]) {     
      if (digitalRead(pins[k]) != state[i]) {
        ++counter[i];
        sprintf(buf,"%02d:+ %d",i+1,counter[i]);
      }
      else {
        --counter[i];
        sprintf(buf,"%02d:- %d",i+1,counter[i]);
      }
//    Serial.println(buf);
      Wire.beginTransmission(I2C_ADDR);
      Wire.write(buf);  
      Wire.endTransmission(); 
    } 
    laststate[i] = state[i];
    if (digitalRead(pins[b]) == LOW) {
       sprintf(buf,"%02d:B",i+1);
//     Serial.println(buf);
       Wire.beginTransmission(I2C_ADDR);
       Wire.write(buf);  
       Wire.endTransmission(); 
       while(digitalRead(pins[b]) == LOW);
       delay(50);
    }
  }
}

Note: those two sketches will need to be updated if more encoders are added.

Once the code is sent/loaded into the two microcontrollers, just plug in the USB-micro cable (shown in the picture on the right) to your Windows 10 PC and it will be added as an audio device (no drivers are needed as it is automatically recognized). Install the MIDI2LR plugin and while in Lightroom you can test out the button and rotary encoder functions and customize the functionality.

Next up for this project is a custom case,  cleaning up the wiring and adding more encoders! Stay tuned!

Multiple Rotary Encoders on an STM32F103C8T6

Rotary encoders are useful for many things.  Here is what they look like (with and without the button):

Rotary EncoderRotary Encoder with Button

They have 5 connections (Power, Ground, Data, Clock and Switch). The button can be turned (continuously in any direction) and it can also be pushed down .

 

 

 

 

The switch connection monitors the button push whereas the data and clock connections are used to monitor the direction and turn of the button.  There are many tutorials on how these ‘encoders’ work, for more information check this link.

Multiple encoders are needed for the MIDI2LR  project that was posted here. In order to test multiple encoders , this sketch called ‘Multiple Rotary Encoders on an STM32F103C8T6’ was created:

// From: earl@microcontrollerelectronics.com
// STM32F103C8T6 with 5 Rotary Encoders
/*
             +-----------------[USB]-----------------+
  [SS2|PB12] | [31]                            [Gnd] |
 [SCK2|PB13] | [30]                  +---+     [Gnd] |
[MISO2|PB14] | [29]    +-----+       |0 0|     [3V3] |
[MOSI2|PB15] | [28]    |Reset|       |x x|   [Reset] | 
       [PA8] | [27]    +-----+       |1 1|      [ 0] | [PB11|SDA2|RX3] 
   [TX1|PA9] | [26]                  +---+      [ 1] | [PB10|SCL2|TX3]
  [RX1|PA10] | [25]                   ^ ^       [33] | [PB1]
 [USB-|PA11] | [24]            Boot1--+ |       [ 3] | [PB0|A0]
 [USB+|PA12] | [23]            Boot0----+       [ 4] | [PA7|A1|MOSI1]
      [PA15] | [20]                             [ 5] | [PA6|A2|MISO1]
       [PB3] | [19]        +-----------+        [ 6] | [PA5|A3|SCK1]
       [PB4] | [18]        | STM32F103 |        [ 7] | [PA4|A4|SS1]
       [PB5] | [17]        | Blue Pill |        [ 8] | [PA3|A5|RX2]
  [SCL1|PB6] | [16]        +-----------+        [ 9] | [PA2|A6|TX2]
  [SDA1|PB7] | [15]                             [10] | [PA1|A7]
       [PB8] | [32]                             [11] | [PA0|A8]
       [PB9] | [PB9]                            [12] | [PC15]
             | [5V]      +---------------+      [13] | [PC14]
             | [Gnd]     |    ST-Link    |      [14] | [PC13|LED]
             | [3V3]     |3V3 DIO CLK GND|     [Vbat]| 
             +-------------+---+---+---+-------------+
                           |   |   |   |

PA9  TX / PA10 RX Serial Upload Serial
PA2  TX / PA3  RX Serial1
PB10 TX / PB11 RX Serial2

*/

// The Rotary Encoder Pins are wired CLK,DT,SW,CLK,DT,SW ..

const uint8_t pins[] = { PB11,PB10,PB1,PB0,PA7,PA6,PA5,PA4,PA3,PA2,PA1,PA0,PB12,PB13,PB14 };
#define NUMPINS (sizeof(pins) / sizeof(pins[0]))

// Number of Rotary Encoders
#define NUMRE (NUMPINS / 3)

int state[]     = {0,0,0,0,0};
int laststate[] = {0,0,0,0,0};
int counter[]   = {0,0,0,0,0};
char buf[80];

void setup() { 
  Serial.begin (115200);
  for(int i=0,j=0,k=1,b=2;i<NUMRE;++i,j+=3,k+=3,b+=3) {
    pinMode(pins[j],INPUT);
    pinMode(pins[k],INPUT);
    pinMode(pins[b],INPUT_PULLUP);
  }
  for(int i=0,j=0;i<NUMRE;++i,j+=3) laststate[i] = digitalRead(pins[j]);
} 

void loop() { 
  for(int i=0,j=0,k=1,b=2;i<NUMRE;++i,j+=3,k+=3,b+=3) {
    state[i] = digitalRead(pins[j]);
    if (laststate[i] != state[i]) {     
      if (digitalRead(pins[k]) != state[i]) ++counter[i];
      else                                  --counter[i];
      sprintf(buf,"RE: %d Position: %d",i+1,counter[i]);
      Serial.println(buf);
    } 
    laststate[i] = state[i];
    if (digitalRead(pins[b]) == LOW) {
       sprintf(buf,"RE: %d Button Push",i+1);
       Serial.println(buf);
       while(digitalRead(pins[b]) == LOW);
       delay(50);
    }
  }
}

The pins are set up in an array so that the code can quickly ‘scan’ through them and not miss any state changes.

Notice that the switch (button press) pins are set up as INPUT_PULLUP.  If they go ‘LOW’ then the button was pushed.

Here is what the wiring looks like:

STM32F103C8T6 Multiple Rotary Encoders

Get the fritzing.org code for that wiring graphic here (so it can be modified if needed).

Powering your electronics projects with an ATX power supply

Working with different electronics projects,  and especially microcontrollers , requires different voltages (usually 3.3,  5 and 12 Volts).  It is very fitting that the power supply used in most desktop computers (an ATX power supply) can provide exactly these voltages.

There are a number of  projects  (found via a Google search) that turn an  ATX power supply into a bench power supply.  Usually those projects highly modify the  power supply itself  and don’t break out all of the specific voltages it supplies.

Powering your electronics projects with an ATX power supply is actually pretty easy without modifying anything.  The ‘secret’ is to plug the ATX power supply connector to an ATX breakout board.  There are several types of these (boards) adapters. Pictured here are two  different ones:

PC Bench Power Supply Adaptor

ATX Breakout Board

 

 

 

 

These breakout boards split off ground, 3.3, 5 and 12 volts. They have a power on/off switch that also turns on or off the power supply.  The binding posts (or screw in connectors) can be wired to  terminal blocks which allow more connections if needed.

Terminal Block

To monitor voltage and amps used by your electronics, add a DSN-VC288 vols/amp measuring module or two. One can monitor the 5 volt circuit and one can monitor the 12 volt circuit.  Here is how the DSN-VC288 is wired:

DSN-VC288

Here is what it looks like when all the pieces are wired together:

 

Bench Power SupplyBench Power Supply

STM32F103C8T6 Pin Tester to check your soldering

How good are you at soldering?   Here is the STM32F103C8T6  microcontroller board that I use  and have to solder all of the pins.

STM32F103C8T6 Front View

Sometimes when I solder pins to the  board, the connection looks OK and even tests OK with continuity on a multimeter.  However, the solder connections even though they pass those two tests sometimes don’t fully connect the pin to the board.  More information about common soldering problems can be found  here.

I came up with a way to better test those connections. It is an Arduino sketch that tests all of the pins to make sure they work when connected to an LED.

Here is the STM32F103C8T6 pin tester to check your soldering sketch:

// From earl@microcontrollerelectronics.com
/*
             +-----------------[USB]-----------------+
  [SS2|PB12] | [31]                            [Gnd] |
 [SCK2|PB13] | [30]                  +---+     [Gnd] |
[MISO2|PB14] | [29]    +-----+       |0 0|     [3V3] |
[MOSI2|PB15] | [28]    |Reset|       |x x|   [Reset] | 
       [PA8] | [27]    +-----+       |1 1|      [ 0] | [PB11|SDA2|RX3] 
   [TX1|PA9] | [26]                  +---+      [ 1] | [PB10|SCL2|TX3]
  [RX1|PA10] | [25]                   ^ ^       [33] | [PB1]
 [USB-|PA11] | [24]            Boot1--+ |       [ 3] | [PB0|A0]
 [USB+|PA12] | [23]            Boot0----+       [ 4] | [PA7|A1|MOSI1]
      [PA15] | [20]                             [ 5] | [PA6|A2|MISO1]
       [PB3] | [19]        +-----------+        [ 6] | [PA5|A3|SCK1]
       [PB4] | [18]        | STM32F103 |        [ 7] | [PA4|A4|SS1]
       [PB5] | [17]        | Blue Pill |        [ 8] | [PA3|A5|RX2]
  [SCL1|PB6] | [16]        +-----------+        [ 9] | [PA2|A6|TX2]
  [SDA1|PB7] | [15]                             [10] | [PA1|A7]
       [PB8] | [32]                             [11] | [PA0|A8]
       [PB9] | [PB9]                            [12] | [PC15]
             | [5V]      +---------------+      [13] | [PC14]
             | [Gnd]     |    ST-Link    |      [14] | [PC13|LED]
             | [3V3]     |3V3 DIO CLK GND|     [Vbat]| 
             +-------------+---+---+---+-------------+
                           |   |   |   |

PA9  TX / PA10 RX Serial Upload Serial
PA2  TX / PA3  RX Serial1
PB10 TX / PB11 RX Serial2

*/

const uint8_t pins[] = {
  PB12,PB13,PB14,PB15,PA8,PA9,PA10,PA11,PA12,PA15,PB3,PB4,PB5,PB6,PB7,PB8,PB9,
  PB11,PB10,PB1,PB0,PA7,PA6,PA5,PA4,PA3,PA2,PA1,PA0,PC15,PC14,PC13
};

#define NUMPINS (sizeof(pins) / sizeof(pins[0]))

void setup() { 
  for(int i=0;i<NUMPINS;++i) pinMode(pins[i],OUTPUT);
} 

void loop() { 
  for(int i=0;i<NUMPINS;++i) {
    digitalWrite(pins[i],!digitalRead(pins[i]));
    delay(100);
  }
}

The sketch uses an array called ‘pins’ with a list of all the STM32F103C8T6 pin names. The setup code sets all the pins to output and the main loop just toggles the pins with a 100 millisecond delay. This makes sure that the LED will toggle ON/OFF if the connection is working properly.

Make sure to use the STM32F103C8T6  Arduino Core from here as it supports using all the pins  as output and testing with an LED.

Once you load it into the STM32F103C8T6, then  just make sure it has power and ground and connect an LED to each pin in succession and make sure it lights up. If it doesn’t, then you know it is a bad connection. This is also a way to test any connection wires that need testing. Sometimes they are ‘bad’ too, due to defects in manufacturing or just usage.

STM32F103C8T6 Pin Tester

5 Way Navigation Button connected to an Arduino or STM32F103C8T6

A 5 way navigation button actually has 7 different navigational settings. It has a lot of functionality in a small package.  The ‘button’ on it can be moved/pushed in 4 directions ( labeled up, down, left, right)  or pressed down. There are also two other switched buttons labeled ‘Set’ and ‘Reset’ for a total of 7 functions. Quite handy!

Here is what it looks like:

5 Way Navigation Button

COM is wired to ground. Each of the other navigation pins are wired to a pin on the STM32F103C8T6 or Arduino . Here is what the wiring looks like when it is wired to an STM32F103C8T6.

STM32F103C8T6 with a 5 Way Navigation Button Here is the Arduino IDE code for the STM32F103C8T6. When the button is moved or pressed it will light the LED and display the action on the serial console. Note that the input pin is initially configured as INPUT_PULLUP which makes the pin HIGH. When the button is pushed/moved it will make the  corresponding pin go to ground (LOW).

// 5 Way Navigation Button for the STM32F103C8T6
// From earl@microcontrollerelectronics.com

#define pinLED PC13
int pins[]   = { 1,2,3,4,5,6,7 };  // PA1 - PA7
int numpins  = 7;

void setup() {
 for (int c = 0; c < numpins; c++) {
   pinMode(pins[c], INPUT_PULLUP);
 }
 pinMode(pinLED,OUTPUT);
 Serial.begin(115200);
 digitalWrite(pinLED,HIGH);
}

void flashled() {
 digitalWrite(pinLED,LOW);
 delay(250);
 digitalWrite(pinLED,HIGH);
}

void loop() {
 for (int c = 0; c < numpins; c++) {
  if (digitalRead(pins[c]) == LOW) { 
    while(digitalRead(pins[c]) == LOW);
    if (c == 6) Serial.println("UP");
    if (c == 5) Serial.println("DOWN");
    if (c == 4) Serial.println("LEFT"); 
    if (c == 3) Serial.println("RIGHT");
    if (c == 2) Serial.println("MID");
    if (c == 1) Serial.println("SET");
    if (c == 0) Serial.println("RESET");
    flashled();
  }
 }
}

Here is the code for an Arduino Uno using Digital Pins (2,3,4,5,6,7,8):

// 5 Way Navigation Code for Arduino Uno
// from earl@microcontrollerelectronics.com

int pins[]   = { PD2,PD3,PD4,PD5,PD6,PD7,8 };
int numpins  = 7;
void setup() {
 for (int c = 0; c < numpins; c++) {
   pinMode(pins[c], INPUT_PULLUP);
 }
 pinMode(LED_BUILTIN,OUTPUT);
 Serial.begin(115200);
 digitalWrite(LED_BUILTIN,LOW);
}
void flashled() {
 digitalWrite(LED_BUILTIN,HIGH);
 delay(250);
 digitalWrite(LED_BUILTIN,LOW);
}
void loop() {
 for (int c = 0; c < numpins; c++) {
  if (digitalRead(pins[c]) == LOW) { 
    while(digitalRead(pins[c]) == LOW);
    if (c == 0) Serial.println("UP");
    if (c == 1) Serial.println("DOWN");
    if (c == 2) Serial.println("LEFT"); 
    if (c == 3) Serial.println("RIGHT");
    if (c == 4) Serial.println("MID");
    if (c == 5) Serial.println("SET");
    if (c == 6) Serial.println("RESET");
    flashled();
  }
 }
}

Here is a top and side view of the button so you can see it looks somewhat like a joystick.

5 Way Navigation Button5 Way Navigation Button

PIR Sensor and Relay Switch to turn on a Light

Ever wonder how a motion detector is used to turn on a light? Take a look at this graphic (made with Fritzing). It shows the circuitry that uses an STM32F103C8T6 microcontroller to control a PIR sensor and relay switch to turn on a Light.

PIR Sensor and Relay Switch to turn on a Light

Get the Fritzing source code for the above image HERE (in case you would like to modify it).  Shown below is the code for the STM32F103C8T6 microcontroller to be used in the Arduino IDE.

//
// STM32F103C8T6 is used to turn on a relay switch
// when motion is detected by a PIR (motion sensor)
// from earl@microcontrollerelectronics.com

/* 
             +-----------------[USB]-----------------+
  [SS2|PB12] | [31]                            [Gnd] |
 [SCK2|PB13] | [30]                  +---+     [Gnd] |
[MISO2|PB14] | [29]    +-----+       |0 0|     [3V3] |
[MOSI2|PB15] | [28]    |Reset|       |x x|   [Reset] | 
       [PA8] | [27]    +-----+       |1 1|      [ 0] | [PB11|SDA2|RX3] 
   [TX1|PA9] | [26]                  +---+      [ 1] | [PB10|SCL2|TX3]
  [RX1|PA10] | [25]                   ^ ^       [33] | [PB1]
 [USB-|PA11] | [24]            Boot1--+ |       [ 3] | [PB0|A0]
 [USB+|PA12] | [23]            Boot0----+       [ 4] | [PA7|A1|MOSI1]
      [PA15] | [20]                             [ 5] | [PA6|A2|MISO1]
       [PB3] | [19]     +---------------+       [ 6] | [PA5|A3|SCK1]
       [PB4] | [18]     | STM32F103C8T6 |       [ 7] | [PA4|A4|SS1]
       [PB5] | [17]     |   Blue Pill   |       [ 8] | [PA3|A5|RX2]
  [SCL1|PB6] | [16]     +---------------+       [ 9] | [PA2|A6|TX2]
  [SDA1|PB7] | [15]                             [10] | [PA1|A7]
       [PB8] | [32]                             [11] | [PA0|A8]
       [PB9] | [PB9]                            [12] | [PC15]
             | [5V]      +---------------+      [13] | [PC14]
             | [Gnd]     |    ST-Link    |      [14] | [PC13|LED]
             | [3V3]     |3V3 DIO CLK GND|     [Vbat]| 
             +-------------+---+---+---+-------------+
                           |   |   |   |

*/

#define pirPin    PA7
#define switchPin PB12

int calibrationTime = 30; 

long unsigned int lowIn;         
long unsigned int pause = 10000;  
boolean lockLow = true;
boolean takeLowTime;  

void setup() {
  Serial.begin(9600);
  pinMode(switchPin,OUTPUT);
  pinMode(pirPin, INPUT);
  digitalWrite(PB12,LOW);
  Serial.print("calibrating sensor ");
  for(int i = 0; i < calibrationTime; i++){
    Serial.print(".");
    delay(1000);
  }
  Serial.println(" done");
  Serial.println("SENSOR ACTIVE");
}

void loop() {
 if(digitalRead(pirPin) == HIGH){
   digitalWrite(switchPin, HIGH);   //the led visualizes the sensors output pin state
   if(lockLow){  
     //makes sure we wait for a transition to LOW before any further output is made:
     lockLow = false;            
     Serial.println("---");
     Serial.print("motion detected at ");
     Serial.print(millis()/1000);
     Serial.println(" sec"); 
     delay(50);
   }         
   takeLowTime = true;
 }
 if(digitalRead(pirPin) == LOW){       
   digitalWrite(switchPin, LOW);  //the led visualizes the sensors output pin state
   if(takeLowTime){
     lowIn = millis();          //save the time of the transition from high to LOW
     takeLowTime = false;       //make sure this is only done at the start of a LOW phase
   }
   //if the sensor is low for more than the given pause, 
   //we assume that no more motion is going to happen
   if(!lockLow && millis() - lowIn > pause){  
     //makes sure this block of code is only executed again after 
     //a new motion sequence has been detected
     lockLow = true;                        
     Serial.print("motion ended at ");
     Serial.print((millis() - pause)/1000);
     Serial.println(" sec");
     delay(50);
   }
 }
}

When motion is detected by the sensor, the relay is turned on which completes the mains circuit. Then, what ever is connected to the relay (like a light) is turned on. The motion detection is also shown by turning on the led and logging to the serial port.

To use this code in a practical situation, remove the serial port messages and change the length of time the relay switch is on.

An STM32F103C8T6 based MIDI Controller for MIDI2LR

For those of you who use Adobe Lightroom,  there is a way to use a MIDI hardware ‘box’ to control various Lightroom actions. I am told that once you use the hardware controls you will never want to go back to the software ones. There is a Lightroom plugin called MIDI2LR which interfaces various MIDI ‘boxes’ to  Lightroom. The MIDI box sends MIDI Control/Change commands to MIDI2LR which then changes them into Lightroom actions. More info on MID2LR can be found HERE.

There are several commercial MIDI controllers which do work with MID2LR, however, features and functionality differ. Since the whole idea for using the hardware controls is ease of use and productivity, I would think that Lightroom enthusiasts would want a means of creating a custom hardware solution.

It is actually relatively easy to create an STM32F103C8T6 based MIDI Controller for MIDI2LR. As a proof of concept, I built one on a breadboard to demonstrate what can be done. Here is the design layout:

An STM32F103C8T6 based MIDI Controller for MIDI2LRDownload the Fritzing diagram source code HERE.  And here is what it looks like all wired together:

An STM32F103C8T6 based MIDI Controller for MIDI2LR

The two buttons next to the display are used to control menu selections. The menu is used to set whether the buttons send MIDI Notes or Change/Control commands and also is used to select up to 5 channels.  Therefore with the 8 buttons  x ( 5 channels) x 2 (Note vs. CC commands), there is a possibility of  80 different commands to send to MIDI2LR.  The 5 channels is arbitrary and of course changeable in the code so possibilities are only limited by practicality. When a button is pressed, the MIDI CC/Note is sent to MIDI2LR and displayed on the OLED screen.

The code below was developed and sent to the STM32F103C8T6 via the Arduino IDE:

// STM32F103C8T6  MIDI Controller
// for the MIDI2LR ADOBE LIGHTROOM PLUGIN
// from earl@microcontrollerelectronics.com

/* 
 *            +-----------------[USB]-----------------+
  [SS2|PB12] | [31]                            [Gnd] |
 [SCK2|PB13] | [30]                  +---+     [Gnd] |
[MISO2|PB14] | [29]    +-----+       |0 0|     [3V3] |
[MOSI2|PB15] | [28]    |Reset|       |x x|   [Reset] | 
       [PA8] | [27]    +-----+       |1 1|      [ 0] | [PB11|SDA2|RX3] 
   [TX1|PA9] | [26]                  +---+      [ 1] | [PB10|SCL2|TX3]
  [RX1|PA10] | [25]                   ^ ^       [33] | [PB1]
 [USB-|PA11] | [24]            Boot1--+ |       [ 3] | [PB0|A0]
 [USB+|PA12] | [23]            Boot0----+       [ 4] | [PA7|A1|MOSI1]
      [PA15] | [20]                             [ 5] | [PA6|A2|MISO1]
       [PB3] | [19]     +---------------+       [ 6] | [PA5|A3|SCK1]
       [PB4] | [18]     | STM32F103C8T6 |       [ 7] | [PA4|A4|SS1]
       [PB5] | [17]     |   Blue Pill   |       [ 8] | [PA3|A5|RX2]
  [SCL1|PB6] | [16]     +---------------+       [ 9] | [PA2|A6|TX2]
  [SDA1|PB7] | [15]                             [10] | [PA1|A7]
       [PB8] | [32]                             [11] | [PA0|A8]
       [PB9] | [PB9]                            [12] | [PC15]
             | [5V]      +---------------+      [13] | [PC14]
             | [Gnd]     |    ST-Link    |      [14] | [PC13|LED]
             | [3V3]     |3V3 DIO CLK GND|     [Vbat]| 
             +-------------+---+---+---+-------------+
                           |   |   |   |
*/

#include <USBComposite.h>

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306_STM32.h>

#define OLED_RESET PB4
Adafruit_SSD1306 display(OLED_RESET);

USBMIDI midi;
#define LED PC13
#define KeyDelay 200
#define maxChannel 5

const char ManufacturerName[] = "Generic Chinese BluePill";
const char DeviceName[]       = "STM32F103C8T6 Midi Device";
const char DeviceSerial[]     = "00000000000000000001";
const int  ProductId          = 0x0031;
const int  VendorId           = 0x1EAF;

const uint8 pins[]    = { PA0,PA1,PA2,PA3,PA4,PA5,PA6,PA7};
const uint8_t notes[] = { 0, 1, 2, 3, 4, 5, 6, 7};

#define Menu        PB8
#define Menu_Select PB9

const char *menu_items[] = {"Channel","Midi","Reset"};
const int numMenuItems   = 3;

char textline[80];

int channel  = 0;
int midicmd  = 0;
int menuitem = -1;

unsigned long timer;
const long interval = 5000;
unsigned long currentmillis;

void setup() {

  USBComposite.setManufacturerString(ManufacturerName);
  USBComposite.setProductString(DeviceName);
  USBComposite.setSerialString(DeviceSerial);
  USBComposite.setVendorId(VendorId);
  USBComposite.setProductId(ProductId);

  midi.begin();
  while (!USBComposite);
  for(int i=0;i<8;++i) pinMode(pins[i],INPUT_PULLUP);
  pinMode(Menu,INPUT_PULLUP);
  pinMode(Menu_Select,INPUT_PULLUP);
  pinMode(LED,OUTPUT);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  timer = millis();
  disptext(0,0,2," MIDI2LR\n\nController\n  Ready!");
}

void disptext(int row, int col, int size, char *text) {
  int i = 0;
  display.clearDisplay();
  display.setTextSize(size);
  display.setTextColor(WHITE);
  display.setCursor(row,col);
  while(text[i] != '\0') {
   if (text[i] == '\n') display.println();
   else display.write(text[i]);
   ++i;
  }
  display.display();
  timer = millis();
}

void loop() {
  currentmillis = millis();
  
  if ( (currentmillis - timer) > 10000) {
    timer = currentmillis;
    display.clearDisplay();
    display.display();
  }

  if (digitalRead(Menu) == LOW) {
    while(digitalRead(Menu) == LOW) delay(50);
    ++menuitem;
    if (menuitem >= numMenuItems) menuitem = 0;
    sprintf(textline,"Menu Item\n%s",menu_items[menuitem]);
    disptext(0,0,2,textline);
  }

  if (digitalRead(Menu_Select) == LOW) {
    if (menuitem == 0) {
      channel += 1; 
      if (channel >= maxChannel) channel = 0;     
      sprintf(textline,"Select\n%s\nMidi %s\nChannel %d",menu_items[menuitem],(midicmd ? "CC" : "Note"),channel);
    }
    if (menuitem == 1) {
      if (midicmd == 0) midicmd = 1;
      else              midicmd = 0;
      sprintf(textline,"Select\n%s\nMidi %s\nChannel %d",menu_items[menuitem],(midicmd ? "CC" : "Note"),channel);
    }
    if (menuitem == 2) {
      midicmd = 0;
      channel = 0;
      sprintf(textline,"Menu\nReset\nMidi %s\nChannel %d",(midicmd ? "CC" : "Note"),channel);
    }
    while(digitalRead(Menu_Select) == LOW) delay(50);   
    disptext(0,0,2,textline);
  }

  for(int i=0;i<8;++i) {
    if (digitalRead(pins[i]) == LOW) {
      digitalWrite(LED,!digitalRead(LED));
      sprintf(textline,
        "Button %d\nChannel %d\nMidi %s\n%d|%s:%d",
        pins[i],channel,(midicmd ? "CC" : "Note"),channel,(midicmd ? "CC" : "Note"),notes[i]
      );
      disptext(0,0,2,textline);
      if (midicmd == 0) {
         midi.sendControlChange(channel, notes[i], 127);
         delay(KeyDelay);
         midi.sendControlChange(channel, notes[i], 0);
      }
      else {
        midi.sendNoteOn(channel, notes[i], 127);
        delay(KeyDelay);
        midi.sendNoteOff(channel, notes[i],0);
      }
      while(digitalRead(pins[i]) == LOW) delay(50);   
    }
  }
}

The Arduino IDE was used to develop and program the STM32F103C8T6. The IDE was also customized to use the Arduino Core for STM32 MCUs. Also note the use of the Adafruit graphics libraries to control the OLED display.

Once the code is sent, just plug in the STM32F103C8T6 via an USB-micro cable to your Windows 10 PC and it will be added as an audio device (no drivers are needed as it is automatically recognized). Install the MIDI2LR plugin and while in Lightroom you can test out the button functions and customize the functionality.

You might say that buttons are OK but dials (rotary encoders) are what is missing.  You are right, that is what is being planned for in the next step of this project. Stay tuned!

WebRTC Phone Calls via Asterisk

Are you ready for another off topic article on WebRTC?  This one is titled WebRTC Phone Calls via Asterisk. I have written about Asterisk before (HERE) and that article did have something to do with microcontrollers  😎  Asterisk is an open source full featured phone system (PBX). In my last post about WebRTC, I showed how to do video/audio sharing via WebRTC. This article shows how to turn your Web browser into a (tele)phone (using the PC audio devices and WebRTC Javascript code). With the code provided you can make and answer calls through your Asterisk system via the Web.

Of course you need an Asterisk system up and running for testing this. There are many helpful tutorials on the internet to help with that. The Asterisk wiki also has very good documentation on installing and customizing Asterisk.

The article to customize Asterisk for WebRTC is HERE. Basically, there are three configuration files that need changed to make WebRTC Phone Calls via Asterisk.  Usually these files (httpd.conf, extensions.conf, sip.conf) are found in the /etc/asterisk directory after installation .

For httpd.conf, you will need to select a port for both TLS and HTTP.  You will also need a valid SSL certificate. I am using one from Letsencrypt.org. In the example below I am using port 7070 for HTTP and 7079 for TLS.

[general]
servername=Asterisk
enabled=yes
bindaddr=(your asterisk server IP address)
bindport=7070
tlsenable=yes
tlsbindaddr=(your asterisk server IP address):7079
tlscertfile=/etc/letsencrypt/live/yourdomain.com/cert.pem
tlsprivatekey=/etc/letsencrypt/live/yourdomain.com/privkey.pem

For sip.conf , you need to specify the parameters for the phone (I am using extension 5099):

[5099]
type=friend
defaultuser=5099
host=dynamic
secret=webrtcs5099*
encryption=yes         ; Tell Asterisk to use encryption for this peer
avpf=yes               ; Tell Asterisk to use AVPF for this peer
icesupport=yes         ; Tell Asterisk to use ICE for this peer
context=webrtc-sip     ; Default context for incoming calls
directmedia=no         ; Asterisk will relay media for this peer
transport=wss          ; Asterisk will allow this peer to register on UDP or WebSockets
force_avp=yes          ; Force Asterisk to use avp. Introduced in Asterisk 11.11
rtcp_mux=yes           ; Tell Asterisk to do RTCP mux
dtlsenable=yes         ; Tell Asterisk to enable DTLS for this peer
dtlsverify=fingerprint ; Tell Asterisk to verify DTLS fingerprint
dtlssetup=actpass      ; Tell Asterisk to use actpass SDP parameter when setting up DTLS
dtlscertfile=/etc/asterisk/keys/pbx.pem

Note that WebRTC will be using the websocket protocol for communications to the Asterisk server.

In extensions.conf is where you specify the context and dialing plan so that the WebRTC enabled phone can make and receive calls:

[webrtc-sip]
include => local-extensions
exten => 5099,1,Dial(SIP/5099,30)

Note: The dialing plan is where security is very important because it specifies who the  ‘phone/user’ is allowed to dial  and also what is allowed to call it.

Once Asterisk has been configured, the WebRTC code can be  accessed to try a call.  To implement the SIP and WebRTC protocols I have chosen to use the JSSIP Javascript library code (HERE).

Download  the JSSIP library and  place it (jssip.min.js) in the same Web directory as the two other files (index.html and sip.js) show below.

Here is the index.html page to enable the phone:

<!DOCTYPE html>
<html>
<head>
<title>WebRTC to VOIP Asterisk Calling</title>
<style>
.btn {
 box-shadow: 0px 1px 0px 0px #f0f7fa;
 background:linear-gradient(to bottom, #33bdef 5%, #019ad2 100%);
 background-color:#33bdef;
 border-radius:6px;
 border:1px solid #057fd0;
 display:inline-block;
 cursor:pointer;
 color:#ffffff;
 font-family:Arial;
 font-size:15px;
 font-weight:bold;
 padding:6px 24px;
 text-decoration:none;
 text-shadow:0px -1px 0px #5b6178;
}

.btn:hover {
 background:linear-gradient(to bottom, #019ad2 5%, #33bdef 100%);
 background-color:#019ad2;
}

.btn:active {
 position:relative;
 top:1px;
}
</style>
<meta charset="utf-8" />
</head>
<body>
<br>
<center>
<H1>Answer or Call via WebRTC from/to VOIP Asterisk<br>with your PC/Laptop Microphone and Speakers/Headset</H1>
<br>
<br>
<form>
<table>
<tr><td>Phone # </td><td><input id=phone name="Phone #"  type=text     value='' /></td></tr>
<tr><td>Password</td><td><input id=pass  name="Password" type=password value='' /></td></tr>
<tr><td>Extension to call</td><td><input id=extension name=extension type=text value='' /></td></tr>
</table>
</form>
<br>
<div id=buttons>
 <button class=btn id=regbtn  onclick="register();">Register</button>
 <button class=btn id=callbtn onclick="call();">Call</button>
</div>
</center>
<script src='jssip.min.js'></script>
<script src='sip.js'></script>
</body>
</html>

Here is the main Javascript code (sip.js):

//JsSIP.debug.enable('JsSIP:*');
//JsSIP.debug.disable('JsSIP:*');

const domain = 'yourdomain.com';
const port   = '7079'

var callbtn  = document.getElementById("callbtn");
var exten    = document.getElementById("extension");
var regbtn   = document.getElementById("regbtn");
var phone    = document.getElementById("phone");
var pass     = document.getElementById("pass");
var socket   = new JsSIP.WebSocketInterface('wss://' + domain + ':' + port + '/ws');
var ua       = "";
var sipAudio = new Audio();         

function register() {
  var configuration = {
    sockets  : [ socket ],
    uri      : 'sip:' + phone.value + '@' + domain,
    password : pass.value,
    register : true, 
  };

  ua = new JsSIP.UA(configuration);

  ua.on('connected',    function(e) {
    regbtn.textContent = 'Connected';
    console.log('agent is connected'); });
  ua.on('disconnected', function(e) {
    regbtn.textContent = 'DisConnected';
    console.log('agent is disconnected'); });
  ua.on('registered',   function(e) { 
    regbtn.textContent = 'Registered';
    console.log('agent is registered'); });
  ua.on('unregistered', function(e) { 
    regbtn.textContent = 'UnRegistered';
    console.log('agent is unregistered'); });
  ua.on('sipEvent', function(e) {
    console.log('sipEvent'); });
  ua.on('newRTCSession', function(e) {
    console.log("newRTCSession");
    console.log(e);
    var session = e.session;
    if (session.direction === "incoming") {
      session.on('peerconnection', function(e) {
        console.log("peerconnection");
        e.peerconnection.addEventListener('addstream', function (e) {
          console.log("Stream added");
          sipAudio.srcObject = e.stream;
          sipAudio.play();
        });
      });
      callbtn.textContent = 'Incoming Call';
      var callOptions = {
        mediaConstraints: {
          audio: true, // only audio calls
          video: false
        }
      };
      session.answer(callOptions);
    }
    else {
      session.connection.addEventListener('addstream', function (e) {
        console.log("Stream added");
        sipAudio.srcObject = e.stream;
        sipAudio.play();
      });
    }
    session.on('confirmed',function(e){ console.log("session confirmed"); });
    session.on('failed',   function(e){ console.log("session failed");    });
    session.on('ended',    function(e){
      console.log("session ended"); 
      callbtn.textContent = 'Call';
    });
    session.on('accepted', function(e){ console.log("session accepted");  });
  });
  ua.on('registrationFailed', function(e) {
    regbtn.textContent = 'Register Failed';
    console.log('agent register failed : ' + e.request +  e.response);
  });
  ua.on('sipEvent', function(e) {
    console.log('Sip Event');
    console.log(e);
  });
  ua.on('newMessage', function(e) {
    console.log('New Message');
    console.log(e);
  });
  ua.start();

}

function call() {
  var eventHandlers = {
    'progress': function(e) {
      callbtn.textContent = 'Call in Progress';
      console.log('call is in progress');
    },
    'failed': function(e) {
      callbtn.textContent = 'Call Failed';
      console.log('call failed');
    },
    'ended': function(e) {
      callbtn.textContent = 'Call';
      console.log('call ended');
    },
    'confirmed': function(e) {
      callbtn.textContent = 'Call Confirmed';
      console.log('call confirmed');
    },
  };

  var options = {
    'eventHandlers'    : eventHandlers,
    'mediaConstraints' : { 'audio': true, 'video': false }
  };

  ua.call('sip:' + exten.value + '@' + domain , options);

  callbtn.textContent = 'Dailing';
}

Make sure to change the domain name and port to match what is configured in Asterisk. Then navigate to the index.html page and you should see this page:

WebRTC Phone Calls via Asterisk

 

 

 

 

 

Then put in the phone number and password  for the WebRTC phone and click Register. If all goes well, the Register button will change to ‘Registered’. Your WebRTC phone is now ready to answer or make calls.

Put in an extension to call and click the Call button. The Call button will change to match the state of the call. Any incoming calls will automatically be answered and the Call button will be updated to the state of the call. Note: WebRTC (the browser) will ask permission to use the microphone when the fist call is made or answered.

Note that diagnostic messages are logged to the web console. Un-commenting the JsSIP debug.enable in sip.js will also show a lot of interesting debug information for those curious as to what is happening behind the scenes.

Load more