STM32单片机定时数据采集系统参数设置与编程

2022-04-25 07:20魏永生
电子元器件与信息技术 2022年3期
关键词:寄存器计数器总线

魏永生

(吉林通用航空职业技术学院,吉林 吉林 132000)

0 引言

制作一个模拟量定时采集转换数据采集卡,其采集电压在5V以内,要求定时控制采集,根据控制硬件要求,单片机芯片选用STM32F103C8T6芯片,该芯片具有ARM32-bit Cortex-M3内核,2个12bit的ADC,型号中的C代表引脚数目是48脚,8代表闪存存储容量为64K,T代表封装,6代表工业级,工作温度最低为-40℃,可在北方冬季野外使用。STM32系列的单片机适用于C语言进行编程烧录[1],所以本次程序设计采用C语言编程,编译环境为keil5编程软件,下载器为ST-LINK。

1 相关IO口设置

STM32的ADC输入通道和AVR类似,采用PA口引脚作为ADC输入端,此次数据采集的接口ADC通道0在PA0上,要设置PA0为模拟输入模式,这里先完成STM32 I/O口的初始化与编程,以设置PA0为模拟输入模为例,程序为:

GPIO_InitTypeDef GPIO_InitStructure;

//定义一个结构体变量

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;//赋值0X0,模拟输入模式

第一条语句定义了一个变量名为G P I O _InitStructure的变量,是一个私有数据声明,变量定义了之后,可将某一GPIO口的对应位初始化,之所以这么定义,是因为STM32的初始化函数多采用结构体形式,C语言中,typedef是用于定义新的类型名,在编译时可以用新定义的类型名来代替已有的类型名,在STM32的头文件中已有的GPIO初始化程序段:

Typedef struct{GPIOSpeed_TypeDef GPIO_Speed;GPIOMode_TypeDefGPIO_Mode;}

GPIO_InitTypeDef;

上面的初始化IO口函数中,用typedef定义了一个GPIO_InitTypeDef类型名的结构体,所以在编程时可以在主函数开头用这个结构体再定义一个变量。初始化定义后还要通过

GPIO_Init(GPIOA , &GPIO_InitStructure)程序将结构体变量地址以实参的形式送给形参

GPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct),其中* GPIO_InitStruct是指向结构GPIO_InitTypeDef的指针。同样,ADC的定时触发还要设置TIM2的CH2通道为复用推挽输出,此时对应的IO位的输出数据不是由GPIO的输出数据寄存器ODR提供,而是由对应的功能模块来提供数据进行推挽输出。

STM32的单片机引脚允许输入电压为3.3V,对于要采集的最大值为5V的电压模拟量要通过两个电阻串联分压,这里采用一个100K精密可调电位器来完成电压变换。

2 定时器与ADC的时钟计算

要继续编程,还要清楚单片机时钟源,而STM32的5个时钟从来源可分为内部RC振荡器和石英晶体振荡器两类。石英晶体能够产生一种稳定的正弦波,但频率比较低,为了获得较高频率,STM32单片机采用了PLL锁相环倍频输出,PLL一般选高速外部时钟HSE作为时钟源,连接关系如图1所示,当HSE为8M晶振时,通过设置PLL的倍频因子为9,得到72MHz官方推荐的稳定运行PLLCLK时钟,当系统时钟SYSCLK通过时钟监视系统选择PLLCLK时钟时,SYSCLK就为稳定的72MHz。接下来AHB总线(advanced high performance bus)的分频器将系统时钟SYSCLK分频后得到AHB总线时钟,一般设置分频系数为1,AHB总线时钟也是72MHz。AHB总线时钟送给5大模块使用,本次项目涉及的是外围总线APB(advanced peripheral bus)时钟,其按外设使用速度又分为APB1和APB2两个外设总线时钟。

图1 STM32 单片机外设时钟源示意图

如图1所示,高速设备使用的时钟PCLK2来自APB2分频器,主要供给UART1、SPI1、Timer1、ADC1、ADC2、PA~PE普通IO口等,分频因子一般设置为1分频,即PCLK2等于系统时钟72M。低速外设使用的时钟为PCLK1,是由APB1预分频2分频得到的低速总线时钟,所以最高为36M,供给通用定时器、CAN、USB、I2C1、I2C2、UART2、3等,模拟接口PA0的时钟来自于APB2,时钟是通过开关电路供给的,开启接口A时钟程序程如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟

定时采集要用到定时器,STM32F103C8T6是中容量的芯片,有1个高级16位定时器TIME1和3个16位通用定时器 TIME2~TIME4。通用定时器主要由16位计数器和与其相关的自动装载寄存器、16位预分频器和4个独立通道构成。计数器可以向上计数、向下计数或者向上向下双向计数。如图1所示,通用定时器的内部时钟CK_INT(Clock Internal)不是直接来自APB1,而是来自于APB1的后一级的一个倍频器,如TIM 2MUL,当APB1的预分频系数为1时,这个倍频器不起作用,定时器的时钟频率等于APB1的频率;当APB1的预分频系数为其他数值(即预分频系数为2、4、8或16)时,这个倍频器起作用,定时器的时钟频率等于APB1的频率两倍[2]。例如AHB总线时钟为72M时,APB1 总线时钟为36M,那么TIME2的内部时钟CK_INT就是APB1的2倍频,即72M。此时,通用定时器的内部时钟频率等于系统时钟频率。

本项目采用TIME2定时器,由于 TIM2 是挂载在 APB1之下,APB1总线时钟经TIM2MUL倍频后还要通过RCC开关控制送到TIM2,成为TIM2内部时钟CK_INT,所以我们通过APB1 总线下的使能函数来使能TIM2时钟,程序如下:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //时钟使能

定时器内部时钟CK_INT在定时器内部还要经过PSC预分频器,PSC 预分频器输出的是最终的时基时钟CK_CNT,用来驱动计数器计数。PSC是一个16 位的预分频器,可以对定时器时钟 CK_INT 进行 1~65536 之间的任何一个数进行分频。预分频器工作原理其实是个计数器,其输入的定时器时钟脉冲每触发一次,预分频器计数器值加1,直到达到预分频器的设定值,本系统中,通用定时器选择TIM2,由TIM2预分频器对内部的时钟CK_INT进行向上加计数,内部的时钟CK_INT的频率在上面已经分析过了,等于系统时钟频率72MHz,要得到10kHz的脉冲需要72MHz/7200,一个脉冲周期T是0.1mS,那么是不是就设置定时器的预分频器PSC=7200呢?当然不是,因为计数从0-7200需要7200个脉冲,7200-0还要再计一个脉冲计数器才归零,这样加起来就是7201个脉冲,所以分频值=PSC+1。分频后时基脉冲频率是

CK_CNT=TIM2CLK/(PSC+1)=72M/7199=10kHz,其倒数即为周期,值为0.1mS,编写TIM2初始程序可以调用子程序TIM2_Int_Init(9999,7199)赋值,也可以直接使用赋值语句:

TIM_TimeBaseStructure.TIM_Prescaler =7199;

//送给计数器的时钟频率10kHz

TIM_TimeBaseStructure.TIM_Period = 9999; //ARR初值,一周期是10000个脉冲

第二条程序中的9999是计数值,累计起来就是一个计数周期的时间。这里通用定时TIM2采用向上计数模式,最大计数值为 65535。在向上计数模式时,自动重载寄存器TIM2_ARR值来设置计数器上限值,自动重载寄存器有两个,一个是用来存放ARR值,我们把它叫作TIM2_ARR,另一个是用来和计数器CNT比较,我们把它叫作实际自动重装载寄存器,若计数器CNT值达到实际自动重装载寄存器的值时产生更新事件,自动把自动重载寄存器TIM2_ARR值装到实际的自动重装载寄存器,即使TIM2_ARR的值不变也要重装,计数器也溢出清零从头开始计数。定时器的定时时间等于计数器的一个脉冲周期乘以脉冲数,脉冲数也就是ARR的值。计数器在CK_CNT的驱动下,计一个数的时间则是CK_CNT的倒数,

T=1/(CK_CNT/(PSC+1))=(PSC+1)/CK_CNT,本次编程中一个脉冲周期T=7200/72M=0.1mS,要产生一次中断的时间是1s时,ARR初值=10000-1=9999,因为到9999还需要一个脉冲回零,一共是10000个脉冲,由此我们得到了定时时间的计算公式:

Tout=((ARR+1)*(PSC+1 ))/CK_CNT[3],其中CK_CNT是定时器时钟频率。

STM32103C8包含2个12位的ADC转换器,把VREF+和VDDA接3V3,一般把VSSA和VREF-接地,ADC转换所需的输入时钟ADC_CLK来自APB2总线的72M时钟,那么APB2总线的72M时钟要经过ADC_PSC分频,分频因子可以是2/4/6/8分频,由于受最小转换时间限制,ADC时钟频率最大不能超过14M,分频因子要大于6才行,72M/6得到12M的ADC时钟ADC_CLK,程序如下:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1|RCC_APB2Periph_GPROA,ENABLE);//使能ADC1和通道时钟。

RCC_ADCCLKConfig(RCC_PCLK2_Div6);//设置ADC分频因子为6,

ADC 使用若干个 ADC_CLK 周期对输入的电压进行采样,每个通道可以分别用不同的时间采样。其中采样周期最小是 1.5 个,即如果我们要达到最快的采样,那么应该设置采样周期为 1.5 个周期,这里说的周期就是 1/ADC_CLK。

因单片机ADC的转换时间要1μs以上,所以最大转换速率是1MHz,不同的程序中,转换时间是不同的,只要大于1μs就可以,转换时间不同主要是设置的采样周期不同,也就是说ADC的转换时间和采样时间有关,转换时间Tconv=采样时间+12.5个周期[3]。当ADCLK=14MHz(最高)时,采样时间设置为1.5周期(最快),那么总的转换时间(最短)Tconv=1.5周期+12.5周期=14周期=1μs。其中12.5个周期是采集12位AD时间是固定的。若PCLK2=72M,经过ADC预分频器能分频到最大的时钟是12M,采样周期设置为最慢239.5个周期,ADC周期是239.5+12.5个周期,算出最短的转换时间为21μs,也就是ADC得到一次转换结果需要耗时21μs。

3 ADC定时触发

STM32的 ADC常规通道有6个外部触发信号,外部是相对ADC而言,这6个触发信号有5个来自芯片内部,包括TIM1的CC1-CC3和TM2的CC2事件等。这里选用TIM2的CC2来触发ADC,TIM2首先要配置为PWM比较输出模式才能生效,TIM2的CH2通道便成了ADC规则通道的触发源,特别要强调的是需要每次比较匹配时,在TIM2_CH2上产生一次上升沿,当TIM2_CNT≦TIM2_CCR2时,通道CH2为低电平,TIM2_CNT≥TIM2_CCR2时,通道CH2为高电平。

TIM_OCInitStructure.TIM_Pulse = 5000;//设置TIM2_CCR2的值

上一程序也是定时器PWM的初始化程序,第一条语句是设置PWM比较寄存器的值为5000,在第5000个脉冲时定时器CH2通道进行电平变换,产生脉冲,由于PWM模式1和模式2的区别是输出电平极性相反,PWM1在向上计数时,TIM2_CNTTIM2_CCR2时通道2为无效电平,否则为有效电平,这里采用了PWM1模式,PWM1的有效电平是由下面语句决定,

TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_Low;//有效电平为低电平

CC2P输入/捕获2输出极性( output polarity),OC2通道配置为输出:0对应OC2高电平有效,1对应OC2低电平有效,TIM_OCPolarity_Low是低电平有效,所以CC2P值为1,要是PWM2模式则相反。

STM32的ADC转换有规则通道和注入通道两种通道,注入通道可以抢占式地打断规则通道的采样,和中断类似,执行注入通道采样后,再执行之前的规则通道采样。在使用ADC的时候需要配置几个参数,一是工作模式ADC_Mode,这里设置为独立模式,在这个模式下,双ADC不能同步,每个ADC接口独立工作。扫描模式ADC_ScanConvMode,这里只是用了一个通道,DISABLE就可以了,如果使用了多个通道的话,则必须将其设置为ENABLE。ADC连续转换模式,ADC_ContinuousConvMode,这里因为是定时采集,所以设置为DISABLE,即单次转换,只转换一次数据就停止,由定时器再次触发转换。ADC_ExternalTrigConv 是外部触发模式设置,ADC_ExternalTrigConv_T2_CC2设置选择外部触发模式中的定时器2通道输出触发。ADC_DataAlign_Right设置为右对齐方式,这样处理从低位开始传输的数据会比较方便,由于数据采集只有一个通道,所以ADC_NbrOfChannel 值为1。下面是ADC数据转换的初始化程序:

上一初始化程序中的ADC_Cmd(ADC1,ENABLE)语句执行后使ADC_CR2寄存器的ADON位为1,ADON是开/关A/D转换器,当该位为1时,写入1将启动转换。

外部触发允许后,产生对应的外部触发脉冲,就会开始ADC1的规则通道转换启动功能。while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC))是等待转换结束,ADC状态寄存器ADC_SR的第1位EOC是转换结束位,转换结束后EOC=1,我们可查询该位来判断转换结束,该位不用软件清零,当从ADC_DR读取数据后,该位自动清0。

读出ADC_DR数据的程序可以用赋值语句data1=ADC_GetConversionValue(ADC1);

由于是12位ADC,所以读取的值最大是4095,还要进行运算处理。

4 项目调试

ADC的输入电压范围设定在0~3.3V,因为ADC是12位的,数据采用右对齐方式时,12位满量程对应的数字值0xFFF即4095,此时对应的输入电平并非AD的参考电压值VREF,而是参考电压值减去一个LSB,1LSB=VREF+/4096。数值0对应的就是0V。如果转换后的数值X对应的模拟电压为Y,那么会有4096/3.3=X/Y,Y=(3.3*X)/4096。

如果说编程是理论基础,程序调试是理实结合的关键环节,好的调试方案可以起到事半功倍的效果,该项目采用三步走,先调试TIM2通道2的PWM输出,调试时注意CCR2给定值要在定时器ARR范围内,为了便于观测占空比,PWM频率可临时调高一些,该项目设置CCR2为ARR的1/2,占空比为50%,便于用示波器测量比较,之后再恢复1Hz,用万用表或示波器测量,显示电路调试时可先用一个整数测试。最后的是ADC采集数据调试,可测量3.3V,显示采集到的4095说明ADC电路正常工作,再调试计算电压值程序,ADC调试的前提是要保证硬件芯片外围的ADC电压正确,再逐条从ADC初始化和读取程序上查找问题,在ADC查询等待时可把显示子程序放入等待语句中执行,防止数码管黑屏。

5 总结

通过数据采集系统的编程和调试,深切体会到STM32单片机系统时钟源分配在定时数据采集项目设计和编程上的重要性,它关系到各部分的参数设置和使用。项目采用外部定时器触发ADC采集到的数据时间间隔准确,定时和数据采集都由单片机硬件完成,软件只需初始化相关硬件即可,运行中不过多占用软件资源、没有中断,高效可靠。该项目探索了STM32F103C8单片机外部定时器触发ADC转换的条件和实现方法,总结起来就是先要将定时器配置为比较输出PWM模式,OC通道复用推挽输出,TIM2_CH2一定是上升沿才能实现预定要求触发。TIM2_CH2输出的电压信号内部接至ADC电路,不需要连线,但TIM2_CH2的PA1引脚可作为调试的测试点,调试PWM功能。ADC初始化很重要,要设置ADC_ExternalTrigConvCmd(ADC1,E N A B L E)外部触发启动功能,此时就不需要A D C_SoftwareStartConvCmd(ADC1, DISABLE)软件启动语句了,在查询等待时可把显示子程序放入等待语句中执行,防止数码管黑屏,这个项目通过ADC外部触发功能实现了高精度的模拟量定时采集和转换,是一次STM32单片机定时、PMW和ADC功能的实践探索和综合应用。

猜你喜欢
寄存器计数器总线
采用虚拟计数器的电子式膜式燃气表
关于CAN总线的地铁屏蔽门控制思路论述
飞思卡尔单片机脉宽调制模块用法研究
移位寄存器及算术运算应用
基于Multisim10.1的任意进制计数器的设计与实现
数字电路环境下汽车控制电路信号设计
SR620型与53230A型计数器的性能测试
算盘是个“小气鬼”
Q&A热线
PCI9030及其PCI总线接口电路设计