Tinyos学习笔记(2)
--ADC的使用
前言
这次的主要工作时搞清楚tinyos在CC2430平台下对ADC的操作。在CC2430下的ADC的工作方式主要有两种:一种为正常模式(包括轮询和中断);另外一种为DMA采集模式。在相应的tinyos实现中主要由AdcP module和AdcAdcDmaP module进行实现。由于本笔记只供自己记录使用,方便以后查询,所以内容比较杂也比较乱是在所难免的,但是笔者能够保证的是内容的足够详细与本人认真的态度。希望大家多提宝贵意见。
(支持原创,如需转载,请注明地址:http://blog.sina.com.cn/litianping0709作者:叶雨荫城(阿雨))
Tinyos中基于CC2430下的ADC的相关模块---普通方式
在应用中使用到的ADC的配置(configuration)模块为AdcC模块,提供的接口主要有AdcControl接口与Read接口,具体的configuration模块我们可以看如下的代码,对于AdcControl接口与Read接口的具体介绍在下面继续进行讲解。可以看到这个configuration模块利用到了generic关键字,generic关键字表明这个模块是一个通用模块,为什么要把ADC采用通用模块机制呢??我们知道,在tinyos中每个组件都是唯一的,每个组件的名字都是唯一的(组件可见性具有全局性质),每个组件完成特定的某个功能。对于ADC来说我们如果让其成为一个唯一的组件可不可以?这样是不行的,为什么,因为我们知道在应用程序中可能不止一个地方用到ADC的组件,并且ADC的端口也不是固定的,所以在这种情况下我们就需要采用通用组件的方法,达到的目的就是完成的功能一样,但是可以由不同的实体来完成这个功能。而对于不同的实体进行区分的话就需要用到相应的ID来进行区别,这个由tinyos的unique函数来进行完成。这样,不同的应用可以调用属于自己的ADC模块并且在signal相应的event时可以通过ID来进行识别。这是tinyos中特别有用的一种机制,在很多地方都会用到。
generic configuration AdcC(){
provides interfaceAdcControl;
provides interfaceRead<int16_t>; //表示特定的类型的接口
}
implementation {
components MainC,AdcP;
MainC.SoftwareInit-> AdcP.Init;//由主函数自动完成初始化
enum { ID =unique("UNIQUE_ADC_PORT"), };//唯一的ID
AdcControl =AdcP.AdcControl[ID];
Read =AdcP.Read[ID];
}
接下来看看相关的接口,首先看看AdcControl的接口定义:
interface AdcControl {
command voidenable(uint8_t reference, uint8_t resolution, uint8_tinput);
command voiddisable();
}
接口实现的功能我们从”Enables the ADC for the chosenchannel with the specified settings. Also, enables interrupts anddisables any current sampling.”中可以知道主要对ADC的功能进行配置及中断的控制,主要由两个功能函数进行实现:enable和disable。当然接口的具体实现在AdcP模块中。
再来看看Read接口定义(注:这个接口在tinyos的标准接口文件夹下,tinyos-2.1.1/tos/interfaces/下):
interfaceRead<val_t> {
command error_tread();
event void readDone(error_t result, val_t val );
}
接口的定义很简单,一个命令read用于读取和一个事件readDone在读取完成后进行触发。
下面就来看这几个接口的具体实现,所有的实现均在AdcP中可以找到。看下面的代码,具体的代码我将进行批注。其中的uniqueCount函数的解释我在tinyos programming中找到了这么一段话足以解释其用意” Because the calls to unique definethe set of valid client Ids, nesC has a second compile-timefunction, uniqueCount(). This function also takes a string key. Ifthere are n calls to unique with a given key (returning values0...n-1), then uniqueCount returns n, and this is resolved atcompile-time.”即在编译阶段决定出有多少个不同的ID用户。
module AdcP {
provides interfaceInit; //提供初始化接口
provides interfaceAdcControl[uint8_t id];
provides interfaceRead<int16_t>[uint8_t id];//Read是一个通用的接口,其中int16_t用于指明读取的数据类型
}
implementation
{
#include "Adc.h"
uint8_treferences[uniqueCount("UNIQUE_ADC_PORT")];
uint8_tresolutions[uniqueCount("UNIQUE_ADC_PORT")];
uint8_tinputs[uniqueCount("UNIQUE_ADC_PORT")];
boolinUse[uniqueCount("UNIQUE_ADC_PORT")];
uint8_tcounter;
uint8_t lastId =uniqueCount("UNIQUE_ADC_PORT");
command error_tInit.init() {
uint8_t i;
for (i = 0; i < uniqueCount("UNIQUE_ADC_PORT"); i++){
inUse[i] = FALSE; //表明未被使用
}
counter = 0;
return SUCCESS;
}
command voidAdcControl.enable[uint8_t id](uint8_t reference, uint8_tresolution, uint8_t input) {//参考电压分辨率 输入端口
if (counter == 0) {
ADCIE = 1;// ADC interrupt enable中断使能
ADC_STOP(); //停止采样
}
if (!inUse[id]) { //判断该id的ADC是否在使用过程中
inUse[id]= TRUE;
counter++;
ADC_ENABLE_CHANNEL(input);//默认为0,为何这里要这样搞这里有错误 inputs[id]应为input 否则不可能采集得到 具体是否正确等验证源程序再说
}
references[id] = reference; //第id个ADC的参考电压
resolutions[id] = resolution; //第id个ADC的分辨率
inputs[id] = input; //存储对应id号的输入通道
}
command voidAdcControl.disable[uint8_t id]() {
if (inUse[id]) {
inUse[id] = FALSE;
ADC_DISABLE_CHANNEL(inputs[id]); // 使用到了ADCCFG寄存器ADC input configuration. ADCCFG[7:0] //select P0_7 - P0_0 as ADCinputs AIN7 – AIN0
counter--;
if (counter == 0) {
ADCIE = 0; //如果没有ADC了就禁止ADC中断
}
}
}
command error_tRead.read[uint8_t id]() { //启动read过程
if (lastId < uniqueCount("UNIQUE_ADC_PORT")){
return FAIL;
} else {
uint8_t temp;
lastId = id; //记住调用方
temp = ADCH;//把旧值存储起来
temp = ADCL;
ADC_SINGLE_CONVERSION(references[id] | resolutions[id] |inputs[id]);
return SUCCESS;
}
}
task voidsignalReadDone();
int16_tvalue;
MCS51_INTERRUPT(SIG_ADC) {
value = (( (uint16_t) ADCH) <<8);
value |= ADCL; //得到存储值
post signalReadDone(); //表明读取完成
}
task voidsignalReadDone() {
uint8_t tmp;
tmp = lastId; //存储调用方
lastId = uniqueCount("UNIQUE_ADC_PORT"); //恢复lastId的值
value >>= (8 - (resolutions[tmp]>> 3));//根据分辨率转换值
//8bit
//value >>= 2;
//value |= 0xC000 * ((value & 0x2000)>> 13);
//#define ADC_8_BIT0x00// 64decimation rate
//#define ADC_10_BIT0x10// 128 decimation rate
//#define ADC_12_BIT0x20// 256 decimation rate
//#define ADC_14_BIT0x30// 512 decimation rate
signal Read.readDone[tmp](SUCCESS, value); // 将值传给调用者
}
default event voidRead.readDone[uint8_t id](error_t result, int16_t val) {
//默认的event handler
}
}
总结起来关于ADC用到的寄存器主要有如下几个:
ADCCFG:设置P0口的输入模式,如果这个寄存器的相应位置置1,说明P0口的对应位将会用作ADC的输入口,一般的宏使用为:
#defineADC_ENABLE_CHANNEL(ch)ADCCFG |=(0x01<<ch)
#defineADC_DISABLE_CHANNEL(ch) ADCCFG&=~(0x01<<ch)
ADCIE:中断控制位,置1启动中断
ADCCON1,ADCCON2及ADCCON3(主要用于连续AD转换,这里不介绍,配置与ADCCON2差不多,这里只介绍单个AD转换)三个不同的控制寄存器,在实际的使用中我们把其相应的功能(注:在ADCCON1中有个特殊的控制位为STSEL,复位状态默认为11,说明启动ADC的转换为手动转换。启动标志位为ADCCON1中的ST标志位置1。):
#define ADC_SINGLE_CONVERSION(settings)do{ADCCON3 = settings; }while(0)
这个宏的设置包括选择参考电压,分辨率及相应的ADC的输入口(注意这个宏在执行后也会自动启动相应的AD转换和设置ST的标志位具有相同的功能)
#define ADC_STOP()do {ADCCON1 |= 0x30; } while (0)
#define ADC_SAMPLE_SINGLE()do {ADC_STOP(); ADCCON1 |= 0x40; } while(0)
这个宏用于人工启动一个新的转换。由于是人工启动,所以在应用程序中在readDone中接收到采集到的数据后必须要记得调用read函数启动另外一次AD转换。
应用程序中ADC模块(直接方式—中断)的使用
在应用程序中的module模块中我们主要需要使用的是AdcControl与Read<int16_t>;一个用于ADC的控制,一个用于ADC值的读取。我写了一个小例子,主要实现采集片内温度的值然后把采集到的值发送到串口进行显示。我写了一个简单的例子程序如下,注释采用英文注释,主要是为了养成一种习惯,这与看别人程序不同:
Module 文件
module TestAdcP{
uses interface Boot;
uses interface Leds;
uses interface SerialByteComm as uart0;
uses interface AdcControl;
uses interface Read<int16_t> asRead;
}
implementation{
#define TEMPSENOR 0x0E //temp sensor
#define ADC_REF_1_25_V 0x00
#define ADC_14_BIT 0x30
#define ADC14_TO_CELSIUS(ADC_VALUE) (((ADC_VALUE)>> 4)-315)
event voidBoot.booted()
{
call Leds.led2On();
callAdcControl.enable(ADC_REF_1_25_V,ADC_14_BIT,TEMPSENOR);
call Read.read();
}
task voiddelayTime()
{
int i=0,j=0;
for(i=0;i<3000;i++)
for(j=0;j<100;j++);;
}
uint8_ttempValue[4];
uint8_t index=0;
event voidRead.readDone(error_t result,int16_t val)
//why cant define a variablein an event??only can show two chars contiously??
{
val=ADC14_TO_CELSIUS(val);
tempValue[0]=val/10+'0';
tempValue[1]=val+'0';
tempValue[2]='C';
tempValue[3]='n';
call uart0.put(tempValue[index++]);
post delayTime();
call Leds.led2Toggle();
}
task void print() //I have todo this because sampling is faster than the sending process ofuart,so we must guaranteed that the sample value can be read againonly after the send process is over.
{
call uart0.put(tempValue[index++]);
index==4?(index=0):index;
}
async event voiduart0.putDone()
{
if(index==0)
call Read.read();
else
postprint();
//none
}
async event voiduart0.get(uint8_t data) {
return;
}
}
Configure文件
configuration TestAdcC{
}
implementation{
components MainC,UartC,TestAdcP,LedsC,new AdcC();
TestAdcP.Leds->LedsC;
TestAdcP.Boot->MainC.Boot;
TestAdcP.uart0->UartC. SerialByteComm;
TestAdcP.AdcControl->AdcC;
TestAdcP.Read->AdcC;
}
试验效果截图(图1)
图1
出现的主要问题:
(1)一定要注意需要人工启动AD转换
(2)必须确保在发送字符完成后再调用put函数,否则容易产生覆盖现象,这就需要一定的逻辑保证,在编程的过程中一定要注意
(3)原版下的ADC的module文件下的ADC_ENABLE_CHANNEL(inputs[id])是错误的,并且在源程序中他们在写的过程中只考虑了AN0-AN7的情况,温度传感器的情况他们没有考虑进来。这个需要一定的改进,例如可以将input进行判断看是否是采集内部还是外部,然后再相应的是否使能相应的AD端口。
Tinyos中基于CC2430下的ADC的相关模块---DMA方式
cc2430中提供给应用层的ADC(DMA)接口主要由AdcDmaC模块提供:
generic configuration AdcDmaC(){
provides interfaceAdcControl; //同样是两个接口
provides interfaceRead<int16_t>;
}
implementation {
components MainC,AdcDmaP;
MainC.SoftwareInit-> AdcDmaP.Init;
enum { ID =unique("UNIQUE_ADC_PORT"), };
AdcControl =AdcDmaP.AdcControl[ID];
Read =AdcDmaP.Read[ID];
components newDmaC(); //使用到了DmaC的功能
AdcDmaP.Dma-> DmaC;
}
可以看到,同样使用的generic关键字来表明这个模块是一个通用模块,提供的接口与前例相同,均为两个接口。只不过在实现的时候用到了DmaC模块提供的Dma接口,这个模块我不准备细说,在后续的学习DMA的过程中我会对DMA做详细的介绍。接下来看看具体的接口实现部分,在模块中AdcDmaP模块中进行实现。同样我会将一些关键部分进行注解。
module AdcDmaP {
provides interfaceInit;
provides interfaceAdcControl[uint8_t id];
provides interfaceRead<int16_t>[uint8_t id];
uses interface Dma;//使用到了Dma接口
}
implementation
{
#include "Adc.h"
#include "dma.h"
uint8_treferences[uniqueCount("UNIQUE_ADC_PORT")]; //这些定义与在AdcP模块中的相同
uint8_tresolutions[uniqueCount("UNIQUE_ADC_PORT")];
uint8_tinputs[uniqueCount("UNIQUE_ADC_PORT")];
boolinUse[uniqueCount("UNIQUE_ADC_PORT")];
uint8_tcounter;
uint8_t lastId =uniqueCount("UNIQUE_ADC_PORT");
int16_tvalue;
dma_config_t *dmaConfig; //dma配置
command error_tInit.init() { //初始化
uint8_t i;
for (i = 0; i < uniqueCount("UNIQUE_ADC_PORT"); i++){
inUse[i] = FALSE;
}
counter = 0;
dmaConfig = call Dma.getConfig(); //得到配置参数的指针,用于后续进行配置
dmaConfig->SRCADDR =(uint16_t) 0xDFBA;// address of source
dmaConfig->DESTADDR = (uint16_t)&value;// address of destination //目的地址
dmaConfig->LEN= 1;
dmaConfig->VLEN= VLEN_USE_LEN; // Using LEN todetermine how many bytes to transfer
dmaConfig->IRQMASK =TRUE;// Issue an IRQ upon completion.
dmaConfig->DESTINC =DESTINC_0;// The destination address is to be incremented by 1 after eachtransfer
dmaConfig->SRCINC= SRCINC_0;// The source address inremented by 1 byte after eachtransfer
dmaConfig->TRIG= DMATRIG_ADC_CHALL; // The DMA channelwill be started manually
dmaConfig->WORDSIZE = WORDSIZE_WORD; //One byte is transferred each time.
dmaConfig->TMODE= TMODE_SINGLE_REPEATED; //The number of bytes specified by LEN is transferred
return SUCCESS;
}
command voidAdcControl.enable[uint8_t id](uint8_t reference, uint8_tresolution, uint8_t input) {
if (counter == 0) {
ADC_STOP();
call Dma.armChannel(); //下面为该函数的具体实现
}
if (!inUse[id]) {
inUse[id] = TRUE;
counter++;
}
//中间省略了ADC_ENABLE_CHANNEL(inputs[id]);部分
references[id] = reference;
resolutions[id] = resolution;
inputs[id] = input;
}
command voidAdcControl.disable[uint8_t id]() {
if (inUse[id]) {
inUse[id] = FALSE;
counter--;
if (counter == 0) {
call Dma.stopTransfer(); //停止传输
}
}
}
command error_tRead.read[uint8_t id]() {
if (lastId < uniqueCount("UNIQUE_ADC_PORT")){
return FAIL;
} else {
uint8_t temp;
lastId = id;
ADC_ENABLE_CHANNEL(inputs[id]); //使能相应的输入通道,这里这样写才是正确的,在AdcP模块中的那样的逻辑是有错的。
ADC_SEQUENCE_SETUP(references[id] | resolutions[id] |inputs[id]);
ADC_SAMPLE_SINGLE(); //开始采样
return SUCCESS;
}
}
task voidsignalReadDone();
async event voidDma.transferDone() { //这时候不是利用ADC的中断来完成采用的signal,而是由Dma的传输完成来代替相应功能的实现,道理是一样的。
post signalReadDone();
}
task voidsignalReadDone() {
uint8_t tmp;
tmp = lastId;
lastId = uniqueCount("UNIQUE_ADC_PORT");
value >>= (8 - (resolutions[tmp]>> 3));
ADC_DISABLE_CHANNEL(inputs[tmp]);
signal Read.readDone[tmp](SUCCESS, value); //读取完成,并传送相应的值。
}
default event voidRead.readDone[uint8_t id](error_t result, int16_t val) {
}
}
应用程序中ADC模块(DMA方式)的使用
和直接方式基本一样,module模块其实是一样的,主要是configuare文件有点区别,因为此时提供AdcControl接口和Read<uint_16>接口的模块此时为AdcDmaC模块。具体的configure文件修改如下,很简单:
configuration TestAdcC{
}
implementation{
components MainC,UartC,TestAdcP,LedsC,newAdcDmaC();//利用到了AdcDmaC
TestAdcP.Leds->LedsC;
TestAdcP.Boot->MainC.Boot;
TestAdcP.uart0->UartC. SerialByteComm;
TestAdcP.AdcControl->AdcC;
TestAdcP.Read->AdcC;
}
至于连续采集的部分基本原理差不多,主要利用DMA来进行实现,这里就不再过多叙述。有兴趣的话大家可以试试做做实验然后做做总结,然后和大家分享学习的成果。