基于51单片机的智能电子秤

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 下载:关注公众号,首页回复“电子秤”获取资料
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章