历史上的今天
返回首页

历史上的今天

今天是:2024年09月16日(星期一)

2021年09月16日 | STM8单片机ADC单次扫描模式

2021-09-16 来源:eefocus

  当STM8单片机使用ADC功能读取多个通道的值时,可以使用单次模式,采样完一个通道之后,重新初始化另一个通道,然后采样,采样完成后继续重新初始化切换下一个通道。但是这样采样起来太麻烦。STM8单片机提供了一个扫描模式,可以依次按照顺序采样多个通道的值,多个通道全部采样完成后,会置位标志位,这样就可以一次性将多个通道的值全部读出来。


  其中官方文档介绍如下:

image.png?imageView2/2/w/550

  从文档中可以看出,采样都是从0通道开始的,比如想采样3个通道值,那么采样的通道号就为0—3。如果想采样3、4通道,那么也得从0通道开始,也就是说要从0通道开始扫描到4通道,就算0、1、2通道不用,它也会扫描,就这一点不好。

image.png?imageView2/2/w/550

  单次采样的时候,ADC_CSR寄存器中的通道号指的是要采样的通道号,要采样那个通道就设置为几,而在扫描模式下,这个通道号指的是要扫描的最大通道号,扫描都是从0通道开始。这里设置时要注意。


  单次扫描模式的配置其实也很简单,只是比单次模式多了一个开启扫描的设置。下面直接通过寄存器来说明单次扫描模式的设置方法。

image.png?imageView2/2/w/550

  首先看ADC_CSR寄存器,这个寄存器里面只需要设置一个通道转换位,这个通道指的是扫描的最大通道。在单次模式下这个通道指的是要读取的通道值,而在扫描模式下,这个通道指的是,最大扫描通道数。扫描都是从0通道开始。EOC位是转换结束标志位,这个在初始化的时候不用设置,只是在读取数据的时候通道判断这个位来读取。

image.png?imageView2/2/w/550

  接下来看ADC_CR1寄存器,这个寄存器要设置的只有一个,就是ADON位,用来控制ADC的转换开关,为1时启动转换功能,为0时关闭转换功能。预分频位和单次转换模式默认值都为0,刚好符合我们的需求,也就是默认2分频,单次转换模式。

image.png?imageView2/2/w/550

  接下来看ADC_CR2寄存器,这个寄存器必须设置的位其实只有一个,就是要要使用扫描模式必须设置SCAN位为1,其他位使用默认值就行。如果数据对齐方式使用左对齐的话,就不用设置。这里使用的是数据右对齐,所以需要将ALIGN位也设置为1.其他位默认为0.

image.png?imageView2/2/w/550

  最后就是这个ADC_TDR寄存器,用来禁止施密特触发器,主要是用来降低单片机功耗。当然这个寄存器不用设置也可以。如果要设置的话,使用了哪几个ADC通道,就将对应的通道位置1就行。


  下面就可以开始编写代码了:


#include "adc.h"

#include "main.h"

#include "led.h"


_Bool ADC_flag = 0;                     //ADC转换成功标志

u16 ADC_DB[10] = {0};

u16 adc_data[5] = {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 设置为悬空输入


    PC_DDR &= ~( 1 << 4 );              //PC4 设置为输入

    PC_CR1 &= ~( 1 << 4 );              //PC4设置为悬空输入

}


//设置为 单次扫描模式

//ch 为ADC通道 连续转换AIN0---AINch 通道的数据

void ADC_CH_Init( u8 ch )

{

    char l = 0;

    ADC_GPIO_Init();    

    ADC_CR1 &= ~( 7 << 4 );   //预分频 2

    ADC_CR2 &= ~( 1 << 6 );   //不使用外部触发

    //禁止 AIN2 AIN4 的施密特触发器,降低 IO 静态功耗  PD5,PD6 上的通道如果施密特方式禁用会导致串口无法收发数据!

    ADC_TDRL |= ( 1 << 2 );

    ADC_TDRL |= ( 1 << 3 );

    ADC_TDRL |= ( 1 << 4 );


    ADC_CR1 &= ~( 1 << 1 );   //单次转换

    ADC_CSR |= 0x04;          //配置通道号最大的那个

    ADC_CR2 |= ( 1 << 3 );    //右对齐


    ADC_CR1 |= ( 1 << 0 );    //开启 ADC

    ADC_CR2 |= ( 1 << 1 );    // SCAN = 1 开启扫描模式


    //当首次置位ADON位时,ADC从低功耗模式唤醒。为了启动转换必须第二次使用写指令来置位ADC_CR1寄存器的ADON位。

    for( l = 0; l < 10; l++ );  //延时,保证ADC模块的上电完成 至少7us

    ADC_CR1 |= ( 1 << 0 );      //再次将CR1寄存器的最低位置1 使能ADC 并开始转换

}


u16 ain2_val = 0,ain3_val = 0,ain4_val = 0;

//读取采样电压值

u16 ReadVol_CHx( void )

{

    u16 voltage = 0;

    u16 temph = 0;

    u8 templ = 0;

    while( 1 )

    {

        LED = !LED;             //程序运行一圈耗时 15us           

        while( ( ADC_CSR & 0x80 ) == 0 );      //等待转换结束  等待时间为 4us

        ADC_CSR &= ~( 1 << 7 );               // 转换结束标志位清零  EOC


        //读取 AIN2 的值

        templ = ADC_DB2RL;

        temph = ADC_DB2RH;

        temph = ( u16 )( templ | ( u16 )( temph << ( u16 )8 ) );

        ain2_val =  temph;

        

        //读取 AIN3 的值

        templ = ADC_DB3RL;

        temph = ADC_DB3RH;

        temph = ( u16 )( templ | ( u16 )( temph << ( u16 )8 ) );

        ain3_val =  temph;


        //读取 AIN4 的值

        templ = ADC_DB4RL;

        temph = ADC_DB4RH;

        temph = ( u16 )( templ | ( u16 )( temph << ( u16 )8 ) );

        ain4_val =  temph;

        

        ADC_CR1 |= 0x01;                        //开启一次转换

    }

    return voltage;

}


  初始化流程就按照上面分析的流程进行,在启动ADC转换的时候,需要开启两次。

image.png?imageView2/2/w/550

  系统启动后ADC默认是在低功耗模式下,第一次设置ADON位是将ADC从低功耗模式唤醒,然后需要延时1个转换周期,等待系统转换稳定。所以在初始化的时候需要设置两次ADON为1。由于这里使用的是单次触发模式,所以每次转换完成之后,ADC默认就关闭了,如果要继续转换,就需要手动将ADON位设置为1.


  也可以使用中断的方式来进行读取数据。


#include "adc.h"

#include "main.h"

#include "led.h"


_Bool ADC_flag = 0;                     //ADC转换成功标志


u16 ADC_DB[10] = {0};

u16 adc_data[5] = {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 设置为悬空输入


    PC_DDR &= ~( 1 << 4 );              //PC4 设置为输入

    PC_CR1 &= ~( 1 << 4 );              //PC4设置为悬空输入

}


//设置为 单次扫描模式

//ch 为ADC通道 连续转换AIN0---AINch 通道的数据

void ADC_CH_Init( u8 ch )

{

    char l = 0;

    ADC_GPIO_Init();


    ADC_CR1 &= ~( 7 << 4 );   //预分频 2

    ADC_CR2 &= ~( 1 << 6 );   //不使用外部触发

    //禁止 AIN2 AIN4 的施密特触发器,降低 IO 静态功耗  PD5,PD6 上的通道如果施密特方式禁用会导致串口无法收发数据!

    ADC_TDRL |= ( 1 << 2 );

    ADC_TDRL |= ( 1 << 4 );


    ADC_CR1 &= ~( 1 << 1 );   //单次转换

    ADC_CSR |= 0x04;          //配置通道号最大的那个

    ADC_CR2 |= ( 1 << 3 );    //右对齐


    ADC_CR1 |= ( 1 << 0 );    //开启 ADC

    ADC_CR2 |= ( 1 << 1 );    // SCAN = 1 开启扫描模式


    ADC_CSR |= ( 1 << 5 );    //EOCIE 使能转换结束中断


    //当首次置位ADON位时,ADC从低功耗模式唤醒。为了启动转换必须第二次使用写指令来置位ADC_CR1寄存器的ADON位。

    for( l = 0; l < 10; l++ );  //延时,保证ADC模块的上电完成 至少7us

    ADC_CR1 |= ( 1 << 0 );      //再次将CR1寄存器的最低位置1 使能ADC 并开始转换


}

u16 ain2_val = 0, ain3_val = 0, ain4_val = 0;

u16 temph = 0;

u8 templ = 0;

//读取采样电压值

u16 ReadVol_CHx( void )

{

    u16 voltage = 0;

    if( ADC_flag == 1 )

    {

        ADC_flag = 0;

        //单通道扫描模式,转换结果存储在 ADC_DBxR 寄存器中

        //读取 AIN2 的值

        templ = ADC_DB2RL;

        temph = ADC_DB2RH;

        temph = ( u16 )( templ | ( u16 )( temph << ( u16 )8 ) );

        ain2_val =  temph;


        //读取 AIN3 的值

        templ = ADC_DB3RL;

        temph = ADC_DB3RH;

        temph = ( u16 )( templ | ( u16 )( temph << ( u16 )8 ) );

        ain3_val =  temph;


        //读取 AIN4 的值

        templ = ADC_DB4RL;

        temph = ADC_DB4RH;

        temph = ( u16 )( templ | ( u16 )( temph << ( u16 )8 ) );

        ain4_val =  temph;


        ADC_CR1 |= 0x01;                        //开启一次转换

    }

    return voltage;

}

//AD中断服务函数 中断号22

#pragma vector = 24                     // IAR中的中断号,要在STVD中的中断号上加2

__interrupt void ADC_Handle( void )

{

    ADC_CSR &= ~( 1 << 7 );               // 转换结束标志位清零  EOC

    ADC_flag = 1;                       // ADC中断标志 置1

}


  中断的开启就是将ADC_CSR寄存器的EOCIE位设置为1,然后当所有的通道转换完成之后,就会产生一次中断,在中断中读取数据。


  这里要注意的地方是在扫描模式时,采样的数据结果不是存放在ADC_DR寄存器中,而是存放在ADC_DBxR寄存器中,通道几就存放在对应的ADC_DBxR寄存器中,这个寄存器分为高位和低位两个。比如通道2的数据,就存放在ADC_DB2RL和ADC_DB2RH寄存器中,在读取数据的时候也要注意,如果数据是左对齐必须先读高8位,再读低位。如果数据是右对齐必须先读低8位,在读高8位。

image.png?imageView2/2/w/550

image.png?imageView2/2/w/550

  数据对齐在官方手册中有详细的说明,在使用的时候要注意这一点。


推荐阅读

史海拾趣

问答坊 | AI 解惑

招聘dsp642兼职,工作地点:北京

招聘dsp642兼职,工作地点:北京 要求熟悉dm642 ,算法优化。 联系方式:qq 455413023…

查看全部问答>

散分 高手帮帮忙

单片机是89C52系列,晶振11.0592。 要求具有以下功能: 1、双串口(其中一路为软件模拟) 2、24路输出和24路输入; 3、两路pwm输出 4、单总线 5、IIC总线 6、随时可能要扩展的其它功能。 这些功能都集中在一块单片机中,如何让它们能够稳定 ...…

查看全部问答>

笨蛋的提问,真诚求教(嵌入式)?

[color=#000000][b]我是一个很笨的大一的专科生,学的专业是嵌入式,现在很迷茫,不知道该怎么发展?我的理想是嵌入式系统设计师。 1.我不是很了解这个行业的特点,发展要求,职业要求,未来趋势,市场需求? 2.嵌入式系统设计是偏软还是偏硬? ...…

查看全部问答>

嵌入式万能驱动

为什么在wince嵌入式系统下不能像在PC机上那样写摄像头的万能驱动?…

查看全部问答>

ShellExecuteEx 打开bat文件一闪

= _T(\"searchlist.bat\");          //执行DOS命令                                TCHAR szAppPath[MAX_PATH] = _T(\"\");& ...…

查看全部问答>

纳米测量与DUT的电连接的秘密

为了与纳米器件[1]或者元件实现电接触,必需提供相应的夹具、显微镜和探针系统[2]。当今的纳米研究者正在使用诸如原子力显微镜、扫描电子显微镜和聚焦离子束工具等手段来实现器件的可视化、对其执行机械测量并进行I-V特性测量[3]。要实现微米和 ...…

查看全部问答>

cpld输出频率可变的方波问题

菜鸟想用cpld实现输出频率可调的方波    外部用个电位器加个a/d  通过调节电位器能实现么 ?    有什么好的方法啊    …

查看全部问答>

altium designer的快捷键

1. PCB设计快捷键(单次按键)单次按键是指按下该键并放开。 1-01 * 在PCB电气层之间切换(小键盘上的*)。在交互布线的过程中,按此键则换层并自动添加过孔。这很常用。 1-02 Tab键 在交互布线或放置元件、过孔等对象的过程中修改对象属性。例如 ...…

查看全部问答>