microcontrollerstm32dmausartlibopencm3

Direct memory access RX for the STM32L1


I've been trying for a while now to transmit a block of data from my computer to an STM32L100C-DISCO over USART. For performance reasons, this is to be done using DMA. So far, however, I have not been able to get it to work. As I cannot seem to figure out what I might be doing wrong, I figured I'd ask here.

I'm using libopencm3, but unfortunately, their otherwise excellent repository of examples does not appear to contain one for DMA on the STM32L1xxx. I checked that I covered all the bases when it comes to the configuration options available in the common DMA header file, though.

Naturally, I've referred to the reference manual for the STM32L1xxx, which mentions the following requests table for DMA1, leading me to believe channel 6 is what I need to be using..

DMA requests table

As I was unsure about the size of the memory and peripheral (i.e. USART2), I varied across all combinations of 8, 16 and 32 bit for both, but to no avail.

Without further ado; this is a minimal working (well, not working..) excerpt of what I'm trying to do. I feel like I'm overlooking something in the DMA configuration, as USART by itself works fine.

At this point, anything is appreciated.

The idea behind this code is basically to loop forever until the data in the buffer is replaced entirely, and then when it is, output it. From the host, I'm sending a kilobyte of highly recognisable data, but all I'm getting back is malformed garbage. It is writing something, but not what I intend for it to write.

EDIT: Here's a picture of the memory map. USART2_BASE evaluates to 0x4000 4400, so that seems to be all right as well.

memory map

#include <libopencm3/stm32/rcc.h>
#include <libopencm3/stm32/gpio.h>
#include "libopencm3/stm32/usart.h"
#include <libopencm3/stm32/dma.h>

const int buflength = 1024;

uint8_t buffer[1024];

static void clock_setup(void)
{
    rcc_clock_setup_pll(&clock_config[CLOCK_VRANGE1_HSI_PLL_32MHZ]);
    rcc_peripheral_enable_clock(&RCC_AHBENR, RCC_AHBENR_GPIOAEN);
    rcc_peripheral_enable_clock(&RCC_APB1ENR, RCC_APB1ENR_USART2EN);
    rcc_periph_clock_enable(RCC_DMA1);

}

static void gpio_setup(void)
{
    gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO3);
    gpio_mode_setup(GPIOA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO2);
    gpio_set_af(GPIOA, GPIO_AF7, GPIO3);
    gpio_set_af(GPIOA, GPIO_AF7, GPIO2);
}

static void usart_setup(void)
{
    usart_set_baudrate(USART2, 115200);
    usart_set_databits(USART2, 8);
    usart_set_stopbits(USART2, USART_STOPBITS_1);
    usart_set_mode(USART2, USART_MODE_TX_RX);
    usart_set_parity(USART2, USART_PARITY_NONE);
    usart_set_flow_control(USART2, USART_FLOWCONTROL_NONE);

    usart_enable(USART2);
}

static void dma_setup(void)
{
    dma_channel_reset(DMA1, DMA_CHANNEL6);
    dma_set_priority(DMA1, DMA_CHANNEL6, DMA_CCR_PL_VERY_HIGH);
    dma_set_memory_size(DMA1, DMA_CHANNEL6, DMA_CCR_MSIZE_8BIT);
    dma_set_peripheral_size(DMA1, DMA_CHANNEL6, DMA_CCR_PSIZE_8BIT);
    dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL6);
    dma_disable_peripheral_increment_mode(DMA1, DMA_CHANNEL6);
    dma_enable_circular_mode(DMA1, DMA_CHANNEL6);
    dma_set_read_from_peripheral(DMA1, DMA_CHANNEL6);

    dma_disable_transfer_error_interrupt(DMA1, DMA_CHANNEL6);
    dma_disable_half_transfer_interrupt(DMA1, DMA_CHANNEL6);
    dma_disable_transfer_complete_interrupt(DMA1, DMA_CHANNEL6);

    dma_set_peripheral_address(DMA1, DMA_CHANNEL6, (uint32_t) USART2_BASE);
    dma_set_memory_address(DMA1, DMA_CHANNEL6, (uint32_t) buffer);
    dma_set_number_of_data(DMA1, DMA_CHANNEL6, buflength);

    dma_enable_channel(DMA1, DMA_CHANNEL6);
}

int main(void)
{
    int i;
    for (i = 0; i < buflength; i++) {
        buffer[i] = 65;
    }
    clock_setup();
    gpio_setup();
    usart_setup();
    dma_setup();

    usart_enable_rx_dma(USART2);
    char flag = 1;
    while (flag) {
        flag = 0;
        for (i = 0; i < buflength; i++) {
            if (buffer[i] == 65) {
                flag = 1;
            }
        }
    }
    usart_disable_rx_dma(USART2);

    for (i = 0; i < buflength; i++) {
        usart_send_blocking(USART2, buffer[i]);
    }
    usart_send_blocking(USART2, '\n');

    return 0;
}

Solution

  • In the end, this is the configuration that I used to get it working.

    const int datasize = 32;
    
    char buffer[32];
    
    static void dma_setup(void)
    {
        dma_channel_reset(DMA1, DMA_CHANNEL6);
    
        nvic_enable_irq(NVIC_DMA1_CHANNEL6_IRQ);
    
        // USART2_DR (not USART2_BASE) is where the data will be received
        dma_set_peripheral_address(DMA1, DMA_CHANNEL6, (uint32_t) &USART2_DR);
        dma_set_read_from_peripheral(DMA1, DMA_CHANNEL6);
    
        // should be 8 bit for USART2 as well as for the STM32L1
        dma_set_peripheral_size(DMA1, DMA_CHANNEL6, DMA_CCR_PSIZE_8BIT);
        dma_set_memory_size(DMA1, DMA_CHANNEL6, DMA_CCR_MSIZE_8BIT);
    
        dma_set_priority(DMA1, DMA_CHANNEL6, DMA_CCR_PL_VERY_HIGH);
    
        // should be disabled for USART2, but varies for other peripherals
        dma_disable_peripheral_increment_mode(DMA1, DMA_CHANNEL6);
        // should be enabled, otherwise buffer[0] is overwritten
        dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL6);
    
        dma_set_memory_address(DMA1, DMA_CHANNEL6, (uint32_t) &buffer);
        dma_set_number_of_data(DMA1, DMA_CHANNEL6, datasize);
    
        dma_disable_transfer_error_interrupt(DMA1, DMA_CHANNEL6);
        dma_disable_half_transfer_interrupt(DMA1, DMA_CHANNEL6);
        dma_enable_transfer_complete_interrupt(DMA1, DMA_CHANNEL6);
    
        usart_enable_rx_dma(USART2);
        dma_enable_channel(DMA1, DMA_CHANNEL6);
    }
    

    Then, when the transfer is complete, the override of the dma1_channel6_isr function gets called and all the data is in buffer.

    I have submitted the full working code as a pull request to the libopencm3-example repository. You can find it here. I will make sure to update the link when the code gets merged.