It’s been a while since my last post. The main reason being a house move. On the plus side this is good for my projects as I now have lots more room to work on them. On the negative side, at least as far as my hobbies are concerned I’ve, somewhat unexpectedly, found myself doing some work for my old company.
I’ve found time to tacke a mini-project I’ve put off doing: building an analogue joystick and getting the MAXI09 to read its position.
Although I built the MAXI09 board more then two years ago, some of the hardware has not yet been tested. This includes the MCP3002 (PDF) SPI ADC IC, and the 9 pin joystick connector it is attached to – the connector dual purposed for Atari style digital joysticks, which of course have been well tested.
First up was building the joystick. The starting point was an XBox analog thumb stick out of a game controller. The thumb stick portion of the controller consists of two potentiometers arranged perpendicular to each other, with the stick itself having a button in its base such that the entire stick is attached to the button. It is possible to buy such a stick, presumably salvaged from game controller, either as a bare component or handily attached to a little PCB with headers for:
- Ground – one side of the potentiometers
- 5V – the other side of the pots
- X – the wiper on the first pot
- Y – the wiper on the second pot
- Fire – this is one side of a button integrated into the thumb stick, the other side of this button being attached to the ground pin
To make the joystick more comfortable to use and more sturdy, it has been enclosed in a small plastic project box. As it turned out, I used a separate fire button from a job lot of arcade-style buttons because the button within the stick is not, to me anyway, easy to operate.
A somewhat fuzzy picture of the completed mini-project:
And here’s a picture of the insides of the joystick box:
After looking around for some multicore cable for the joystick, I settled on butchering a network cable for its CAT6 cable. This worked out quite well; the solid cable is easier to solder then stranded, but I still find “in air” soldering awkward and it took about half an hour to attach the 9 pin plug!
- A “don’t care” bit; the diagram is not very clear but the first bit recieived after the master asserts the /CS signal is a don’t care.
- Start (always a 1)
- SGL/DIFF; sets single ended or differential mode – this application using single ended mode, which is enabled with a 1
- ODD/SIGN; in single ended mode this sets which input is used (X or Y in this application)
- MSBF; a 1 sets Most Significant Bit First
This diagram illustrates how working with this bit-orientated behaviour is perfectly possible in a byte-centric SPI controller: as the command byte is sent out, a byte needs to be read back in. To obtain the value, bits can be squashed back together to produce the 10 bit result.
The problem for MAXI09OS is that the syswrite command does not work like this. It is (or was) not possible to read in a byte at the same time as sending a byte to a device. Normally there is no reason to need to do this, but SPI has this interesting behaviour that an IC can send data simultaneously to receiving it. Extending the SPI write sub to set the a register to the value obtained when shifting out the byte should have been straightforward enough.
Upon looking at the write code in the SPI driver in MAXI09 I hit a bit of a snag, or at least an annoyance: it wouldn’t be possible to read in the byte in the write subroutine without completely rewriting it and making it extremely slow. This is because the write code efficiently uses both 8 bit accumulators. The only way to structure the write action to make it read in a byte at the same time would be to move the sending and receiving of a single bit into its own subroutine, making the code much slower then it is currently.
Because of wanting to see the ADCs working, I hit upon the idea of a quick hack: make the write call only send the top 5 bits. It would then be possible for the subsequent sysread calls to read the data generated without missing out most of the 10 bits of value data. Of course with this hack in place it would not be possible to use the SPI driver with either the RTC or the EEPROM ICs. This worked fine, which was a big relief: both the ADC was generating valid data, and the joystick itself was wired properly. However, I wont elaborate much on this because I pretty soon decided that, now rather then later, it was time to implement a proper SPI host controller in DISCo. This would make the sysread and syswrite subs inside the SPI driver completely trivial. Both would be the same, except that sysread always shifts out a zero byte.
I looked at two different, but related, SPI host controllers for inspiration
The first was 65SPI by Daryl Rictor and the second was 65SPI/B by André Fachat. Both are similar and each has its pros and cons. I prefer the 65SPI/B overall as it has interrupt routing, a handy feature for propagating, say, Real Time Clock ticks through without consuming interrupt lines further up the chain. Conversely the 65SPI/B only supports four device instead of the 65SPI’s eight.
Both devices share a bunch of other features:
- Support for all four SPI modes (CPOL and CPHA)
- Support for dividing a system clock signal down to produce the SPICLK signal
- Status register containing wether a byte is currently being clocked out, and wether the received byte has been read by the host
- Interrupt or polled modes
- Fast send and receive; receive without sending and vice-versa
I have implemented the simplest possible SPI host controller. It lacks all the above features and contains only two registers, SPIIN and SPIOUT, as well as the already implemented SPISELECT used for selecting which IC should be the target. The implementation is so trivial I can include it here:
library IEEE; use IEEE.STD_LOGIC_1164.ALL; use IEEE.STD_LOGIC_UNSIGNED.ALL; use work.P_DISCO.ALL; entity spishifter is port ( SPIOUTREG : in T_BYTEREG; -- What we are shifting out SPISELECTS : in T_BYTEREG; -- What device is selected SPIINDATA : out STD_LOGIC_VECTOR (7 downto 0); -- What byte was received E : in STD_LOGIC; -- Ticker MISO : in STD_LOGIC_VECTOR (3 downto 0); -- The inbound lines from each IC MOSI : out STD_LOGIC; -- The outbound line SCLK : out STD_LOGIC); -- The SPI clock line end entity; architecture behavioral of spishifter is signal OUTSTATE : STD_LOGIC_VECTOR (7 downto 0); -- Currently shifting byte out signal INSTATE : STD_LOGIC_VECTOR (7 downto 0); -- Ditto for in signal SINGLEMISO : STD_LOGIC; -- The selected input signal COUNTER : STD_LOGIC_VECTOR (2 downto 0); -- Byte shifting counter (0..7) signal RUNNING : STD_LOGIC; -- Are we shifting? signal TICK : STD_LOGIC; -- In or Out halves begin -- Obtain the selected ICs input line SINGLEMISO <= MISO (0) when SPISELECTS.DATA = x"00" else MISO (1) when SPISELECTS.DATA = x"01" else MISO (2) when SPISELECTS.DATA = x"02" else MISO (3) when SPISELECTS.DATA = x"03" else '0'; process (E) begin if (E'Event and E = '0') then if (SPIOUTREG.nIE = '0') then -- Reset for byte transfer OUTSTATE <= SPIOUTREG.DATA; -- Grab byte we are sending INSTATE <= x"00"; -- Clear input COUNTER <= "000"; -- MSB will be first RUNNING <= '1'; -- Go! TICK <= '1'; -- Start in input half else if (RUNNING = '1') then if (TICK = '0') then -- Output; shift byte up one OUTSTATE (7 downto 0) <= OUTSTATE (6 downto 0) & '0'; -- Next byte COUNTER <= COUNTER + 1; if (COUNTER = "111") then -- Done. Mark not running and grab input byte RUNNING <= '0'; SPIINDATA <= INSTATE; end if; else -- Input; shift byte up one grabbing selected MISO INSTATE (7 downto 0) <= INSTATE (6 downto 0) & SINGLEMISO; end if; -- Invert TICK <= not TICK; end if; end if; end if; end process; -- Send out MSB and set clock MOSI <= OUTSTATE(7) when RUNNING = '1' else '0'; SCLK <= TICK when RUNNING = '1' else '0'; end architecture;
Fortunately all three types of SPI IC on the MAXI09 board support CPOL=0 CPAH=0 mode, as this is all that this code supports.
The SPIOUTREG and SPISELECTREG entities encapsulate a byte-wide addressable register. A key output of this entity is the nIE signal, which is low on (and only on) the system clock where the register is written to. This is the signal which starts the SPI sending (and receiving) action. The 8 bits are shifted out from this register, while the input is shifted in to a plain vector which, when addressed by the MPU at a particular address in DISCo, the received byte is presented.
I am only a VHDL newbie, despite having played around with it, on and off, for a few years now and I welcome any tips on improving this code.
Criticially, the RUNNING signal is not exposed to a register. Since it isn’t exposed, the code in the SPI driver in MAXI09OS most wait (via a sequence of nop instructions) for the SPI host to shift out the byte. Calculating the minimum time that the MPU must wait was an interesting little excersise.
Since there are 8 bits to send, and each bit takes 2 machine cycles (TICK must go from 1 to 0 and back to 1), and the 6809 nop instruction also takes 2 machine cycles to execute, 8 nops are required. This means that the SIPCLK line runs at 1MHz, which is nice and fast for my little micro. It means that SPI data could be sent, back to back, at a rate of about 100KB/sec, but of course the driver overhead in MAXI09 means bytes cannot be sent back to back.
Below is a screenshot from my Saleae Logc 16. I did my testing with the DS1305, setting and getting the time. The screenshot shows the time being sent retrieved from the IC:
In the case of the RTC code (user/gettime.asm in the git repo), getchars was used to read 7 bytes. This has additional overhead, on top of the overhead from the driver switch in sysread. None the less, the time taken to transfer a byte is about 40uS, giving a transfer rate of 25KB/s, which isn’t completely terrible for this micro.
Unfortunately I did not graph the previous bit-banged implementation of the SPI host, but it is possible to calculate the time taken to shift out one bit from the code:
ldb #SCLK ; clock high ... stb SPIOUT ; ... set it ldb SPIIN ; get state of miso rorb ; rotate into carry rola ; rotate back into a clr SPIOUT ; clock low
I make this 2+5+5+2+2+7=23 machine cycles, more then an order of magnitude greater then the two taken inside DISCo to shift one bit. Of course this ignores setup for sending a byte, in both cases. And it also ignores, in the above bit-banging code, the requirement to shift in the received byte, as the byte to be sent is shifted out. All in all I think this is a fantastic improvement.
After I’d verified that the SPI host was working by interfacing it with the DS1305 RTC, I set about interfacing with the analogue joystick. In the end I wrote a C program to do this (user/c/examples/joystick.c). This required adjusting the libmaxi09os glue for syswrite to get it to set a return value, which is the value read in by the (for example, and currently only) SPI driver spiwrite implementation. The only interesting line from this whole program is this one:
uint8_t pos = ((spidata << 6) | (spidata >> 2));
This joins up the value from the two bytes received from the IC, massages them together, and then discards the lowest two bits of the 10 bit value, leaving an 8 bit quantity for the joystick position.
This works very well; my test program is able to sample the joystick position in the X and Y directions and show it on the console. It’s obviously many times slower then reading the digital joystick position, but should be fast enough for a simple game, something I intend to tackle next…