Ticker

6/recent/ticker-posts

MPLAB Xpress: MP3 click piano



Turning your PC into a piano? All you need is one MPLAB Xpress Evaluation board, one MP3 click board, a pair of PC speakers (headphones work too) and some programming skills. The idea behind this blog post is to configure the VS1053 IC onboard the MP3 click board into real-time MIDI mode by uploading a small patch code, then sending MIDI codes according to key presses on the PC.

So, one has to solve two issues: to establish communication with the VS1053 and to convert key presses to MIDI codes. I will start with the latter, as that is the boring and complicated part of this project.

We start with a standard US PC keyboard. The keys that will be used as piano keyboard are as follows:PC Keyboard layout

PC keyboard layout

When a key is pressed the PC receives the ASCII code corresponding to that key press. Different key combinations, such as pressing combinations of Shift, Alt, Ctrl or by having Caps Lock on will transmit different ASCII codes. As such, a key can have multiple ASCII code mappings. When using a PC terminal program to send data to the MPLAB Xpress evaluation board, what is sent is the ASCII code for each key press. Depending on the PC terminal program used, one might not be able to send complex key combinations but only lowercase and uppercase letters, and the special characters above the numbers.

In this code example I will take into account only the Caps Lock being on. So, the ASCII codes (in hexadecimal) for my keyboard are:

PC keyboard ASCII codes mapping

In this project I want to to use this keyboard as a piano keyboard, something that looks like this in C major scale letter notation:

PC keyboard: piano layout

When playing MIDI music, each note has its own MIDI code. Here is how the above keyboard looks in as MIDI codes, also in hexadecimal format:

MIDI codes for the piano keyboard

From ASCII codes to MIDI notes

The first issue to be solved is to convert the ASCII codes that are sent via the PC terminal to MIDI codes. One can observe that there is a difference of 0x20 in hexadecimal between the ASCII codes for a lowercase letter and the ASCII code for the same letter but in uppercase. Also we observe that the first uppercase letter is A, which has the ASCII code of 0x41, all other letters being in successive order.We can use this is our advantage, by using look-up tables to perform the conversion between ASCII codes and MIDI codes.

There will be two look-up tables: one for numbers, and one for uppercase letters. Lowercase letters are easily converted to uppercase by just subtracting 0x20, then the look-up table for the uppercase letters can be used.

Not all letters correspond to piano keys. The keys that have no correspondence will be assigned a value of 0x00, and it will be ignored later in the routine that performs sending of MIDI codes.

So, first the numbers. If the received ASCII code is between 0x30 and 0x39 then we have a number. The offset of 0 is 0x30 in the ASCII table and we can just subtract it. Then we build out table as follows:

// Key:           0    1    2    3    4    5    6    7    8    9
// Ascii code: 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39
// Note:                  C#5  D#5       F#5  G#5  A#5  C#6  D#6
// MIDI code:  0x57      0x49 0x4B      0x4E 0x50 0x52      0x55
NLookup_table=[0x57,0x00,0x49,0x4B,0x00,0x4E,0x50,0x52,0x00,0x55];

The same process goes for letters:

// Key: 	  A    B    C    D    E    F    G    H    I    J
// ASCII code: 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0x48 0x49 0x4A
// Note:              G4   C4  D#4   E5       F#4  G#4   C6  A#4
// MIDI code:       0x43 0x3C 0x3F 0x4C      0x42 0x44 0x54 0x46
LLookup_table=[0x00,0x43,0x3C,0x3F,0x4C,0x00,0x42,0x44,0x54,0x46,

// Key: 	  K    L    M    N    O    P    Q    R    S    T
// ASCII code: 0x4B 0x4C 0x4D 0x4E 0x4F 0x50 0x51 0x52 0x53 0x54
// Note:                   B4   A4   D6   E6   C5   F5  C#4   G5
// MIDI code:            0x47 0x45 0x56 0x58 0x48 0x4D 0x3D 0x4F
               0x00,0x00,0x47,0x45,0x56,0x58,0x48,0x4D,0x3D,0x4F,


// Key: 	  U    V    W    X    Y    Z
// ASCII code: 0x55 0x56 0x57 0x58 0x59 0x5A
// Note:         B5   F4   D5   D4   A5   4C
// MIDI code:  0x53 0x41 0x4A 0x3E 0x51 0x3C
               0x53,0x41,0x4A,0x3E,0x51,0x3C];

When receiving one uppercase letter all we have to do is to subtract the offset of 0x41, then take the corresponding MIDI code from the lookup table. When we have lowercase letters we have to subtract 0x61, then it goes all the same.


MP3 click: Realtime MIDI mode

Now let’s take a look at the hardware too: I have one DM164140 MPLAB Xpress Evaluation board, with an MP3 click board installed. The MP3 click is based on the VC1053 audio processor. Communication between the PIC16F18855 microcontroller and the VS1053 IC is done via SPI interface, with a twist: the VS1053 has two CS (chip select) lines, one for accessing control registers and one for sending data. An extra DREQ (data request) line is pulled up when VS1053 can receive new data. The complete schematic is given in the MP3 click manual.

Normally, the VS1053 uses UART communication when configured in MIDI mode. But UART communication is not available in the MP3 click implementation. So, to play MIDI notes one must first upload a small software patch that you will get from VLSI patches page: our patch is named “VS1053b Realtime MIDI Start code”.

Such patch is nothing but a small piece of code, in hexadecimal format, that must be uploaded into the VS1053 to bring extra functionalities or to solve some known issues. With this patch loaded we can send MIDI commands on the SPI interface.

You might notice that I have used the same plugin in the Flip and Click “Theremin” post, and it takes a lot from that code. The only difference is the programming language: the theremin was programmed in Arduino IDE. Now I will use MPLAB Xpress online IDE and XC8 comppiler, and I will use functions generated by Microchip Code Configurator.

I will start from the plugin code, which is organized as 16-bits data, matching the VS1053 architecture:

const unsigned short VS1053b_Realtime_MIDI[28] = {
  0x0007, 0x0001, 0x8050, 0x0006, 0x0014, 0x0030, 0x0715, 0xb080,  /*  8 */
  0x3400, 0x0007, 0x9255, 0x3d00, 0x0024, 0x0030, 0x0295, 0x6890,  /* 16 */
  0x3400, 0x0030, 0x0495, 0x3d00, 0x0024, 0x2908, 0x4d40, 0x0030,  /* 24 */
  0x0200, 0x000a, 0x0001, 0x0050,}

which is loaded using the following function:

void VSLoadUserCode(void)
{
  unsigned int i = 0;
  while (i < sizeof(VS1053b_Realtime_MIDI) / sizeof(VS1053b_Realtime_MIDI[0])) {
    unsigned short addr, n, val;
    addr = VS1053b_Realtime_MIDI[i++];
    n = VS1053b_Realtime_MIDI[i++];
    while (n--) {
      val = VS1053b_Realtime_MIDI[i++];
      VSWriteRegister(addr, val >> 8, val & 0xFF);
    }
  }
}

The first information we read from the plugin is an address of a VS1053 register. We then read the number of words to be written at that address (there’s an internal auto-increment function of VS53). Then we write the data using the VSWriteRegister function.

So, we read the address 0x0007, where we have to write a number 0x0001 of 16-bit values. We take 0x0850 from the plugin and we write it. Then we have the address 0x0006, where he have to write 0x0014 (in decimal that’s 20) values, from 0x0030 to 0x0200. Finally, we write 0x0050 to address 0x000A.

Writing is performed by the VSWriteRegister function:

void VSWriteRegister(unsigned char addressbyte, unsigned char highbyte, unsigned char lowbyte){
  //Wait for DREQ to go high indicating IC is available
  Wait_for_DREQ();
  // CS# should be low for the full duration of operation
  CS_SetLow();
  //SCI consists of instruction byte, address byte, and 16-bit data word.
  SPI1_Exchange8bit(0x02);         // Write instruction
  SPI1_Exchange8bit(addressbyte);  // Destination register
  SPI1_Exchange8bit(highbyte);     // MSB
  SPI1_Exchange8bit(lowbyte);      // LSB
  //Wait for DREQ to go high indicating command is complete
  Wait_for_DREQ();
  //Deselect Control
  CS_SetHigh();
}

this function relies on the Wait_for_DREQ(), which is a blocking function:

void Wait_for_DREQ (void){
  int DREQ_status;
  DREQ_status = DREQ_GetValue();
  while (DREQ_status == 0){
      // update DREQ status
      DREQ_status = DREQ_GetValue();
  }
}

What happens here is that we wait for the DREQ line to go high, signaling that the VS1053 is ready to accept new data. We then set the CS line low, which tells VS1053 that we will send a command. Then we use the SPI1_exchange8bit function (generated by MCC) to send the value 0x02, which signifies that a write operation follows. We then send the address, followed by MSB and LSB. Finally, we wait for DREQ to go high again, signaling that the write was successful, and we bring up the CS line.

VS1053: playing MIDI data
Now that we loaded the code we can play the music. To send MIDI information we must be in data mode, which is set by pulling DCS low:
void talkMIDI(uint8_t cmd, uint8_t data1, uint8_t data2) {
  // Wait for chip to be ready (Unlikely to be an issue with real time MIDI)
  Wait_for_DREQ();
  // Set DCS to low (sending data)
  DCS_SetLow();
  sendMIDI(cmd);
  // Some commands only have one data byte. All cmds less than 0xBn have 2 data bytes
  // (sort of: http://253.ccarh.org/handout/midiprotocol/)
  if( (cmd & 0xF0) <= 0xB0 || (cmd & 0xF0) >= 0xE0) {
    sendMIDI(data1);
    sendMIDI(data2);
  } else {
    sendMIDI(data1);
  }
  // We sent the command, now we set DCS to high
  DCS_SetHigh();
}

Here we send only the data, without register address or read/write code:

void sendMIDI(uint8_t data)
{
  SPI1_Exchange8bit(0x00);
  SPI1_Exchange8bit(data);
}

Two more helper functions were created, to send and stop playing musical notes:

void noteOn(uint8_t channel, uint8_t note, uint8_t attack_velocity) {
  talkMIDI( (0x90 | channel), note, attack_velocity);
}

void noteOff(uint8_t channel, uint8_t note, uint8_t release_velocity) {
  talkMIDI( (0x80 | channel), note, release_velocity);

For a complete reference of MIDI commands one can check this summary of MIDI messages.

MP3 click: the piano

Now is time to put all together and go to the main code listing:

Post a Comment

0 Comments