本帖最后由 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)
欢迎大家讨论交流。
|
不错不错,学习了