學院真空實驗室的熱偶真空計表頭損壞了,托人找了許多儀器儀表商店、公司,買不到型號相符的。前些天,王博找到我,讓我做一個數字式表頭取代之(原表頭乃是指針式的),已知基本數據如下: 熱偶內阻:未知 表頭內阻:要求在40R~~300R之間的某一值,但該值未知 儀器處于“測量”檔位時,熱偶的輸出電壓可能通過了一個限流電阻加在了表頭上(阻值未知),通過表頭的電壓:0~~10mV; 電壓對應于真空室的壓強:260.00Pa~~0.10Pa(詳見數據表格) 儀器處于“加熱”檔位時,表頭與一個未知阻值電阻并聯,要求正確顯示干路總電流(范圍:20.0~40.0mA),以便對熱偶真空規管的工作電流進行控制、校準,使之正常工作。由于外部并聯的小電阻阻值未知,通過表頭的電流亦不能事先確定(根據實際測試數據估算,應不大于2mA) 熱偶真空計輸出電壓與電流關系: 原始表格: 電壓(mV) 壓強(Pa)
0.8 266.000
0.9 200.500
1.0 201.191
1.1 136.381
1.3 106.386
1.4 88.344
1.6 76.475
1.7 67.260
1.9 59.942
2.0 54.052
2.2 49.441
2.4 45.134
2.5 40.935
2.7 37.576
2.8 34.458
3.0 31.893
3.1 29.704
3.3 27.514
3.5 25.616
3.6 23.932
3.8 22.389
3.9 21.004
4.1 19.798
4.2 18.532
4.4 17.518
4.5 16.542
4.7 15.645
4.9 14.834
5.0 14.072
5.2 13.319
5.3 12.594
5.5 11.869
5.6 11.142
5.8 10.460
5.9 9.912
6.1 9.364
6.3 8.841
6.4 8.321
6.6 7.824
6.7 7.364
6.9 6.903
7.0 6.445
7.2 5.991
7.3 5.536
7.5 5.119
7.7 4.744
7.8 4.368
8.0 3.994
8.1 3.659
8.3 3.325
8.4 2.990
8.6 2.657
8.8 2.350
8.9 2.042
9.1 1.735
9.2 1.428
9.4 1.080
9.5 0.837
9.7 0.569
9.8 0.294
10.0 0.010
原始曲線:
首先,需要數據表格轉換成電壓以0.1mV步進的新表格:使用origin75繪圖軟件,把原始數據擬合成一條電最相近的曲線,然后對曲線進行重新取點,得出以0.1mV電壓步進的“電壓—壓強”數據表格。
新的數據表格: 電壓(mV) 壓強(Pa) 0.8 260.00
0.9 210.94
1.0 164.99
1.1 138.95
1.2 117.94
1.3 101.91
1.4 90.34
1.5 81.51
1.6 75.29
1.7 69.62
1.8 64.18
1.9 59.63
2.0 55.56
2.1 52.29
2.2 49.56
2.3 46.44
2.4 44
2.5 41.09
2.6 38.88
2.7 36.96
2.8 34.95
2.9 33.03
3.0 31.65
3.1 29.96
3.2 28.81
3.3 27.29
3.4 26.15
3.5 25
3.6 24.11
3.7 22.86
3.8 22.06
3.9 21.07
4.0 20.27
4.1 19.42
4.2 18.62
4.3 18.03
4.4 17.4
4.5 16.74
4.6 16.13
4.7 15.64
4.8 15.1
4.9 14.64
5.0 14.16
5.1 13.62
5.2 13.16
5.3 12.68
5.4 12.22
5.5 11.76
5.6 11.28
5.7 10.82
5.8 10.36
5.9 10.06
6.0 9.69
6.1 9.26
6.2 9.1
6.3 8.73
6.4 8.39
6.5 8.02
6.6 7.69
6.7 7.4
6.8 7.11
6.9 6.84
7.0 6.55
7.1 6.25
7.2 5.92
7.3 5.65
7.4 5.36
7.5 5.1
7.6 4.84
7.7 4.62
7.8 4.4
7.9 4.14
8.0 3.92
8.1 3.71
8.2 3.49
8.3 3.27
8.4 3.06
8.5 2.84
8.6 2.64
8.7 2.46
8.8 2.25
8.9 2.07
9.0 1.85
9.1 1.67
9.2 1.46
9.3 1.26
9.4 0.87
9.5 0.9
9.6 0.73
9.7 0.55
9.8 0.37
9.9 0.19
10.0 0.01
新的曲線:
硬件電路:
電路比較簡單:使用OP07進行兩級同相放大,輸出電壓送至M8的AD0中進行AD采樣(內部2.56V參考電壓),經M8處理之后,送到液晶進行顯示。第一級同相輸入端通過一個1K的精調電阻并接到地,以便控制表頭的輸入電阻(內阻)為0R~1000R的任意值。
軟件算法律: AD采樣的值經M8內部處理,轉換成電壓顯示數據,一方面進行顯示,一方面換算成對應的壓強與電流數據,分別進行顯示。值得一提的是,電流與電壓之間必然具有線性關系,表現為具有一個等效電阻R=V/I,由于R的值未知,可能在0.00~600.00R之間,應此另外設置按鍵控制R的值在0.00~600.00之間,當然,設置的值必須存于M8內部非易失性存儲器EEPROM中,以便永久保存。 由于M8引IO引腳較少,只設置兩個控制鍵以節省IO口。兩個控制鍵,一個加,一個減,完全可以實現控制。為了使提高控制速度,每個控鍵均具有長按、短按、連發功能。短按一次,加0.01或減0.01;長按(長于1s屬于),則每隔300ms加1或減1。 另外,為了減少運算量,幾乎所有的小數運算皆代以整數運算進行。例如,100.72-50.61,在程序中表示為10072-5061,運算結果為5011,最終顯示時,先顯示5011/100(整數部分),再顯示“.”(打小數點),最后顯示5011%100(小數部分),顯示實際效果為:50.11,與原始運算結果一致。這種顯示方式顯然稍復雜一些,但由于不使用浮點運算,大大減少了程序代碼及運算時間,提高了程序效率。 本設計的濾波設計頗有特點(主要是指軟件濾波)。除硬件上注重退耦濾波之外,在軟件上設置采樣頻率為50HZ(也可以是100HZ、200HZ等),這樣可以去除市電工頻工擾,使顯示相當的穩定。測試7.4mV電壓,若以30Hz、48Hz、60Hz等與50不成整倍數關系的采樣頻率,顯示電壓將在7.2mV~7.6mV之間隨機跳動;而若以25、50Hz、100Hz進行采樣,則顯示將穩定在7.2mV,連續觀測60s未出現波動,比數字萬用表的測量結果還穩定。除此之外,為進一步提高測量精度與穩定性(主要是穩定性),采取連續采樣50次求平均值的辦法。
這里指出一個屢試不爽的經驗結論:采樣測量系統中,使用過采樣技術,具有微量紋波的信號比純凈的穩定信號的測量更精確。例如,使用過采樣技術,10的AD以128倍于信號紋波頻率的頻率采樣,理論上可以獲得最高17位的精度!曾有人使用AVR的10AD做電阻測量,做到了16位精度。故而,某些時候,為了用較低位數的AD獲得較高的采樣精度,甚至需要給被測信號人為加入某種頻率的紋波。令人震驚!——為什么呢?因為,過采樣技術可以提高采樣精度——但對于純凈的直流信號無效。
好了,不羅嗦了,下面發程序:
/***************************************************************************** 單 位:廣西民族大學物理與電子工程學院07物本班
文件名稱:ZD0-2熱偶真空計表頭.c
摘 要:ZD0-2熱偶真空計表頭主程序文件
當前版本:V2.0
作 者:白 羽
完成日期:2010年10月5日 *****************************************************************************/
#define CRYSTAL 7.3728 //設置晶振頻率
#define LCD_RS P(PORTD,2) //設置液晶與M8的引腳連接
#define LCD_EN P(PORTD,3)
#define LCD_DN P(PORTD,H) #include<iom8v.h>
#include<eeprom.h>
#include<白羽AVR庫\LCD.h>
#include<白羽AVR庫\Timer.h> const UINT16 VPdata[93] = { //“電壓—壓強”表格
26000,21094,16499, /* 0.8--1.0 */
13895,11794,10191,9034,8151,7529,6962,6418,5963,5556, /* 1.1--2.0 */
5229 ,4956 ,4644 ,4400,4109,3888,3696,3495,3303,3165, /* 2.1--3.0 */
2996 ,2881 ,2729 ,2615,2500,2411,2286,2206,2107,2027, /* 3.1--4.0 */
1942 ,1862 ,1803 ,1740,1674,1613,1564,1510,1464,1416, /* 4.1--5.0 */
1362 ,1316 ,1268 ,1222,1176,1128,1082,1036,1006,969 , /* 5.1--6.0 */
926 ,910 ,873 ,839 ,802 ,769 ,740 ,711 ,684 ,655 , /* 6.1--7.0 */
625 ,592 ,565 ,536 ,510 ,484 ,462 ,440 ,414 ,392 , /* 7.1--8.0 */
371 ,349 ,327 ,306 ,284 ,264 ,246 ,225 ,207 ,185 , /* 8.1--9.0 */
167 ,146 ,126 ,87 ,90 ,73 ,55 ,37 ,19 ,1 /* 9.1--10.0*/
}; UINT8 flag = 0; //10ms中斷標志
UINT8 count = 0; //10ms累加標志
UINT16 AdData = 0; //電壓 (mV)*10
UINT32 AdSum = 0; //50次AD采樣和
UINT16 Rin = 1254; //輸入電阻*100
UINT16 KeyCount = 0; //控鍵按下時間 union EUINT16
{
UINT16 edata;
UINT8 echar[2];
}; void StartAdc(void); //啟動模數轉換
void RingBell(UINT8 Pascal); //低壓強時響鈴
void LcdShowVoltage(UINT16 AdData); //顯示電壓數據
void LcdShowCurrent(UINT16 AdData); //顯示電流數據
void LcdShowPascal(UINT8 AdData); //顯示壓強數據
UINT8 ScanfKey(void); //按鍵處理程序
void EEPROMWriteData(UINT16 data)
{
union EUINT16 eRin;
eRin.edata = data;
EEPROMWriteBytes(10,eRin.echar,2); //寫temp[3]到以10為首的eeprom中
}
UINT16 EEPROMReadData(void)
{
union EUINT16 eRin;
EEPROMReadBytes(10,eRin.echar,2); //寫temp[3]到以10為首的eeprom中
return eRin.edata;
}
void main(void)
{
LcdInit();
Rin = EEPROMReadData(); //讀取eeprom值
Timer1Period(2,21702); //10ms定時
StartAdc(); //啟動AD轉換
Enable_OC1A();
Enable_INTERRUPTS();
while(1)
{
if(flag == 1)
{
flag = 0;
count++;
AdSum += ADC;
StartAdc();
if(count == 50)
{
static UINT8 VIflag = 0;
count = 0;
AdData = AdSum/75.1; //50次的平均電壓值(mV)*10 [8--100]
AdSum = 0;
/**********************************/ //500mS
if(AdData < 120) //小于12.0mV,是電壓采樣信號
{
if(VIflag == 0)
{
VIflag = 1;
Disable_INTERRUPTS();
LcdInit();
Enable_INTERRUPTS();
}
LcdLocate(1,12);LcdShowString(" V ");
LcdLocate(2,12);LcdShowString("Heat");
LcdLocate(1,3); LcdShowVoltage(AdData); //顯示電壓
LcdLocate(2,2); LcdShowPascal(AdData); //顯示壓強
}
else //大于12.0mV,是電流采樣信號
{
if(VIflag == 1)
{
VIflag = 0;
Disable_INTERRUPTS();
LcdInit();
Enable_INTERRUPTS();
}
LcdLocate(1,12);LcdShowString(" I ");
LcdLocate(2,12);LcdShowString("Test");
LcdLocate(1,3); LcdShowVoltage(AdData); //顯示電壓
LcdLocate(2,3); LcdShowCurrent(AdData); //顯示電流
}
/**********************************/
}
/***************************************************/ //10mS
{
UINT8 key = ScanfKey();
if(key == BIT(2)) //左鍵
{
if(KeyCount == 2) //短按
{
Rin++;
}
else if((KeyCount > 50) && (KeyCount <= 60000)) //長按
{
if((KeyCount%15) == 0) //每500ms加100
{
Rin += 100;
}
}
EEPROMWriteData(Rin);
}
else if(key == BIT(0)) //右鍵
{
if(KeyCount == 2)
{
Rin--;
}
else if((KeyCount > 50) && (KeyCount <= 60000)) //長按
{
if((KeyCount%15) == 0) //每500ms加100
{
Rin -= 100;
}
}
EEPROMWriteData(Rin);
}
}
/***************************************************/
}
}
} //啟動AD轉換
void StartAdc(void)
{
ADMUX = BIT(6)|BIT(7) + 0; //通道0、內部2.56V
ADCSRA = BIT(ADEN)|BIT(ADSC)|BIT(ADIF) + 6; //ADC使能、開始轉換、清標志
} //顯示電壓(mV):兩位小數
void LcdShowVoltage(UINT16 AdData)
{
if(AdData >= 100)
{
LcdShowNumber(AdData/10,2);
}
else
{
LcdShowChar(' ');
LcdShowNumber(AdData/10,1);
}
LcdShowChar('.');
LcdShowNumber(AdData%10,1);
LcdShowString("0mV");
} //顯示電流(mA):兩位小數
void LcdShowCurrent(UINT16 AdData)
{
UINT32 Current = (UINT32)AdData * 10000 / Rin;
if(Current%10 >= 5)
{
Current += 10;
}
Current /= 10;
if(Current >= 10000)
{
LcdShowString(" I=****");
return;
}
if(Current >= 1000)
{
LcdShowNumber(Current/100,2);
}
else
{
LcdShowChar(' ');
LcdShowNumber(Current/100,1);
}
LcdShowChar('.');
LcdShowNumber(Current%100,2);
LcdShowString("mA ");
} //顯示壓強
void LcdShowPascal(UINT8 AdData)
{
UINT16 Pascal;
static UINT8 flag = 0;
//電壓數據變換成壓強數據
if(AdData < 8) //壓強很大(電壓小于0.8mV)
{
Pascal = 26001; //用一個很大的值表示超量程
}
else if(AdData <= 100) //正常壓強范圍
{
Pascal = *(VPdata + AdData - 8);
}
else //壓強很小(電壓大于10.0mV)
{
Pascal = 0; //用一個很小的值表示小壓強
}
//壓強數據小于10(即壓強小于0.1Pa)時提醒0.5秒
if(Pascal <= 10)
{
if(flag < 2)
{
DDRB |= BIT(1);
PORTB |= BIT(1);
flag++;
}
else
{
PORTB &= ~BIT(1);
flag = 2;
}
}
else
{
flag = 0;
}
//將壓強數據送至液晶進行顯示
if(Pascal > 26000)
{
LcdShowString(" P=**** ");
return;
}
else if(Pascal >= 10000)
{
LcdShowNumber(Pascal/100,3);
}
else if(Pascal >= 1000)
{
LcdShowChar(' ');
LcdShowNumber(Pascal/100,2);
}
else
{
LcdShowString(" ");
LcdShowNumber(Pascal/100,1);
}
LcdShowChar('.');
LcdShowNumber(Pascal%100,2);
LcdShowString("Pa");
}
UINT8 ScanfKey(void)
{
UINT8 flag = 0;
static UINT8 NewKey = 0, OldKey = 0;
DDRB |= BIT(0)|BIT(2);
PORTB |= BIT(0)|BIT(2);
NewKey = PINB & (BIT(0) | BIT(2));
if(NewKey == OldKey) //兩次相等
{
if(NewKey == (BIT(0) | BIT(2))) //兩次相等但為空
{
flag = 0; //返回空
KeyCount = 0;
}
else //兩次相等且非空
{
flag = 1; //返回鍵值
KeyCount++;
}
}
OldKey = NewKey;
return (flag == 1) ? NewKey : 0;
}
#pragma interrupt_handler Timer1_10ms_ISR:iv_OC1A
void Timer1_10ms_ISR(void)
{
flag = 1;
}
/********************************END END END END *******************************************************/
通過設計這個表頭,我有兩點體會: 一、深切體會到設計一個完整理的系統與單獨完成一個簡單的功能,那是相差很遠的兩種境界。后者通常簡單,由此很容易得出前者“也不難”的結論。而實際上,前者比后者難實現多了!完成前者,需要一整套的理論知識、一整套的工程思想、一整套處理困難的方法,一整套的實踐經驗,還要相當的細心、考察現實中所有可能夠的情形并區別對待,要關注盡可能多的Bug(系統漏洞)并做好相應的防范措施。系統還要做足硬件保護,不僅要保護本身,還要保護與這銜接的外部系統;當兩者矛盾時,還要懂得折中處理。 二、軟件編程上,強烈需要一個多任務操作系統。沒有這個系統,多任務的程序編寫將大受限制。目前,技術領域中,AVR單片機尚未有成熟的操作系統,我還是有必要實現一個,哪怕有一點Bug。
|