SPI transaction terminates early - ESP-IDF - microcontroller

An ESP32 app using ESP-IDF (ESP32 SDK) communicates with two SPI slaves on the same SPI bus (ILI9341 TFT driver, NRF24L01+ RF transceiver). Overall, it works great. However, some of the data received from the RF transceiver is truncated, i.e. only the first few bytes are correct and the rest is garbage.
The problem is more or less reproducible and only occurs if there is SPI communication with the other slave (TFT driver) immediately before receiving the truncated data.
The problematic SPI transaction is a full-duplex transaction that sends a command byte and 10 dummy bytes while receiving 10 bytes. It uses the VSPI bus and DMA channel 1. If the problem occurs, only the first few bytes are correct while the last 2 to 6 bytes are invalid (0 or the value of the dummy bytes).
I dug into the SDK code (spi_master.c), added debug code and observed a surprising value in the DMA's lldesc_t struct:
At transaction start, it is initialized with length = 0x0c and size = 0x0c. 0x0c is 12 bytes, i.e. the 10 bytes rounded to the next word.
At transaction end, the values are length = 0x07 and size = 0x0c (length can vary slightly). So the transaction only reads 7 bytes and then somehow terminates. Or rather the DMA operations terminates.
Would you agree that the data indicates an early termination?
What could be the cause for the early termination?
Are there some registers that could indicate the cause of the
problem?
The code is pretty straightforward:
uint8_t* buffer = heap_caps_malloc(32, MALLOC_CAP_DMA);
...
memset(buffer, CMD_NOP, len);
spi_transaction_t trx;
memset(&trx, 0, sizeof(spi_transaction_t));
trx.cmd = 0x61;
trx.tx_buffer = buffer;
trx.length = 8 * 10;
trx.rx_buffer = buffer;
trx.rxlength = 8 * 10;
esp_err_t ret = spi_device_transmit(spi_device, &trx);

It seems that the following warning – found in the SPI Slave driver documentation – also applies to a SPI master receiving data from a slave:]
Warning: Due to a design peculiarity in the ESP32, if the amount of
bytes sent by the master or the length of the transmission queues in
the slave driver, in bytes, is not both larger than eight and
dividable by four, the SPI hardware can fail to write the last one to
seven bytes to the receive buffer.
I've now changed the sender side to send at least 12 bytes and multiples of 4 and the problem is gone.
Let me now if you think it just works because of luck and my assumption is wrong.

Related

Wire Library - just the first byte device address is being transmitted

I am starting to study the Wire library (no previous Arduino Wire library experience), I read some info taken from here.
As you all know, this really simple example changes the value of a AD5171 digital potentiometer via I2C. Written by Nicholas Zambetti and Shawn Bonkowski, demonstrates use of the Wire library.
I just copied and reduced the code below a little from the example. I am an experienced assembler and C/C++ programmer and hardware developer/designer. Although several I2C devices like DS3231 RTC, etc. work fine using standard Arduino libraries, the mentioned example doesn't work for me in my working NANO board. What am I doing wrong?
This code should transmit:
first the I2C protocol device address - Start / 8 + 1bits
test instruction data byte
variable 1 test byte constantly incremented
I2C Stop condition
The only byte I can see in my oscilloscope is just the first one (please see picture below). The 2 data bytes are not being transmitted. If I reduce the transmission to just the step #2 instruction single data byte, the same result is shown.
#include <Wire.h>
void setup()
{
Wire.begin(); // join i2c bus (address optional for master)
}
byte val = 0;
void loop()
{
Wire.beginTransmission(44); // transmit to device #44 (0x2c)
Wire.write(byte(0x55)); // sends instruction byte
Wire.write(val++); // sends potentiometer value byte
Wire.endTransmission(); // stop transmitting
delay(50); // some time delay for my oscilloscope
}
This is what this code produces:
I see transferred data as 01011000 1. I hope the is right decoded. Why you not zoom it more for better reading? It is hard see data levels on raising clock edges. This match to address 44, 0x2C . As you can also see that you get NACK on the end of address octet. So this means no device with transmitting address is on the bus.
You can get error code about sending process from endTransmission function as return int. Send this value to serial monitor.
Error codes you can see in arduino documentation
uint8_t err;
err= Wire.endTransmission(); // stop transmitting and get status
You can also use sketch i2c_scanner from arduino Wire examples to discovery right address of your device.

Mavlink command what does the [180] means?

I am trying to send a mavlink command for instance
GPS_RTCM_DATA ( #233 )
flags uint8_t
len uint8_t
data uint8_t[180] RTCM message (may be fragmented)
https://mavlink.io/en/messages/common.html#GPS_RTCM_DATA
I understand uint8_ would be in a single byte unsigned int.
What does the [180] means?
The uint8_t[180] in the MAVLink GPS_RTCM_DATA message means that the data field can contain up to 180 bytes.
Beware that RTCM messages can be bigger than 180 bytes and be fragmented in
more than one GPS_RTCM_DATA message.
You can check the flags field as stated in the mavlink documentation:
LSB: 1 means message is fragmented, next 2 bits are the fragment ID,
the remaining 5 bits are used for the sequence ID. Messages are only
to be flushed to the GPS when the entire message has been
reconstructed on the autopilot. The fragment ID specifies which order
the fragments should be assembled into a buffer, while the sequence ID
is used to detect a mismatch between different buffers. The buffer is
considered fully reconstructed when either all 4 fragments are
present, or all the fragments before the first fragment with a non
full payload is received. This management is used to ensure that
normal GPS operation doesn't corrupt RTCM data, and to recover from a
unreliable transport delivery order.
I tried every but it doesn't work. Except putting it as a 180 byte arrays. The data might be only 30 bytes for example. But input with the other 150 0x00 bytes in this way, the python program accepts my command. Strangely so. I can't explain why but in this case it works.

BlueNRG wrong header

I bought STEVAL-MKSBOX1V1 of ST and wanted to write my own library to create a BLE application.
The BLE module on board is SPBTLE-1S which mounts BlueNRG-1, I wrote my own firmware in order to communicate using SPI protocol.
After resetting the module using the pin I send a read request from MCU to the BLE module,
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,0);
HAL_SPI_TransmitReceive(&hspi2,tx,rx,5,1);
HAL_GPIO_WritePin(SPI1_CS_GPIO_Port,SPI1_CS_Pin,1);
I send:
{0x0b,0x00,0x00,0x00,0x00}
I get:
{0xff,0x08,0x00,0x06,0x00}
So apparently nothing good because the first byte is 0xff and not 0x02 (right?), but the 4th bit(0x06) should indicate the amount of data inside the read buffer.
If I read 6 bytes I get
{0x04,0xff,0x03,0x01,0x00,0x01}
And according to AN4494 It is exactly what I should read after the reset event on the module.
Then my question is why I keep getting {0xff,0x08,0x00,0x06,0x00} and not something like {0x02,....}?
Is there anything related to BlueNRG1 that is different from BlueNRG2? (It is so hard to find good documentation)
If anyone has some experience on SPI communication with this module I would like to have a bit of consulting.
Thanks in advance
{0xff,..} would be correct.
AN4494 is for BlueNRG-MS, the 5-byte SPI header for MISO is: {Ready, WBufLen, 0x00, RBufLen, 0x00}. In BlueNRG-1 and BlueNRG-2, that SPI header has been modified to: {0xff, CmdLen0, CmdLen1, DataLen0, DataLen1}, in order to support ACI packets with length more than 127 bytes in a single SPI transaction. The first byte in the header is fixed to 0xff.
Unfortunately currently there is no document describing the modification. You may need to refer to DTM SPI sample project to know the details.
In your transaction, if executing a Read command and continuing to read MISO for the 6 byte data:
{0xff,0x08,0x00,0x06,0x00},{0x04,0xff,0x03,0x01,0x00,0x01}
That is a ACI Blue initialized event, saying firmware has started properly. That can be parsed:
Header
0x0006: 6 bytes (data) is available to be read
Data
0x04: HCI event packet
0xFF: ACI (vendor specific) event
0x03: parameter length
0x10 0x00: event = 0x0001, which is ACI_BLUE_INITIALIZED_EVENT
0x01: reason code = 1, which means firmware started properly

high-speed case construct assembler + load DPTR fast - 8051

I'm currently implementing a serial routine for an 8051 IC (specifically AT89C4051) and I don't have much stack space or memory left, and in order for me to achieve a decent baud rate on the serial port (38K or better), I need to make a high speed case construct since in my serial port interrupt routine, I'm building a packet and checking it for validity.
Assume we are in the serial interrupt and R0 is the address to the memory space in which data is to be received. Let's assume start address is 40h
So here we go with a bunch of compares:
Branching via many compares
serial:
mov A,SBUF
mov #R0,A
mov A,R0
anl A,#07h ;our packet is 8 bytes so get current packet # based on what we stored so far
cjne A,#0h,nCheckMe ;this gets scanned if A=7... waste 2 clock cycles
//We're working on first byte
ajmp theend
nCheckMe:
cjne A,#1h,nCheckThem ;this gets scanned if A=7... waste 2 clock cycles
//We're working on second byte
ajmp theend
nCheckThem:
...
cjne A,#7h,nCheckEnd
//We're working on last byte
ajmp theend
nCheckEnd:
theend:
inc R0
reti
The above code might be practical at first but as the current byte in the packet to work on increases, the routine runs 2 clock cycles slower each time because of the extra "cjne" instruction processing. For example, if we are on the 7th byte, then "cjne" would happen many times because it has to scan through each case which adds slowness.
Branching via jump
Now I thought of using just a jump but I can't figure out how to load DPTR at high speed because the interrupt can get called even when some other process is using the value of DPTR.
I thought of this code:
serial:
mov A,SBUF
mov #R0,A
mov A,R0
anl A,#07h ;our packet is 8 bytes so get current packet # based on what we stored so far
swap A ;multiply A times 16 and
rr A ;divide A by 2 so we get address times 8 since each block uses 8 bytes of code space.
mov R3,DPH ;save DPTR high byte without breaking stack
mov R6,DPL ;save DPTR low byte
mov dptr,#table
jmp #A+DPTR
theend:
mov DPL,R6 ;restore DPTR low byte
mov DPH,R3 ;restore DPTR high byte
inc R0 ;move on to next position
reti
table:
;insert 8 bytes worth of code for 1st case
;insert 8 bytes worth of code for 2nd case
;insert 8 bytes worth of code for 3rd case
...
;insert unlimited bytes worth of code for last case
In my code, R3 and R6 were free so I used them to store the old DPTR value but those mov instructions as well as loading the new DPTR value take 2 cycles each for 10 cycles total (including restoring old value).
Is there a faster way to process a case construct in 8051 assembly code so that my serial routine processes faster?
Don't run logic in the ISR if possible. If you insist, you might be able to assign DPTR to the ISR and only use it in very short pieces of normal code with interrupts disabled. Alternatively, a PUSH+RET trick could work.
Here is a chained approach, where each processed character just sets the address for the next step. If you can ensure the steps are within the same 256 byte block, you only ever need to update the low byte. The total overhead is 8 cycles, but you also save the 4 cycles for the arithmetic so it's a win of 6 cycles.
.EQU PTR, 0x20 ; two bytes of SRAM
; Initialize PTR to address of step1
serial:
push PTR
push PTR+1
ret
step1:
; do stuff
mov PTR, #low8(step2)
reti
last_step:
; do stuff
mov PTR, #low8(step1)
reti

Add to QByteArray with 1 byte the 9-th bit

I have a situation right now when I have after reading the byte stream from COM port in object of QByteArray type exactly and only 1 byte of data. BUT one very non-friendly protocol requires to have 9 bits of data after reading data from COM port.
But according to win32API function: ReadFile(....) I can read from the COM stream ONLY bytes= 1,2,3.....
So - That's why I am reading only 8 bits=1 byte with help of this function and with help of some operations with parity bit I am calculating the value of the 9th bit of generalized data...
So on one hand I have 1 byte (8 bits) of proper(real) data - on another hand I have a value of this 9th bit (0 or 1); 2 objects which in sum must create the value of generalized data.
How I can combine these objects into one & final QByteArray object? Because the global function ReadComData can and must return only QByteArray object.
UARTs cannot "write" 9-bit data. On the wire, your (typically 8-bit) data are usually framed between a start-bit and a stop-bit, so you have 10 bits transmitted for every byte you send. If you have a parity bit, it is transmitted after the last data bit, but before the stop bit. But this is generated by the sending UART, not part of a protocol. A data bus for a typical UART 16550 is only 8-bits wide (you can actually send 5-, 6-, 7-, or 8-bit data).
On the receiving end, the UART has to be configured based on what is on the wire. If your sender is using a parity bit, then you program the UART (via the "COM" port settings) accordingly. The parity bit is just to help check for errors on the wire. It is based on the data bits -- you cannot put another data bit in a parity bit. The receiving UART can be used to check for parity errors (read via the line status register (LSR)), and this can be passed up to you via system calls.
It is possible your protocol is splitting up the data across multiple bytes. If that's the case, then convert two bytes into one 16-bit word and mask the 6 bits you don't want to use.

Resources