An analogue joystick and implementing an SPI controller

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:

  1. Ground – one side of the potentiometers
  2. 5V – the other side of the pots
  3. X – the wiper on the first pot
  4. Y – the wiper on the second pot
  5. 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!

I did not bother to electrically test the joystick; instead I move right along to attaching it to the MAXI09 board. It was at that point (and at no time before) when I spent more then 30 seconds looking at the MCP3002 datasheet. Pretty quickly I spotted a problem: unlike the DS1305 (PDF); the Real Time Clock and CAT25256 (PDF); the 32KByte EEPROM, this SPI ADC uses a bit-orientated protocol. The RTC and the EEPROM send and receive commands and data in whole bytes, and never at the same time, whereas the ADC starts sending a response after just a few bits of command have been sent from the master. From the datasheet:
From this you can see that the IC will start presenting the captured value after receiving the 5 bit command sequence, which looks like this, in the order they appear on the wire:
  1. 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.
  2. Start (always a 1)
  3. SGL/DIFF; sets single ended or differential mode – this application using single ended mode, which is enabled with a 1
  4. ODD/SIGN; in single ended mode this sets which input is used (X or Y in this application)
  5. MSBF; a 1 sets Most Significant Bit First
After sending one dummy bit, the IC will immediately start sending bit 9 (the MSB) of the ADC result. Since this behaviour will no doubt catch some people out operating this IC from a byte-orientated SPI controller, as typically found in an MCU, is covered in a special section of the datasheet where another diagram is provided:

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;
For what it’s worth this is entirely my own code.

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[0] << 6) | (spidata[1] >> 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…

1 thought on “An analogue joystick and implementing an SPI controller

  1. Brendan

    Hi, note that in order to mitigate metastability issues, registered inputs should always be sequentially double sampled, meaning an input at reg_a.in is registered in reg_a.out on a clock transition and feeds reg_b.in, registered in reg_b.out on the second sequential clock, where reg_b.out is free of metastability.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.