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 下载:关注公众号,首页回复“电子秤”获取资料