If you have been following along with my Retro Challenge 2024 posts, you’ll know that I have designed and built a rotary encoder module for the RC2014 computer.
I wanted to put together all my learning and build a music player for the RC2014. This will run using my RC2014 Classic 2 computer. It uses the LCD Driver Module, and the YM2149 Sound Card Module from Z80Kits. In addition, it also uses my ROM board, and of course the Rotary Encoder Module.
The plan is to encode a few of the example tunes provided with the SDK for the YM2149 Sound Card into one program. I will then display the tunes on the LCD, and use the Rotary Encoder Module to allow the different tunes to be selected and played. The ROM board is used to allow me easier access to the SCM ROM to load my assembled program.
I used the example code I wrote earlier for the LCD to display the 3 track titles. I also used the example code for the rotary encoder to move up and down inside the menu.
Moving an arrow in the LCD
I did have to develop some new functionality to move an error up and down in the LCD. I didn’t want to rewrite the whole screen, just select a character and either write an arrow or a space to that location.
I firstly refactored my Z80 code to send a command or data to the LCD screen. This was based on a suggestion I received on social media. The code sends a byte then waits for the LCD to say it’s ready for the next byte.
LCD_R EQU 218
LCD_D EQU 219
; Send a command byte to the LCD.
; A - Command in
; A, C registers used.
send_command:
out (LCD_R),a
jr lcd_busy
; Send a data byte to the LCD
; A - Byte in
; A, C registers used.
send_data:
out (LCD_D),a
lcd_busy:
in a,(LCD_R)
rlca
jr c,lcd_busy
ret
The LCD layout isn’t sequential in the 4×20 character display I am using. Line 3 follows line 1 by 20 characters. Line 2 is then offset by 64 characters, followed by line 4 at 84 characters.
Line | Offset |
1 | 0 |
2 | 64 |
3 | 20 |
4 | 84 |
The command to move an arrow is the offset from the above table OR’d with $80 (which is the command to set the DDRAM in the LCD module). So to draw an arrow on line 2, and to wipe line’s 3 and 4 I could use the following code.
ld a,$80|64 ; $80 is the set address command, 64 is the offset.
call .draw_arrow
ld a,$80|20
call .wipe_arrow
ld a,$80|84
call .wipe_arrow
.draw_arrow:
call send_command
; show an arrow
ld a,%01111110 ; this is the arrow character from the manual
call send_data
ret
.wipe_arrow:
call send_command
; show a space
ld a,' '
call send_data
ret
Sending Debug Information To The Serial Port
While I was developing the code I needed to send some debugging information to the serial port to make sure I knew I was moving through the lines correctly.
The code to play the selected track had a small piece of code that could send to the serial port.
TX:
push af
.txbusy
in a,($80) ; read serial status
bit 1,a ; check status bit 1
jr z, .txbusy ; loop if zero (serial is busy)
pop af
out ($81), a ; transmit the character
ret
To send a single character I could load the character into register a and then call TX. So to send the character ‘R’ to the serial port, I could do the following.
ld a,'R'
call TX
I was keeping track of the current track as either 1, 2, or 3. To send this to the serial port I needed to convert the number into it’s ASCII character code. This turned out to be very simple due to the way the designed of ASCII chose the code for the digits. The ASCII code for $1 is $31, for $2 is $32, and $3 is $33. I just have to OR $30 to the value to convert it to ASCII. So to send the character ‘1’ to the serial port, I could do the following.
ld a,$1
or $30
call TX
Building The Music Player
Thankfully, the work I’ve completed over the course of the Retro Challenge month worked well together. I was able to tweak the existing PTPlayer example code from the sound module to insert my rotary encoder detection routines into the main loop. In the loop I would then move the arrow in the display if necessary, and change the current track if the rotary encoder was pressed.
Here is a video of the music player in action.
The one thing I have found is that if I turn the rotary encoder too fast, it doesn’t always correctly pick up the turn. This is because there is a lot more happening in the loop playing the music so the encoder isn’t being sampled as frequently as in my test code. A possible solution to this could be to look at using interrupts, but I won’t have time to do this before the end of the Retro Challenge.