I’m needing to do continual SPI communication to read values from a dual channel ADC I have, and have written a kinda state-machine to do so. However, it doesn’t seem to be getting into the state that reads the second channel and I can’t figure out why. here’s the VHDL…
SPI_read: process (mclk)
--command bits: Start.Single.Ch.MSBF....
constant query_x: unsigned(ADC_datawidth-1 downto 0) := "11010000000000000"; -- Query ADC Ch0 ( inclinometer x-axis)
constant query_y: unsigned(ADC_datawidth-1 downto 0) := "11110000000000000"; -- Query ADC Ch1 ( inclinometer y-axis)
begin
if rising_edge(mclk) then
-- when SPI is not busy, change state and latch Rx data from last communication
if (SPI_busy = '0') then
case SPI_action is
when SETUP =>
SPI_pol <= '0'; -- Clk low when not active
SPI_pha <= 1; -- First edge is half an SCLK period after CS activated
SPI_action <= READ_X;
when READ_X =>
SPI_Tx_buf <= query_x; -- Load in command
y_data <= "00000" & SPI_Rx_buf(11 downto 1);
SPI_send <= '1';
SPI_action <= READ_Y;
when READ_Y =>
SPI_Tx_buf <= query_y; -- Load in command
x_data <= "00000" & SPI_Rx_buf(11 downto 1);
SPI_send <= '1';
SPI_action <= READ_X;
end case;
else
SPI_send <= '0'; -- Deassert send pin
end if;
end if;
end process SPI_read;
The command is sent to the Tx buffer, and the value from the last received data is written to a signal which is output to some seven segment displays. A pulse from SPI_send is required to start the transfer, and when started, SPI_busy is set high until the transfer is completed.
Right now it’ll only send the query_x over SPI, and I can know this since I can see it on the scope. Interestingly, however, It’s outputting the same value to both displays which leads me to think that it’s still getting into it’s READ_Y state, but not changing the Tx Data it’s outputting.
I’ve been staring at this code for hours now, and I can’t figure it out. Sometimes a fresh pair of eyes makes life easier, so if you spot anything please let me know.
Also, I’m very open to suggestions of better ways to deal with this, I’m just learning VHDL so I’m not even sure I’m doing things the right way mostly!
You are thinking along the right basic lines, but there are various things not-quite-right with your state machine – as the other answers say – and these are easy to discover in simulation.
For example
Now if SPI_Busy stays low for a second cycle, this will clearly not stay in state READ_X but transition directly to READ_Y which is probably not what you want.
A more normal state machine would treat the states as outermost, and interpret the input signals differently for each state :
You can see that this version treats the Busy signal as a handshake, and only progresses when Busy changes state. I am certain you can make your approach (IF outermost) work if you want, but you will have tofigure out how to apply the handshaking principle yourself.
There is also no “Idle” state where neither X or Y is being read; this SM will read X and Y alternately as fast as it can. Commonly you would read them both, then return to Idle until some other Start signal commanded you to leave Idle and perform a fresh set of reads.
You can possibly also make the state machine more robust with a “when others” clause. It’s not a must, if you guarantee to cover all your defined states, but it can make maintenance easier. On the other hand, without such a clause, the compilers will let you know of any uncovered states, guarding against mistakes.
There is a myth that a “when others” clause is essential and synthesis tools generate safer but less optimal state machines from a “when others” clause. However there are synthesis attributes or command line options to control how synth tools generate state machines.