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
No comments:
Post a Comment