Micro-XRCE-DDS : Serial communication
- markdownLast time, I talked about installing a eProsima Micro XRCE-DDS client into a stm32 based microcontroller. This time, I'll show you a way to talk with a DDS agent using serial communication.
Since simple UART functions like HAL_UART_Receive
don't tell us required information like how many byte we received, and also because we want SPEED, we will use the DMA version of these functions.
Setting up communication
dans cubemx, mettre en mode normal (on ne veux pas d'interruption) serial_transport_external.c/h
https://github.com/micro-ROS/micro_ros_setup/issues/270#issuecomment-772330417
Writing
XRCE need our function to be synchrone, wich is a bit of a shame since a big advantage of DMA is that it can transmit data while the µC is doing something else.
That's said, writting data is actually pretty straitforward:
size_t uxr_write_serial_data_platform(void* args, const uint8_t* buf, size_t len, uint8_t* errcode)
{
uxrSerialPlatform* platform = (uxrSerialPlatform*)args;
HAL_StatusTypeDef ret;
if (platform->uartHandle->gState == HAL_UART_STATE_READY)
{
ret = HAL_UART_Transmit_DMA(platform->uartHandle, (uint8_t*) buf, len);
while (ret == HAL_OK && platform->uartHandle->gState != HAL_UART_STATE_READY)
{
sleep(1);
}
return (ret == HAL_OK) ? len : 0;
}
return 0;
}
The code is simple, if our UART is ready, we start a DMA write and we activly wait until we are ready again.
Reading
Reading data is a bit more complexe. Since DMA is it's own hardware and work in parallele with our program, we have to cheat a little to get what we want.
An other thing to keep in mind is that XRCE doesn't really care if we received everything it expected. The procotol itself protect us.
#define UART_DMA_BUFFER_SIZE 2048
static uint8_t dma_buffer[UART_DMA_BUFFER_SIZE];
static size_t dma_head = 0, dma_tail = 0;
size_t uxr_read_serial_data_platform(void* args, uint8_t* buf, size_t len, int timeout, uint8_t* errcode)
{
uxrSerialPlatform* platform = (uxrSerialPlatform*)args;
if (platform->uartHandle->gState == HAL_UART_STATE_READY)
{
HAL_UART_Receive_DMA(platform->uartHandle, dma_buffer, UART_DMA_BUFFER_SIZE);
}
__disable_irq();
dma_tail = UART_DMA_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(platform->uartHandle->hdmarx);
__enable_irq();
size_t wrote = 0;
while ((dma_head != dma_tail) && (wrote < len))
{
buf[wrote] = dma_buffer[dma_head];
dma_head = (dma_head + 1) % UART_DMA_BUFFER_SIZE;
wrote++;
}
return wrote;
}
Because of the asynchrone nature of DMA and the way XRCE expect us the retreive data, we use a circular buffer. Basically, the DMA is always reading in our buffer, wich is large enough to contain any XRCE trame.
The real magic is with dma_tail
. We need to disable the IRQ to be sure that the DMA won't change it's counter while we are reading it.
dma_tail
contain how many bytes we received in our buffer, but since it's circular, we need dma_head
to tell us where is the begining of the current reading.
For exemple, at the first call, we have dma_tail=NB_RECEIVED_BYTE
and dma_head=0
. On the second call, dma_head
is now equals to the last dma_tail
and dma_tail
has increased by the new number of read byte.
We continue like this until we get to dma_tail=UART_DMA_BUFFER_SIZE
. At this point, our function will return without having readen everything, but it's alright, XRCE can dectect it and we will get the remaining bytes on the next call.
The next article will discuss how to write custom data using XRCE.