DMA for embedded systems

One of the key features of LwRB library is that it can be seamlessly integrated with DMA controllers on embedded systems.

Note

DMA stands for Direct Memory Access controller and is usually used to off-load CPU. More about DMA is available on Wikipedia.

DMA controllers normally use source and destination memory addresses to transfer data in-between. This features, together with LwRB, allows seamless integration and zero-copy of application data at interrupts after DMA transfer has been completed. Some manual work is necessary to be handled, but this is very minor in comparison of writing byte-by-byte to buffer at (for example) each received character.

Below are 2 common use cases:

  • DMA transfers data from LwRB memory to (usually) some hardware IP

  • DMA transfers data from hardware IP to memory

Zero-copy data from LwRB memory

This describes how to pass LwRB output memory address as pointer to DMA (or any other processing function). After data is successfully processed, application can skip processed data and mark buffer as free for new data being written to it.

Data transfer from memory to hardware IP

Data transfer from memory to hardware IP

  • Case A: Initial state, buffer is full and holds 7 bytes

  • Case B: State after skipping R pointer for 3 bytes. Buffer now holds 4 remaining bytes

  • Case C: Buffer is empty, no more memory available for read operation

Code example:

Skip buffer data after usage
 1#include "lwrb/lwrb.h"
 2
 3/* Declare rb instance & raw data */
 4lwrb_t buff;
 5uint8_t buff_data[8];
 6
 7size_t len;
 8uint8_t* data;
 9
10/* Initialize buffer, use buff_data as data array */
11lwrb_init(&buff, buff_data, sizeof(buff_data));
12
13/* Use write, read operations, process data */
14/* ... */
15
16/* IMAGE PART A */
17
18/* At this stage, we have buffer as on image above */
19/* R = 5, W = 4, buffer is considered full */
20
21/* Get length of linear memory at read pointer */
22/* Function returns 3 as we can read 3 bytes from buffer in sequence */
23/* When function returns 0, there is no memory available in the buffer for read anymore */
24if ((len = lwrb_get_linear_block_read_length(&buff)) > 0) {
25    /* Get pointer to first element in linear block at read address */
26    /* Function returns &buff_data[5] */
27    data = lwrb_get_linear_block_read_address(&buff);
28
29    /* Send data via DMA and wait to finish (for sake of example) */
30    send_data(data, len);
31
32    /* Now skip sent bytes from buffer = move read pointer */
33    lwrb_skip(&buff, len);
34
35    /* Now R points to top of buffer, R = 0 */
36    /* At this point, we are at image part B */
37}
38
39/* IMAGE PART B */
40
41/* Get length of linear memory at read pointer */
42/* Function returns 4 as we can read 4 bytes from buffer in sequence */
43/* When function returns 0, there is no memory available in the buffer for read anymore */
44if ((len = lwrb_get_linear_block_read_length(&buff)) > 0) {
45    /* Get pointer to first element in linear block at read address */
46    /* Function returns &buff_data[0] */
47    data = lwrb_get_linear_block_read_address(&buff);
48
49    /* Send data via DMA and wait to finish (for sake of example) */
50    send_data(data, len);
51
52    /* Now skip sent bytes from buffer = move read pointer */
53    /* Read pointer is moved for len bytes */
54    lwrb_skip(&buff, len);
55
56    /* Now R points to 4, that is R == W and buffer is now empty */
57    /* At this point, we are at image part C */
58}
59
60/* IMAGE PART C */
61
62/* Buffer is considered empty as R == W */

Part A on image clearly shows that not all data bytes are linked in single contiguous block of memory. To send all bytes from lwrb, it might be necessary to repeat procedure multiple times

Skip buffer data for non-contiguous block
 1/* Initialization part skipped */
 2
 3/* Get length of linear memory at read pointer */
 4/* When function returns 0, there is no memory
 5   available in the buffer for read anymore */
 6while ((len = lwrb_get_linear_block_read_length(&buff)) > 0) {
 7    /* Get pointer to first element in linear block at read address */
 8    data = lwrb_get_linear_block_read_address(&buff);
 9
10    /* If max length needs to be considered */
11    /* simply decrease it and use smaller len on skip function */
12    if (len > max_len) {
13        len = max_len;
14    }
15
16    /* Send data via DMA and wait to finish (for sake of example) */
17    send_data(data, len);
18
19    /* Now skip sent bytes from buffer = move read pointer */
20    lwrb_skip(&buff, len);
21}

Zero-copy data to LwRB memory

Similar to reading data from buffer with zero-copy overhead, it is possible to write to lwrb with zero-copy overhead too. Only difference is that application now needs pointer to write memory address and length of maximal number of bytes to directly copy into buffer. After successful processing, buffer advance operation is necessary to manually increase write pointer and to increase number of bytes in buffer.

Data transfer from memory to hardware IP
  • Case A: Initial state, buffer is empty as R == W

    • Based on W pointer position, application could write 4 bytes to contiguous block of memory

  • Case B: State after advancing W pointer for 4 bytes. Buffer now holds 4 bytes and has 3 remaining available

  • Case C: Buffer is full, no more free memory available for write operation

Code example:

Advance buffer pointer for manually written bytes
 1/* Declare rb instance & raw data */
 2lwrb_t buff;
 3uint8_t buff_data[8];
 4
 5size_t len;
 6uint8_t* data;
 7
 8/* Initialize buffer, use buff_data as data array */
 9lwrb_init(&buff, buff_data, sizeof(buff_data));
10
11/* Use write, read operations, process data */
12/* ... */
13
14/* IMAGE PART A */
15
16/* At this stage, we have buffer as on image above */
17/* R = 4, W = 4, buffer is considered empty */
18
19/* Get length of linear memory at write pointer */
20/* Function returns 4 as we can write 4 bytes to buffer in sequence */
21/* When function returns 0, there is no memory available in the buffer for write anymore */
22if ((len = lwrb_get_linear_block_write_length(&buff)) > 0) {
23    /* Get pointer to first element in linear block at write address */
24    /* Function returns &buff_data[4] */
25    data = lwrb_get_linear_block_write_address(&buff);
26
27    /* Receive data via DMA and wait to finish (for sake of example) */
28    /* Any other hardware may directly write to data array */
29    /* Data array has len bytes length */
30    /* Or use memcpy(data, my_array, len); */
31    receive_data(data, len);
32
33    /* Now advance buffer for written bytes to buffer = move write pointer */
34    /* Write pointer is moved for len bytes */
35    lwrb_advance(&buff, len);
36
37    /* Now W points to top of buffer, W = 0 */
38    /* At this point, we are at image part B */
39}
40
41/* IMAGE PART B */
42
43/* Get length of linear memory at write pointer */
44/* Function returns 3 as we can write 3 bytes to buffer in sequence */
45/* When function returns 0, there is no memory available in the buffer for write anymore */
46if ((len = lwrb_get_linear_block_write_length(&buff)) > 0) {
47    /* Get pointer to first element in linear block at write address */
48    /* Function returns &buff_data[0] */
49    data = lwrb_get_linear_block_write_address(&buff);
50
51    /* Receive data via DMA and wait to finish (for sake of example) */
52    /* Any other hardware may directly write to data array */
53    /* Data array has len bytes length */
54    /* Or use memcpy(data, my_array, len); */
55    receive_data(data, len);
56
57    /* Now advance buffer for written bytes to buffer = move write pointer */
58    /* Write pointer is moved for len bytes */
59    lwrb_advance(&buff, len);
60
61    /* Now W points to 3, R points to 4, that is R == W + 1 and buffer is now full */
62    /* At this point, we are at image part C */
63}
64
65/* IMAGE PART C */
66
67/* Buffer is considered full as R == W + 1 */

Example for DMA transfer from memory

This is an example showing pseudo code for implementing data transfer using DMA with zero-copy overhead. For read operation purposes, application gets direct access to LwRB read pointer and length of contiguous memory.

It is assumed that after DMA transfer completes, interrupt is generated (embedded system) and buffer is skipped in the interrupt.

Note

Buffer skip operation is used to mark sent data as processed and to free memory for new writes to buffer

DMA usage with buffer
 1/* Declare rb instance & raw data */
 2lwrb_t buff;
 3uint8_t buff_data[8];
 4
 5/* Working data length */
 6volatile size_t len;
 7
 8/* Send data function */
 9void send_data(void);
10
11int
12main(void) {
13    /* Initialize buffer */
14    lwrb_init(&buff, buff_data, sizeof(buff_data));
15
16    /* Write 4 bytes of data */
17    lwrb_write(&buff, "0123", 4);
18
19    /* Send data over DMA */
20    send_data();
21
22    while (1);
23}
24
25/* Send data over DMA */
26void
27send_data(void) {
28    /* If len > 0, DMA transfer is on-going */
29    if (len > 0) {
30        return;
31    }
32
33    /* Get maximal length of buffer to read data as linear memory */
34    len = lwrb_get_linear_block_read_length(&buff);
35    if (len > 0) {
36        /* Get pointer to read memory */
37        uint8_t* data = lwrb_get_linear_block_read_address(&buff);
38
39        /* Start DMA transfer */
40        start_dma_transfer(data, len);
41    }
42
43    /* Function does not wait for transfer to finish */
44}
45
46/* Interrupt handler */
47/* Called on DMA transfer finish */
48void
49DMA_Interrupt_handler(void) {
50    /* Transfer finished */
51    if (len > 0) {
52        /* Now skip the data (move read pointer) as they were successfully transferred over DMA */
53        lwrb_skip(&buff, len);
54
55        /* Reset length = DMA is not active */
56        len = 0;
57
58        /* Try to send more */
59        send_data();
60    }
61}

Tip

Check STM32 UART DMA TX RX Github repository for use cases.