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读取说明到此结束,感谢大家观看。