stm32dmastm32f4discoverystm32ldiscoverylibopencm3

STM32 DMA: bytes remaining in buffer, encoded?


For quite a while now I've been struggling with DMA communication with two STM32 boards in some form or another. My current issue is as follows.

I have a host (a Raspberry Pi) running the following code, waiting for the board to initialise communication:

#include <fcntl.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    unsigned int usbdev;
    unsigned char c;
    system("stty -F /dev/ttyUSB0 921600 icanon raw");
    usbdev = open("/dev/ttyUSB0", O_RDWR);
    setbuf(stdout, NULL);
    fprintf(stderr, "Waiting for signal..\n");
    while (!read(usbdev, &c, 1));
    unsigned char buf[] = "Defend at noon\r\n";
    write(usbdev, buf, 16);
    fprintf(stderr, "Written 16 bytes\r\n");
    while (1) {
        while (!read(usbdev, &c, 1));
        printf("%c", c);
    }
    return 0;
}

Basically it waits for a single byte of data before it'll send "Defend at noon" to the board, after which it prints everything that is sent back.

The boards first send out a single byte, and then wait for all incoming data, replace a few bytes and send it back. See the code at the end of this post. The board can be either an STM32L100C or an STM32F407 (in practice, the discovery boards); I'm experiencing the same behaviour with both at this point.

The output I'm seeing (on a good day - on a bad day it hangs on Written 16 bytes) is the following:

Waiting for signal..
Written 16 bytes
^JDefend adawnon

As you can see, the data is sent and four bytes are replaced as expected, but there's an extra two characters in front (^J, or 0x5E and 0x4A). These turn out to be a direct consequence of the signal_host function. When I replace the character with something arbitrary (e.g. x), that is what's being output at that position. It is interesting to note that \n actually gets converted to its caret notation ^J somewhere along the road. It appears that this occurs in the communication to the board, because when I simply hardcode a string in the buffer and use dma_transmit to send that to an non-interactive host program, it gets printed just fine.

It looks like I've somehow miss-configured DMA in the sense that there's some buffer that is not being cleared properly. Additionally, I do not really trust the way the host-side program is using stty.. However, I've actually had communication working flawlessly in the past, using this exact code. I compared it to the code stored in my git history across several months, and I cannot find the difference/flaw.

Note that the code below uses libopencm3 and is based on examples from libopencm3-examples.

STM32L1 code:

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

void clock_setup(void)
{
    rcc_clock_setup_pll(&clock_config[CLOCK_VRANGE1_HSI_PLL_32MHZ]);
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_USART2);
    rcc_periph_clock_enable(RCC_DMA1);
}

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

void usart_setup(int baud)
{
    usart_set_baudrate(USART2, baud);
    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);
}

void dma_request_setup(void)
{
    dma_channel_reset(DMA1, DMA_CHANNEL6);

    nvic_enable_irq(NVIC_DMA1_CHANNEL6_IRQ);

    dma_set_peripheral_address(DMA1, DMA_CHANNEL6, (uint32_t) &USART2_DR);
    dma_set_read_from_peripheral(DMA1, DMA_CHANNEL6);

    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);

    dma_disable_peripheral_increment_mode(DMA1, DMA_CHANNEL6);
    dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL6);

    dma_disable_transfer_error_interrupt(DMA1, DMA_CHANNEL6);
    dma_disable_half_transfer_interrupt(DMA1, DMA_CHANNEL6);
    dma_enable_transfer_complete_interrupt(DMA1, DMA_CHANNEL6);
}

void dma_transmit_setup(void)
{
    dma_channel_reset(DMA1, DMA_CHANNEL7);

    nvic_enable_irq(NVIC_DMA1_CHANNEL7_IRQ);

    dma_set_peripheral_address(DMA1, DMA_CHANNEL7, (uint32_t) &USART2_DR);
    dma_set_read_from_memory(DMA1, DMA_CHANNEL7);

    dma_set_peripheral_size(DMA1, DMA_CHANNEL7, DMA_CCR_PSIZE_8BIT);
    dma_set_memory_size(DMA1, DMA_CHANNEL7, DMA_CCR_MSIZE_8BIT);

    dma_set_priority(DMA1, DMA_CHANNEL7, DMA_CCR_PL_VERY_HIGH);

    dma_disable_peripheral_increment_mode(DMA1, DMA_CHANNEL7);
    dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL7);

    dma_disable_transfer_error_interrupt(DMA1, DMA_CHANNEL7);
    dma_disable_half_transfer_interrupt(DMA1, DMA_CHANNEL7);
    dma_enable_transfer_complete_interrupt(DMA1, DMA_CHANNEL7);
}

void dma_request(void* buffer, const int datasize)
{
    dma_set_memory_address(DMA1, DMA_CHANNEL6, (uint32_t) buffer);
    dma_set_number_of_data(DMA1, DMA_CHANNEL6, datasize);

    dma_enable_channel(DMA1, DMA_CHANNEL6);
    signal_host();
    usart_enable_rx_dma(USART2);
}

void dma_transmit(const void* buffer, const int datasize)
{
    dma_set_memory_address(DMA1, DMA_CHANNEL7, (uint32_t) buffer);
    dma_set_number_of_data(DMA1, DMA_CHANNEL7, datasize);

    dma_enable_channel(DMA1, DMA_CHANNEL7);
    usart_enable_tx_dma(USART2);
}

int dma_done(void)
{
    return !((DMA1_CCR6 | DMA1_CCR7) & 1);
}

void dma1_channel6_isr(void) {
    usart_disable_rx_dma(USART2);
    dma_clear_interrupt_flags(DMA1, DMA_CHANNEL6, DMA_TCIF);
    dma_disable_channel(DMA1, DMA_CHANNEL6);
}

void dma1_channel7_isr(void) {
    usart_disable_tx_dma(USART2);
    dma_clear_interrupt_flags(DMA1, DMA_CHANNEL7, DMA_TCIF);
    dma_disable_channel(DMA1, DMA_CHANNEL7);
}

void signal_host(void) {
    usart_send_blocking(USART2, '\n');
}

int main(void)
{
    clock_setup();
    gpio_setup();
    usart_setup(921600);
    dma_transmit_setup();
    dma_request_setup();

    unsigned char buf[16];

    dma_request(buf, 16); while (!dma_done());

    buf[10] = 'd';
    buf[11] = 'a';
    buf[12] = 'w';
    buf[13] = 'n';

    dma_transmit(buf, 16); while (!dma_done());

    while(1);

    return 0;
}

STM32F4 code:

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

void clock_setup(void)
{
    rcc_clock_setup_hse_3v3(&hse_8mhz_3v3[CLOCK_3V3_168MHZ]);
    rcc_periph_clock_enable(RCC_GPIOA);
    rcc_periph_clock_enable(RCC_USART2);
    rcc_periph_clock_enable(RCC_DMA1);
}

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

void usart_setup(int baud)
{
    usart_set_baudrate(USART2, baud);
    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);
}

void dma_request_setup(void)
{
    dma_stream_reset(DMA1, DMA_STREAM5);

    nvic_enable_irq(NVIC_DMA1_STREAM5_IRQ);

    dma_set_peripheral_address(DMA1, DMA_STREAM5, (uint32_t) &USART2_DR);
    dma_set_transfer_mode(DMA1, DMA_STREAM5, DMA_SxCR_DIR_PERIPHERAL_TO_MEM);

    dma_set_peripheral_size(DMA1, DMA_STREAM5, DMA_SxCR_PSIZE_8BIT);
    dma_set_memory_size(DMA1, DMA_STREAM5, DMA_SxCR_MSIZE_8BIT);

    dma_set_priority(DMA1, DMA_STREAM5, DMA_SxCR_PL_VERY_HIGH);

    dma_disable_peripheral_increment_mode(DMA1, DMA_SxCR_CHSEL_4);
    dma_enable_memory_increment_mode(DMA1, DMA_STREAM5);

    dma_disable_transfer_error_interrupt(DMA1, DMA_STREAM5);
    dma_disable_half_transfer_interrupt(DMA1, DMA_STREAM5);
    dma_disable_direct_mode_error_interrupt(DMA1, DMA_STREAM5);
    dma_disable_fifo_error_interrupt(DMA1, DMA_STREAM5);
    dma_enable_transfer_complete_interrupt(DMA1, DMA_STREAM5);
}

void dma_transmit_setup(void)
{
    dma_stream_reset(DMA1, DMA_STREAM6);

    nvic_enable_irq(NVIC_DMA1_STREAM6_IRQ);

    dma_set_peripheral_address(DMA1, DMA_STREAM6, (uint32_t) &USART2_DR);
    dma_set_transfer_mode(DMA1, DMA_STREAM6, DMA_SxCR_DIR_MEM_TO_PERIPHERAL);

    dma_set_peripheral_size(DMA1, DMA_STREAM6, DMA_SxCR_PSIZE_8BIT);
    dma_set_memory_size(DMA1, DMA_STREAM6, DMA_SxCR_MSIZE_8BIT);

    dma_set_priority(DMA1, DMA_STREAM6, DMA_SxCR_PL_VERY_HIGH);

    dma_disable_peripheral_increment_mode(DMA1, DMA_SxCR_CHSEL_4);
    dma_enable_memory_increment_mode(DMA1, DMA_STREAM6);

    dma_disable_transfer_error_interrupt(DMA1, DMA_STREAM6);
    dma_disable_half_transfer_interrupt(DMA1, DMA_STREAM6);
    dma_disable_direct_mode_error_interrupt(DMA1, DMA_STREAM6);
    dma_disable_fifo_error_interrupt(DMA1, DMA_STREAM6);
    dma_enable_transfer_complete_interrupt(DMA1, DMA_STREAM6);
}

void dma_request(void* buffer, const int datasize)
{
    dma_set_memory_address(DMA1, DMA_STREAM5, (uint32_t) buffer);
    dma_set_number_of_data(DMA1, DMA_STREAM5, datasize);

    dma_channel_select(DMA1, DMA_STREAM5, DMA_SxCR_CHSEL_4);
    dma_enable_stream(DMA1, DMA_STREAM5);
    signal_host();
    usart_enable_rx_dma(USART2);
}

void dma_transmit(const void* buffer, const int datasize)
{
    dma_set_memory_address(DMA1, DMA_STREAM6, (uint32_t) buffer);
    dma_set_number_of_data(DMA1, DMA_STREAM6, datasize);

    dma_channel_select(DMA1, DMA_STREAM6, DMA_SxCR_CHSEL_4);
    dma_enable_stream(DMA1, DMA_STREAM6);
    usart_enable_tx_dma(USART2);
}

int dma_done(void)
{
    return !((DMA1_S5CR | DMA1_S6CR) & 1);
}

void dma1_stream5_isr(void) {
    usart_disable_rx_dma(USART2);
    dma_clear_interrupt_flags(DMA1, DMA_STREAM5, DMA_TCIF);
    dma_disable_stream(DMA1, DMA_STREAM5);
}

void dma1_stream6_isr(void) {
    usart_disable_tx_dma(USART2);
    dma_clear_interrupt_flags(DMA1, DMA_STREAM6, DMA_TCIF);
    dma_disable_stream(DMA1, DMA_STREAM6);
}

void signal_host(void) {
    usart_send_blocking(USART2, '\n');
}

int main(void)
{
    clock_setup();
    gpio_setup();
    usart_setup(921600);
    dma_transmit_setup();
    dma_request_setup();

    unsigned char buf[16];

    dma_request(buf, 16); while (!dma_done());

    buf[10] = 'd';
    buf[11] = 'a';
    buf[12] = 'w';
    buf[13] = 'n';

    dma_transmit(buf, 16); while (!dma_done());

    while(1);

    return 0;
}

Solution

  • Well, I can be brief about this one.

    I recommend against using stty for this sort of thing. I realise I have probably not configured stty properly, and with some option-tweaking it is probably possible to get it right, but it's completely unclear. I ended up throwing it out the window and using pyserial instead. I should've done that weeks ago. The above STM32 code works fine and the required Python code is completely trivial.

    #!/usr/bin/env python3
    import serial
    
    dev = serial.Serial("/dev/ttyUSB0", 921600)
    
    dev.read(1)  # wait for the signal
    dev.write("Defend at noon\r\n".encode('utf-8'))
    
    while True:
        x = dev.read()
        print(x.decode('utf-8'), end='', flush=True)