概述
基于CC2530-ADC采集特别坑
- 开头
- 什么是ADC(简单讲 模拟量--> 数字量)
- CC2530的A/D转换位数(注意这里)
- 初始化ADC相关寄存器
- 功能部分
- 处理AD部分
开头
给定自己实现一个功能,Zigbee +光敏二极管 采集(ADC值 和 电压值)数据发送到串口。
什么是ADC(简单讲 模拟量–> 数字量)
将时间上连续变化的模拟量转化为脉冲有无的数字 量,这一过程叫数字化,实现数字化的关键设备是ADC。
ADC:数模转换器,将时间和幅值连续的模拟量转化为时间和幅值离散的数字量,A/D转换一般要经过采样、保持、量化、编码4个过程。
CC2530的A/D转换位数(注意这里)
TI提供的CC2530的A/D转换位数是8,10,12,14, 但转换的实际有效位ENOB只有7,9,10,12(不包括: 符号位、最后1位精度损失位)。
数字转换结果可以获得,且结果总是驻留在ADCH和ADCL寄存器组合的MSB(高位)段
CC2530内部ADC各精度对应位(没问题)
在此我们选用实际有效12位
下图是从CC2530的中文手册里截出来,细品,您细品,是不是数据手册表达得有点问题(红色部分是我修正的内容),
初始化ADC相关寄存器
我们这里以 光敏二极管 为例子,采集ADC引脚P0^0,12位精度
1.设置IO口
P0^0 设置为 外设功能、输入引脚、模拟引脚
2.置参考电压,分辨率 通道 启动转换
通道0 512抽取率(12位精度) 参考电压AVDD5(3.3V)
void initial_AD()
{
//设置IO口
P0SEL|=(1<<0); //P0^0外设引脚
P0DIR&=~(1<<0); //P0^0为输入引脚
APCFG|=0x01; //P0^0模拟引脚
//ADCIF = 0; //清除中断标志位
//设置参考电压,分辨率 通道 启动转换
ADCCON3=0xB0; //1011 0000 通道0 512抽取率 参考电压AVDD5,3.3V
}
功能部分
1.判断是否转换完成
2.获取AD值
3.处理AD值
处理AD部分
1. 例程项目(带错误)
uint getTemperature(void){
unsigned int value; //存储AD值
AdcValue = 0;
ADCCON3|=0x3E; // 使用1.25V内部电压,12位分辨率,AD源为:片内温度传感器
ADCCON1|=0x40; //开启单通道ADC
while(!(ADCCON1&0x80)); //等待AD转换完成
value = ADCL >> 2; //ADCL寄存器低2位无效
value |= (((uint)ADCH) << 6);
return value*0.0629-303.3; //根据AD值,计算出实际的温度
}
ADCL和ADCH都是8位,
按照上面算法计算12位分辨率得出 Value 的值是 2^13=8192 比正常值大了一倍。()
错误在 ADCL应该往右移3位,这里却少移1位,导致后面ADCH的位数只能整体向左挪一位。
2. 书本(带错误)
可怕的是书本也是按照这种算法写,得出的值大一倍。
3. 博主(验证过程)
首先让光敏二极管尽量接近最大值,直接把光敏二极管短接了
从上串口调试助手中能看到,得出的值接近8192,实锤,上述算法是有误的。
/*************************************************************************************************/
下面开始更正错误
12位精度的ADC_H(7)和ADC_L(5)的高低位(橙色区域表示有效)
大家动手指头亲自数一下有效是哪几位。
处理方法:
低位:ADCL>>3 去掉低4位包括(精度损失位和无效位)
高位:( ( ADCH<<1 ) >>1 ) (稳起见去掉符号位)
串口助手中可以看出AD值在 2^12=4096之间 ,说明获取的AD值没问题,加上转换电压公式,可以精准算出当前测量的电压值。
完整项目代码
#include "iocc2530.h"
unsigned char AD_data[]="0000-->";
unsigned char V_data[]="00000";
unsigned int adcvalue=0; //ADC值
unsigned int result=0; //电压值
/**********************/
void uart_senString(unsigned char *str); //发送字符串
void uart_senbit(unsigned char bit); //发送字符
void delay(unsigned int tt);
void uart_init();
void Timeinit();
void initial_AD();
/**********************/
void main()
{
CLKCONCMD &=~0x40; //切换系统时钟源为32M晶振
while(CLKCONSTA & 0x40);//等待32M外部晶振稳定
CLKCONCMD &=~0x47; //设置系统主时钟频率为32M
Timeinit(); //初始化定时器
uart_init(); //初始化串口
initial_AD(); //初始化AD
while(1)
{
while(!(ADCCON1&0x80));//等待转换完成
//获取AD值
adcvalue=ADCL>>3;
adcvalue|=(unsigned int)(ADCH<<1>>1) <<5;
AD_data[0]=adcvalue/1000%10+0x30;
AD_data[1]=adcvalue/100%10+0x30;
AD_data[2]=adcvalue/10%10+0x30;
AD_data[3]=adcvalue%10+0x30;
//获取电压值
result=33000*adcvalue/40950; //AD值转换成电压
V_data[0]=(result/1000%10)+0x30;
V_data[1]='.';
V_data[2]=(result/100%10)+0x30;
V_data[3]=(result/10%10)+0x30;
V_data[4]=(result%10)+0x30;
uart_senString("ADC值:");
uart_senString(AD_data); //ADC值发送到PC端
uart_senString("电压值:");
uart_senString(V_data); //电压值发送到PC端
uart_senString("rn");
delay(8000);
delay(8000);
ADCCON3=0xB0; //再次启动转换
}
}
//中断服务函数
#pragma vector=T1_VECTOR
__interrupt void Time1()
{
T1STAT &= ~0x01;
}
//串口接收服务函数
#pragma vector=URX0_VECTOR
__interrupt void uartIE()
{
}
void initial_AD()
{
//设置IO口
P0SEL|=(1<<0); //外设功能
P0DIR&=~(1<<0); //输入
APCFG|=0x01; //配置模拟IO
//设置参考电压,分辨率 通道 启动转换
ADCCON3=0xB0; //1011 0000 通道0 512抽取率 参考电压AVDD5(3.3V)
}
void Timeinit()
{
//系统晶振32M
T1CC0H =0x61;
T1CC0L =0xA8; //定时0.1秒
T1CTL =0x0E; //分频系数是128,模模式
T1CCTL0 |=0x04; //通道0比较模式
T1IE=1; //使能定时器中断
T1OVFIM=1; //使能定时器溢出中断
EA=1; //总中断使能
}
void uart_init()
{
//初始化引脚
P0SEL |=0x0C; //串口外设
//设置串口波特率
U0BAUD =59;
U0GCR =8;
//设置串口控制器和串口通讯
U0UCR |=0x80; //清除和控制
U0CSR |=0xC0; //串口状态和使能
//清除中断标志位
URX0IF =0;
UTX0IF =0;
//使能中断
URX0IE =1; //串口接收中断
EA=1;
}
void uart_senbit(unsigned char bit)
{
U0DBUF=bit;
while(!UTX0IF);
UTX0IF=0;
}
void uart_senString(unsigned char *str)
{
while(*str !='