在使用STM8单片机的ADC功能时,读取ADC数据时一般有两种方式,一种是通常不断地读取采样标志位,来判断ADC采样是否结束,一种是通过中断的方式来通知系统采样是否结束。
有时候采样ADC数据的时候,需要按照一定的时间间隔,定点的去采样数据。一般使用的方式就是通过定时器定时,然后在定时中断函数中再去读取ADC采样的数据。但是这种方式采样的时间是不固定的,比如进入定时器中断后,ADC采样刚结束,就可以直接使用当前采样到的数据。但是如果运气不好的话,进入定时中断后,ADC采样刚开始,那么此时就需要等到ADC采样结束后,才能使用数据。这样就好导致每次读取ADC数据时还会有随机的一段等到ADC数据的延时。
通常查阅STM8单片机的手册发现,ADC采样可以通过定时器来触发。
通过定时器来触发ADC采样时,定时器的定时时间是固定的,采样时间也是固定的,这样采样数据的间隔也就是固定的。这样通过定时器来触发ADC的采样时间,就能完全保证每次读取ADC采样数据的时间间隔都是一样的,从而避免了数据的误差。
数据手册中对于开启ADC触发功能描述如下:
对于如何通过代码来设置ADC触发,官方也没有详细的说明,在网上也没有找到相关例程。所以只能自己摸索,还好通过自己的一番摸索,成功的通过定时器的TRGO事件触发了ADC的启动。
关于ADC相关寄存器的设置,基本就是上面说的6条。接下来需要设置的就是定时器的相关寄存器。
关于定时器只需要设置CR2寄存器中的 MMS位就可以了。
接下来就通过代码来实现。
#include "adc.h"
#include "main.h"
#include "led.h"
u16 DATAH = 0; //ADC转换值高8位
u16 DATAL = 0; //ADC转换值低8位
_Bool ADC_flag = 0; //ADC转换成功标志
u16 adc_cnt = 0;
//AD通道引脚初始化
void ADC_GPIO_Init( void )
{
PD_DDR &= ~( 1 << 2 ); //PD2 设置为输入
PD_CR1 &= ~( 1 << 2 ); //PD2 设置为悬空输入
PD_DDR &= ~( 1 << 3 ); //PD3 设置为输入
PD_CR1 &= ~( 1 << 3 ); //PD3 设置为悬空输入
}
//ch 为单片机的ADC 通道
//ADC输入通道初始化入口参数表示通道选择
void ADC_CH_Init( u8 ch )
{
char l = 0;
ADC_CR1 = 0x00; //fADC = fMASTER/2, 8Mhz 单次转换,禁止转换
ADC_CR2 = 0x00; //默认左对齐 读数据时先读高在读低
ADC_CR2 |= ( 1 << 6 ); //外部触发使能
ADC_CSR |= ch; //控制状态寄存器 选择要 AD输入通道 如:PD2(AIN3)
ADC_TDRL = ( 1 << ch ); //禁止相应通道 施密特触发功能 1左移ch位
ADC_CR1 |= 0x01; //使能ADC并开始转换
ADC_CSR |= ( 1 << 5 ); //EOCIE 使能转换结束中断 EOC中断使能
for( l = 0; l < 100; l++ ); //延时,保证ADC模块的上电完成 至少7us
ADC_CR1 = ADC_CR1 | 0x01; //再次将CR1寄存器的最低位置1 使能ADC 并开始转换
}
u16 value = 0;
//AD中断服务函数 中断号22
#pragma vector = 24 // IAR中的中断号,要在STVD中的中断号上加2
__interrupt void ADC_Handle( void )
{
ADC_CSR &= ~0x80; // 转换结束标志位清零 EOC
//默认左对齐 读数据时先读高高8位 再读低8位
DATAH = ADC_DRH; // 读出ADC结果的高8位
DATAL = ADC_DRL; // 读出ADC结果的低8位
ADC_flag = 1; // ADC中断标志 置1
value = ( DATAH << 2 ) + DATAL ; //得到十位精度的数据 0--1024
adc_cnt++;
LED = !LED;
}
在ADC代码中,相比普通的ADC初始化方式,这里只需要增加一句对ADC_CR2寄存器的设置。
ADC_CR2 |= ( 1 << 6 ); //外部触发使能
在ADC_CR2寄存器中 使能外部触发转换功能,设置外部触发事件为 内部定时器1 TRG事件。
这里ADC使用的是单次触发模式,开启外部触发功能,开启ADC转换中断,当ADC转换完成之后,就会进入到ADC中断中,在中断中读取采样的数据,然后翻转LED的状态,通过示波器观察LED引脚电平的变化,就可以知道ADC中断进入的频率了。
接下来编写定时器初始化代码。
unsigned long time_cnt = 0;
// 使用 定时器触发 ADC采样
void tim1_init( void )
{
TIM1_ARRH = ( unsigned char )( 1000 >> 8 ); //定时1ms
TIM1_ARRL = ( unsigned char )( 1000 );
TIM1_PSCRH = ( unsigned char )( 0x0F >> 8 ); // 16M / (1+15) =1M
TIM1_PSCRL = ( unsigned char )( 0x0F );
TIM1_RCR = 0x00; //重复计数器值
TIM1_SR1 = ( ~0x01 ); //清除更新中断标志
TIM1_CR2 |= ( 2 << 4 ); //使能信号,用于触发输出(TRGO)
TIM1_CR1 |= 0x01; //使能计数器
TIM1_IER |= 0x01; //更新中断使能
}
#pragma vector = 13 //IAR中的中断号,要在STVD中的中断号上加2
__interrupt void Timer1_Handle( void ) //1ms 定时中断
{
TIM1_SR1 = ( ~0x01 ); //清除更新中断标志
time_cnt++;
}
定时器的初始化代码,也比正常情况下初始化代码多了一行。
TIM1_CR2 |= ( 2 << 4 ); //使能信号,用于触发输出(TRGO)
用来开启定时的的TRG功能。
经过测试,这里定时器CR2寄存器中的值 只能设置为 010 或者 011,设置为其他值时,不能触发ADC采样。最开始测试的时候按照芯片资料上这个说明,MMS的值设置的是 001,ADC总是触发不了,还以为是方法的问题,最后才发现是MMS值设置的问题。
ADC和定时器初始化代码设置完成之后,接下来在主函数中初始化这两个函数就行了,按照资料上说的,首先初始化完ADC之后,再初始化定时器。
void main( void )
{
__asm( "sim" ); //禁止中断
SysClkInit();
delay_init( 16 );
LED_GPIO_Init();
ADC_GPIO_Init();
ADC_CH_Init(3);
tim1_init();
__asm( "rim" ); //开启中断
while( 1 )
{
}
}
接下来运行程序。
分别在ADC中断中和定时器中断中用一个变量来统计中断执行的次数,通过变量变量观察窗口可以看到,ADC中断的次数比定时器中断的次数多了1次。这是因为ADC在初始化的时候,已经运行了一次。
然后通过示波器观察LED口的电平。
定时器的定时时间是1ms,LED的高低电平时间也是1ms,说明通过定时器触发ADC采样功能是正常运行的。
为了减小系统进入中断的次数,可以将定时器的中断功能关闭掉。定时器中断功能关闭后,ADC的触发功能依然可以正常使用。
这样只需要开启一个ADC中断,再加上定时器的TRG触发功能后,就可以实现ADC定时采样的功能了。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://hxydj.blog.csdn.net/article/details/121905362
内容来源于网络,如有侵权,请联系作者删除!