This is a little helper for simavr that allows to connect multiple devices to the same SPI-bus. Each device must have a separate CS-pin (active low). To make it work on real hardware each device must tristate its output (MISO) when CS is high (don't worry about this for simulation, only worry about it if/once you want to build the real hardware - check datasheet of the corresponding devices).
AGPLv3+ and NO WARRANTY!
I assume you are familiar with simavr already. To make everything work you need the simavr-library and headers, the code that will run on the simulated AVR and code to simulate each external device (SPI-device in this application) connected to your AVR. You will also need libelf installed on your system (Debian: sudo apt install libelf1
).
simavr-spi-dispatcher only has two public functions:
spi_dispatcher_t * make_new_spi_dispatcher(struct avr_t * avr, char const * const name);
void init_spi_dispatcher(spi_dispatcher_t * const dispatcher, char const * const devicelist, avr_irq_t * spi_in_irq, avr_irq_t * spi_out_irq, ...);
Call make_new_spi_dispatcher()
after having initialized the AVR (avr_init()
). The arguments of this function should be pretty self-explanatory...
Call init_spi_dispatcher()
with the following arguments:
- A pointer to the dispatcher-structure as returned by
make_new_spi_dispatcher()
. - A list of device-names separated by ',' - for example "nRF,RAM,SD" if you want to connect a nRF24L01+, some external SPI-RAM and a SD-card to the SPI-bus on your AVR.
- The IRQ for SPI-input of the AVR (MISO) as returned by
avr_io_getirq()
. - The IRQ for SPI-output of the AVR (MOSI) as returned by
avr_io_getirq()
. - For each device you need need to specifiy four additional arguments, see below.
- A pointer (
void *
) to some data structure used by your SPI-device. This argument is simply passed to the callback-functions for each simulated device so you can have multiple instances of the same SPI-device connected to your AVR (or even several AVR inside the simulation, see this issue) if supported by the simulation code of the device. This parameter can beNULL
if not needed. - The IRQ for the CS-pin of the device as returned by
avr_io_getirq()
. Mandatory. - A pointer to a callback-function (of type
cb_cs_changed_t
) from the simulator code of the device that will be called when the CS-pin of the device changes state. You can specifyNULL
if you don't need this callback. - A pointer to a callback-function (of type
cb_spi_transaction_t
) from the simulator code of the device that will be called for each SPI-read-write to the device (only if CS is low). Mandatory.
typedef void (*cb_cs_changed_t)(void * device, const uint32_t CS);
The first argument is the pointer to some data structure that was specified as the first additional argument to init_spi_dispatcher()
. It is not touched by the dispatcher and can be NULL
.
The second argument is the new level of the CS-pin (0 or 1). Use this for example to reset the state of the SPI-interface of your device when CS goes high. I used a uint32_t
instead of a simple bool
for CS because that's what simavr uses internally.
typedef uint8_t (*cb_spi_transaction_t)(void * device, const uint8_t rx);
The first argument is the same as described above. The second argument is the byte sent by the AVR (==MOSI) to be used by your simulation code. The function returns what your device sends back to the AVR (==MISO).
In this example i want to connect three devices to the SPI1 (ie the USART1 used as a SPI-master, thats why in the code i use AVR_IOCTL_*UART*_GETIRQ
) of an Atmega1284P:
- A nRF24L01+
- some external RAM
- a SD-card
The nRF has its CE-pin (called CSN, don't confuse this with the CE-pin for RX/TX!) connected to PD6. There are two nRF (and two AVR, second not shown here) inside the "circuit" so the simulation code of the nRF needs a pointer to the correct data structure.
The RAM has its CE-pin connected to PC2. The pointer to the device-internal data structure is NULL
, the simulation code of the RAM does not support multiple instances and will ignore this argument.
The SD-card has its CE-pin connected to PD7. Same thing for the pointer as above.
spi_dispatcher_t * dispatch1=make_new_spi_dispatcher(avr1, "AVR1");
init_spi_dispatcher(dispatch1, "nRF,RAM,SD", avr_io_getirq(avr1, AVR_IOCTL_UART_GETIRQ('1'), UART_IRQ_OUTPUT), avr_io_getirq(avr1, AVR_IOCTL_UART_GETIRQ('1'), UART_IRQ_INPUT), \
nRF1, avr_io_getirq(avr1, AVR_IOCTL_IOPORT_GETIRQ('D'), 6), &csn_nRF, &spi_nRF, \
NULL, avr_io_getirq(avr1, AVR_IOCTL_IOPORT_GETIRQ('C'), 2), &ce_RAM, &spi_RAM, \
NULL, avr_io_getirq(avr1, AVR_IOCTL_IOPORT_GETIRQ('D'), 7), &ce_SD, &spi_SD);
- By default the code is configured for a maximum of two dispatchers with maximum five SPI-devices each inside your project. You can adjust these parameters inside the header-file.
- As said above the CS-pin of your device must be active low.
- If at some point there is more than one selected SPI-device (more than one CS-pin low) the dispatcher will abort simulation with an error (bus collision).