Wednesday, 28 September 2011

24. Homebrew Hex to 7 segment display drivers working.

Yes I did it. I wrote PIC machine code to program the PIC16F505 Microcontrollers to perform the function of Hex to 7 segment Display Drivers.

I started writing the code before I even had the PIC Programmer or PIC chips. On the first run, I found I had an issue with the inputs not being grounded with pull-down resistors, causing the inputs to fluctuate randomly, but some 10K resistors on the inputs connected to ground sorted that. If you have a switch, when its on its fine, a voltage goes to set the pin high or 1. When the switch is off the pull-down resistors connect the pin to ground giving a low or 0 input.

I did initially opt to use one pin on the PIC16F505 as a latch, so it would only display the input when a button was pressed. I decided it wasn't necessary and the program loops around reading the inputs and setting the outputs to light up the LEDs.

Hers's a pic of the setup with annotation.

The PIC assembly code is here:

; include file with config bit definitions
 include "p16f505.inc" ;

; config - enable internal OSC and allow use of RB4
;        - disable MCLRE and allow use of RB3 (input only)
;        - disable Watchdog Timer, we don't use it.
 __config _IntRC_OSC_RB4EN & _MCLRE_OFF & _WDT_OFF


; local regsiters
INB equ 0x08
INC equ  0x09
INPUT equ  0x0A
OUTB equ 0x0B
OUTC equ 0x0C
PWRSTS equ  0x0D
SEGDATA equ 0x10

SEG0 equ 0x10
SEG1 equ 0x11
SEG2 equ 0x12
SEG3 equ 0x13
SEG4 equ 0x14
SEG5 equ 0x15
SEG6 equ 0x16
SEG7 equ 0x17
SEG8 equ 0x18
SEG9 equ 0x19
SEGA equ 0x1A
SEGB equ 0x1B
SEGC equ 0x1C
SEGD equ 0x1D
SEGE equ 0x1E
SEGF equ 0x1F

; start here 
 org 000
 movf STATUS,PWRSTS ; Save Power on status

; Port configuration
;                                        a
;              +-----+                 ---
;         VCC [|     |] GND           f| g |b
;  D3 --> RB5 [|     |] RB0 --> a ---
;  D2 --> RB4 [| PIC |] RB1 --> b     e|   |c
;  D1 --> RB3 [| 16F |] RB2 --> c       ---
;  D0 --> RC5 [| 505 |] RC0 --> d        d
;  NC     RC4 [|     |] RC1 --> e
;   g <-- RC3 [|     |] RC2 --> f
;              +-----+
;

;                 RRRRRR
;                 BBBBBB
;                 543210
init movlw b'111000' ; Set PORTB Pins
 tris PORTB

;                 RRRRRR
;                 CCCCCC
;                 543210
 movlw b'100000' ; Set PORTC Pins
 tris PORTC

; Load Segment values into memory.
;Digit gfedcba abcdefg a b c d e f g
;0 0x3F 0x7E on on on on on on off
;1 0x06 0x30 off on on off off off off
;2 0x5B 0x6D on on off on on off on
;3 0x4F 0x79 on on on on off off on
;4 0x66 0x33 off on on off off on on
;5 0x6D 0x5B on off on on off on on
;6 0x7D 0x5F on off on on on on on
;7 0x07 0x70 on on on off off off off
;8 0x7F 0x7F on on on on on on on
;9 0x6F 0x7B on on on on off on on
;A 0x77 0x77 on on on off on on on
;b 0x7C 0x1F off off on on on on on
;C 0x39 0x4E on off off on on on off
;d 0x5E 0x3D off on on on on off on
;E 0x79 0x4F on off off on on on on
;F 0x71 0x47 on off off off on on on

 movlw 0x3F
 movwf SEG0

 movlw 0x06
 movwf SEG1

 movlw 0x5B
 movwf SEG2

 movlw 0x4F
 movwf SEG3

 movlw 0x66
 movwf SEG4

 movlw 0x6D
 movwf SEG5

 movlw 0x7D
 movwf SEG6

 movlw 0x07
 movwf SEG7

 movlw 0x7F
 movwf SEG8

 movlw 0x6F
 movwf SEG9

 movlw 0x77
 movwf SEGA

 movlw 0x7C
 movwf SEGB

 movlw 0x39
 movwf SEGC

 movlw 0x5E
 movwf SEGD

 movlw 0x79
 movwf SEGE

 movlw 0x71
 movwf SEGF

; End of initialisation

; To here 37 Instructions @ 4MHz = 37uS

; Main Program Starts here.
poweron clrf OUTB  ; Clear output Register B
 clrf OUTC  ; Clear output Register C
 btfss PWRSTS,RBWUF; Check Power-On Status
 goto display  ; blank if power on

; Reset output values and indexes
loop clrf OUTB  ; Clear output Register B
 clrf OUTC  ; Clear output Register C
 movlw SEGDATA  ; Load W with SEG DATA base
 movwf FSR  ; Save to FSR

; read value from PORTs
 movf PORTB,W      ; Read PORTB
 movwf INB  ; Save PORTB
 movf PORTC,W  ; Read PORTC
 movwf INC  ; Save PORTC

; combine port reads into a nibble in W register
 clrf INPUT  ; Clear INPUT register
 btfsc INB,RB5  ; Test RB5 (D3)
 bsf INPUT,3  ; Set D0
 btfsc INB,RB4  ; Test RB4 (D2)
 bsf INPUT,2  ; Set D1
 btfsc INB,RB3  ; Test RB3 (D1)
 bsf INPUT,1  ; Set D2
 btfsc INC,RC5  ; Test RC5 (D0)
 bsf INPUT,0  ; Set D3
 movf INPUT,W  ; Move value to W


; add value to index register and fetch segment value
 addwf FSR,F  ; Add index to FSR pointer

; prepare register output bits
 btfsc INDF,0  ; Test for 'a' segment
 bsf OUTB,RB0 ; Set 'a' segment
 btfsc INDF,1  ; Test for 'b' segment
 bsf OUTB,RB1 ; Set 'b' segment
 btfsc INDF,2  ; Test for 'c' segment
 bsf OUTB,RB2 ; Set 'c' segment
 btfsc INDF,3  ; Test for 'd' segment
 bsf OUTC,RC0 ; Set 'd' segment
 btfsc INDF,4  ; Test for 'e' segment
 bsf OUTC,RB1 ; Set 'e' segment
 btfsc INDF,5  ; Test for 'f' segment
 bsf OUTC,RB2 ; Set 'f' segment
 btfsc INDF,6  ; Test for 'g' segment
 bsf OUTC,RB3 ; Set 'g' segment

; move output to ports
display movf OUTB,W  ; Get values to send to PORTB
 movwf PORTB  ; Send to PORTB
 movf OUTC,W  ; Get values to send to PORTC
 movwf PORTC  ; Send to PORTC

; around again
 goto loop  ; go back to loop - 50uS loop
 end

Friday, 23 September 2011

23. Put out by the output.

I had this idea, that I would connect up some 7 segment displays to show what all the registers where set to if I stepped through a Z80 program. Its a little bit ambitious at this stage of my journey, so I thought I would start by creating something to input Z80 commands directly onto the data bus. I bought some in-line 8 bit switches, however the data pins on the Z80 are split into two group by the fact that the +5V Vcc input sit between them. Not only that, they are not in order like the address pins. So I put some jump wires in to put them into the right order. Now it'd be great to have a hex display of the data that I am trying to put in, so I thought I could use two 7 segment displays. Easy right?

For each nibble (thats what 4 bits of a byte are called) I need a 7 segment display. One for the upper values of 0123456789ABCDEF and one for the lower. The displays have 8 inputs 7 for the segments and one for the decimal point. So I read that to convert 4 bits to 7 I needed a hex to 7 segment display driver. Cool, so I go surfing the web to find one. All I can can find are BCD to 7 segment display drivers (BCD is Binary Coded Decimal and basically allows the values 0123456789) such as the 7447. I eventually find some, the DM9368 and the MC14495. They don't make them any more, but if you hunt around you can find them for like £30 each.

Going back to my original idea of displaying the registers, I worked out I'd need 52. Ouch!

As I desperately try to find alternatives, one blogger mentions using a PIC microprocessor instead, and programming it to perform the same function. Hmm, could this work?

So I get diverted into looking at what these PIC things are. I soon discover that they're kind of like the ATMega that powers the Arduino, but can come in very small sizes, I mean some are only 8 pins. This diversion opens up a whole new world. New software, new machine code language, new software to program them and new programmers to, well, program them.

Needless to say, I have been reading numerous .pdf documents on how all this stuff works. I'm not even sure if it will work yet. I've initially plumbed for the PIC16F505. I was going to go with the PIC16C505 from Maplin for £1.89, but its a bit outdated, but the PIC16F505 is 79p from RS Components. Now it used to be that RS were only open to Businesses, but I believe they have now opened up their doors to the general public.

I found this USB PIC Programmer too for £35. So I am going to bite the bullet and give it a go because 79p x 52 is a lot easier to swallow. Of course I'll just try with 1 for now.

The software is free from Microchip and I've started writing some simple code so I can get the hang of it. At least its compiling ok.

So here I am programming a completely new and alien (to me) microprocessor so I can get some output (and input) from (to) a Z80 using some 7 segment displays. Kind of ironic really.

I may be a while....

Wednesday, 7 September 2011

22. Building An EEPROM Programmer (Part 3).

Ok So does it actually work. YES!

On linux you can check the data in the zx81.rom file by typing:
od -Ax -t x1 zx81.rom

Running the EERPROM Reader in Part 1, I can visually see that they look the same. As a final check I got the ERRPROM Reader to add all the bytes together (OK not the best way to check sum a file but I am not here to write a proper CRC checksum routine for the Arduino just now), and also wrote a C program to do the same on Linux called sum.c:

#include 
main()
{
 int c,sum;
 sum=0;
 while ((c=getchar()) != EOF) {
  sum=sum+c;
 }
 printf("Checksum: %d\n",sum);
}

The final value came back as 855105 for both.

Result!

As a final check, I unplugged the Arduino removing power from the EEPROM in the process. Left it for a few minutes and then plugged that Arduino again restoring power to the EEPROM and reran the EEPROM Reader program. All data preserved and sum still the same.

21. Building An EEPROM Programmer (Part 2).

The Arduino can receive data via the Serial interface. This is how I want to send the Z80 programs to the EEPROM. The problem is the Arduino is not reading fast enough and so what gets sent is lost. Its fine for sending the odd few bytes now and then, but (using my zx81.rom as a test) sending 8K of data is a bit much for it to handle in one go.

The Serial buffer on the Arduino is 128 bytes, so all I was getting to start with was the first 128 bytes of the 8K successfully, after that it was a random sets of 128 bytes from the Serial port that it grabbed hold of before the stream of data ended.

To compensate for this, I decided to send 64 bytes at a time with 1 second interval. Now this is fairly easy to do in Linux with a script:

eeprom_send.sh
#!/bin/bash
split -C 64 ${1} ${1}.part.
for part in `ls ${1}.part.*`
do
 cat ${part} >> ${2}
 sleep 1
done
rm -f ${1}.part.*

To use it type:
./eeprom_send.sh zx81.rom /dev/ttyACM0

But first you just need to program the Arduino and then open the serial monitor window (dont ask me why).

Here is the Arduino program:
/*
 EEPROM Programmer
*/
#define memsize 8192

int STS   = 13;  // Status Indicator

int AP[16] = {22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52};
int AD[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int DP[8]  = {23,25,27,29,31,33,35,37};
int DD[8]  = {0,0,0,0,0,0,0,0};

int CE  = 4;
int OE  = 3;
int WE  = 2;

int i;
int A;
int D;
int wait;

void setup() {
  
  Serial.begin(115200);
  Serial.flush();
  
  pinMode(STS,   OUTPUT);
  
  // Setup Address Pins 
  for (i=0;i<16;i++) {
    pinMode(AP[i],OUTPUT);
  }
  
  // Setup Data Pins
  for (i=0;i<8;i++) {
    pinMode(DP[i],OUTPUT);
  }
  
  // Setup Control Pins
  pinMode(CE, OUTPUT);
  pinMode(WE, OUTPUT);
  pinMode(OE, OUTPUT);
  
  // Setup Chip
  digitalWrite(CE, LOW);
  digitalWrite(WE, LOW);
  digitalWrite(OE, HIGH);

  Serial.println("Waiting for Data...");
  while(Serial.available()==0) {
    digitalWrite(STS,LOW);
    delay(100);
    digitalWrite(STS,HIGH);
    delay(100);
  }

  for (A=0;A<memsize;A++) {
    
    D=Serial.read();
    digitalWrite(STS,HIGH);  //Signal that we're writing.

    // Setup Address Pins
    for (i=0;i<16;i++) {
      if((A&bit(i))>0) {
        AD[i]=HIGH;
      } else {
        AD[i]=LOW;
      }      
      digitalWrite(AP[i],AD[i]);
    }
    delay(1);
    
    digitalWrite(OE,LOW);   // Reads Disabled
    delay(1);

    digitalWrite(CE,HIGH);    // Chip Enable
    delay(1);

    digitalWrite(WE,HIGH);    // Write Enabled
    delay(1);
    
    // Setup Data Pins
    for (i=0;i<8;i++) {
      if((D&bit(i))>0) {
        DD[i]=HIGH;
      } else {
        DD[i]=LOW;
      }      
      digitalWrite(DP[i],DD[i]);
    }
    delay(1);

    digitalWrite(WE,LOW);      // Write Disabled
    delay(1);

    digitalWrite(CE,LOW);      // Chip Disabled
    delay(1);

    digitalWrite(OE,HIGH);       // Reads Enabled
    delay(1);
    
   digitalWrite(STS,LOW); // Signal that we're waiting
    wait=0;
    while(Serial.available()==0) {
      delay(1);
      if (wait>2000) {
        A=memsize;
        break;
      }
      wait++;
    }
    
  }
  
}

void loop() {
  digitalWrite(STS, HIGH);   // set the LED on
  delay(1000);              // wait for a second
  digitalWrite(STS, LOW);    // set the LED off
  delay(1000);              // wait for a second
}

Tuesday, 6 September 2011

20. Building An EEPROM Programmer (Part 1).

Its all very well having an EEPROM chip to store our program for the Z80, but somehow we need to get the code onto it. Enter, the EEPROM programmer. So far I have wired up the EEPROM to the Arduino Mega and I can read from it successfully. Writing to it is easy enough, but just now I can't bulk load a file to it. When I do, go find Part 2 :)

Here's a diagram I made using this great tool called Fritizing:
As you can see I've used a 74LS04 Hex Inverter to make sure the 3 control lines are HIGH by default as the pins on the Arduino are LOW by default and as the control pins are 'Active Low' we don't want data overwritten accidentally. It also means that setting the pin to HIGH in the Arduino code means the pin is Active.

Here's the EEPROM Reader code for the Arduino Mega:
/*
 EEPROM Chip Reader
*/

#define memsize 8192

int STS   = 13;  // Status Indicator

int AP[16] = {22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52};
int AD[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int DP[8]  = {23,25,27,29,31,33,35,37};
int DD[8]  = {0,0,0,0,0,0,0,0};

int CE  = 4;
int OE  = 3;
int WE  = 2;

int i;
int j;
int D;
int A;

void setup() {
      
  // Setup Control Pins
  pinMode(CE, OUTPUT);
  pinMode(WE, OUTPUT);
  pinMode(OE, OUTPUT);
  
  // Disable Chip, and disable read and write.
  digitalWrite(CE, LOW);
  digitalWrite(WE, LOW);
  digitalWrite(OE, LOW);

  Serial.begin(115200);
  
  Serial.println("Reading EEPROM...");
  
  pinMode(STS,   OUTPUT);
  digitalWrite(STS,HIGH);
  
  // Setup Address Pins 
  for (i=0;i<16;i++) {
    pinMode(AP[i],OUTPUT);
  }
  
  // Setup Data Pins
  for (i=0;i<8;i++) {
    pinMode(DP[i],INPUT);
  }

  delay(1000);
  
  for (A=0;A<memsize;) {
    
    if (A<4096) Serial.print("0");
    if (A<256)  Serial.print("0");
    if (A<16)   Serial.print("0");
    Serial.print(A,HEX);
    Serial.print(" ");

    for (j=0;j<16;j++) {
    
      // Setup Address Pins
      for (i=0;i<16;i++) {
        if((A&bit(i))>0) {
          AD[i]=HIGH;
        } else {
          AD[i]=LOW;
        }      
        digitalWrite(AP[i],AD[i]);
      }
    
      digitalWrite(CE,HIGH);      // Chip Enabled
      digitalWrite(OE,HIGH);      // Read Enabled
    
      // Read Data Pins
      D=0;
      for (i=0;i<8;i++) {
        DD[i]=digitalRead(DP[i]);
        D=D+bit(i)*DD[i];
      }
    
      digitalWrite(OE,LOW);     // Read Disabled
      digitalWrite(CE,LOW);     // Chip Disabled
    
      if (D<16) Serial.print("0");
      Serial.print(D,HEX);
      Serial.print(" ");
      
      A++;
    }
    
    Serial.println();
    
  }
  
}

void loop() {
  digitalWrite(STS, HIGH);   // set the LED on
  delay(500);              // wait for a second
  digitalWrite(STS, LOW);    // set the LED off
  delay(500);              // wait for a second
}