雷竞技注册
项目

与SAM4S外设DMA控制器进行数模转换

2016年5月9日经过罗伯特·凯

本系列三篇文章的第3部分将向您展示如何为离散正弦信号生成值,并连续地将该数据转换为模拟信号,而不会使CPU负担过重。

本系列三篇文章的第3部分将向您展示如何为离散正弦信号生成值,并连续地将该数据转换为模拟信号,而不会使CPU负担过重。

支持信息

所需的硬件/软件

前面的文章

不要让你的处理器压力过大

在上一篇文章中,我们完成了配置数模转换器控制器(dac)的过程,然后使用一个无限while循环连续产生一个三角波。这绝不是一项微不足道的成就,但与此同时,让CPU承担继续这个DAC活动所需的所有指令是远远不理想的。

让我们说我们使用1 MHz的DAC采样率;if the microcontroller has other important tasks to perform, it would be seriously inconvenient for the processor to be interrupted one million times per second and forced to execute the various instructions needed to access an array, increment a counter, load data into the DACC’s conversion-data register, or perform any other tasks that might be needed in a particular application.

当然,芯片设计者早就认识到这个问题——为什么要用像把数据从一个内存位置移动到另一个内存位置这样简单的事情来阻塞一个复杂的微处理器呢?为什么不合并另一个只知道如何移动数据的“处理器”,并让这个子处理器减轻CPU的负载呢?嗯,事实证明这确实是一个非常好的想法,而且这些数据移动子处理器——被称为直接内存访问(DMA)控制器——已经被整合到许多微控制器和数字信号处理器中。

在项目的这个阶段,我们将通过使用SAM4S外围DMA控制器(PDC)来为我们的数模转换添加DMA功能。在此模块的帮助下,我们可以使用最小的CPU干预将RAM从RAM移动正弦波值。下表,来自第491页SAM4S数据表(PDF)列出PDC支持的外围设备:

右边的“通道号”表示PDC在处理多个传输请求时使用的优先级,通道0是最高优先级。在这个项目中,我们不需要担心任何优先级的问题,因为我们只使用了一个PDC外围设备。还要注意,有些通道支持传输和接收,这意味着您可以使用PDC将数据移动到UART,以进行传输,并存储由UART接收的数据。

首先,一些三角学

在我们使用PDC将正弦数据移动到DACC之前,我们需要正弦数据。因此,让我们来看看如何生成离散时间的正弦波值。首先,我们需要包含一个标题文件,该文件将提供我们需要的数学功能。我们将使用“ARM_MATH.H”文件,该文件为我们提供SIN()函数,并将PI定义为3.14159265358979。现在让我们提醒自己关于正弦波的一些基本特征:

当我们使用sin()函数时,对应于一个完整周期的值需要从0到2π的参数。当参数为0、π或2π时,sin()函数返回0;当参数恰好在0和π之间(即π/2)时,它返回1(最大值),当参数恰好在π和2π之间(即3π/2)时,它返回-1(最小值)。为了创建一个最大值和最小值不同于1和-1的正弦函数,我们将sin()的返回值乘以一个称为振幅的常数。我们也可以给正弦函数一个偏移量。,将它提高到x轴以上,这样波的任何部分都不会延伸到0以下——通过在所有的数据点上加上一个常数,这些数据点是由返回值乘以振幅得到的。

因此,如果我们想生成一个正弦波循环,第一个SIN()参数应该是0,最后一个应该是2π。但是我们如何处理介于两者之间的所有论点?嗯,此时我们需要确定每个正弦波周期的数据点,aka样本。然后,我们将2π除以每个周期的样本数量,以确定参数从一个数据点增加到下一个数据点。

以下代码应该帮助您了解整个离散的正弦生成过程:

//变量的离散时间sine wave uint16_t sine wave_12bit [SAMPLES_PER_CYCLE];浮动DiscreteSineArgument DiscreteSineArgumentStep;DiscreteSineArgument = 0;DiscreteSineArgumentStep =(2 *π)/ SAMPLES_PER_CYCLE;for (n = 0;n < SAMPLES_PER_CYCLE;n++) {SineWave_12bit[n] = (uint16_t)((SINE_AMPLITUDE * sin(discretesinearargument)) + SINE_MIDRANGE);离散化参数=离散化参数+离散化参数;}

我喜欢一个平滑的正弦波,所以在这个项目中,我们将使用100个样本每个周期。SINE_AMPLITUDE被设置为2047.5,也就是DAC 12位范围的一半。这意味着最大值将高于中值2047.5计数,最小值将低于中值2047.5计数,因此正弦波将覆盖DAC的整个4095计数输出范围。

SINE_MIDRANGE也被设置为2047.5,因为我们希望最小值是0计数。(通常0计数对应0 V的模拟输出电压,但在SAM4S DAC的情况下,0计数对应1 / 6的模拟参考电压。本文的“结果与结论”部分简要讨论了这个问题前一篇文章)。

回到PDC

以下是启动PDC通道所涉及的步骤:

  1. 添加PDC ASF模块(我不知道为什么它被称为“外围DMA控制器”例子“):

  1. 定义一个pdc_packet_t类型的结构,用于PDC通道配置,以及一个PDC类型的指针变量,用于指向外设的PDC寄存器的基址。
  2. 使用所使用的外设对应的函数将相关的地址加载到base-address指针中;对于这个项目,函数是dacc_get_pdc_base()。
  3. 使用pdc_packet_t结构来存储DMA传输的起始地址和长度。如果移动的是8位数据,长度指的是字节;如果移动的是16位数据,长度指的是半字;如果移动的是32位数据,长度指的是字。
  4. 使用pdc_tx_init()或pdc_rx_init()结合pdc_packet_t结构来配置DMA通道以发送或接收。
  5. 使用pdc_enable_transfer()启动PDC传输。

PDC在适当的时间自动移动数据,因为其操作由由外围设备控制的发送和/或接收状态信号控制。例如,对于DACC,仅当DACC的发射就绪信号指示DAC硬件准备好执行新转换时,PDC才会传输新数据。

上述过程可以如下翻译成代码:

//PDC变量pdc_packet_t DACC_PDC_Config;Pdc * PDC_ptr_to_DACC;PDC_ptr_to_DACC = dacc_get_pdc_base(通道);DACC_PDC_Config。ul_addr = (uint32_t) SineWave_12bit;DACC_PDC_Config。ul_size = SAMPLES_PER_CYCLE;pdc_tx_init (PDC_ptr_to_DACC &DACC_PDC_Config &DACC_PDC_Config);pdc_enable_transfer (PDC_ptr_to_DACC PERIPH_PTCR_TXTEN);

我们还需要中央处理器

当PDC完成将所有数据从缓冲区移动到DACC时,使用中断来激发CPU干预;处理器需要知道PDC传输是完整的,以便它可以重新配置PDC信道,从而确保正弦波数据一直移动到DACC。因此,实现PDC所涉及的最后一步是启用适当的中断和设置当PDC完成传输时执行的中断服务例程(ISR)。

在ISR内部,我们简单地用相同的缓冲区和传输长度重新配置PDC通道。感谢DACC的综合FIFO(在“基础”中讨论前一篇文章),处理器在DACC退出转换数据之前,处理器应该毫无困难地完成此重新配置。

这是中断相关的代码:

int main (void){…//启用DACC的"end of transmit buffer"中断dacc_enable_interrupt(DACC, DACC_IMR_ENDTX);//在嵌套的矢量中断控制器NVIC_EnableIRQ(DACC_IRQn)中启用DACC中断。} void DACC_Handler(void){//确认"end of transmit buffer interrupt"被触发if (dacc_get_interrupt_status(dac) & DACC_ISR_ENDTX){//重新配置PDC,使正弦波生成继续pdc_tx_init(pdc_ptr_to_dac, & dacc_pdc_config, & dacc_pdc_config);}}

您可能想知道Atmel Studio是如何知道DACC_Handler()函数是DACC isr的——没有任何东西显式地标识它。实际上,它非常简单:函数名“DACC_Handler”是DACC的ISR的官方标识符,所以您所要做的就是将函数命名为DACC_Handler(),它将自动用作任何DACC中断的ISR。同样的方案也适用于其他外设——adc_handler()、UART0_Handler()等。

一个还是两个?

当您使用pdc_tx_init()或pdc_rx_init()配置PDC通道时,您必须为“当前”传输指定起始地址和传输长度。但是,您可以为“下一个”传输指定起始地址和传输长度。在上面的代码中,我为“当前”和“下一个”传输配置了通道。要将通道配置为仅一次传输,第三个参数使用NULL,如下所示:

pdc_tx_init (PDC_ptr_to_DACC &DACC_PDC_Config, NULL);

使用这两种传输的好处是减少了CPU干预;CPU移动两个缓冲区而不是一个缓冲区后,会重新配置PDC通道。我想two-transfer配置也会方便的如果你想移动数据或从一个缓冲然后或从不同的缓冲:您可以设置两个缓冲区与一个叫pdc_rx_init()或pdc_tx_init(),从而保持适当的序列不确定哪些缓冲所需的CPU被只是感动。

结果

您可以使用以下链接下载源和项目文件:

DMA_DAC_SineWave_Part3.zip

这是一个范围捕获,显示了DAC生成的正弦波:

请注意,频率为10 kHz。这就是我们预期的:样品周期为1 /(1MHz)=1μs,我们每周期有100个样本。因此,正弦波周期为100μs,1 /(100μs)= 10kHz。我们可以概括如下:

\ [f{正弦}= \压裂{样本\速度}{样品周期\ / \}\]

如果您想要更高的正弦波频率,您可以减少每个周期的采样(导致低质量的波形)或提高采样率(SAM4S DAC的最大稳定时间是0.5µs,因此您可能不应该将采样率提高到超过2 MHz)。

结论

在本项目系列中,我们详细介绍了计时器/计数器模块、数模转换器控制器、外围DMA控制器和一些用于生成离散时间正弦波的代码。这个功能对于各种应用程序都是有用的。一个这样的应用,即为软件定义的无线电产生基带波形,将成为未来一篇文章的主题。