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_blocking
和usart_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的主要内容,如果未能解决你的问题,请参考以下文章
Stm32L151RCxxx USART 挂起问题,同时基于中断的 TX/RX