#include "defineports.h"
__CONFIG(INTIO & WDTEN & MCLRDIS & BOREN & LVPDIS & PWRTEN);
//各種模式定義,位操作
#define MODE_NORMAL 0x00
#define MODE_SETTING 0x01
#define MODE_NO_NTC 0x02
unsigned char currMode = 0;
#define NTC_TEMP_BUFF_NUM 6
signed char prevCfgTemp = 60;
signed char cfgTemp = 60;
signed char currTemp = 0; //當前NTC所測得的溫度
signed char ntcTempBuff[NTC_TEMP_BUFF_NUM];
unsigned char ntcTempBuffCnt = 0;
//EEPROM
#define EE_VERIFY_ADDR 0x20
#define EE_VERIFY_VALUE 0x35
#define EE_CFG_TEMP_ADDR 0x21
//用于按鍵防抖
unsigned char downKeyHistory = 0;
unsigned char upKeyHistory = 0;
bit downKeyPressed = 0;
bit upKeyPressed = 0;
bit halfSecond = 0; //每500ms反轉一次
bit revertLed = 0;
unsigned int tickNum = 0;
unsigned int btnIdleTicks = 0;
//PID系數和中間變量
#define PID_PROPORTION 8 // 比例系數
#define PID_INTEGRAL 1 // 積分系數
#define PID_DERIVATIVE 1 // 微分系數
signed char pidLastError = 0; // 上次偏差
signed int pidSumError = 0; // 歷史誤差累計值
signed int pidResult = 0;
//以度為單位的NTC溫度查找表,起始溫度為-9度(因為只有兩位數碼管)
/* 換一個NTC后,如果知道B值,則修改此python代碼中的B值,然后執行生成此表
import math
B = 3800
sOut = "#define TBL_NTC_TEMP_NUM 110\nconst unsigned int tblNtcTemp[TBL_NTC_TEMP_NUM] = {\n"
for i in range(-9, 100, 10):
sl = []
for j in range(i, i + 10):
sl.append('%.0f' % (10000 * math.exp(B * (1 / (273.15 + j) - 1 / (273.15 + 25) ) ) ) )
sOut += ' ' + ', '.join(sl) + ',\n'
sOut += '};'
with open('c:/tblntctemp.txt', 'w') as f:
f.write(sOut)
*/
#define TBL_NTC_TEMP_NUM 110
const unsigned int tblNtcTemp[TBL_NTC_TEMP_NUM] = {
31057, 29915, 28822, 27778, 26778, 25822, 24906, 24029, 23190, 22385,
21614, 20875, 20166, 19486, 18834, 18208, 17607, 17029, 16475, 15943,
15431, 14939, 14466, 14011, 13574, 13153, 12748, 12357, 11982, 11620,
11272, 10936, 10613, 10301, 10000, 9710, 9430, 9160, 8900, 8648,
8406, 8171, 7945, 7726, 7515, 7311, 7113, 6922, 6738, 6559,
6386, 6219, 6058, 5901, 5750, 5603, 5461, 5323, 5190, 5060,
4935, 4814, 4696, 4582, 4471, 4364, 4260, 4159, 4061, 3965,
3873, 3783, 3696, 3612, 3529, 3450, 3372, 3296, 3223, 3152,
3083, 3015, 2950, 2886, 2824, 2764, 2705, 2648, 2592, 2538,
2485, 2434, 2384, 2335, 2288, 2242, 2197, 2153, 2110, 2068,
2028, 1988, 1949, 1912, 1875, 1839, 1804, 1770, 1737, 1704,
};
//段對應關系
//RB0-G, RB1-F, RB2-B, RB3-A, RB4-E, RB5-D, RB6-DP, RB7-C
//0 - 9,
//'-', 'E', 'H', 'L'
unsigned char segMap[] = {0x41, 0x7b, 0xc2, 0x52, 0x78, 0x54, 0x44, 0x73, 0x40, 0x50,
0xfe, 0xc4, 0x68, 0xcd};
#define SEG_IDX_DASH 10
#define SEG_IDX_E 11
#define SEG_IDX_H 12
#define SEG_IDX_L 13
unsigned char digit0Seg = 0;
unsigned char digit1Seg = 0;
//延時函數,一個循環剛好10個指令,在最后加上函數調用的花銷7個指令即可。
//假定4m晶體,則:
//延時0.5ms可以用:delayit(50)
//延時1ms可以用:delayit(100)
//延時10ms可以用:delayit(1000)
void delayit(unsigned int d)
{
while(--d){;}
}
//EEPROM寫數據函數
void SaveCfgToEeprom()
{
unsigned char n;
if (prevCfgTemp == cfgTemp)
return;
for (n = 2; n > 0; n--)
{
while (WR == 1);
if (n == 2)
{
EEADR = EE_CFG_TEMP_ADDR;
EEDATA = (unsigned char)cfgTemp;
}
else
{
EEADR = EE_VERIFY_ADDR;
EEDATA = EE_VERIFY_VALUE;
}
WREN = 1;
EECON2 = 0x55;
EECON2 = 0xaa;
WR = 1;
}
while (WR == 1);
WREN = 0;
prevCfgTemp = cfgTemp;
}
//EEPROM讀數據函數
void RestoreCfgFromEeprom()
{
while (RD == 1);
EEADR = EE_VERIFY_ADDR;
RD = 1;
while (RD == 1);
if (EEDATA != EE_VERIFY_VALUE)
return;
EEADR = EE_CFG_TEMP_ADDR;
RD = 1;
while (RD == 1);
cfgTemp = (signed char)EEDATA;
if ((cfgTemp < -9) || (cfgTemp > 99))
cfgTemp = 50;
prevCfgTemp = cfgTemp;
}
//掃描按鍵事件,并且更新當前模式狀態
void ScanButtons()
{
if (currMode & MODE_NO_NTC)
return;
downKeyHistory = downKeyHistory << 1 | !PORT_BUTTON_DOWN;
if ((downKeyHistory & 0x07) == 0x07) //確定按鍵按下
{
downKeyPressed = 1;
btnIdleTicks = 0;
}
else
{
downKeyPressed = 0;
}
upKeyHistory = upKeyHistory << 1 | !PORT_BUTTON_UP;
if ((upKeyHistory & 0x07) == 0x07) //確定按鍵按下
{
upKeyPressed = 1;
btnIdleTicks = 0;
}
else
{
upKeyPressed = 0;
}
if (currMode & MODE_SETTING)
{
if (((downKeyHistory & 0x0f) == 0x07) && !upKeyPressed)
{
if (--cfgTemp < -9)
cfgTemp = 99;
}
else if (((upKeyHistory & 0x0f) == 0x07) && !downKeyPressed)
{
if (++cfgTemp > 99)
cfgTemp = -9;
}
//設置模式下4s無動作則自動退出
if (btnIdleTicks > 800)
{
currMode = MODE_NORMAL;
ntcTempBuffCnt = 0;
pidResult = 0;
SaveCfgToEeprom();
}
}
else if ((currMode == MODE_NORMAL) && downKeyPressed && upKeyPressed)
{
currMode |= MODE_SETTING;
PORT_HEATER = 0;
}
}
//三中取二判斷端口狀態
unsigned char PortNtcStatus()
{
unsigned char p = 0;
if (PORT_NTC)
p++;
delayit(1);
if (PORT_NTC)
p++;
delayit(1);
if (PORT_NTC)
p++;
if (p >= 2)
return 1;
else
return 0;
}
//判斷NTC是否正常連接,原理是改變另一個端口輸出值,看其是否跟著改變
unsigned char IsNtcAvailable()
{
unsigned int cnt = 1000;
TRIS_MEASURER = 1;
TRIS_REF_RESISTOR = 1;
TRIS_NTC = 1;
if (PortNtcStatus())
{
TRIS_MEASURER = 0;
PORT_MEASURER = 0;
while (PortNtcStatus() && (cnt > 0))
cnt--;
}
else
{
TRIS_MEASURER = 0;
PORT_MEASURER = 1;
while ((PortNtcStatus() == 0) && (cnt > 0))
cnt--;
}
PORT_MEASURER = 1;
if (cnt)
return 1;
else
return 0;
}
//使用PID算法根據當前溫度計算要加熱的時間(加熱周期為Tick中斷的周期)
void CalPidResult()
{
signed char curError, dError;
curError = cfgTemp - currTemp;
if (curError > 1)
{
dError = curError - pidLastError;
pidSumError += curError;
pidLastError = dError;
pidResult = curError*PID_PROPORTION + pidSumError*PID_INTEGRAL + dError*PID_DERIVATIVE;
pidResult >>= 3;
}
else
{
pidSumError = 0;
pidLastError = 1;
pidResult = 0; //curError * PID_PROPORTION;
}
/*if (curError > 10)
pidResult = 10
else if (curError > 1)
pidResult = curError * 2;
else
pidResult = 0;*/
}
//使用普通IO口計算NTC的電阻,進而查表得出當前溫度
//軟件濾波算法:采樣多次,然后去掉一個最大值,一個最小值,剩下的取平均
void GetCurrentTemp()
{
//因為NTC為通過插座外接,可能會出現斷開、接觸不好、斷路等異常,此變量用于防止死循環
unsigned int cntSafe = 20000;
unsigned int cntNtc, cntRef;
float resistorNtc, ratio;
signed int sumTemp;
unsigned char idx;
signed char maxTemp, minTemp;
signed char thisTemp = 0;
if (IsNtcAvailable() == 0)
{
currMode |= MODE_NO_NTC;
currTemp = 120;
ntcTempBuffCnt = 0;
return;
}
else
{
currMode &= ~MODE_NO_NTC;
}
//輸出高電平給電容充電
TRIS_REF_RESISTOR = 1;
TRIS_NTC = 1;
TRIS_MEASURER = 0;
PORT_MEASURER = 1;
PORT_NTC = 0;
PORT_REF_RESISTOR = 0;
delayit(50); //讓電容充分充電
//停止TMR1,重新設置TMR1
TMR1ON = 0;
TMR1H = 0;
TMR1L = 0;
//打開NTC端口,讓電容放電,獲取放電時間
GIE = 0;
TRIS_MEASURER = 1;
PORT_NTC = 0;
TRIS_NTC = 0;
TMR1ON = 1; //TMR1開始計時
while ((PORT_MEASURER) && (cntSafe > 0))
cntSafe--;
TMR1ON = 0;
cntNtc = (TMR1H << 8) + TMR1L;
GIE = 1;
//NTC可能斷開連接了?
if (cntSafe == 0)
{
currMode |= MODE_NO_NTC;
currTemp = 120;
ntcTempBuffCnt = 0;
return;
}
else
{
currMode &= ~MODE_NO_NTC;
}
//同樣步驟測試參考電阻,然后通過兩個計數值的對比就可以計算出NTC電阻
//輸出高電平給電容充電
TRIS_NTC = 1;
TRIS_MEASURER = 0;
PORT_MEASURER = 1;
delayit(50); //讓電容充分充電
//重新設置TMR1
TMR1H = 0;
TMR1L = 0;
//打開參考電阻端口,讓電容放電,獲取放電時間
GIE = 0;
TRIS_MEASURER = 1;
TRIS_REF_RESISTOR = 0;
PORT_REF_RESISTOR = 0;
TMR1ON = 1; //TMR1開始計時
do {}
while (PORT_MEASURER);
TMR1ON = 0;
cntRef = (TMR1H << 8) + TMR1L;
GIE = 1;
TRIS_REF_RESISTOR = 1;
TRIS_MEASURER = 0;
PORT_MEASURER = 1;
//計算得到當前NTC阻值,然后就可以查表得到溫度值了
if ((cntNtc > 0) && (cntRef > 0))
{
ratio = (float)cntNtc / (float)cntRef;
resistorNtc = REF_RESISTOR_VALUE * ratio;
if (resistorNtc < tblNtcTemp[TBL_NTC_TEMP_NUM - 1]) //溫度越限
{
thisTemp = 120;
}
else if (resistorNtc > tblNtcTemp[0])
{
thisTemp = -120;
}
else
{
for (idx = TBL_NTC_TEMP_NUM; idx > 0; idx--)
{
if (resistorNtc <= tblNtcTemp[idx - 1])
{
thisTemp = idx - 10; //溫度表從-9度開始
break;
}
}
}
}
else //計數值為零可能是短路
{
thisTemp = 120;
}
//軟件濾波:去掉一個最大值,去掉一個最小值,然后剩下的取平均
ntcTempBuff[ntcTempBuffCnt] = thisTemp;
if (++ntcTempBuffCnt >= NTC_TEMP_BUFF_NUM)
{
ntcTempBuffCnt = 0;
//去掉一個最大值和一個最小值,然后剩余取平均
maxTemp = ntcTempBuff[0];
minTemp = maxTemp;
sumTemp = maxTemp;
for (idx = 1; idx < NTC_TEMP_BUFF_NUM; idx++)
{
thisTemp = ntcTempBuff[idx];
if (thisTemp > maxTemp)
maxTemp = thisTemp;
if (thisTemp < minTemp)
minTemp = thisTemp;
sumTemp += thisTemp;
}
currTemp = (sumTemp - maxTemp - minTemp) >> 2;
//通過PID計算加熱周期數
CalPidResult();
}
}
//在數碼管上顯示溫度
//此函數僅更新段碼到內存緩沖區,然后由Tick中斷函數輸出到數碼管顯示,
//Tick中斷每5ms掃描一次數碼管,無閃爍
void DisplayTemp(signed char temp)
{
if (currMode & MODE_NO_NTC) //顯示EE
{
digit0Seg = segMap[SEG_IDX_E];
digit1Seg = digit0Seg;
}
else if (temp < -9) //顯示LL
{
digit0Seg = segMap[SEG_IDX_L];
digit1Seg = digit0Seg;
}
else if (temp > 99) //顯示HH
{
digit0Seg = segMap[SEG_IDX_H];
digit1Seg = digit0Seg;
}
else if ((temp >= 0) && (temp < 10)) //0-9則高位消隱
{
digit0Seg = segMap[temp];
digit1Seg = 0xff;
}
else if (temp < 0)
{
temp = -temp;
digit0Seg = segMap[temp];
digit1Seg = segMap[SEG_IDX_DASH];
}
else
{
digit1Seg = segMap[temp / 10];
digit0Seg = segMap[temp % 10];
}
}
//顯示子程序
void Display()
{
//如果不是正常狀態,則在數碼管上閃爍設定的溫度或錯誤信息
if (currMode)
{
if (halfSecond)
DisplayTemp(cfgTemp);
else
{
digit0Seg = 0xff;
digit1Seg = 0xff;
}
}
else
{
DisplayTemp(currTemp);
}
}
//中斷服務
void interrupt Tick()
{
if (TMR2IE && TMR2IF) //定時器2中斷服務程序
{
TMR2IF = 0;
LED_DIGIT_1 = 1;
LED_DIGIT_0 = 1;
if ((++tickNum % 100) == 0)
halfSecond = !halfSecond;
if (btnIdleTicks < 2000)
btnIdleTicks++;
//加熱處理
if ((currMode == MODE_NORMAL) && (pidResult > 0))
{
PORT_HEATER = 1;
pidResult--;
}
else
{
PORT_HEATER = 0;
}
//數碼管消隱
delayit(5);
//動態掃描LED
revertLed = !revertLed;
if (revertLed)
{
PORTB = digit0Seg;
if (digit0Seg != 0xff)
LED_DIGIT_0 = 0;
}
else
{
PORTB = digit1Seg;
if (digit1Seg != 0xff)
LED_DIGIT_1 = 0;
}
}
}
//======================================================
//主函數入口
//======================================================
void main()
{
//關閉模擬比較器
//CM2 = 1;
//CM1 = 1;
//CM0 = 1;
CMCON = 0x07;
//關閉USART
SPEN = 0;
TRISA = TRISA_INIT_VALUE;
PORTA = PORTA_INIT_VALUE;
TRISB = TRISB_INIT_VALUE;
PORTB = PORTB_INIT_VALUE;
delayit(10000);
//TIMER1設定為同步定時器,預分頻1,當前暫停計數
//TIMER1用于IO口測電阻時計數使用
T1CON = 0b00000000;
TMR1IE = 0;
//TIMER2產生一個5ms的定時中斷
T2CON = 0B00100101;
PR2 = 249;
TMR2IE = 1;
PEIE = 1;
GIE = 1;
//啟動wdt,預分頻128,溢出時間大約2.3s
OPTION_REG = 0b10001111;
//TO位非零:不是wdt復位
if (STATUS & 0x10)
currMode |= MODE_SETTING;
RestoreCfgFromEeprom();
while (1)
{
GetCurrentTemp();
ScanButtons();
Display();
asm("CLRWDT");
}
}