SPI Master Receive Problems
-
- Posts: 126
- Joined: Tue May 17, 2016 8:12 pm
SPI Master Receive Problems
Hi guys. First post. Great forum. Thanks a lot to everybody for their time, effort and expertise that you so kindly share.
Regarding the subject, I exhausted google and this forum for some answer I cannot find. Hope somebody can direct me to the proper place/forum/document.
SPI Master in simple transmit mode, speed up to 12MHZ (no more) I used Saleae to trace everything.
Write is Ok, not intuitive but Ok.
Reading is a rather difficult and strange. My case is I need to send a CMD+ ADDH+ADDL to get 1 or more bytes.
Single Byte request.
Need to send CMD+ADDH+ADDL+DUMMY to get 4 answers and return the last one. Not very intuitive. SPI Protocol I guess. Since its just 4 bytes of tx_buffer and rx_buffer not a real problem, just weird.
Now to receive 1000 bytes, the slave has the same procedure. CMD+ADDH+ADDL and keep reading until CS goes High to mark end of reception. To do this I need to SEND a tx_buffer of 1000 bytes!!!! because if I SEND just the 3 bytes of CMD_ADDR, I get no replies.
How can I make this happen WITHOUT making a local buffer (1000 bytes in this case but imagine about 10,000 ).
Code
int FramSPI::readMany (uint16_t framAddr, uint8_t *values,uint16_t howmany)
{
esp_err_t ret=0;
spi_transaction_t t;
uint8_t *tx=(uint8_t*)malloc(howmany+3);
memset(&t, 0, sizeof(t));
*tx=MBRSPI_READ;
*(tx+1)=framAddr>>8;
*(tx+2)=framAddr& 0xff;
t.length=(howmany+3)*8;
t.tx_buffer=tx;
t.rxlength=(howmany+3)*8;
t.rx_buffer=values;
ret=spi_device_transmit(spi, &t);
return ret;
}
BTW, I could write 10,000 bytes and confirmed them but the above code looks fine with the Saleae but the buffer "values" gets junk.
Attach some Salaea screens.
Screenshot 1, 1 byte read. Notice that a dummy had to be sent after CMD+ADDH+ADDL and return last reply (0x20) (Could not attach due to forum limitations rules I guess)
Screenshot 2. A 10,000 byte write to Fram. Perfect.!!!!
Screenshot 3. A 10,000 read request sending just the CMD+ADDH+ADDL. Stops after ADDL. How to send 10,000 dummies without a malloc?
Screenshot 4. A 10,000 read request sending the CMD+ADDH+ADDL+ 9997 dummies from a malloc buffer. Fram sends the 10,000 bytes but the "values" buffer gets only junk. Tried many varieties of buffers, in main app memory, malloc and others with no success.
Thanks again.
Regarding the subject, I exhausted google and this forum for some answer I cannot find. Hope somebody can direct me to the proper place/forum/document.
SPI Master in simple transmit mode, speed up to 12MHZ (no more) I used Saleae to trace everything.
Write is Ok, not intuitive but Ok.
Reading is a rather difficult and strange. My case is I need to send a CMD+ ADDH+ADDL to get 1 or more bytes.
Single Byte request.
Need to send CMD+ADDH+ADDL+DUMMY to get 4 answers and return the last one. Not very intuitive. SPI Protocol I guess. Since its just 4 bytes of tx_buffer and rx_buffer not a real problem, just weird.
Now to receive 1000 bytes, the slave has the same procedure. CMD+ADDH+ADDL and keep reading until CS goes High to mark end of reception. To do this I need to SEND a tx_buffer of 1000 bytes!!!! because if I SEND just the 3 bytes of CMD_ADDR, I get no replies.
How can I make this happen WITHOUT making a local buffer (1000 bytes in this case but imagine about 10,000 ).
Code
int FramSPI::readMany (uint16_t framAddr, uint8_t *values,uint16_t howmany)
{
esp_err_t ret=0;
spi_transaction_t t;
uint8_t *tx=(uint8_t*)malloc(howmany+3);
memset(&t, 0, sizeof(t));
*tx=MBRSPI_READ;
*(tx+1)=framAddr>>8;
*(tx+2)=framAddr& 0xff;
t.length=(howmany+3)*8;
t.tx_buffer=tx;
t.rxlength=(howmany+3)*8;
t.rx_buffer=values;
ret=spi_device_transmit(spi, &t);
return ret;
}
BTW, I could write 10,000 bytes and confirmed them but the above code looks fine with the Saleae but the buffer "values" gets junk.
Attach some Salaea screens.
Screenshot 1, 1 byte read. Notice that a dummy had to be sent after CMD+ADDH+ADDL and return last reply (0x20) (Could not attach due to forum limitations rules I guess)
Screenshot 2. A 10,000 byte write to Fram. Perfect.!!!!
Screenshot 3. A 10,000 read request sending just the CMD+ADDH+ADDL. Stops after ADDL. How to send 10,000 dummies without a malloc?
Screenshot 4. A 10,000 read request sending the CMD+ADDH+ADDL+ 9997 dummies from a malloc buffer. Fram sends the 10,000 bytes but the "values" buffer gets only junk. Tried many varieties of buffers, in main app memory, malloc and others with no success.
Thanks again.
- Attachments
-
- Screen Shot3.png (101.1 KiB) Viewed 19279 times
-
- Screen Shot2.png (122.07 KiB) Viewed 19279 times
-
- Screen Shot4.png (137.31 KiB) Viewed 19279 times
Re: SPI Master Receive Problems
I've had similar problems with SPI reads (see viewtopic.php?f=14&t=1527).
I'm now using non DMA spi driver (viewtopic.php?f=18&t=1546) which passes all my tests for all combinations of write/reads.
If your processor has nothing useful to do during the long transfers, there's really no point in using DMA.
I'm now using non DMA spi driver (viewtopic.php?f=18&t=1546) which passes all my tests for all combinations of write/reads.
If your processor has nothing useful to do during the long transfers, there's really no point in using DMA.
Last edited by loboris on Tue Mar 28, 2017 8:30 am, edited 1 time in total.
-
- Posts: 9703
- Joined: Thu Nov 26, 2015 4:08 am
Re: SPI Master Receive Problems
Are you using half-duplex mode? It seems to me you need to use that. In full-duplex mode (the default) you will always receive data as you send it.
-
- Posts: 126
- Joined: Tue May 17, 2016 8:12 pm
Re: SPI Master Receive Problems
Thanks loboris and ESP_Sprite.
Half Duplex did solve the problem related to the rxbuffer extra bytes! Still, its manageable.
Loboris, as I understand how to use your driver, add spi_master_nodma.c and .h to driver and driver/include and have an #include in the app to them with this name. Still the spi_master.h and c have to be removed from the components in order not to have duplicate definitions.
Also a minor change to the spi_bus_add_device add in the &devcfg AND have to make a spi_device_select.
I tried the above but I get a compiler error. It does not allow a typdef struct xxx because it is already defined. Verbatim from the compiler:
Im now a C++ app not a C app. After several hours and trying all compiler directives and what I know, I could not fix it. If you can tell me how to do it, ill give it a try.
BTW, I can confirm that every 32 bytes of read in the spi you get a byte replaced by 0. As a note, I can not imagine working on these devices w/o a Logical Analyzer.
Do you know/understand what the command_bits, address_bits and dummy_bits are in the spi_device_interface_config_t? If they can be set and activated at will, they could be usefull, specially in the case of an spi device that has cmd-address structure. When set like 8,16 each, its send 24 bits of 0s before the first byte of the spi message.
Thanks again.
Half Duplex did solve the problem related to the rxbuffer extra bytes! Still, its manageable.
Loboris, as I understand how to use your driver, add spi_master_nodma.c and .h to driver and driver/include and have an #include in the app to them with this name. Still the spi_master.h and c have to be removed from the components in order not to have duplicate definitions.
Also a minor change to the spi_bus_add_device add in the &devcfg AND have to make a spi_device_select.
I tried the above but I get a compiler error. It does not allow a typdef struct xxx because it is already defined. Verbatim from the compiler:
Code: Select all
In file included from /Users/RSN/esp/TestApp/main/./FramSPI.h:7:0,
from /Users/RSN/esp/TestApp/main/./FramSPI.cpp:3:
/Users/RSN/esp/TestApp/main/./spi_master_nodma.h:140:16: error: using typedef-name 'spi_host_t' after 'struct'
typedef struct spi_host_t* spi_host_handle_t;
^
/Users/RSN/esp/TestApp/main/./spi_master_nodma.h:130:3: note: 'spi_host_t' has a previous declaration here
} spi_host_t;
^
/Users/RSN/esp/TestApp/main/./spi_master_nodma.h:141:16: error: using typedef-name 'spi_device_interface_config_t' after 'struct'
typedef struct spi_device_interface_config_t* spi_device_interface_config_handle_t;
^
/Users/RSN/esp/TestApp/main/./spi_master_nodma.h:88:3: note: 'spi_device_interface_config_t' has a previous declaration here
} spi_device_interface_config_t;
BTW, I can confirm that every 32 bytes of read in the spi you get a byte replaced by 0. As a note, I can not imagine working on these devices w/o a Logical Analyzer.
Do you know/understand what the command_bits, address_bits and dummy_bits are in the spi_device_interface_config_t? If they can be set and activated at will, they could be usefull, specially in the case of an spi device that has cmd-address structure. When set like 8,16 each, its send 24 bits of 0s before the first byte of the spi message.
Thanks again.
Re: SPI Master Receive Problems
@rsimpsonbusa
Sorry, I've only tested the driver locally.
I have now changed the conflicting functions and struct names in spi_master_nodma driver, so now you can place spi_master_nodma.c in <esp-idf_path>/components/driver directory and spi_master_nodma.h in <esp-df_path>/components/driver/include/driver directory. No need to remove the original spi_master driver.
If you set command_bits and/or address_bits in spi_nodma_device_interface_config_t structure, command and/or address fields from spi_nodma_transaction_t structure will be send before data phase (before the data from tx_buffer/tx_data are sent). If dummy bits is set, dummy-bits clocks are emited before data phase with no data on mosi pin.
Remember that command_bits can be max 16 bits and address_bits max 64 bits. See how it's used in ts_cmd function in spi_master_demo.c to send command and receive response to/from touch controller.
In spi_master_nodma driver you can easily change command_bits, address_bits and dummy_bits before the actual transmission.
If 'handle' is spi_nodma_device_handle_t, and 't' is spi_nodma_transaction_t, just set handle->cfg.command_bits = nn and set t.command to desired value.
After changing command_bits, address_bits and dummy_bits, spi_nodma_device_select(handle, 1) must be executed to changes to take effect!
I've tested sending & receiving large amount of data and it works without any problem.
Your function could look like this:
Sorry, I've only tested the driver locally.
I have now changed the conflicting functions and struct names in spi_master_nodma driver, so now you can place spi_master_nodma.c in <esp-idf_path>/components/driver directory and spi_master_nodma.h in <esp-df_path>/components/driver/include/driver directory. No need to remove the original spi_master driver.
If you set command_bits and/or address_bits in spi_nodma_device_interface_config_t structure, command and/or address fields from spi_nodma_transaction_t structure will be send before data phase (before the data from tx_buffer/tx_data are sent). If dummy bits is set, dummy-bits clocks are emited before data phase with no data on mosi pin.
Remember that command_bits can be max 16 bits and address_bits max 64 bits. See how it's used in ts_cmd function in spi_master_demo.c to send command and receive response to/from touch controller.
In spi_master_nodma driver you can easily change command_bits, address_bits and dummy_bits before the actual transmission.
If 'handle' is spi_nodma_device_handle_t, and 't' is spi_nodma_transaction_t, just set handle->cfg.command_bits = nn and set t.command to desired value.
After changing command_bits, address_bits and dummy_bits, spi_nodma_device_select(handle, 1) must be executed to changes to take effect!
I've tested sending & receiving large amount of data and it works without any problem.
Your function could look like this:
Code: Select all
int FramSPI::readMany (uint16_t framAddr, uint8_t *values,uint16_t howmany)
{
esp_err_t ret=0;
// if command_bits & address_bits are set before, you don't need next 2 lines!
spi->command_bits = 8; // if MBRSPI_READ is 8-bit command
spi->address_bits = 16;
// if command_bits or address_bits are changed here, you have to execute
//spi_nodma_device_select(spi, 1)
spi_nodma_transaction_t t;
memset(&t, 0, sizeof(t));
t.command = MBRSPI_READ;
t.address = framAddr;
// or if address bytes have to be swapped t.command=(framAddr<<8) | ((framAddr >> 8) & 0xff);
t.rxlength=(howmany+3)*8;
t.rx_buffer=values;
ret=spi_nodma_device_transmit(spi, &t);
return ret;
}
-
- Posts: 126
- Joined: Tue May 17, 2016 8:12 pm
Re: SPI Master Receive Problems
Great, Ill try it later and report back.
RSN
RSN
-
- Posts: 126
- Joined: Tue May 17, 2016 8:12 pm
Re: SPI Master Receive Problems
Hi loboris.
No joy Well some...
The compiler gave the same error so I finally realized what it was. The typedef struct ...... had to be changed as follows
As simple as that. Its a typedef for a pointer to something, in this case a Struct, but for some reason C++ does not accept the Struct after typedef.
I suggest you put a guard for C++ like
Next the real stuff.
A 1 byte write (actually 4 to the spi) is done correctly (screenshot1). Not so MANY(100) bytes(screenshot2), it drops the CS at some point I don't know why. Same CS drop happens in the Read (1 byte or many) Screenshot 3.
Regards.
Read Code
No joy Well some...
The compiler gave the same error so I finally realized what it was. The typedef struct ...... had to be changed as follows
Code: Select all
typedef spi_nodma_host_t* spi_nodma_host_handle_t;
typedef spi_nodma_device_interface_config_t* spi_nodma_device_interface_config_handle_t;
As simple as that. Its a typedef for a pointer to something, in this case a Struct, but for some reason C++ does not accept the Struct after typedef.
I suggest you put a guard for C++ like
Code: Select all
#ifdef __cplusplus
typedef spi_nodma_host_t* spi_nodma_host_handle_t;
typedef spi_nodma_device_interface_config_t* spi_nodma_device_interface_config_handle_t;
#else
typedef struct spi_nodma_host_t* spi_nodma_host_handle_t;
typedef struct spi_nodma_device_interface_config_t* spi_nodma_device_interface_config_handle_t;
#endif
A 1 byte write (actually 4 to the spi) is done correctly (screenshot1). Not so MANY(100) bytes(screenshot2), it drops the CS at some point I don't know why. Same CS drop happens in the Read (1 byte or many) Screenshot 3.
Regards.
Read Code
Code: Select all
uint8_t FramSPI::read8 (uint16_t framAddr)
{
uint8_t here;
spi_nodma_transaction_t t;
uint8_t data[4]={0,0,0,0};
memset(&t, 0, sizeof(t)); //Zero out the transaction
data[0]=MBRSPI_READ;
data[1]=framAddr>>8;
data[2]=framAddr& 0xff;
t.rxlength=8;
t.rx_buffer=&here;
t.length=24; //Command is 4 byes *8 =32 bits
t.tx_buffer=&data;
spi_nodma_transfer_data(spi, &t); //Transmit!
return here;
}
- Attachments
-
- Screen Shot2.png (77.75 KiB) Viewed 19209 times
-
- Screen Shot1.png (66.56 KiB) Viewed 19209 times
-
- Screen Shot3.png (67.88 KiB) Viewed 19209 times
Re: SPI Master Receive Problems
Thank you for the suggestion about C++ guard, I'll add it.
About the CS problem, please try to use "software CS":
spi_nodma_transfer_data will automatically activate CS if it was not activated by spi_nodma_device_select before starting any transmit/receive operation and deactivate it after all transmit/receive is done, the CS will stay active during the whole transaction. It works that way both in duplex and in half duplex mode.
Or you can execute spi_nodma_device_select before the spi_nodma_transfer_data, in that case you can execute multiple spi_nodma_transfer_data and deactivate CS with spi_nodma_device_deselect after the last one.
When using "software CS" CS will always be active between spi_nodma_device_select & spi_nodma_device_deselect. Don't forget that these two functions must always be used in pairs.
About the CS problem, please try to use "software CS":
Code: Select all
spi_nodma_device_interface_config_t devcfg={
........
.spics_io_num=-1, //we will use "software" CS pin
.spics_ext_io_num=PIN_NUM_CS, //your "software" CS pin
........
};
Or you can execute spi_nodma_device_select before the spi_nodma_transfer_data, in that case you can execute multiple spi_nodma_transfer_data and deactivate CS with spi_nodma_device_deselect after the last one.
When using "software CS" CS will always be active between spi_nodma_device_select & spi_nodma_device_deselect. Don't forget that these two functions must always be used in pairs.
Last edited by loboris on Thu Mar 30, 2017 6:54 am, edited 2 times in total.
-
- Posts: 126
- Joined: Tue May 17, 2016 8:12 pm
Re: SPI Master Receive Problems
I think there is Concept problem.
CS must remain low during the TRANSACTION, which in theory is ALL TXbuffer bits + RXBuffer bits. That is, not only during transmission but also IF there is reception. That is why the Write works ok, because we are saying write 4 bytes *8 =32 bits and receive 0 bits TOTAL of 32 bits. But when you read, your say Transmit 3*8=24 bits Transmit (MOSI) and Receive 8 bits (MISO) TOTAL of 32 and THEN pull CS high, not after 24 txbits are sent. Apparently this is the case on hand.
I guess that the receive bits required will be read from MISO line in requested bits(rxlength) and could be that the MISO lines is inactive and will send back 0XFF per byte requested (MISO line is pulled high by definition). This you can see in the Logic Analyzer if you disconnect the MISO line for example.
So, in theory, a HALF DUPLEX write cmd (1 byte) is CMD+ADDl+ADDH+VAL would be 32 bits TOTAL to tx(32)+rx(0). A Read (1 byte) is CMD+ADDL+ADDH(24) and then read byte (8) from the MISO line TOTAL 32.
Does this make sense to you or am I missing something?
Your suggestion means that the TRANSMIT (spi_nodma_transfer) routine is not really an atomic one(TX+RX) and we need to be monitoring the spi low level routines , am I correct? Thanks again.
Regards.
CS must remain low during the TRANSACTION, which in theory is ALL TXbuffer bits + RXBuffer bits. That is, not only during transmission but also IF there is reception. That is why the Write works ok, because we are saying write 4 bytes *8 =32 bits and receive 0 bits TOTAL of 32 bits. But when you read, your say Transmit 3*8=24 bits Transmit (MOSI) and Receive 8 bits (MISO) TOTAL of 32 and THEN pull CS high, not after 24 txbits are sent. Apparently this is the case on hand.
I guess that the receive bits required will be read from MISO line in requested bits(rxlength) and could be that the MISO lines is inactive and will send back 0XFF per byte requested (MISO line is pulled high by definition). This you can see in the Logic Analyzer if you disconnect the MISO line for example.
So, in theory, a HALF DUPLEX write cmd (1 byte) is CMD+ADDl+ADDH+VAL would be 32 bits TOTAL to tx(32)+rx(0). A Read (1 byte) is CMD+ADDL+ADDH(24) and then read byte (8) from the MISO line TOTAL 32.
Does this make sense to you or am I missing something?
Your suggestion means that the TRANSMIT (spi_nodma_transfer) routine is not really an atomic one(TX+RX) and we need to be monitoring the spi low level routines , am I correct? Thanks again.
Regards.
Re: SPI Master Receive Problems
Sorry about the mess with transaction/transmission usage. I've edited the post to be more clear.
The problem exists only when using the hardware CS (.spics_io_num > 0), because then the spi hardware activates/deactivates the CS. I'm always using the software CS (.spics_io_num = -1 & .spics_ext_io_num=MY_CS), that way I have more control over CS. You do have some delay between CS activation and spi CLK start, but when transfering large amount of data it is hardly the problem
The problem exists only when using the hardware CS (.spics_io_num > 0), because then the spi hardware activates/deactivates the CS. I'm always using the software CS (.spics_io_num = -1 & .spics_ext_io_num=MY_CS), that way I have more control over CS. You do have some delay between CS activation and spi CLK start, but when transfering large amount of data it is hardly the problem
Who is online
Users browsing this forum: ESP_Roland and 55 guests