Need help with an API

I need to make a program that writes to a register only when that register needs to be changed. I'm not quite sure how to do that.

-Ian

Could you elaborate on that a bit? Do you mean like a shift register device like a 595, a CPU register, or something else? What are you trying to do?

-Paul

Hey Paul,

I'm working with an AY-3-8910 Programable Sound Generator (PSG). It has an 8-bit data input that doubles as the register select (3 channels (coarse and fine tune), noise period, Envelope, Mixer, etc.). I'm using a 595 to write to the data inputs. Instead of just scanning through the registers and setting them if they change or not, how can I only change the data of a register only when I need it to change?

-Ian

That sounds fun. I'm not sure if I understand the question, but from what I just read about the sound chip, it looks like you first write the register's number (to the 595) and bring the sound chip's control lines to the "latch address" setting, then write the data byte to the 595 and bring the control pins to the "latch data" setting. It doesn't look like you need to do any scanning.

While I'm not very familiar with the chip, I imagine it's something like this:

// First, the pins that everything's hooked up to:
#define shiftOutDataPin 2
#define shiftOutClockPin 3
#define shiftOutLatchPin 4
#define soundChipBC1 5
#define soundChipBDIR 7

// The routine you can call to write a register on the sound chip
void WriteSoundChipRegister( byte inRegister, byte inValue )
{
    // First put the register address on the 595
    digitalWrite(shiftOutLatchPin, LOW);
    shiftOut(shiftOutDataPin, shiftOutClockPin, LSBFIRST, inRegister);   
    digitalWrite(latchPin, HIGH);

    // Now tell the chip to read the 595's value as an address
     digitalWrite(soundChipBDIR, LOW); // Bring this low while setting up the BC1
     digitalWrite(soundChipBC1, HIGH);
     digitalWrite(soundChipBDIR, HIGH); // Now bring it high to initiate the write

    // Now put the register's value on the 595
    digitalWrite(shiftOutLatchPin, LOW);
    shiftOut(shiftOutDataPin, shiftOutClockPin, LSBFIRST, inValue);   
    digitalWrite(latchPin, HIGH);

    // Now tell the chip to read the 595's value as a register value
     digitalWrite(soundChipBDIR, LOW); // Bring this low while setting up the BC1
     digitalWrite(soundChipBC1, LOW);
     digitalWrite(soundChipBDIR, HIGH); // Now bring it high to initiate the write

}

You use it by doing something like WriteSoundChipRegister( 3, 123 ); (which would put a value of 123 int register 3).

I hope that was what you were asking.

-Paul

I just read your other post about the chip, and I think I see your question more fully. You mentioned that you only want to write the value when the registers need to change - the question is, when do you want the registers to change? If you don't change them, then the chip will keep emitting the same sound (or lack thereof), so you'd want to think about the design of the loop - would it go through a sequence of frequencies and volumes repeatedly, would the sound be triggered by a push-button or other event, would it just make sound once when you turn the thing on, etc?

-Paul

(BTW, that code I wrote assumes you wire up BC2 high; that was from a quick look at the datasheet, and could be mistaken)

I was never really good at thinking to write functions when I was taking C++. That does make sense though, having something that says register number and value to that register. i will add that to the library that i have to write up for this chip...

Yes, BC2 is hardwired high for this project. As for triggering the sound, I kinda want it to work like a keyboard. Something like one or two octaves of keys with a knob or rotary encoder to shift up and down through the octaves. I'm probably going to have to use an Arduino Mega to pull off the full range of what I want to do (keyboard for channel 1, keyboard for channel 2, 8 or 16 step sequencer for channel 3).

I suppose the problem is how to change, say the Envelope period, only when the knobs set up for the course and fine tune of the envelope period are turned. Or the period of channel 1 when a key is pressed.

Does that make more sense?

-Ian

Yeah that’ll take some software, but I think a decimilanove will be have enough pins if you use a multiplexer for doing some of the reading (like for the keyboard).

So the next step would be getting the keyboard hooked up, but you could just start, say, with two switches that’ll be your “keys”, hooked up to a couple input pins.

To do the overall approach, you’ll want to use a “state machine” array to keep track of each of your keys, and at what point in the notes’ cycles each are in. So a note would have states, which you could organize several different ways: off, attack, decay, sustain, release; or maybe off, stage1, stage2, stage3, etc. In your loop, you could scan the keyboard for keys that have either been pressed or released, then update the state for those keys appropriately (when pressed, a key would go from ‘off’ to ‘attack’, or when released, it would go from ‘sustain’ (or however far it got) to ‘release’. The other states would change by timers, whose length could be editable from your encoders.

Think of something like this:

enum NoteState
{
   eNote_Off,
   eNote_Attack,
   eNote_Decay,
   eNote_Sustain,
   eNote_Release
};

struct NoteInfo
{
    NoteInfo() : state(eNote_Off) {} // Start with the notes off
    NoteState state;            // The state of the note
    long lastStateChangeMS;  // The time in ms of the last state change
    byte pinNumber;            // You only need this if your notes are all on different pins, rather than multiplexed through a single pin
};
// The number of notes
#define NUM_NOTES 24

// The big array of notes
NoteInfo  gNoteInfoArray[NUM_NOTES];

// Times in ms
int gAttackTime = 50;
int gDecayTime = 300;
int gReleaseTime = 500;

void setup()
{
// set up your pins in the array here
}

void loop()
{
  // first scan the keyboard
  int keynum;
  for(keynum = 0; keynum < NUM_NOTES; keynum++ )
  { // ... Insert something like:
     if( gNoteInfoArray[keynum].state == eNote_Off && 
        digitalRead( gNoteInfoArray[keynum].pinNumber ) == HIGH  )
     { // Change the state of the note, and record the time
            gNoteInfoArray[keynum].state = eNote_Attack;
            gNoteInfoArray[keynum].lastStateChangeMS = millis();
           // And maybe call your "turn on note" function...
      }
     // Then do the same for releases
    ...
   }
// next run the state machine to update the chip
  for(keynum = 0; keynum < NUM_NOTES; keynum++ )
  {
       switch( gNoteInfoArray[keynum].state )
       {
            case eNote_Attack:
                      // Update each note that's in the attack phase, and switch it to decay if the time is up
                 break;
            case eNote_Decay:
                      // Update each note that's in the decay phase, and switch it to sustain if the time is up
                 break;
            ...  // and so on
       }
  }
   
}

Anyway, I hope that is a good starting point.

Cheers,
Paul

Wow, yeah that is a nice starting point. I'm not sure I quite understand the "enum" and "struct" bits. Are those in the reference section of the main Arduino site?

-Ian

Oh yeah, those are C / C++ bits. An enum lets you define a type that can take one of several specific value, like "eNote_Off" or "eNote_Attack". You could also do it like this

#define Note_Off 0
#define Note_Attack 1
#define Note_Decay 2
...

See http://www.cplusplus.com/doc/tutorial/other_data_types/#enum

Structs are an easy way to put several related things together into a single type that you can use all together. Since each note has several pieces of info about it, we can put them all together into a struct. A struct is the same thing as a class, more or less.

http://www.cplusplus.com/doc/tutorial/structures/

Cheers, Paul