【电赛】MSP432P401R——ADC+DMA+串口打印

一、简介

MSP432P401R LaunchPad

        最近在准备2023年的电赛,因为怕题目要求限板卡,于是我们临时学习了一下TI的评估板,但是在网上发现更多的是给小车题开发的,给信号组的例程有点少,特别是使用了DMA的高速ADC例程更是寥寥无几,于是我打算借用这篇文章记录一下自己的学习成果,并尽量详细地解释如何使用MSP432P401R单片机进行ADC转换并通过DMA进行数据传输,最后通过串口打印出结果。这是一种常用的数据采集和处理方式,具有实时性和准确性,可以借用DMA这个单片机秘书减少CPU的负担。本人目前大二,能力有限,难免有错误和不妥之处,恳请广大读者批评指正。·

二、原理介绍 

1.ADC14模块简介

        ADC14模块包含一个 14位的逐次渐进(SAR)内核,采样选择控制,多达 32个独立的转换和控制的缓冲区。转换控制缓冲区在无 CPU 干预的情况下,允许多达32路独立 ADC采样值进行转换和保存。(注意此处的14位!!!)

ADC_Volt1 = (float)(data_array1[j++]) / 16383.0f * 3.3f;

遵循ADC转换公式:

2.ADC14的转换模式

        在MSP432单片机的ADC模块中,提供了四种不同的转换模式,分别是:单次转换模式、连续转换模式、序列转换模式和自动重复单次转换模式。下面我将简单介绍这四种模式:

  1. 单通道单次模式:在这种模式下,ADC只对一个预设的通道进行一次转换。

  2. 序列通道模式(自动扫描模式):在这种模式下,ADC会按照预设的顺序,依次对多个通道进行一次转换。

  3. 重复单通道模式:在这种模式下,ADC会对一个预设的通道进行连续的、重复的转换。

  4. 重复序列通道模式(重复自动扫描模式):在这种模式下,ADC会按照预设的顺序,连续地、重复地对多个通道进行转换。

我使用的是第三种——重复单通道模式,原理图如下:

重复单通道模式的流程图

3.DMA简介

  1. 定义和功能:DMA(Direct Memory Access)是一种允许一些硬件子系统在不通过CPU的情况下,直接读取或写入系统内存的技术。在MSP432P401R单片机中,DMA可以在不占用CPU的情况下,将ADC的数据直接传输到内存,或者将内存中的数据直接输出到DAC。
  2. 工作原理:当DMA控制器收到一个DMA请求后,它会暂停CPU,然后直接占用总线,将数据从外设传输到内存,或者从内存传输到外设。在数据传输完成后,DMA控制器会向CPU发送一个中断信号,通知数据传输已经完成,CPU可以继续执行其他任务。
  3. DMA模块的特色:MSP432P401R单片机的DMA模块有32个通道,可以同时处理多个DMA任务。它支持的数据传输模式有单次传输和连续传输两种。在连续传输模式下,DMA控制器可以在一次请求中传输多个数据,从而提高数据传输效率。
  4. DMA的优势:DMA可以将CPU从繁重的数据传输任务中解放出来,使CPU有更多的时间去处理其他任务。同时,DMA的数据传输速度比CPU直接控制的速度要快,所以使用DMA可以提高整个系统的运行效率。
  5. DMA在ADC中的应用:在ADC连续采样的应用中,可以使用DMA将ADC的数据直接传输到内存,这样不仅可以减少CPU的负担,还可以确保ADC数据的实时性。

4.DMA的传输模式

  1. 单次传输模式(Single Transfer Mode):在这种模式下,每次DMA请求只会传输一个数据单元,然后DMA控制器会暂停,直到收到下一次DMA请求。这种模式用于数据传输量较小,或者数据传输速率较低的情况。
  2. 块传输模式(Block Transfer Mode):在这种模式下,每次DMA请求会传输一个数据块,然后DMA控制器会暂停,直到收到下一次DMA请求。这种模式用于数据传输量较大,但是数据传输不需要很快的情况。
  3. 请求链模式(Demand Transfer Mode):在这种模式下,DMA控制器在完成一个数据传输后,会立即检查是否有新的DMA请求。如果有新的DMA请求,DMA控制器会立即开始新的数据传输。这种模式用于数据传输速率需要很快,或者数据传输量很大的情况。
  4. 乒乓模式(Ping-Pong Mode):在这种模式下,DMA控制器会在两个数据缓冲区之间来回切换。当一个数据缓冲区被填满时,DMA控制器会切换到另一个数据缓冲区进行数据传输。这种模式用于需要连续不断的数据传输,而且不能有数据传输的延迟的情况。
  5. 循环计数模式(Cyclic Mode):在这种模式下,DMA控制器会在数据缓冲区内循环传输数据。当达到数据缓冲区的尾部时,DMA控制器会自动从数据缓冲区的头部开始新的数据传输。这种模式用于需要循环处理相同数据的情况。

我使用的是——自动模式(Ping-Pong Mode)

三、程序实现

1.ADC配置

void ADC_config(void)
{
    /* 启用浮点运算的FPU */
    MAP_FPU_enableModule();
    MAP_FPU_enableLazyStacking();

    /* Initializing ADC (MCLK/1/1) */
    MAP_ADC14_enableModule();                                                                 // 使能ADC14模块
    MAP_ADC14_initModule(ADC_CLOCKSOURCE_MCLK, ADC_PREDIVIDER_4, ADC_DIVIDER_5, ADC_NOROUTE); // 初始化ADC 时钟 分频  通道 2.4MHz

    MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN5, GPIO_TERTIARY_MODULE_FUNCTION); // 模拟输入
    MAP_ADC14_configureSingleSampleMode(ADC_MEM0, true);                                                    // 单通道配置 多次转化true
    MAP_ADC14_configureConversionMemory(ADC_MEM0, ADC_VREFPOS_AVCC_VREFNEG_VSS, ADC_INPUT_A0, false);       // 使用内部电源电压参考 非差分输入false
    MAP_ADC14_enableInterrupt(ADC_INT0);                                                                    // ADC通道0的中断

    /* Setting up the sample timer to automatically step through the sequence
     * convert.
     */
    MAP_ADC14_enableSampleTimer(ADC_AUTOMATIC_ITERATION); // 自动触发

    /* Triggering the start of the sample */
    MAP_ADC14_enableConversion();        // 使能开始转换(触发后 自动ADC上电)
    MAP_ADC14_toggleConversionTrigger(); // 开启第一次软件触发
}

ADC配置函数:

  1. 启用浮点运算单元(FPU):使用的两行代码启用了FPU,并开启Lazy Stacking特性,这可以优化浮点数的计算速度和效率。

  2. 启动ADC模块:通过调用MAP_ADC14_enableModule()函数,启动ADC14模块,使其可以开始工作。

  3. 初始化ADC模块:其中指定了ADC的时钟源,时钟预分频器和分频器分别设为4和5,这样ADC的工作频率就被设定为了2.4MHz,这些配置将决定ADC的采样速率和信号路径。

  4. 配置GPIO引脚:将GPIO引脚P5.5配置为ADC的模拟输入,这样ADC就可以通过这个引脚获取模拟信号。

  5. 配置ADC采样模式:选择重复单通道模式,这意味着ADC在每次完成一次采样转换后,会自动开始新的转换。

  6. 配置ADC转换内存:确定ADC的参考电压源、输入信号源和模式,这些设置会影响ADC转换的结果。

  7. 启用ADC中断:允许ADC在完成一次转换后产生中断,这样可以在转换完成后及时获取和处理数据。

  8. 启动ADC样本采样定时器:选择了自动迭代模式,这使得ADC在完成一次采样后,可以自动开始下一次采样。

  9. 启动ADC转换:启动ADC的转换功能,并通过软件触发第一次转换。

*系统主时钟MCLK的频率是48MHz,那么经过预分频器和分频器后,ADC的时钟频率计算方式如下:(非满速状态,满速只需将MAP_ADC14_initModule函数中的4和5改成1和1即可)

2.DMA配置

/* DMA Control Table  这一段是必须要有的*///
#if defined(__TI_COMPILER_VERSION__)	
#pragma DATA_ALIGN(controlTable, 1024)	
#elif defined(__IAR_SYSTEMS_ICC__)		
#pragma data_alignment = 1024	
#elif defined(__GNUC__)
__attribute__((aligned(1024)))
#elif defined(__CC_ARM)
__align(1024)
#endif
uint8_t controlTable[1024];
/**************************************/

#define DAM_SIZE 1024

void DMA_config(void)
{
    MAP_WDT_A_holdTimer(); // 关闭看门狗

    memset(data_array1, 0x11, DAM_SIZE); // 目标数组初始化

    GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN0);
    GPIO_setAsOutputPin(GPIO_PORT_P2, GPIO_PIN1);
    GPIO_setOutputHighOnPin(GPIO_PORT_P1, GPIO_PIN0);

    DMA_enableModule();               // 使能DMA模块
    DMA_setControlBase(controlTable); // 设置控制表基地址

    DMA_disableChannelAttribute(DMA_CH7_ADC14,
                                UDMA_ATTR_ALTSELECT | UDMA_ATTR_USEBURST |
                                    UDMA_ATTR_HIGH_PRIORITY |
                                    UDMA_ATTR_REQMASK); // 禁用通道特征

    DMA_setChannelControl(UDMA_PRI_SELECT | DMA_CH7_ADC14, UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);
    // 设置通道控制参数   		ADC映射        主数据结构     16位数据大小    	源地址增量        目标地址增量      仲裁大小
    DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_CH7_ADC14, UDMA_MODE_AUTO, (void *)&ADC14->MEM[0], data_array1, DAM_SIZE);
    // 设置通道传输参数       	ADC映射        主数据结构         自动模式        		源数据            目标数据    传输数量


    DMA_requestChannel(7);            // 通道7请求传输
    DMA_assignChannel(DMA_CH7_ADC14); // 指定外设映射

    DMA_assignInterrupt(DMA_INT1, 7);
    Interrupt_enableInterrupt(INT_DMA_INT1);
    DMA_clearInterruptFlag(7);
    Interrupt_enableMaster();

    DMA_enableChannel(7);
    ADC14_enableConversion();
    DMA_requestSoftwareTransfer(7); // DMA_CH7_ADC14通道软件启动
}

DMA配置函数:

  1. 关闭了看门狗定时器,这是为了防止在配置DMA的过程中由于超时而触发看门狗定时器重置系统。

  2. 初始化了两个数组data_array1和data_array2,这两个数组将被用作DMA的乒乓模式的数据存储。

  3. 启动了DMA模块,并设置了DMA控制表的基地址。

  4. 禁用了DMA通道的某些属性,例如选择乒乓模式,使用突发模式,高优先级以及请求屏蔽。

  5. 分别为DMA通道设置了控制参数和传输参数。这些参数包括数据大小,源地址和目标地址的增量,仲裁大小,传输模式,源数据和目标数据的地址,以及传输的数据数量。

  6. 请求DMA通道,并指定了DMA通道与ADC的映射。

  7. 指定DMA中断,并启用中断。这样,当DMA传输完成时,可以触发中断,以便主处理器可以处理已经完成传输的数据。

  8. 启动DMA通道,并使能ADC转换。然后,通过软件请求DMA传输,这将开始DMA的数据传输。

3.DMA中断服务函数

#define DAM_SIZE 1024
uint16_t data_array1[DAM_SIZE]; // DMA数组

void DMA_INT1_IRQHandler(void)
{
    GPIO_setOutputHighOnPin(GPIO_PORT_P2, GPIO_PIN1); // 点亮绿灯

    DMA_requestChannel(7);            // 通道7请求传输
    DMA_assignChannel(DMA_CH7_ADC14); // 指定外设映射

    DMA_assignInterrupt(DMA_INT1, 7);
    Interrupt_enableInterrupt(INT_DMA_INT1);
    DMA_clearInterruptFlag(7);

    DMA_enableChannel(7);
    ADC14_enableConversion();

    /* Switch between primary and alternate bufferes with DMA's PingPong mode */

        DMA_setChannelControl(UDMA_PRI_SELECT | DMA_CH7_ADC14,
                              UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1);
        DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_CH7_ADC14,
                               UDMA_MODE_AUTO, (void *)&ADC14->MEM[0],
                               data_array1, DAM_SIZE);
    DMA_requestSoftwareTransfer(7); // DMA_CH7_ADC14通道软件启动
}

DMA服务函数: 

  1. GPIO_setOutputHighOnPin(GPIO_PORT_P2, GPIO_PIN1): 这是使 GPIO_PORT_P2 上的 GPIO_PIN1 输出高电平信号的函数,可能是映射到一个 LED 灯,这样执行该行代码可以点亮该灯,表示已经进入了 DMA 中断。

  2. DMA_requestChannel(7): 请求DMA通道7用于数据传输。

  3. DMA_assignChannel(DMA_CH7_ADC14): 指定通道7用来服务 ADC14。

  4. DMA_assignInterrupt(DMA_INT1, 7): 将中断DMA_INT1分配给通道7。

  5. Interrupt_enableInterrupt(INT_DMA_INT1): 开启DMA_INT1中断。

  6. DMA_clearInterruptFlag(7): 清除DMA通道7的中断标志。

  7. DMA_enableChannel(7): 启用DMA通道7。

  8. ADC14_enableConversion(): 启动ADC14转换。

  9. DMA_setChannelControl(...): 设置通道7的传输特性,如16位源和目标、无源递增、16位目标递增和仲裁大小。

  10. DMA_setChannelTransfer(...): 配置通道7的ADC转换,设定源地址(ADC结果存储区)和目标地址(数据数组),以及使用的模式(赋值了 UDMA_MODE_AUTO,也就是自动模式)。

  11. DMA_requestSoftwareTransfer(7): 最后使用软件触发通道7的数据传输。

4.timeA.c

  1. #include "timeA.h"
    
    /*********************************************************************************************************/
    
    /**************************************         TIMA0          *******************************************/
    uint16_t cnt;
    
    void TimA0_Int_Init(uint16_t ccr0, uint16_t psc)
    {
    	cnt=0;
        // 1.增计数模式初始化
        Timer_A_UpModeConfig upConfig;
        upConfig.clockSource = TIMER_A_CLOCKSOURCE_SMCLK;                                      //时钟源
        upConfig.clockSourceDivider = psc;                                                     //时钟分频 范围1-64
        upConfig.timerPeriod = ccr0;                                                           //自动重装载值(ARR)
        upConfig.timerInterruptEnable_TAIE = TIMER_A_TAIE_INTERRUPT_DISABLE;                   //禁用 tim溢出中断
        upConfig.captureCompareInterruptEnable_CCR0_CCIE = TIMER_A_CCIE_CCR0_INTERRUPT_ENABLE; //启用 ccr0更新中断
        upConfig.timerClear = TIMER_A_DO_CLEAR;                                                // Clear value
    
        // 2.初始化定时器A
        MAP_Timer_A_configureUpMode(TIMER_A0_BASE, &upConfig);
    
        // 3.选择模式开始计数
        MAP_Timer_A_startCounter(TIMER_A0_BASE, TIMER_A_UP_MODE);
    
        // 4.清除比较中断标志位
        MAP_Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_0);
    
        // 5.开启串口端口中断
        MAP_Interrupt_enableInterrupt(INT_TA0_0);
    }
    
    // 6.编写TIMA ISR
    void TA0_0_IRQHandler(void)
    {
        MAP_Timer_A_clearCaptureCompareInterrupt(TIMER_A0_BASE, TIMER_A_CAPTURECOMPARE_REGISTER_0);
    
        /*开始填充用户代码*/
    	if(cnt<100)
    	{
    		cnt++;
    	}
    	if(cnt==99)
    	{
    		DMA_disableChannel(7);
    		MAP_GPIO_toggleOutputOnPin(GPIO_PORT_P1, GPIO_PIN0);
    	}
        
    
        /*结束填充用户代码*/
    }
    /*********************************************************************************************************/
    
    

    5.timeA.h

  2. /****************************************************/
    // MSP432P401R
    // 定时器A
    // Bilibili:m-RNA
    // E-mail:m-RNA@qq.com
    // 创建日期:2021/8/26
    /****************************************************/
    
    #ifndef __RNA_TIMA_H
    #define __RNA_TIMA_H
    #include <ti/devices/msp432p4xx/driverlib/driverlib.h>
    
    
    void TimA0_Int_Init(uint16_t ccr0, uint16_t psc);
    
    #endif
    

6.main.c文件

/******************************************************************************
// MSP432P401R
// 13 ADC_DMA v2.0

*******************************************************************************/

/*
 * 2021/10/28 更新
 * 
 * CCS支持printf
 * Keil支持标准C库跟微库
 * 用Keil开发终于可以不开微库啦
 *
 * 使用标准C库时,将无法使用scanf;
 * 如果需要使用scanf时,请使用微库 MicroLIB。
 * 
 */

#include "sysinit.h"
#include "usart.h"
#include "adc.h"
#include "delay.h"
#include "timeA.h"


#define DAM_SIZE 1024
#define CLKDIV 64   //时钟源分频
#define CCR0 37499  // 比较值0


extern uint16_t cnt;
uint16_t print_flag;

uint16_t a = 0;

extern uint16_t data_array1[DAM_SIZE]; // DMA数组
/*
 * 定时器中断周期:
 *
 * T_timer_a = CLKDIV * (CCR0 + 1) / f_clk 
 *           = 64 * 37500 / 48000000 
 *           = 0.05s = 20Hz
 */
//float f = i * SAMPLING_RATE / (float)FFT_SIZE;

int main(void)
{
	SysInit();		   //第3讲 时钟配置
	delay_init();
	uart_init(115200); //第7讲 串口实验
	TimA0_Int_Init(CCR0,CLKDIV); // 第8讲 TIMA中断
	MAP_WDT_A_holdTimer();
	ADC_config();
	DMA_config();
	print_flag=0;
	printf("OK");
	while (1)
	{		
		if(cnt==100&&print_flag==0)
		{
			printf("%.3f\t",(float)(data_array1[a]) / 16383.0f * 3.3f);
			a++;
		}
		if(a>=DAM_SIZE)
		{
			print_flag=1;
		}
		
		if(cnt<100)
		{
			DMA_requestSoftwareTransfer(7); // DMA_CH7_ADC14通道软件启动
		}
	}
}

        实验时发现在while(1)中加入“DMA_requestSoftwareTransfer(7);”后串口打印速度会加快,输出的电压精确性暂未知。cnt在定时为0.05s的定时器中自增(代码中为长定时5s)。

四、实验现象

         实验时将P5.5(ADC通道0)引脚接上杜邦线(或其他连接方式)作为输入端,此处我是将P5.5引脚接上了信号发生器(频率100KHZ,幅度1v,直流偏移1.5v)。

 

 

        实验时将ADC的分频取消了(也就是1分频),单片机启动后采集数据,定时器定时5s,5s一到就用printf把data_array1数组中的元素全部打印出来,用excel生成图表。根据其测100kHZ时的现象推测出采样频率约为3.4MHZ。后经计算,验证完毕。

其中的14就代表着14位的ADC 

五、参考网站和文献

刘杰。2016.5。基于固件的MSP432微控制器原理及应用。北京:北京航空航天大学出版社。

【新提醒】MSP432的DMA乒乓模式怎么传输ADC采集的数据 - 24小时必答区 (51hei.com)http://www.51hei.com/bbs/dpj-196187-1.htmlMSP432P4电赛入门速成/Keil/CCS/VScode_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Rb4y1z7KJ/?spm_id_from=333.999.0.0&vd_source=d6746572f4237ef92ce6381108dd5f89

  • 20
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 27
    评论
下面是一个基于MSP432P401R微控制器的ADCDMA的例程,用于将ADC采样的数据传输到内存中: ```c #include <ti/devices/msp432p4xx/driverlib/driverlib.h> #define NUM_SAMPLES 100 uint16_t adcData[NUM_SAMPLES]; int main(void) { // 初始化系统时钟和外设 MAP_WDT_A_holdTimer(); MAP_Interrupt_disableMaster(); // 配置ADC MAP_GPIO_setAsPeripheralModuleFunctionInputPin(GPIO_PORT_P5, GPIO_PIN4, GPIO_TERTIARY_MODULE_FUNCTION); MAP_ADC14_enableModule(); MAP_ADC14_initModule(ADC_CLOCKSOURCE_SMCLK, ADC_PREDIVIDER_1, ADC_DIVIDER_1, 0); MAP_ADC14_configureSingleSampleMode(ADC_MEM0, true); MAP_ADC14_setResolution(ADC_14BIT); MAP_ADC14_setSampleHoldTime(ADC_PULSE_WIDTH_4, ADC_PULSE_WIDTH_4); MAP_ADC14_enableSampleTimer(ADC_MANUAL_ITERATION); // 配置DMA MAP_DMA_enableModule(); MAP_DMA_setControlBase(MSP_EXP432P401RLP_DMAControlTable); MAP_DMA_enableChannel(0); MAP_DMA_setChannelControl(UDMA_PRI_SELECT | DMA_CH0_ADC14, UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1024); MAP_DMA_setChannelTransfer(UDMA_PRI_SELECT | DMA_CH0_ADC14, UDMA_MODE_PINGPONG, (void*)&ADC14->MEM[0], adcData, NUM_SAMPLES); // 启动DMA传输 MAP_DMA_enableChannel(0); // 启动ADC采样 MAP_ADC14_enableConversion(); MAP_ADC14_toggleConversionTrigger(); // 等待DMA传输完成 while (MAP_DMA_getChannelStatus(DMA_CH0_ADC14) != UDMA_TRNMODE_STOP) { } // 停止ADCDMA MAP_ADC14_disableConversion(); MAP_DMA_disableChannel(0); MAP_DMA_disableModule(); // 在这里可以使用adcData数组中的数据 while (1) { // 主循环 } } ``` 这个例程配置了ADC14模块以采样P5.4引脚的模拟信号,并使用DMA将采样数据传输到名为adcData的数组中。你可以在代码的注释部分后续添加适合你的应用程序的代码。 请注意,在使用此示例代码之前,你应该确保已经安装了MSP432P4xx驱动库,并将其包含在项目中。此外,还要根据你的硬件连接和需求进行适当的修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 27
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷的卡尔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值