1.概述
(1)系統原理
本電子秤系統利用壓力傳感器採集因壓力變化產生的電壓信號,經過電壓放大電路放大,然後再經過模數轉換器轉換爲數字信號,最後把數字信號送入單片機。單片機經過相應的處理後,得出當前所稱物品的重量及總額,然後再顯示出來。此外,還可通過鍵盤設定所稱物品的價格。主要技術指標爲:稱量範圍0~5kg;分度值0.001kg;電源DC1.5V(一節5號電池供電)。
(2)功能
a.量程:0-5Kg
b.可結合鍵盤輸入貨物單價,並計算出總價格
c.具有去皮、休眠、切換量程等功能
d.輸出的重量分度值爲0.001Kg,並採用四捨五入
2.硬件電路設計
(1)系統硬件框圖
系統硬件由6個部分組成:控制器部分、測量部分、報警部分、數據顯示部分、鍵盤部分、和電路電源部分,系統設計總體方案框圖,如圖。
(2)壓力信號處理電路
a. 壓力信號處理電路主要分爲兩部分,一部分是壓力傳感器,主要是將壓力信號轉換爲電信號,壓力傳感器內部電路圖,如圖。
本設計採用SP20C-G501電阻應變式傳感器,其最大量程爲7.5 Kg.稱重傳感器由組合式S型樑結構及金屬箔式應變計構成,具有過載保護裝置。
b.壓力信號處理電路另一部分爲AD轉換電路,主要功能爲將模擬信號轉換爲數字信號,供單片機處理。本AD轉換芯片採用電子秤專用模擬/數字(A/D)轉換器芯片hx711對傳感器信號進行調理轉換,是一款專爲高精度電子秤而設計的24 位A/D 轉換器芯片,電路如圖。
(3)總體硬件電路圖
由於其他電路都是比較常規的電路,就貼上總體的電路供大家參考。
原理圖
PCB電路圖
3.軟件設計
總體程序主要分爲五部分:
1.鍵盤驅動程序
2.AD採集處理程序
3.LCD12864驅動程序
4.定時器中斷程序
5.主程序
這裏我就只貼出部分程序(詳細的程序可下載源碼去看)
(1)鍵盤驅動程序
#include "Main.h"
#define KEY_PORT P3 //按鍵輸入單片機端口
#define KEY_MASK 0xF0 //掩碼
#define KEY_SEARCH_STATUS 0
#define KEY_ACK_STATUS 1
#define KEY_REALEASE_STATUS 2
sbit Beep = P1^3; //sbit類似於宏定義,可以在其他文件用到時重複聲明,不可在頭文件用extern sbit聲明
/********************************************************************
函數名稱: UINT_8 KeyRead()
功能簡介: 狀態機鍵盤掃描
入口參數: 無
返回值 :掃描的鍵值
*********************************************************************/
UINT_8 KeyRead(void)
{
static UINT_8 KeyStatus = KEY_SEARCH_STATUS, KeyCurPress = 0; //定義狀態變量KeyStatus爲靜態局部變量,初始化爲檢測狀態
UINT_8 KeyValue = 0; //定義狀態變量KeyCurPress爲靜態局部變量,初始化爲0(無按鍵按下)
KEY_PORT = KEY_MASK; //把KEYPORT前四行拉低,通過與KEYMASK列檢測是否有按鍵按下
KeyValue = (~KEY_PORT) & KEY_MASK; //KEYPORT高四位其中一個爲0時證明有按鍵按下,KeyValue不爲0
switch (KeyStatus) //初始狀態爲KEY_SEARCH_STATUS
{
case KEY_SEARCH_STATUS : //掃描狀態
{
if (KeyValue)
{
KeyStatus = KEY_ACK_STATUS; //轉到確認狀態
}
return 0; //返回鍵值爲0
}
break;
case KEY_ACK_STATUS : //確認狀態
{
if (!KeyValue)
{
KeyStatus = KEY_SEARCH_STATUS; //如果是抖動引起的就回到掃描態,進入確認態
}
else //確認按鍵按下
{
TR1 = 1; //確認有按鍵按下時開啓定時器1中斷
KEY_PORT = 0XFE; //把第一行拉低,檢測哪一列被按下
switch (KEY_PORT)
{
case 0XEE: KeyCurPress = KEY1; break; //KeyCurPress的值爲靜態局部變量,可以保存
case 0XDE: KeyCurPress = KEY2; break;
case 0XBE: KeyCurPress = KEY3; break;
case 0X7E: KeyCurPress = KEY4; break;
default : break;
}
KEY_PORT = 0XFD; //把第二行拉低,檢測哪一列被按下
switch (KEY_PORT)
{
case 0XED: KeyCurPress = KEY5; break;
case 0XDD: KeyCurPress = KEY6; break;
case 0XBD: KeyCurPress = KEY7; break;
case 0X7D: KeyCurPress = KEY8; break;
default : break;
}
KEY_PORT = 0XFB; //把第二行拉低,檢測哪一列被按下
switch (KEY_PORT)
{
case 0XEB: KeyCurPress = KEY9; break;
case 0XDB: KeyCurPress = KEY10; break;
case 0XBB: KeyCurPress = KEY11; break;
case 0X7B: KeyCurPress = KEY12; break;
default : break;
}
KEY_PORT = 0XF7; //把第二行拉低,檢測哪一列被按下
switch (KEY_PORT)
{
case 0XE7: KeyCurPress = KEY13; break;
case 0XD7: KeyCurPress = KEY14; break;
case 0XB7: KeyCurPress = KEY15; break;
case 0X77: KeyCurPress = KEY16; break;
default : break;
}
KeyStatus = KEY_REALEASE_STATUS;
}
return 0;
}
break;
case KEY_REALEASE_STATUS :
{
if (!KeyValue)
{
TR1 = 0; //鬆手就關閉中斷
Beep = 1; //防止Beep爲低電平時一直鳴叫
KeyStatus = KEY_SEARCH_STATUS;
return KeyCurPress ;
}
else
{
return 0;
}
}
break;
default : return 0; break;
}
}
(2)AD採集程序
#include "Main.h"
#define StandardValue 1600000 //質量基準值
sbit RATE = P2^5; //AD轉換速率控制端
sbit DOUT = P2^6; //串行輸入數據端
sbit PD_CLK = P2^7; //時鐘信號端
UINT_32 Offset = 0; //零點偏移
FLOAT_32 Weight = 0; //質量
FLOAT_32 WeightTemp = 0.0; //質量中間變量
/********************************************************************
函數名稱: UINT_32 AD_Hx711(void)
功能簡介: AD採集
入口參數: 無
返回值 :模擬電壓經過AD轉化後的數字量
*********************************************************************/
UINT_32 AD_Hx711(void)
{
UINT_32 AD_Value = 0;
UINT_8 i = 0;
PD_CLK = 0;
AD_Value = 0;
while(DOUT);
for (i = 0; i < 24; i++)
{
PD_CLK = 1;
_nop_();
_nop_();
_nop_();
AD_Value = AD_Value << 1;
PD_CLK = 0;
if(DOUT)
{
AD_Value++;
}
}
PD_CLK = 1;
AD_Value = AD_Value^0x800000; //輸出的是補碼
PD_CLK = 0;
_nop_();
_nop_();
_nop_();
return(AD_Value);
}
/********************************************************************
函數名稱: void AD_Offset()
功能簡介: AD採集零點偏移平均值,也可當去皮使用
入口參數: 無
返回值 :無
*********************************************************************/
void AD_Offset()
{
UINT_8 i = 5;
UINT_32 AD_Sum = 0;
for (; i > 0; i--)
{
AD_Sum = AD_Sum + AD_Hx711();
}
Offset = (UINT_32)(AD_Sum / 5);
}
/********************************************************************
函數名稱: FLOAT_32 AD_Weight(UINT_32 ADvalue)
功能簡介: AD值轉換爲以g爲單位的質量
入口參數: UINT_32 ADvalue
返回值 :轉化後以g爲量綱的質量值
*********************************************************************/
FLOAT_32 AD_Weight(UINT_32 ADvalue)
{
INT_32 Dvalue = 0; //值會小於0
Dvalue = ADvalue - Offset;
if (Dvalue < 0)
{
Dvalue = 0;
}
return (FLOAT_32)Dvalue / StandardValue * 4000;
}
(3)主程序
#include "Main.h"
bit g_DecimalPointflag = 0, KeycanFlag = 0, RangeFlag = 0, TotalFlag = 0; //小數點按下標誌位,數字按鍵按下標誌位,量程切換標誌位
sbit Beep = P1^3; //蜂鳴器報警
sbit Led = P1^4; //報警LED
sbit RATE = P2^5; //轉換速率調節
UINT_16 g_HighPrice = 0; //輸入單價存儲整數部分變量
UINT_8 g_LowPrice = 0; //輸入單價存儲小數點後部分變量
UINT_8 Count1 = 0, Count2 = 0, DecimalPointCount = 0; //Count1限定輸入單價整數部分的次數(限定爲5位數,超過自動清零),Count限定爲兩位小數,超過自動清零
FLOAT_32 g_Price = 0.0; //物品單價(整數與小數之和)
UINT_32 g_TotalPrices = 0; //物品總額
FLOAT_32 g_TempPrices = 0.0; //總額中間變量(防止總額過大超出液晶屏顯示,超出時警告)
UINT_8 code table1[] = {"歡迎你"};
UINT_8 code table2[] = {"重量G: g"};
UINT_8 code table3[] = {"單價$:"};
UINT_8 code table4[] = {"總額$:"};
UINT_8 code table5[] = {"Err!"};
UINT_8 code table6[] = {" "}; //清屏的某一行
UINT_8 code table7[] = {"重量G:0.000Kg"};
UINT_8 code table8[] = {"0.000"}; //用空格會引起閃爍感
UINT_8 WeightTable[6]; //存儲質量的字符串
UINT_8 TotalPricesTable[11]; //存儲總額字符串
void Init(void); //初始化函數
void Clean_Price(void); //清除函數
void Display_Int2str(UINT_32 DecNum, UINT_8 Str[], UINT_8 DDRAM); //輸入十進制顯示字符函數
void Init_Weighttab(UINT_8 *String1, UINT_8 *String2); //初始化字符串數組
void main()
{
INT_8 j = 0, k = 0, count = 0;
UINT_8 Key = 0, Num = 0; //Key存儲按鍵的鍵值,Num存儲按下的是那個數字
UINT_32 tmp = 0;
Init();
while (1)
{
WDT_FeedDog(); //喂狗
if (KeycanFlag == 0) //價格還沒輸入時可以顯示(防止價格輸入一半DDRAM地址改變,造成價格不可連續輸入)
{
if (TimeCount >= 50) //定時時間超過250ms執行AD採集
{
// Offset = AD_Hx711(); //8527820-8475960 =51860
// Display_Int2str(Offset, WeightTable, 0x93);
WeightTemp = AD_Weight(AD_Hx711()); //把AD值轉化爲以克爲單位的質量(含小數)
if (WeightTemp > 4000) //超出量程4000g報警
{
for (k = 0; k < 4; k++)
{
LcdDdram_Display(1,3,table5); //輸出錯誤字符串
}
Beep = 0; //蜂鳴器鳴叫
Led = 0; //Led亮
}
else
{
Beep = 1; //重量少於最大量程關閉蜂鳴器
Led = 1;
tmp = (UINT_32)(WeightTemp); // tmp = (UINT_32)(WeightTemp + 0.5); //將質量四捨五入,例如1000.50 》1001
if(RangeFlag == 0) //量程單位爲g
{
Init_Weighttab(WeightTable,table6); //初始化字符數組爲空格,防止上次字符位數比這次大,沒有清除
k = 0; //例如上次顯示1234,這次12,殘留34
Weight = tmp; //中間質量變量賦給質量
while (tmp != 0)
{
WeightTable[k++] = 0x30 + tmp % 10; //提取十進制最後一位轉換爲字符
tmp /= 10;
}
if (k == 0) //質量爲0時
{
WeightTable[k++] = '0';
}
Write_Cmd(0x93);
count = k;
while (k > 0)
{
Write_Data(WeightTable[k-1]); //質量倒序輸出,因爲之前是倒序輸入,高位地址爲高位,由高位到低位
k--;
}
for (k = count; k < 5; k++ ) //把上次殘留的字符清掉
{
Write_Data(WeightTable[k]);
}
}
else
{
Init_Weighttab(WeightTable,table8); //清空字符串數組
k = 0;
Weight = tmp / 1000.0; //質量/g 1000 = /kg
while (tmp != 0)
{
WeightTable[k++] = 0x30 + tmp % 10;
tmp /= 10;
if (k == 3) //i=3時就是獲得3位小數時插入小數點
{
WeightTable[k] = '.';
k += 1;
}
}
if (k == 4) //剛好3個小數補0 0.123
{
WeightTable[k] = '0';
k++; //和上面統一,k比實際大1,下面再減回
}
if (k < 3) //當不夠兩位小數時,例如1實際代表的是0.01
{
WeightTable[4] = 0x30; //在最高位插入0
WeightTable[3] = '.'; //在最次高位插入.
for (j = k; j < 3; j++)
{
WeightTable[j] = 0x30; //如果只有0位時插入一個插入兩個0 0.00
} //如果只有1位時插入一個插入1個0 0.0
k = 5; //0.001剛好5個數
}
Write_Cmd(0x93); //重定位液晶DDRAM地址
while (k > 0)
{
Write_Data(WeightTable[k-1]); //總額結算,倒序輸出,因爲之前是倒序輸入,高位地址爲高位
k--;
}
}
}
TimeCount = 0;
Write_Cmd(0x8b); //顯示完體重就定位價格顯示地址
}
}
if (TimeIRQflag == 1) //5ms定時中斷 ,5ms是爲了可以利用來去抖延時而不失效率,由掃描態轉入確認態至少5ms
{
TimeIRQflag = 0;
Key = KeyRead(); //鍵盤掃描,獲取鍵值 按鍵的意義: 1 2 3 量程切換
switch (Key) // 4 5 6 去皮
{ // 7 8 9 清除
case KEY1 : if (TotalFlag== 0) { Write_Data(0x31); Num = 1; } break; // . 0 開關 結算
case KEY2 : if (TotalFlag== 0) { Write_Data(0x32); Num = 2; } break;
case KEY3 : if (TotalFlag== 0) { Write_Data(0x33); Num = 3; } break;
case KEY4 :
{
RangeFlag = !RangeFlag ;
Clean_Price();
if (RangeFlag == 0) //量程切換模式,0爲g爲單位,1爲Kg爲單位
{
LcdDdram_Display(1,0,table2); //把字符串Kg 變成 g ,殘留1個g
Write_Data(' '); //清掉殘留的g
}
else
{
LcdDdram_Display(1,0,table7); //把字符串g 變成 Kg
}
Write_Cmd(0x8b);
}
break;
case KEY5 : if (TotalFlag== 0) { Write_Data(0x34); Num = 4;} break;
case KEY6 : if (TotalFlag== 0) { Write_Data(0x35); Num = 5;} break;
case KEY7 : if (TotalFlag== 0) { Write_Data(0x36); Num = 6;} break;
case KEY8 : AD_Offset(); break;
case KEY9 : if (TotalFlag== 0) { Write_Data(0x37); Num = 7;} break;
case KEY10 : if (TotalFlag== 0) { Write_Data(0x38); Num = 8;} break;
case KEY11 : if (TotalFlag== 0) { Write_Data(0x39); Num = 9;} break;
case KEY12 : Clean_Price(); break;
case KEY13 :
{
if (DecimalPointCount == 0 && KeycanFlag == 0)
{
Write_Data(0x30);
Write_Data(0x2e); //顯示‘.’
}
if (DecimalPointCount == 0 && KeycanFlag == 1)
{
Write_Data(0x2e);
}
if (DecimalPointCount > 200)
{
DecimalPointCount = 0;
}
DecimalPointCount++;
g_DecimalPointflag = 1; //小數點按下
KeycanFlag = 1;
}
break;
case KEY14 : if (TotalFlag== 0) { Write_Data(0x30); Num = 0; } break;
case KEY15 : PCON_PD(); break;
case KEY16 :
{
TotalFlag = 1; //總額顯示之後不能輸入價格
if (Count1 == 1)
{
Count1 = 0; //小數點後面只有1位數的時候*10
g_LowPrice *= 10; //變大10倍,防止出現例如輸入1.2的時候,小數部分爲0.02
}
g_Price = g_HighPrice + g_LowPrice / 100.0; //單價 = 整數 + 小數
g_TempPrices = g_Price * Weight * 100; //總額中間變量 = 價格 * 重量 * 100(保留兩位小數點)
if (g_TempPrices >999999999) //超出屏幕顯示(10位,1位爲小數點)報警
{
LcdDdram_Display(3,3,table5); //超出量程報錯
break;
} //(int)(a+0.5)爲四捨五入
g_TotalPrices = (UINT_32)(g_TempPrices+0.5); //把浮點型轉換爲整型,2.10*1*100 = 209.99 (優化出錯)!!!!
Display_Int2str(g_TotalPrices, TotalPricesTable, 0x9b); //顯示總額
}
break;
default : break;
}
if (Key == KEY1 || Key == KEY2 || Key == KEY3 || Key == KEY5 || Key == KEY6 //有數字按鍵按下時
|| Key == KEY7 || Key == KEY9 || Key == KEY10 || Key == KEY11 || Key == KEY14)
{
KeycanFlag = 1;
if (g_DecimalPointflag != 1) //數字按下時小數點還沒按下輸入的是整數部分
{
Count2++;
if (Count2 >= 5) //不可以輸入超過5位數
{
Clean_Price(); //超過自動清零
}
g_HighPrice = g_HighPrice * 10 + Num; //連續輸入整數部分時,前一位*10再+後一位
}
else //有小數點按下了
{
Count1++;
if (Count1 >= 3) //小數部分不可以輸入超過2位數
{
Clean_Price(); //超過自動清零
}
g_LowPrice = g_LowPrice * 10 + Num;
}
}
}
}
}
/********************************************************************
函數名稱: Init()
功能簡介: 初始化定時器,液晶初始化
入口參數: 無
返回值 :無
*********************************************************************/
void Init(void)
{
RATE = 0; //初始化Hx711轉換速率爲10Hz(RATE = 0時AD轉換速率爲10Hz,RATE = 1爲80Hz)
Time_Init(); //定時器0中斷初始化
LCD_Init(); //12864液晶初始化
LcdDdram_Display(0,0,table1); //第1行顯示數據table1內容 (萬家福超市歡迎你)
LcdDdram_Display(1,0,table2); //第2行顯示數據table2內容 (重量G: g)
LcdDdram_Display(2,0,table3); //第3行顯示數據table3內容 (單價$:)
LcdDdram_Display(3,0,table4); //第4行顯示數據table4內容 (總額$:)
AD_Offset(); //採集零偏的平均值
}
/********************************************************************
函數名稱: void Clean_Price(void)
功能簡介: 清除函數,把單價、總額變量等清0
入口參數: 無
返回值 :無
*********************************************************************/
void Clean_Price(void)
{
LcdDdram_Display(2,3,table6); //清除單價顯示
LcdDdram_Display(3,3,table6); //清除總額顯示
g_HighPrice = 0; //清除價格
g_LowPrice = 0;
Count1 = 0;
Count2 = 0;
KeycanFlag = 0; //清除禁止AD採集標誌位
TotalFlag = 0;
DecimalPointCount = 0;
g_DecimalPointflag = 0; //小數點
Write_Cmd(0x8b); //DDRAM地址指針調整
}
/********************************************************************
函數名稱: void Display_Int2str(UINT_32 DecNum, UINT_8 Str[], UINT_8 DDRAM)
功能簡介: 把十進制數轉換爲字符串輸出,保留兩位小數點
此段代碼是把十進制的“總額”倒序轉換爲字符串存放在數組TotalPricesTable【】裏面,
總額TotalPrices的LSB位存放在字符數組的最低位,這樣做的原因有兩點:
可以確定小數點存放在數組那個位置(由保留小數點位數(i)確定,從最低位數0起到第i位插入小數點)
如果設定12864液晶爲地址自動減1模式時爲小端字節,例如把654321倒序輸出就會變成214365
入口參數: UINT_32 DecNum, UINT_8 Str[], UINT_8 DDRAM
返回值 :無
*********************************************************************/
void Display_Int2str(UINT_32 DecNum, UINT_8 Str[], UINT_8 DDRAM)
{
INT_8 i = 0, j = 0;
while (DecNum != 0) //十進制數不爲0時
{
Str[i++] = 0x30 + DecNum % 10; //提取十進制最後一位轉換爲字符
DecNum /= 10;
if (i == 2) //倒序存儲到Str數組裏去,例如1234,存放爲4321
{
Str[i] = '.'; //i=2時就是獲得兩位小數時插入小數點
i+=1;
}
}
if (i == 3) //Str只有兩位數時,在最前面插入‘0’
{
Str[i] = '0';
i++;
}
Write_Cmd(DDRAM); //重定位液晶DDRAM地址
if (i < 2) //當不夠兩位小數時,例如1實際代表的是0.01
{
Str[3] = 0x30; //在最高位插入0
Str[2] = '.'; //在最次高位插入.
for (j = i; j < 2; j++)
{
Str[j] = 0x30; //如果只有0位時插入一個插入兩個0 0.00
} //如果只有1位時插入一個插入1個0 0.0
i = 4; //字符數組TotalPricesTable長度
}
while (i >= 1) //i設定大於1不大於0是因爲前面的i在不符合循環條件時自動加1,比實際字符數組長度多1位
{
Write_Data(Str[i-1]); //總額結算,倒序輸出,因爲之前是倒序輸入,高位地址爲高位
i--;
}
}
/********************************************************************
函數名稱: Init_Weighttab()
功能簡介: 初始化字符數組
入口參數: UINT_8 *String1, UINT_8 *String2
返回值 :無
*********************************************************************/
void Init_Weighttab(UINT_8 *String1, UINT_8 *String2 )
{
UINT_8 i;
for (i = 0; i < 5 ;i++)
{
*String1++ = *String2;
}
}
源碼+電路圖+PCB 下載:關注公衆號,首頁回覆“電子秤”獲取資料