本文轉自: Flywithliye本次設計要求,依據實驗數據,設計簡易的電池電量監測電路。該3AH的電池,在某固定環境下放電實驗數據如表 1所示。要求依據測量電壓推算時間,以此作為電量標識,并采用某種方式進行顯示。 表 1 放電實驗數據 2. 設計思路
2.1. 設計假設 本次設計基于以下假設。在任意時刻t測得開路電壓值 ,不考慮其在t時刻前的具體放電過程。即認為該時刻電池開路電壓V,是持續以放電實驗中使用的放電電流 (500mA),由滿電量電壓4.35V,放電 時間得到的。即,認為,電池剩余電量與電池兩端開路電壓具有一一對應關系。 2.2. 設計方案通過AD芯片獲取電池兩端開路電壓,將模擬量電壓值轉換為單片機可處理的數字量。隨后依據該測得的電壓值按照所建立數學模型進行運算。然后將該數字電壓值及其運算結果通過數碼管顯示,并同時通過串行口發送至上位機。上位機接收到數據后做相關處理顯示工作。 2.3. 數學實現 的值由插值的方式計算。依據表 1中的實驗數據,通過測得的電壓V,由下列公式逆向計算,其中 為測得電壓兩側的實驗數據點時間及電壓。則測得該電壓時,各量值如下所示。(電壓處于實驗數據邊界的情況已在程序實現中完成,此處省略) 放電時間: 已用電量: 剩余電量: 剩余電量百分比: 其中時間單位為min,電流單位為mA,電量單位為mA min。 按照如上方式使用實驗數據,運用Matlab繪圖得到如下結果。 圖 1 實驗曲線 其繪圖過程如下述程序所示。 圖 2 實驗曲線繪制 3. 電路設計
3.1. 整體電路結構整體電路原理圖由Protues軟件進行繪制并仿真。為簡化連線便于觀察邏輯,其中使用了網絡標簽的方式,對實際物理上相連而原理圖中未連接的管腳,給予了相同的名稱,則在邏輯上他們互相連接。具體電路原理圖見附件1。 3.2. 單片機最小系統單片機最小系統是單片機能夠正常執行內置程序,發揮其基礎功能的必須組成部分。包括單片機,電源部分,時鐘電路部分及復位電路部分。 3.2.1. 原理圖圖 3 AT89C52最小系統 3.2.2. AT89C52單片機本次設計中主要用到了該型單片機的IO口,串口及定時器。其中,P0口用于和AD轉換電路交換數據。P2口部分管腳用于控制AD轉換芯片工作及讀取其狀態。P3口3.1管腳用作第二功能串行口數據輸出,其余用到的管腳用于進行數碼管位選。P1口用于數碼管段選。 本次設計未使用片外數據/程序存儲器,因此 均處于懸空狀態。且本次設計中,與ADC芯片的數據交換未采用外部拓展總線的方式,將其視為普通IO設備值直接進行操作和讀取。 3.2.3. 時鐘電路時鐘電路為單片機最小系統組成之一,用于提供時鐘信號,驅動CPU指令的執行,并為定時器等提供內部時間基準。本次設計采用無源晶振,為保證串口通信波特率準確,其頻率選定為11.0592Mhz。 3.2.4. 復位電路復位電路用于在單片機程序執行出現異常時,重新初始化運行,從而防止由數字電路競爭冒險及環境干擾引起的,單片機系統的不正常工作。本次設計中的復位電路同時具有上電自動復位及按鍵復位的功能。其中上電時由電容C5將RST引腳拉高,提供復位信號。 3.2.5. 電源單片機由5V穩壓電源供電,由于仿真軟件對其供電電路進行了省略,因此在圖 3中并未繪出電源系統。本次設計中部分其余芯片也未繪出電源。 3.3. AD轉換電路設計AD轉換芯片用于將模擬量的電壓值轉換為可用數字電路處理的數字量值。該部分電路是本次設計中最為核心的部分。用于測試得到電池兩端電壓值,供單片機進行數學處理和相關顯示處理。 由于軟件仿真限制,在本次設計中采用電位器RV1模擬電池兩端電壓。同時,為簡化電路,該芯片模擬地同數字地連在一起。 圖中AIN0管腳上的U3(AIN0)箭頭為Protues軟件提供的電壓探針。用于直觀顯示輸入電壓值,與通過ADS7825轉換后的結果做對比參考。 3.3.1. 原理圖圖 4 ADC轉換電路 3.3.2. ADS7825為提高測量分辨率,本次設計ADC芯片選用ADS7825。ADS7825為四通道輸入,16位AD轉換芯片,5V供電, 輸入電壓范圍,內置2.5V參考電壓,最大采樣轉換時間25us。其輸出方式可由 設置為并行輸出和串行輸出。本次設計采用并行輸出。 其中,當BYTE為0時,D7-D0輸出高八位;BYTE為1時,D7-D0輸出低八位。 用于控制轉換和讀寫,當該位為0時,啟動依次轉換;為1時,啟動數據輸出,外部設備可讀取。 用于指示目前是否轉換完成,當該位為0時,正在轉換;為1時,轉換完成。 其基本串行輸出操作流程如下所述。 首先通過A1,A0管腳選擇輸入通道。將 拉低40ns(最大12us)即可啟動一次AD轉換。(由此處可知,可使用賦值語句連讀對 進行操作,依次連續令其為1,0,1,由于單片機執行指令需要消耗指令周期的時間,可以滿足40ns的要求)轉換開始時, 引腳被拉低,并將持續為低,直至轉換完成,輸出寄存器被更新后,該位被拉高。若BYTE位為低,則在 上升沿時,轉換結果高8位輸出,反之,低八位輸出。在 為0(即轉換進行時)時,所有的轉換指令都將被忽略。 其內部結構如圖 5所示。截取自其數據手冊。 圖 5 ADS7825內部結構圖 本次設計采用AIN0通道輸入電池電壓,在仿真過程中,由電位器RV1分壓模擬變化的電池電壓。注意到其輸入電阻并不高,因此在實際應用時,輸入端應添加電壓跟隨器提高后級輸入阻抗。 3.4. 數碼管電路設計
3.4.1. 原理圖圖 6數碼管驅動控制電路 3.4.2. 數碼管本次設計采用八位共陰極數碼管用于顯示結果,其中高四位用于顯示當前電池兩端電壓,低四位用于顯示當前電池剩余電量百分比(最低為未用)。采用74LS138進行數碼管位選,進行動態掃描。74LS373采用直通方式,用于傳送數碼管段碼及提供驅動電流。 3.4.3. 74LS13874LS138是3線-8線譯碼器,有三個選擇輸入端,三個允許輸入端和八個輸出端。其真值表如表 2所示。G1, , 為控制端,A、B、C為輸入端, Y0~Y7為輸出端,低有效.當 G1, , =1 0 0 時才能進行譯碼輸出,否則8個輸出端Y0~Y7全為1。 表 2 74LS138真值表 3.4.4. 74LS37374LS373是8數據鎖存器。主要用于數碼管、按鍵等等的控制,以及總線擴展時,P0口地址的鎖存。其真值表如表 3所示。 表 3 74HC373 真值表
3.5. 串行口電路設計
3.5.1. 原理圖圖 7串行口通訊電路 3.5.2. MAX232由于RS-232標準采用負邏輯,即邏輯1為-3V~-15V,邏輯0為+3~+15V。而本單片機系統為TTL信號系統。TTL電平規定,+5V等價于邏輯“1”,0V等價于邏輯“0”。因此,DB-9與單片機的連接需要進行電平轉換,如圖 8所示,選用MAX232芯片完成上述功能。 圖 8 MAX232引腳圖及典型應用電路 3.5.3. DB-9由于 RS-232C 并未定義連接器的物理特性,因此,出現DB-25 和 DB-9 各種類型的連接器,其引腳的定義也各不相同。如圖 7右側P1組件所示即為 DB-9 連接器。本次設計僅使用其TXD,GND引腳。 如圖所示,使用MAX232芯片進行電平轉換后,MAX232輸入管腳T1IN與AT89C52串行口P3.1/TXD連接。(由于軟件特殊原因,在MAX232芯片1通道T1OUT處增加74LS04非門進行處理。本次設計中單片機僅使用發送功能,未連接AT89C52接收端P3.0/RXD) 4. 程序設計
4.1. 單片機程序設計
4.1.1. 程序流程圖圖 9 下位機程序流程圖 4.1.2. 程序設計本次單片機程序設計中,使用T1作為串口波特率發生器,T0作為定時器用于定時刷新動態數碼管。 主函數對各功能函數進行調用,其形式較為簡單。在程序右側添加由詳細功能注釋說明。其中兩次測量間隔使用的是軟件延時。通過for語句消耗單片機執行指令的時間,實現延時。 該函數用于控制ADS7825對模擬電壓進行采集轉換,并將16位轉換結果儲存在16位整數中。其具體操作過程如3.3.2中文字所述。 該函數主要用于電池電壓值,已用時間,剩余電量的計算。其中,對小數的處理采用了擴大1000或100倍后四舍五入取整的方式,這樣為后續取出原小數部分各位數值提供了便利。 電壓值U計算公式如下。 其中result為ADS7825輸出16位數據。由于該芯片支持負電壓輸入,因此上式中分母中指數部分為15而非16。 已用時間t由插值函數Linear(double v)運算得到。 剩余電量計算公式如下。 其中,t為已用時間,單位min;180000為電池容量Q,單位mA min;500為放電電流,單位mA。 該函數用于實現由測得電壓U反向計算當前已放電時間。計算結果以unsigned int型變量返回。采用線性插值方式計算,其中當測得電壓正好與實驗數據點相同時,直接取用對應數據點的時間值。 用于進行插值的實驗數據以code標識符標識,另其存放在程序存儲器當中,以節省RAM空間。當測得電壓值超出實驗數據時,分別認為以放電時間為0和360,即認為電池充滿電未使用和電池電量已完全釋放完畢。 該函數主要用于,依據最新獲得的數值型電壓值,電量值,更新數碼管顯示緩沖區。但實際上,數碼管的動態掃描不在本函數中完成,由定時器T0定時掃描。刷新過程中對電壓值最高位小數點及電量百分比最高位0進行了顯示處理。 該函數用于組織一幀通信數據并由串口發送至上位機。為測試方便,其中使用了中文字符。定義回車換行符為一幀數據結束,供上位機識別所需。 其他函數包括定時器T0/T1的工作模式配置函數及其中斷相應函數,還包括用于串行口發送字符/字符串的函數。 以上提到的函數在附件2中給出。 4.2. 上位機程序設計為有效縮短開發時間,本次設計中的上位機設計采用圖形化編程語言Labview設計完成,其主要功能即為通過串行口接收由下位機傳送的字符串數據,并對其進行解析,隨后使用圖形化的方式顯示當前剩余電量百分比等。 4.2.1. 程序流程圖圖 10 上位機程序流程圖 4.2.2. 界面設計圖 11 上位機界面設計 4.2.3. 程序設計上位機使用圖形化編程語言Labview設計完成,其程序框圖(即源程序)如下所示。其中主要使用了平鋪式順序結構,while循環,條件結構。 圖 12 下位機程序設計 5. 系統調試5.1. 串口參數設置本次設計約定串口通信有關參數如下。 表 4串口參數約定 按照表1設置串口參數如下。
圖 13 MCU串口配置 圖 14 上位機串口配置
5.2. 調整滑動變阻器調整圖 4中滑動變阻器RV1動觸點的位置合理。 5.3. 啟動MCU軟件仿真點擊下圖第一個按鈕啟動仿真。 圖 15 Protues啟動 出現如下圖所示內容說明仿真成功執行。 圖 16仿真正在進行 5.4. 打開上位機串口連接點擊下圖第一個按鈕啟動上位機程序。 圖 17 Labview啟動 上圖變換為下圖顯示時說明上位機成功啟動。 圖 18Labview已啟動 5.5. 觀察數碼管顯示及上位機顯示下圖中左側顯示當前測得的電壓值,右側顯示當前剩余電量(最高位0做處理后并未顯示),最右側數碼管在本次程序設計中被禁止使能,始終處于關閉狀態。 圖 19 下位機數碼管顯示 圖 20上位機顯示電池電壓及剩余電量 6. 總結本次設計主要解決了AD轉換及數據處理問題。其次主要包括串行口配置使用,數碼管動態定時刷新等的設計。在設計開始,查找了較多的AD轉換芯片,并通過仿真軟件,對照數據手冊,對其進行功能驗證。早期驗證的主要為ADC0808/0809,AD1674,ADS7824等,考慮分辨率及仿真軟件限制等因素,最后選擇了16位ADS7825芯片。仿真邏輯關系與數據手冊顯示基本全部對應。 下位機軟件設計中,將各功能模塊單獨寫成函數,使得主函數基本流程較為清晰。對各變量,常量,函數等進行了較多的注釋說明,以提高程序可讀性和后續修改的便利性。為節省開發設計時間,編程過程中對同一數值進行了多次運算,并未考慮存儲后再使用的方式,有可能造成了不必要的程序執行時間開銷。考慮了電壓值最高位(個位)處的小數點處理,及電池電量百分比最高位為0時的處理,適應了閱讀習慣。 ADC芯片輸入端在設計時未考慮測量電池電壓時的負載效應,可能會引起較大誤差。可以通過引入電壓跟隨器提高輸入阻抗的方式,進一步降低誤差。 上位機設計中主要完成的工作是對下位機傳送來的字符串的解析和顯示。每次采樣轉換完成后,下位機發送一條以回車換行結尾的字符串,上位機通過該結束位檢測一幀數據傳輸完畢,隨后對接收到的數據幀進行字符串分離解析等工作。
7. 附件1(硬件原理圖)
單片機源程序如下:
- #include<reg52.h>
-
- sbit PARSER = P2^0; //串并行控制位
- sbit BYTE = P2^1; //高低字節控制位
- sbit RC = P2^4; //讀取轉換控制位
- sbit BUSY = P2^2; //忙狀態位
- sbit ADDR_A = P3^5; //低位地址控制位
- sbit ADDR_B = P3^6; //低位地址控制位
- sbit ADDR_C = P3^7; //高位地址控制位
-
- void ConfigUART(unsigned int baud); //串行口配置函數
- void ConfigTimer0(); //定時器0配置函數
- void SendData(unsigned char ch); //字符發送函數
- void SendString(char *s); //字符串發送函數
- void GetVoltage(); //ADC電壓獲取函數
- unsigned int Linear(double v); //線性插值函數,參數v為實測電壓
- void DataProcess(); //數據處理函數
- void LedBufRefresh(); //數碼管顯示緩沖區刷新函數
- void UartSend(); //串口數據發送函數
-
- unsigned char voltage[] = {'0','.','0','0','0',0};
- unsigned char time_used[] = {'0','0','0',0};
- unsigned char percentage[] = {'0','0','0',0};
- unsigned long j,time_used_value,result,percentage_value,voltage_value;
- unsigned int code time_sample[21]={0,18,36,54,72,90,108,126,144,162,180,198,216,234,252,270,288,306,324,342,360};
- double code voltage_sample[21]={4.35,4.24,4.135,4.005,3.92,3.889,3.858,3.826,3.8,3.78,3.762,3.743,3.725,3.705,3.686,3.667,3.65,3.628,3.492,3.05,2};
-
- //數碼管顯示字符轉換表
- unsigned char code LedChar[] = {
- 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
- 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
- };
- //數碼管顯示緩沖區,初值0x00確保啟動時都不亮
- unsigned char LedBuff[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
-
- void main()
- {
- ConfigUART(9600); //配置串行口工作模式及參數
- ConfigTimer0(); //配置定時器0用于數碼管刷新
- EA = 1; //打開總中斷
- while(1)
- {
- GetVoltage(); //獲取ADC電壓值
- DataProcess(); //數據處理
- LedBufRefresh(); //顯示緩沖區刷新
- UartSend(); //串口發送
- for(j=0;j<30000;j++); //延時讀取
- }
- }
- /* 數據處理函數 */
- void DataProcess()
- {
- /* 計算電壓值 */
- voltage_value = (unsigned long)(((double)result * 10 / 32767) * 1000 + 0.5);
- /* 電壓值數組 */
- voltage[4] = '0' + voltage_value % 10;
- voltage[3] = '0' + (voltage_value /10) % 10;
- voltage[2] = '0' + (voltage_value /100) % 10;
- voltage[0] = '0' + (voltage_value /1000) % 10;
- /* 剩余用時數組 */
- time_used_value = Linear((double)result * 10 / 32767);
- time_used[2] = '0' + time_used_value % 10;
- time_used[1] = '0' + (time_used_value / 10) % 10;
- time_used[0] = '0' + (time_used_value / 100) % 10;
- /* 百分比數組 */
- percentage_value = (unsigned long)((double)(180000 - time_used_value * 500) / 180000 * 100 + 0.5);
- percentage[2] = '0' + percentage_value % 10;
- percentage[1] = '0' + (percentage_value / 10) % 10;
- percentage[0] = '0' + (percentage_value / 100) % 10;
- if((percentage_value / 100) % 10)
- {
- percentage[0] = '0' + (percentage_value / 100) % 10;
- }
- else
- {
- percentage[0] = ' ';
- }
- }
- /* 線性插值函數,參數v為實測電壓 */
- unsigned int Linear(double v)
- {
- unsigned int i,t1,t2,t;
- double v1,v2;
- if(v >= 4.35) //大于最大電壓
- {
- t = 0;
- return t;
- }
- if(v <= 2) //小于最小電壓
- {
- t = 360;
- return t;
- }
- for(i=0; i<21; i++) //遍歷插值范圍
- {
- if(voltage_sample[i] < v)
- {
- v1 = voltage_sample[i-1];
- v2 = voltage_sample[i];
- t1 = time_sample[i-1];
- t2 = time_sample[i];
- t = t2 - (v - v2) * (double)(t2 - t1) / (v1 - v2);
- break; //計算插值結果t
- }
- else if(voltage_sample[i] == v)
- {
- t = time_sample[i]; //恰好取采樣值
- break;
- }
- }
- return t;
- }
- /* ADC電壓獲取函數 */
- void GetVoltage()
- {
- unsigned char high,low;
- PARSER = 1; //并行輸出
- RC = 1; //啟動轉換
- RC = 0;
- RC = 1;
-
- while(!BUSY); //等待轉換完畢
-
- P0 = 0xFF; //讀取高八位
- BYTE = 0;
- high = P0;
-
- P0 = 0xFF; //讀取低八位
- BYTE = 1;
- low = P0;
-
- result = high * 256 + low; //合并高低字節
- }
- /* 定時器0中斷服務函數 */
- void InterruptTimer0() interrupt 1
- {
- static unsigned char i = 0; //動態掃描的索引
- TH0 = 0xFC; //重新加載初值
- TL0 = 0x67;
- //以下代碼完成數碼管動態掃描刷新
- P1 = 0x00; //顯示消隱
- switch(i)
- {
- case 0: ADDR_C = 0; ADDR_B = 0; ADDR_A = 0; i++; P1=LedBuff[0]; break;
- case 1: ADDR_C = 0; ADDR_B = 0; ADDR_A = 1; i++; P1=LedBuff[1]; break;
- case 2: ADDR_C = 0; ADDR_B = 1; ADDR_A = 0; i++; P1=LedBuff[2]; break;
- case 3: ADDR_C = 0; ADDR_B = 1; ADDR_A = 1; i++; P1=LedBuff[3]; break;
- case 4: ADDR_C = 1; ADDR_B = 0; ADDR_A = 0; i++; P1=LedBuff[4]; break;
- case 5: ADDR_C = 1; ADDR_B = 0; ADDR_A = 1; i++; P1=LedBuff[5]; break;
- case 6: ADDR_C = 1; ADDR_B = 1; ADDR_A = 0; i=0; P1=LedBuff[6]; break;
- //case 7: ADDR_C = 1; ADDR_B = 1; ADDR_A = 1; i=0; P1=LedBuff[7]; break;
- default: break;
- }
- }
- /* 數碼管顯示緩沖區刷新函數 */
- void LedBufRefresh()
- {
- LedBuff[6] = ~LedChar[percentage_value % 10];
- LedBuff[5] = ~LedChar[(percentage_value / 10) % 10];
- if((percentage_value / 100) % 10)
- {
- LedBuff[4] = ~LedChar[(percentage_value / 100) % 10];
- }
- else
- {
- LedBuff[4] = 0;
- }
- LedBuff[3] = ~LedChar[voltage_value % 10];
- LedBuff[2] = ~LedChar[(voltage_value /10) % 10];
- LedBuff[1] = ~LedChar[(voltage_value /100) % 10];
- LedBuff[0] = ~(LedChar[(voltage_value /1000) % 10] &0x7F);
- }
- void UartSend()
- {
- SendString("當前電壓:");
- SendString(voltage);
- SendString("V");
- // SendString(" 已用時間:");
- // SendString(time_used);
- // SendString("Min");
- SendString(" 剩余電量:");
- SendString(percentage);
- SendString("%\r\n");
- }
- /* 串口配置函數,baud-通信波特率 */
- void ConfigUART(unsigned int baud)
- {
- SCON = 0x50; //配置串口為模式1
- TMOD &= 0x0F; //清零T1的控制位
- TMOD |= 0x20; //配置T1為模式2
- TH1 = 256 - (11059200/12/32)/baud; //計算T1重載值
- TL1 = TH1; //初值等于重載值
- ET1 = 0; //禁止T1中斷
- ES = 1; //使能串口中斷
- TR1 = 1; //啟動T1
- }
- /* 定時器0配置函數 */
- void ConfigTimer0()
- {
- TMOD &= 0xF0; //清零T0的控制位
- TMOD |= 0x01; //設置T0為模式1
- TH0 = 0xFC; //為T0賦初值0xFC67,定時1ms
- TL0 = 0x67;
- ET0 = 1; //使能T0中斷
- TR0 = 1; //啟動T0
- }
- /* UART中斷服務函數 */
- void InterruptUART() interrupt 4
- {
- if(RI) //接收到字節
- {
- RI = 0; //清零接收中斷標志位
- }
- if(TI) //字節發送完畢
- {
- TI = 0; //清零發送中斷標志位
- }
- }
- /* UART字符發送函數 */
- void SendData(unsigned char ch)
- {
- SBUF = ch; //啟動發送
- while(!TI); //等待結束
- }
- /* UART字符串發送函數 */
- void SendString(unsigned char *s)
- {
- while(*s) //循環發送
- {
- SendData(*s++);
- }
- }
復制代碼
|