Serial port

As was done in the 4SE07 lab, we want to be able to send image data from the serial port. We will configure the serial port, then write a decoding task to handle received bytes.

Fortunately, this will be much simpler to do so using Rust and Embassy.

The procedure

Of course, we will create a serial_receiver asynchronous task. This task will:

  1. receive the needed peripherals needed to configure the serial port
  2. receive the image bytes
  3. update the shared image by copying the received bytes
  4. loop to receiving the image bytes

Receiving the image efficiently

How can we receive the image most efficiently? How will we handle incomplete images, or extra bytes sent after the image?

The first byte sent is a marker (0xff), we must wait for it. Then we should receive 192 bytes, none of which should be a 0xff. We want to receive all bytes in one DMA (direct memory access) transaction. But what will happen if an image is incomplete?

In this case, another image will follow, starting with a 0xff. In our buffer, we will have:

<------------------------ 192 --------------------->
| o | … | o | o | o | o | 0xff | n | n | n | … | n |
<---------- P ---------->      <-------- N -------->

where o belongs to the original image, and n to the new image (N bytes received). In this case, we should rotate the data P+1 places to the left (or N places to the right, which is equivalent) so that the new image data n is put at the beginning of the buffer, in order to have

<------------------------ 192 -------------------->
|n | n | n | … | n | o | … | o | o | o | o | 0xff |
<------- N --------X--------- P ----------->

We just need to receive the 192-N bytes starting after the N bytes, and check again that there is no 0xff in the buffer. If this is the case, we have a full image, otherwise we rotate again, etc.

Note that the initial situation, after receiving the 0xff marker, is similar to having N being 0, there is no need to special case it.

The task

❎ Create the serial_receiver task. This task receives several peripherals: the USART1 peripheral, the serial port pins, and the DMA channel to use for the reception.

By looking at the figure 29 on page 339 of the STM32L4x5 reference manual, you will see that the DMA channel for transmission (TX) of USART1 is DMA1_CH4, and the DMA channel for reception (RX) is DMA1_CH5.

❎ Create the Uart device. Also, don't forget to configure the baudrate to 38400.

Note that Uart::new() expects a _irq parameter. This is a convention for Embassy to ensure at compile time that you have properly declared that the corresponding IRQ is forwarded to the HAL using the bind_interrupts!() macro.

bind_interrupts!(struct Irqs {
    USART1 => usart::InterruptHandler<USART1>;
});

Irqs is the singleton that needs to be passed as the _irq parameter of Uart::new().

The logic

❎ Implement the reception logic, and update the shared image when the bytes for a full image have been received.

Some tips:

  • Use the algorithm shown in "Receiving the image efficiently" above:

    1. Create a buffer to hold 192 bytes
    2. Wait for the 0xff marker — you have then received N=0 image bytes at this stage
    3. Receive the missing 192-N bytes starting at offset N of the buffer
    4. If, looking from the end, you find a 0xff in the buffer at position K:
    • Shift the buffer right by K positions
    • Set N to K and go back to step 3 Otherwise, you have a full image, you can update the shared image and go to step 2.
  • To update the shared image from the received bytes, you can extract it from the static mutex-protected IMAGE object, then request the &mut [u8] view of the image with .as_mut(), since you have implemented AsMut<[u8; 192]> on Image. You can then use an assignment to update the image content from the buffer you have received.

❎ Start the serial_receiver task from main(). Check that you can display data received from the serial port.

Congratulations, your project rocks!