Controlling a PG320240H-P9 with a dsPIC33FJ128GP802

Sunday, 21st March 2010

In a previous entry I mentioned that I had purchased a PG320240H-P9 graphical LCD. This is a 320×240 white-on-blue pixel display, and it does not have an on-board controller or RAM. To display something on it you need to constantly refresh it with picture data; in this instance, sending four pixels at a time, starting from the top left and working from left to right, top to bottom — a bit like the scanning pattern of a CRT monitor.

FFC adaptor.

Connecting a circuit to the LCD is made slightly more tricky by its use of a 16-pin 1mm flexible flat cable. To get around this I soldered together an adaptor using a suitable FCC connector, pin strip, piece of stripboard and a fairly excessive quantity of hot melt adhesive. Even more tricky was the lack of a suitable datasheet for the LCD. After some digging I located this one for the PG320240WRM-HNNIS1 — it's slightly different, but contains timing diagrams and specifications that seem to work with the LCD I bought. One thing I still haven't worked out is the contrast adjustment; a 5K variable resistor between 0V and the relevant pin seems to have had the best results thus far. A helpful webpage, Graphical LCD controller for ST8024+ST8016 based displays, has a plain English description of how to drive the LCD, though as far as I'm aware the M pin should have its logic level toggled every frame, giving you a "glass" frequency of half of the refresh rate, not 200Hz-400Hz. The lack of a proper datasheet makes these things a little complicated!

LCD driven by an ATmega644P, showing a picture of a cat.

My first attempt to drive the LCD involved an ATmega644P, a microcontroller with 64KB of flash ROM and 4KB of RAM. The above photo shows it displaying a picture of a cat, which was stored in ROM and output using the following code:

#include <stdint.h>
#include <avr/io.h>
#include <avr/pgmspace.h>

#define LCD_FLM   (6)
#define LCD_M     (5)
#define LCD_C1    (4)
#define LCD_C2    (3)
#define LCD_D_OFF (2)

#define LCD_CONTROL_PORT (PORTC)
#define LCD_CONTROL_PIN  (PINC)
#define LCD_CONTROL_DDR  (DDRC)

#define LCD_DATA_PORT    (PORTA)
#define LCD_DATA_PIN     (PINA)
#define LCD_DATA_DDR     (DDRA)

#include "cat.h"

int main(void) {

    // Make control pins outputs.
    LCD_CONTROL_DDR |= _BV(LCD_FLM) | _BV(LCD_M) | _BV(LCD_C1) | _BV(LCD_C2) | _BV(LCD_D_OFF);
    
    // Make data pins outputs.
    LCD_DATA_DDR |= 0b1111;
    
    // Enable the LCD.
    LCD_CONTROL_PORT |= _BV(LCD_D_OFF);
    
    for(;;) {
    
        // Toggle the M pin to provide the LCD AC voltage.
        LCD_CONTROL_PIN |= _BV(LCD_M);
        
        const uint8_t* picture_ptr = cat_picture;
    
        // Scan 240 rows in the image.
        for (uint8_t row = 0; row < 240; ++row) {
        
            // Begin the line.
            LCD_CONTROL_PIN |= _BV(LCD_C1);
            LCD_CONTROL_PIN |= _BV(LCD_C1);

            if (row < 2) LCD_CONTROL_PIN |= _BV(LCD_FLM);
            
            // Send 40 eight-bit words.
            for (uint8_t column = 0; column < 40; ++column) {
                LCD_DATA_PORT = pgm_read_byte(picture_ptr) >> 4;
                LCD_CONTROL_PIN |= _BV(LCD_C2);
                LCD_CONTROL_PIN |= _BV(LCD_C2);
                LCD_DATA_PORT = pgm_read_byte(picture_ptr);
                LCD_CONTROL_PIN |= _BV(LCD_C2);
                LCD_CONTROL_PIN |= _BV(LCD_C2);
                ++picture_ptr;
            }
        }
    }

}

A 320×240 display has 76,800 pixels, and if you store each pixel as a single bit (so eight pixels per byte) you need 9600 bytes to store a complete frame, which clearly won't fit in the 4KB offered by the ATmega644P. Rather than upgrade to an AVR with more memory, I jumped to the dsPIC33FJ128GP802, a 16-bit microcontroller with 16KB of RAM. As well as quadrupling the RAM from the ATmega644P it also doubles the program memory (128KB from 64KB) and speed (40 MIPS from 20 MIPS). When working with AVRs I'd been using a slow home-made serial programmer, and rather than continue with this sorry state of affairs (lack of debugging capabilities is never fun, especially when it takes over a minute to program the microcontroller) I treated myself to a PICkit 3 Debug Express.

dsPIC33FJ128GP802 controlling the PG320240H-P9

The above photo shows the LCD connected to the microcontroller as well as the PICkit 3. The dsPIC33FJ128GP802 requires a voltage supply from 3.0V to 3.6V, not the 5V I am used to, so to power it I have put two IN4001 rectifier diodes in series with the 5V regulator output. Each diode incurs a voltage drop of 0.7V, producing 3.6V for the rest of the circuit. The LCD is powered from the main 5V supply, but it seems happy with the 3.6V logic "high" from the dsPIC.

The LCD is connected to the dsPIC as follows:

  • FLM to RB15
  • M to RB14
  • C1 to RB13
  • C2 to RB12
  • /D_OFF to RB11
  • D0~D3 to RA0~RA3

A 10K resistor is included between /D_OFF and ground. This is very important, as it holds the /D_OFF line low when RB11 is floating (e.g. during reset), forcing the display off — if the display is powered, but is not being actively refreshed, the LCD panel can become overloaded and damaged.

I have knocked together a simple demo that shows a few different graphics on the LCD. The LCD is constantly refreshed by an interrupt service routine that runs in the background, leaving some CPU time to the user program. As there is only enough RAM for a single frame buffer, animation has to be quite simple to avoid flickering, but I've still managed to include my favourite spinning cube.

The project can be downloaded here. I'm still getting to grips with the dsPIC series; the code is likely to be pretty awful, and I still have a problem where the dsPIC resets itself every couple of minutes (I'm not really sure if this is a software or hardware issue). Still, it's a start, and I hope that I can use this LCD as the display for my Z80 computer project.

Update: Having seen this post, the chap who originally suggested that I investigate the dsPIC33FJ128GP802 sent me an email with some advice, chiefly about my poor power supply, missing decoupling capacitors and use of an electrolytic capacitor on the VCAP pin. I have since replaced the two rectifier diode affair with a proper 3.3V regulator for the power supply, added a decoupling capacitor across AVDD/AVSS and moved the decoupling capacitor between VDD/VSS closer to the microcontroller. I have also ordered some tantalum capacitors to replace the electrolytic one. A bit of debugging found that the watchdog timer is responsible for the spurious resets; I have disabled it in the code for the time being, which has stopped the resets.

Adding more stereoscopic modes to Quake II's OpenGL renderer

Thursday, 11th March 2010

Quake II's OpenGL renderer supports stereoscopic rendering providing you own a video card that has the requisite hardware and driver support ("quad-buffered" OpenGL – rather than a single front and back buffer you have two front buffers and two back buffers, one for each eye). Not owning such a video card I decided to have a go at adding some other stereoscopic rendering modes that worked with regular hardware.

The four new stereoscopic rendering modes
The four new stereoscopic rendering modes

The ability to enable or disable drawing with a particular colour component in OpenGL makes implementing an anaglyph mode very simple – temporarily switch off red when drawing the view from one eye and temporarily switch off blue and green when drawing the view from the other to produce a final image that can be used with red/cyan 3D glasses. A new string console variable cl_stereo_anaglyph_colors can be amended to set the colour of your particular glasses, e.g. mg for magenta/green.

By drawing a mask to the stencil buffer before rendering one can easily add "interleaved" modes; there's the standard row interleaved format, but I've also added column and pixel interleaved formats.

It looks like the stereoscopic OpenGL code was started but not finished in Quake II; there were a number of odd bugs, such as the viewport position being changed instead of the camera position when drawing the left and right eye views (producing two views that were offset in 2D, not 3D). A snippet of code hints towards why this may be:

#if 0 // commented out until H3D pays us the money they owe us
    GL_DrawStereoPattern();
#endif

H3D manufactured a VGA adaptor for 3D glasses which relied on a special pattern being displayed on the screen to enable it and switch it to the correct mode rather than let the user do so. H3D went bust toward the end of 1998, so I guess id software never got their money and that function has been commented out ever since.

Anaglyph rendering example

The new binaries can be downloaded from the Stereo Quake page, and the source can be found on Google Code.

ATmega644P CHIP-8/SCHIP interpreter

Sunday, 7th March 2010

In an attempt to solve the screen resolution problem issue I've bought a very cheap 320×240 pixel graphical LCD – a PG320240H-P9 on eBay for $24. Part of the reason for its cheapness may be down to its the lack of a controller; you need to constantly refresh the LCD with pixel data yourself (easier to use modules have integrated controllers that refresh the display for you from some on-board RAM). If I manage to get it working I'll have a 128×64 pixel graphical LCD going spare – finding a use for it could make an interesting project.

I have a bit of a soft spot for the CHIP-8 programming language, having previously written an few implementations. The CHIP-8 environment requires just under 3.5KB of RAM, and my recent investment in an ATmega644P boasting 4KB of RAM provided me with a microcontroller that was up to the task.

Complete CHIP-8/SCHIP system

Beyond the ATmega644P and LCD the hardware is pretty simple; a potentiometer is provided to adjust the speed of the interpreter when it's running, from 1/8th speed up to 8× speed. Sound is output using a piezo transducer, which I've taped to the hard plastic lid from a tube of chocolates to amplify it. Games rely on a 4×4 hex keypad for input, and as I do not have a 4×4 keypad – hex or otherwise – I assembled my own on another breadboard. I don't even have sixteen switches of the same type, hence the mixture in the above photo. A schematic of the hardware can be downloaded in PDF format.

Game menu

When you reset the circuit a list of all of the programs stored on the microcontroller is shown on the LCD. The 64KB of flash memory on the ATmega644P is enough to store the code for the interpreter and all of the CHIP-8 and SCHIP games available on the Internet. For a change I've decided to have a go at designing a variable width font rather than use one of my existing fixed-width fonts; I don't think it looks too shabby.

'Joust' summary

When a game has been selected a (gramatically incorrect) summary of the game is shown. To the right of the screen is a 4×4 grid informing the player which key does what; arrows for directional controls, a diamond for "fire" or confirmation actions and a tick/cross for yes/no input. There doesn't seem to be any particular convention for keypad input in CHIP-8/SCHIP games, which makes this feature invaluable!

Ant

Click here to download the source code.

Thinking about CP/M

Wednesday, 24th February 2010

It's been some time since I worked on my Z80 computer project, but the recent electronics projects I've completed have got me thinking about it again.

I did record a video to demonstrate the basic parts of the computer and some of its flaws a few months ago, which can be seen above. However, I'm now thinking of a more radical redesign than fixing the I/O board's shortcomings.

One of the reasons for my lack of motivation is that even if I did get something working I wouldn't have much software to run on it; it would be a lot of work to write software that only ran on that one particular machine. BBC BASIC helps somewhat, but an even better solution would be to model the device on an existing machine and run its operating system on it.

Fortunately, there was a popular operating system for the 8080 (and, by extension, the Z80) – CP/M. This is a very simple operating system that inspired DOS. Crucially, it is not hardware-specific, the source code is available and there is a wide range of software available for it, including BBC BASIC.

CP/M is made up of three main components. At the highest level is the Console Command Processor, or CCP. This provides the command-line interface, a handful of built-in commands and handles loading and executing external programs. It achieves this with the aid of the Basic Disk Operating System, or BDOS, which exposes a number of useful routines for a variety of tasks, such as outputting text to the display, searching for files on the disk or reading console input.

Both of the above components are machine-independent – they simply need to be copied to the correct address in RAM when the computer starts. Relocating them to a particular address requires setting a single value in their respective source files and reassembling them, which is nice and easy. It's the third component – the Basic I/O System, or BIOS – that requires a bit more work. This is the only part that is tailored to a particular machine's hardware, and my current implementation is listed below.

CCP    = $DC00
BDOS   = $E406
BIOS   = $F200

IOBYTE = $0003
CDISK  = $0004

DMAAD  = $0008
CTRACK = $000A
CSEC   = $000C

.org BIOS

	jp BOOT    ; COLD START
WBOOTE
	jp WBOOT   ; WARM START
	jp CONST   ; CONSOLE STATUS
	jp CONIN   ; CONSOLE CHARACTER IN
	jp CONOUT  ; CONSOLE CHARACTER OUT
	jp LIST    ; LIST CHARACTER OUT
	jp PUNCH   ; PUNCH CHARACTER OUT
	jp READER  ; READER CHARACTER OUT
	jp HOME    ; MOVE HEAD TO HOME POSITION
	jp SELDSK  ; SELECT DISK
	jp SETTRK  ; SET TRACK NUMBER
	jp SETSEC  ; SET SECTOR NUMBER
	jp SETDMA  ; SET DMA ADDRESS
	jp READ    ; READ DISK
	jp WRITE   ; WRITE DISK
	jp LISTST  ; RETURN LIST STATUS
	jp SECTRAN ; SECTOR TRANSLATE

DISKPARAM
	.dw $0000  ; No sector translation.
	.dw $0000  ; Scratch
	.dw $0000  ; Scratch
	.dw $0000  ; Scratch
	.dw DIRBUF ; Address of a 128-byte scratch pad area for directory operations within BDOS. All DPHs address the same scratch pad area.
	.dw DPBLK  ; Address of a disk parameter block for this drive. Drives with identical disk characteristics address the same disk parameter block.
	.dw CHK00  ; Address of a scratch pad area used for software check for changed disks. This address is different for each DPH.
	.dw ALL00  ; Address of a scratch pad area used by the BDOS to keep disk storage allocation information. This address is different for each DPH.

DIRBUF
	.fill 128
	
DPBLK           ; DISK PARAMETER BLOCK, COMMON TO ALL DISKS
	.DW 26      ; SECTORS PER TRACK
	.DB 3       ; BLOCK SHIFT FACTOR
	.DB 7       ; BLOCK MASK
	.DB 0       ; NULL MASK
	.DW 242     ; DISK SIZE-1
	.DW 63      ; DIRECTORY MAX
	.DB 192     ; ALLOC 0
	.DB 0       ; ALLOC 1
	.DW 16      ; CHECK SIZE
	.DW 2       ; TRACK OFFSET

CHK00
	.fill 16

ALL00
	.fill 31

; =========================================================================== ;
; BOOT                                                                        ;
; =========================================================================== ;
; The BOOT entry point gets control from the cold start loader and is         ;
; responsible for basic system initialization, including sending a sign-on    ;
; message, which can be omitted in the first version.                         ;
; If the IOBYTE function is implemented, it must be set at this point.        ;
; The various system parameters that are set by the WBOOT entry point must be ;
; initialized, and control is transferred to the CCP at 3400 + b for further  ;
; processing. Note that register C must be set to zero to select drive A.     ;
; =========================================================================== ;
BOOT
	xor a
	ld (IOBYTE),a
	ld (CDISK),a
	jp GOCPM

; =========================================================================== ;
; WBOOT                                                                       ;
; =========================================================================== ;
; The WBOOT entry point gets control when a warm start occurs.                ;
; A warm start is performed whenever a user program branches to location      ;
; 0000H, or when the CPU is reset from the front panel. The CP/M system must  ;
; be loaded from the first two tracks of drive A up to, but not including,    ;
; the BIOS, or CBIOS, if the user has completed the patch. System parameters  ;
; must be initialized as follows:                                             ;
;                                                                             ;
; location 0,1,2                                                              ;
;     Set to JMP WBOOT for warm starts (000H: JMP 4A03H + b)                  ;
;                                                                             ;
; location 3                                                                  ;
;     Set initial value of IOBYTE, if implemented in the CBIOS                ;
;                                                                             ;
; location 4                                                                  ;
;     High nibble = current user number, low nibble = current drive           ;
;                                                                             ;
; location 5,6,7                                                              ;
;     Set to JMP BDOS, which is the primary entry point to CP/M for transient ;
;     programs. (0005H: JMP 3C06H + b)                                        ;
;                                                                             ;
; Refer to Section 6.9 for complete details of page zero use. Upon completion ;
; of the initialization, the WBOOT program must branch to the CCP at 3400H+b  ;
; to restart the system.                                                      ;
; Upon entry to the CCP, register C is set to thedrive;to select after system ;
; initialization. The WBOOT routine should read location 4 in memory, verify  ;
; that is a legal drive, and pass it to the CCP in register C.                ;
; =========================================================================== ;
WBOOT

GOCPM
	ld a,$C3      ; C3 IS A JMP INSTRUCTION
	ld ($0000),a  ; FOR JMP TO WBOOT
	ld hl,WBOOTE  ; WBOOT ENTRY POINT
	ld ($0001),hl ; SET ADDRESS FIELD FOR JMP AT 0
	
	ld ($0005),a  ; FOR JMP TO BDOS
	ld hl,BDOS    ; BDOS ENTRY POINT
	ld ($0006),hl ; ADDRESS FIELD OF JUMP AT 5 TO BDOS

	ld bc,$0080   ; DEFAULT DMA ADDRESS IS 80H
	call SETDMA

	ei            ; ENABLE THE INTERRUPT SYSTEM
	ld a,(CDISK)  ; GET CURRENT DISK NUMBER
	ld c,a        ; SEND TO THE CCP
	jp CCP        ; GO TO CP/M FOR FURTHER PROCESSING

; =========================================================================== ;
; CONST                                                                       ;
; =========================================================================== ;
; You should sample the status of the currently assigned console device and   ;
; return 0FFH in register A if a character is ready to read and 00H in        ;
; register A if no console characters are ready.                              ;
; =========================================================================== ;
CONST
	out (2),a \ ret

; =========================================================================== ;
; CONIN                                                                       ;
; =========================================================================== ;
; The next console character is read into register A, and the parity bit is   ;
; set, high-order bit, to zero. If no console character is ready, wait until  ;
; a character is typed before returning.                                      ;
; =========================================================================== ;
CONIN
	out (3),a \ ret

; =========================================================================== ;
; CONOUT                                                                      ;
; =========================================================================== ;
; The character is sent from register C to the console output device.         ;
; The character is in ASCII, with high-order parity bit set to zero. You      ;
; might want to include a time-out on a line-feed or carriage return, if the  ;
; console device requires some time interval at the end of the line (such as  ;
; a TI Silent 700 terminal). You can filter out control characters that cause ;
; the console device to react in a strange way (CTRL-Z causes the Lear-       ;
; Siegler terminal to clear the screen, for example).                         ;
; =========================================================================== ;
CONOUT
	out (4),a \ ret

; =========================================================================== ;
; LIST                                                                        ;
; =========================================================================== ;
; The character is sent from register C to the currently assigned listing     ;
; device. The character is in ASCII with zero parity bit.                     ;
; =========================================================================== ;
LIST
	out (5),a \ ret

; =========================================================================== ;
; PUNCH                                                                       ;
; =========================================================================== ;
; The character is sent from register C to the currently assigned punch       ;
; device. The character is in ASCII with zero parity.                         ;
; =========================================================================== ;
PUNCH
	out (6),a \ ret

; =========================================================================== ;
; READER                                                                      ;
; =========================================================================== ;
; The next character is read from the currently assigned reader device into   ;
; register A with zero parity (high-order bit must be zero); an end-of-file   ;
; condition is reported by returning an ASCII CTRL-Z(1AH).                    ;
; =========================================================================== ;
READER
	out (7),a \ ret

; =========================================================================== ;
; HOME                                                                        ;
; =========================================================================== ;
; The disk head of the currently selected disk (initially disk A) is moved to ;
; the track 00 position. If the controller allows access to the track 0 flag  ;
; from the drive, the head is stepped until the track 0 flag is detected. If  ;
; the controller does not support this feature, the HOME call is translated   ;
; into a call to SETTRK with a parameter of 0.                                ;
; =========================================================================== ;
HOME
	ld bc,0
	jp SETTRK

; =========================================================================== ;
; SELDSK                                                                      ;
; =========================================================================== ;
; The disk drive given by register C is selected for further operations,      ;
; where register C contains 0 for drive A, 1 for drive B, and so on up to 15  ;
; for drive P (the standard CP/M distribution version supports four drives).  ;
; On each disk select, SELDSK must return in HL the base address of a 16-byte ;
; area, called the Disk Parameter Header, described in Section 6.10.          ;
; For standard floppy disk drives, the contents of the header and associated  ;
; tables do not change; thus, the program segment included in the sample      ;
; CBIOS performs this operation automatically.                                ;
;                                                                             ;
; If there is an attempt to select a nonexistent drive, SELDSK returns        ;
; HL = 0000H as an error indicator. Although SELDSK must return the header    ;
; address on each call, it is advisable to postpone the physical disk select  ;
; operation until an I/O function (seek, read, or write) is actually          ;
; performed, because disk selects often occur without ultimately performing   ;
; any disk I/O, and many controllers unload the head of the current disk      ;
; before selecting the new drive. This causes an excessive amount of noise    ;
; and disk wear. The least significant bit of register E is zero if this is   ;
; the first occurrence of the drive select since the last cold or warm start. ;
; =========================================================================== ;
SELDSK
	ld hl,DISKPARAM
	ld a,c
	or a
	ret z
	ld hl,$0000 ; Only disc 0 is supported.
	ret

; =========================================================================== ;
; SETTRK                                                                      ;
; =========================================================================== ;
; Register BC contains the track number for subsequent disk accesses on the   ;
; currently selected drive. The sector number in BC is the same as the number ;
; returned from the SECTRAN entry point. You can choose to seek the selected  ;
; track at this time or delay the seek until the next read or write actually  ;
; occurs. Register BC can take on values in the range 0-76 corresponding to   ;
; valid track numbers for standard floppy disk drives and 0-65535 for         ;
; nonstandard disk subsystems.                                                ;
; =========================================================================== ;
SETTRK	
	ld (CTRACK),bc
	ret

; =========================================================================== ;
; SETSEC                                                                      ;
; =========================================================================== ;
; Register BC contains the sector number, 1 through 26, for subsequent disk   ;
; accesses on the currently selected drive. The sector number in BC is the    ;
; same as the number returned from the SECTRAN entry point. You can choose to ;
; send this information to the controller at this point or delay sector       ;
; selection until a read or write operation occurs.                           ;
; =========================================================================== ;
SETSEC
	ld (CSEC),bc
	ret

; =========================================================================== ;
; SETDMA                                                                      ;
; =========================================================================== ;
; Register BC contains the DMA (Disk Memory Access) address for subsequent    ;
; read or write operations. For example, if B = 00H and C = 80H when SETDMA   ;
; is called, all subsequent read operations read their data into 80H through  ;
; 0FFH and all subsequent write operations get their data from 80H through    ;
; 0FFH, until the next call to SETDMA occurs. The initial DMA address is      ;
; assumed to be 80H. The controller need not actually support Direct Memory   ;
; Access. If, for example, all data transfers are through I/O ports, the      ;
; CBIOS that is constructed uses the 128 byte area starting at the selected   ;
; DMA address for the memory buffer during the subsequent read or write       ;
; operations.                                                                 ;
; =========================================================================== ;
SETDMA
	ld (DMAAD),bc
	ret

; =========================================================================== ;
; READ                                                                        ;
; =========================================================================== ;
; Assuming the drive has been selected, the track has been set, and the DMA   ;
; address has been specified, the READ subroutine attempts to read one sector ;
; based upon these parameters and returns the following error codes in        ;
; register A:                                                                 ;
;                                                                             ;
;     0 - no errors occurred                                                  ;
;     1 - nonrecoverable error condition occurred                             ;
;                                                                             ;
; Currently, CP/M responds only to a zero or nonzero value as the return      ;
; code. That is, if the value in register A is 0, CP/M assumes that the disk  ;
; operation was completed properly. If an error occurs the CBIOS should       ;
; attempt at least 10 retries to see if the error is recoverable. When an     ;
; error is reported the BDOS prints the message BDOS ERR ON x: BAD SECTOR.    ;
; The operator then has the option of pressing a carriage return to ignore    ;
; the error, or CTRL-C to abort.                                              ;
; =========================================================================== ;
READ
	out (13),a \ ret

; =========================================================================== ;
; WRITE                                                                       ;
; =========================================================================== ;
; Data is written from the currently selected DMA address to the currently    ;
; selected drive, track, and sector. For floppy disks, the data should be     ;
; marked as nondeleted data to maintain compatibility with other CP/M         ;
; systems. The error codes given in the READ command are returned in register ;
; A, with error recovery attempts as described above.                         ;
; =========================================================================== ;
WRITE
	out (14),a \ ret

; =========================================================================== ;
; LISTST                                                                      ;
; =========================================================================== ;
; You return the ready status of the list device used by the DESPOOL program  ;
; to improve console response during its operation. The value 00 is returned  ;
; in A if the list device is not ready to accept a character and 0FFH if a    ;
; character can be sent to the printer. A 00 value should be returned if LIST ;
; status is not implemented.                                                  ;
; =========================================================================== ;
LISTST
	out (15),a \ ret

; =========================================================================== ;
; SECTRAN                                                                     ;
; =========================================================================== ;
; Logical-to-physical sector translation is performed to improve the overall  ;
; response of CP/M. Standard CP/M systems are shipped with a skew factor of   ;
; 6, where six physical sectors are skipped between each logical read         ;
; operation. This skew factor allows enough time between sectors for most     ;
; programs to load their buffers without missing the next sector. In          ;
; particular computer systems that use fast processors, memory, and disk      ;
; subsystems, the skew factor might be changed to improve overall response.   ;
; However, the user should maintain a single-density IBM-compatible version   ;
; of CP/M for information transfer into and out of the computer system, using ;
; a skew factor of 6.                                                         ;
;                                                                             ;
; In general, SECTRAN receives a logical sector number relative to zero in BC ;
; and a translate table address in DE. The sector number is used as an index  ;
; into the translate table, with the resulting physical sector number in HL.  ;
; For standard systems, the table and indexing code is provided in the CBIOS  ;
; and need not be changed.                                                    ;
; =========================================================================== ;
SECTRAN
	ld h,b
	ld l,c
	ret

Quite a number of the above routines simply output the value of the accumulator to a port. This is because I'm running CP/M in a Z80 emulator that I've knocked together, and am handling writes to particular ports by implementing the machine-specific operations (such as console input or output) in C#. The floppy disk file system is also emulated in C#; when the program starts, it pulls all the files from a specified directory into an in-memory disk image. Writing to any sector deletes all of the files in this directory then extracts the files from the in-memory virtual disk image back into it. This is not especially efficient, but it works rather well.

To turn this into a working bit of hardware, I intend to replace the C# part with a microcontroller to handle keyboard input, text output and interfacing to an SD card for file storage. It would also be responsible for booting the system by copying the OS to Z80 memory from the SD card. I'm not sure the best way to connect the microcontroller to the Z80, though; disk operations use DMA, which is easy enough, but for lighter tasks such as querying whether console input is available or outputting a character to the display it would be nice to be able to go via I/O ports. A couple of I/O registers may be sufficient as per the current design; a proper Z80 PIO would be even better if I can get my hands on one.

Of more concern is a suitable display; the above screenshot is from an 80-character wide display. Assuming a character was four pixels wide (which is about as narrow as they can be made whilst still being legible) imposes a minimum resolution of 320 pixels horizontally – my current LCD is only 128 pixels wide (not even half way there), and larger ones are really rather expensive!

Building a VGA line blanker and 3D glasses driver

Monday, 15th February 2010

Assembling a circuit on breadboard is a good way to experiment with electronics, but the result is not something you could really use – it's bulky, fragile and awkward to set up. It's far nicer to solder the components of the circuit together to form a more permanent device and put it in a enclosure to make it robust. This is not something I'm especially good at, but something I thought I'd try with the VGA line blanker and LCD shutter glasses controller I've been experimenting with recently.

VGA line blanker and LCD shutter glasses controller

In the past I've struggled along with a hand drill and the nail file on a Swiss Army knife, but have more recently acquired a high-speed rotary tool and an assortment of attachments which make things much easier. I took some photos when building this project, which I've documented below; I'm not sure my techniques are very efficient, but I do get there in the end. I'd be very glad to hear any advice anyone has!

A plain project box Back of the project box marked for cutting

I started with a plain project box. Having planned roughly where I was going to put the VGA ports and DC power socket, I covered one side of the box in masking tape and drew on where I was going to put the holes.

VGA socket holes drilled out VGA socket holes roughly cut

To cut straight-edged holes, such as those required for a D-subminiature connector, I drill a hole in each corner and use a small cylindrical burr to cut between the holes. This leaves a very rough edge, but is a good start.

Upper VGA socket hole widened sufficiently to accomodate a VGA connector

I then widen the hole using a large cylindrical burr and a needle file until the part I'm attempting to mount fits snugly.

Holes for the jack post marked Both VGA socket holes widened, with holes for the jack posts drilled

When I had both VGA connectors in place, I marked and drilled the holes for the jack posts that the VGA leads will screw into. Neither hole is especially neatly cut, but the D-subminiature connector overlaps the hole sufficiently to hide any shoddy workmanship.

Hole for the power socket drilled All of the sockets installed in the back of the project box

The last part of the back is the DC power socket. As I don't have a drill bit large enough to cut the hole on its own, I drill it as large as I can then widen it using the cylindrical burrs mentioned before. With all of the holes cut, I inserted the components to see how they look and identified one problem – I'd underestimated how fat the connectors on the end of VGA leads are. Fortunately, I have a slim VGA cable that fits, but a regular sized one does not – in future I'll need to remember to put the VGA connectors further apart!

Hole for the LCD glasses socket cut 3.5mm stereo jack socket for the LCD glasses installed

With that mistake fresh in my mind, I thought I'd move onto something a bit more difficult to get wrong – the 3.5mm stereo jack on the front of the box to plug the glasses into. This is just another round hole, cut in the same way as the DC power socket.

Holes for the control switches marked Holes for the control switches cut

The two control switches on the top of the box require much larger holes. These were cut in the same way as before – a small hole is gradually widened by using a cylindrical burr. This is a very tedious job, not helped by having to keep stopping to clean the melted plastic that adheres to the burr.

Control switches installed

Finally, the switches were installed. I was originally going to use latching push buttons, but had previously used those nice round rocker switches as the power switch on the AVR TV Game project so opted to use them instead.

Stripboard cut to fit Stripboard installed in enclosure

The final bit of physical work was to cut some stripboard down to size to fit inside the enclosure. These were cut by first scoring along the tracks where the cut was to be made, then snapping the board over the edge of a table. This results in a clean break, but to ensure a snug fit the boards were tidied up with a sanding drum. The lid (or, in my case, base) of the enclosure has a raised edge that fits inside the box, so the sanding drum was also used to remove two of the corners of the stripboard pieces to allow the base to fit.

Preliminary tracks cut in stripboard

The next stage was to move onto the electronics, and I started with the circuit board that was to host the video amplifier IC, voltage regulator and Schmitt trigger on vsync/hsync. The video amplifier is attached to a TSSOP14 adaptor that has a D-shaped pin configuration, with two rows of four pins and two rows of three pins. Having cut through the tracks in the stripboard to mount the amplifier, I needed to find some suitable pin sockets.

8-way pin socket cut in two 8-way pin socket cut in two and neatened up

As I don't have any pin sockets with just three pins in them (only two, four and eight) I cut two eight-way pin sockets in two with a pair of wire cutters then tidied up the ragged edges with a sanding drum and needle file.

Video amplifier socket soldered in place

With the pin sockets soldered in place you can see the D shape I mentioned above. I don't generally plan stripboard circuits very thoroughly, preferring to start by placing large components in approximately the right location with respect to where the external connectors are and how they need to relate to other components. Once those are in place I add smaller components (such as discrete resistors or capacitors) before finishing by adding the wire links to connect all of the parts together. This does lead to situations where I wish that I'd placed a component one hole along to give myself more space or to avoid having to insert so many wire links, but it generally works.

Stripboard with cuts between holes Video amplifier resistors in place

With the video amplifier in position, I added the resistors that are required on its inputs and outputs. To keep the circuit reasonably compact I cut through stripboard tracks between the holes using a conical HSS burr with a small tip – this is an especially useful tool when you need to deal with double-row pin sockets

Power supply support components Socket for Schmitt trigger IC and pin headers for vsync/hsync jumpers

I then added the support circuitry for the voltage regulator (smoothing capacitors and a rectifier diode to protect the circuit if the polarity of the power supply is incorrect) and a socket for the Schmitt trigger IC. I find the easiest way to keep components in place on any sort of through-hole board is to tape them down firmly with masking tape before soldering – bending the legs out makes the parts much harder to remove if you make a mistake. Blu-Tack is easier to use but has a habit of melting when soldering and leaving an unpleasant blue residue on your circuit, so I'd advise against it! To make this part of the circuit slightly more future-proof a pair of jumpers are used to connect the sync lines (vsync and hsync) from the VGA input and VGA output together. These could be removed if I decided to change the logic board to override these signals – for example, as part of a sync-doubler, which injects a vsync pulse half way down the screen.

Connector between the video amplifier circuit board and the rest of the system Populated video amplifier circuit board

I finally added the bulkiest components; the 5V regulator and the pin header to connect the upper and lower boards together. Soldering pin headers to the underside of a board is a fiddly job, but is required in this instance to connect the bottom of the upper board to the top of the lower board.

Top view of the populated video amplifier circuit board Video amplifier circuit board installed in the enclosure

With the upper board completed it was time to put it into the enclosure and solder the VGA connectors and DC power socket to it. This is the part I least enjoy.

VGA connectors with stranded wires attached

I started by soldering some stranded wire to the VGA connectors. Most of the wires are the same length, as they are required to carry signals to and from the circuit, but some wires are shorter and only connected to one of the VGA connectors. These are the white, yellow, orange and brown wires in the above photo, and these are attached to pins used to exchange information between the PC and the monitor (e.g. supported resolutions and refresh rates). As we're not interested in these, they're connected straight through from one connector to the other.

Pins used for monitor identification passed through hole in enclosure Both VGA connectors installed

I inserted the VGA connector with these identification pins into the top hole, passed the shorter identification wires through the other and soldered them to the second VGA connector. This leaves the red, green, blue, vsync, hsync and ground pins loose inside, ready to be connected to the upper circuit board.

DC power socket

The DC power socket also needs to be connected to the circuit board, but at only two wires that's a much simpler job.

External connectors soldered to the video amplifier circuit board Video amplifier circuit board hooked up and installed in the case

All of the loose leads are soldered onto the circuit board and stripboard is slotted into place inside the enclosure. The wires could be shorter, but that would have made soldering them a bit harder.

Cut tracks for the logic circuit board

The lower circuit board will host the main logic for the project – it receives the vsync and hsync signals, and uses these to control whether the video signal should be blanked or not, and which shutter on the glasses should be closed and which should be open. It also contains the oscillator that generates the AC voltage that drives the glasses. I arranged the three logic ICs roughly next to eachother according to their layout on the breadboard version of the circuit and cut the stripboard tracks as appropriate.

IC sockets soldered to the logic circuit board Discrete components added to the logic circuit board

I started by adding the sockets for the ICs and pin header to connect this circuit board to the video amplifier one, then added the discrete components. As before, I taped the components down before soldering them in place to make the task easier. Being able to copy the circuit directly from the breadboard version also made the task much easier.

Top view of the wire links on the logic circuit board Bottom view of the wire links on the logic circuit board

The last step for this part of the project was, as before, adding the wire links. Rather than run long wires around ICs I found it more practical to solder a few wires onto the underside of the stripboard.

Pin sockets and wires for the connector cable One end of the connector cable soldered

The two circuit boards needed to be connected together somehow. Without the facilities to make a proper ribbon cable, I just soldered some lengths of stranded wire (rather messily) between two pin sockets. As I'm not outputting anything to vsync or hsync (I'm feeding the input sync signals straight back to the output via the jumpers previously discussed), I didn't need to connect anything to these pins – hence the apparently missing wires in the photos.

Connector cable bent to fit Both circuit boards installed in the enclosure

The cable to connect the two boards together needed to be bent to fit – it's getting snug, but everything's in there without having to be forced, which is a good sign.

3.5mm stereo jack socket for LCD glasses connector Stereo jack socket soldered to the logic circuit board

The next job was to attach the 3.5mm stereo jack that the LCD shutter glasses are plugged into. This is pushed through the hole in the enclosure from the inside and screwed on from the outside, so it can be soldered directly to the circuit board without having to thread it through the hole first. The small red "washer" is a length of enamelled wire that has been bent around the thread of the jack socket and is used as a spacer – without it, quite a lot of the thread protrudes from the front of the box, looking rather untidy.

Control switches with connecting wires All parts installed in the enclosure

Last of all are the two control switches. These are soldered to the track side of the stripboard like the stereo jack, but must be snapped through their holes in the enclosure first, which is why they were left until last. Everything is slotted into place, the base of the enclosure is screwed on, and the project is pretty much complete.

Tightly packed VGA cables

The VGA cables don't fit especially well – the D-subminiature sockets are a bit too close to eachother. If I use a thin VGA extension cable and wiggle the leads I can just about get both to screw in.

LCD shutter glasses showing the left eye view of a row-interleaved image

The demonstration pattern from some previous ramblings of mine is quite useful for testing 3D glasses, and by holding the left eye of the shutter glasses to the screen you can see that only the "L" part of the image is let through.

Page 14 of 53 110 11 12 13 14 15 16 17 1853

Older postsNewer postsLatest posts RSSSearchBrowse by dateIndexTags