A venture in retrotronics

Even though I didn't have the money to buy one myself back then in the early eighties, the Commodore 64 was my first love. And that had everything to do with the Video Interface Chip (VIC-II) and the Sound Interface Device (SID) in this machine.

In this project I've created a development board connecting a SID sound chip to an ATmega328P MCU. A 74LS374 IC containing eight D-type flipflops is used as a register to temporary store the address for the SID chip while setting up the data, allowing the address and data bus to be combined at the MCU pins.

Although I haven't put these to use yet, even the SID's pins for the paddles and the line in pin have been made available via connectors.

Schematic

The schematic of this development board is included here. As you can see, I used KiCad to design the schematic and the board.

In the top right quarter it shows a straight-forward ATmega328P-PU development board, providing a reset button, and individual breakout connectors for UART, ISP, I2C and power.

The lower part of the schematic holds the SID chip and all its peripheral parts. It is directly based on the original data sheets for the SID [1, 2]. In this part of the schematic we also find the 74LS374 IC, providing eight flipflops from which we will deploy six as temporary registers for the address (5 bits) and the R/W signal. The idea for this setup with some additional glue logic was borrowed from this guy, who used it to create a quick-and-dirty MIDI player for SID. Eight pins for the combined data/address/R/W bus plus another three for the PHI2, /CS and CLK signals brings the total number of pins to drive the SID chip from the ATmega328P MCU to eleven.

Note that the PHI2 signal of the SID chip is connected to the OC1A pin of the MCU, allowing us to clock it at the required 1 MHz using Timer 1.

Finally, at the top left part of the schematic we find two power regulators. These LM2940CT LDO parts provide the 5V for the logic and the 9V or 12V for the analog sound part of the SID chip, the specific required voltage for the latter depending on its revision.

Board

The PCB for this development board was laid out by hand, resulting in a neat layout that is pleasant to the eye too :-) Below you see the PCB layout together with the 3D model generated by KiCad, and some photos of the actual board after assembly.

If you feel like building a similar board yourself, you can download the parts for KiCad here.

Software

The software written for this project includes an extensive library to interact with the SID chip in general, in this particular setup in specific. Below you see the function to initialize Timer 1 to generate the 1 MHz CLK signal (on an MCU running at 8 MHz).


// initialize clock on pin OC1A
uint8_t SID_init_clock_OC1A (void) {

  // starts a 1 MHz clock signal on pin OC1A (based on Timer 1)
  //     e.g. http://www.bot-thoughts.com/2011/06/generate-clock-signal-with-avr-atmega.html

  // set registers
  TCNT1 = 0;               // initialize counter (not necessary)
  OCR1A = 3;               // count 4 steps (0-3), on an 8 MHz clock -> 1 MHz signal
  TCCR1A = (1 << COM1A0);  // toggle pin OC1A on each match
  TCCR1B = (1 << WGM12)    // CTC Mode (clear and restart on match)
      | (1 << CS10);       // no prescaling

  // set pin OC1A (PB1) to output
  DDRB |= (1 << PB1);

  return 0;

  };

And these are the functions to initialize the combined data/address bus, and switch from write mode to read mode, and back again.


// put data bus in read mode (local function, static scope)
static uint8_t SID_init_data_bus_read_mode (void) {

  // sets ports for data bus D0-D7 to input

  // initialize ports for data bus
  for (register SID_data_bus_type line = D0; line < Dtop; line++) {  // for all ports
    *SID_data_bus_DDR_ref[line] &= ~(1 << SID_data_bus_bit[line]);   // input
    }

  return 0;

  };


// put data bus in write mode (local function, static scope)
static uint8_t SID_init_data_bus_write_mode (void) {

  // sets ports for data bus D0-D7 to output

  // initialize ports for data bus
  for (register SID_data_bus_type line = D0; line < Dtop; line++) {  // for all ports
    *SID_data_bus_DDR_ref[line] |= (1 << SID_data_bus_bit[line]);    // output
    *SID_data_bus_port_ref[line] &= ~(1 << SID_data_bus_bit[line]);  // bus signal waiting low
    }

  return 0;

  };


// initialize (data and address) busses
uint8_t SID_init_busses (void) {

  // puts busses in write mode (default)
  // and initializes ports for CLK and /CS signals

  // put data bus in write mode
  SID_init_data_bus_write_mode ();
  // TODO: check return value

  // initialize port for CLK signal
  *SID_CLK_signal_DDR_ref |= (1 << SID_CLK_signal_bit);   // output
  *SID_CLK_signal_port_ref |= (1 << SID_CLK_signal_bit);  // CLK signal waiting high

  // initialize port for /CS signal
  *SID_CS_signal_DDR_ref |= (1 << SID_CS_signal_bit);   // output
  *SID_CS_signal_port_ref |= (1 << SID_CS_signal_bit);  // /CS signal waiting high

  return 0;

  };

De rest of the library consists of declarations for all the specific registers of the SID chip, and functions to put the chip to work. Below you find the complete header file. The library will probably become available under an open-source license in the future.


#ifndef AOI_MOSTECHSID_LIB  // include guard
#define AOI_MOSTECHSID_LIB


// MOStech SID library for AVR

// description


// preamble

// headers


// constants


// declarations


// busses

// data bus (lines)
typedef enum SID_data_bus_type {
  D0 = 0,
  D1 = 1,
  D2 = 2,
  D3 = 3,
  D4 = 4,
  D5 = 5,
  D6 = 6,
  D7 = 7,
  Dtop = 8,  // sentinel for loops
  } SID_data_bus_type;

// address bus (lines)
typedef enum SID_address_bus_type {
  A0 = 0,
  A1 = 1,
  A2 = 2,
  A3 = 3,
  A4 = 4,
  RW = 5,    // can also be used as sentinel for loops
  Atop = 6,  // sentinel for loops
  } SID_address_bus_type;

// data bus (registers)
extern const uint8_t SID_data_bus_bit[];
extern volatile uint8_t *SID_data_bus_DDR_ref[];
extern volatile uint8_t *SID_data_bus_port_ref[];
extern volatile uint8_t *SID_data_bus_pin_ref[];

// address bus (registers)
extern const uint8_t SID_address_bus_bit[];
extern volatile uint8_t *SID_address_bus_DDR_ref[];
extern volatile uint8_t *SID_address_bus_port_ref[];

// CLK signal
extern const uint8_t SID_CLK_signal_bit;
extern volatile uint8_t *SID_CLK_signal_DDR_ref;
extern volatile uint8_t *SID_CLK_signal_port_ref;

// /CS signal
extern const uint8_t SID_CS_signal_bit;
extern volatile uint8_t *SID_CS_signal_DDR_ref;
extern volatile uint8_t *SID_CS_signal_port_ref;

// pulse
typedef enum SID_pulse_type {SID_PULSE_POSITIVE, SID_PULSE_NEGATIVE} SID_pulse_type;


// chip

// chip type
typedef enum SID_chip_type {SID_CHIP_6581, SID_CHIP_6582, SID_CHIP_8580} SID_chip_type;

// voices
typedef enum SID_voice_type {
  SID_VOICE1 = 0,
  SID_VOICE2 = 1,
  SID_VOICE3 = 2,
  SID_VOICEtop = 3,  // sentinel for loops
  } SID_voice_type;

// voice register offsets
extern const uint8_t SID_voice_register_offset[];

// voice registers
extern const uint8_t SID_voice_register_Freq_Lo;  // Frequency
extern const uint8_t SID_voice_register_Freq_Hi;
extern const uint8_t SID_voice_register_PW_Lo;    // Pulse Width
extern const uint8_t SID_voice_register_PW_Hi;
  // bits
  extern const uint8_t SID_voice_register_PW_Hi_HI;  // Pulse Width HI
extern const uint8_t SID_voice_register_Control;  // Control Register
  // bits
  extern const uint8_t SID_voice_register_Control_GATE;      // Gate
  extern const uint8_t SID_voice_register_Control_SYNC;      // Sync
  extern const uint8_t SID_voice_register_Control_RING_MOD;  // Ring Modulated
  extern const uint8_t SID_voice_register_Control_TEST;      // Test
  extern const uint8_t SID_voice_register_Control_Triangle;  // Triangle Wave
  extern const uint8_t SID_voice_register_Control_Sawtooth;  // Sawtooth Wave
  extern const uint8_t SID_voice_register_Control_Square;    // Square Wave (Pulse)
  extern const uint8_t SID_voice_register_Control_Noise;     // Noise
extern const uint8_t SID_voice_register_AD;       // Attack/Decay
  // bits
  extern const uint8_t SID_voice_register_AD_DCY;  // Decay
  extern const uint8_t SID_voice_register_AD_ATK;  // Attack
extern const uint8_t SID_voice_register_SR;       // Sustain/Release
  // bits
  extern const uint8_t SID_voice_register_SR_RLS;  // Release
  extern const uint8_t SID_voice_register_SR_STN;  // Sustain

// filter registers
extern const uint8_t SID_filter_register_FC_Lo;     // Cutoff/Center Frequency
  // bits
  extern const uint8_t SID_filter_register_FC_Lo_LO;  // Cutoff/Center Frequency LO
extern const uint8_t SID_filter_register_FC_Hi;
extern const uint8_t SID_filter_register_RES_Filt;  // Resonance of the Filter
  // bits
  extern const uint8_t SID_filter_register_RES_Filt_Filt1;   // Filter Voice 1
  extern const uint8_t SID_filter_register_RES_Filt_Filt2;   // Filter Voice 2
  extern const uint8_t SID_filter_register_RES_Filt_Filt3;   // Filter Voice 3
  extern const uint8_t SID_filter_register_RES_Filt_FiltEX;  // Filter external audio input
  extern const uint8_t SID_filter_register_RES_Filt_RES;     // RES
extern const uint8_t SID_filter_register_Mode_Vol;  // Mode/Volume
  // bits
  extern const uint8_t SID_filter_register_Mode_Vol_VOL;    // Volume
  extern const uint8_t SID_filter_register_Mode_Vol_LP;     // Low Pass
  extern const uint8_t SID_filter_register_Mode_Vol_BP;     // Band Pass
  extern const uint8_t SID_filter_register_Mode_Vol_HP;     // High Pass
  extern const uint8_t SID_filter_register_Mode_Vol_3_OFF;  // Voice 3 off

// misc registers (read-only)
extern const uint8_t SID_misc_register_PX;           // POTX
extern const uint8_t SID_misc_register_PY;           // POTY
extern const uint8_t SID_misc_register_OSC3_Random;  // Voice 3 Oscillator/Random output
extern const uint8_t SID_misc_register_ENV3;         // Voice 3 Envelope Generator output


// chip (state)

// chip voice state
typedef struct SID_chip_voice_state_type {
  uint16_t voice_Freq;          // Frequency
  uint16_t voice_PW;            // Pulse Width (12 bits)
  bool voice_Control_GATE;      // Gate (switch)
  bool voice_Control_SYNC;      // Sync (switch)
  bool voice_Control_RING_MOD;  // Ring Modulated (switch)
  bool voice_Control_TEST;      // Test (switch)
  bool voice_Control_Triangle;  // Triangle Wave (switch)
  bool voice_Control_Sawtooth;  // Sawtooth Wave (switch)
  bool voice_Control_Square;    // Square Wave (Pulse) (switch)
  bool voice_Control_Noise;     // Noise (switch)
  uint8_t voice_ATK;            // Attack (4 bits)
  uint8_t voice_DCY;            // Decay (4 bits)
  uint8_t voice_STN;            // Sustain (4 bits)
  uint8_t voice_RLS;            // Release (4 bits)
  } SID_chip_voice_state_type;

// chip status
typedef enum SID_chip_status_type {
  CHIP_STATUS_OK,
  CHIP_STATUS_ERROR,
  } SID_chip_status_type;

// chip state
typedef struct SID_chip_state_type {
  SID_chip_type chip;
  // voices
  SID_chip_voice_state_type voice1;
  SID_chip_voice_state_type voice2;
  SID_chip_voice_state_type voice3;
  // filter
  uint16_t filter_FC;  // Cutoff/Center Frequency (11 bits)
  bool filter_Filt1;   // Filter Voice 1 (switch)
  bool filter_Filt2;   // Filter Voice 2 (switch)
  bool filter_Filt3;   // Filter Voice 3 (switch)
  bool filter_FiltEX;  // Filter external audio input (switch)
  uint8_t filter_RES;  // RES (4 bits)
  uint8_t filter_VOL;  // Volume (4 bits)
  bool filter_LP;      // Low Pass (switch)
  bool filter_BP;      // Band Pass (switch)
  bool filter_HP;      // High Pass (switch)
  bool filter_3_OFF;   // Voice 3 off (switch)
  // misc (last read)
  uint8_t misc_PX;           // POTX
  uint8_t misc_PY;           // POTY
  uint8_t misc_OSC3_Random;  // Voice 3 Oscillator/Random output
  uint8_t misc_ENV3;         // Voice 3 Envelope Generator output
  // status
  SID_chip_status_type status;
  } SID_chip_state_type;


// defaults


// prototypes

// busses

// initialize clock on pin OC1A
extern uint8_t SID_init_clock_OC1A (void);

// initialize (data and address) busses
extern uint8_t SID_init_busses (void);

// chip (states)

// create chip (state)
extern SID_chip_state_type *SID_create_chip (SID_chip_type chip);

// initialize chip
extern uint8_t SID_init_chip (SID_chip_state_type *chip_state_p);

// remove chip
extern uint8_t SID_remove_chip (SID_chip_state_type *chip_state_p);

// transactions

// read register
extern uint8_t SID_read_register (uint8_t SID_register, uint8_t *value_p);

// read Misc registers
extern uint8_t SID_read_registers_Misc (SID_chip_state_type *chip_state_p);

// write register
extern uint8_t SID_write_register (uint8_t SID_register, uint8_t value);

// set voice
extern uint8_t SID_set_voice (SID_chip_state_type *chip_state_p, SID_voice_type voice,
    uint16_t voice_Freq,          // Frequency
    uint16_t voice_PW,            // Pulse Width (12 bits)
    bool voice_Control_GATE,      // Gate (switch)
    bool voice_Control_SYNC,      // Sync (switch)
    bool voice_Control_RING_MOD,  // Ring Modulated (switch)
    bool voice_Control_TEST,      // Test (switch)
    bool voice_Control_Triangle,  // Triangle Wave (switch)
    bool voice_Control_Sawtooth,  // Sawtooth Wave (switch)
    bool voice_Control_Square,    // Square Wave (Pulse) (switch)
    bool voice_Control_Noise,     // Noise (switch)
    uint8_t voice_ATK,            // Attack (4 bits)
    uint8_t voice_DCY,            // Decay (4 bits)
    uint8_t voice_STN,            // Sustain (4 bits)
    uint8_t voice_RLS);           // Release (4 bits)

// set voice Gate bit on
extern uint8_t SID_set_voice_GATE_on (SID_chip_state_type *chip_state_p, SID_voice_type voice);

// set voice Gate bit off
extern uint8_t SID_set_voice_GATE_off (SID_chip_state_type *chip_state_p, SID_voice_type voice);

// set filter
extern uint8_t SID_set_filter (SID_chip_state_type *chip_state_p,
    uint16_t filter_FC,  // Cutoff/Center Frequency (11 bits)
    bool filter_Filt1,   // Filter Voice 1 (switch)
    bool filter_Filt2,   // Filter Voice 2 (switch)
    bool filter_Filt3,   // Filter Voice 3 (switch)
    bool filter_FiltEX,  // Filter external audio input (switch)
    uint8_t filter_RES,  // RES (4 bits)
    uint8_t filter_VOL,  // Volume (4 bits)
    bool filter_LP,      // Low Pass (switch)
    bool filter_BP,      // Band Pass (switch)
    bool filter_HP,      // High Pass (switch)
    bool filter_3_OFF);  // Voice 3 off (switch)


#endif  // AOI_MOSTECHSID_LIB

Currently this library has only been used to test the correct functioning of the board.

Availability

If you feel like building a similar board yourself, you can download the parts for KiCad here.

The C library currently is not, but probably will become available under an open-source license in the future.

Let me know if you are interested in using this board; I may make it available through Tindie.

What's next

  • derive a streaming/file format from the SID file formats to have this board play some old-school chipmusic
  • add a MicroSD card slot to this board, so it can play tunes from a memory card
  • connect this board to the internet, so it can download tunes to play
0
0
0
s2smodern

Add comment


Security code
Refresh

Tindie badge