STM32L1 的直接存储器访问 RX

Posted

技术标签:

【中文标题】STM32L1 的直接存储器访问 RX【英文标题】:Direct memory access RX for the STM32L1 【发布时间】:2015-09-16 04:35:42 【问题描述】:

一段时间以来,我一直在尝试通过 USART 将数据块从我的计算机传输到 STM32L100C-DISCO。出于性能原因,这将使用 DMA 来完成。然而,到目前为止,我还没有让它工作。由于我似乎无法弄清楚我可能做错了什么,所以我想我会在这里问。

我正在使用libopencm3,但不幸的是,他们在其他方面非常出色的repository of examples 似乎在STM32L1xxx 上不包含用于DMA 的。不过,当涉及到common DMA header file 中可用的配置选项时,我检查了我是否涵盖了所有基础。

当然,我参考了 STM32L1xxx 的参考手册,其中提到了 DMA1 的以下请求表,这让我相信通道 6 是我需要使用的......

由于我不确定内存和外围设备(即 USART2)的大小,我在 8 位、16 位和 32 位的所有组合中进行了更改,但无济于事。

事不宜迟;这是我正在尝试做的最小工作(嗯,不工作..)的摘录。我觉得我忽略了 DMA 配置中的某些内容,因为 USART 本身可以正常工作。

在这一点上,任何事情都是值得赞赏的。

这段代码背后的想法基本上是永远循环,直到缓冲区中的数据被完全替换,然后当它被替换时,输出它。从主机,我正在发送一千字节的高度可识别的数据,但我得到的只是格式错误的垃圾。它正在写一些东西,但不是我打算写的东西。

编辑:这是内存映射的图片。 USART2_BASE 计算结果为 0x4000 4400,所以这似乎也没问题。

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

【问题讨论】:

只是一个想法:不是一个解决方案,但如果你能计时 RX 需要多长时间,看看这是否与波特率(最小 0.08 秒)一致,这可能会显示错误事件是否正在触发DMA(假设波特率是正确的,因为您有非 DMA 工作)。 我不确定我是否完全理解您的意思,但我尝试将两边的波特率降低到 9600,但这并没有解决问题(或为我提供有意义的输出)。调用usart_send_blockingusart_recv_blocking 确实可以正常工作。 添加;选择 9600 是为了谨慎起见——我认为这将是一个安全的下限。 我正在查看第 726 页的数据表说。 “在DMA控制寄存器中写入USART_DR寄存器地址,将其配置为传输源。”你已经使用了USART_BASE。 st.com/web/en/resource/technical/document/reference_manual/… USART2 映射到 0x40004400,USART_DR 的偏移量为 4。 【参考方案1】:

最后,这是我用来让它工作的配置。

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

然后,当传输完成时,dma1_channel6_isr 函数的覆盖被调用,所有数据都在buffer 中。

我已将完整的工作代码作为拉取请求提交到 libopencm3-example 存储库。你可以找到它here。我会确保在代码合并后更新链接。

【讨论】:

作为增强示例的建议,以及异步 DMA 经常缺少的东西,将支持接收超时。一些 USART 外设具有可以在内部生成的超时中断,并且在某些部件上您需要使用定时器中断。使用上面的配置,我知道这就是你想要的,如果接收到 31 个字符,则不会有任何回显。使用接收超时,如果没有接收到更多字符,将回显 31 个字符,然后它可以重新配置并接收最多接下来的 32 个字符。 这不适用于我目前的情况(因为缺少单个字节是终止的原因),但我可以将其包含在示例中。感谢您的建议。【参考方案2】:

我不熟悉libopencm3或STM32L系列,但熟悉STM32F系列。我知道外围设备存在差异,但我相信您的错误在于以下行:

dma_set_peripheral_address(DMA1, DMA_CHANNEL6, (uint32_t) USART2_BASE);

在这里,您将 DMA 外设地址设置为 USART2_BASE 地址,但通常,您希望将其设置为 USART2 数据寄存器,它可能不正确位于 USART2_BASE

我现在看到您的问题中的一些 cmets 已经指出了这一点,但是仍然存在如何指示数据寄存器的问题。使用 ST 外设库,有外设的内存映射结构。似乎 libopencm3 有一个已定义的宏,您可以将其用于数据寄存器地址:USART2_DR。 Here is the definition in the documentation

所以,我相信如果您将上面的行更改为以下内容,它可能会解决您的问题:

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

【讨论】:

嗯,我将USART2_BASE 解释为唯一可用的似乎是错误的。在正确安装 libopencm3 而不是使用(现在结果是)可用文件的一小部分后,USART2_DR 确实解决了。我现在得到的不是垃圾,而是通过usart_recv_blocking 收到的最后一个字符,重复。这不是我想要的位置(而且我可能仍然错过配置一些小问题),但它正在接近。 我已经有一段时间没有使用 STM32 DMA 控制器了,但听起来,出于某种原因,USART 没有正确控制 DMA 通道上的流量,而 DMA 控制器是只是按照自己的节奏从USART2_DR 中读出datasize 字节,而不是USART 的。再次,回到 libopencm3 文档,看起来 dma_set_peripheral_flow_control 是设置 DMA 控制器以由 USART 控制的调用。 libopencm3.github.io/docs/latest/stm32l1/html/… 不幸的是,流控制需要额外的硬件,这在我目前的情况下不起作用(即我的控制台没有 RTS/CTS 引脚) 这不是 UART 的流控制,而是 DMA 传输的流控制。现在您的传输设置为允许 DMA 控制器控制从 UART 到内存的流量,但是 DMA 控制器不知道什么时候有新字符,所以它只是一遍又一遍地复制 DR(这就是为什么你获取重复接收的最后一个字符)。如果添加我提到的调用,它允许 UART 控制 DMA 的流量。当接收到一个新字符时,它会向 DMA 控制器发送一个信号,告诉它从源读取并复制到目标。

以上是关于STM32L1 的直接存储器访问 RX的主要内容,如果未能解决你的问题,请参考以下文章

STM32L1xx——ADC(中断/DMA)样例代码

Stm32L151RCxxx USART 挂起问题,同时基于中断的 TX/RX

STM32L1X系列GPIO运用

STM32上能跑Android吗?对存储器有啥要求呢?

译STM32L4x6系列用户手册第四章 - 防火墙(FireWall)

STM32L1xx——sx1278开发之扩频技术基础知识