当前位置:首页 > 公众号精选 > wenzi嵌入式软件
[导读]引言 ADC 的功能是将模拟信号采样得到数字信号,而有些时候,我们需要使用到定时采样,比如在计算一个采集的波形的频率的时候,我们需要精确的知道采样频率,也就是 1 s 内采集的点数,这个时候,就需要使用到定时采集。定时采样有如下三种方法: 使用定时器


引言

ADC 的功能是将模拟信号采样得到数字信号,而有些时候,我们需要使用到定时采样,比如在计算一个采集的波形的频率的时候,我们需要精确的知道采样频率,也就是 1 s 内采集的点数,这个时候,就需要使用到定时采集。定时采样有如下三种方法:

  • 使用定时器中断,每隔一段时间进行 ADC 转换,但是这样每次都必须读 ADC 的数据寄存器,非常浪费时间。

  • 把 ADC 设置成连续转换模式,同时对应的 DMA 通道开启循环模式,这样 ADC 就一直在进行数据采集然后通过 DMA 把数据搬运至内存。这样进行处理的话,需要加一个定时中断,用来读取内存中的数据。

  • 使用 ADC 的定时器触发 ADC 转换的功能,然后使用 DMA 进行数据的搬运。这样就只要设置好定时器的触发间隔,就能实现 ADC 定时采样转换的功能,然后使能 DMA 转换完成中断,这样每次转换完就会产生中断。

本文,笔者将采用第三种方法进行 AD 采集,使用 TIM 定时器触发 AD 采集,然后 DMA 搬运至内存。

ADC 简介

首先来看一下 ADC 的框图:

ADC 框图


在本文中,我们使用的是规则通道进行转换,这里要指出的一点是规则通道和注入通道两者的区别,以下是关于两种通道的说明:

  • 规则通道:我们平时使用的就是这个通道,就是规规矩矩的按照我们设定的转换顺序就行转换的通道。

  • 注入通道:注入通道可以理解为是插入,也就是插队的意思,它是一种不安分的通道。它是一种在规则通道转换的时候强行插入要进行转换的一种,它的存在就像是程序中的中断一样,换个角度说,也就是注入通道只有在规则通道存在的情况下才会存在。

说了规则通道和注入通道的区别之后,我们来看我们在本文中所用到的规则通道的触发方式。我们最为常用的一种就是软件触发,即配置到 ADC 之后,就会自动地进行转换,然后去读 ADC 的数据寄存器就可以得到 ad 转换得到的数值。还有一种方法就是外部触发,而外部触发又包括定时器触发和外部 IO 触发,在本文中,我们使用的是定时器触发,通过上述的 ADC 功能框图,我们可以知道 ADC 的定时器触发又有如下几种类型:

  • TIM1_CH1 :定时器 1 的通道 1 的 PWM 触发

  • TIM1_CH2 : 定时器 2 的通道 2 的 PWM 触发

  • TIM1_CH3: 定时器 1 的通道 3 的 PWM 触发

  • TIM2_CH2 : 定时器 2 的通道 2 的 PWM 触发

  • TIM3_TRGO: 定时器 3 触发,TRGO属于内部触发,不需要配置对应的输出IO脚.相当于是TIM3的定时器内部计数一样,只是到了一定时间就触发ADC转换,而这个触发的实现,不依赖IO口的配置.

  • TIM4_CH4 : 定时器 4 的通道 4 的 PWM 触发

定时器配置

在进行了上述简单的介绍之后,我们来具体到代码的细节来看,本文采用的是 TIM4_CH4 进行外部触发 ADC 采样。首先来看 TIM 的配置,代码如下:

void ADC1_External_T4_CC4_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    TIM_TimeBaseInitTypeDef   TIM_TimeBaseStructure;
    TIM_OCInitTypeDef         TIM_OCInitStructure;

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);

    /* Time Base configuration */
    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); 
    TIM_TimeBaseStructure.TIM_Period = 72 - 1;          
    TIM_TimeBaseStructure.TIM_Prescaler = sample_psc;       
    TIM_TimeBaseStructure.TIM_ClockDivision = 0x00;    
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

    /* TIM1 channel1 configuration in PWM mode */
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; 
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;                
    TIM_OCInitStructure.TIM_Pulse = 60
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;         
    TIM_OC4Init(TIM4, &TIM_OCInitStructure);

    TIM_CtrlPWMOutputs(TIM4, ENABLE);
    TIM_Cmd(TIM4, DISABLE);
}

在这里需要注意的是 和 sample_psc 是个变量,而这个变量可以通过调用库函数 TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC) 重新配置 TIM 所产生的 pwm 的频率,详细的原理不在这里进行赘述了,既然都能够改变 TIM 产生的 PWM 的原理,那么也就能够动态地改变 ADC 的采样频率,也就是决定 ADC 在 1 s 中能够采样多少个点,具体的原理在后续指出。还有一个需要注意的地方是 TIM_Cmd(TIM4,DISABLE),这里配置的是禁止 TIM 定时器使能,因为还有 ADC 和 DMA 还没有进行配置,因此,我们需要在 ADC 和 DMA 都配置好之后,再将 TIM4 进行使能。

DMA 配置

因为笔者所涉及到的 ADC 的具体应用是这样的,也就是通过定时器触发 ADC 采集,然后采集一定数量的点数之后,在这里笔者每个 ADC 的通道是采集了 256 个点,然后对这 256 个点进行处理,处理完毕之后,再以一定时间间隔再采集 256 个点,周而复始地进行采集和处理。并且,这里需要的是同时采集 2 个通道的数据,每个通道采集 256 个点,也就是说,我们一次性处理的是 256 * 2 = 512 个点的数据,采集完成之后,再通过 DMA 将数据其搬运至内存,因此,也就有了如下所示的 DMA 配置:

static void ADC1_DMA1_Init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    /* DMA1 Channel1 Configuration ----------------------------------------------*/
    DMA_DeInit(DMA1_Channel1);
    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address;
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ADC_ConvertedValue;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize = ADC_BUFF_LEN*2;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);

    DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);

    /* Enable DMA1 channel1 */
    DMA_Cmd(DMA1_Channel1, ENABLE);
}

代码比较直观,都是一些相关的配置,这里所要指出的一点是在第五行配置了中断服务函数 DMA1_Channel1_IRQn,具体的思路就是当采集的点数满足设定的点数时,就进入中断服务函数进行处理,在这里需要注意的是我们是从 ADC 外设将数据搬运至内存,所以DMA外设的地址是 ADC1 数据寄存器的地址,可以使用宏定义的方式定义如下:

#define ADC1_DR_Address    ((uint32_t)0x4001244C)

也可以直接取地址的方式设置,设置方式如下所示:

DMA_InitStructure.DMA_PeripheralBaseAddr = ( u32 ) ( & ( ADC_x->DR ) );

设置好外设的地址之后,我们就需要设置内存的地址,在这里,因为我们要采集两个通道的数据,并且每个通道要采集 256 个点的数据,所以在这里定义了一个如下所示的二维数组:

uint16_t ADC_ConvertedValue[ADC_BUFF_LEN][2] = {0};

上述中的 ADC_BUFF_LEN 就是一个通道要采集的点数,也就是 256 个,2所代表的就是有两个通道。在这里需要稍微思考的一下是二维数组的定义方式,为什么定义成的是 256 行 2 列 的二维数组,而不是 2 行 256 列的二维数组,我们来看一下 256 行 2 列的数组的布局如下:

二维数组内存分布


根据二维数组的大小也解释了 DMA 的 Buffer_size 是 ADC_BUFF_LEN * 2 ,同时,由于在下面设置了 内存地址是递增的,而又有两个通道,那么他的转换顺序是这样的,也就是先转换通道 1 的值存入数组,然后再转换通道 2 的数据存入数组,然后,以一定时间间隔地转换 512 次,然后发生 DMA 中断,这样也就能够说明数组为什么是定义成 256 行 2 列了。

ADC 配置

在配置了定时器和 DMA 之后,我们接下来来进行 ADC 的配置,上文中,我们配置的是使用 TIM4 的 4 通道产生 PWM 来触发 ADC 进行采集,然后设置了 DMA 来进行数据的搬运,因此, ADC 模块的配置如下所示:

void ADC_init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    ADC_InitTypeDef ADC_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

    /* ADC1 configuration ------------------------------------------------------*/
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4;
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel = 2;
    ADC_Init(ADC1, &ADC_InitStructure);

    RCC_ADCCLKConfig(RCC_PCLK2_Div6); 
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5); 
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 2, ADC_SampleTime_239Cycles5);  

    ADC_Cmd(ADC1, ENABLE);

    //外部触发
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);

    //使用DMA
    ADC_DMACmd(ADC1, ENABLE);

    //校准ADC
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));

    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));  

}

配置过程比较简单,没有什么逻辑性可言,不在这里进行赘述,这里需要指出的一点是因为我们设置的是 2 个通道的采集,所以,在这里应该使能 ADC 的扫描模式,另一方面,我们采用的是 TIM 产生 pwm 触发 adc 进行采集,所以要禁止 ADC 的连续转换模式,这就是两个需要注意的地方。

DMA 中断服务函数

在前文我们说了,我们通过 pwm 触发 ADC 采集,当采集了规定的点数之后,就会产生 DMA 中断,然后在 DMA 中断里面去处理数据,但是由于中断服务函数的要求是执行时间尽可能短,所以,我们可以在中断服务函数里置位数据采集完成标志位的方式来使得主程序进行数据处理,程序代码如下所示:

void ADC1_DMA1_IT_Hander(void)
{    
    if (DMA_GetFlagStatus(DMA1_FLAG_TC1))
    {
        DMA_ClearITPendingBit(DMA1_FLAG_TC1);
        //rt_sem_release(adc_complete_sem);
        adc_complete_flag = 1;
    }
}

上述代码中,被注释掉的部分是释放信号量,这个是使用 RTOS 是用来同步线程的一个操作,其功能与裸机的标志位是相同的。

总结

上述便是本次分享的内容,其实现的一个功能便是使用 PWM 触发 ADC 多通道采集,并使用 DMA 进行搬运,通过这样子就可以精确地控制 ADC 的采样频率,也就是控制 1 s 钟可以采集多少个点。最后,而这个采样频率就是 pwm 的频率,但是为了更加精确的计算其真实的采样频率还应该加上 ADC 通道的转换一个数据的转换时间,这样才是最为精确的采样频率。在下一篇文章中,笔者将继续介绍基于这篇文章的应用,也就是根据采样得到的点,计算波形的频谱,计算波形的频率。

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

Holtek新推出BS67F2432具备触控按键、高精准度HIRC与LCD驱动器Flash MCU。主要特色为内建高精准度4MHz HIRC振荡电路、8路触控按键及最大支持4COM×15SEG LCD驱动器。适用于触控接...

关键字: MCU LCD驱动器 定时器

Holtek持续扩展Touch A/D Flash MCU产品,新增系列成员BS86C12CA,延续优良抗干扰特性,提供丰富的定时器资源并支持LXT振荡器。引脚与BS86C08C及BS86D12C相容,具高性价比,适合需...

关键字: MCU LXT振荡器 定时器

采用MCU(微控制器单元)模块实现定时器的设计是通过利用MCU内部的定时器/计数器资源来实现的。定时器是MCU中的一个重要功能模块,它可以在特定的时间间隔内执行特定的操作,如产生中断、更新定时器值、触发其他设备等。

关键字: mcu模块 定时器

单片机的外设是指与单片机核心处理部分相连的附加硬件模块,它们能够扩展单片机的功能和能力。这些外设包括各种模块和接口,用于处理特定的任务或实现特定的功能。

关键字: 单片机 定时器

PIC单片机是基于RISC系统结构的单片机,最初的设计是支持PDP(编程数据处理器)计算机。大量的操作可以用来控制外围设备。PIC单片机比微控制器具有更快的程序执行能力。它是由微芯片技术公司于1889年发明的,是一种8位...

关键字: PIC单片机 定时器 中断

外部输入、输出继电器、内部继电器、定时器、计数器等器件的接点可多次重复使用,无需用复杂的程序结构来减少接点的使用次数。

关键字: plc编程 定时器 计数器

单片机可以通过“定时/计数模式选择位C/T”令定时/计数器工作于定时或计数模式下,也可通过“工作方式选择位M1M0”设定其工作方式。C/T和M1M0等与定时/计数器有关的位在寄存器TCON或TMOD中,见表4-8和表4-...

关键字: 寄存器 计数器 定时器

在家电产品和工业应用系统中,定时和计数是两种常用的功能,如:微波炉加热计时和流水线上产品数目统计等。MCS-51单片机内部集成的两个可编程定时/计数器T0和T1使用灵活、方便,在仪器仪表等工业产品中应用广泛。

关键字: 计数器 定时器 单片机

TMOD 的地址是 89H ,它不能位寻址 ,它里面的内容被称为方式字,设置时一次写入,其各位的定义如图 6.2 所示。高 4 位用于定时器 T1 ,低 4 位用于定时器 T0 。

关键字: 定时器 计数器 单片机

单片机定时器其实跟我们平时常说的计数器,是同一个电子元件,只不过计数器记录的是单片机外部情况,所接收的也是外部脉冲,而定时器则是由单片机自身提供的一个非常稳定的计数器,这个稳定的计数器就是单片机上连接的晶振部件。

关键字: 定时器 计数器 单片机
关闭
关闭