jiongsheng

jiongsheng

MSPM0学习,ADC定时器触发,DMA搬运

前言#

如果您未看过笔者之前写的 "第一步配置" 和 "MSPM0 教程之调用 SYSCONFIG 以及配置 GPIO 输出和输入",强烈建议您将这两篇先看了,以免出现配置错误,如果您以及完成了 SYSCONFIG 的配置,请继续往下看

创易栈 MSPM0 开发板参考电压选择#

image-20230606194556451

根据创易栈的用户手册,板载 VREF2.5V,我们把板子上的拨码开关拨到 ON 即可

image-20230606195417792

SYSCONFIG 配置#

在 keil 内打开 empty.syscfg, 然后调用 SYSCONFIG 工具

image-20230606184519254

进入 SYSCONFIG,我们再添加 ADC,先看看他的介绍

image-20230607184911524

在基本配置中,用户可以:

  • 配置采样时钟和采样模式:这些参数影响了 ADC 的采样率,即每秒钟对输入信号进行采样的次数。
  • 配置 ADC 转换内存:存储从模拟信号转换得到的数字数据。
  • 启用 / 禁用以下功能:
    • 硬件平均模式:通过对多次采样结果进行平均,减少随机噪声的影响。
    • 使用烧耗电流源:可能用于在 ADC 转换过程中提供稳定的参考电压。
    • 窗口比较器模式:只有当输入信号在一个指定的范围内时,才进行 ADC 转换。
    • 触发模式:可以设置 ADC 转换只在接收到一个特定的触发信号时开始。

在高级配置中,用户可以:

  • 配置转换频率 / 分辨率:影响了 ADC 转换的精度和速度。
  • 启用 FIFO 模式:在这种模式下,ADC 可以连续进行多次转换,并将结果存入一个先进先出(FIFO)的队列中,方便后续处理。
  • 配置电源下降模式:在不需要进行 ADC 转换时,可以降低 ADC 模块的电源,以节省能源。
  • 指定采样时间:对于快速变化的模拟信号,可能需要更短的采样时间。

ADC 参数#

image-20230607185752720

如参考手册所示,该 ADC 的在不同分辨率下的转换周期

image-20230607190449924

如果我们使用 ULPCLK,可以绕过这个采样延迟

勾选重复采样模式

image-20230608203635086

在 SYSCONFIG 中配置触发源,我设置为事件触发,这个事件由定时器发布

image-20230607191152249

Conversion Data Format 这个选项中选择无符号右对齐

在高级设置这里,我们可以通过设置采集的时间来确定采样率 (12bit 上限为 1.45M),采样率等于 32 (时钟频率)/(1 / 采样时间 + 转换时间 (12bit 分辨率 为 14 个时钟周期)),通过计算可以得到,将采样时间设置为 250ns(即八个 (32M) 时钟周期), 此时采样率就为 32/(8+14)=1.45M

image-20230610201941717

然后我们先去配置定时器,定时器配置如图,确保设置好对应的触发事件

image-20230610195248506

接着再回到 ADC 配置页,设置好订阅事件

image-20230610195517210

然后打开 DMA 传输,设置好 DMA 的参数,包括触发源,数据长度和传输地址模式设置以及传输模式

image-20230610195613677

传输模式定义了数据如何从源地址传输到目标地址,有四种模式:

  1. Single Transfer:这种模式下,每次传输都需要一个触发信号。当按照 "Transfer Size" 设定的次数完成传输后,DMA 将被禁用。

  2. Block Transfer:这种模式下,一个触发信号就能传输完整的数据块,数据块的大小由 "Transfer Size" 决定。每次传输结束后,DMA 将被禁用。

  3. Repeated Single Transfer:这种模式下,每次传输都需要一个触发信号,但在所有传输完成后,DMA 仍然保持启用状态。

  4. Repeated Block Transfer:这种模式下,一个触发信号就能传输完整的数据块,数据块的大小由 "Transfer Size" 决定。和 "Repeated Single Transfer" 一样,所有传输完成后,DMA 仍然保持启用状态。

选择哪种传输模式取决于你的具体需求。例如,如果你需要连续传输大量数据,可以选择 "Block Transfer" 或 "Repeated Block Transfer" 模式;如果你需要精细地控制每次的数据传输,可以选择 "Single Transfer" 或 "Repeated Single Transfer" 模式。

image-20230608204512192

点击 FILE-save, 然后到 keil 中编译,导入配置

main 相关代码#

#define ADCSize 128

typedef enum ADCFlag

{

  ADCFlagDmaStart = 0,

  ADCFlagDmaDone,

  ADCFlagConversionDone,

  ADCFlagConversionStart

}ADCFlag;

unsigned short ADCResult[ADCSize];//定义ADC接收缓冲区
ADCFlag adcflag;//定义枚举变量adcflag

void UART1SendString(char *str)
{
    while(*str != '\0')
    {
        DL_UART_transmitDataBlocking(UART1_INST, *str);
        str++;
    }
}
void UART1SendChar(char c)
{
    DL_UART_transmitDataBlocking(UART1_INST, c);
}

void strreverse(char* begin, char* end) {
   char aux;
   while(end>begin)
       aux=*end, *end--=*begin, *begin++=aux;
}
/**

 * @brief  将整数值转换为指定基数的空终止字符串,并将结果存储在由str参数给出的数组中。
   *

 * @param  value: 要转换为字符串的值。

 * @param  str: 用于存储结果的空终止字符串的数组。该数组应足够长,以容纳结果字符串(包括空字符)。

 * @param  base: 要转换的数字的基数。它必须在[2, 36]范围内。对于大于10的基数,此函数使用小写字母。
   *

 * @note   如果基数为10且value为负,则结果字符串前会带有负号(-)。对于任何其他基数,value始终被视为无符号。
   *

 * @note   如果基数不在[2, 36]范围内,函数将不执行任何操作,str将是一个空字符串。
   *

 * @note   该函数不处理"unsigned long"数字 - 它将把它们视为"long"。因此,对于大于2147483647的值,该函数不适用于大于10的基数。
   *

 * @return 无。
   */
   void itoa(int value, char* str, int base) {
   static char num[] = "0123456789abcdefghijklmnopqrstuvwxyz";
   char* wstr = str;
   int sign;

   // Validate base
   if (base < 2 || base > 35) { *wstr = '\0'; return; }

   // Take care of sign
   if ((sign=value) < 0) value = -value;

   // Conversion. Number is reversed.
   do *wstr++ = num[value%base]; while(value/=base);

   if(sign<0) *wstr++='-';
   *wstr='\0';

   // Reverse string
   strreverse(str,wstr-1);
   }


/**

 * @brief  处理ADC结果的函数
   *
 * @param  adcflag: 指向ADC标志的指针,当ADC完成数据转换后,该标志会被设置为ADCFlagDmaDone
 * @param  ADCResult: 指向存储ADC结果的数组的指针
 * @param  resultSize: ADC结果数组的大小
   *
 * @note   当adcflag指向的值为ADCFlagDmaDone时,函数会停止定时器和ADC的数据转换,然后遍历ADCResult数组,
 * 并使用itoa函数将每个结果转换为字符串,然后通过UART发送出去。在发送完所有的结果后,函数会将adcflag指向的值设置为ADCFlagConversionStart,
 * 表示ADC可以开始新一轮的数据转换。注意,在发送字符串时,每个结果后面都会发送一个换行符,以便接收端能够正确地识别每个结果的边界。
    *
 * @return 无。
   */
   void processADCResults(ADCFlag* adcflag, unsigned short* ADCResult, int resultSize) 
   {
   if (*adcflag == ADCFlagDmaDone) 
   {
       DL_Timer_stopCounter(TIMER_0_INST);
       DL_ADC12_stopConversion(ADC12_0_INST);
       volatile int i;
       for ( i = 0; i < resultSize; i++) 
       {
          char buffer[12]; // Buffer for integer to string conversion, max size for int32 is 11 characters + null terminator
            itoa(ADCResult[i], buffer, 10);
            UART1SendString(buffer);
            UART1SendString("\r\n");
       }
       *adcflag = ADCFlagConversionStart;
       i = 0;
   }
   }
   void setupDMA(DMA_Regs *dma, uint8_t channelNum, unsigned int srcAddr, unsigned int destAddr, unsigned int transferSize)
   {
    DL_DMA_setSrcAddr(dma, channelNum, (unsigned int) srcAddr);
    DL_DMA_setDestAddr(dma, channelNum, (unsigned int) destAddr);
    DL_DMA_setTransferSize(dma, channelNum, transferSize);
    DL_DMA_enableChannel(dma, channelNum);
   }

int main(void)
{
    SYSCFG_DL_init();
    NVIC_EnableIRQ(UART1_INT_IRQn);
    NVIC_EnableIRQ(ADC0_INT_IRQn);
    adcflag = ADCFlagConversionStart;
    setupDMA(DMA,DMA_CH0_CHAN_ID,(unsigned int) & (ADC0->ULLMEM.MEMRES[0]),(unsigned int) &ADCResult,ADCSize);

    if(adcflag == ADCFlagConversionStart)
   {
       DL_Timer_startCounter(TIMER_0_INST);
       DL_ADC12_startConversion(ADC12_0_INST);
   }
   while (1) 
    {


   processADCResults(&adcflag, ADCResult, ADCSize);
    }
}


实际演示#

因为我每次只采 128 个点,并且不是连续采样,所以每次重新 RST,采样的信号肯定不和上次连续,就会出现下面这种尖点,从一次连续信号的角度来看,效果还不错

image-20230610192450348

转载请标明出处

by <a href=https://www.cnblogs.com/jiongsheng > QDU_jiongsheng

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。