Bootstrap

MSPM0L-配置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中配置ADC的触发源,我设置为事件触发,这个事件由定时器发布

image-20230607191152249

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

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

上述计算过程如有问题敬请指正

image-20240715175828606

注意

如果使用MSPM0G的话,这里的转换时间并不相同,应按照数据手册的时钟频率与转换时间关系表确定

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

image-20230610195248506

接着再回到ADC配置页,设置好订阅事件和触发模式

image-20230610195517210

image-20240711104737286

然后打开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);
       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);
   }
   while (1) 
    {


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

void ADC0_IRQHandler (void)
{


        switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST))
        {

        case DL_ADC12_IIDX_DMA_DONE:
            /* code */
            adcflag = ADCFlagDmaDone;
            DL_ADC12_clearInterruptStatus(ADC12_0_INST,DL_ADC12_IIDX_DMA_DONE);//Clear Interrupt Status
            break;
        default:
            break;
        }

}

实际演示

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

image-20230610192450348

转载请标明出处

by QDU_jiongsheng

;