Monday, August 31, 2015

Driver Work, Menu, and First Compile

This morning, I worked on coding drivers. Once the UART one is finished, I'll be able to start testing them.

I also worked on a menu system that I will use at the beginning. The menu system will allow faster programming, flash facilities, memory dump, flash erase, bank switching, and eventually running of programs in other banks.

Finally, I got an entire program to compile and link. I'm not sure stuff is going into the right places yet, but it linked even though I used the sdcclib facility rather than the sdar archiver which seg faults when I try to use it. The map file looks reasonable, and there is a lot of data in the .ihx file. So I think it is actually linking. It has my custom crt0.s. I think I need to specify that RAM starts at 0x8000 with a linker command. But I'm close, anyway. The whole system needs refinement, but I'll be there before too long.

I also have to get the final HW configured. That means, tying the UART IRQ to INT and connecting the timer CPLD to the SPI SCK, Din, and GPIO[1], and INT. Those last ones will be soldered to the CPLD pins and then wire wrapped on the other end. I also should solder in a ground post or two for the oscilloscope probes.

Sunday, August 30, 2015

Flow Control and Print Format

I got tired of banging my head against the wall with the toolchain compiling. So I decided to just work on something else for a while.

This morning, I connected the CTS and RTS to the RS232 port. So, I'll start to see if I can do hardware flow control. Which I want since I have 1) a slow processor, 2) a limited 8 byte FIFO in the MAX3110E, and 3) a plan to run at the maximum baud rate. I still have to tie the IRQ to the CPU. Then I'll be done with communication connections.

I also am working on the parts of the C library that are missing while I'm not getting the toolchain to compile. Since the printf formatting has all kinds of stuff in it, I am just going to write my own minimal formatter. It won't do everything, but it will do everything I need. No floating point or  long long.

I also wrote some test code so I know that it works. At least well enough for now--with correct input.

// Formatter for printf, sprintf, vprintf, vsprintf, etc.
//  This is a very light and minimal formatter that includes only
//  a subset of the standard functionality. This list explains what
//  this light version is capable of:
//    %c - print character
//    %s - print string
//    %% - print '%'
//    %d - print signed int
//    %x - print unsigned int in hex
//    %p - print 2 byte unsigned pointer in hex
//    %#[xduls] - print and pad to # characters. If # starts with 0, use
//        leading zeros on unsigned int/long
//    %-#[sulds] - as above, but right justify
//    %l[xd] - 4 byte long instead of 2 byte int
//    %u[xd] - unsigned

void print_int(uint32_t data, int base, bool isUnsigned, bool leadingZeroes, int size, int length, bool rightJustify, char** pBuffer, void(*write)(char c, char** pBuffer))
{
    char buf[BUFLEN];
    static const char* ascii = "0123456789abcdef";
    int i = BUFLEN - 1;
    bool negSign = false;
    uint32_t udata = data;
    if (!isUnsigned && ((int32_t)data) < 0)
    {
        negSign = true;
        udata = (~data) + 1;
    }
    if (size == 2)
    {
        udata &= 0xffff;
    }
    do
    {
        buf[i--] = ascii[udata % base];
        udata = udata / base;
    } while (udata != 0);

    if (negSign)
    {
        buf[i--] = '-';
    }
    while (i >= 0 && length > (BUFLEN - i))
    {
        buf[i--] = leadingZeroes ? '0' : ' ';
    }
    i++; // move to the valid data
    for (; i < BUFLEN; i++)
    {
        write(buf[i], pBuffer);
    }
}

int print_format(void(*write)(char c, char** pBuffer), char** pBuffer, const char* format, va_list ap)
{
    char c;
    char* start = *pBuffer;
    int16_t len;
    bool isunsigned;
    int16_t base;
    bool leadingZero;
    bool neg;
    int16_t padding;
    long data;
    char* p;
    int i;

    // while not at end of line
    while (c = *format++)
    {
        if (c != '%')
        {
            write(c, pBuffer);
        }
        else
        {
            bool done = false;
            len = 2;
            isunsigned = false;
            leadingZero = false;
            neg = false;
            padding = 0;
            while (!done)
            {
                c = *format++;
                switch (c)
                {
                case '%':
                    write(c, pBuffer);
                    done = true;
                    break;
                case 's':
                    // get ptr from va_list
                    p = va_arg(ap, char*);
                    if (padding > 0)
                    {
                        int slen = strlen(p);
                        int spaces = padding - slen;
                        if (spaces > 0)
                        {
                            // padded string
                            if (neg)
                            {
                                for (i = 0; i < spaces; i++)
                                {
                                    write(' ', pBuffer);
                                }
                            }

                            while (c = *p)
                            {
                                write(c, pBuffer);
                            }

                            if (!neg)
                            {
                                for (i = 0; i < spaces; i++)
                                {
                                    write(' ', pBuffer);
                                }
                            }
                        }
                        else
                        {
                            // string longer than padding
                            while (c = *p)
                            {
                                write(c, pBuffer);
                            }
                        }
                    }
                    else
                    {
                        while (c = *p)
                        {
                            write(c, pBuffer);
                        }
                    }
                    done = true;
                    break;
                case 'c':
                    c = va_arg(ap, int); // char is promoted to int
                    write(c, pBuffer);
                    done = true;
                    break;
                case 'h':
                    len = 2;
                    break;
                case 'u':
                    isunsigned = true;
                    break;
                case 'l':
                    len = 4;
                    break;
                case '-':
                    neg = true;
                    break;
                case '0':
                    if (!padding)
                    {
                        // if no padding yet, then this means leading zeroes are shown
                        leadingZero = true;
                    }
                    // fall through
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    padding = padding * 10 + c - '0';
                    break;
                case 'p':
                    isunsigned = true;
                    len = 2;
                case 'd':
                case 'x':
                    base = c == 'd' ? 10 : 16; // 16 on x or p
                    if (len == 4)
                    {
                        data = va_arg(ap, long);
                    }
                    else
                    {
                        data = va_arg(ap, int);
                    }
                    print_int(data, base, isunsigned, leadingZero, len, padding, neg, pBuffer, write);
                    done = true;
                    break;
                }
            }
        }
    }
    **pBuffer = '\0';
    return *pBuffer - start;
}

Saturday, August 29, 2015

Notes

Sometimes coming out of the BUSREQ mode, the process restarts somewhere else. So I have to figure out why. Not that it's that bad. Bus everything should go Hi-Z before the BUSREQ goes high. I should ensure that happens.

I'm not sure why I have to issue a "fp reset' after a 'fp pins 1'. This issue is possibly related to the issue above.

I was working in 9600 baud on the serial port. My test to see if 115.2 kbaud worked. Yay!!! So I'll switch to that. Twelve times faster. I will connect the RTS and CTS lines and use HW flow control. I'm not sure my little Z-80 can keep up with 115.5k otherwise.

I'm absorbing the nuances of the SDCC toolchain. It's an open-source project so it has the pluses (within my budget) and minuses (hard to use) that one would expect.

I wanted to compile the SDCC toolchain. That required the boost library which is huuuuge. Eventually after figuring out user property sheets in VS 13 I got it to compile. But the sdar is not included. And it gives me the segmentation fault.

I could run this all in linux in VirtualBox but as of now, VirtualBox doesn't work on Windows 10 and I upgraded to Windows 10. So now I'm trying to get it running on my cloud server. Learning wget and installing bison, flex, boost, g++, gputils, texinfo (for binutils) ... Tech'in ain't easy.

[Update:] Not sure how much fruit this is bearing. My cloud server, being shared, is configured to have no swap file. The RAM is the RAM. And I'm apparently running out of memory. I
tried to remove the -pipe flag in a makefile, but that didn't work...

Back to try to compile in Windows with Cygwin....

Friday, August 28, 2015

SDCC And The Z-80

I'm not exactly sure exactly how I'm supposed to use the Z-80 code that ships with SDCC. There is a set of C library source code with processor specific directories. I can link against it, and the linker doesn't complain about missing putchar and getchar. I can define my own putchar and it doesn't complain about multiply defined putchar. This is not what I'd expect.

There is seemingly no makefile that would use these to create libraries. So I copied them to my local area and wrote my own makefiles to make my own libraries. I used the assembler and compiler to create a 'z80.lib' in the z80 directory. Then a crt.lib, float.lib, and longlong.lib in the src directory.

This approach presented some questions.
sdar gives a segmentation fault--so I'm using the deprecated sdcclib
some functions are multiply defined--mostly low level basic arithmetic. Why?
some functions are completely unimplemented--mostly the char is.* functions. Why?
there are several printf options, none of which seeming appropriate

So my plan to get this working is to:
add the implementation for the is.* functions
add an implementation for  __print_format
add getchar and putchar

This should give me a version of C that will work for my purposes. I'll leave out longlong and float.

I also have to look into something I saw. A developer of a graphing calculator said on the SDCC forum, I think, the Z-80 compiler stopped creating correct code after some checkin. So I'll have to consider pulling that last version from SVN and compiling that.

Or look into other toolchains. Or fixing it if it's broken and contributing to the sdcc project.

I also don't know how to do something that I'd like to do. Namely, I want to have a C runtime in bank 0 and then an application in bank 1. So I want the bank 1 application to access the C runtime code in bank 0 without actually linking it. That might involve an assembly file that defines a bunch of stubs that forward to the actual code-- just a set of jumps that jump the appropriate code in bank 0 or maybe to a vector in bank 0 so that I can replace the code in bank0 without needing to recompile the applications.

Just stuff I'm thinking about....

Thursday, August 27, 2015

Agenda

There is a lot to do to get this working:
  • Make PC program that talks to Arduino do entire flash programming in one shot
  • Add "bank" to PC program and Arduino to add a bank offset to the .ihx file
  • Figure out which sd-cc file I need to make a C runtime for Z80
  • Write makefiles
  • Continue working on subsystem drivers
  • Work on a basic menu system
  • Connect INT to MAX3110E
  • Solder in Timer CPLD and connect to INT
  • Telnet?

The menu system should allow me to do this:
  • Flash programming
  • Bank switching
  • Mem dump


Wednesday, August 26, 2015

Scope Arrived

This morning my scope arrive at the FedEx store down the street. So I picked it up and came to my lab (Liberty Bar which is a cafe by morning).



The first thing I did was power it up. Then calibrated my probes.

I ran the code that should work but didn't. But this time it did after I jiggled the board and hit reset. Then I put in the code that didn't work.

The part that's new is that it uses RAM with push and pop. I put in a loop like this:

loop:
    ld sp, 0
    ld hl, 0
    push hl
    jr loop:

I inspected the CS line on the SRAM chip. Solid 5V. So it's not reading RAM. So I looked at the A14 and A15 lines from the CPU. Little bumps on A15-A14 where A13-A4 went all the up to 5V. That meant bus contention. Since it's on A14 and A15, it's obviously related to the bank switching.

So some inspection there led me to the reason. The Flash programmer had temporary lines to the bank switcher as well as the flash chip. So it was plugged into A14 and A15 on both sides of the bank switcher. Which meant the input and output of the bank switcher were connected. There's a contention.

I took those wires off and voila! It worked. At least, it worked once I got the assembly in shape. It successfully ran a RAM test.

Now that it works again, it's back to the driver code. And jekyll and pygments on the side.

Having a scope will definitely speed things up in development. If only I had a logic analyzer... ;-)

Monday, August 24, 2015

Installing Eclipse and EclipseSDCC

I had to add Java to machine to allow me to use the Rackspace console to upgrade my cloudserver. So I have Java. I never liked Eclipse, but I like text makefiles even less. So I decided to see if I can use the lesser of two evils. I'm loading Eclipse now. I also loaded EclipseSDCC which is a plugin that should make it easy to use the SDCC toolchain in projects. There is a site at http://www.embeddedcraft.org/free8051.html which I am referring to as well.

So my first problem, some error 13. Googling led me to the conclusion that I need to install the JDK. That seemed to work. I can launch Eclipse.

Now to add the plugin. Just some drag/drop from a ZIP file.

Let's launch again, shall we....

OK, I'm going to go ahead and do the tutorial just because the last time I tried to use Eclipse it was just an exercise in frustration.

The first part of the tutorial assumed I know what views and perspectives are. I don't, so I clicked on a link to Views. But how to go back? There is no back button. ... Or is there. Above the text, I see the tops of some itty bitty buttons. One looks like the of an arrow pointing left. I click it. It goes back. Eclipse Help doesn't render properly on my Windows 10 Surface Pro 3. Grrr... Did I mention that Eclipse was an exercise in frustration?

Did I jump ahead in the tutorial? It doesn't work.  OK, I tried to run the program from the GIUT command line and the command line tells me cygwin1.dll and cygstdc__-6.dll are missing from my computer. Cygwin Bash runs it fine. So I guess it's a PATH thing. I won't be debugging my Z-80 code in Eclipse anyway.

I'm tired of the tutorial. So I'll just see if I can do a Z-80 project.... I can see the MCS-51 project under Others... I expect to see. Hopefully I can convert it to a Z80 project. Also, there is a toolchain under C, but not under C++.

And, I get "Project cannot be created" "Reason:" "Internal Error:", and when I click details "java.lang.NullPointerException". One solution was to copy the "os" folder from one file to another, but that didn't work. I suppose I could install a 32 bit version of Eclipse....

Did I mention that Eclipse is an exercise in frustration? Makefiles are starting to look pretty nice about now.....



Ordered a Scope

OK, I'm having a little trouble getting things working exactly right. So, I broke down and ordered an oscope on Amazon. Unfortunately, they can't deliver to Amazon locker, to that means I get to spend Thursday at home.

The good news is that there is plenty to keep me busy until then. I am not at all blocked waiting for this shipment.

Sunday, August 23, 2015

Working on Drivers

It's been a couple of days since I posted. I'm in the process of coding stuff that will run on the computer. Also, learning Jekyll to change the way I blog (especially looking forward to using pygmints which will do syntax coloring on C, C++, C#, verilog, and, if I learn python, SDCC Z80 assembly).

The drivers are for:

  • SPI (CPLD)
  • GPIO (CPLD)
  • 1ms ticker (CPLD)
  • UART (CPLD--Max3110E)
  • flash - (direct access on bus)
  • timer - (using 1ms ticker)
  • memory bank switcher (CPLD)
  • IHX programmer (using flash)

This includes ring buffers for the UART. All this stuff will help me develop applications such as a native flash programmer, menu system, debug monitor, and anything else my mind comes up with.

Monday, I'll do my SRAM/bus test.

And since I'm starting to play with jekyll, I'll be presenting my code in pymentized color. Jekyll is a new kind of platform for me. I never heard of liquid or markdown before. Getting it running was a chore and half. The tutorials seem to be on the Mac, but I'm running it on a linux cloud server whcih is tricky because my usual SOCKS5 proxy SSH tunnel into the server wasn't letting me see the page when I ran "jekyll serve". So finally, I just created a soft link to the /var/www/bricologica/ directory so that I can see the _site through bricologica.com/something. I have some work to do to make it look decent and to get it running the way I want, but it shouldn't be too far off. It will be nice to get rid of WordPress.

But, most of my effort will be on the computer.

Saturday, August 22, 2015

This Is What A Working Z-80 Computer Looks Like

This is it!

Front:





Back:


Yes, I'm still giddy over the fact that it works..... :-)

Friday, August 21, 2015

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

My goal was to have my computer write a stream of 'A' and right now, after fixing the previous bug, puTTY is filling up with AAAAAAAAAAAAAA.......

Triumph!!!!

Having problems with the power going out. Not if its thermal shutdown or something in the connection. But, that's minor. Because AAAAAAAAAAAAAAAAAAAA.....

My in circuit flash programmer goes in and out with "fp pins 1" to turn it on and "fp pins 0" to turn it off. I recompiled, relinked, erased flash with "fp erase 0 1", and did a "fp write spam.ihx". And now....AAAAAAAAAAAAAAAAA....

This deserves a beer!

Z-80 Is Executing Properly....

I came down to Metrix Create:Space where I knew they had a scope. I looked at the inputs on SCK and CS on the MAX3110E chip. They look just like they should. I can see the clocks and the data writing 0x40 followed by 0x41.

...which is a problem. I should be writing 0x80 followed by 0x41....

BRB....

All Parts Mounted--But No Serial

OK, so all the parts are mounted on the board! Yay!

But, it doesn't seem to be sending out 'A' to the serial port which is the only thing it knows how to do. Why? Well, lot's of potential reasons... And hopefully I'll figure it out tomorrow.

I can use the DMM to see the duty cycle of any given pin. The clock is right is right around 2.5V. The A0 line is right around 2.5 as well. These do go to 5V when I push the reset button. So, the Z-80 is doing something. I just don't know what.

I verified that I can access the flash in circuit. So I can write simpler programs to the flash for the Z-80. Maybe start with "jr 0" at 0 to ensure that all address lines other than the first two are 0. If even A2 is 2.5V then it's not executing jr 0 at 0 which should only require A0 and A1.

But, it's 9:21pm on a Friday night and I had a pretty productive day. So, I'll spend the rest of my day doing something other than trying to figure out why my computer isn't spamming 'A'.

2MHz Oscillator and Reset Button Relocated

OK, I moved them. Here is the latest with the oscillator socket rotated 90 degrees. The black wire for power was long enough, but the red one was too short. Hence that extra bit of red wire:



And here is the back. I put the decoupling cap in:


Now it's time to reconnect the wire wrap and ensure once again that I can work with the flash. Then, it's time to pop in the Z-80.....

Oscillator chip/Z-80 Overlap

Everything was going well. Sure, I had to figure out the Arduino connections to the MCU and flash programmer to get everything working in the final state. But I got all the connections made was soon able to read and program the flash with all the new connections (and a temporary bridge from BUSREQ to BUSACK to be removed when the Z-80 is mounted).

But then, it happened. My 2MHz oscillator is too close to the Z-80 and I can't fit both. So I have to move the 14 pin DIP socket. There is room. I'll make it horizontal instead of vertical. I think I have to move the reset button but that's actually not bad since I'd like it closer to the edge.

But other than that, I'm ready to mount the Z-80. I guess it's time to set the soldering iron to smoulder.... At least the chip only has 4 connections--2 easy wire wraps and 2 power.

Wire Wrapping For First Run Tentatively Complete

My first program which should spam 'A' at puTTY is almost ready to be run. I just completed the final wirerapping for everything I can remember I need. INT is not hooked up yet and neither is the timer CPLD. But the rest is.

So, I'll some continuity checking to ensure I have connections where I think I have them. Then a power up test to see if I have power. Then a final check to see if I can read the flash with the in-circuit flash programmer.

If all that passes, I pop in the 3 last chips, the 2MHz oscillator, the SRAM, and the Z-80.

Here is the fun side:



Progress

My flash programmer/bus controller PC app and Arduino app now talk to the GPIO/SPI CPLD.
Here is how I set the baud rate on the MAX3110E UART now:
fp gpioout 1 1
fp gpiodir 1 1

fp gpioout 1 0
fp clock 8

fp spireg 40
fp spictrl 28
fp clock 50

fp spireg 0a
fp spictrl 28
fp clock 50

fp clock 1
fp gpioout 1 1

fp spireg ff


I also finished the Intel Hex flash programmer. It's not fast, but it works. It will do until I can have flash programming built into the Z-80 code.

So, I decided that I should come up with a way to ensure the CPU is working with something simple. So I decided to try this: which will just spam 'A' out the serial port (I think--need to make sure I have enough NOPs and make sure it really does what I think it does).

This is spam.s:
.area _CODE
;.org 0

; configure GPIO 0 to high
 ld a, #1
 out (0x80), a

; configure GPIO 0 to output
 ld a, #1
 out (0x81), a

; configure GPIO 0 to low
 ld a, #0
 out (0x80), a

; write c0 to MAX3110E (configure)
 ld a, #0xc0
 out (0x83), a

 ld a, #0x28
 out (0x84), a

 nop
 nop
 nop
 nop
 nop
 nop
 nop
 nop


; write 0a to MAX3110E (9600 baud)
 ld a, #0x0a
 out (0x83), a

 ld a, #0x28
 out (0x84), a

 nop
 nop
 nop
 nop
 nop
 nop
 nop
 nop

; configure GPIO 0 to high
 ld a, #1
 out (0x80), a

loop:

; configure GPIO 0 to low
 ld a, #0
 out (0x80), a

; write 40 to MAX3110E (write)
 ld a, #0x40
 out (0x83), a

 ld a, #0x28
 out (0x84), a

 nop
 nop
 nop
 nop
 nop
 nop
 nop
 nop


; write 0a to MAX3110E ('A')
 ld a, #0x41
 out (0x83), a

 ld a, #0x28
 out (0x84), a

 nop
 nop
 nop
 nop
 nop
 nop
 nop
 nop

; configure GPIO 0 to high
 ld a, #1
 out (0x80), a

 jr loop


It's assembled with this:

cygwin-bash$ sdasz80 -o spam.s
cygwin-bash$ sdld -i spam.rel
where -o causes the .rel to be generated and -i causes the linker to produce a .ihx file.

which produces spam.ihx:

:200000003E01D3803E01D3813E00D3803EC0D3833E28D38400000000000000003E0AD3837B
:200020003E28D38400000000000000003E01D3803E00D3803E40D3833E28D384000000004F
:1A004000000000003E41D3833E28D38400000000000000003E01D38018D694
:00000001FF

Those strings of zeroes are the 'nop' opcodes, so it looks reasonable. I have already used the new Intel Hex feature of the flash programmer to program it to the flash. So once the Z-80 is plugged in, it should immediately start spamming 'A' to the serial port.

So, now I'm getting ready to make the connections I need to insert the 2MHz oscillator, RAM, and Z80. I need to:

  • rewire RESET to the button
  • BUSACK to the flash programming system
  • BUSREQ to the flash programming system and pulled high
  • INT pulled high (for now)
  • WAIT pulled permanently high
  • CLK to oscillator
  • oscillator OE pulled permanently high
  • bank switch CPLD CS0 to flash CE
  • bank switch CPLD CS1 to SRAM CE
  • bank switch Aout14-17 to flash and SRAM


Thursday, August 20, 2015

Simplified Timer

I realized that the timer CPLD verilog was overly complicated. So I simplified it:

// This CPLD is a timer circuit that generates a 500Hz tick. It takes a 2MHz
// clock in. Scales that down to 125MHz with a 4 bit counter.
// Then it counts 125 of those in a 7 bit counter that resets on 7c.
// When the reset occurs, the pin_nINT line goes from Hi-Z to low.
//
// The falling edge of pin_nCS pin brings pin_nINT back to Hi-Z.
//
// SCK and Dout are used to read the 3 bit tick counter. This is used
// to ensure that the count is kept synched. Dout is updated on the
// falling edge of SCK and the MSB is shifted first. 
//


module timer (
    input pin_CLK,
    input pin_nCS,
    input pin_SCK,
    output pin_Dout,
    output pin_nINT
    );

    reg [10:0] counter;
    reg [2:0] ticks;
    reg [2:0] tickShift;
    wire nTick;
    reg nInt;
    reg nCS0;
    reg nCS1;
    reg SCK0;

    // Not synthesized. Just for testing
    initial begin
        counter = 0;
        ticks = 0;
    end

    assign nTick = ~(counter == 11'b11111001111); // 124'15 causes 125'0->0'0
    assign pin_nINT = (nInt == 1'b1) ? 1'bz : 1'b0;
    assign pin_Dout = pin_nCS ? 1'bz : tickShift[2];

    always @(posedge pin_CLK) begin
        if (nTick == 1'b0) begin
            counter <= 0;
            ticks <= ticks+1;
        end
        else begin
            counter <= counter+1;
        end

        nCS0 <= pin_nCS;
        nCS1 <= nCS0;

        // Set interrupt on the CLK. Reset it on the falling edge of
        // pin_nCS+2 clocks
        if (nTick == 1'b0) begin
            nInt <= 1'b0; 
        end
        else if ((nCS1 & (~nCS0)) == 1'b1) begin
            nInt <= 1'b1; 
        end

        // search for falling edge of SCK in CLK
        SCK0 <= pin_SCK;
        if (~pin_nCS) begin
            if (SCK0 & ~pin_SCK) begin
                tickShift[2:0] <= { tickShift[1:0], 1'b0 };
            end
        end
        else begin
            tickShift[2:0] <= ticks[2:0];
        end

    end

endmodule

Wednesday, August 19, 2015

Agenda For The Next Few Days


Still a lot to do in the near term future. This will get me to the point where I can say I am working on a computer. Right now, I say I'm working on a thing which will eventually be a computer.
  1. Update flashProg.exe and Arduino to do multiple clock pulses.
  2. Add verbosity and delay to Arduino code using the configuration word that's there but not yet used
  3. Add support for SPI/GPIO CPLD
  4. Add support for MAX3110E UART
  5. Add support for timer CPLD
  6. Program and test the timer CPLD
  7. Finish intel hex file flash programming in flashProg.exe
  8. Write all NOP to flash followed by RST 00H.
  9. Insert Z-80, 2MHz, osc, and make all the required connections.
  10. Ensure it works. <-- at this point, I can say it's a computer
  11. Write crt0.s with startup, interrupt table, RAM test
  12. Write ISR for timer and UART
  13. Write simple driver for UART (open,close,read,write,ioctl)
  14. Write putchar() and getchar()
  15. Output "Hello World!" on startup. Or "It's alive! It's alive!"
I'll decide what's next after that. Obviously, test bank switching, on board flash writing.  Debug monitor. SD card in SPI mode (+ FAT16 file system)? A free RTOS? A Forth-like language?

Timer CPLD

Here is the verilog for the 4th CPLD that I figured out I need. It's a 1ms timer. It will pull the interrupt pin low and return to Hi-Z mode when the interrupt is cleared by reading the tick counter. The tick counter is 3 bits and can be used to ensure some degree of consistency when late servicing the tick interrupt or when using shared interrupts (which I will do). It has a very small pin footprint. Just clock, read-only SPI, chip select, and the interrupt line. This one fits into the smaller M4A5-32/32 so I'll use that.

// This CPLD is a timer circuit that generates a 500Hz tick. It takes a 2MHz
// clock in. Scales that down to 125MHz with a 4 bit counter.
// Then it counts 125 of those in a 7 bit counter that resets on 7c.
// When the reset occurs, the pin_nINT line goes from Hi-Z to low.
//
// The falling edge of pin_nCS pin brings pin_nINT back to Hi-Z.
//
// SCK and Dout are used to read the 3 bit tick counter. This is used
// to ensure that the count is kept synched. Dout is updated on the
// falling edge of SCK and the MSB is shifted first. 
//


module timer (
    input pin_CLK,
    input pin_nCS,
    input pin_SCK,
    output pin_Dout,
    output pin_nINT
    /*
    output [3:0] test_prescaler,
    output [6:0] test_counter,
    output [2:0] test_ticks,
    output [2:0] test_tickShift,
    output test_nCount,
    output test_nTick,
    output test_nInt,
    output test_nCS0,
    output test_nCS1
    */
    );

    /*
    assign test_prescaler = prescaler;
    assign test_counter = counter;
    assign test_ticks = ticks;
    assign test_tickShift = tickShift;
    assign test_nCount = nCount;
    assign test_nTick = nTick;
    assign test_nInt = nInt;
    assign test_nCS0 = nCS0;
    assign test_nCS1 = nCS1;
    */

    reg [3:0] prescaler;
    reg [6:0] counter;
    reg [2:0] ticks;
    reg [2:0] tickShift;
    reg nCount;
    reg nTick;
    reg nInt;
    reg nCS0;
    reg nCS1;
    reg SCK0;
    reg SCK1;

    initial begin
        prescaler = 0;
        counter = 0;
        ticks = 0;
    end

    assign pin_nINT = (nInt == 1'b1) ? 1'bz : 1'b0;

    always @(posedge pin_CLK) begin
        prescaler <= prescaler + 1;
        nCount <= ~(&prescaler); // 0 once every 16 pin_CLK
        nTick <= ~(counter == 7'b1111100); // 124 caused 125->0
        if (nCount == 1'b0) begin
            if (nTick == 1'b0) begin
                counter <= 0;
                ticks <= ticks+1;
            end
            else begin
                counter <= counter+1;
            end
        end

        nCS0 <= pin_nCS;
        nCS1 <= nCS0;

        // Set interrupt on the CLK. Reset it on the falling edge of
        // pin_nCS+2 clocks
        if ((nTick | nCount) == 1'b0) begin
            nInt <= 1'b0; 
        end
        else if ((nCS1 & (~nCS0)) == 1'b1) begin
            nInt <= 1'b1; 
        end

        SCK0 <= pin_SCK;
        SCK1 <= SCK0;
        if (~pin_nCS) begin
            //if (SCK1 & ~SCK0) begin
            if (SCK0 & ~pin_SCK) begin
                tickShift[2:0] <= { tickShift[1:0], 1'b0 };
            end
        end
        else begin
            tickShift[2:0] <= ticks[2:0];
        end

    end
    assign pin_Dout = pin_nCS ? 1'bz : tickShift[2];

endmodule

MAX3110E Works--Hardware for RS232C to the Board

I popped in the 1.8342MHz oscillator (after tying OE high) and the MAX3110E (after tying SHDN high) and started to see if I could get communication to work.The first thing I did was try to configure the MAX chip. It took a couple of tries to get everything all set. Here is the sequence that I need to do:

flashProg.exe reset <-- prep Arduino
flashProg.exe pins 1 <-- prep Arduino

flashProg.exe wp16 80 1
flashProg.exe wp16 81 1 <-- CS# GPIO output enable


Then this code sets the baud rate top 9600:

flashProg.exe wp16 81 1 
flashprog.exe wp16 80 0 <-- CS# low
flashprog.exe wp16 83 c0 <-- c0 means write configure
flashprog.exe wp16 84 28 <-- shift out 8 bits of '83' toward MSB

flashprog.exe pinwrite 1000 0 <-- clock pulses
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000


flashprog.exe rp16 83 <-- read the first 8 bits shifted in

pause 2

flashprog.exe wp16 83 a <- 0a is the baud rate scale value plus other stuff
flashprog.exe wp16 84 28 <- 28 shifts 8 bits of '83' out toward MSB

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000

flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000
flashprog.exe pinwrite 1000 0
flashprog.exe pinwrite 1000 1000


flashprog.exe rp16 83 <-- reads the second 8 bits shifted in
flashProg.exe wp16 80 1 <-- CS high

I had some trouble at the beginning with the actual communication. To start, I connected the RS232C lines to the RX and TX pins of the MAX3110E. Oops. They actually take the 0-5V IO from the buffers (RS232C-TTL inverters) that also live in the MAX3110E. I was supposed to go through those and then to RX and TX. I think I had mechanical connection problems with the port (just sticking wires into holes on the M/F gender changer). And, of course, I was afraid I might have fried the RX and/or TX ports.

So I backed up to do what I should have done. Work in stages instead of trying to get it all at once.

I routed RX to TX and sent data (replace the flashprog.exe wp16 83 c0 with flashprog.exe wp16 83 80) and  flashprog.exe wp16 83 a with flashprog.exe wp16 83 30) where 30 is the digit '0'. And indeed, I received the data I sent. So I knew RX and TX weren't fried.

Then I connected RX and TX to the buffers and tied the other ends of the buffers together. Again, I received the data I sent. So I knew the buffer circuits weren't fried (at not totally fried).

Then I tried to just receive on the RS232C from puTTY. I was finally able to get data. Finally, I connected the transmit side up. And '0' popped up on puTTY's screen. Just as I'd hoped.

Finally, here is a picture:

Tuesday, August 18, 2015

GPIO Write Problem

I just spent some time bangin my head against the wall because I couldn't write and then read the same register in the new CPLD with the GPIO and shift register.

All the lines seemed to be doing what they were supposed to according to my DMM. I finally figured out that the lines were doing a simplified version of actual CPU addressing. And there was no rising edge on the clock while WR# was low. This meant that nothing could get written. Once I fixed that in the Arduino, the writes worked. Whew!

My testing after this has all been good. Shift register is shifting. GPIOs are driving (or not).

MX Anywhere Mouse Repair

I love my MX Anywhere mouse. But I hate the fact that after a year or so, the left click button starts to go bad and registers double-clicks instead of single clicks. Now that I have a soldering iron, I was able to do something about it. I used the right mouse button from an old one whose left button was bad and popped it off. Then I popped off the left button of my current mouse. Then cleaned up the solder pads with some braid. Finally, I popped the right click button onto my current mouse.

The DigiKey part number is EG4686DKR-ND, btw. It's the E-Switch TS20100F070S.

I was afraid that I wouldn't be able to get it off with just one soldering iron. But I was able with very little effort. I didn't need one of those pincer soldering irons or a second normal one.

System Tick

It occurred to me that I don't really have a good way to have a system tick. I tried to fit a counter into the last CPLD. Nothing doing!

So, I think I'll take the dedicated JTAG programmer and make it also work with the computer. So I need a CS, SCK, Dout, and INT which is Hi-Z or low.

The counter will just be a 12 bit counter that pulls INT low on every bit 10 change. and stop on CS going low. It will be possible to clock out bits 12-11. Super simple. Just a 12 bit counter. A 2 bit shift register, and INT pin control. I should be able to squeeze this into a M4A5 32/32.... Maybe. Just 4 pins.

Of course, this will be a tick of 1.024ms.

I could make the counter count to 1000. But I'd rather just deal with the extra .024.

Though, what would it take to do a 1ms tick? Run the 2MHz signal to a 4 bit counter. The output of the 4th bit will be a 125kHz signal. 125kHz is 125 per ms. So, then a 7 bit counter that resets when it reaches 124 (0x7c). This counter resets once every 1/8ms. So then a 3 bit counter to 1ms. Then a 1 bit counter for 2ms. That last bit is for the fact that many sources might cause an interrupt, so it's good to have an extra bit so the ISR knows where the count is at.

But, for now, I'll let this go. I don't really need a tick quite yet. It's just nice to know I can get it with 4 lines on one more CPLD.

That CPLD doesn't have a set of pins for wire wrap. So I'd have to add a header. Or solder one end to the socket. Still lots of options....

BTW, if you don't know what a system tick is, it's a way to have the OS know how long it's been running. It's useful for such things as IO timeouts, process time slicing, time keeping.

I really should have made the system tick something I thought about early on. It's pretty important. The fact that I didn't start to think about it until now is pretty damning. But I did leave enough flexibility in the system. And when I started I did think that CPLDs might be a little more powerful than I'd hoped. Just part of the learning process.

Flash Programmer Stopped Working

I went to try to test my flash programming after adding all the new parts. And it was no longer working. I has last written ascii 123456789 (i.e. 31 31 32 ...) to location 0. But I was reading b1 b2 b3.... Interesting. It's as if the D7 bit were tied high.

A little inspection of the board revealed the source of the error. I tied the power of the CPLD to the wrong pin. To the D7 pin. So I'll have to pop off the wire and move it over one. Whew! That won't be too hard.

Monday, August 17, 2015

Final CPLD in Place!

I successfully programmed the verilog for the GPIO/SPI chip into the CPLD and popped it into place. Next step is to wire it up and use the flash programmer bus controller to see if I can control GPIO, read GPIO, and use the SPI bus.

Here is a picture with all three CPLDs in place:

It is the one in the upper left.

The pin assignments section from the .lco file that I will use to wire it in. I'll start with A7:0, D7:0, RD, WR, and IORQ. Also CLK to the simulated CLOCK from the Arduino.

[LOCATION ASSIGNMENTS]
Layer = OFF;
pins_A_7_ = pin,20,-,B,-;
pins_D_7_ = pin,2,-,A,-;
pin_CLK = pin,11,-,-,-;
pin_nRESET = pin,16,-,B,-;
pin_nIORQ = pin,15,-,B,-;
pin_nRD = pin,27,-,C,-;
pin_nWR = pin,14,-,B,-;
pin_Din = pin,29,-,C,-;
pins_A_6_ = pin,9,-,A,-;
pins_A_5_ = pin,7,-,A,-;
pins_A_4_ = pin,5,-,A,-;
pins_A_3_ = pin,33,-,-,-;
pins_A_2_ = pin,21,-,B,-;
pins_A_1_ = pin,17,-,B,-;
pins_A_0_ = pin,18,-,B,-;
pins_D_6_ = pin,25,-,C,-;
pins_D_5_ = pin,31,-,C,-;
pins_D_4_ = pin,36,-,D,-;
pins_D_3_ = pin,4,-,A,-;
pins_D_2_ = pin,26,-,C,-;
pins_D_1_ = pin,41,-,D,-;
pins_D_0_ = pin,43,-,D,-;
pins_GPIO_7_ = pin,6,-,A,-;
pin_Dout = pin,19,-,B,-;
pin_SCK = pin,3,-,A,-;
pins_GPIO_6_ = pin,24,-,C,-;
pins_GPIO_5_ = pin,28,-,C,-;
pins_GPIO_4_ = pin,42,-,D,-;
pins_GPIO_3_ = pin,8,-,A,-;
pins_GPIO_2_ = pin,30,-,C,-;
pins_GPIO_1_ = pin,39,-,D,-;
pins_GPIO_0_ = pin,37,-,D,-;

Z-80 Code Generation with SDCC

I started looking at how sdcc compiles C for the Z-80. In order to write assembly for the processor, I need to knwo the calling convention. After all, I need to write putchar() and getchar() to use the GPIO/SPI CPLD to talk to the MAX3110E.

The calling convention is that arguments are pushed onto the stack from right to left. Return values are returned in L for 8 bit, HL for 16 bit, and DEHL for 32 bit return values.

The other part that's important to know is who saves registers. The default is that the caller saves them. The problem is when the caller calls a small function that doens't use registers. In this case, it would be nice to let the the callee save the registers. One way of doing this is using the --callee-saves function-list or --callee-saves-all. Also the __naked option can be used on the function definition. Finally, #pragma callee_saves can be used. Except, I didn't notice any of that working. SO right now, I'm concluding that it is not supported on the Z-80 port. So, I'll just assume it's always caller saved and the functions can just use any register (except IX).

I'm not sure why some functions use IX and some don't. But it probably doesn't really matter too much. I just have to know how to use IX if I want and how not to if I don't.

Here is some sample C code to see how the calling convention works.


int plus(int a, int b, int c)
{
    int sum;
    sum = a + b + c;
    return sum;
}


int plusplus(int d, int e, int f)
{
    int sum = 0;
    int i;
    for (i = 0; i < 5; i++)
    {
        sum += plus(d, e, f);
    }
    return sum;
}


Here is the assembly.  The <-- notes are mine.

;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 3.5.2 #9283 (MINGW64)
; This file was generated Mon Aug 17 20:12:13 2015
;--------------------------------------------------------
 .module argtest
 .optsdcc -mz80
 
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
 .globl _plusplus
 .globl _plus
;--------------------------------------------------------
; special function registers
;--------------------------------------------------------
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
 .area _DATA
;--------------------------------------------------------
; ram data
;--------------------------------------------------------
 .area _INITIALIZED
;--------------------------------------------------------
; absolute external ram data
;--------------------------------------------------------
 .area _DABS (ABS)
;--------------------------------------------------------
; global & static initialisations
;--------------------------------------------------------
 .area _HOME
 .area _GSINIT
 .area _GSFINAL
 .area _GSINIT
;--------------------------------------------------------
; Home
;--------------------------------------------------------
 .area _HOME
 .area _HOME
;--------------------------------------------------------
; code
;--------------------------------------------------------
 .area _CODE
;argtest.c:1: int plus(int a, int b, int c)
; ---------------------------------
; Function plus
; ---------------------------------
_plus::
;argtest.c:4: sum = a + b + c;
 ld hl,#4 <-- No IX. Why?
 add hl,sp
 ld iy,#2
 add iy,sp
 ld a,0 (iy)
 add a, (hl)
 ld d,a
 ld a,1 (iy)
 inc hl
 adc a, (hl)
 ld e,a
 ld a,d
 ld hl,#6
 add hl,sp
 add a, (hl)
 ld d,a
 ld a,e
 inc hl
 adc a, (hl)
 ld h,a
 ld l, d
;argtest.c:5: return sum;
 ret
;argtest.c:9: int plusplus(int d, int e, int f)
; ---------------------------------
; Function plusplus
; ---------------------------------
_plusplus::
 call ___sdcc_enter_ix <-- sets up ix -- see below
;argtest.c:11: int sum = 0;
;argtest.c:13: for (i = 0; i < 5; i++)
 ld hl,#0x0000
 ld e,l
 ld d,h
00102$:
;argtest.c:15: sum += plus(d, e, f);
 push hl  <-- caller save HL for for _plus (sum)
 push de  <-- caller save DE for for _plus (i)
 ld c,8 (ix)
 ld b,9 (ix)
 push bc
 ld c,6 (ix)
 ld b,7 (ix)
 push bc
 ld c,4 (ix)
 ld b,5 (ix)
 push bc
 call _plus
 pop af <-- throw away passed parameter d
 pop af <-- throw away passed parameter e
 pop af <-- throw away passed parameter f
 ld c,l <-- return value low
 ld b,h <-- return value high
 pop de <-- restore caller saved registers
 pop hl <-- restore caller saved registers
 add hl,bc
;argtest.c:13: for (i = 0; i < 5; i++)
 inc de
 ld a,e
 sub a, #0x05
 ld a,d
 rla
 ccf
 rra
 sbc a, #0x80
 jr C,00102$
;argtest.c:17: return sum;
 pop ix
 ret <-- HL already has sum
 .area _CODE
 .area _INITIALIZER
 .area _CABS (ABS)



This is part of the library:


;--------------------------------------------------------------------------
;  crtenter.s
;
;  Copyright (C) 2015, Alan Cox, Philipp Klaus Krause
;
;  This library is free software; you can redistribute it and/or modify it
;  under the terms of the GNU General Public License as published by the
;  Free Software Foundation; either version 2, or (at your option) any
;  later version.
;
;  This library is distributed in the hope that it will be useful,
;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;  GNU General Public License for more details.
;
;  You should have received a copy of the GNU General Public License
;  along with this library; see the file COPYING. If not, write to the
;  Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
;   MA 02110-1301, USA.
;
;  As a special exception, if you link this library with other files,
;  some of which are compiled with SDCC, to produce an executable,
;  this library does not by itself cause the resulting executable to
;  be covered by the GNU General Public License. This exception does
;  not however invalidate any other reasons why the executable file
;   might be covered by the GNU General Public License.
;--------------------------------------------------------------------------

 .area   _CODE

 .globl ___sdcc_enter_ix

; Factor out some start of function code to reduce code size

___sdcc_enter_ix:
 pop hl ; return address
 push ix ; save frame pointer
 ld ix, #0
 add ix, sp ; set ix to the stack frame
 jp (hl) ; and return

Soldering Finished

If all goes well, this is last of the soldering. Here are some final pictures.

The back and front of the board with all sockets in place:



Power to the new CPLD and communication parts:




Sunday, August 16, 2015

Power Delivery and SDCC Research

I made some wires for delivering power to the last of the chips (GPIO/SPI, 1.342MHz oscillator, MAX3110E). Here they are:



It is also Tyler's last day at one of my laboratories, Liberty, so there is Hamm's and a shot of bourbon in the picture. (Yes, I nurse my shots).

I was planning to solder them in today, but probably best to wait until I'm a little less inebriated. I've also been reading about the fine details of using the SDCC compiler suite to start to create code that the Z-80 can run.

I also got my RS232C port delivered to the Amazon locker. So I have to go pick that up. Then I'll be able to test the CPLD and the MAX3110E once all this is soldered in.

Saturday, August 15, 2015

Soldering The GPIO/SPI CPLD Socket

Since I'll start testing the SPI GPIO CPLD pretty soon, I figured there was no better time to start soldering the socket than right now. So I did.  Here it is 1/4 done:

And here it is with all the connections except power:


Next I need to get the power in. And also to the oscillator socket and MAX3110E UART socket. That power will be about the last of the soldering.

GPIO SPI CPLD

In order to connect the UART (MAX3110E) part to the Z-80, I'll use a CPLD that does SPI. My first CPLD plan was fairly specific to the SPI of the UART part. But, it makes more sense to make something a little more general purpose. So, I came up with the following verilog design.

This design should allow me to do not just SPI but also I2C. And it has 8 GPIO.

The MAX3110E requires that its CS goes low while SCLK is low. Then a 16 bit data exchange takes place. Then while SCLK is low, CS goes high. It updates Dout and expects Din to be updated on SCLK falling edge and clocks in and expects the user to clock in on the rising edge. Sixteen times.

So this will be able to do it by using a GPIO as CE.

Here some example Z-80 assembly that would use the CPLD to communicate with the MAX3110E:


; Assume HL is the data to write out to MAX3110E and HL will have the
; exchanged MAX3110E data upon return
; CS# low
    IN A, (0x80)         ; get current GPIOs
    AND A, ~0x01         ; set bit 0 to 0
    OUT (0x80), A        ; write GPIO back out

; exchange H
    OUT (0x83), H        ; load shift register
    OUT (0x84), 0x48     ; shift 8 toward MSB
    NOP                  ; give it time to work
    NOP
    IN H, (0x83)         ; read in first 8 bits from MAX3110E

; exchange L
    OUT (0x83), L        ; load shift register
    OUT (0x84), 0x48     ; shift 8 toward MSB
    NOP                  ; give it time to work
    NOP
    IN L, (0x83)         ; read in last 8 bits from MAX3110E

; CS# high
    IN A, (0x80)         ; get current GPIOs
    OR A, 0x01           ; set bit 0 to 0
    OUT (0x80), A        ; write GPIO back out

    RET


Here is the verilog:

// SPI module
//
//  This CPLD will provide 8 GPIO and limited serial IO for SPI and/or I2C
//      The registers are:
//      0x80: GPIO outvalues
//          A read/write register holding values to be written on GPIO lines
//          that are in output mode
//      0x81: GPIO direction (1=out, 0=in)
//          A read/write register to control the input/output mode of the GPIO
//          lines
//      0x82: GPIO levels
//          A read only register with the current GPIO levels.
//      0x83: shift register (shifted based on control register)
//          A read/write register with data to be shifted in/out
//      0x84: shift control
//          bits 3-0: count
//              0: no shifting
//              1-8: shift by the amount
//              9-15: not supported
//          bit 4: 1=HiZ Dout and use it for input, 0=use Din for input
//              Use 1 for I2C type buses where the clock master controls
//              a bidirectional data line
//              Use 0 for full duplex buses with separate Din and Dout
//          bit 5: 1=shift toward MSB 0=SCK shift toward LSB
//              Use 1 to shift up. If the count is less than 8, the value
//              written to the shift register must be shifted by the CPU
//              Data is read into the LSB
//              Use 0 to shift down. Data is read into the MSB
//          bit 6: 1=SCK goes write high read low 0=SCK goes write low read high
//              Internally, the counter is decremented every 2 pin_CLK. The
//              first beat outputs Dout. The second shifts Din in.
//              Use 0 if the first beat is 0 and the second is 1. i.e. read on
//              rising edge
//              Use 1 if the first beat is 1 and the second is 0. i.e. read on
//              falling edge
//          bit 7: 1=SCK default high, 0=SCK default low
//              Use this bit to set the SCK before and after the actual
//              shift operation
//
//  Usage:
//      0x80, 0x81, and 0x82 control 8 GPIO pins
//      read and write to 0x83 for data shifted in/out
//      Use 0x84 to write a write only count. This will immediately start
//      to shift the shift register in/out. Use a NOP or two to ensure it
//      is complete before reading or writing
//      Use bit5 =1 for 2 wire serial and =0 for 3 wire serial.
//
`define IO_VALUE 5'b10000
`define IO_RANGE 7:3
`define ACTIVELOW 1'b0

module GPIO_SPI(
    // 2MHz clock for CPU
    input pin_CLK,

    // RESET
    input pin_nRESET,

    // CPU ddress bus
    input [7:0] pins_A,

    // CPU data bus
    inout [7:0] pins_D,

    // CPU pins
    input pin_nIORQ,
    input pin_nRD,
    input pin_nWR,
    
    // Pins to UART
    input pin_Din,
    inout pin_Dout,
    output reg pin_SCK,

    // GPIO
    inout [7:0]pins_GPIO
);

    // registers
    reg [7:0] regGpioOutLevel;
    reg [7:0] regGpioDirection;
    reg [7:0] regShift;
    reg [3:0] regCounter;
    reg regDoutIsDin;
    reg regShiftTowardMSB;
    reg regSckHigh;
    reg regDefaultSCK;

    // shift
    reg sck;
    reg Dout;
    wire Din;
    
    // Internal databus for output
    reg [7:0] Dint;

    // IO request in the addressable range of this module
    wire nInRange;

    assign Din = regDoutIsDin ? pin_Dout : pin_Din;
    assign pin_Dout = regDoutIsDin ? 1'bz : Dout;

    // An IO read in the addressable range of this module
    assign nInRange = ((pins_A[`IO_RANGE] == `IO_VALUE)? 1'b0 : 1'b1) | pin_nIORQ;

    // Bidirectional databus control
    assign pins_D = ((pin_nRD | nInRange) == `ACTIVELOW) ? Dint : 8'bzzzzzzzz;

    assign pins_GPIO[0] = regGpioDirection[0] ? regGpioOutLevel[0] : 1'bz;
    assign pins_GPIO[1] = regGpioDirection[1] ? regGpioOutLevel[1] : 1'bz;
    assign pins_GPIO[2] = regGpioDirection[2] ? regGpioOutLevel[2] : 1'bz;
    assign pins_GPIO[3] = regGpioDirection[3] ? regGpioOutLevel[3] : 1'bz;
    assign pins_GPIO[4] = regGpioDirection[4] ? regGpioOutLevel[4] : 1'bz;
    assign pins_GPIO[5] = regGpioDirection[5] ? regGpioOutLevel[5] : 1'bz;
    assign pins_GPIO[6] = regGpioDirection[6] ? regGpioOutLevel[6] : 1'bz;
    assign pins_GPIO[7] = regGpioDirection[7] ? regGpioOutLevel[7] : 1'bz;


    // Keep internal databus updated
    always @(*) begin
        case (pins_A[2:0])
            3'b000: begin
                Dint <= regGpioOutLevel[7:0];
            end
            3'b001: begin
                Dint <= regGpioDirection[7:0];
            end
            3'b010: begin
                Dint <= pins_GPIO[7:0];
            end
            3'b011: begin
                Dint <= regShift[7:0];
            end
            default: begin
                Dint <= 8'bxxxxxxxx;
            end
        endcase
    end

    always @(posedge pin_CLK) begin

        if (pin_nRESET == `ACTIVELOW) begin
            regGpioDirection <= 0;
            regCounter <= 0;
            regDoutIsDin <= 0;
            regShiftTowardMSB <= 0;
            regSckHigh <= 0;
            sck <= 0;
        end
        else begin
            if ((nInRange | pin_nWR) == `ACTIVELOW) begin
                case (pins_A[2:0])
                3'b000: begin
                    regGpioOutLevel[7:0] <= pins_D[7:0];
                end
                3'b001: begin
                    regGpioDirection[7:0] <= pins_D[7:0];
                end
                3'b011: begin
                    regShift[7:0] <= pins_D[7:0];
                end
                3'b100: begin
                    regCounter[3:0] <= pins_D[3:0];
                    regDoutIsDin <= pins_D[4];
                    regShiftTowardMSB <= pins_D[5];
                    regSckHigh <= pins_D[6];
                    regDefaultSCK = pins_D[7];
                    sck <= 1'b0;
                end
                endcase
                pin_SCK <= regDefaultSCK;
            end
            else if ((|regCounter) == 1'b1) begin
                if (sck == 1'b0) begin
                    Dout <= regShiftTowardMSB ? regShift[7] : regShift[0];
                end
                else begin
                    regCounter <= regCounter - 1;
                    if (regShiftTowardMSB) begin
                        regShift <= { regShift[6:0], Din };
                    end
                    else begin
                        regShift <= { Din, regShift[7:1] };
                    end
                end
                sck <= ~sck;
                pin_SCK <= sck ^ regSckHigh;
            end
            else begin
                pin_SCK <= regDefaultSCK;
            end
        end
    end

endmodule

Thursday, August 13, 2015

Preparation For Mounting SRAM and Z-80

Here is a list of things I have to do before finally mounting the SRAM and Z-80.

Connect Aout of the MCU to the address bus
Connect CS0 to flash CE and CS1 to SRAM CE
Connect pullups to INT, BUSREQ, WAIT
Connect a RESET button
Pop in the 2MHz oscillator
Connect flash programmer CPLD ENABLE to BUSACK
Pull up (down?) the output enable line of the oscillator

I'll make sure once again that the flash programmer Hi-Z's everything on the bus when it's supposed to. Then pop in the parts.

Seeing if the CPU works will take an oscilloscope. They have at least one down at Metrix CreateSpace a few blocks from where I live. So I'll power up down there.

Since I have no output right now, it will be tough to know if the processor is working properly. I have to get the UART working pretty quick and write some bug free code to use it. Maybe I should do that before mounting the CPU... I suppose I better figure out a way to get another COM port on my Surface Pro 3....

Note To Self

Lay in and solder all power and caps before any wire wrapping in any given area of the board. Otherwise, you run into soldering really close to wirewrap. I've got most of the wires laid in and will solder them in tomorrow morning. Here are some pictures. First the front:


Then close-ups of the back.

Here is some copper wire peeking up and wrapping around the power posts:

The cap for the 2 MHz clock chip:

Caps for the UART and 1.8342MHz clock. It's gonna be a tight squeeze!


Flash File Program (.bin) And Aout of MCU Work

I finally tried to write a file to the flash. And it wrote. Working on the CRC-32 now. So far it doesn't seem to match cygwin's cksum (which doesn't output in hex for  some reason.)

Arduino Code

This is going to be a long post. It is parts of the Arduino code that runs the flash programmer and bus.
SetupLoop.cpp


#include "Flash.h"
#include "serialIF.h"
#include "Interpreter.h"
#include "Arduino.h"
#include "CPLD.h"

// Arduino uses 'setup' and 'loop' instead of 'main'

void setup() {

    pinMode(13, OUTPUT);

    // Blink LED twice
    digitalWrite(13, HIGH);
    delay(500);
    digitalWrite(13, LOW);
    delay(500);
    digitalWrite(13, HIGH);
    delay(500);
    digitalWrite(13, LOW);
}

void loop() {

    Interpreter interpreter;
    interpreter.Loop();

}

SerialIF.h

#ifndef INCLUDE_SERIALIF_H
#define INCLUDE_SERIALIF_H

// The SerialIF class provides application level access to communication facilities
// c must be:
//      > command
//      < response
//      ! text
//      $ payload
//      . ready for payload
// Each exchange has an id
// packet ID is used for payloads
class SerialIF
{
 public:
     SerialIF();
     ~SerialIF();

public:
    // Basic send. See class doc for 'c'. response is a buffer of up to 256. length is 1-256, 
    bool Send(char c, const uint8_t* response, int16_t length, uint8_t id, uint16_t packetId);

    // Basic send. See class doc for 'c'. buffer is a buffer of 256 bytes. length is 1-256, 
    int16_t Receive(char *pC, uint8_t* buffer, uint8_t* pId, uint16_t *pPacketId);
};

#endif


SerialIF.cpp

#include "SerialIF.h"

#define HEADER_LENGTH (6)
#define TIMEOUT (10000)
#define BAUD_RATE (9600)

///////////////////////////////////////////////////////////////////////////////
//
//      Object section
//

SerialIF::SerialIF()
{
    Serial.begin(BAUD_RATE);
    Serial.setTimeout(TIMEOUT);
}

SerialIF::~SerialIF()
{
    Serial.end();
}

///////////////////////////////////////////////////////////////////////////////
//
//      Low Level Read/Write section
//

// A single communication consists of a header and the data
// Header is 6 bytes:
//      [0] A character:
//          '>' PC to Arduino command
//          '<' Arduino to PC response
//          '!' In progess Arduino -> PC text
//          '$' Payload packet (256 bytes unless the last one)
//          '.' OK to send next payload packet
//      [1] length 1-255, 0 means 256
//      [2] 8 bit transaction id
//      [3] sum adjustment. Adjust this so the sum of header and data is 0xff
//      [4] 2 byte packet # high byte
//      [5] 2 byte packet # low byte
//
//  The header is hidden from the interface. The user simply used the 'command', 'response', 
//  'eventString', or 'buffer' and the proper calling function
//

bool SerialIF::Send(char c, const uint8_t* response, int16_t length, uint8_t id, uint16_t packetId)
{
    // Setup up header
    uint8_t header[HEADER_LENGTH];
    header[0] = c;
    header[1] = (uint8_t)length; // 256 converts to 0 in cast
    header[2] = id;
    header[3] = 0;
    header[4] = (byte)(packetId >> 8);
    header[5] = (byte)packetId;

    // Determine sum for error checking
    uint8_t sum = 0;
    for (int16_t i = 0; i < HEADER_LENGTH; i++)
    {
        sum += header[i];
    }
    for (int16_t i = 0; i < length; i++)
    {
        sum += response[i];
    }
    header[3] = (uint8_t)~sum;
    
    // ensure it all gets send
    const uint8_t* p = header;
    int len = HEADER_LENGTH;
    int sent = 0;
    while (sent < len)
    {
        int got = Serial.write(p, len - sent);
        if (got < 0 || got > len - sent)
        {
            digitalWrite(13, HIGH);
            return false;
        }
        sent += got;
        p += got;
    }
    len = length;
    p = response;
    sent = 0;
    while (sent < len)
    {
        int got = Serial.write(p, len - sent);
        if (got < 0 || got > len - sent)
        {
            digitalWrite(13, HIGH);
            return false;
        }
        sent += got;
        p += got;
    }
    return true;
}

int16_t SerialIF::Receive(char *pC, uint8_t* buffer, uint8_t* pId, uint16_t *pPacketId)
{
    uint32_t stopOn = millis() + TIMEOUT;
    int16_t received = 0;
    int16_t expecting = HEADER_LENGTH;
    uint8_t header[HEADER_LENGTH];
    uint8_t* p;

    // Get header
    p = &header[0];
    while (millis() < stopOn && received < expecting)
    {
        size_t got = Serial.readBytes(p, expecting - received);
        if (got > (size_t)(expecting - received))
        {
            digitalWrite(13, HIGH);
            return -1;
        }
        p += got;
        received += got;
    }

    if (received == expecting)
    {
        // extract the type character
        *pC = header[0];

        // get the data size
        expecting = header[1];
        if (expecting == 0)
        {
            expecting = 256;
        }

        // get the ID
        *pId = header[2]; // return to user
        *pPacketId = (header[4] << 8) | header[5];
    }
    else
    {
        return -1;
    }

    // Get the data portion
    p = buffer;
    *p = 'x'; // destroy first data
    received = 0;
    while (millis() < stopOn && received < expecting)
    {
        size_t got = Serial.readBytes(p, expecting - received);
        if (got > (size_t)(expecting - received))
        {
            digitalWrite(13, HIGH);
            return -1;
        }
        received += got;
        p += got;
    }

    if (received == expecting)
    {
        // ensure the sum is 255
        uint8_t sum = 0;
        for (int16_t i = 0; i < HEADER_LENGTH; ++i)
        {
            sum += header[i];
        }
        for (int16_t i = 0; i < expecting; ++i)
        {
            sum += buffer[i];
        }
        if (sum != 255)
        {
            return -1;
        }
    }
    else
    {
        return -1;
    }

    return received;
}


CPLD.h

#ifndef INCLUDE_CPLD_H
#define INCLUDE_CPLD_H

// The CPLD class handles low level access to the CPLD which controls the 39SF020A flash device
class CPLD
{
protected:
    CPLD();

    // Send CPLD reset sequence
    void ResetSequence();

    // Update address bits 3:0
    void UpdateAddress_3_0(uint32_t addr);

    // Update address bits 7:4
    void UpdateAddress_7_4(uint32_t addr);

    // Update address bits 17:8
    void UpdateAddress_17_8(uint32_t addr);

    // Update chip control where bits 3-0 are drive, CS*, WR*, and RD*
    void UpdateChip(int16_t chip);

    // Update the CTRL register that tells which register to pulse data in and out
    void UpdateCtrl(int16_t ctrl);

    // Swaps a data byte on Din/Dout
    uint8_t ReadWrite(uint8_t data);

    // Pulse CLK high and low
    void PulseClk();

    // Pulse nCTRL low and high
    void PulseCtrl();

public:
    // Control pins directly
    bool DirectWrite(uint16_t mask, uint16_t values);

    // Read current state of pins
    bool DirectRead(uint16_t mask, uint16_t* pValues);

private:
    // Update an arbitrary part of the address bits
    void UpdateAddress_start_end(uint32_t addr, int16_t startBit, int16_t endBit);

    // Keep track of last control address to know which parts to avoid shifting out
    int16_t m_lastCtrl;

public:
    static const int16_t PIN_nCTRL = 2;
    static const int16_t PIN_Dout = 3;
    static const int16_t PIN_Din = 4;
    static const int16_t PIN_CLK = 5;
    static const int16_t PIN_ENABLE = 6;
};

#endif


CPLD.cpp

#include "CPLD.h"

// Adds delay if necessary for viewing pins with DMM
#define SUPER_SLO_MO()

#define LAST_CTRL_INIT (-1)

CPLD::CPLD()
{
    m_lastCtrl = LAST_CTRL_INIT;
}

///////////////////////////////////////////////////////////////////////////////
//
//      Reset section
//

void CPLD::ResetSequence()
{
    // initialize pins before setting output mode
    digitalWrite(PIN_ENABLE, HIGH);
    digitalWrite(PIN_CLK, HIGH);
    digitalWrite(PIN_nCTRL, HIGH);
    digitalWrite(PIN_Din, HIGH);

    // set pin modes
    pinMode(PIN_ENABLE, OUTPUT);
    pinMode(PIN_nCTRL, OUTPUT);
    pinMode(PIN_Dout, INPUT);
    pinMode(PIN_Din, OUTPUT);
    pinMode(PIN_CLK, OUTPUT);

    // Execute reset sequence
    PulseClk();
    PulseClk();
    digitalWrite(PIN_nCTRL, LOW);
    delayMicroseconds(1);
    PulseClk();
    PulseClk();
    PulseClk();
    digitalWrite(PIN_nCTRL, HIGH);
    digitalWrite(PIN_nCTRL, HIGH);
    delayMicroseconds(1);
    SUPER_SLO_MO();
    digitalWrite(PIN_ENABLE, LOW);
    delayMicroseconds(1);
    SUPER_SLO_MO();
}

///////////////////////////////////////////////////////////////////////////////
//
//      Address Shift Register section
//

void CPLD::UpdateAddress_3_0(uint32_t addr)
{
    UpdateCtrl(0x01);
    UpdateAddress_start_end(addr, 3, 0);
}

void CPLD::UpdateAddress_7_4(uint32_t addr)
{
    UpdateCtrl(0x02);
    UpdateAddress_start_end(addr, 7, 4);
}

void CPLD::UpdateAddress_17_8(uint32_t addr)
{
    UpdateCtrl(0x03);
    UpdateAddress_start_end(addr, 17, 8);
}
void CPLD::UpdateAddress_start_end(uint32_t addr, int16_t endBit, int16_t startBit)
{
    int16_t i;
    for (i = startBit; i <= endBit; ++i)
    {
        uint32_t mask = 1L << i;
        digitalWrite(PIN_Din, (addr & mask) ? HIGH : LOW);
        digitalWrite(PIN_Din, (addr & mask) ? HIGH : LOW); // delay
        SUPER_SLO_MO();
        PulseClk();
    }
}

// Chip bits are:
//  3   drive id not nRD low, or read on rising edge
//  2   nCS
//  1   nWR
//  0   nRD
void CPLD::UpdateChip(int16_t chip)
{
    int16_t i;
    UpdateCtrl(0x07);
    for (i = 0; i < 4; ++i)
    {
        digitalWrite(PIN_Din, (chip & 1) ? HIGH : LOW);
        digitalWrite(PIN_Din, (chip & 1) ? HIGH : LOW); // delay
        SUPER_SLO_MO();
        PulseClk();
        chip >>= 1;
    }
    PulseCtrl();
}

///////////////////////////////////////////////////////////////////////////////
//
//      CPLD Control section
//

// Control register
void CPLD::UpdateCtrl(int16_t ctrl)
{
    int16_t i;
    digitalWrite(PIN_nCTRL, LOW);
    SUPER_SLO_MO();
    if (true || m_lastCtrl != ctrl)
    {
        m_lastCtrl = ctrl;
        for (i = 0; i < 3; ++i)
        {
            int16_t bit = ctrl & 1;
            ctrl = ctrl >> 1;
            digitalWrite(PIN_Din, bit ? HIGH : LOW);
            digitalWrite(PIN_Din, bit ? HIGH : LOW); // delay
            SUPER_SLO_MO();
            PulseClk();
        }
    }
    else
    {
        delayMicroseconds(1);
    }
    digitalWrite(PIN_nCTRL, HIGH);
    SUPER_SLO_MO();
}

// clock exchange 8 bits on 'data'->Din and Dout->return.
uint8_t CPLD::ReadWrite(uint8_t data)
{
    int16_t i;
    uint8_t outData = 0;
    UpdateCtrl(0x00);
    for (i = 0; i < 8; ++i)
    {
        digitalWrite(PIN_Din, (data & 1) ? HIGH : LOW);
        digitalWrite(PIN_Din, (data & 1) ? HIGH : LOW); // delay
        SUPER_SLO_MO();
        PulseClk();
        outData >>= 1;
        if (digitalRead(PIN_Dout) == HIGH)
        {
            outData |= 0x80;
        }
        data >>= 1;
    }
    return outData;
}

// Brings clk high then low
void CPLD::PulseClk()
{
    digitalWrite(PIN_CLK, HIGH);
    digitalWrite(PIN_CLK, HIGH); // delay
    SUPER_SLO_MO();
    digitalWrite(PIN_CLK, LOW);
    digitalWrite(PIN_CLK, LOW); // delay
    SUPER_SLO_MO();
}

// Brings nCTRL low then high -- Use to strobe bus control
void CPLD::PulseCtrl()
{
    digitalWrite(PIN_nCTRL, LOW);
    digitalWrite(PIN_nCTRL, LOW); // delay
    SUPER_SLO_MO();
    digitalWrite(PIN_nCTRL, HIGH);
    digitalWrite(PIN_nCTRL, HIGH); // delay
    SUPER_SLO_MO();
}

///////////////////////////////////////////////////////////////////////////////
//
//      Direct Arduino Pin Access section
//

bool CPLD::DirectWrite(uint16_t mask, uint16_t values)
{
    for (int i = 0; i < 13; i++)
    {
        uint16_t bit = 1 << i;
        if (mask & bit)
        {
            digitalWrite(i, (values & bit) ? (HIGH) : (LOW));
        }
    }
    return true;
}

bool CPLD::DirectRead(uint16_t mask, uint16_t* pValues)
{
    *pValues = 0;
    for (int i = 0; i < 13; i++)
    {
        uint16_t bit = 1 << i;
        if (mask & bit)
        {
            if (digitalRead(i))
            {
                *pValues |= bit;
            }
        }
    }
    return true;
}


Bus.h

#ifndef INCLUDE_BUS_H
#define INCLUDE_BUS_H
#include "CPLD.h"

// The Bus class provides access to the bus on the output of the CPLD
class Bus : public CPLD
{
public:
    Bus();
    ~Bus();

    // Sets the input/output pin mode
    virtual bool EnablePins(bool enable);

    // Resets the Bus
    virtual bool Reset(bool force);

    // Write data to *ptr.
    // NOTE: This is a NOT toggle bit operation, just sends data to *ptr
    // returns true if successful
    void Write(uint32_t ptr, uint8_t data);

    // Read data at *ptr into *pData
    // returns true if successful
    void Read(uint32_t, uint8_t* pData);

    // Read data at last *ptr into *pData
    // returns true if successful
    void Read(uint8_t* pData);

    // Updates entire address bus
    void UpdateAddress(uint32_t addr);

protected:
    // Update address bits only on parts that are different
    void UpdateAddressDiff(uint32_t addr);

    // Pulse RD
    void PulseRD();

    // Pulse WR
    void PulseWR();

private:
    // Keep track of last programmed address to know which parts to avoid shifting out
    uint32_t m_lastAddr;

    // Keep track of whether the CPLD has been reset
    bool m_reset;
};

#endif


Bus.cpp

#include "Bus.h"

#define LAST_ADDR_INIT (0xffffffff)

Bus::Bus() : CPLD()
{
    m_lastAddr = LAST_ADDR_INIT;
    m_reset = false;
}

Bus::~Bus()
{
}

///////////////////////////////////////////////////////////////////////////////
//
//      Reset section
//

bool Bus::Reset(bool force)
{
    if (force || !m_reset)
    {
        ResetSequence();

        m_lastAddr = LAST_ADDR_INIT;
        m_reset = true;
    }
    return true;
}

bool Bus::EnablePins(bool enable)
{
    if (enable)
    {
        pinMode(PIN_ENABLE, OUTPUT);
        pinMode(PIN_nCTRL, OUTPUT);
        pinMode(PIN_Dout, INPUT);
        pinMode(PIN_Din, OUTPUT);
        pinMode(PIN_CLK, OUTPUT);
    }
    else
    {
        // TODO when pullups are on board, make all these revert to INPUT
        pinMode(PIN_ENABLE, INPUT_PULLUP);
        pinMode(PIN_nCTRL, INPUT_PULLUP);
        pinMode(PIN_Dout, INPUT);
        pinMode(PIN_Din, INPUT_PULLUP);
        pinMode(PIN_CLK, INPUT_PULLUP);
    }
    return true;
}


///////////////////////////////////////////////////////////////////////////////
//
//      Basic Operation section
//


void Bus::Write(uint32_t addr, uint8_t data)
{
    UpdateAddressDiff(addr);
    ReadWrite(data);
    PulseWR();
}

void Bus::Read(uint32_t addr, uint8_t* pData)
{
    UpdateAddressDiff(addr);
    PulseRD();
    *pData = ReadWrite(0);
}

void Bus::Read(uint8_t* pData)
{
    PulseRD();
    *pData = ReadWrite(0);
}

///////////////////////////////////////////////////////////////////////////////
//
//      Address Shift Register section
//

// Updates entire address bus
void Bus::UpdateAddress(uint32_t addr)
{
    UpdateAddress_3_0(addr);
    UpdateAddress_7_4(addr);
    UpdateAddress_17_8(addr);
}

// Updates address bus shifting only those shift registers that have changed
void Bus::UpdateAddressDiff(uint32_t addr)
{
    if (m_lastAddr == LAST_ADDR_INIT)
    {
        UpdateAddress(addr);
    }
    else
    {
        uint32_t diffs = (uint32_t)(addr ^ m_lastAddr);
        if (diffs & 0x0000f)
        {
            UpdateAddress_3_0(addr);
        }
        if (diffs & 0x000f)
        {
            UpdateAddress_7_4(addr);
        }
        if (diffs & 0x3ff00UL)
        {
            UpdateAddress_17_8(addr);
        }
    }
}

///////////////////////////////////////////////////////////////////////////////
//
//      Bus Control Control section
//

//  D[7:0]  -------------------------
//  nCS     ^^^\_________________/^^^
//  nRD     ^^^\_________________/^^^
//  nWR     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  read                   *
//  Reads when drive goes high (*)
void Bus::PulseRD()
{
    UpdateChip(0xa); // CS, RD
    UpdateChip(0x2); // drive, CS, RD
    UpdateChip(0xa); // CS, RD
    UpdateChip(0xf); // done
}

//  D[7:0]  -[                     ]-
//  nCS     ^^^\_________________/^^^
//  nRD     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  nWR     ^^^^^^^\_________/^^^^^^^
//  Writes when nWR goes low
void Bus::PulseWR()
{
    UpdateChip(0x3); // drive, CS
    UpdateChip(0x1); // drive, CS, WR
    UpdateChip(0x3); // drive, CS
    UpdateChip(0xf); // done
}



Flash.h

#ifndef INCLUDE_FLASH_H
#define INCLUDE_FLASH_H

#include "Bus.h"

// The Flash class handles application level interface to the flash
class Flash
{
public:
    Flash(Bus& bus);

    // Resets the CPLD
    bool ForceReset();

    // Resets the CPLD if it hasn't been reset yet
    bool Reset();

    // Programs data to *ptr.
    // NOTE: This is a toggle bit operation
    // i.e. it puts the flash into byte program mode
    // and waits for the flash to finish
    // returns true if successful
    bool Program(uint32_t addr, uint8_t data);

    // Read data at *ptr into *pData
    // returns true if successful
    bool Read(uint32_t addr, uint8_t* pData);

    // Erases 4KB block with addr
    // NOTE: This is a toggle bit operation
    // returns true if successful
    bool EraseSector(uint32_t addr);

    // Erases entire chip
    // NOTE: This is a toggle bit operation
    // returns true if successful
    bool EraseChip();

protected:
    // Reads until the toggle bits stop toggling
    // Returns true if the read data matches expected
    // Use expected of 0xff for erase operations
    bool Toggle(uint8_t expected);

private:
    // Low level interface
    Bus m_bus;
};

#endif



Flash.cpp

#include "Bus.h"
#include "Flash.h"

Flash::Flash(Bus& bus)
{
    m_bus = bus;
}

///////////////////////////////////////////////////////////////////////////////
//
//      Byte Access section
//

bool Flash::Program(uint32_t addr, uint8_t data)
{
    m_bus.Write(0x5555, 0xaa);
    m_bus.Write(0x2aaa, 0x55);
    m_bus.Write(0x5555, 0xa0);
    m_bus.Write(addr, data);
    return Toggle(data);
}

bool Flash::Read(uint32_t addr, uint8_t* pData)
{
    m_bus.Read(addr, pData);
    return true;
}


///////////////////////////////////////////////////////////////////////////////
//
//      Erase section
//

bool Flash::EraseSector(uint32_t addr)
{
    m_bus.Write(0x5555, 0xaa);
    m_bus.Write(0x2aaa, 0x55);
    m_bus.Write(0x5555, 0x80);
    m_bus.Write(0x5555, 0xaa);
    m_bus.Write(0x2aaa, 0x55);
    m_bus.Write(addr, 0x30);
    return Toggle(0xff);
    m_bus.Write(0x2aaa, 0xf0);
}

bool Flash::EraseChip()
{
    m_bus.Write(0x5555, 0xaa);
    m_bus.Write(0x2aaa, 0x55);
    m_bus.Write(0x5555, 0x80);
    m_bus.Write(0x5555, 0xaa);
    m_bus.Write(0x2aaa, 0x55);
    m_bus.Write(0x5555, 0x10);
    return Toggle(0xff);
}

///////////////////////////////////////////////////////////////////////////////
//
//      Reset section
//

bool Flash::Reset()
{
    return m_bus.Reset(false);
}

bool Flash::ForceReset()
{
    return m_bus.Reset(true);
}

///////////////////////////////////////////////////////////////////////////////
//
//      Support section
//

bool Flash::Toggle(uint8_t expected)
{
    bool toggling = true;
    uint8_t data0;
    uint8_t data1;
    m_bus.Read(&data0);
    while (toggling)
    {
        m_bus.Read(&data1);
        if ((data1 ^ data0) & 0x40)
        {
            // toggle bit toggling
            data0 = data1; // set up for next toggle check
        }
        else
        {
            toggling = false;
        }
    }
    return data1 == expected;
}


MCUBus.h

#ifndef INCLUDE_MCUBUS_H
#define INCLUDE_MCUBUS_H
#include "Bus.h"

// The MCUBus class is used to allow the flash programmer to test the MCU chip before
// connecting the CPU and SRAM to the bus.
class MCUBus : public Bus
{
public:
    MCUBus();
    ~MCUBus();

    // Sets the input/output pin mode
    virtual bool EnablePins(bool enable);

    // Resets the Bus
    virtual bool Reset(bool force);

    // Writes 'data' to 16 bit address 'addr'
    bool WriteMem(uint16_t addr, uint8_t data);

    // Gets pData from 16 bit address 'addr'
    bool FetchMem(uint16_t addr, uint8_t *pData);

    // Gets pData from 16 bit address 'addr'
    bool ReadMem(uint16_t addr, uint8_t *pData);

    // Writes 'data' to io port 'port'
    bool WriteIO(uint8_t port, uint8_t data);

    // Reads 'data' from io port 'port'
    bool ReadIO(uint8_t port, uint8_t *pData);

    // Sets the HALT line
    void HaltLine(bool level);

    // Sets the BUSACK line
    void BusAckLine(bool level);

    // Sets the IOREQ line
    void IOReqLine(bool level);

    // Sets the MREQ line
    void MemReqLine(bool level);

    // Sets the M1 line
    void M1Line(bool level);

    // Sets the CLK line
    void Clock(bool level);

protected:
    void PulseIORD();
    void PulseIOWR();
    void PulseFetch();
    void PulseMemRD();
    void PulseMemWR();

public:
    static const int PIN_IOREQ = 7;
    static const int PIN_MREQ = 8;
    static const int PIN_M1 = 9;
    static const int PIN_HALT = 10;
    static const int PIN_BUSACK = 11;
    static const int PIN_CLOCK = 12;
};

#endif


MCUBus.cpp

#include "MCUBus.h"

//#define SUPER_SLO_MO() delay(1000)
#define SUPER_SLO_MO() delay(1)
//#define SUPER_SLO_MO()

MCUBus::MCUBus()
{
}

MCUBus::~MCUBus()
{
}


// Sets the input/output pin mode
bool MCUBus::EnablePins(bool enable)
{
    if (enable)
    {
        digitalWrite(PIN_HALT, HIGH);
        digitalWrite(PIN_MREQ, HIGH);
        digitalWrite(PIN_IOREQ, HIGH);
        digitalWrite(PIN_M1, HIGH);
        digitalWrite(PIN_CLOCK, HIGH);
        digitalWrite(PIN_BUSACK, HIGH);

        pinMode(PIN_HALT, OUTPUT);
        pinMode(PIN_MREQ, OUTPUT);
        pinMode(PIN_IOREQ, OUTPUT);
        pinMode(PIN_M1, OUTPUT);
        pinMode(PIN_CLOCK, OUTPUT);
        pinMode(PIN_BUSACK, OUTPUT);
    }
    else
    {
        // TODO when pullups are on board, make all these revert to INPUT
        pinMode(PIN_HALT, INPUT_PULLUP);
        pinMode(PIN_MREQ, INPUT_PULLUP);
        pinMode(PIN_IOREQ, INPUT_PULLUP);
        pinMode(PIN_M1, INPUT_PULLUP);
        pinMode(PIN_CLOCK, INPUT_PULLUP);
        pinMode(PIN_BUSACK, INPUT_PULLUP);
    }
    return Bus::EnablePins(enable);
}

// Resets the Bus
bool MCUBus::Reset(bool force)
{
    Clock(HIGH);
    Clock(LOW);
    Clock(HIGH);
    Clock(LOW);
    Clock(HIGH);
    Clock(LOW);
    return Bus::Reset(force);
}


bool MCUBus::WriteMem(uint16_t addr, uint8_t data)
{
    UpdateAddressDiff(addr);
    ReadWrite(data);
    PulseMemWR();
    return true;
}

bool MCUBus::FetchMem(uint16_t addr, uint8_t *pData)
{
    UpdateAddressDiff(addr);
    PulseFetch();
    *pData = ReadWrite(0);
    return true;
}

bool MCUBus::ReadMem(uint16_t addr, uint8_t *pData)
{
    UpdateAddressDiff(addr);
    PulseMemRD();
    *pData = ReadWrite(0);
    return true;
}

bool MCUBus::WriteIO(uint8_t port, uint8_t data)
{
    UpdateAddressDiff(port);
    ReadWrite(data);
    PulseIOWR();
    return true;
}

bool MCUBus::ReadIO(uint8_t port, uint8_t *pData)
{
    UpdateAddressDiff(port);
    PulseIORD();
    *pData = ReadWrite(0);
    return true;
}


void MCUBus::HaltLine(bool level)
{
    digitalWrite(PIN_HALT, level);
}

void MCUBus::BusAckLine(bool level)
{
    digitalWrite(PIN_BUSACK, level);
}

void MCUBus::IOReqLine(bool level)
{
    digitalWrite(PIN_IOREQ, level);
}

void MCUBus::MemReqLine(bool level)
{
    digitalWrite(PIN_MREQ, level);
}

void MCUBus::M1Line(bool level)
{
    digitalWrite(PIN_M1, level);
}

void MCUBus::Clock(bool level)
{
    digitalWrite(PIN_CLOCK, level);
}

//  D[7:0]  -------------------------
//  CLK     ^^^^^^\___/^^^^\____/^^^^
//  nIOREQ  ^^^\_________________/^^^
//  nCS     ^^^\_________________/^^^
//  nRD     ^^^\_________________/^^^
//  nWR     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  read                   *
//  Reads when drive goes high (*)
void MCUBus::PulseIORD()
{
    Clock(LOW);
    UpdateChip(0xe); // RD
    IOReqLine(LOW);
    SUPER_SLO_MO();

    Clock(HIGH);
    UpdateChip(0x6); // drive, RD
    SUPER_SLO_MO();

    Clock(LOW);
    UpdateChip(0xe); // RD
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

    Clock(LOW);
    IOReqLine(HIGH);
    UpdateChip(0xf); // done
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

}
//  D[7:0]  -[                     ]-
//  CLK     ^^^^^^\___/^^^^\____/^^^^
//  nIOREQ  ^^^\_________________/^^^
//  nCS     ^^^\_________________/^^^
//  nRD     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  nWR     ^^^^^^^\_________/^^^^^^^
//  Writes when nWR goes low
void MCUBus::PulseIOWR()
{
    Clock(LOW);
    UpdateChip(0x7); // drive
    IOReqLine(LOW);
    SUPER_SLO_MO();

    Clock(HIGH);
    UpdateChip(0x5); // drive, WR
    SUPER_SLO_MO();

    Clock(LOW);
    UpdateChip(0x7); // drive
    UpdateChip(0xf); // done
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

    Clock(LOW);
    IOReqLine(HIGH);
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

}

//  D[7:0]  -------------------------
//  CLK     ^^^^^^\___/^^^^\____/^^^^
//  nMREQ   ^^^\_________________/^^^
//  nM1     ^^^\_________________/^^^
//  nCS     ^^^\_________________/^^^
//  nRD     ^^^\_________________/^^^
//  nWR     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  read                   *
//  Reads when drive goes high (*)
void MCUBus::PulseFetch()
{
    Clock(LOW);
    M1Line(LOW);
    UpdateChip(0xe); // RD
    MemReqLine(LOW);
    SUPER_SLO_MO();

    Clock(HIGH);
    UpdateChip(0x6); // drive, RD
    SUPER_SLO_MO();

    Clock(LOW);
    UpdateChip(0xe); // RD
    M1Line(HIGH);
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

    Clock(LOW);
    MemReqLine(HIGH);
    UpdateChip(0xf); // done
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

}

//  D[7:0]  -------------------------
//  CLK     ^^^^^^\___/^^^^\____/^^^^
//  nMREQ   ^^^\_________________/^^^
//  nM1     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  nCS     ^^^\_________________/^^^
//  nRD     ^^^\_________________/^^^
//  nWR     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  read                   *
//  Reads when drive goes high (*)
void MCUBus::PulseMemRD()
{
    Clock(LOW);
    UpdateChip(0xe); // RD
    MemReqLine(LOW);
    SUPER_SLO_MO();

    Clock(HIGH);
    UpdateChip(0x6); // drive, RD
    SUPER_SLO_MO();

    Clock(LOW);
    UpdateChip(0xe); // RD
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

    Clock(LOW);
    MemReqLine(HIGH);
    UpdateChip(0xf); // done
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

}

//  D[7:0]  -[                     ]-
//  CLK     ^^^^^^\___/^^^^\____/^^^^
//  nMREQ   ^^^\_________________/^^^
//  nCS     ^^^\_________________/^^^
//  nRD     ^^^^^^^^^^^^^^^^^^^^^^^^^
//  nWR     ^^^^^^^\_________/^^^^^^^
//  Writes when nWR goes low
void MCUBus::PulseMemWR()
{
    Clock(LOW);
    UpdateChip(0x7); // drive
    MemReqLine(LOW);
    SUPER_SLO_MO();

    Clock(HIGH);
    UpdateChip(0x5); // drive, WR
    SUPER_SLO_MO();

    Clock(LOW);
    UpdateChip(0x7); // drive
    UpdateChip(0xf); // done
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

    Clock(LOW);
    MemReqLine(HIGH);
    SUPER_SLO_MO();

    Clock(HIGH);
    SUPER_SLO_MO();

}



Exchanger.h

#ifndef INCLUDED_EXCHANGER_H
#define INCLUDED_EXCHANGER_H

#include "SerialIF.h"

// The exchanger handles communication between the PC and the Arduino
class Exchanger {
public:
    Exchanger();
    ~Exchanger();

    // Expect a command. User supplied buffer. It provides the actual length received. Returns true on success
    bool WaitForCommand(uint8_t* receiveBuffer256, int* pLengthReceived);

    // Send a response in sendBuffer256. 'lengthToSend' is 1-255 and 0 means 256
    bool SendResponse(const uint8_t* sendBuffer256, uint8_t lengthToSend);

    // Sends a payload part. packetId is sequential from 0. lengthToSend is 1-255 and 0 means 256
    bool SendPayload(uint16_t packetId, const uint8_t* sendBuffer256, uint8_t lengthToSend);

    // Waits for a payload part. pLengthReceived is 1-255 and 0 means 256
    bool WaitForPayload(uint16_t expectedPacketId, uint8_t* receiveBuffer256, uint8_t* pLengthReceived);

    // Sends a ready to indicate ready for the next payload part
    bool SendReady(uint16_t packetId);

    // Waits for ready to send next packet. buffer is a buffer that can be destroyed
    bool WaitForReady(uint16_t packetId, uint8_t* buffer);

    // Sends a text string (up to but not including the NUL terminator)
    bool SendText(const char* text);

private:
    SerialIF m_serial;
    byte m_id;
};

#endif

Exchanger.cpp

#include "Exchanger.h"

Exchanger::Exchanger() : m_serial()
{
}

Exchanger::~Exchanger()
{
}

///////////////////////////////////////////////////////////////////////////////
//
//      PC -> Arduiono Receive section
//

bool Exchanger::WaitForCommand(uint8_t* receiveBuffer256, int* pLengthReceived)
{
    bool ok = false;
    char c;
    uint16_t packetId;
    uint16_t length = m_serial.Receive(&c, receiveBuffer256, &m_id, &packetId);
    if ((c == '>') & (length > 0))
    {
        *pLengthReceived = length;
        ok = true;
    }
    return ok;
}

bool Exchanger::WaitForReady(uint16_t expectedPacketId, uint8_t* buffer)
{
    bool ok = false;
    char c;
    uint16_t packetId;
    int recv = m_serial.Receive(&c, buffer, &m_id, &packetId);
    if (c == '.' && packetId == expectedPacketId && recv == 1)
    {
        ok = true;
    }
    return ok;
}


bool Exchanger::WaitForPayload(uint16_t expectedPacketId, uint8_t* receiveBuffer256, uint8_t* pLengthReceived)
{
    bool ok = false;
    char c;
    uint16_t packetId;
    uint16_t length = m_serial.Receive(&c, receiveBuffer256, &m_id, &packetId);
    if (c == '$' && length > 0 && packetId == expectedPacketId)
    {
        *pLengthReceived = length;
        ok = true;
    }
    return ok;
}


///////////////////////////////////////////////////////////////////////////////
//
//      Arduoino -> PC section
//

bool Exchanger::SendResponse(const uint8_t* sendBuffer256, uint8_t lengthToSend)
{
    return m_serial.Send('<', sendBuffer256, lengthToSend, m_id, 0);
}

bool Exchanger::SendReady(uint16_t packetId)
{
    uint8_t buffer[1];
    return m_serial.Send('.', buffer, 1, m_id, packetId);
}

bool Exchanger::SendPayload(uint16_t packetId, const uint8_t* sendBuffer256, uint8_t lengthToSend)
{
    int length = lengthToSend;
    if (length == 0)
    {
        length = 256;
    }
    bool ok = m_serial.Send('$', sendBuffer256, length, m_id, packetId);
    return ok;
}

bool Exchanger::SendText(const char* text)
{
    int16_t length = strlen(text);
    bool ok = m_serial.Send('!', (uint8_t*)text, length, m_id, 0xdead);
    return ok;
}



Interpreter.h

#ifndef INCLUDE_INTERPRETER_H
#define INCLUDE_INTERPRETER_H
#include "MCUBus.h"
#include "Flash.h"
#include "SerialIF.h"
#include "Exchanger.h"

class Interpreter
{
public:
    Interpreter();
    ~Interpreter();

    // Run the interpreter
    void Loop();

    // Execute a command
    bool Execute(const uint8_t* command, int commmandLength, uint8_t* pResponse, int* pResponseLength);

    // Send a diag message up to 100 chars
    static void DiagnosticText(const char* format, ...);

protected:
    bool CRC(uint32_t addr, uint32_t length, uint32_t* pCRC);
    uint32_t IncrementalCRC(uint32_t crc, uint8_t data);

private:
    static Interpreter* m_instance;

    uint16_t m_configuration;
    uint16_t m_status;

    MCUBus m_bus;
    SerialIF m_serialIF;
    Flash m_fp;
    Exchanger m_exch;
    uint8_t m_id; // rotating transaction ID
};

#endif




Interpreter.cpp

#include "Interpreter.h"
#include "Exchanger.h"
#include "Flash.h"
#include "CRC32.h"
#include "CommandCodeEnum.h"
#include "Commands.h"
#include "Responses.h"

#define BLINK_ON_SUCCESS 0
#define BLINK_ON_FAILURE 0
#define VERBOSE 1

///////////////////////////////////////////////////////////////////////////////
//
//      Object section
//

Interpreter::Interpreter() : m_fp(m_bus)
{
    m_status = 0;
    m_configuration = 0;
    m_instance = this;
}

Interpreter::~Interpreter()
{
}

Interpreter* Interpreter::m_instance;

void Interpreter::Loop()
{
    uint8_t command[256];
    uint8_t response[256];
    while (1)
    {
        int receivedLength;
        int responseLength;
        if (m_exch.WaitForCommand(command, &receivedLength) > 0)
        {
            m_fp.Reset();

            bool ok = Execute(command, receivedLength, response, &responseLength);

            if (ok)
            {
                m_exch.SendResponse(response, responseLength);
                if (BLINK_ON_SUCCESS)
                {
                    // half second beep bo beep
                    digitalWrite(13, HIGH);
                    delay(200);
                    digitalWrite(13, LOW);
                    delay(100);
                    digitalWrite(13, HIGH);
                    delay(200);
                    digitalWrite(13, LOW);
                }
            }
            else
            {
                response[0] = '?';
                response[1] = 0xde;
                response[2] = 0xad;
                response[3] = 0xbe;
                response[4] = 0xef;
                m_exch.SendResponse(response, 5);
                if (BLINK_ON_FAILURE)
                {
                    // flutter LED
                    for (int16_t i = 0; i < 10; ++i)
                    {
                        digitalWrite(13, HIGH);
                        delay(50);
                        digitalWrite(13, LOW);
                        delay(50);
                    }
                }
            }
        }
    }
}

void Interpreter::DiagnosticText(const char* format, ...)
{
#define DIAGNOSTIC_TEXT_SIZE 100
    char text[DIAGNOSTIC_TEXT_SIZE];
    va_list ap;
    va_start(ap, format);
    vsnprintf(text, DIAGNOSTIC_TEXT_SIZE, format, ap);
    va_end(ap);
    m_instance->m_exch.SendText(text);
}

///////////////////////////////////////////////////////////////////////////////
//
//      Command Dispatch section
//

bool Interpreter::Execute(const uint8_t* command, int commmandLength, uint8_t* pResponse, int* pResponseLength)
{
    bool ok = false;

    switch (command[0])
    {
    case NOP:
    {
        CmdNOP cmd(command, commmandLength);
        ok = true;
        RespNOP resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case Status:
    {
        CmdStatus cmd(command, commmandLength);
        ok = true;

        RespStatus resp(cmd, pResponse);
        uint32_t status = m_configuration;
        status <<= 16;
        status |= m_status;
        resp.SetStatus(status);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case Configure:
    {
        CmdConfigure cmd(command, commmandLength);
        ok = true;
        m_configuration = (uint16_t)cmd.GetConfig();
        RespConfigure resp(cmd, pResponse);
        resp.SetResult(resp.Length());
        *pResponseLength = resp.Length();
    }
    break;
    case ForceReset:
    {
        CmdForceReset cmd(command, commmandLength);
        ok = m_fp.ForceReset();
        RespForceReset resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case EnablePins:
    {
        CmdEnablePins cmd(command, commmandLength);
        bool enable = cmd.GetEnable();
        ok = m_bus.EnablePins(enable);
        RespEnablePins resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;

    case ReadByte:
    {
        CmdReadByte cmd(command, commmandLength);
        uint8_t data;
        ok = m_fp.Read(cmd.GetAddress(), &data);
        RespReadByte resp(cmd, pResponse);
        resp.SetData(data);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case WriteByte:
    {
        CmdWriteByte cmd(command, commmandLength);
        m_bus.Write(cmd.GetAddress(), cmd.GetData());
        ok = true;
        //DiagnosticText("Write byte %05lx <- data-blogger-escaped--="" data-blogger-escaped-addr="cmd.GetAddress();" data-blogger-escaped-break="" data-blogger-escaped-case="" data-blogger-escaped-cmd.getaddress="" data-blogger-escaped-cmd.getdata="" data-blogger-escaped-cmd="" data-blogger-escaped-cmdprogrambyte="" data-blogger-escaped-cmdreadblock="" data-blogger-escaped-command="" data-blogger-escaped-commmandlength="" data-blogger-escaped-for="" data-blogger-escaped-if="" data-blogger-escaped-int32_t="" data-blogger-escaped-length="" data-blogger-escaped-load="" data-blogger-escaped-m_exch.waitforready="" data-blogger-escaped-offset="" data-blogger-escaped-ok="ok" data-blogger-escaped-packet="" data-blogger-escaped-packetid="" data-blogger-escaped-partlen="" data-blogger-escaped-presponse="" data-blogger-escaped-presponselength="resp.Length();" data-blogger-escaped-programbyte:="" data-blogger-escaped-readblock:="" data-blogger-escaped-ready="" data-blogger-escaped-resp.setresult="" data-blogger-escaped-resp="" data-blogger-escaped-respprogrambyte="" data-blogger-escaped-respwritebyte="" data-blogger-escaped-send="" data-blogger-escaped-to="" data-blogger-escaped-uint16_t="" data-blogger-escaped-uint32_t="" data-blogger-escaped-wait="" data-blogger-escaped-while="" data-blogger-escaped-x=""> 256)
                {
                    partLen = 256;
                }
                for (int i = 0; i < partLen; ++i)
                {
                    uint8_t b;
                    ok = ok && m_fp.Read(addr + offset + i, &b);
                    b = (byte)(addr + offset + i);
                    pResponse[i] = b;
                }

                // send it out
                ok = ok && m_exch.SendPayload(packetId, pResponse, partLen);

                // prepare for next packet
                offset += partLen;
                packetId++;
            }
        }
        RespReadBlock resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case ProgramBlock:
    {
        CmdReadBlock cmd(command, commmandLength);
        uint32_t addr = cmd.GetAddress();
        uint32_t length = cmd.GetLength();
        uint32_t offset = 0;
        uint16_t packetId = 0;
        uint8_t buffer[256];
        ok = true;
        while (offset < length)
        {
            if (ok)
            {
                uint8_t partLen;
                ok = ok && m_exch.SendReady(packetId);
                ok = ok && m_exch.WaitForPayload(packetId, buffer, &partLen);
                int len = partLen;
                if (len == 0)
                {
                    len = 256;
                }

                // program packet
                for (int i = 0; i < len; ++i)
                {
                    uint8_t b = buffer[i];
                    ok = ok && m_fp.Program(addr + offset + i, b);
                }

                // prepare for next packet
                offset += len;
                packetId++;
            }
        }
        RespReadBlock resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;

    case EraseSector:
    {
        CmdEraseSector cmd(command, commmandLength);
        int sector = cmd.GetSector();
        int count = cmd.GetCount();
        ok = sector >= 0 && sector < 64 && count >= 1 && (sector + count) < 64;
        for (int i = sector; i < sector + count; ++i)
        {
            if (ok)
            {
                ok = m_fp.EraseSector(sector << 12);
            }
        }
        RespEraseSector resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case EraseChip:
    {
        CmdEraseChip cmd(command, commmandLength);
        ok = m_fp.EraseChip();
        RespEraseChip resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;

    case VerifyErase:
    {
        CmdVerifyErase cmd(command, commmandLength);
        uint8_t data;
        uint32_t errorCount = 0;
        uint32_t addr = cmd.GetAddress();
        uint32_t length = cmd.GetLength();
        ok = true;
        for (uint32_t i = 0; i < length; i++)
        {
            ok = ok && m_fp.Read(addr + i, &data);
            if (data != 0xff)
            {
                //DiagnosticText("Error %d at %x", data, addr + i);
                errorCount++;
            }
        }
        RespVerifyErase resp(cmd, pResponse);
        resp.SetErrorCount(errorCount);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;

    case BlockCRC:
    {
        CmdBlockCRC cmd(command, commmandLength);
        uint8_t data;
        uint32_t addr = cmd.GetAddress();
        uint32_t length = cmd.GetLength();
        uint32_t crc = 0xffffffffUL;
        ok = true;
        for (uint32_t i = 0; i < length; i++)
        {
            ok = ok && m_fp.Read(addr + length, &data);
            data = 0xff; // TODO remove after test
            crc = CRC32::IncrementalCRC(crc, data);
        }
        RespBlockCRC resp(cmd, pResponse);
        resp.SetCRC(crc ^ 0xffffffffUL);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;

    case DirectPinRead:
    {
        CmdDirectPinRead cmd(command, commmandLength);
        uint16_t levels;
        ok = m_bus.DirectRead(cmd.GetMask(), &levels);
        RespDirectPinRead resp(cmd, pResponse);
        resp.SetPins(levels);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case DirectPinWrite:
    {
        CmdDirectPinWrite cmd(command, commmandLength);
        ok = m_bus.DirectWrite(cmd.GetMask(), cmd.GetLevels());
        RespDirectPinWrite resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case DirectPinMode:
    {
        CmdDirectPinMode cmd(command, commmandLength);
        uint8_t pin = cmd.GetPin();
        uint8_t mode = cmd.GetMode();
        ok = pin < 14 && (mode == INPUT || mode == INPUT_PULLUP || mode == OUTPUT);
        if (ok)
        {
            pinMode(pin, mode);
        }
        RespDirectPinMode resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;

    case ReadMem:
    {
        CmdReadMem cmd(command, commmandLength);
        uint8_t data;
        ok = m_bus.ReadMem(cmd.GetAddress(), &data);
        RespReadMem resp(cmd, pResponse);
        resp.SetData(data);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case FetchMem:
    {
        CmdFetchMem cmd(command, commmandLength);
        uint8_t data;
        ok = m_bus.FetchMem(cmd.GetAddress(), &data);
        RespFetchMem resp(cmd, pResponse);
        resp.SetData(data);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case WriteMem:
    {
        CmdWriteMem cmd(command, commmandLength);
        ok = m_bus.WriteMem(cmd.GetAddress(), cmd.GetData());
        RespWriteMem resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case ReadPort:
    {
        CmdReadPort cmd(command, commmandLength);
        uint8_t data;
        ok = m_bus.ReadIO(cmd.GetPort(), &data);
        RespReadPort resp(cmd, pResponse);
        resp.SetData(data);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    case WritePort:
    {
        CmdWritePort cmd(command, commmandLength);
        ok = m_bus.WriteIO(cmd.GetPort(), cmd.GetData());
        RespWritePort resp(cmd, pResponse);
        resp.SetResult(ok);
        *pResponseLength = resp.Length();
    }
    break;
    }
    return ok;
}



ArgInfo.h

#ifndef INCLUDED_ARGINFO_H
#define INCLUDED_ARGINFO_H
#include "CommandCodeEnum.h"

struct ArgTypes
{
    CommandCodes code;
    const char* name;
    int location;
    int length;
};

// The ArgInfo class contains the information needed for commands and responses to location
// and extract the information in the communication packets
class ArgInfo
{
public:
    ArgInfo();
    ~ArgInfo();

    // Gets data from a command
    bool CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, uint32_t* pData);
    bool CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, uint16_t* pData);
    bool CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, uint8_t* pData);
    bool CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, int* pData);
    bool CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, bool* pData);

    // Inserts data into a response
    bool ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, uint32_t data);
    bool ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, uint16_t data);
    bool ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, uint8_t data);
    bool ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, int data);
    bool ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, bool data);

    // Gets the total length of the command
    int CommandLength(CommandCodes code);

    // Gets the total length of the response which is the length of the command, the status, and arguments
    int ResponseLength(CommandCodes code);

protected:
    // Gets the location and length of argument #'argNo' for command of 'commandCode'
    bool GetCommandInfo(CommandCodes code, int argNo, int* pLocation, int* pLength);

    // Gets the location and length of argument #'argNo' for response of 'commandCode'
    bool GetResponseInfo(CommandCodes code, int argNo, int* pLocation, int* pLength);

    // Gets the actual data at 'location'
    bool ExtractFrom(const uint8_t* buffer, int location, int length, uint32_t* pData);

    // Inserts the actual data at 'location'
    bool InsertInto(uint8_t* buffer, int location, int length, uint32_t data);

    // Number of ArgTypes in COMMAND_ARGS
    static int COMMAND_ARGC;

    // Number of ArgTypes in RESPONSE_ARGS
    static int RESPONSE_ARGC;

    // Array of info about command arguments
    static ArgTypes COMMAND_ARGS[];

    // Array of info abaout response arguments
    static ArgTypes RESPONSE_ARGS[];
};

#endif


ArgInfo.cpp

#include "ArgInfo.h"


#include "Interpreter.h" // TODO for diag

// locations start at 0. THey will be set in the c'tor
ArgTypes ArgInfo::COMMAND_ARGS[] =
{
    { Configure, "Configure", 0, 2 },

    { ReadByte, "Address", 0, 3 },

    { WriteByte, "Address", 0, 3 },
    { WriteByte, "Data", 0, 1 },

    { ProgramByte, "Address", 0, 3},
    { ProgramByte, "Data", 0, 1},

    { ReadBlock, "Address", 0, 3},
    { ReadBlock, "Length", 0, 3},

    { ProgramBlock, "Address", 0, 3},
    { ProgramBlock, "Length", 0, 3},

    { EraseSector, "Sector", 0, 1},
    { EraseSector, "Count", 0, 1},

    { VerifyErase, "Address", 0, 3},
    { VerifyErase, "Length", 0, 3},

    { BlockCRC, "Address", 0, 3},
    { BlockCRC, "Length", 0, 3},

    { EnablePins, "Pin", 0, 1},
    { EnablePins, "Mode", 0, 1},

    { DirectPinWrite, "Mask", 0, 2},
    { DirectPinWrite, "Levels", 0, 2},

    { DirectPinRead, "Mask", 0, 2},

    { ReadMem, "Address", 0, 2},

    { FetchMem, "Address", 0, 2},

    { WriteMem, "Address", 0, 2},
    { WriteMem, "Data", 0, 1},

    { ReadPort, "Port", 0, 1},

    { WritePort, "Port", 0, 1},
    { WritePort, "Data", 0, 1},
};

// locations all default to 1. These may not be set in the c'tor
ArgTypes ArgInfo::RESPONSE_ARGS[] =
{
    { ReadByte, "Data", 1, 1 },
    { ReadMem, "Data", 1, 1 },
    { FetchMem, "Data", 1, 1 },
    { ReadPort, "Data", 1, 1 },

    { Status, "Status", 1, 4 },

    { VerifyErase, "ErrorCount", 1, 4 },

    { BlockCRC, "CRC", 1, 4 },

    { DirectPinRead, "Levels", 1, 2 },
};

// Gets sizes at compile time
int ArgInfo::COMMAND_ARGC = sizeof(COMMAND_ARGS) / sizeof(COMMAND_ARGS[0]);
int ArgInfo::RESPONSE_ARGC = sizeof(RESPONSE_ARGS) / sizeof(RESPONSE_ARGS[0]);

ArgInfo::ArgInfo()
{
    // Figure out locations from sizes
    CommandCodes lastCommandCode = (CommandCodes)-1;
    int cumulativeLocation = 1;
    for (int i = 0; i < COMMAND_ARGC; ++i)
    {
        if (COMMAND_ARGS[i].code != lastCommandCode)
        {
            // add into response
            for (int j = 0; j < RESPONSE_ARGC; ++j)
            {
                if (RESPONSE_ARGS[j].code == lastCommandCode)
                {
                    RESPONSE_ARGS[j].location = cumulativeLocation;
                    cumulativeLocation += RESPONSE_ARGS[j].length;
                }
            }

            lastCommandCode = COMMAND_ARGS[i].code;
            cumulativeLocation = 1;
        }
        COMMAND_ARGS[i].location = cumulativeLocation;
        cumulativeLocation += COMMAND_ARGS[i].length;
    }
}

ArgInfo::~ArgInfo()
{
}

int ArgInfo::CommandLength(CommandCodes code)
{
    int cumulativeLength = 1;
    for (int i = 0; i < COMMAND_ARGC; ++i)
    {
        CommandCodes thisCommandCode = COMMAND_ARGS[i].code;

        if (thisCommandCode < code)
        {
        }
        else if (thisCommandCode == code)
        {
            cumulativeLength += COMMAND_ARGS[i].length;
        }
        else
        {
            //break; // not in order--cannot break
        }
    }
    return cumulativeLength;
}

int ArgInfo::ResponseLength(CommandCodes code)
{
    int cumulativeLength = CommandLength(code) + 1; // +1 for result
    for (int i = 0; i < RESPONSE_ARGC; ++i)
    {
        CommandCodes thisCommandCode = RESPONSE_ARGS[i].code;

        if (thisCommandCode < code)
        {
        }
        else if (thisCommandCode == code)
        {
            cumulativeLength += RESPONSE_ARGS[i].length;
        }
        else
        {
            //break; // not in order--cannot break
        }
    }
    return cumulativeLength;
}


bool ArgInfo::CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, uint32_t* pData)
{
    int location;
    int length;
    uint32_t data32 = 0;

    bool ok = GetCommandInfo(code, argNo, &location, &length);

    if (ok)
    {
        ok = ExtractFrom(buffer, location, length, &data32);
    }
    if (ok)
    {
        *pData = (uint32_t)data32;
    }

    return ok;
}

bool ArgInfo::CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, uint16_t* pData)
{
    int location;
    int length;
    uint32_t data32 = 0;

    bool ok = GetCommandInfo(code, argNo, &location, &length);

    if (ok)
    {
        ok = ExtractFrom(buffer, location, length, &data32);
    }
    if (ok)
    {
        *pData = (uint16_t)data32;
    }

    return ok;
}

bool ArgInfo::CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, uint8_t* pData)
{
    int location;
    int length;
    uint32_t data32 = 0;

    bool ok = GetCommandInfo(code, argNo, &location, &length);

    if (ok)
    {
        ok = ExtractFrom(buffer, location, length, &data32);
    }
    if (ok)
    {
        *pData = (uint8_t)data32;
    }

    return ok;
}

bool ArgInfo::CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, int* pData)
{
    int location;
    int length;
    uint32_t data32 = 0;

    bool ok = GetCommandInfo(code, argNo, &location, &length);

    if (ok)
    {
        ok = ExtractFrom(buffer, location, length, &data32);
    }
    if (ok)
    {
        *pData = (int)data32;
    }

    return ok;
}

bool ArgInfo::CommandExtract(CommandCodes code, int argNo, const uint8_t* buffer, bool* pData)
{
    int location;
    int length;
    uint32_t data32 = 0;

    bool ok = GetCommandInfo(code, argNo, &location, &length);

    if (ok)
    {
        ok = ExtractFrom(buffer, location, length, &data32);
    }
    if (ok)
    {
        *pData = data32 != 0;
    }

    return ok;
}


bool ArgInfo::ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, uint32_t data)
{
    int location;
    int length;
    uint32_t data32 = data;

    bool ok = GetResponseInfo(code, argNo, &location, &length);
    if (ok)
    {
        ok = InsertInto(buffer, location, length, data32);
    }
    return ok;
}

bool ArgInfo::ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, uint16_t data)
{
    int location;
    int length;
    uint32_t data32 = data;

    bool ok = GetResponseInfo(code, argNo, &location, &length);
    if (ok)
    {
        ok = InsertInto(buffer, location, length, data32);
    }
    return ok;
}

bool ArgInfo::ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, uint8_t data)
{
    int location;
    int length;
    uint32_t data32 = data;

    bool ok = GetResponseInfo(code, argNo, &location, &length);
    if (ok)
    {
        ok = InsertInto(buffer, location, length, data32);
    }
    return ok;
}

bool ArgInfo::ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, int data)
{
    int location;
    int length;
    uint32_t data32 = data;

    bool ok = GetResponseInfo(code, argNo, &location, &length);
    if (ok)
    {
        ok = InsertInto(buffer, location, length, data32);
    }
    return ok;
}

bool ArgInfo::ResponseInsert(CommandCodes code, int argNo, uint8_t* buffer, bool data)
{
    int location;
    int length;
    uint32_t data32 = data;

    bool ok = GetResponseInfo(code, argNo, &location, &length);
    if (ok)
    {
        ok = InsertInto(buffer, location, length, data32 ? 1 : 0);
    }
    return ok;
}

bool ArgInfo::GetCommandInfo(CommandCodes code, int argNo, int* pLocation, int* pLength)
{
    int i = 0;
    while (i < COMMAND_ARGC && COMMAND_ARGS[i].code != code)
    {
        ++i;
    }
    if (i == COMMAND_ARGC)
    {
        return false;
    }
    int argC = 0;
    while (i < COMMAND_ARGC && COMMAND_ARGS[i].code == code && argC < argNo)
    {
        ++i;
        ++argC;
    }
    if (i == COMMAND_ARGC || COMMAND_ARGS[i].code != code)
    {
        return false;
    }
    *pLocation = COMMAND_ARGS[i].location;
    *pLength = COMMAND_ARGS[i].length;
    return true;
}

bool ArgInfo::GetResponseInfo(CommandCodes code, int argNo, int* pLocation, int* pLength)
{
    int i = 0;
    while (i < RESPONSE_ARGC && RESPONSE_ARGS[i].code != code)
    {
        ++i;
    }
    if (i == RESPONSE_ARGC)
    {
        return false;
    }
    int argC = 0;
    while (i < RESPONSE_ARGC && RESPONSE_ARGS[i].code == code && argC < argNo)
    {
        ++i;
        ++argC;
    }
    if (i == RESPONSE_ARGC || RESPONSE_ARGS[i].code != code)
    {
        return false;
    }
    *pLocation = RESPONSE_ARGS[i].location;
    *pLength = RESPONSE_ARGS[i].length;
    return true;
}

bool ArgInfo::ExtractFrom(const uint8_t* buffer, int location, int length, uint32_t* pData)
{
    bool ok = length <= 4 && location >= 0 && location < 256;

    if (ok)
    {
        uint32_t data = 0;
        for (int i = location; i < location + length; ++i)
        {
            data <<= 8;
            data |= buffer[i];
        }
        *pData = data;
    }
    return ok;
}

bool ArgInfo::InsertInto(uint8_t* buffer, int location, int length, uint32_t data)
{
    bool ok = length <= 4 && location >= 0 && (location+length) < 256;

    if (ok)
    {
        for (int i = location + length - 1; i >= location; --i)
        {
            buffer[i] = (uint8_t)data;
            data >>= 8;
        }
    }
    return ok;
}


Commands.h

#ifndef INCLUDED_COMMANDS_H
#define INCLUDED_COMMANDS_H

#include "CmdResp.h"
#include "ArgInfo.h"

// Abstract base class for all commands
class Command : public CmdResp
{
public:
    // Gets the CommandCode for the command
    CommandCodes Code();

    // Gets the length of the buffer
    int Length();

    // Gets the buffer
    const byte* Buffer();

protected:
    Command(const byte* buffer, int length);

    const byte* m_buffer;
    int m_length;
    //static ArgInfo argInfo;
};

// The rest of these are derived command classes

class CmdNOP : public Command
{
public:
    CmdNOP(const byte* buffer, int length);

protected:
    static const CommandCodes MY_CODE = NOP;
};

class CmdStatus : public Command
{
public:
    CmdStatus(const byte* buffer, int length);

protected:
    static const CommandCodes MY_CODE = Status;
};

class CmdConfigure : public Command
{
public:
    CmdConfigure(const byte* buffer, int length);

    uint16_t GetConfig();

protected:
    static const CommandCodes MY_CODE = Configure;
};

class CmdForceReset : public Command
{
public:
    CmdForceReset(const byte* buffer, int length);

protected:
    static const CommandCodes MY_CODE = ForceReset;
};

class CmdEnablePins : public Command
{
public:
    CmdEnablePins(const byte* buffer, int length);

    bool GetEnable();

protected:
    static const CommandCodes MY_CODE = EnablePins;
};


class CmdReadByte : public Command
{
public:
    CmdReadByte(const byte* buffer, int length);

    uint32_t GetAddress();

protected:
    static const CommandCodes MY_CODE = ReadByte;
};

class CmdWriteByte : public Command
{
public:
    CmdWriteByte(const byte* buffer, int length);

    uint32_t GetAddress();
    uint8_t GetData();

protected:
    static const CommandCodes MY_CODE = WriteByte;
};

class CmdProgramByte : public Command
{
public:
    CmdProgramByte(const byte* buffer, int length);

    uint32_t GetAddress();
    uint8_t GetData();

protected:
    static const CommandCodes MY_CODE = ProgramByte;
};


class CmdReadBlock : public Command
{
public:
    CmdReadBlock(const byte* buffer, int length);

    uint32_t GetAddress();
    uint32_t GetLength();

protected:
    static const CommandCodes MY_CODE = ReadBlock;
};

class CmdProgramBlock : public Command
{
public:
    CmdProgramBlock(const byte* buffer, int length);

    uint32_t GetAddress();
    uint32_t GetLength();

protected:
    static const CommandCodes MY_CODE = ProgramBlock;
};


class CmdEraseSector : public Command
{
public:
    CmdEraseSector(const byte* buffer, int length);

    uint8_t GetSector();
    uint8_t GetCount();

protected:
    static const CommandCodes MY_CODE = EraseSector;
};

class CmdEraseChip : public Command
{
public:
    CmdEraseChip(const byte* buffer, int length);

protected:
    static const CommandCodes MY_CODE = EraseChip;
};

class CmdVerifyErase : public Command
{
public:
    CmdVerifyErase(const byte* buffer, int length);

    uint32_t GetAddress();
    uint32_t GetLength();

protected:
    static const CommandCodes MY_CODE = VerifyErase;
};

class CmdBlockCRC : public Command
{
public:
    CmdBlockCRC(const byte* buffer, int length);

    uint32_t GetAddress();
    uint32_t GetLength();

protected:
    static const CommandCodes MY_CODE = BlockCRC;
};


class CmdDirectPinRead : public Command
{
public:
    CmdDirectPinRead(const byte* buffer, int length);

    uint16_t GetMask();

protected:
    static const CommandCodes MY_CODE = DirectPinRead;
};

class CmdDirectPinWrite : public Command
{
public:
    CmdDirectPinWrite(const byte* buffer, int length);

    uint16_t GetMask();
    uint16_t GetLevels();

protected:
    static const CommandCodes MY_CODE = DirectPinWrite;
};

class CmdDirectPinMode : public Command
{
public:
    CmdDirectPinMode(const byte* buffer, int length);

    uint8_t GetPin();
    uint8_t GetMode();

protected:
    static const CommandCodes MY_CODE = DirectPinMode;
};


class CmdReadMem : public Command
{
public:
    CmdReadMem(const byte* buffer, int length);

    uint16_t GetAddress();

protected:
    static const CommandCodes MY_CODE = ReadMem;
};

class CmdFetchMem : public Command
{
public:
    CmdFetchMem(const byte* buffer, int length);

    uint16_t GetAddress();

protected:
    static const CommandCodes MY_CODE = FetchMem;
};

class CmdWriteMem : public Command
{
public:
    CmdWriteMem(const byte* buffer, int length);

    uint16_t GetAddress();
    uint8_t GetData();

protected:
    static const CommandCodes MY_CODE = WriteMem;
};

class CmdReadPort : public Command
{
public:
    CmdReadPort(const byte* buffer, int length);

    uint8_t GetPort();

protected:
    static const CommandCodes MY_CODE = ReadPort;

};

class CmdWritePort : public Command
{
public:
    CmdWritePort(const byte* buffer, int length);

    uint8_t GetPort();
    uint8_t GetData();

protected:
    static const CommandCodes MY_CODE = WritePort;
};

#endif


Commands.cpp

#include "Commands.h"

Command::Command(const byte* buffer, int length) : m_buffer(buffer), m_length(length)
{
}

CommandCodes Command::Code()
{
    return (CommandCodes)m_buffer[0];
}

int Command::Length()
{
    return m_length;
}

const byte* Command::Buffer()
{
    return m_buffer;
}

ArgInfo CmdResp::argInfo;

CmdNOP::CmdNOP(const byte* buffer, int length) : Command(buffer, length) { }

CmdStatus::CmdStatus(const byte* buffer, int length) : Command(buffer, length) { }

CmdConfigure::CmdConfigure(const byte* buffer, int length) : Command(buffer, length) { }

uint16_t CmdConfigure::GetConfig()
{
    uint16_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

CmdForceReset::CmdForceReset(const byte* buffer, int length) : Command(buffer, length) { }

CmdEnablePins::CmdEnablePins(const byte* buffer, int length) : Command(buffer, length) { }

bool CmdEnablePins::GetEnable()
{
    bool data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

CmdReadByte::CmdReadByte(const byte* buffer, int length) : Command(buffer, length) { }

uint32_t CmdReadByte::GetAddress()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

CmdWriteByte::CmdWriteByte(const byte* buffer, int length) : Command(buffer, length) { }

uint32_t CmdWriteByte::GetAddress()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint8_t CmdWriteByte::GetData()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}


CmdProgramByte::CmdProgramByte(const byte* buffer, int length) : Command(buffer, length) { }

uint32_t CmdProgramByte::GetAddress()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint8_t CmdProgramByte::GetData()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}


CmdReadBlock::CmdReadBlock(const byte* buffer, int length) : Command(buffer, length) { }

uint32_t CmdReadBlock::GetAddress()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint32_t CmdReadBlock::GetLength()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}


CmdProgramBlock::CmdProgramBlock(const byte* buffer, int length) : Command(buffer, length) { }

uint32_t CmdProgramBlock::GetAddress()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint32_t CmdProgramBlock::GetLength()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}

CmdEraseSector::CmdEraseSector(const byte* buffer, int length) : Command(buffer, length) { }

uint8_t CmdEraseSector::GetSector()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint8_t CmdEraseSector::GetCount()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}

CmdEraseChip::CmdEraseChip(const byte* buffer, int length) : Command(buffer, length) { }

CmdVerifyErase::CmdVerifyErase(const byte* buffer, int length) : Command(buffer, length) { }

uint32_t CmdVerifyErase::GetAddress()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint32_t CmdVerifyErase::GetLength()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}

CmdBlockCRC::CmdBlockCRC(const byte* buffer, int length) : Command(buffer, length) { }

uint32_t CmdBlockCRC::GetAddress()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint32_t CmdBlockCRC::GetLength()
{
    uint32_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}

CmdDirectPinRead::CmdDirectPinRead(const byte* buffer, int length) : Command(buffer, length) { }

uint16_t CmdDirectPinRead::GetMask()
{
    uint16_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

CmdDirectPinWrite::CmdDirectPinWrite(const byte* buffer, int length) : Command(buffer, length) { }

uint16_t CmdDirectPinWrite::GetMask()
{
    uint16_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

uint16_t CmdDirectPinWrite::GetLevels()
{
    uint16_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}

CmdDirectPinMode::CmdDirectPinMode(const byte* buffer, int length) : Command(buffer, length) { }

uint8_t CmdDirectPinMode::GetPin()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}
uint8_t CmdDirectPinMode::GetMode()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}

CmdReadMem::CmdReadMem(const byte* buffer, int length) : Command(buffer, length) { }

uint16_t CmdReadMem::GetAddress()
{
    uint16_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

CmdFetchMem::CmdFetchMem(const byte* buffer, int length) : Command(buffer, length) { }

uint16_t CmdFetchMem::GetAddress()
{
    uint16_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

CmdWriteMem::CmdWriteMem(const byte* buffer, int length) : Command(buffer, length) { }

uint16_t CmdWriteMem::GetAddress()
{
    uint16_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}
uint8_t CmdWriteMem::GetData()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}

CmdReadPort::CmdReadPort(const byte* buffer, int length) : Command(buffer, length) { }

uint8_t CmdReadPort::GetPort()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}

CmdWritePort::CmdWritePort(const byte* buffer, int length) : Command(buffer, length) { }

uint8_t CmdWritePort::GetPort()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 0, m_buffer, &data);
    return data;
}
uint8_t CmdWritePort::GetData()
{
    uint8_t data;
    argInfo.CommandExtract(MY_CODE, 1, m_buffer, &data);
    return data;
}


Responses.h

#ifndef INCLUDED_RESPONSES_H
#define INCLUDED_RESPONSES_H

#include "Commands.h"
#include "CommandCodeEnum.h"
#include "CmdResp.h"

// Abstract base class for all responses
class Response : public CmdResp
{
protected:
    Response(Command& command, uint8_t* buffer256);

public:
    void SetResult(int result);
    int Length();
    CommandCodes Code();

protected:
    Command& m_command;
    uint8_t* m_buffer256;
    //static ArgInfo argInfo;
};

// The rest of these are derived response classes

class RespNOP : public Response
{
public:
    RespNOP(CmdNOP& cmd, uint8_t* buffer256); 
};

class RespStatus : public Response 
{
public:
    RespStatus(CmdStatus& cmd, uint8_t* buffer256);

    bool SetStatus(uint32_t data);
};

class RespConfigure : public Response 
{ 
public:
    RespConfigure(CmdConfigure& cmd, uint8_t* buffer256);
};

class RespForceReset : public Response 
{
public:
    RespForceReset(CmdForceReset& cmd, uint8_t* buffer256);
};

class RespEnablePins : public Response 
{ 
public:
    RespEnablePins(CmdEnablePins& cmd, uint8_t* buffer256);
};

class RespReadByte : public Response 
{ 
public:
    RespReadByte(CmdReadByte& cmd, uint8_t* buffer256);

    bool SetData(uint8_t data);
};
class RespWriteByte : public Response 
{ 
public:
    RespWriteByte(CmdWriteByte& cmd, uint8_t* buffer256);
};

class RespProgramByte : public Response 
{ 
public:
    RespProgramByte(CmdProgramByte& cmd, uint8_t* buffer256);
};

class RespReadBlock : public Response 
{ 
public:
    RespReadBlock(CmdReadBlock& cmd, uint8_t* buffer256);
};

class RespProgramBlock : public Response 
{ 
public:
    RespProgramBlock(CmdProgramBlock& cmd, uint8_t* buffer256);
};

class RespEraseSector : public Response 
{ 
public:
    RespEraseSector(CmdEraseSector& cmd, uint8_t* buffer256);
};
class RespEraseChip : public Response 
{ 
public:
    RespEraseChip(CmdEraseChip& cmd, uint8_t* buffer256);
};

class RespVerifyErase : public Response 
{ 
public:
    RespVerifyErase(CmdVerifyErase& cmd, uint8_t* buffer256);

    bool SetErrorCount(uint32_t data);
};

class RespBlockCRC : public Response 
{ 
public:
    RespBlockCRC(CmdBlockCRC& cmd, uint8_t* buffer256);

    bool SetCRC(uint32_t crc);
};

class RespDirectPinRead : public Response 
{ 
public:
    RespDirectPinRead(CmdDirectPinRead& cmd, uint8_t* buffer256);
    bool SetPins(uint16_t pins);
};

class RespDirectPinWrite : public Response 
{ 
public:
    RespDirectPinWrite(CmdDirectPinWrite& cmd, uint8_t* buffer256);
};

class RespDirectPinMode : public Response 
{ 
public:
    RespDirectPinMode(CmdDirectPinMode& cmd, uint8_t* buffer256);
};

class RespReadMem : public Response 
{ 
public:
    RespReadMem(CmdReadMem& cmd, uint8_t* buffer256);

    bool SetData(uint8_t data);
};

class RespFetchMem : public Response 
{ 
public:
    RespFetchMem(CmdFetchMem& cmd, uint8_t* buffer256);

    bool SetData(uint8_t data);
};

class RespWriteMem : public Response 
{ 
public:
    RespWriteMem(CmdWriteMem& cmd, uint8_t* buffer256);
};

class RespReadPort : public Response 
{ 
public:
    RespReadPort(CmdReadPort& cmd, uint8_t* buffer256);

    bool SetData(uint8_t data);
};

class RespWritePort : public Response 
{ 
public:
    RespWritePort(CmdWritePort& cmd, uint8_t* buffer256);
};


#endif

Responses.cpp

#include "Responses.h"

Response::Response(Command& command, uint8_t* buffer256) : m_command(command), m_buffer256(buffer256)
{
    memcpy(m_buffer256, m_command.Buffer(), m_command.Length());
}

void Response::SetResult(int result)
{
    int length = Length();
    m_buffer256[length - 1] = result;
}

int Response::Length()
{
    return argInfo.ResponseLength(m_command.Code());
}

CommandCodes Response::Code()
{
    return m_command.Code();
}

RespNOP::RespNOP(CmdNOP& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespStatus::RespStatus(CmdStatus& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespConfigure::RespConfigure(CmdConfigure& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespForceReset::RespForceReset(CmdForceReset& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespEnablePins::RespEnablePins(CmdEnablePins& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespReadByte::RespReadByte(CmdReadByte& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespWriteByte::RespWriteByte(CmdWriteByte& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespProgramByte::RespProgramByte(CmdProgramByte& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespReadBlock::RespReadBlock(CmdReadBlock& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespProgramBlock::RespProgramBlock(CmdProgramBlock& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespEraseSector::RespEraseSector(CmdEraseSector& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespEraseChip::RespEraseChip(CmdEraseChip& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespVerifyErase::RespVerifyErase(CmdVerifyErase& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespBlockCRC::RespBlockCRC(CmdBlockCRC& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespDirectPinRead::RespDirectPinRead(CmdDirectPinRead& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespDirectPinWrite::RespDirectPinWrite(CmdDirectPinWrite& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespDirectPinMode::RespDirectPinMode(CmdDirectPinMode& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespReadMem::RespReadMem(CmdReadMem& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespFetchMem::RespFetchMem(CmdFetchMem& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespWriteMem::RespWriteMem(CmdWriteMem& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespReadPort::RespReadPort(CmdReadPort& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}
RespWritePort::RespWritePort(CmdWritePort& cmd, uint8_t* buffer256) : Response(cmd, buffer256) {}

bool RespStatus::SetStatus(uint32_t data)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, data);
}

bool RespReadByte::SetData(uint8_t data)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, data);
}

bool RespReadMem::SetData(uint8_t data)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, data);
}

bool RespFetchMem::SetData(uint8_t data)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, data);
}

bool RespReadPort::SetData(uint8_t data)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, data);
}

bool RespVerifyErase::SetErrorCount(uint32_t data)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, data);
}

bool RespBlockCRC::SetCRC(uint32_t crc)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, crc);
}

bool RespDirectPinRead::SetPins(uint16_t pins)
{
    return argInfo.ResponseInsert(Code(), 0, m_buffer256, pins);
}



CmdResp.h

#ifndef INCLUDED_CMDRESP_H
#define INCLUDED_CMDRESP_H

#include "ArgInfo.h"
class CmdResp
{
 protected:
    static ArgInfo argInfo;
};

#endif


CommandCodeEnum.h

#ifndef INCLUDED_COMMANDCODEENUM_H
#define INCLUDED_COMMANDCODEENUM_H

enum CommandCodes
{
    NOP = 0x00,
    Status = 0x01,
    Configure = 0x02,
    ForceReset = 0x08,
    EnablePins = 0x09,

    ReadByte = 0x10,
    WriteByte = 0x11,
    ProgramByte = 0x12,

    ReadBlock = 0x20,
    ProgramBlock = 0x22,

    EraseSector = 0x30,
    EraseChip = 0x33,

    VerifyErase = 0x40,
    BlockCRC = 0x41,

    DirectPinRead = 0x80,
    DirectPinWrite = 0x81,
    DirectPinMode = 0x82,

    ReadMem = 0x90,
    FetchMem = 0x91,
    WriteMem = 0x92,
    ReadPort = 0x98,
    WritePort = 0x99
};

#endif