本帖最后由 小恩GG 于 2024-5-15 18:03 编辑
MCXN947:Ctimer硬件触发ADC多通道采样与DMA传输 MCXN947是一款高性能微控制器,它具备灵活的ADC多通道采样与DMA传输功能。通过配置Ctimer硬件触发机制,MCXN947可以实现精确的多通道模拟信号采集,并且利用DMA(直接内存访问)传输高效地处理数据。这种结合使用不仅提高了数据采集的效率,还确保了在连续转换模式下数据的完整性和可靠性。此外,MCXN947支持单端模拟信号采样,差分模拟信号采样,采样频率可以达到2MSPS,允许用户根据需求选择不同的采集模式,而且可以通过多种方式触发ADC转换,包括软件触发、定时器触发等。这些特性使得MCXN947非常适合用于需要连续监测和处理多个模拟信号的应用场合。 本文在创建最基本的工程上创建了六个文件分别为:bsp_ctimer.c、bsp_ctimer.h、bsp_adc.c、bsp_adc.h bsp_eDMA.c、bsp_eDMA.h 本文将会详细讲解Ctimer硬件触发ADC多通道采样与DMA传输。 1.Ctimer 配置 Ctimer 配置定义了两个结构体,分别是CTIMER0_config和CTIMER0_Match_3_config。 CTIMER0_config结构体包含了定时器的配置信息,包括模式、输入源和预分频值。 CTIMER0_Match_3_config结构体包含了匹配通道3的配置信息,包括匹配值、计数器复位、计数器停止、输出控制、输出引脚初始状态和中断使能。 在CTIMER0_Init函数中,首先启用CTIMER0的时钟,并将其切换到FRO_HF。接着,设置CTIMER0CLKDIV分频器为1。然后,使用CTIMER0_config配置CTIMER0外设,并使用CTIMER0_Match_3_config配置CTIMER0的匹配通道3。接下来,将timer0_call回调函数注册到CTIMER0外设的单次回调模式。最后,启动CTIMER0定时器。 Note:Ctimer配置后每一秒会触发一次ADC0,输出控制应选择:Toggle bit/output。通过手册查看ADC0_TRIG0寄存器,我们必须使用CTIMER0_Match_3通道外部触发ADC0。 下图为ADC Trigger 输入源: 这个寄存器是通过INPUTMUX选择ADC触发输入。 INPUTMUX实现了许多输入多路复用器,这些多路复用器从多个输入中选择一个,将其路由到给定外设的特定输入信号。这用于允许用户配置内部模块或设备上的外部引脚之间的数据路径。对于每个模块输入(来自INPUTMUX的输出),都有一个寄存器选择要使用的输入,其中的寄存器名和描述提供了由每个寄存器控制的模块输入的详细信息。每个MUX的输入信号/引脚选项都是可配置的,并且可以因MUX而异,多路复用器选择图如下: 代码如下: bsp_ctimer.c - #include "bsp_ctimer.h"
- const ctimer_config_t CTIMER0_config = {
- .mode = kCTIMER_TimerMode,
- .input = kCTIMER_Capture_0,
- .prescale = 47999
- };
- const ctimer_match_config_t CTIMER0_Match_3_config = {
- .matchValue = 999,
- .enableCounterReset = true,
- .enableCounterStop = false,
- .outControl = kCTIMER_Output_Toggle,
- .outPinInitState = false,
- .enableInterrupt = true
- };
- /* Single callback functions definition */
- ctimer_callback_t CTIMER0_callback[] = {timer0_call};
- void CTIMER0_Init(void)
- {
- /*!< Set up clock selectors */
- CLOCK_AttachClk(kFRO_HF_to_CTIMER0); /*!< Switch CTIMER0 to FRO_HF */
- CLOCK_SetClkDiv(kCLOCK_DivCtimer0Clk, 1U);/*!< Set CTIMER0CLKDIV divider to value 1 */
- /* CTIMER0 peripheral initialization */
- CTIMER_Init(CTIMER0_PERIPHERAL, &CTIMER0_config);
- /* Match channel 3 of CTIMER0 peripheral initialization */
- CTIMER_SetupMatch(CTIMER0_PERIPHERAL, CTIMER0_MATCH_3_CHANNEL, &CTIMER0_Match_3_config);
- CTIMER_RegisterCallBack(CTIMER0_PERIPHERAL, CTIMER0_callback, kCTIMER_SingleCallback);
- /* Start the timer */
- CTIMER_StartTimer(CTIMER0_PERIPHERAL);
- }
复制代码bsp_ctimer.h - #ifndef BSP_CTIMER_H_
- #define BSP_CTIMER_H_
- #include "fsl_ctimer.h"
- #define CTIMER0_PERIPHERAL CTIMER0
- /* Timer tick frequency in Hz (input frequency of the timer) */
- #define CTIMER0_TICK_FREQ 1000UL
- /* Timer tick period in ns (input period of the timer) */
- #define CTIMER0_TICK_PERIOD 1000000UL
- /* Definition of PWM period channel. */
- #define CTIMER0_PWM_PERIOD_CH kCTIMER_Match_0
- /* Definition of channel 3 ID */
- #define CTIMER0_MATCH_3_CHANNEL kCTIMER_Match_3
- /* CTIMER0 interrupt vector ID (number). */
- #define CTIMER0_TIMER_IRQN CTIMER0_IRQn
- void CTIMER0_Init(void);
- /***********************************************************************************************************************
- * Callback functions
- **********************************************************************************************************************/
- /* Single callback function declaration */
- extern void timer0_call(uint32_t flags);
- extern ctimer_callback_t CTIMER0_callback[];
- #endif /* BSP_CTIMER_H_ */
复制代码
2.ADC 配置 ADC通道:选择ADC模拟通道ADC0_A0、ADC0_B ADC0_config的LPADC配置结构体,其中enableInDozeMode设置为true表示在低功耗模式下启用ADC转换器;conversionAverageMode设置为kLPADC_ConversionAverage128表示使用128次平均模式进行转换;referenceVoltageSource设置为kLPADC_ReferenceVoltageAlt3表示使用VDD参考电压源;powerLevelMode设置为kLPADC_PowerLevelAlt1表示选择最低功率设置。 另外,还定义了两个转换命令的配置数组ADC0_commandsConfig和触发器的配置数组ADC0_triggersConfig。ADC0_commandsConfig包含两个转换命令的配置信息,第一个命令使用单端A通道采样,第二个命令使用单端B通道采样。ADC0_triggersConfig包含一个触发器的配置信息,目标命令ID为1,延迟时间为0,FIFO通道选择 FIFO 0,优先级为1,启用硬件触发。ADC0_Init函数初始化过程:开启模拟模块的参考电压功能、配置ADC0时钟源、初始化LPADC 、执行自动校准确保更准确的转换结果、启用DMA请求允许数据自动传输到内存、配置转换命令、配置触发器。 Note:ADC配置要注意触发方式启用硬件触发(即外部触发源),双通道采样要将Command1的NextCommand 设置为Command2才能实现级联双通道采样。 代码如下: bsp_adc.c - #include "bsp_adc.h"
- const lpadc_config_t ADC0_config = {
- .enableInDozeMode = true,
- .conversionAverageMode = kLPADC_ConversionAverage128,
- .enableAnalogPreliminary = false,
- .powerUpDelay = 0x80UL,
- .referenceVoltageSource = kLPADC_ReferenceVoltageAlt3,
- .powerLevelMode = kLPADC_PowerLevelAlt1,
- .triggerPriorityPolicy = kLPADC_ConvPreemptImmediatelyNotAutoResumed,
- .enableConvPause = false,
- .convPauseDelay = 0UL,
- .FIFO0Watermark = 0UL,
- .FIFO1Watermark = 0UL
- };
- lpadc_conv_command_config_t ADC0_commandsConfig[2] = {
- {
- .sampleChannelMode = kLPADC_SampleChannelSingleEndSideA,
- .channelNumber = 0U,
- .channelBNumber = 0U,
- .chainedNextCommandNumber = 2,
- .enableChannelB = false,
- .enableAutoChannelIncrement = false,
- .loopCount = 0UL,
- .hardwareAverageMode = kLPADC_HardwareAverageCount1,
- .sampleTimeMode = kLPADC_SampleTimeADCK3,
- .hardwareCompareMode = kLPADC_HardwareCompareDisabled,
- .hardwareCompareValueHigh = 0UL,
- .hardwareCompareValueLow = 0UL,
- .conversionResolutionMode = kLPADC_ConversionResolutionStandard,
- .enableWaitTrigger = false
- },
- {
- .sampleChannelMode = kLPADC_SampleChannelSingleEndSideB,
- .channelNumber = 0U,
- .channelBNumber = 0U,
- .chainedNextCommandNumber = 0,
- .enableChannelB = false,
- .enableAutoChannelIncrement = false,
- .loopCount = 0UL,
- .hardwareAverageMode = kLPADC_HardwareAverageCount1,
- .sampleTimeMode = kLPADC_SampleTimeADCK3,
- .hardwareCompareMode = kLPADC_HardwareCompareDisabled,
- .hardwareCompareValueHigh = 0UL,
- .hardwareCompareValueLow = 0UL,
- .conversionResolutionMode = kLPADC_ConversionResolutionStandard,
- .enableWaitTrigger = false
- }
- };
- lpadc_conv_trigger_config_t ADC0_triggersConfig[1] = {
- {
- .targetCommandId = 1,
- .delayPower = 0UL,
- .channelAFIFOSelect = 0,
- .channelBFIFOSelect = 0,
- .priority = 1,
- .enableHardwareTrigger = true
- }
- };
- void ADC0_Init(void)
- {
- /*OPEN Analog Modules vref*/
- SPC_EnableActiveModeAnalogModules(DEMO_SPC_BASE, kSPC_controlVref);
- /*enable clock ADC0*/
- /*!< Set up clock selectors */
- CLOCK_AttachClk(kFRO12M_to_ADC0);/*!< Switch ADC0 to FRO12M */
- /*!< Set up dividers */
- CLOCK_SetClkDiv(kCLOCK_DivAdc0Clk, 1U);/*!< Set ADC0CLKDIV divider to value 1 */
- /* Initialize LPADC converter */
- LPADC_Init(ADC0_PERIPHERAL, &ADC0_config);
- /* Perform auto calibration */
- LPADC_DoAutoCalibration(ADC0_PERIPHERAL);
- /* Enable DMA request on FIFO 0 watermark event */
- LPADC_EnableFIFO0WatermarkDMA(ADC0_PERIPHERAL, true);
- /* Configure conversion command 1. */
- LPADC_SetConvCommandConfig(ADC0_PERIPHERAL, 1, &ADC0_commandsConfig[0]);
- /* Configure conversion command 2. */
- LPADC_SetConvCommandConfig(ADC0_PERIPHERAL, 2, &ADC0_commandsConfig[1]);
- /* Configure trigger 0. */
- LPADC_SetConvTriggerConfig(ADC0_PERIPHERAL, 0, &ADC0_triggersConfig[0]);
- }
复制代码bsp_adc.h - #ifndef BSP_ADC_H_
- #define BSP_ADC_H_
- #include "fsl_lpadc.h"
- #include "fsl_spc.h"
- /* Alias for ADC0 peripheral */
- #define ADC0_PERIPHERAL ADC0
- #define DEMO_SPC_BASE SPC0
- /***********************************************************************************************************************
- * Global variables
- **********************************************************************************************************************/
- extern const lpadc_config_t ADC0_config;
- extern lpadc_conv_command_config_t ADC0_commandsConfig[2];
- extern lpadc_conv_trigger_config_t ADC0_triggersConfig[1];
- void ADC0_Init(void);
- #endif /* BSP_ADC_H_ */
复制代码
3.eDMA配置 eDMA 配置首先定义了三个结构体变量:transferConfig、lpadcDmaChnlConfig和userConfig,分别用于配置数据传输、EDMA通道和用户配置。 接下来,对lpadcDmaChnlConfig的各个字段进行了初始化设置,包括数据符号扩展位位置、通道抢占配置、请求源等。 然后,通过调用EDMA_GetDefaultConfig函数获取默认配置,并使用EDMA_Init函数初始化EDMA模块。 接着,定义了一个源地址srcAddr,指向ADC0的RESFIFO0寄存器。 然后,使用EDMA_PrepareTransfer函数准备数据传输的配置,包括源地址、源大小、目标地址、目标大小、每次传输的数据大小、总传输次数和传输方向。 之后,将transferConfig.dstMajorLoopOffset设置为负的目标地址大小,用于在传输完成后将目标地址恢复为原始值。 接下来,使用EDMA_SetTransferConfig函数设置传输配置,并使用EDMA_InitChannel函数初始化EDMA通道。 最后,启用IRQ中断,并使用EDMA_EnableChannelRequest函数使能EDMA通道请求。 代码如下: bsp_eDMA.c - #include "bsp_eDMA.h"
- #include "bsp_adc.h"
- extern AT_NONCACHEABLE_SECTION_ALIGN_INIT(uint32_t destAddr[BUFFER_LENGTH], 32U);
- void EDMA_Configuration(void)
- {
- edma_transfer_config_t transferConfig;
- edma_channel_config_t lpadcDmaChnlConfig;
- edma_config_t userConfig;
- lpadcDmaChnlConfig.channelDataSignExtensionBitPosition = 0U;
- lpadcDmaChnlConfig.channelPreemptionConfig.enableChannelPreemption = false;
- lpadcDmaChnlConfig.channelPreemptionConfig.enablePreemptAbility = true;
- lpadcDmaChnlConfig.channelRequestSource = DEMO_DMA_REQUEST;
- lpadcDmaChnlConfig.protectionLevel = kEDMA_ChannelProtectionLevelUser;
- #if !(defined(FSL_FEATURE_EDMA_HAS_NO_CH_SBR_SEC) && FSL_FEATURE_EDMA_HAS_NO_CH_SBR_SEC)
- lpadcDmaChnlConfig.securityLevel = kEDMA_ChannelSecurityLevelNonSecure;
- #endif /* !(defined(FSL_FEATURE_EDMA_HAS_NO_CH_SBR_SEC) && FSL_FEATURE_EDMA_HAS_NO_CH_SBR_SEC) */
- /* Configure EDMA channel for one shot transfer */
- EDMA_GetDefaultConfig(&userConfig);
- EDMA_Init(DEMO_DMA_BASEADDR, &userConfig);
- void *srcAddr = (uint32_t *)&(ADC0_PERIPHERAL->RESFIFO[0U]);
- EDMA_PrepareTransfer(&transferConfig, srcAddr, sizeof(uint32_t), destAddr, sizeof(destAddr[0]), sizeof(destAddr[0]),
- sizeof(destAddr), kEDMA_PeripheralToMemory);
- /* Used to change the destination address to the original value */
- transferConfig.dstMajorLoopOffset = (int32_t)((-1) * sizeof(destAddr));
- EDMA_SetTransferConfig(DEMO_DMA_BASEADDR, DEMO_DMA_CHANNEL_0, &transferConfig, NULL);
- EDMA_InitChannel(DEMO_DMA_BASEADDR, DEMO_DMA_CHANNEL_0, &lpadcDmaChnlConfig);
- EnableIRQ(DEMO_DMA_IRQ);
- EDMA_EnableChannelRequest(DEMO_DMA_BASEADDR, DEMO_DMA_CHANNEL_0);
- }
复制代码
bsp_eDMA.h - #ifndef BSP_EDMA_H_
- #define BSP_EDMA_H_
- #include "fsl_edma.h"
- /* EDMA Defines */
- #define DEMO_DMA_BASEADDR DMA0
- #define DEMO_DMA_CHANNEL_0 0U
- #define DEMO_DMA_IRQ EDMA_0_CH0_IRQn
- #define DEMO_DMA_IRQ_HANDLER EDMA_0_CH0_IRQHandler
- #define BUFFER_LENGTH 2U
- #define DEMO_DMA_REQUEST kDma0RequestMuxAdc0FifoARequest
- void EDMA_Configuration(void);
- #endif /* BSP_EDMA_H_ */
复制代码
4.main.c文件 timer0_call函数:这是一个回调函数,用于在定时器触发时切换LED的状态。它使用GPIO_PortToggle函数来切换指定引脚的电平状态。 DEMO_DMA_IRQ_HANDLER函数:这是DMA通道的中断处理程序。当DMA传输完成时,该函数会被调用。它首先检查DMA通道的状态标志位是否包含中断标志位,如果是,则增加DMACounter计数器的值,清除中断标志位,并重新使能DMA通道请求。 int main(void)内容: 初始化输入多路复用器(INPUTMUX),将ADC0 FIFO0请求连接到DMA0 通道21,并添加Ctimer0M3ToAdc0Trigger信号。 初始化CTIMER0模块,用于触发ADC0。 初始化ADC0模块。 配置EDMA模块。 初始化LED红色引脚为逻辑低电平状态。 进入一个无限循环,等待DMA传输完成。 当DMA传输完成后,打印ADC0_A0和ADC0_B0的值。 代码如下: main.c - /*
- * Copyright 2016-2024 NXP
- * All rights reserved.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
- /**
- * @file MCXN947_ProjectADC.c
- * @brief Application entry point.
- */
- #include <stdio.h>
- #include "board.h"
- #include "peripherals.h"
- #include "pin_mux.h"
- #include "clock_config.h"
- #include "MCXN947_cm33_core0.h"
- #include "fsl_debug_console.h"
- #include "fsl_inputmux.h"
- #include "fsl_edma.h"
- /* TODO: insert other include files here. */
- #include "bsp_adc.h"
- #include "bsp_ctimer.h"
- #include "bsp_eDMA.h"
- /*******************************************************************************
- * Variables
- ******************************************************************************/
- volatile uint32_t DMACounter = 0U;
- AT_NONCACHEABLE_SECTION_ALIGN_INIT(uint32_t destAddr[BUFFER_LENGTH], 32U) = {0x00U};
- /* TODO: insert other definitions and declarations here. */
- #define BOARD_LED_GPIO BOARD_LED_RED_GPIO
- #define BOARD_LED_GPIO_PIN BOARD_LED_RED_GPIO_PIN
- void timer0_call(uint32_t flags)
- {
- GPIO_PortToggle(BOARD_LED_GPIO, 1u << BOARD_LED_GPIO_PIN);
- }
- void DEMO_DMA_IRQ_HANDLER(void)
- {
- if ((EDMA_GetChannelStatusFlags(DEMO_DMA_BASEADDR, DEMO_DMA_CHANNEL_0) & kEDMA_InterruptFlag) != 0U)
- {
- DMACounter++;
- EDMA_ClearChannelStatusFlags(DEMO_DMA_BASEADDR, DEMO_DMA_CHANNEL_0, kEDMA_InterruptFlag);
- EDMA_EnableChannelRequest(DEMO_DMA_BASEADDR, DEMO_DMA_CHANNEL_0);
- }
- }
- int main(void) {
- uint32_t i = 0U;
- volatile uint32_t currentCounter = 0U;
- INPUTMUX_Init(INPUTMUX);
- /* Connect ADC FIFO flag to DMA0 Channel 0 trigger */
- INPUTMUX_EnableSignal(INPUTMUX0, kINPUTMUX_Adc0FifoARequestToDma0Ch21Ena, true);
- /*Initializing CTIMER0 M3 triggers ADC0*/
- INPUTMUX_AttachSignal(INPUTMUX,0,kINPUTMUX_Ctimer0M3ToAdc0Trigger);
- /* Board pin init */
- CLOCK_EnableClock(kCLOCK_Gpio0);
- /* Init board hardware. */
- BOARD_InitBootPins();
- BOARD_InitBootClocks();
- BOARD_InitDebugConsole();
- CTIMER0_Init();
- ADC0_Init();
- EDMA_Configuration();
- LED_RED_INIT(LOGIC_LED_OFF);
- /* Enter an infinite loop, just incrementing a counter. */
- PRINTF("DMA Transfer ADC0 START!\n\r");
- while(1) {
- /* Wait for DMA to be done */
- while (currentCounter == DMACounter)
- {
- }
- currentCounter = DMACounter;
- PRINTF("\n\r");
- for (i = 0; i < BUFFER_LENGTH; i++)
- {
- if(i==0)
- {
- PRINTF("ADC0_A0 = %d\n\r", ((destAddr[i] & 0x7FFFU) >> 3));
- }
- if(i==1)
- {
- PRINTF("ADC0_B0 = %d\n\r", ((destAddr[i] & 0x7FFFU) >> 3));
- }
- }
- PRINTF("\n\r");
- }
- return 0 ;
- }
复制代码
5.实验效果 我们将ADC0_A0(J4-2)连接到3v3、ADC0_B0(J4-4)连接到GND,硬件连接如下: 串口打印效果如下: 6.总结 在MCXN947微控制器上,通过配置CTIMER硬件来触发ADC进行多通道采样,再结合DMA传输机制,可以实现高效且精确的数据采集和处理流程。这种组合利用了CTIMER提供的可编程定时功能来周期性地启动ADC转换,同时DMA自动传输数据,减少了CPU的负担,提高了系统的整体性能。 |