toyoshim / SoundCortex

A firmware that makes your microcomputer work as a historical sound chip
26 stars 5 forks source link

Is SoundCortex readable? #9

Open Kyuchumimo opened 1 month ago

Kyuchumimo commented 1 month ago

I am using SoundCortexLPC and I can write data perfectly well to it via I2C, but I can't seem to read data.

With PSG registers I always get 0xA1 as answer. With SCC addresses I almost always get 0xA3 as answer. This seems to match the addresses of the IOEXT protocol.

If you build it with IOEXT support enabled, IO ports are assigned as

* 0xA0: PSG address latch
* 0xA1: PSG data read/write
* 0xA2: SCC address latch
* 0xA3: SCC data read/write

I use MicroPython, and the code I use for testing is as follows:

toyoshim commented 1 month ago

Registers here don't mean the emulated chip's register, but a specially assigned register to realize a library specific function, i.e. reading the library version.

https://github.com/toyoshim/SoundCortex/blob/master/src/SCC.c#L108 https://github.com/toyoshim/SoundCortex/blob/master/src/SCC.c#L108

Implementation is here.

Kyuchumimo commented 1 month ago

I want to implement that the mixers of both sound chips are readable as they are registers / memory adresses that need to be bit modified. Is this the right way to do it?

PSG.c

bool PSGRead(uint8_t reg, uint8_t* value) {
  switch (reg) {
  case 0x07:  // mixer
    for (int i = 0; i < 3; ++i) {
      *value |= PSGWork.synth[i].tone << i;
      *value |= PSGWork.synth[i].noise << i + 3;
    }
    break;
  case 0xfe:  // minor version
    *value = 1;
    break;
  case 0xff:  // major version
    *value = 1;
    break;
  default:
    return false;
  }
  return true;
}

SCC.c

bool SCCRead(uint8_t reg, uint8_t* value) {
  switch (reg) {
  case 0xaf:  // mixer
    for (int i = 0; i < 5; ++i) {
      *value |= SCCWork.synth[i].tone << i;
    }
    break;
  case 0xfe:  // minor version
    *value = 1;
    break;
  case 0xff:  // major version
    *value = 1;
    break;
  default:
    return false;
  }
  return true;
}
toyoshim commented 1 month ago

Do you really need to implement this in the SoundCortex side? I think your code should know what was written last time as your code was the single writer in your system? Adding a local cache in your code would work effectively as a query via i2c costs. Also I would like to keep the code for the SoundCortex as small as possible.

Kyuchumimo commented 1 month ago

Not really.

I had already thought about the other way you mention, but since the original sound chips can be read, it seemed like a good idea to have it implemented.

Kyuchumimo commented 1 month ago

I took the trouble to fill in all the remaining registers and memory addresses. I could make a pull request, but since you say that you are not interested in making the microcontroller readable due to memory space issues, it doesn't make much sense, but I'll leave it anyway in case someone else is interested in the future.

PSG.c

// PSG.c

bool PSGRead(uint8_t reg, uint8_t* value) {
  switch (reg) {
  case 0x00:  // TP[7:0] for Ch.A
    *value = PSGWork.channel[0].tp & 0xff;
    break;
  case 0x01:  // TP[11:8] for Ch.A
    *value = PSGWork.channel[0].tp >> 8;
    break;
  case 0x02:  // TP[7:0] for Ch.B
    *value = PSGWork.channel[1].tp & 0xff;
    break;
  case 0x03:  // TP[11:8] for Ch.B
    *value = PSGWork.channel[1].tp >> 8;
    break;
  case 0x04:  // TP[7:0] for Ch.C
    *value = PSGWork.channel[2].tp & 0xff;
    break;
  case 0x05:  // TP[11:8] for Ch.C
    *value = PSGWork.channel[2].tp >> 8;
    break;
  case 0x06:  // NP[4:0]
    *value = PSGWork.noise.np;
    break;
  case 0x07:  // mixer
    for (int i = 0; i < 3; ++i) {
      *value |= PSGWork.synth[i].tone << i;
      *value |= PSGWork.synth[i].noise << i + 3;
    }
    break;
  case 0x08:  // M/L[3:0] for Ch.A
    *value = PSGWork.channel[0].ml;
    break;
  case 0x09:  // M/L[3:0] for Ch.B
    *value = PSGWork.channel[1].ml;
    break;
  case 0x0a:  // M/L[3:0] for Ch.C
    *value = PSGWork.channel[2].ml;
    break;
  case 0x0b:  // EP[7:0]
  case 0x0c:  // EP[15:8]
  case 0x0d:  // CONT/ATT/ALT/HOLD
    // TODO: support envelope.
    break;
  case 0x0e:
  case 0x0f:
    break;
  case 0xfe:  // minor version
    *value = 1;
    break;
  case 0xff:  // major version
    *value = 1;
    break;
  default:
    return false;
  }
  return true;
}

SCC.c

// SCC.c

bool SCCRead(uint8_t reg, uint8_t* value) {
  // Register map is compatible with SCC+.
  if (reg <= 0x9f) {
    int ch = reg >> 5;
    int offset = reg & 0x1f;
    *value = SCCWork.synth[ch].wt[offset];
  } else if (reg <= 0xa9) {
    int ch = (reg - 0xa0) >> 1;
    if (reg & 1)
      *value = SCCWork.channel[ch].tp >> 8;
    else
      *value = SCCWork.channel[ch].tp & 0xff;
  } else if (reg <= 0xae) {
    int ch = reg - 0xaa;
    *value = SCCWork.channel[ch].ml;
  } else if (reg == 0xaf) {
    for (int i = 0; i < 5; ++i) {
      *value |= SCCWork.synth[i].tone << i;
    }
  } else if (reg == 0xfe) {
    // minor version
    *value = 1;
  } else if (reg == 0xff) {
    // major version
    *value = 1;
  } else {
    return false;
  }
  return true;
}