MSX Assembly Page

Yamaha YM2148, FM Sound Synthesizer Unit MIDI interface

The Yamaha YM2148 is used as the MIDI UART for the SFG-01 and SFG-05 FM Sound Synthesizer Unit modules. In these modules it works in tandem with the YM2151 “OPM” sound chip. The YM2148 and YM2151 are accessed through memory mapped I/O at address 3FF0H-3FF6H of the SFG’s ROM. More specifically:

3FF0HWFM address register
3FF1HWFM data register
3FF1HRFM status register
3FF2HR/WExternal mounted keyboard (MK) I/O
3FF4HWExternal IRQ vector
3FF5HR/WMIDI UART data read / write buffer
3FF6HWMIDI UART command register
3FF6HRMIDI UART status register

Note that these are not regular memory locations; the values you read and write are not stored. They are dynamic and behave like I/O ports do. Reading an address can access a different function than writing to it. For example writing to 3FF6H configures the UART, but reading from 3FF6H will not give you the current configuration; in stead it gives status information.

In this document we will focus on the YM2148 MIDI UART’s registers. For more information about the FM registers, refer to the OPM documentation. For more information about the Yamaha external keyboard I/O register, refer to NYYRIKKI’s documentation.

Register overview

Following is an overview of the YM2148’s registers. See the usage section for more information.

MIDI UART IRQ vector (3FF3H, write)

MIDI interrupt vector for use with IM2.

External IRQ vector (3FF4H, write)

External interrupt vector for use with IM2.

Data buffer (3FF5H, read / write)

Data buffer read / write port.

There are separate buffers for reading and writing. The buffer size is 1 byte.

Status register (3FF6H, read)

Returns UART status information.

bit 0TxRDYTransmitter ready(1 = ready)
bit 1RxRDYReceiver ready(1 = ready)
bit 4OEOverrun error(1 = error occurred)
bit 5FEFraming error(1 = error occurred)

The error flags only apply to incoming data.

Command register (3FF6H, write)

Configures the UART.

bit 0TxENTransmit enable(1 = enabled)
bit 1TxIETxRDY interrupt enable(1 = enabled)
bit 2RxENReceive enable(1 = enabled)
bit 3RxIERxRDY interrupt enable(1 = enabled)
bit 4ERError reset command(1 = reset error flags)
bit 5Unknown
bit 6Unknown
bit 7IRInternal reset command(1 = reset UART, other bits should be 0)

Note that some bits act as commands when they are set (notably, bit 4), but others are regular flags. Therefore, you will want to mirror the current value so that you can only change the bits you need to change. You need to mirror it because you can’t read back the value you’ve written.

Detecting the SFG-01 and SFG-05

When inserted in the special side expansion slot in Yamaha MSX computers, the SFG-01/05 will always be present in slot 3-3. However, it can also be inserted into regular cartridge slots with the Yamaha UCN-01 Unit Connector adapter, so you should not make any assumptions about the slot it is in. In stead, you need to search for it.

Like any search routine, you need to iterate through all slots and subslots when they’re expanded, and look for the identification string “MCHFM0” at address 80H. Consult the MBIOS documentation section 1-5 for more details.

NYYRIKKI’s FM-Sound Synthesizer Unit documentation provides an example detection routine.

Initialising and using the YM2148 MIDI UART

Before you start using the YM2148, you first want to reset it. You can do this by writing the value 80H to the command port. After it has been reset, configure it for normal operation by writing the value 05H.

Now you can send and receive data. Use the TxRDY flag in the status register to determine whether the buffer is empty, and then write the data byte you want to transmit to the data buffer. Use the RxRDY flag in the status register to determine whether a data byte is available, and if it is set you can read the value from the data buffer.

If the status register’s overrun error flag is set, it means a byte was received while the previous value hadn’t been read yet. If the framing error flag is set, it means something went wrong during transmission of the value. If one of these errors occur, you can clear the flags by setting the reset error bit in the command register (beware to keep the other bits intact).

Note that the error flags themselves do not raise interrupts, so clearing them is not necessary. You could even choose to ignore them entirely. When an overflow error occurs, the buffer will contain the new byte. When a framing error occurs, the badly received byte will most likely not be placed in the buffer (needs to be confirmed).

The YM2148 can also be configured to generate interrupts when a value has been received or when it’s ready to send.

To generate an interrupt when a byte is received, set the RxRDY interrupt enable flag in the command register. Now as soon as the RxRDY flag goes high, the Z80 will receive an interrupt signal. The interrupt signal is cleared as soon as the value is read from the buffer.

To generate an interrupt when a byte is ready to be sent, set the TxRDY interrupt enable flag in the command register. An interrupt signal will be generated when the TxRDY flag is high. The interrupt signal will be cleared as soon as a value is set to the buffer, and if there are no more bytes to send you can disable the interrupt by disabling the command register flag.

A rather unique feature of the YM2148 is that it can also generate vectors for interrupt mode 2 (IM2). The Z80 interrupt mode 2 combines a base address set in the I register with a vector provided by the device, forming an index into an interrupt handler address table. This allows device interrupts to immediately invoke their handlers rather than query all devices one by one as it is usually done on MSX.

On the YM2148, the vector can be set by the user program. The MIDI UART IRQ vector register allows you to specify the vector used for interrupts originating from the YM2148 itself. The external IRQ vector register is used to specify the vector send for timer interrupts coming from the YM2151.

A word of warning though: IM2 falls outside of the normal operation of MSX computers. It works, and is used sometimes, but it is not the way things are normally done and has a couple of pitfalls as a result. Also, the usage documentation is likely incomplete; it seems that command register bits 5 and 6 are involved, but some further investigation is needed to figure out the details.

So, do not feel obliged to use the IM2 mode just because it supports it. For first-time users, I would recommend to start with normal IM1 mode interrupt handling, and only move to IM2 mode when there is a need for it.