stm8 adc


原文链接: stm8 adc
void InitADC(void)
{
	//PD6 set as FloatingInput
	PD_DDR &= 0xBF;//1011 1111
	PD_CR1 &= 0xBF;
	PD_CR2 &= 0xBF;
	
	ADC_CR1 = 0x01;//first time open ADC£¬second time convert ADC data
	ADC_CSR = 0X06;//choose channel AIN6
	ADC_CR2 = 0X00;//left arrange (default)
}

unsigned int GetADC(void)
{
	volatile unsigned int adcValue;
	volatile unsigned char tmp = 100;
	ADC_CR1 |= 0x01;//first time open ADC
	while(tmp--);
	ADC_CR1 |= 0x01;//second time convert ADC data
	
	while((ADC_CSR & 0x80) == 0);//waitting for convert finish
	ADC_CSR &= 0xEF;//clear the flag of End Of Convert
	adcValue = (unsigned int)ADC_DRH;
	adcValue = adcValue << 2;
	adcValue |= ADC_DRL;
		
    // 默认左对齐
	return(adcValue * 5000UL / 1023UL);//10 Vdd/1023*ADC
}
[uptk/STM8S003-](https://github.com/uptk/STM8S003-)

#define	nop4()		_asm("nop");_asm("nop");_asm("nop");_asm("nop");
void ADC_init(void)                                 
{
  ADC_CR1 = 0;//fadc = fmaster/2 = 8M,single mode,off                     
  ADC_CR2 = (1<<3)|(0<<1);//right align                        
  
  ADC_CSR = 0;
// ADC_TDRL = (1<<6)|(1<<3);                                     
}

u16 ADC_get_val(u8 ch)
{
	u16 sum,min = 0xffff,max = 0,cnt;
	u8 try_cnt;
	ADC_CSR = (ADC_CSR & 0x70)|ch;
	ADC_CR1 |= 1;
	nop4();//32 cycle for establish
	nop4();
	nop4();
	nop4();
	nop4();
	nop4();
	nop4();
	nop4();
	
	for(cnt = 0,sum = 0;cnt < 10;cnt++){
		u16 tmp;
		ADC_CR1 |= 1;
		#ifndef	DEBUG
		while(!(ADC_CSR & 0x80));
		#else
		try_cnt = 100;
		while((!(ADC_CSR & 0x80))&&(try_cnt--));
		if(!try_cnt){
			//BUG_ON();
			if(!(error.flag & ERR_ADC)){
				error.flag |= ERR_ADC;
			}
		}
		#endif
		ADC_CSR &= 0x7f;
		tmp = ADC_DRL;
		tmp |= (ADC_DRH&3)<<8;
		sum += tmp;
		if(min > tmp)
			min = tmp;
		if(max < tmp)
			max = tmp;
	}
	ADC_CR1 &= ~1;
	return (sum - min - max)/8;
}

````
```cpp
\#include "IOSTM8S105K4.h"
\#define u16 unsigned int
\#define u8 unsigned char

void System_Init(void){
   CLK_SWR = 0xE1;  //选择高速内部时钟HSI为主时钟
   CLK_ICKR |= 0x01;  //高速内部HSI时钟使能while(!(CLK_ICKR&0x02)); //等待内部高速HSI就绪
   CLK_CKDIVR = 0x18;   // Fhsi = Fhsi = Fcpu = Fmaster = 2MHz
}
/*延时xms函数@ HSI 2MHz*/
void delay_ms(u16 xms){
  u16 i,j;
  for(i=xms;i>0;i--)
   for(j=330;j>0;j--)
   {
      asm("nop");
   }
}
void Gpio_Init(void){
  /*PE5设置为推挽输出*/
  PE_DDR|=0x20;
  PE_CR1|=0x20;
  PE_CR2|=0x00;  //最大为10MHz
}
/*ADC初始化*/
void Adc_Init(void){
  /*PB0 设置为悬浮输入*/
  PB_DDR &=0xFE;  //PB0输入模式
  PB_CR1 &=0xFE;  //PB0浮空输入
  PB_CR2 &=0xFE;  //PB0禁止外部中断

  /*开启ADC时钟*/
  CLK_PCKENR2 |= 0x08;  //开启ADC时钟  ADC时钟默认是开启的,可不设置
  /*设置ADC时钟、转换模式、数据对齐格式和通道*/
  ADC_CR1 = 0x00;  //ADC时钟1MHz,单次转换,关闭ADC  默认如此
  ADC_CR2 = 0x00;  //数据左对齐,禁止外部触发,禁止扫描模式   默认如此
  ADC_CSR = 0x00;  //选择通道0 AIN0   默认如此
}
/*获取10位的ADC转换结果*/
u16 Get_Adc_Result(void){
  u8 ADC_H,ADC_L;
  u16 ADC_temp;
  ADC_CR1 |= 0x01;  //启动ADC 启动ADC电源
  delay_ms(1);      //延时1ms 等待ADC启动
  ADC_CR1 |= 0x01;  //再次启动ADC转换
  while((ADC_CSR&0x80) == 0); //等待ADC转换结束
  ADC_H = ADC_DRH;  //左对齐 先读高八位
  ADC_L = ADC_DRL;  //读取低位值
  ADC_temp = (ADC_H << 8) +ADC_L; //两个八位数据合并成一个16位数据
  return ADC_temp;  //返回10位ADC转换结果
}

int main( void ){
  u16 temp;
  System_Init();
  Gpio_Init();
  Adc_Init();
  while (1)
  {
    temp = Get_Adc_Result();
    if(temp>=800)  //如果ADC转换结果大于800
    {
      PE_ODR = 0x00;  //PE5口的LED点亮
    }
    else PE_ODR = 0xFF;  //否则PE5口的LED不点亮
    delay_ms(800);  //延时800ms
  }
}

想学会如何在STM8上使用ADC这个功能,我们先得了解单片机中ADC究竟是什么。

//ADC通道号定义
#define		ADC_Chanel0			(unsigned char)0x00
#define		ADC_Chanel1			(unsigned char)0x01
#define		ADC_Chanel2			(unsigned char)0x02
#define		ADC_Chanel3			(unsigned char)0x03
#define		ADC_Chanel4			(unsigned char)0x04
#define		ADC_Chanel5			(unsigned char)0x05
#define		ADC_Chanel6			(unsigned char)0x06
#define		ADC_Chanel7			(unsigned char)0x07
 
//*************************************
// 函数名称:Init_AD
// 函数功能:对AD初始化,开启或关闭ADC电路
// 入口参数:ADC电路使能参数
// 出口参数:无
/***************************************/
void Init_AD(uint8 Mode)
{
		uint16 i  ;
 
		if(Mode == ENB)	//启动ADC电路
		{
			// F/4 转换时间分频  Fadc = Fmaster/ADC_CR1[4..6]
			// [000 F/2]  [001 F/3]  [010 F/4]
			ADC_CR1 = (ADC_CR1 & 0X8F) | 0X00 ;  	//f/2
			// 数据左对齐
			ADC_CR2 &=  ~0X08 ;
			// 启动ADC
			ADC_CR1 |= 0X01 ;
			// 等待AD电路稳定
			for(i=0;i<500;i++) ;
 
			ADC_CSR &= ~0X80 ;		//转换结束标志清0
 
			return ;
		}
		else
		{
			ADC_CR1 &= ~0X01;	//关闭ADC电路
			return ;
		}
}
 
//*************************************
// 函数名称:AdcSwitch
// 函数功能: 在单通道模式下转换某一通道的AD值
//					 在(扫描模式下)连续转化1-Chanel的AD值
// 入口参数:要转换的AD通道号
// 出口参数:无
/***************************************/
void AdcSwitch(uint8 Chanel)
{
		//选择转换通道
		ADC_CSR = (ADC_CSR & 0XF0) | Chanel;
		// 启动ADC
		ADC_CR1 |= 0X01 ;
		//等待转换
		while(!(ADC_CSR & 0X80));
		ADC_CSR &= ~0X80 ;				
 
		return ;
}
 
//*************************************
// 函数名称:Adc_Mode_Scan
// 函数功能: ADC扫描模式设置函数
// 入口参数:扫描模式开启参数
// 出口参数:无
/***************************************/
void Adc_Mode_Scan(uint8 Value)
{
	if(Value == ENB)
	{
		ADC_CR2 |=  0X02 ;
	}
	else
	{
		ADC_CR2 &=  ~0X02 ;
	}
}
 
//---应用实例-----
	//ADC初始化设置
	Init_AD(ENB);	//初始化ADC电路并开启电路
//		Adc_Mode_Scan(OFF) ;//ADC扫描模式使能
 
	//ADC 转换
	AdcSwitch(ADC_Chanel3) ;//转换通道3
	TmpH = ADC_DRH ;	//取转换结果
	TmpL = ADC_DRL ;	

ADC是模拟信号转成数值信号,单片机只能识别TTL电平,其实就是 1 或者 0 ,但是如果我们给它一个3.3V电压,单片机就无法识别,,若想使用单片机读取出来得时候,它必须将模拟量变成数字量。

了解完后,我们就开始讲解了。

编译环境:

我的编译环境是IAR,这款软件是现在STM8的主流平台,比较推荐。不过我打算等到STCubeMX更新出比较方便的版本后再去使用Keil5,因为我在用STM32的时候就是利用Keil5,的确很方便,你们也可以学着用一下。

主芯片:

我的主芯片是STM8S系列中的003,其中STM8S的003、005、和103、105,配置一样(外设和CPU频率,FLASH),在代码相同的情况下均可进行烧写。

库文件的添加:

我们的工程可以在IAR的例程中复制,操作过程:打开STM8S_StdPeriph_Lib(这是一个官方的库文件,下载IAR STM8包的时候就携带,里面有库文件和相对应的例程),将Libraries文件复制到你工程所在的文件下,并将里面的库函数添加到你的IAR工程列表当中,如图。

添加完成后,有可能你会看到一些C文件会有红色的小点报错,这是因为你选的芯片上没有该功能,你需要将其删掉才能不报错。

添加成功后,我们需要将头文件添加进来,如图。

你们也可以跟我一样,将不同功能的函数分开写成C文件和H文件,如图。

代码编写:

首先,我们需要配置一下内部晶振,并且初始化你所要使用的GPIO、串口。其次,配置ADC,最后对ADC读取函数的进行一个调用,获取ADC值,再用Uart输出。

晶振配置:

将所有的晶振幅值都写成枚举,这样的话可以直接通过在主函数调用再传参改变它的晶振大小。

 1 void Clk_Config(uint8_t SYS_CLOCK) 2{  3   switch(SYS_CLOCK) 4   { 5     case  SYS_CLOCK_2MHZ : CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8); break; 6     case  SYS_CLOCK_4MHZ : CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV4); break; 7     case  SYS_CLOCK_8MHZ : CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV2); break; 8     case  SYS_CLOCK_16MHZ: CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); break; 9     default:                                                              break;  10   }
11 }

GPIO配置:

这里将一些所要用到基础的的引角初始化,特殊的GPIO口我们就另外再说。

1 void MX_GPIO_Init(void)2 {3   //LED
4   GPIO_Init(Led_Opt_GPIO_Port,Led_Opt_Pin, GPIO_MODE_OUT_PP_HIGH_FAST);5 
6 }

Uart配置:

在STM8S003中,Uart只有 Uart1 可用,所以在这里就只使用这个。将Uart的发送、接收引角在Uart初始化函数里面初始,主要的配置参数为:115200波特率、1个停止位、无奇偶校验位、同步模式禁用、SLK PIN禁用、发送和接收都开启。

调用UART1_ITConfig( )函数令Uart开启中断。

 1 void MX_UART1_Init(void) 
 2 { 
	 3   UART1_DeInit(); 
	 4   GPIO_Init(Uart_TX_GPIO_Port, Uart_TX,GPIO_MODE_OUT_PP_HIGH_FAST); 5   GPIO_Init(Uart_RX_GPIO_Port, Uart_RX,GPIO_MODE_IN_PU_IT);   6   UART1_Init(115200,UART1_WORDLENGTH_8D,UART1_STOPBITS_1,UART1_PARITY_NO,UART1_SYNCMODE_CLOCK_DISABLE,UART1_MODE_TXRX_ENABLE); 7   UART1_ITConfig(UART1_IT_RXNE_OR,ENABLE); 8   UART1_Cmd(ENABLE);//启用串口
 9   
10   enableInterrupts(); 11 }

ADC:

STM8SF003这款芯片能用的是5个AD采样通道,分别为为AIN2~AIN6。其一个通道AIN7,但在官方手册中我没找到有对其描述的,感兴趣的朋友可以去察看芯片的英文手册进行研究,也许会找到和我不一样的结果。

在ADC头文件中,我将ADC1所有的ADC1_CHANNEL(ADC通道)都进行枚举,以方便调用。

 1 /* Enum ----------------------------------------------------------------------\*/
 2 
 3 enum ADC1_CHANNEL 4 {                                        //bit  8  7  6  5  4  3  2  1
 5   
 6     ADC1_CHANNEL2 = 0x01,                //     0  0  0  0  0  0  0  1
 7     ADC1_CHANNEL3 = 0x02,                //     0  0  0  0  0  0  1  0
 8     ADC1_CHANNEL4 = 0x04,                //     0  0  0  0  0  1  0  0
 9     ADC1_CHANNEL5 = 0x08,                //     0  0  0  0  1  0  0  0
10     ADC1_CHANNEL6 = 0x10                 //     0  0  0  1  0  0  0  0
11         
12 };  

在ADC.C文件中,我分为了多个函数,降低他们的耦合性,也方便理解。

首先是ADC中引角的初始化,将你所选通道的引角进行初始化,没有选到的就不进行初始化。


1/********************************* 2* Function Name : MX_ADC_GPIO_Init 3* Description : ADC GPIO Init 4 * Input : ADC1_CHANNEL 5 * Output : None 6 * Return : None 7 ********************************/
8
9 void MX_ADC_GPIO_Init(uint8_t ADC1_CHANNEL)10 {11 switch(ADC1_CHANNEL)12 {13 case ADC1_CHANNEL2: GPIO_Init(ADC_Opt_GPIOC_Port,ADC_channe2_Pin,GPIO_MODE_IN_PU_NO_IT);break;14 case ADC1_CHANNEL3: GPIO_Init(ADC_Opt_GPIOD_Port,ADC_channe3_Pin,GPIO_MODE_IN_PU_NO_IT);break;15 case ADC1_CHANNEL4: GPIO_Init(ADC_Opt_GPIOD_Port,ADC_channe4_Pin,GPIO_MODE_IN_PU_NO_IT);break; 16 case ADC1_CHANNEL5: GPIO_Init(ADC_Opt_GPIOD_Port,ADC_channe5_Pin,GPIO_MODE_IN_PU_NO_IT);break;17 case ADC1_CHANNEL6: GPIO_Init(ADC_Opt_GPIOD_Port,ADC_channe6_Pin,GPIO_MODE_IN_PU_NO_IT);break;18 } 19
20 }

然后是ADC1的选择通道初始化:ADC连续读取,所选的通道,二分频,外部转换触发,外部触发器不开启,数据右对齐,施密特触发,不开启。(注意:STMS8003中的串口使用了PD5和PD6,与ADC1中通道5、通道6发生冲突,故不可使用。如需使用,请将串口的TX\RX引角更改换为其它的引角。)

<pre class="hljs markdown">
 1/\*\****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\***\* 2\* Function Name  : MX_ADC1_CHANNEL_Init 3 \* Description    : ADC CHANNEL Init 4 \* Input          : ADC1_CHANNEL 5\* Output         : None 6 \* Return         : None 7 \*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\****\*/
 8 
 9 void MX_ADC1_CHANNEL_Init(uint8_t ADC1_CHANNEL)10 {11   switch(ADC1_CHANNEL)12   {13     case ADC1_CHANNEL2: 14     {15        ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_2, 16        ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, 17        ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_CHANNEL2, DISABLE);break;18     }19     case ADC1_CHANNEL3:20     {21        ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_3, 22        ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, 23        ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_CHANNEL3, DISABLE);break;24     }25     case ADC1_CHANNEL4:26     {27        ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_4, 28        ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, 29        ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_CHANNEL4, DISABLE);break;30     } 31     case ADC1_CHANNEL5:32     {33        ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_5, 34        ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, 35        ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_CHANNEL5, DISABLE);break;36     }37     case ADC1_CHANNEL6:38     {39        ADC1_Init(ADC1_CONVERSIONMODE_CONTINUOUS, ADC1_CHANNEL_6, 40        ADC1_PRESSEL_FCPU_D2, ADC1_EXTTRIG_TIM, DISABLE, 41        ADC1_ALIGN_RIGHT, ADC1_SCHMITTTRIG_CHANNEL6, DISABLE);break;42     }43   } 44 }

这里就是将所有的ADC初始化进行一个统一的一个归类。


1/********************************* 2 * Function Name : MX_ADC1_Init 3 * Description : ADC Init 4 * Input : ADC1_CHANNEL 5 * Output : None 6 * Return : None 7 ********************************/
8 void MX_ADC1_Init(uint8_t ADC1_CHANNEL) 9 { 10 //初始化GPIO
11 MX_ADC_GPIO_Init(ADC1_CHANNEL);12
13 //初始化ADC1所有寄存器
14 ADC1_DeInit();15
16 //配置ADC1寄存器中的参数
17 MX_ADC1_CHANNEL_Init(ADC1_CHANNEL);18
19 //使能ADC1
20 ADC1_Cmd(ENABLE);21
22 //ADC1转换开始
23 ADC1_StartConversion();24 }

最后就是数据获取,可以选择直接获取数据,也可以获取十次数据后取平均数。

注意:

ADC获取的值是AD值,需要将其进行代入公式中才能得到电压值。

Vin = (ADC * Vref) / 1024

VCC很明显可以使用万用表先测出来,1024是因为STM8S这款的AD是10位精度。

我这里测试的VCC是3.35V,VCC另外一个意思就是单片机的供给电源。

<pre class="hljs markdown">
 1/\*\****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\***\* 2\* Function Name  : MX_ADC1_Get_Data 3\* Description    : get VCC data 4\* Input          : None 5 \* Output         : None 6 \* Return         : fVCC 7 \*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\****\*/
 8 float MX_ADC1_Get_Data(void) 9 {10   int iADC1_Value;11   float fVCC;12   
13   //读取转换结果
14   iADC1_Value = ADC1_GetConversionValue();15   fVCC = (iADC1_Value * 3.350)/1024;16   return fVCC;17   
18 }19 
20 
21 /\*\****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\***\*22 \* Function Name  : MX_ADC1_Get_Average_Data23 \* Description    : Get VCC ten times average data. 24 \* Input          : None25 \* Output         : None26 \* Return         : fAverage_VCC27 \*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\*****\****\*/
28 float MX_ADC1_Get_Average_Data(void)29 {30   int i;31   float fAverage_VCC = 0.0;32   
33   for(i=0;i<10;i++)34   {35    fAverage_VCC += MX_ADC1_Get_Data();36   }37   fAverage_VCC /= 10;38   39   return fAverage_VCC;40   41 }

将上面需要用到的函数在主函数中调用打印即可。

测试结果:

我用的调试仪器是可调式电源,可通过旋钮控制电压的输出大小。

当没有输出电压时,打印的值为0V.

当可调式电源输出的电压值为3.3V,串口助手上打印的也是3.3V。

当可调式电源输出的电压值为24V,串口助手上打印的也是24V。

注意:请不要将24V电源直接通入单片机中,我是自己设置了一条电路进行测试的。ADC的最大输入电压是3.3V,为了安全起见,请不要超过该值。

基于STM8的ADC读取说明到此结束,感谢大家观看。

`