I'm trying to send data to an SPI-attached device and it's not working as expected.
My device is wired to the QCA 4020 developer kit board as follows:
- 3.3v power - attached to J1, pin 4 (Arduino pin 3V3)
- Ground - attached to J1, pin 7 (Arduino pin AGND)
- SPI CLK - attached to J5 pin 4 (GPIO25_BE, which the schematic says is SCK for an SPI master)
- SPI MOSI - attached to J5 pin 6 (GPIO26_BE, which the schematic says is MOSI for an SPI master)
- SPI MISO - attached to J5 pin 8 (GPIO27_BE, which the schematic says is MISO for an SPI master)
- Chip select - Attached to J5 pin 2 (GPIO24_BE, which the schematic says is SS for an SPI master)
On the software side, I made a copy of the Hello World demo app and replaced the code in its main thread with some code that appears to be correct for sending bytes to the SPI bus, but it doesn't seem to work. The device doesn't respond and the callback function is never called.
I've seen this same symptom (no callback function) from my Wi-Fi tests as well. It would appears that some system thread is not running, but I have not yet figured out what I need to do in order to start that thread. I have not yet found anything in the documentation or the QCLI_demo application to do this.
Does anyone know what I'm missing? What is the secret magic I need to do to start the background threads that SPI (and WLAN) use? Is there a function I need to call? Do I need to change the auto-generated files in the src/export directory? Do I need to make changs in the build/gcc folder (Makefile? app.config? Something else?)
Here's an excerpt from my code, which is a modification of the Hello World demo application. The values sent over SPI are to initialize an ST7789 TFT display controller chip, but that shouldn't be relevant to the problem here. There is additional work for driving the display, but that code (toggling a GPIO pin) is working, so I omitted it from this excerpt. If I can send any bytes over the SPI bus at all, the rest won't be a problem. (Printf is my function for sending text to the UART console - you can safely ignore it):
// Globals void *spi_handle = NULL; qapi_SPIM_Config_t spi_config = {0}; // SPI transfers are asynchronous, so we need a callback function and signal // so we can block until completion #define SPI_COMPLETION_MASK (0x10) qurt_signal_t spi_completion_signal; void init_spi(void) { qapi_Status_t status; status = qapi_SPIM_Open(QAPI_SPIM_INSTANCE_1_E, &spi_handle); if (status) { Printf("Can't open SPI Master instance 1. Status=%ld\r\n", status); return; } status = qapi_SPIM_Enable(spi_handle); if (status) { Printf("Can't enable SPI master (handle=%p). Status=%ld\r\n", spi_handle, status); return; } qurt_signal_create(&spi_completion_signal); spi_config.SPIM_Clk_Polarity = QAPI_SPIM_CLK_IDLE_LOW_E; spi_config.SPIM_Shift_Mode = QAPI_SPIM_INPUT_FIRST_MODE_E; spi_config.SPIM_CS_Polarity = QAPI_SPIM_CS_ACTIVE_LOW_E; spi_config.SPIM_CS_Mode = QAPI_SPIM_CS_DEASSERT_E; spi_config.SPIM_Clk_Always_On = QAPI_SPIM_CLK_NORMAL_E; spi_config.SPIM_Bits_Per_Word = 8; spi_config.SPIM_Slave_Index = 0; spi_config.min_Slave_Freq_Hz = 500000; // Minimum 500kHz spi_config.max_Slave_Freq_Hz = 16000000; // Maximum 16MHz spi_config.deassertion_Time_Ns = 1000; spi_config.loopback_Mode = 0; spi_config.hs_Mode = 0; } void spi_completion_callback_fn(uint32_t status, void *context) { if (status) { Printf("SPI callback failed with status %ld\r\n", status); } qurt_signal_set(&spi_completion_signal, SPI_COMPLETION_MASK); } void wait_for_spi_completion(void) { qurt_signal_wait(&spi_completion_signal, SPI_COMPLETION_MASK, QURT_SIGNAL_ATTR_WAIT_ANY | QURT_SIGNAL_ATTR_CLEAR_MASK); } void write_buffer(uint8_t *buffer, size_t len) { qapi_Status_t status; qapi_SPIM_Transfer_t tx_info = { 0 }; tx_info.buf_phys_addr = buffer; tx_info.buf_len = len; status = qapi_SPIM_Full_Duplex(spi_handle, &spi_config, &tx_info, NULL, spi_completion_callback_fn, NULL); if (status) { Printf("Failed to send %d bytes over SPI. Status = %ld\r\n", len, status); } else { wait_for_spi_completion(); } } void write_byte(uint8_t value) { write_buffer(&value, 1); } enum { SWRESET = 0x01, // Software reset SLPOUT = 0x11, // Sleep out NORON = 0x13, // Normal display mode on INVON = 0x21, // Inverse video mode on DISPON = 0x29, // Display on CASET = 0x2A, // Column address set RASET = 0x2B, // Row address set RAMWR = 0x2C, // RAM Write MADCTL = 0x36, // Memory and data access control COLMOD = 0x3A // Color mode (interface pixel format) } commands; send_spi_commands() { write_byte(SWRESET ); Sleep(200); write_byte(SLPOUT); Sleep(200); write_byte(COLMOD); write_byte(0x55); Sleep(10); write_byte(MADCTL); write_byte(0); Sleep(10); write_byte(INVON); Sleep(10); write_byte(NORON); Sleep(10); write_byte(DISPON); Sleep(2000); } static void MainAppThread(void *Param) // Replaces HelloWorld_Thread { init_spi(); send_spi_commands(); while(true) { Sleep(10000); // Will be replaced with other code later } }
I still have had no luck getting any kind of callback from SPI, but I think I learned a little bit more about what I need to do.
It appears that there are no QAPI calls to specify which GPIO pins are used for each of the 4 SPI master instances, but I think I need to edit the various export/DevCfg_master_*.xml files. Looking at the comments in the XML files, I think I need to edit the hex values in the "spi1" and "spi2" devices. For example, in DevCfg_master_devcfg_out.xml:
In the above example, I think I am configuring SPI instance 1 so GPIO6 (pin J5-5) is MOSI, GPIO5 (pin J5-3) is MISO, GPIO7 (pin J5-7)is CS, GPIO4 (pin J5-1) is CLK and GPIO14 (pin J5-21) is CS for the secondary device on the SPI bus.
I made this change to all four XML files (and, of course, re-wired my device to use those GPIO pins), but the above code still doesn't get any callback. I'm still clueless about what I need to do in order to make the SPI master code work.
Has anybody else successfully run an SPI master on this board? I can't possibly be the only one.
Yes. We have discovered that with the updated Printf function, the callback happens. I still need to figure out how to assign the GPIO pins I want to use for the SPI bus. I'll post a followup question when I learn more.
I've managed to make a bit of progress. I can now move data to my display using the SPIM API. As it turns out, there was one additional problem, in addition to the stack overflow from my "Printf" function. I had the SPIM shift mode set wrong for my device. Changing it to QAPI_SPIM_OUTPUT_FIRST_MODE_E makes it work.
So that first task is completed. The problem now is that the CDB uses the same GPIO pins for SPI and for JTAG. So whenever I run my test app, I need to remove jumpers J30, J31, J37, J38, J39, and J40. Then when I edit my code and want to re-flash it, I need to put them all back. This is really a pain in the neck.
So I'm now trying to see if there is any way to reassign the SPI interface to different GPIO pins.
Looking at the schematics, it appears that GPIOs 4, 5, 6 and 7 are not used by any hardware devices. I would like to use them for SPI CLK, MISO, MOSI and SS, respectively.
I edited DevCfg_master_devcfg_out_cdb.xml in the "spi1" driver section, replacng the GPIO numbers to match the above assignments (0x18 -> 0x07, 0x19 -> 0x04, 0x1A -> 0x06, 0x1B -> 0x05):
But doing only that didn't work. The app starts, but the SPIM calls don't seem to work. The SPI callback function never gets called, so something has either crashed or is blocked indefinitely.
Is it possible to reassign an SPI master to a different set of pins? And if it is possible, what do I need to do differently? Do I need to change something else? Do I need to initialize something? Maybe use the GPIO calls to set up the pins such that the SPI library can use them?
Hi,
You can also remap the JTAG pins to other ports.
It can be done via GPIO_9, GPIO_25 and GPIO_18
GPIO_9=0, GPIO_25=0, GPIO_18=0 means NO JTAG
GPIO_9=0, GPIO_25=0, GPIO_18=1 means JTAG in GPIO [53:50]
GPIO_9=0, GPIO_25=1, GPIO_18=0 means JTAG in GPIO [11:8]
GPIO_9=0, GPIO_25=1, GPIO_18=1 means JTAG in GPIO [27:24]
Other option is that you can flash via USB, its quicker.