本帖最后由 DKENNY 于 2023-9-18 14:05 编辑
最近看了看ADC采样,想着把ADC和DMA以及TMR结合起来研究看看,达到使用定时器触发ADC采样,并使用DMA传输的功能,最近正好收到了APM32F411开发板,就想着使用这款开发板实现一下这个功能。
1. APM32F411简介 -带有FPU的32位ARM CortexM4F内核 -支持最高120MHz的工作频率 -具有512KB+NVR 32KB的flash容量,128KB的SRAM容量 -支持睡眠、停机、待机三种工作模式 -支持CF卡 、SRAM、PSRAM、NOR和NAND存储器 -具有多个通信外设以及模拟外设 更多详细资料可以访问他们的官网(https://geehy.com)。
2. APM32F411系统工作主频
在调试时,我发现这款芯片的官方SDK里面,芯片的运行主频不是120MHz,而是使用的是100MHz。当然,这个主频我们是可以直接算出来的。我们进入Debug视图,运行到main函数,打开RCM模块视图。
我们可以看到PLL1CFG寄存器中的各项值,这时,就可以参考官方手册给出的计算公式,计算出它的主频。
通过查看官方用户手册,上面有关于这部分的详细阐述,最后算得系统实际工作主频为8M*(100/4)/2= 100MHz。这对后面我配置TMR2定时触发是有一定影响的,所以在这里提一下。
3. 代码 1、定时器 这里我选择TMR2定时器的时钟更新事件触发ADC采样,也就是TMR的重装载寄存器置位1次,则触发一次ADC数据采集。
- /*
- 定时器初始化
- */
- void TMR_Init(void)
- {
- TMR_BaseConfig_T timBaseConfig;
-
- //系统工作主频为100M,由于APB1是2分频,所以时基计算如下
- timBaseConfig.period = 3; //设置定时器时基为4us,(3+1)*(99+1)/(50M*2) = 4us
- timBaseConfig.division = 99;
- timBaseConfig.clockDivision = TMR_CLOCK_DIV_1;
- timBaseConfig.countMode = TMR_COUNTER_MODE_UP;
- TMR_ConfigTimeBase(TMR2,&timBaseConfig);
-
- //选择外部触发源
- TMR_SelectOutputTrigger(TMR2, TMR_TRGO_SOURCE_UPDATE);
-
- TMR_EnableInterrupt(TMR2,TMR_INT_UPDATE);
- NVIC_EnableIRQRequest(TMR2_IRQn,0,0);
- TMR_Enable(TMR2);
- }
- /*
- TMR2中断服务函数
- */
- void TMR2_IRQHandler(void)
- {
- if (TMR_ReadIntFlag(TMR2, TMR_INT_UPDATE) != RESET)
- {
- TMR_ClearIntFlag(TMR2, TMR_INT_UPDATE);
- if(count)
- {
- count--;
- }
- else
- {
- //定时1s,打印一次数据
- count = 250000;
- if(DMA_ReadCurrentMemoryTarget(DMA2_Stream0))
- {
- printf("The memory currently accessed by DMA : 1 \r\n");
- printf("Volatage = %.2f V\r\n",(double)adc_data[1][0]/4096*3.3);
- }
- else
- {
- printf("The memory currently accessed by DMA : 0 \r\n");
- printf("Volatage = %.2f V\r\n",(double)adc_data[0][0]/4096*3.3);
- }
- APM_TINY_LEDToggle(LED2);
- }
- }
- }
2、ADC APM32F411有2个ADC源,我使用的是ADC1,并且设置的是TMR2定时器触发,我们可以看看官方手册对于ADC外部触发的描述。
- /**
- ADC初始化代码
- */
- void ADC_Init(void)
- {
- ADC_Config_T adcConfig;
- ADC_CommonConfig_T adcCommonConfig;
- ADC_Reset();
-
- /*
- ADC采样:50M,(112+12)/50M = 2.48us,2.48us进行一次ADC采样
- */
- adcCommonConfig.accessMode = ADC_ACCESS_MODE_DISABLED;//ADC不采用直接访问DMA模式
- adcCommonConfig.mode = ADC_MODE_INDEPENDENT; //ADC设置为独立模式
- adcCommonConfig.prescaler = ADC_PRESCALER_DIV2; //ADC 2分频:100M/2=50M
- adcCommonConfig.twoSampling = ADC_TWO_SAMPLING_5CYCLES; //2个采样阶段之间的延迟:只在双重或三重交错模式下使用
- ADC_CommonConfig(&adcCommonConfig);
- adcConfig.resolution = ADC_RESOLUTION_12BIT;//设置ADC分辨率
- adcConfig.scanConvMode = DISABLE;//不使能ADC循环扫描模式
- adcConfig.continuousConvMode = DISABLE;//设置ADC单次转换模式,否则除了第一次是由外部TMR触发以外,其他的都是软件触发了
- adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_RISING; //设置ADC外部上升沿触发
- adcConfig.extTrigConv = ADC_EXT_TRIG_CONV_TMR2_TRGO;//设置ADC使用TMR2触发
- adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT; //ADC数据右对齐
- adcConfig.nbrOfChannel = 1; //设置ADC转换通道个数
- ADC_Config(ADC1, &adcConfig);
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_112CYCLES);
- ADC_EnableDMA(ADC1);
- ADC_EnableDMARequest(ADC1);
- ADC_Enable(ADC1);
- }
3、DMA 我这里只用了一路ADC一个通道数据传输到DMA,也可以ADC多通道传输,只需要稍微改一下DMA程序并定义几个空间数据用来存放数据即可。 - /*DMA初始化*/
- void DMA_Configuration(void)
- {
- DMA_Config_T dmaConfig;
- DMA_Reset(DMA2_Stream0);
- dmaConfig.channel = DMA_CHANNEL_0;
- dmaConfig.peripheralBaseAddr = (uint32_t) & (ADC1->REGDATA);//配置DMA的外设基地址为ADC1数据寄存器
- dmaConfig.memoryBaseAddr = (uint32_t)adc_data[0];//配置DMA的主存基地址
- dmaConfig.dir = DMA_DIR_PERIPHERALTOMEMORY;//配置DMA传输方向:外设->主存
- dmaConfig.bufferSize = 1;
- dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;//DMA外设基地址不自增
- dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;//DMA主存基地址自增
- dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;//DMA以半字为单位进行传输
- dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
- dmaConfig.loopMode = DMA_MODE_NORMAL;//设置DMA为NORMAL模式,也就是说,当外部TMR触发ADC采样时,DMA开始传输
- dmaConfig.priority = DMA_PRIORITY_HIGH;
- dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE;
- dmaConfig.fifoThreshold = DMA_FIFOTHRESHOLD_HALFFULL;
- dmaConfig.memoryBurst = DMA_MEMORYBURST_SINGLE;
- dmaConfig.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
- DMA_Config(DMA2_Stream0, &dmaConfig);
-
- //开启DMA双缓冲模式
- DMA_ConfigBufferMode(DMA2_Stream0,(uint32_t)adc_data[1],DMA_MEMORY_0);
- DMA_EnableDoubleBufferMode(DMA2_Stream0);
- DMA_Enable(DMA2_Stream0);
- //使能DMA完成中断
- DMA_EnableInterrupt(DMA2_Stream0,DMA_INT_TCI**);
- NVIC_EnableIRQRequest(DMA2_STR0_IRQn,0,0);
- }
- /*
- DMA_Stream0中断服务函数
- */
- void DMA2_STR0_IRQHandler(void)
- {
- if(DMA_ReadIntFlag(DMA2_Stream0,DMA_INT_TCI**0) != RESET)
- {
- DMA_ClearIntFlag(DMA2_Stream0,DMA_INT_TCI**0);
- }
- }
4、main函数 - volatile uint32_t adc_data[2][1]; // 存储ADC采集的数据
- void RCM_Init(void);
- void GPIO_Init(void);
- void TMR_Init(void);
- void ADC_Init(void);
- void DMA_Configuration(void);
- volatile uint32_t count = 250000;
- /*
- 时钟初始化
- */
- void RCM_Init(void)
- {
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_DMA2 | RCM_AHB1_PERIPH_GPIOA);
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
- RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR2);
- }
- /*
- GPIO初始化
- */
- void GPIO_Init(void)
- {
- GPIO_Config_T gpioConfig;
- gpioConfig.pin = GPIO_PIN_0;
- gpioConfig.mode = GPIO_MODE_AN;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(GPIOA, &gpioConfig);
- }
- int main(void)
- {
- USART_Config_T usartConfig;
-
- usartConfig.baudRate = 115200;
- usartConfig.hardwareFlow = USART_HARDWARE_FLOW_NONE;
- usartConfig.mode = USART_MODE_TX_RX;
- usartConfig.parity = USART_PARITY_NONE;
- usartConfig.stopBits = USART_STOP_BIT_1;
- usartConfig.wordLength = USART_WORD_LEN_8B;
-
- USART_Config(USART1,&usartConfig);
- APM_TINY_COMInit(COM1,&usartConfig);
- APM_TINY_LEDInit(LED2);
- RCM_Init();
- GPIO_Init();
- TMR_Init();
- ADC_Init();
- DMA_Configuration();
- while (1)
- {
- }
- }
4. 运行效果
5. 例程源码
DMA_TMR2_ADC.zip
(734.51 KB, 下载次数: 9)
欢迎大家讨论交流。
|