Macintosh Classic CRT with modern linux computer – Part 2

This article will cover the software aspect of my Macintosh Classic project. The final solution is running on a BeagleBone Black, using it’s programmable realtime unit. If you want to learn more about PRU programming, you can do so here. If you haven’t done so already, I recommend you to read the other parts of this series. You can find the table of contents at the end of this article.

You might ask yourself what the code does, it basically loads an image into the BBB’s DDR memory and then sends the data over to the display with the waveforms discussed in Part 1 of the series:

macintosh_cat
Figure 1: Macintosh Classic displays an image of a cat.

Currently I’ll not upload the complete code, because I’m not completely satisfied with it and it still has an error or two, but I’ll upload it completely as soon as it is perfect. One of the things that bothers is the noise generated at the right hand side of the image. This comes due to slightly imprecise timing of the video signal. Also the current version of the code generates 16 black bars on the screen running vertically over the screen.

I managed to fix the issues and the complete code is now available for download at github!
Here is an image of the updated software displaying the cat image:

IMG_1721
Figure 1.1: Fixed the black-bar-error.

Difficulties during development

I ran into different difficulties while working on this project. But dificulties and problems are a great way to learn, and I’m happy about every little mistake I made during the development process, because I learned a lot beacause of them. Anyways I want to tell you about the biggest mistakes I made, so you don’t have to run into all the problems:

The first major problem was to select the wrong platform for this project. I first tried to realize it with a Raspberry Pi which did not work out very well. Please don’t misunderstand me: The Pi is a very nice (and cheap) little machine, but unfortunately it’s simply not capable of doing stuff in realtime while having an embedded linux system running. But I learned something about precise signal generation with the Pi, useful for let’s say controlling servos.

The first version took about two, maybe three hours with breaks, to develop. The program itself is neither that complicated, nor very long. Anyways it was not working (I’ll show you the part that caused this program in the listing below) properly. It gave me something like this:

IMG_1505
Figure 2: The CRT only shows the same image fragment 16 times, not the whole image

I tried to find the cause for this error, but I simply couldn’t. I calculated everything over and over again and the code seemed completely correct but the output wasn’t. After some hours of calculating, changing the code, compiling and testing it over and over again I decided it would be a great idea to delete the whole code (and the github repo it was in) and to start all over. At this point I thought that the problem was in the host program and it had something to do with writing the data to the RAM. I had a very nice solution which was pretty complicated to read (but short and elegant), so I tried to write a much simpler (and longer) solution, which I did. After I made up a simpler solution (which basically did the same thing as the complicated one) I tested it and it still did not work. So I recalculated each address and pixel again and then it finally came to my mind that it might be the PRU program, that caused that error. And then I found it and I felt so stupid afterwards. It was a simple typo:

In the section that reads one line of pixel data from the DDR memory I had an address I was incrementing after each load operation by 0x04 instead of 0x40. Which made a difference of 60 bytes. So that was the error that almost made me go insane. A simple typo. What I want to say: Sometimes your eyes make you see what you want them to see. Try to read every value carefully and don’t delete your code.

About the code

Instead of uploading the whole code, I’ll explain how the code works and what it does. This program also consists of two separate applications and one device tree overlay:

The device tree
This file describes what pins will be used by the PRU program. I’ll not cover this topic here,  if you want to read more about it, you can do so here.

The host application
This part of the program does the memory management. It is written in C and allocates the necessary memory to hold one frame in, writes new frames into the memory and notifies the PRU program when a frame is ready to be drawn. It is also needed for starting and halting the PRU and for general user IO.

The following snippet shows how the host program writes the image data to the the BBB’s DDR memory. Please note that constant variable values and memory allocation are not included, it is just a snippet. The complete code will be available for download on github.

void writeFrameToVRAM(void)
{
    // Write each line of the video data to
    // the memory.

    for(int i = 0; i < DISP_HEIGHT; i++)
    {
           // Compose the address to write the current
           // line to.
	   volatile unsigned long *data_addr = vram + 
                                               DDR_OFFSET +
                                               LINEBYTES * i;

           // Optional step to make the code easier
           // to understand: Get one line of pixel data
           // out of the complete screen data
           // One line consists of 64 bytes (64*8 = 512 pixels)
	   volatile char lineData[64];
	   int byteCounter = 0;

           // Pack together one line of video data
           // into a seperate array
	   for(int o = 0; o < 64; o++)
		lineData[o] = test_image[i*64+o];
		
	    // Now split the created array into 16 chunks
            // of 32 bits each (This is the max. address
            // length on the PRU, so it is also the max.
            // size of data you can fit into one register
            // in your PRU program)
            for(int u = 0; u < LINE_REGISTERS; u++)
	    {
	        volatile char registerBytes[4];
		for(int c = 0; c < REGISTER_BYTES; c++)
		{
		    volatile char currByte;
		    if(INVERT_COLORS == 1)
		        currByte = ~lineData[byteCounter];
		    else
			currByte = lineData[byteCounter];
				
		    registerBytes[c] = currByte;
		    byteCounter++;
		}
			
		// Now cast the register data array to a
                // 32-bit data type (i.e. unsigned long)
		volatile unsigned long regData = 0l;
		
                // Watch out for the correct byte order!
		regData += registerBytes[0] << 24;
		regData += registerBytes[1] << 16;
		regData += registerBytes[2] << 8;
		regData += registerBytes[3];
		
		// Now simply write the register data to the
                // DDR memory and increase the data address.
		*data_addr = regData;
		data_addr++;
	    }
      }
}

The PRU application
This is the delicate bit. It reads the video data and then bit bangs it over to the CRT’s display hardware. It also generates the horizontal and vertical synchronization signals that are needed to build an image on the screen. This section needs to be perfectly timed, which can easily be missed. I calculated the exact timings, but this part is really easy to mess up and I have to tweak the timings a little bit to get a perfect result.

The first snippet shows how the data is read out of the DDR memory:

.macro READVRAMDATA
	LBBO    r4, r27, 0, 64
	ADD	r27, r27, 0x00000040
.endm

Even though this is just a two line macro this was the bit that caused me a lot of problems during development. the ADD instruction’s third parameter was misspelled, so the address was incremented by a wrong value and thus a wrong section of the memory was read. There is nothing special to say about this macro, just that it reads 64 bytes from the DDR memory (at the address stored in r27) and stores the data to the registers r4 to r20. The address is incremented afterwards. The following macro shows the bit-banging part of the application:

.macro WRITEREG
.mparam reg
	MOV    r21, 31
	QBA    WRITEPIXEL
	WRITEPIXEL:
		SUB        r21, r21, 1
		DELAY_NS   85;
		QBBS	   WRITEPIXEL_HIGH, reg.t31
		QBBC	   WRITEPIXEL_LOW, reg.t31
	WRITEPIXEL_HIGH:
		SET	   r30.t0
		LSL	   reg, reg, #1
		QBNE	   WRITEPIXEL, r21, 0
                QBA        WRITEPIXEL_END
	WRITEPIXEL_LOW:
		CLR	   r30.t0
		LSL	   reg, reg, #1
		QBNE	   WRITEPIXEL, r21, 0
        WRITEPIXEL_END:
                NOP
.endm

Update (See bold lines in the snippet above): I fixed the bug where the display showed vertical black lines across the image. This was due to the fact that I forgot to tell the processor to jump to the end of the WRITEREG macro after it had written all the bits of one one register. So if the last pixel was white, the display put out an additional black pixel afterwards. The new code jumps to the end (thus leaving the WRITEPIXEL_LOW untouched) when the last bit was output.

This macro bursts out one register at a time. It simply sets the output register’s bit zero to the value of the bit that is currently being sent out and then waits a specified time until it repeats the process. This is done 32 times in total, so each pixel of the register is sent out once. There might be a better solution for this (like using logical functions) and I suspect the cause of the timing issues here, so I have to improve this section before I publish the final code. Anyways the output generated by the PRU looks like shown in fig. 3:

macintosh_crt_pixeldata_somedata
Figure 3: The video data (Ch1, yellow) and the HSYNC signal (Ch2, cyan)

Basically this is all the PRU program does. It reads out the data for one line and then sends out each register the data is stored. It also generates the HSYNC and VSYNC signal according to the timing table discussed in Part 1 of the series. It generates the two signals the same way I discussed it in this article.

The complete code

I’ll update this section as soon as I fixed the issues described above.
The complete code is available for download at github!

Table of contents

Part 0 – The story behind the project
Part 1 – The CRT
Part 2 – The Software (You are here)
Part 3 – Additional Thoughts

comment-banner

Advertisements

Leave your two cents, comment here!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s