With a lot of trial and error and some hints from one Glenn Neidermeier at http://pushpopmov.blogspot.com/2011/03/sdcc-makefile.html, who left a record of travelling a similar path in 2011, I think I finally have my code linking and going to the right place. I was using the sdld directly, followed by attempts to use sdldz80. But the correct thing is to just use sdcc -mz80. That means you lose the linker options which I tried passing with the -Wl to no avail.... Undocumented complex tools are a pain to work with. /sigh
But the important part is that it's now sort of working. And that means I can start testing my hardware much more thoroughly.
If all goes well, it will run as soon as I load it up.... Of course, that's not too likely. Which means I'll be using my oscilloscope and scaring the patrons at my favorite cafe tomorrow morning.
Thursday, September 3, 2015
VirtualBox is Working and Toolchain is Compiled
I upgraded VirtualBox on my Surface Pro 3 today. Instead of just crashing on launch, it now runs. Windows 10 is still not officially supported, but so far it seems to be running. I have it running Mint 17.1. I did apt-get install flex, bison, texinfo, subversion, git, gcc, g++, libboost-graph-dev, and binutils. But it seems to get to the end. I ran ./configure with the --disable-pic14-port and --disable-pic16-port. That way it doesn't require I pull in the PIC stuff. And it did install properly.
This is the basic operation. # is super-user stuff, $ is user stuff. Of course, YMMV. Or probably YMWV (your mileage will vary).
Let me know if I'm missing any steps....
Update--just in case you're interested, the tools in ~/sdcc-3.5.2/sdccd/bin/ can be run in gdb. So if something goes awry, I have a fighting chance of diagnosing the problem.
This is the basic operation. # is super-user stuff, $ is user stuff. Of course, YMMV. Or probably YMWV (your mileage will vary).
# apt-get install bison
# apt-get install flex
# apt-get install subversion
# apt-get install git
# apt-get install binutils
# apt-get install gcc
# apt-get install g++
# apt-get install libboost-graph-dev
# apt-get install texinfo
$ cd ~
$ svn checkout svn://svn.code.sf.net/p/sdcc/code/trunk sdcc-3.5.2
$ cd sdcc-3.5.2/sdcc
$ ./configure --disable-pic14-port and --disable-pic16-port
$ make
# cd ~/sdcc-3.5.2/sdcc
# make install
Let me know if I'm missing any steps....
Update--just in case you're interested, the tools in ~/sdcc-3.5.2/sdccd/bin/ can be run in gdb. So if something goes awry, I have a fighting chance of diagnosing the problem.
Fun With Makefiles and SDCC
My choice was getting the SDCC toolchain working with Eclipse or dealing with makefiles. And I decided to go with makefiles. I sort of know how to use them--at least I know enough to know how they work and what they look like when they are correct. I just don't know the details on the repurposed punctuation. There is nothing in software development quite as unelegant as make. Shell scripts and Perl are bad, but make... Ugh!
So, I'm starting to build up a makefile system. I have a top level one where I can do all my CFLAGS settings and pass them down.
My first problem was that sdcc.exe wasn't finding my include files. I was getting the address with:
SW_DIR= $(shell pwd)
INCLUDES=-I${SW_DIR}/drivers -I${SW_DIR}/app
I'm running this in cygwin, so the directory looks like /cygdrive/c/users/Mark/Documents/Projects/Z80/SW/
Well, it turns out, I need to pass a Windows style directory, so I updated SW_DIR to:
SW_DIR= $(shell pwd | sed 's/\/cygdrive\/c\//C:\\\\/' | sed 's/\//\\\\/g')
So that fixed that.
I'm on my way to having an actual compiled ihx.
I've decided to start dirt simple. Just direct access to the drivers without the C runtime. I'll know it's working because gets out of the box in the toolchain echoes the incoming characters. So if I turn off local echo on puTTY and see the characters I type, I'll know. I'll also output a string at the beginning.
....
And it compiles and links. Nothing is in the right place yet. But getting closer.....
.....
Now that I'm trying to pass possibly correct values to the linker, it just sits there and does nothing... I have to ctrl-C out.
Grrrr.....
So, I'm starting to build up a makefile system. I have a top level one where I can do all my CFLAGS settings and pass them down.
My first problem was that sdcc.exe wasn't finding my include files. I was getting the address with:
SW_DIR= $(shell pwd)
INCLUDES=-I${SW_DIR}/drivers -I${SW_DIR}/app
I'm running this in cygwin, so the directory looks like /cygdrive/c/users/Mark/Documents/Projects/Z80/SW/
Well, it turns out, I need to pass a Windows style directory, so I updated SW_DIR to:
SW_DIR= $(shell pwd | sed 's/\/cygdrive\/c\//C:\\\\/' | sed 's/\//\\\\/g')
So that fixed that.
I'm on my way to having an actual compiled ihx.
I've decided to start dirt simple. Just direct access to the drivers without the C runtime. I'll know it's working because gets out of the box in the toolchain echoes the incoming characters. So if I turn off local echo on puTTY and see the characters I type, I'll know. I'll also output a string at the beginning.
....
And it compiles and links. Nothing is in the right place yet. But getting closer.....
.....
Now that I'm trying to pass possibly correct values to the linker, it just sits there and does nothing... I have to ctrl-C out.
Grrrr.....
Wednesday, September 2, 2015
Mostly Compiles!
I have now compiled all the drivers, all the C library, and the menu application. I still need putchar, getchar, maybe something connected to system ticks as that should be available in the C library. I also need to hookup the interrupts in crt0.s to INT# and NMI#. Finally, I gotta figure out how to tell SDCC that RAM starts at 0x8000.
Also, since this my bring-up of a board that has had only some testing, I need to come up with a scheme where I can know how far it's getting using GPIO outs that I can monitor on the scope.
Actually, what I really need first is something that programs flash natively. Maybe put that in a bank of flash and use a GPIO to switch to it.... Because I can use the Arduino to program the flash (and indeed will at the beginning, but it's not fast.
I have figure out how to make sure stuff fits and stuff goes into the right place. I noticed that some asserts are causing strings to go into memory which is likely a luxury I don't want, though I'm not compiling with the debug flag, so I don't know why the asserts are even being generated. Lot's of little things with this toolchain because it targets super small targets so has pretty minimal libraries.
I also have to figure out better organization. I have makefiles in directories. I need a better way to tie them together. I'm not a big makefile fan, so I only sort of know how-- maybe export vars as environment variables and call submakes or something like that. Recursive make is evil, but it's easier than Eclipse.
And if I get sick of all that, I still have the Pygments and Jekyll stuff to work on....
Also, since this my bring-up of a board that has had only some testing, I need to come up with a scheme where I can know how far it's getting using GPIO outs that I can monitor on the scope.
Actually, what I really need first is something that programs flash natively. Maybe put that in a bank of flash and use a GPIO to switch to it.... Because I can use the Arduino to program the flash (and indeed will at the beginning, but it's not fast.
I have figure out how to make sure stuff fits and stuff goes into the right place. I noticed that some asserts are causing strings to go into memory which is likely a luxury I don't want, though I'm not compiling with the debug flag, so I don't know why the asserts are even being generated. Lot's of little things with this toolchain because it targets super small targets so has pretty minimal libraries.
I also have to figure out better organization. I have makefiles in directories. I need a better way to tie them together. I'm not a big makefile fan, so I only sort of know how-- maybe export vars as environment variables and call submakes or something like that. Recursive make is evil, but it's easier than Eclipse.
And if I get sick of all that, I still have the Pygments and Jekyll stuff to work on....
Critical Naked Interrupts and Unpromoted Characters -- In SDCC
I decided to compile a test file just to see exactly what __critical, __naked, and __interrupt do. Here are the results.
__critical causes the prolog to insert:
which saves the interrupt register to into the accumulator and then pushes it onto the stack.
The epilog is:
which is curious because what's that return on parity odd? Well, if the interrupts were already disabled when calling di, they shouldn't be reenabled. It would be possible to use a reference count to keep track of nested __critical functions, but this works as well. When the ld a,i is executed, PO gets the current enabled/disabled state. If the P flag is PO, then the interrupts were disabled coming and won't be enabled.
Is this better than a reference count? If it's used lightly, cetainly. It's a total of 7 bytes with 2 bytes overhead per nested call. A reference count would be 6 bytes, with how many every bytes are needed to store the count and use the count.
__naked causes the prolog and epilog to not generate. The code is generated according to the calling convention. But no registers are saved or restored, no return is generated, and function level
__critical doesn't take effect because that inserts the critical code into the prolog and epilogs.
__interrupt causes function to save and restore AF, BC, DE, HL, and IY. The return is reti.
__naked __interrupt is equivalent to __naked since __interrupt only affects the prolog and epilog.
__critical __interrupt is used to emit the retn instead of reti for returning from a non-maskable interrupt.
Other testing also shows that the __critical { ... } works as expected, putting the same di, ei, jp PO into the code itself.
Finally, a note on integer promotion of function arguments. SDCC doesn't follow the C standard of promoting chars to ints. And thank goodness! Because although an int is 16 bits, the Z-80 works best with 8 bit data, so coercing it to 16 bits when it can be treated as 8 bit data would be counter productive.
For reference, the test.c file is:
And the test.asm file is:
__critical causes the prolog to insert:
ld a,i
di
push af
which saves the interrupt register to into the accumulator and then pushes it onto the stack.
The epilog is:
pop af
ret PO
ei
ret
which is curious because what's that return on parity odd? Well, if the interrupts were already disabled when calling di, they shouldn't be reenabled. It would be possible to use a reference count to keep track of nested __critical functions, but this works as well. When the ld a,i is executed, PO gets the current enabled/disabled state. If the P flag is PO, then the interrupts were disabled coming and won't be enabled.
Is this better than a reference count? If it's used lightly, cetainly. It's a total of 7 bytes with 2 bytes overhead per nested call. A reference count would be 6 bytes, with how many every bytes are needed to store the count and use the count.
__naked causes the prolog and epilog to not generate. The code is generated according to the calling convention. But no registers are saved or restored, no return is generated, and function level
__critical doesn't take effect because that inserts the critical code into the prolog and epilogs.
__interrupt causes function to save and restore AF, BC, DE, HL, and IY. The return is reti.
__naked __interrupt is equivalent to __naked since __interrupt only affects the prolog and epilog.
__critical __interrupt is used to emit the retn instead of reti for returning from a non-maskable interrupt.
Other testing also shows that the __critical { ... } works as expected, putting the same di, ei, jp PO into the code itself.
Finally, a note on integer promotion of function arguments. SDCC doesn't follow the C standard of promoting chars to ints. And thank goodness! Because although an int is 16 bits, the Z-80 works best with 8 bit data, so coercing it to 16 bits when it can be treated as 8 bit data would be counter productive.
For reference, the test.c file is:
#include <stdio.h>
int main(char* argv[], int argc)
{
argc; argv;
printf("Hello, World!");
return 0;
}
int test0(int a, int b)
{
return a+b;
}
int test0c(int a, int b) __critical
{
return a+b;
}
int test0n(int a, int b) __naked
{
return a+b;
}
int test0cn(int a, int b) __critical __naked
{
return a+b;
}
void test0i() __interrupt
{
__asm__("nop");
}
void test0in() __interrupt __naked
{
__asm__("nop");
}
void test0ic() __interrupt __critical
{
__asm__("nop");
}
And the test.asm file is:
;--------------------------------------------------------
; File Created by SDCC : free open source ANSI-C Compiler
; Version 3.5.2 #9283 (MINGW64)
; This file was generated Wed Sep 02 15:19:10 2015
;--------------------------------------------------------
.module test
.optsdcc -mz80
;--------------------------------------------------------
; Public variables in this module
;--------------------------------------------------------
.globl _test0ic
.globl _test0in
.globl _test0i
.globl _test0cn
.globl _test0n
.globl _test0c
.globl _test0
.globl _main
.globl _printf
;--------------------------------------------------------
; 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
;test.c:3: int main(char* argv[], int argc)
; ---------------------------------
; Function main
; ---------------------------------
_main::
;test.c:6: printf("Hello, World!");
ld hl,#___str_0
push hl
call _printf
pop af
;test.c:7: return 0;
ld hl,#0x0000
ret
___str_0:
.ascii "Hello, World!"
.db 0x00
;test.c:10: int test0(int a, int b)
; ---------------------------------
; Function test0
; ---------------------------------
_test0::
;test.c:12: return a+b;
ld hl,#4
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 l, d
ld h, e
ret
;test.c:14: int test0c(int a, int b) __critical
; ---------------------------------
; Function test0c
; ---------------------------------
_test0c::
ld a,i
di
push af
;test.c:16: return a+b;
ld hl,#4
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 l, d
ld h, e
pop af
ret PO
ei
ret
;test.c:18: int test0n(int a, int b) __naked
; ---------------------------------
; Function test0n
; ---------------------------------
_test0n::
;test.c:20: return a+b;
ld hl,#4
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 l, d
ld h, e
;test.c:22: int test0cn(int a, int b) __critical __naked
; ---------------------------------
; Function test0cn
; ---------------------------------
_test0cn::
;test.c:24: return a+b;
ld hl,#4
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 l, d
ld h, e
;test.c:27: void test0i() __interrupt
; ---------------------------------
; Function test0i
; ---------------------------------
_test0i::
push af
push bc
push de
push hl
push iy
;test.c:29: __asm__("nop");
nop
pop iy
pop hl
pop de
pop bc
pop af
reti
;test.c:31: void test0in() __interrupt __naked
; ---------------------------------
; Function test0in
; ---------------------------------
_test0in::
;test.c:33: __asm__("nop");
nop
;test.c:35: void test0ic() __interrupt __critical
; ---------------------------------
; Function test0ic
; ---------------------------------
_test0ic::
push af
push bc
push de
push hl
push iy
;test.c:37: __asm__("nop");
nop
pop iy
pop hl
pop de
pop bc
pop af
retn
.area _CODE
.area _INITIALIZER
.area _CABS (ABS)
Tuesday, September 1, 2015
Pictures of the Latest Hardware Changes
Here is the timer CPLD soldered to the 12 pin header. It is not connected into the system yet. Just ready for wire-wrap at any time.
Here are the ground pins for the scope:
Here is a thing I made to debug serial communication with a scope back when I was a young'in in the '90's. I wired the MAX3110E RS232 buffers to unused pins on the oscillator socket:
I wired the MAX3110E IRQ to INT on the Z-80. So all I have left to do with hardware its wire in the timer CPLD to the GPIO, SPI, and INT on the Z-80.
Here are the ground pins for the scope:
Here is a thing I made to debug serial communication with a scope back when I was a young'in in the '90's. I wired the MAX3110E RS232 buffers to unused pins on the oscillator socket:
I wired the MAX3110E IRQ to INT on the Z-80. So all I have left to do with hardware its wire in the timer CPLD to the GPIO, SPI, and INT on the Z-80.
More Soldering and Drivers
I think I just might be done with soldering. I soldered in a 12 pin header, the 5 connections from the timer CPLD to the header, and 2 posts (pulled from the 12 pin headers that I turned into 11 pin headers) so that I have out of the way ground points for the oscilloscope probes.
I have also been working on the UART drivers and the intel hex (*.ihx) drivers for flash programming.
Now, I'm in the process of a paper self code review.
The UART driver is fairly complex. It has to take a lot of stuff into account: interrupts, initialization, buffers, hardware flow control, support for timeouts and blocking calls.
I wrote this up, but haven't tested it or even compiled it. Also it has not gone through my initial self code review. But here are the uart.h, uart.c, ringBuffer.h, ringBuffer.c, spi.h, spi.c, gpio.h and gpio.c.
NOTE! Not tested or even compiled. The code below is guaranteed to fail. Do NOT copy and paste this thinking this is a working solution. You are free to copy and paste it--just don't expect it to work. When it's working, I'll post more. It is just to give you an idea of what I'm working on at the moment.
uart.h:
uart.c:
ringBuffer.h:
ringBuffer.c:
spi.h:
spi.c:
gpio.h:
gpio.c:
I have also been working on the UART drivers and the intel hex (*.ihx) drivers for flash programming.
Now, I'm in the process of a paper self code review.
The UART driver is fairly complex. It has to take a lot of stuff into account: interrupts, initialization, buffers, hardware flow control, support for timeouts and blocking calls.
I wrote this up, but haven't tested it or even compiled it. Also it has not gone through my initial self code review. But here are the uart.h, uart.c, ringBuffer.h, ringBuffer.c, spi.h, spi.c, gpio.h and gpio.c.
NOTE! Not tested or even compiled. The code below is guaranteed to fail. Do NOT copy and paste this thinking this is a working solution. You are free to copy and paste it--just don't expect it to work. When it's working, I'll post more. It is just to give you an idea of what I'm working on at the moment.
uart.h:
#ifndef INCLUDE_DRIVER_UART_H
#define INCLUDE_DRIVER_UART_H
#include "common.h"
// Initialize the UART. Note that the MAX3110E may take 25ms
// to stablize. So it may fail when called to early. Call until
// it returns true.
bool uartInit(uint16_t config);
// Reconfigure the UART
bool uartReconfig(uint16_t config);
// Adds b to the transmit buffer. Then attempts to send.
// Return true if successful or false if an error was
// encountered. If the error has a new UART_ERROR_TX_OVERFLOW bit,
// the the byte was not added.
// It is not a blocking function.
bool send(uint8_t b);
// Removes a byte from the receive buffer and returns in *pByte.
// returns true if successful or false an error was encountered
// If *pByte is -1, the buffer was empty but this does NOT indicate
// an error.
bool recv(int16_t* pByte);
// Block until something interesting happens. It won't necessarily be the desired event so the
// user still has to check. Also, released on system tick.
void pend();
// Sets/clears RTS. Use to stop transmission before doing something that may cause buffer overflow
bool setRTS(bool ready);
// Bit locations in the error status
#define UART_ERROR_INITIALIZATION_BIT (0)
#define UART_ERROR_RX_OVERFLOW_BIT (1)
#define UART_ERROR_FRAMING_BIT (2)
#define UART_ERROR_PARITY_BIT (3)
#define UART_ERROR_TX_OVERFLOW_BIT (4)
// Gets the current errors. Also, clears any error bits sent in clear.
// If any of the UART calls return false, then this should be called to clear the error
// state before another call.
uint16_t uartErrorReadAndClear(uint16_t clear);
// UART Config
// Use these bits and BAUD rates with uartInit and uartReconfig
#define UART_BAUD_300 (0x0f)
#define UART_BAUD_600 (0x0e)
#define UART_BAUD_1200 (0x0d)
#define UART_BAUD_2400 (0x0c)
#define UART_BAUD_4800 (0x0b)
#define UART_BAUD_9600 (0x0a)
#define UART_BAUD_19200 (0x09)
#define UART_BAUD_14400 (0x03)
#define UART_BAUD_28800 (0x02)
#define UART_BAUD_38400 (0x08)
#define UART_BAUD_57600 (0x01)
#define UART_BAUD_115200 (0x00)
#define UART_7_BIT_WORDS_BIT (4)
#define UART_PARITY_ENABLE_BIT (5)
#define UART_TWO_STOP_BITS_BIT (6)
//#define UART_IRDA_MODE_BIT (7)
#define UART_RAM_INTERRUPT_BIT (8)
#define UART_PM_INTERRUPT_BIT (9)
#define UART_RM_INTERRUPT_BIT (10)
#define UART_TM_INTERRUPT_BIT (11)
//#define UART_FIFO_DISABLE_BIT (13)
#define RESET_BIT(x, b) x &= ~(1<<b)
#define SET_BIT(x, b) x |= (1<<b)
#define SET_BIT_BY(x, b, v) x = (v ? (x | (1<<b)) : (x & ~(1<<b)))
#define IS_BIT(x, b) ((x & (1<<b)) != 0)
#endif
uart.c:
#include "gpio.h"
#include "spi.h"
#include "uart.h"
#include "ringBuffer.h"
// The UART code handles communication between the board and a PC
// It provides open/close/read/write. The read and write functions are
// blocking, but ring buffers enable some degree of buffering.
// The hardware supports CTS/RTS flow control
// Possible errors are read overflow, framing, parity
// Supported baud rates are 115.2K, 57.6K, 38.4K 19.2K 9600, 4800, 1200, 300
// Initialization may require several calls as the HW takes up to 25 ms
// to stabilize.
#define UART_RECV_RING_BUFFER_SIZE (64)
#define UART_XMIT_RING_BUFFER_SIZE (64)
// For hardware flow control, set these:
// Stop threshold is the number of bytes used in the receive buffer above
// which RTS is deasserted. It must be less than UART_RECV_RING_BUFFER_SIZE
// Note that there is an 8 byte FIFO in the MAX3110E and that every write
// necessetates a read because of the exchange way the chip works
#define UART_RECV_RTS_STOP_THRESHOLD (UART_RECV_RING_BUFFER_SIZE - 12)
// The restart provides a little bit of hysteresis for RTS control
// It must be less than UART_RECV_RTS_STOP_THRESHOLD
#define UART_RECV_RTS_RESTART_THRESHOLD (UART_RECV_RTS_STOP_THRESHOLD - 8)
// UART Read/Write
#define UART_PARITY_BIT_BIT (8)
#define UART_RTS_CTS_BIT (9)
#define UART_FRAMING_ERROR_BIT (10)
#define UART_TRANSMIT_ENABLE_BIT (10)
// UART Common
#define UART_TRANSMIT_EMPTY_BIT (14)
#define UART_DATA_READY_BIT (15)
uint16_t uartConfig = 0;
uint8_t recvRingBuffer[UART_RECV_RING_BUFFER_SIZE];
uint8_t xmitRingBuffer[UART_XMIT_RING_BUFFER_SIZE];
struct RingBuffer rxBuffer;
struct RingBuffer txBuffer;
#define UART_STATE_WAITING_ON_CTS (0)
//#define UART_STATE_WAITING_ON_RX_HW (1)
#define UART_STATE_WAITING_ON_TX_HW (2)
#define UART_STATE_REQ_RTS_CHANGE (3)
#define UART_STATE_REQ_RTS_LEVEL (4)
#define UART_STATE_REQ_RM_CHANGE (5)
#define UART_STATE_REQ_RM_LEVEL (6)
#define UART_STATE_REQ_RAM_CHANGE (7)
#define UART_STATE_REQ_RAM_LEVEL (8)
//#define UART_STATE_WAITING_ON_RX_RB (9)
//#define UART_STATE_WAITING_ON_TX_RB (10)
#define UART_STATE_USER_RTS (11)
#define UART_STATE_PENDING (12)
uint16_t uartState;
#define UART_ERROR_TO_REPORT_BIT (15)
int uartError;
uint16_t writeUartConfig(uint16_t config);
uint16_t readUartConfig();
uint16_t writeAndReadUart(uint8_t b);
uint16_t readUart();
///////////////////////////////////////////////////
//
// Interface functions
//
// Initialize the UART. Note that the MAX3110E may take 25ms
// to stablize. So it may fail when called to early. Call until
// it returns true
bool uartInit(uint16_t config)
{
bool success;
// initial state
uartState = (1 << UART_STATE_WAITING_ON_TX_HW);
// create reing buffers
ringBufferInit(&rxBuffer, recvRingBuffer, sizeof(recvRingBuffer));
ringBufferInit(&txBuffer, xmitRingBuffer, sizeof(xmitRingBuffer));
// Config part
return uartReconfig(config);
}
// Reconfigure the UART (baud, stop bits, parity, bits per word,
bool uartReconfig(uint16_t config)
{
bool success;
uartConfig = config & 0xc000;
writeUartConfig(config);
success = ((config ^ readUartConfig()) & ~0xc000) == 0;
SET_BIT_BY(uartError, UART_ERROR_INITIALIZATION_BIT, success);
RESET_BIT(uartError, UART_ERROR_TO_REPORT_BIT); // reporting now
return success;
}
// Sends b out the serial port.
// If there are bytes in the xmit buffer, it is added.
// IF the xmit buffer is empty and the UARt can take a byte,
// then the byte is written out.
// If the byte cannot be written out, it is added to the xmit
// buffer.
bool send(uint8_t b)
{
if (!ringBufferFull(&txBuffer))
{
ringBufferEnqueue(&txBuffer, b);
}
else
{
SET_BIT(uartError, UART_ERROR_TX_OVERFLOW_BIT);
}
RESET_BIT(uartError, UART_ERROR_TO_REPORT_BIT); // reporting
return (uartError & 0x7fff) != 0;
}
// A non-blocking recv. It returns true if OK, false if an error
// The pByte will get (int)-1 if there is nothing to receive
// If an error has occured, return false // TODO
// If the recv buffer is not empty, dequeue and return
// Else set the read pending bit and spin wait
bool recv(int16_t *pByte)
{
if (!ringBufferEmpty(&rxBuffer))
{
*pByte = ringBufferDequeue(&rxBuffer);
// if falls below RTS threshold, then turn RTS back on
if (IS_BIT(uartState, UART_STATE_REQ_RTS_LEVEL) && !IS_BIT(uartState, UART_STATE_USER_RTS))
{
if (ringBufferCount(&rxBuffer) < UART_RECV_RTS_RESTART_THRESHOLD)
{
SET_BIT(uartState, UART_STATE_REQ_RTS_CHANGE);
SET_BIT(uartState, UART_STATE_REQ_RTS_LEVEL);
}
}
}
else
{
*pByte = -1;
}
RESET_BIT(uartError, UART_ERROR_TO_REPORT_BIT); // reporting
return (uartError & 0x7fff) != 0;
}
// Use for creating blocking calls. The user can pass an idle function and a pointer that will be
// continually called until the call ends
void pend(void(*cbIdleFn)(void* p), void* p)
{
SET_BIT(uartState, UART_STATE_PENDING);
while (IS_BIT(uartState, UART_STATE_PENDING))
{
// Do nothing or do idle
if (cbIdleFn)
{
cbIdleFn(p);
}
}
}
void configRTS(bool config)
{
// if turning back on, only request change if off and threshold is not met
SET_BIT(uartState, UART_STATE_USER_RTS, config);
bool reqLevel = config && ringBufferCount(&txBuffer) < UART_RECV_RTS_STOP_THRESHOLD;
bool curLevel = IS_BIT(uartState, UART_STATE_REQ_RTS_LEVEL);
if (reqLevel != curLevel)
{
SET_BIT(uartState, UART_STATE_REQ_RTS_CHANGE);
SET_BIT_BY(uartState, UART_STATE_REQ_RTS_LEVEL, reqLevel);
readWrite();
}
}
uint16_t uartErrorReadAndClear(uint16_t clear)
{
uartError &= ~clear;
return uartError;
}
///////////////////////////////////////////////////
//
// Internal functions
//
// This is the work horse function that does the actual reading and writing through the UART.
// It handles the RX, TX, RTS, CTS, errors, interrupt enables,
bool readWrite()
{
uint16_t read;
uint16_t write = 0;
bool te = false; // use readWrite instead of read, but if nothing to write, use TE#
bool loop = true;
bool wrote = false;
bool errorOccured = false;
while (loop)
{
loop = false;
// See if reconfig is necesary
if (uartState & ((1 << UART_STATE_REQ_RM_CHANGE) | (1 << UART_STATE_REQ_RAM_CHANGE)))
{
// clear requests
uartState &= ~((1 << UART_STATE_REQ_RM_CHANGE) | (1 << UART_STATE_REQ_RAM_CHANGE));
if (uartState & (1 << UART_STATE_REQ_RM_CHANGE))
{
SET_BIT_BY(uartConfig, UART_RM_INTERRUPT_BIT, IS_BIT(uartState, UART_STATE_REQ_RM_LEVEL));
}
if (uartState & (1 << UART_STATE_REQ_RAM_CHANGE))
{
SET_BIT_BY(uartConfig, UART_RAM_INTERRUPT_BIT, IS_BIT(uartState, UART_STATE_REQ_RAM_LEVEL));
}
writeUartConfig(uartConfig);
}
// See if reconfig is necesary
write = 0x0000;
if (IS_BIT(uartState, UART_STATE_REQ_RTS_CHANGE))
{
SET_BIT_BY(write, UART_RTS_CTS_BIT, IS_BIT(uartState, UART_STATE_REQ_RTS_LEVEL));
te = true;
}
if (!wrote && (uartState & ((1 << UART_STATE_WAITING_ON_CTS) | (1 << UART_STATE_WAITING_ON_TX_HW))) == 0)
{
wrote = true; // We'll write once per call to this function because the HW has a read FIFO but not a write FIFO, so may read multiples
if (!ringBufferEmpty(&txBuffer))
{
uint8_t b = ringBufferDequeue(&txBuffer);
write |= 0x8000 | b;
te = false; // if requestiung a TE# write, no need because we're writing anyway
}
}
// When there is nithing to write, but we want to update RTS, we need to set the
// UART_TRANSMIT_ENABLE_BIT in the write side exchange
if (te)
{
te = false;
write |= 0x8000 | (1 << UART_TRANSMIT_ENABLE_BIT);
}
read = writeAndReadUart(write);
if (IS_BIT(read, UART_FRAMING_ERROR_BIT))
{
if (IS_BIT(uartState, UART_STATE_REQ_RAM_LEVEL))
{
SET_BIT(uartState, UART_STATE_REQ_RAM_CHANGE);
SET_BIT(uartState, UART_STATE_REQ_RAM_LEVEL);
SET_BIT(uartError, UART_ERROR_FRAMING_BIT);
loop = true; // need to change RAM
errorOccured = true;
}
}
else if (IS_BIT(uartState, UART_STATE_REQ_RAM_LEVEL))
{
SET_BIT(uartState, UART_STATE_REQ_RAM_CHANGE);
RESET_BIT(uartState, UART_STATE_REQ_RAM_LEVEL);
loop = true; // need to change RAM
}
// keep track of last T bit
SET_BIT_BY(uartState, UART_STATE_WAITING_ON_TX_HW, IS_BIT(read, UART_TRANSMIT_EMPTY_BIT));
// keep track of CTS
SET_BIT_BY(uartError, UART_STATE_WAITING_ON_CTS, IS_BIT(read, UART_RTS_CTS_BIT));
if (IS_BIT(read, UART_PARITY_BIT_BIT))
{
SET_BIT(uartError, UART_ERROR_PARITY_BIT);
errorOccured = true;
}
if (IS_BIT(read, UART_DATA_READY_BIT))
{
if (!ringBufferFull(&rxBuffer))
{
ringBufferEnqueue(&rxBuffer, (uint8_t)read);
if (!IS_BIT(uartConfig, UART_STATE_REQ_RTS_LEVEL) && ringBufferCount(&rxBuffer) >= UART_RECV_RTS_STOP_THRESHOLD)
{
// buffer getting too full, lets hold off on
SET_BIT(uartState, UART_STATE_REQ_RTS_CHANGE);
RESET_BIT(uartState, UART_STATE_REQ_RTS_LEVEL);
SET_BIT(uartState, UART_STATE_REQ_RM_CHANGE);
RESET_BIT(uartState, UART_STATE_REQ_RM_LEVEL);
loop = true; // need to take down RTS
}
}
else
{
SET_BIT(uartError, UART_ERROR_RX_OVERFLOW_BIT);
errorOccured = true;
}
}
}
if (errorOccured)
{
SET_BIT(uartError, UART_ERROR_TO_REPORT_BIT);
}
}
// UART ISR
// called from the actual ISR, so this one doesn't end in reti
void uartIsr()
{
// Do the read/write which will clear R&RM, T&TM, and RA&RAM, and update the RX buffers, and
// send a byte if there is one on the TX buffer
readWrite();
// This ISR might be called on any ISR, so it's possible that nothing interesting happened.
// It is also called on system tick, so even though the UART has no information, it
// is possible to take advantage of this to implement timeouts
RESET_BIT(uartState, UART_STATE_PENDING);
}
///////////////////////////////////////////////////
//
// Low Level functions
//
uint16_t writeUartConfig(uint16_t config)
{
uartConfig = config & 0x3fff;
return uartExchange(0xC000 | uartConfig);
}
uint16_t readUartConfig()
{
return uartExchange(0x4000 | uartConfig);
}
uint16_t writeAndReadUart(uint16_t write)
{
return uartExchange(0x8000 | write);
}
uint16_t readUart()
{
return uartExchange(0x0000);
}
// Low level function that used the SPI/GPIO module to exchange a 16 word
// with the MAX3110E
uint16_t uartExchange(uint16_t write)
{
uint16_t read;
gpioWriteLevel(0, false);
read = spiExchange((uint8_t)(write >> 8), SPI_SHIFT_TO_MSB, 8);
read <<= 8;
read |= spiExchange((uint8_t)(write), SPI_SHIFT_TO_MSB, 8);
gpioWriteLevel(1, false);
}
ringBuffer.h:
#ifndef INCLUDE_RING_BUFFER_H
#define INCLUDE_RING_BUFFER_H
#include "common.h"
struct RingBuffer
{
uint8_t* buffer;
int count;
int enqueuePoint;
int dequeuePoint;
};
// Prepare a ring buffer for use
void ringBufferInit(struct RingBuffer* rb, uint8_t* buffer, int count);
// Query if ring buffer is empty
bool ringBufferIsEmpty(struct RingBuffer* rb);
// Query if ring buffer is full
bool ringBufferIsFull(struct RingBuffer* rb);
// Query number of items in ring buffer
int ringBufferCount(struct RingBuffer* rb);
// Add a byte into the ring buffer
void ringBufferEnqueue(struct RingBuffer* rb, uint8_t b);
// Gets an item if available. The user can send an optional
// pointer to bool which will be true if data was retrieved
uint8_t ringBufferDequeue(struct RingBuffer* rb);
#endif
ringBuffer.c:
#include "ringBuffer.h"
// This is a ring buffer for uint8_t of arbitrary length
// It is empty when enqueuePoint == dequeuePoint
// Initialize a new ring buffer. User supplies
void ringBufferInit(struct RingBuffer* rb, uint8_t* buffer, int count)
{
rb->buffer = buffer;
rb->count = count;
rb->enqueuePoint = 0;
rb->dequeuePoint = 0;
}
bool ringBufferIsEmpty(struct RingBuffer* rb)
{
return rb->enqueuePoint == rb->dequeuePoint;
}
// The buffer is full if the enqueuePoint is one less than the dequeue
// point. If the dequeue point is 0, then the
bool ringBufferIsFull(struct RingBuffer* rb) __critical
{
int diff = rb->dequeuePoint - rb->enqueuePoint;
return diff == 1 || ((diff + rb->count) == 1);
}
// Gets the number of items in the ring buffer
int ringBufferCount(struct RingBuffer* rb) __critical
{
int diff = rb->dequeuePoint - rb->enqueuePoint;
if (diff < 0)
{
diff += rb->count;
}
return diff;
}
// Adds a point to the ring buffer if space is available
// Returns true if the item was successfully added
void ringBufferEnqueue(struct RingBuffer* rb, uint8_t b) __critical
{
assert(!ringBufferIsFull(rb));
int p = rb->enqueuePoint;
rb->buffer[p] = b;
if (p == (rb->count - 1))
{
p = 0;
}
else
{
p++;
}
rb->enqueuePoint = p;
}
// Gets an item if available. The user can send an optional
// pointer to bool which will be true if data was retrieved
uint8_t ringBufferDequeue(struct RingBuffer* rb) __critical
{
uint8_t b = 0;
assert(!ringBufferIsEmpty(rb));
int p = rb->dequeuePoint;
b = rb->buffer[p];
if (p == (rb->count - 1))
{
p = 0;
}
else
{
p++;
}
rb->dequeuePoint = p;
return b;
}
spi.h:
#ifndef INCLUDE_DRIVER_SPI_H
#define INCLUDE_DRIVER_SPI_H
#include "common.h"
#define SPI_SHIFT_TO_MSB (0x20)
#define SPI_SHIFT_TO_LSB (0x00)
#define SPI_DEFAULT_HIGH (0x00)
#define SPI_DEFAULT_LOW (0x00)
#define SPI_INVERT_SCK (0x00)
#define SPI_DIRECT_SCK (0x00)
// Exchange 0-8 bits on SPI
uint8_t spiExchange(uint8_t b, uint8_t flags, uint8_t count);
#endif
spi.c:
#include "spi.h"
Z80_IO_PORT(SPI_REG, 0x83);
Z80_IO_PORT(SPI_CTRL, 0x84);
// Exchange 0-8 bits on SPI
uint8_t spiExchange(uint8_t b, uint8_t flags, uint8_t count)
{
SPI_REG = b;
SPI_CTRL = flags | count;
// delay for SPI clock
__asm__("nop");
__asm__("nop");
__asm__("nop");
__asm__("nop");
return SPI_REG;
}
gpio.h:
#ifndef INCLUDE_DRIVER_GPIO_H
#define INCLUDE_DRIVER_GPIO_H
#include "common.h"
// Gets the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
bool gpioReadDirection(int bit);
// Writes the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
void gpioWriteDirection(int bit, bool out);
// Gets the level to drive if GPIO 'bit' is an output
bool gpioReadOutLevel(int bit);
// Sets the level to drive if GPIO 'bit' is an output
void gpioWriteLevel(int bit, bool out);
// Gets the actual level of GPIO 'bit'
bool gpioReadInLevel(int bit);
#endif
gpio.c:
#include "gpio.h"
Z80_IO_PORT(GPIO_DIR, 0x80);
Z80_IO_PORT(GPIO_OUT, 0x81);
Z80_IO_PORT(GPIO_IN, 0x82);
// Gets the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
bool gpioReadDirection(int bit)
{
assert(bit >= 0 && bit < 7);
return (GPIO_DIR & (1 << bit)) != 0;
}
// Writes the direction 0=in, 1=out of GPIO number 'bit' which is 0-7
void gpioWriteDirection(int bit, bool out)
{
int b;
assert(bit >= 0 && bit < 7);
b = GPIO_DIR;
if (out)
{
b |= (1 << bit);
}
else
{
b &= ~(1 << bit);
}
GPIO_DIR = b;
}
// Gets the level to drive if GPIO 'bit' is an output
bool gpioReadOutLevel(int bit)
{
assert(bit >= 0 && bit < 7);
return (GPIO_OUT & (1 << bit)) != 0;
}
// Sets the level to drive if GPIO 'bit' is an output
void gpioWriteLevel(int bit, bool out)
{
int b;
assert(bit >= 0 && bit < 7);
b = GPIO_OUT;
if (out)
{
b |= (1 << bit);
}
else
{
b &= ~(1 << bit);
}
GPIO_OUT = b;
}
// Gets the actual level of GPIO 'bit'
bool gpioReadInLevel(int bit)
{
assert(bit >= 0 && bit < 7);
return (GPIO_IN & (1 << bit)) != 0;
}
Subscribe to:
Posts (Atom)



