一、简介
最近在准备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模块中,提供了四种不同的转换模式,分别是:单次转换模式、连续转换模式、序列转换模式和自动重复单次转换模式。下面我将简单介绍这四种模式:
-
单通道单次模式:在这种模式下,ADC只对一个预设的通道进行一次转换。
-
序列通道模式(自动扫描模式):在这种模式下,ADC会按照预设的顺序,依次对多个通道进行一次转换。
-
重复单通道模式:在这种模式下,ADC会对一个预设的通道进行连续的、重复的转换。
-
重复序列通道模式(重复自动扫描模式):在这种模式下,ADC会按照预设的顺序,连续地、重复地对多个通道进行转换。
我使用的是第三种——重复单通道模式,原理图如下:
3.DMA简介
- 定义和功能:DMA(Direct Memory Access)是一种允许一些硬件子系统在不通过CPU的情况下,直接读取或写入系统内存的技术。在MSP432P401R单片机中,DMA可以在不占用CPU的情况下,将ADC的数据直接传输到内存,或者将内存中的数据直接输出到DAC。
- 工作原理:当DMA控制器收到一个DMA请求后,它会暂停CPU,然后直接占用总线,将数据从外设传输到内存,或者从内存传输到外设。在数据传输完成后,DMA控制器会向CPU发送一个中断信号,通知数据传输已经完成,CPU可以继续执行其他任务。
- DMA模块的特色:MSP432P401R单片机的DMA模块有32个通道,可以同时处理多个DMA任务。它支持的数据传输模式有单次传输和连续传输两种。在连续传输模式下,DMA控制器可以在一次请求中传输多个数据,从而提高数据传输效率。
- DMA的优势:DMA可以将CPU从繁重的数据传输任务中解放出来,使CPU有更多的时间去处理其他任务。同时,DMA的数据传输速度比CPU直接控制的速度要快,所以使用DMA可以提高整个系统的运行效率。
- DMA在ADC中的应用:在ADC连续采样的应用中,可以使用DMA将ADC的数据直接传输到内存,这样不仅可以减少CPU的负担,还可以确保ADC数据的实时性。
4.DMA的传输模式
- 单次传输模式(Single Transfer Mode):在这种模式下,每次DMA请求只会传输一个数据单元,然后DMA控制器会暂停,直到收到下一次DMA请求。这种模式用于数据传输量较小,或者数据传输速率较低的情况。
- 块传输模式(Block Transfer Mode):在这种模式下,每次DMA请求会传输一个数据块,然后DMA控制器会暂停,直到收到下一次DMA请求。这种模式用于数据传输量较大,但是数据传输不需要很快的情况。
- 请求链模式(Demand Transfer Mode):在这种模式下,DMA控制器在完成一个数据传输后,会立即检查是否有新的DMA请求。如果有新的DMA请求,DMA控制器会立即开始新的数据传输。这种模式用于数据传输速率需要很快,或者数据传输量很大的情况。
- 乒乓模式(Ping-Pong Mode):在这种模式下,DMA控制器会在两个数据缓冲区之间来回切换。当一个数据缓冲区被填满时,DMA控制器会切换到另一个数据缓冲区进行数据传输。这种模式用于需要连续不断的数据传输,而且不能有数据传输的延迟的情况。
- 循环计数模式(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配置函数:
-
启用浮点运算单元(FPU):使用的两行代码启用了FPU,并开启Lazy Stacking特性,这可以优化浮点数的计算速度和效率。
-
启动ADC模块:通过调用MAP_ADC14_enableModule()函数,启动ADC14模块,使其可以开始工作。
-
初始化ADC模块:其中指定了ADC的时钟源,时钟预分频器和分频器分别设为4和5,这样ADC的工作频率就被设定为了2.4MHz,这些配置将决定ADC的采样速率和信号路径。
-
配置GPIO引脚:将GPIO引脚P5.5配置为ADC的模拟输入,这样ADC就可以通过这个引脚获取模拟信号。
-
配置ADC采样模式:选择重复单通道模式,这意味着ADC在每次完成一次采样转换后,会自动开始新的转换。
-
配置ADC转换内存:确定ADC的参考电压源、输入信号源和模式,这些设置会影响ADC转换的结果。
-
启用ADC中断:允许ADC在完成一次转换后产生中断,这样可以在转换完成后及时获取和处理数据。
-
启动ADC样本采样定时器:选择了自动迭代模式,这使得ADC在完成一次采样后,可以自动开始下一次采样。
-
启动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配置函数:
-
关闭了看门狗定时器,这是为了防止在配置DMA的过程中由于超时而触发看门狗定时器重置系统。
-
初始化了两个数组data_array1和data_array2,这两个数组将被用作DMA的乒乓模式的数据存储。
-
启动了DMA模块,并设置了DMA控制表的基地址。
-
禁用了DMA通道的某些属性,例如选择乒乓模式,使用突发模式,高优先级以及请求屏蔽。
-
分别为DMA通道设置了控制参数和传输参数。这些参数包括数据大小,源地址和目标地址的增量,仲裁大小,传输模式,源数据和目标数据的地址,以及传输的数据数量。
-
请求DMA通道,并指定了DMA通道与ADC的映射。
-
指定DMA中断,并启用中断。这样,当DMA传输完成时,可以触发中断,以便主处理器可以处理已经完成传输的数据。
-
启动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服务函数:
-
GPIO_setOutputHighOnPin(GPIO_PORT_P2, GPIO_PIN1): 这是使 GPIO_PORT_P2 上的 GPIO_PIN1 输出高电平信号的函数,可能是映射到一个 LED 灯,这样执行该行代码可以点亮该灯,表示已经进入了 DMA 中断。
-
DMA_requestChannel(7): 请求DMA通道7用于数据传输。
-
DMA_assignChannel(DMA_CH7_ADC14): 指定通道7用来服务 ADC14。
-
DMA_assignInterrupt(DMA_INT1, 7): 将中断DMA_INT1分配给通道7。
-
Interrupt_enableInterrupt(INT_DMA_INT1): 开启DMA_INT1中断。
-
DMA_clearInterruptFlag(7): 清除DMA通道7的中断标志。
-
DMA_enableChannel(7): 启用DMA通道7。
-
ADC14_enableConversion(): 启动ADC14转换。
-
DMA_setChannelControl(...): 设置通道7的传输特性,如16位源和目标、无源递增、16位目标递增和仲裁大小。
-
DMA_setChannelTransfer(...): 配置通道7的ADC转换,设定源地址(ADC结果存储区)和目标地址(数据数组),以及使用的模式(赋值了 UDMA_MODE_AUTO,也就是自动模式)。
-
DMA_requestSoftwareTransfer(7): 最后使用软件触发通道7的数据传输。
4.timeA.c
-
#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
-
/****************************************************/ // 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