2024-02-01 08:52 AM
I am trying to receive a frame containing 10 bytes of serial data, each frame is transmitted at a 100ms rate.
However, as the UART can start receiving the 10 byte frame at any point in time, the received data is becoming out of sync.
How do I sync the UART peripheral so that it always starts receiving at the start of the 10 byte frame ?
Below is a scope capture of the 10 bytes of serial data:
Below is a scope capture of the 10 bytes of serial data being sent at a 100ms rate:
I am using the STM32 Nucleo-64 development board with STM32L433RC MCU.
Its as if I need to monitor the serial line with a timer, in order to detect the period when the serial line is high for say 50ms and use this timer to enable and disable the UART peripheral. But there must be a better way to do this ?
The UART is configured as shown below and the rx buffer is a size of 10.
The 10 bytes are transmitted by a electronic speed controller, used for radio control applications and contains the following data:
Byte 0: Temperature
Byte 1: Voltage high byte
Byte 2: Voltage low byte
Byte 3: Current high byte
Byte 4: Current low byte
Byte 5: Consumption high byte
Byte 6: Consumption low byte
Byte 7: Rpm high byte
Byte 8: Rpm low byte
Byte 9: 8-bit CRC
2024-02-02 05:57 AM
Can you not monitor the request line?
2024-02-02 06:06 AM
Possibly, but that would require writing a driver for this non-standard DSHOT protocol. I can't see how any of the hardware peripherals could decode this protocol, so it would have to be all done in software which would increase the burden on the processor.
Below is a screen capture of the DSHOT protocol, it uses two discrete PWM duty cycles to represent 0s and 1s.
The DSHOT data frame contains the following:
The DSHOT driver would have to monitor the bit used to indicate a telemetry request, then measure the duty cycle of that bit to see if its a 0 or 1.
2024-02-02 06:15 AM
Sounds like your only option is to look for the gaps between transmissions, then.
Have you tried contacting Powerdrives - maybe they can suggest something? eg, there must be some minimum gap between transmissions?
2024-02-02 06:29 AM - edited 2024-02-02 06:41 AM
I can use a tool called 'Mission Planner' to connect to the Flight Controller via USB and change lots of parameters.
This includes the rate at which the telemetry data is requested, the default is 100ms.
In which case the DSHOT command will set the telemetry request bit every 100ms
I don't know the range of this configurable parameter, it could be down to 10ms.
2024-02-02 06:57 AM - edited 2024-02-02 07:01 AM
The document tells you it's 115200 bit/s.
Doesn't say the format; guessing 8 data bits, 1 start bit, 1 stop bit, no parity - so 10 bit-times per data byte
So 115200 bit/s gives 11520 byte/s, or 87us/byte
So the 10-byte frame will take 870us (or a bit more if there's idle time between bytes)
So, with a frame every 100ms, that leaves ~ 99ms gap between frames ...
EDIT
which does correspond to your scope screenshot:
2024-02-02 07:09 AM
Thats correct Andrew
So I am trying the HAL_UARTEx_ReceiveToIdle_IT function
In main I use...
HAL_UARTEx_ReceiveToIdle_IT(&hlpuart1, rx_buff, 10);
The call back is...
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == LPUART1)
{
HAL_UARTEx_ReceiveToIdle_IT(&hlpuart1, rx_buff, 10);
process_telemetry();
}
}
It appears to work because when I set a breakpoint in HAL_UARTEx_RxEventCallback the data seems to be synchronized.
However, it does stop working when I continue to run the program after a break point, the only way to fix it is to restart the program.
I'm wondering if there is a buffer overrun when the program stops, because the serial data is still being transmitted.
I'm sure you can disable a peripheral whilst the program has stopped (to avoid overruns etc), but I cant remember where this setting is !?
2024-02-02 07:18 AM
You don't have to bother with synchronization, just save everything you receive to the telemetry file. Whatever processes that file later can use the CRC to delineate the 10-byte blocks. Any telemetry processing has to check the CRC anyway so use that requirement to simplify your recording code.
An efficient buffering scheme depends on how helpful your UART DMA is (a 20-byte circular with half-complete and complete interrupts is what you really want) and your filesystem's preferred blocking.
2024-02-02 07:51 AM
This, perhaps buffering with much larger blocks that are sector/cluster sized/aligned
There's also a COTS implementation using the STM32F411, these can be reprogrammed with custom code, but basically fire-hoses data from the serial port onto files on the MicroSD card.
https://github.com/d-ronin/openlager
https://www.aliexpress.com/i/3256804756627458.html?gatewayAdapt=4itemAdapt
2024-02-02 08:46 AM
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_DMA(&hlpuart1, rx_buff, 10); //You need to toggle a breakpoint on this line!
process_telemetry();
}
I see you call process_telemetry but no where do i see that you are saving the data to the SD card?
When you say the data is out of sync, do you mean that the packet contains some data from one packet and data from another packet?
If somewhere you are saving the data to the SD card, are you sure you're not getting another interrupt before the data is saved? Thus, your latter part of the packet is being written over with new data?
Toggle an LED when it starts the process of saving the data and toggle again when it finishes saving the data. Then you can see if the toggle happens in between each UART packet.
2024-02-02 03:53 PM
Myself, I would think about these approaches to solve the issue:
1. receive a permanent stream and find the packet in receiver buffer:
Keep going to receive all what you see and place it in a buffer. The buffer should be at least twice the size of the packets, e.g. 20 bytes - or even longer, e.q. for Nx packet size (as 10 bytes). The buffer wraps to start (not a FIFO buffer with shifting inside the buffer).
When you have a finished DMA (assuming: using DMA and waiting for 10 bytes): iterate over the buffer and use the CRC check as indication where a packet seems to be correct:
Take 10 bytes from location 0, build CRC, if it mismatches: shift to start now to location 1 and do all again. Do this "incremental" move of packet start pointer until you find a correct CRC. Then the packet should be found.
Now you would know the "offset" where packets start in buffer. Keep going now to take packets with the "found offset".
(and bear in mind when the buffer wraps to beginning because buffer was full).
If the offset was found once - it should be clear where the packet starts are.
But you have to do all the time again when you do not keep going with this process, or you have a lost packet (start over with this "synchronization").
See David Littel's comment
2. Use the gap (time elapsed) to sync:
When you have for sure a difference between the gap the bytes of a packet are sent and the duration where nothing is sent - use this as indication where a new packet comes in:
Use UART in INT mode (not DMA), so, for every received byte you get an interrupt. This stores the bytes received in a buffer.
Measure now the elapsed time (e.g. via HAL_GetTick() ). If you see now that the next following byte comes 100 ms later (as the bytes before) - check the length received when you have started. Count on every INT the number of bytes you got and remember the time stamp.
If you see now, there was a gap (larger time now between two bytes) but you have not counted up yet to 10 (bytes received): the packet which you have in memory is incomplete: discard it and start again: back to buffer start, back to INT counter 0 and do again: receive and count, measure difference on time stamps. You should get now 10 bytes in a row before you see again "a gap" on time stamps. These 10 bytes are now your valid packet.
It works only in INT mode, with "single-byte" reception and measuring on every byte received the time (comparing time stamps). Not possible in DMA mode!
3. Monitor the external "sync" signal:
As I understood from previous comments: there is a signal which triggers the device to send these 10 bytes.
Why you cannot use this signal as "packet start" indication?
Configure a GPIO with INT (raising or falling edge), connect this signal as input to your MCU. When the two external devices are "talking" now to each other, you monitor this signal. You get a GPIO INT (with an edge) and this is an indication for you as: "now, make the UART Rx hot, be prepared to listen and to store the next 10 bytes in a buffer".
Now you should be in sync with the byte stream and you have locked onto the packets.