BBC micro:bit SN76489N VGM chiptunes player

Dependencies:   microbit

main.cpp

Committer:
linker3000
Date:
2018-02-11
Revision:
1:09a3520030b4
Parent:
0:e4dc860c8472
Child:
2:74ecf50e4424

File content as of revision 1:09a3520030b4:

/*
BBC micro:bit VGM player

Version: 0.1 NK 10-Feb-2018

Copyright 2018 N Kendrick (nigel-dot-kendrick-at-gmail-dotcom)

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Inspired by Arduino code produced by Artkasser (https://github.com/artkasser)

This code plays BBC micro (SN76489) VGM sound files on a BBC micro:bit.

Note that in this code version, the VGM data is stored as a byte array file
(minus the header info), and not read-in from anywhere. Maybe later!?

External circuitry is needed to make this work:

1 x BBC micro:bit breakout board (eg: Kitronik) to get to the required pins
1 x SN74HC595 shift register IC - easy to obtain
1 x SN76489 programmable sound generator (PSG) IC (From a reputable[!]
    supplier. You can get them on Ebay and elsewhere but beware of fakes.
2 x 0.1uF ceramic capacitors
1 x 4MHz oscillator module - the connections list below assumes a '14-pin'
    unit - such as something similar to QX14T50B4.000000B50TT.
    Note, you want a 4MHz crystal MODULE, not a 4MHz crystal.
1 x Breadboard to build the circuit, unless there is room on the breakout board
    or you use perfboard/stripboard or make your own PCB etc.
2 x 16-pin IC sockets (optional, and not needed for a breadboard)
1 x audio amplifier or lead to connect PSG audio out to a set of active
    speakers, you could instead connect a pieze element (not a buzzer) to the
    audio out pin of the PSG. If connecting to a piezo sounder, you'll need a
    1K resistor.
1 x Assorted wiring

Connections:

    The micro:bit takes power as usual from the connected micro USB cable.

    Everything else i powered from a separate 5V supply, such as a USB phone
    charger. You *could* try powering these parts from the 3V connection on
    the micro:bit - it might work, but the PSG and the oscillator module are
    officially 5V parts so YMMV.

    Remember to ensure that the micro:bit's 0V/GND line is connected to the
    GND line of all the other parts to ensure proper circuit operation. DO NOT
    connect the 5V power line to the micro:bit's 3V line as you'll break
    something!

    Hookup overview:

    From                 Function      Connect to
    micro:bit P15          MOSI     74HC595 pin 14 (SER)
    micro:bit P13          CE       74HC595 pin 12 (RCLK)
    micro:bit P2           SER      74HC595 pin 11 (SRCLK)
    micro:bit P8           PSG ~WE  SN76489 pin 5 (~WE)
    74HC595 Data out       Data bus PSG Data lines - Note that the PSG data
                                    lines are labelled in REVERSE, so the
                                    wiring between the 595 and the 76489 should
                                    be 74HC595 D0 --- D7 SN76489
                                       74HC595 D1 --- D6 SN76489 etc
    Oscillator P7          GND      To common ground with all other GND pins
    Oscillator P8          CLK      SN76489 pin 16
    Audio out              AUDIO    SN76489 pin 7 to an external amplifier,
                                    or a piezo sounder through a 1K resistor
    +5V                    VCC      Both ICs, pin 16, Oscillator pin 14; all
                                    active components EXCEPT the micro:bit
    micro:bit GND          GND      connect to the common GND on the external
                                    parts

For stability, connect 0.1uF capacitors between VCC and GND near the both the
74HC595 and SN76489 chips.

Double-check all wiring before powering up anything!

*/

#include "MicroBit.h"
#include "nkpins.h"

//VGM file as a byte array with header info removed...
#include "sonic.h"

MicroBit uBit;
MicroBitDisplay display;

const uint16_t SampleTime = 23; //VGM format = 44,100Hz sampling = 23uS/sample

// General program variables

uint16_t Samples = 0;
uint16_t vgmpos = 0;

// Microbit SPI port setup
// Using the default SPI pins defined in the micro:bit docs...
SPI spi(mbit_p15, mbit_p14, mbit_p13); // mosi, miso (not used), sclk
DigitalOut cs(mbit_p1);     //Chip select pin for the shift register
DigitalOut PSG_WE(mbit_p8); //~WE pin for PSG

void PutByte (uint8_t b)
// Write data to the shift register via SPI interface
{
    cs = 1;
    spi.write(b);
    cs = 0;
}

void SendByte(uint8_t b)
{
    PSG_WE = 1;
    PutByte(b);
    PSG_WE = 0;
    wait_us(SampleTime);
    PSG_WE = 1;
}

void SilenceAllChannels()
{
    SendByte(0x9f);
    SendByte(0xbf);
    SendByte(0xdf);
    SendByte(0xff);
}

void Playloop()
//Process the VGM data
{

    bool runstop = false; 
    // True when we get to the 'end of data' value in the array. 
    // There's no current trap for if the last byte in the array isn't 0x66
    
    do {
        uint8_t vgmdata = (*(VGMDataArray + vgmpos));

        // Process VGM codes.

        /* Some ranges are reserved for future use, with different numbers of
           operands:

           0x30..0x3F dd          : one operand, reserved for future use
                                 Note: used for dual-chip support
           0x40..0x4E dd dd       : two operands, reserved for future use
                                 Note: was one operand only til v1.60
           0xA1..0xAF dd dd       : two operands, reserved for future use
                                 Note: used for dual-chip support
           0xBC..0xBF dd dd       : two operands, reserved for future use
           0xC5..0xCF dd dd dd    : three operands, reserved for future use
           0xD5..0xDF dd dd dd    : three operands, reserved for future use
           0xE1..0xFF dd dd dd dd : four operands, reserved for future use

           This programming does NOT currently cater for those codes
          */

        if ((vgmdata & 0xF0) == 0x70) {
            // 0x7n : wait n+1 samples, n can range from 0 to 15
            //Samples = 1;
            vgmpos++;
            wait_us(((vgmdata & 0x0F)+1) * SampleTime);
        } else {
            switch (vgmdata) {
                case 0x50: // 0x50 dd : PSG (SN76489/SN76496) write value dd
                    vgmpos++;
                    vgmdata = (*(VGMDataArray + vgmpos));
                    SendByte(vgmdata);
                    vgmpos++;
                    break;

                case 0x61: // 0x61 nn nn : Wait n samples, n can range from 0 to 65535
                    vgmpos++;
                    Samples = (uint16_t)( (*(VGMDataArray + vgmpos)) & 0x00FF );
                    vgmpos++;
                    Samples |= (uint16_t)(( (*(VGMDataArray + vgmpos)) << 8) & 0xFF00 );
                    vgmpos++;
                    wait_us(Samples * SampleTime);
                    break;

                case 0x62: // wait 735 samples (60th of a second)
                    vgmpos++;
                    wait_ms(17);
                    break;

                case 0x63: // wait 882 samples (50th of a second)
                    vgmpos++;
                    wait_ms(20);
                    break;

                case 0x66: // 0x66 : end of sound data
                    vgmpos = 0;
                    SilenceAllChannels();
                    wait_ms(2000);
                    runstop = true;
                    break;

                default:
                    break;
            } //end switch

        } // end else


    } while (!runstop);
}
int main()
{
    // Initialise the micro:bit runtime.
    uBit.init();

    //SPI setup
    // Chip must be deselected
    cs = 1;

    // Setup the spi for 8 bit data, high steady state clock,
    // second edge capture, with a 1MHz clock rate (microbit and shift
    // register can handle it - tested).
    spi.format(8,3);
    spi.frequency(1000000);

    // Silence the PSG

    PSG_WE = 1;
    SilenceAllChannels();
    wait_ms(500);

    //Play music
    Playloop();
    //Mute the last tone
    SilenceAllChannels();

    display.scroll("** DONE ** :)");

    //Tidy up and we're done
    release_fiber();

}