51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)

51单片机综合课程设计----多功能电子秤

关键词:proteus仿真、51单片机、DS1302时钟芯片、DB18B20温度芯片、AT24C02存储芯片、LCD1602液晶屏、ADC0832芯片等,文末给出了能用的底层驱动文件

考完试了,就来整理一下博客吧,哈(有种停课不停学的感jio哈哈哈哈哈哈哈)

1 仿真电路

51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第1张图片

2 设计任务

2.1设计内容

**内容有点太过庞杂,一想也难有人看(莫名其妙水了2000字。。。)主要的,我觉得的小创新就是可以连续进行几个的检测,进入相关的模式后,就可以进行检测,当数值稳定1s后才记录存储,并开始测量下一个数值(写博客写到这里忽然想起有个优化项:就是需要测再测下一个之前需要数值改变才行不然一个数值重复测。。这是个bug),其他的就都是常见的那些模块进行的糅合了,没有我自己想的东西了。。。然后看程序图应该大家就懂了,要是想详细了解请看下面: **

1)设计一个多功能电子秤控制器。通过单片机来控制各个模块的运行,保证降低各个模块之间的耦合度,并注意时序。
2)设计交互式界面。通过8个独立按键来控制单片机不同功能的实现,在正常状态下,单片机读取RTC芯片的时间数据并控制LCD1602液晶屏显示时间,包括年份、月份、日、小时、分钟、秒,实时更新,通过用户键入不同的按键,液晶屏可以显示:时间、修改时间、环境温度、当前测量的单个物品的重量、一批物品的重量、一批物品重量的数据等;其中数据包括:测量这一批物品的时间(月份,日,小时,分钟)、测量时选取的标准重量与可接受的误差范围、测量的总量、测量的不符合预期标准的产品数量,并且加入相关的状态显示,如表示当前模式,液晶屏左上角,显示时间时为MODE1,测量单个物品时为MODE2,测量一批物品时为MODE3。并在保存数据与发送数据时进行提示等,提高人机交互体验。
3)设计独立按键来进行人机交互。通过键入不同的值,并根据设计来实现不同的功能具体如下(一共有八个独立按键):
当k1被按下时:进入时间修改矫正模式,一共有6种状态。从第一次按下开始选定秒,此时按下k5可以进行当前秒的增加,按下k6可以进行当前秒的减少。继续按下k1键,则分别可以修改分钟、小时、日、月份、年份的数值大小,并实时在液晶屏上显示;当第7次按下k1时,则返回到正常显示模式。
在时间修改过程中,除了按下增加键k5或减少键k6,其他按键均无效。
当k2被按下时:进入单个重量称量模式。只有一种状态。当第一次按下后液晶屏显示相关界面,并且此时通过改变滑动变阻器来模拟重量的改变,液晶屏实时显示,并且设置一个最大量程,可以通过k5或k6按键来调整,当当前测量的重量高于最大量程时,则指示灯点亮,且蜂鸣器开始发出声音。当再次按下后返回到时间显示模式。在测量过程中,只有k7去皮按键可以使用,按下后则实现去皮功能,其他按键均无效。
当k3被按下时:进入一批产品测量模式,一共有4种状态。从第一次按下后,进入第一种状态选择要测量的标准重量,标准重量有几个标准值可供选择,通过k5和k6来选择不同的数值作为本次标准重量;第二次按下后,进入第二种状态选择可接受的误差,有几个可接受误差工选择,通过k5和k6来选择不同的数值作为本次可接受误差;第三次按下后,开始进行产品测量,并实时显示测量一共测量的个数,并显示当前测量个数,只有当前测量的产品重量保持稳定1s后,才存储下这个数据到AT24C02中(最多存储246个数据,且每个数据不能超过255,可以通过其他单位比例换算的方式来获得超过255后的数据存储,这里没有进行拓展),并判断该重量是否是坏值,更新测量总数,同时每次记录下一个数值后,指示灯反转状态;第四次按下按键后,开始存储测量总数、坏值总数、标准重量、可接受误差、测量时间到中,液晶屏显示处理信息,完成后回到时间显示模式。在回到时间显示模式前,其他按键均无效。
当k4被按下时:进入上次数据查看模式,一共4种状态。当第一次按下时液晶屏显示上次测量的时间;第二次按下时,显示上次的测量标准:标准重量与可接受误差;第三次按下后,显示上次测量的总数与坏值总数;第四次按下时,显示屏提示是否要把数据发送到电脑端,此时若按下k8则发送,若再次按下k4则回到时间显示模式。在回到时间显示模式前,在此过程中除了第四次按下k6后可以选择按k8,其他按键均无效。
当k5被按下时:增加数值。只有上面相关的模式下按下才有效,其他时间都无效。
当k6倍按下时:减少数值。只有上面相关的模式下按下才有效,其他时间都无效。
当k7倍按下时:去皮操作。只有上面相关的模式下按下才有效,其他时间都无效。
当k8被按下时:进入温度模式或发送数据模式。当在k4第四次按下后按k8,则发送数据到电脑端,同时每成功发送一组数据(包括该数据的测量的序数和重量大小)指示灯状态反转一次;当在时间显示模式下按下k8,则进入温度显示模式;这俩种状态都要通过再次按下k8键返回时间显示模式,且在过程中其他模式均无效。
4)设计其他传感器与外设,包括滑动变阻器模拟的压力传感器,通过ADC0832芯片来进行A/D转换并把数据发送给单片机,温度传感器DS18B20进行温度检测,用DS1302时钟芯片提供准确时,用MAX232电平转换来做串口通讯,并设计一个蜂鸣器与蜂鸣器进行状态提示。

2.2程序框图:

51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第2张图片
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第3张图片
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第4张图片51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第5张图片

2.3程序一些结果仿真显示

时间显示模式
时间显示模式与时间修改模式的显示相同,结果如下:
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第6张图片
显示时间与电脑当前时间相同。

单个重量称量模式
这里只演示在超过最大量程时的情况,这里吧最大量程通过按键改为了3.47kg。
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第7张图片
此时指示灯亮,同时蜂鸣器响。

一批产品测量模式
如图,首先延时了选择标准重量和误差时的情景,然后显示了在记录数据时,指示灯状态指示。
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第8张图片
相关的参数选择好,就开始记录数值了:
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第9张图片

上次数据查看模式
如图显示,这里和前面预期结果相符合,在选择发送数据时,每成功发送一个指示灯状态改变,通过串口显示器可以看到数据的传输,最后一个图为全部数据传输成功。
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第10张图片
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第11张图片
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第12张图片
发送数据时指示灯亮:
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第13张图片
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第14张图片

温度查看模式
如图,可以通过改变DB18B20来实时显示温度:
51单片机设计多功能电子秤(显示时间、温度、存储、串口、报警装置等)_第15张图片

2.4已知BUG

1)温度只能显示正整数。这里只取正整数是因为上面提到的受到变量不能创建的限制。
2)记录的数据受限,只能记录246个详细数据(受到AT24C02内存的限制),同时数据不能超过255大小,这里并没有进行变量优化,来使0-255的大小可以去代表更多(但减少精度),也是受到了变量创建的限制,而proteus库中8位单片机最好的便只是89C55,而使用32位单片机又不划算。
3)在单次测量时,使用去皮功能后,就会有BUG,就是也能去皮,但是这时候报警的数值应该记录错误了
4)串口通信时,使用电脑接收的时候出现乱码,每次测试乱码也不同,感觉就是有数据发生了丢失的现象。

2.5收获和学习过程中的解决问题:

1)串口向计算机发送char类型的变量时,应该使(char类型变量+‘0’)变为他所代表的字符,如向LCD1602中写入数据一样。
2)对于记录数据的时间控制。考虑到现实中测量物品重量时物品会有不稳定重量的现象,于是在定时器中记录俩个重量,是1s前后检测到的,通过对比是否相同来判断当前是否稳定并记录处理,该时间可以调控。
3)在刚开使用串口通信时,发现并不能成功发送数据,仔细研究后发现是因为T0定时器与产生波特率的T1定时器互相干扰,故在串口通信时只T1工作,此时关闭T0的计数和中断,同时在T0工作时只在温度查看模式和时间显示模式每秒进行温度转换,测一批重量模式时启动,这样来减少CPU的处理,让单片机更稳定,节省资源。
4)随着程序的代码量的增大,发现以前的显示模式除了问题,于是做了简化:本来是每次进入显示处理都要读取IIC,发现此时读出的数据是错误的,后来改为了只在按下相应按键时读取一次,然后注意时序,跳出按键处理后就进入显示处理,使显示成功。
5)在串口发送数据时,字符串结尾要加“\r\n”才能进行换行,不能只输入’\n’。
6)存储和读取AT24C02时,读取温度时,及串口发送数据等要留有一定的时间间隔,否则容易出现失败。
7)最棘手的问题。因为工程的增加,导致变量也在增多,本来选用的是89C51,然后不够用便选用了89C55,最后程序设计完成后,查阅资料发现89C55与89C52的RAM大小相等,考虑实际成本变该用了89C52,在这个过程中因为RAM可存储变量,故保证工程一直在SMALL模式编译,使得程序读取变量在RAM中速度快,时序得到了保证。
8)时序逻辑很重要,有的时候改变了函数的执行位置便引起了不理想的结果,并且有时指令写错了一个括号的位置也引起了很久的查找,一定要认真看,单步调试。

2.6设想拓展

1)可以再显示时间的时候来让指示灯通过定时器产生PWM波,周期固定,但是占空比随着温度在一定的范围内改变而等比例从0-100进行改变,可以通过亮度来大体感知温度,可以使用89C52新增的T2来产生PWM波,但是受到变量创建的限制,还有时间的限制并没有进行拓展。
2)进行多机通信,做四五个主机实现我的这个多功能电子秤的功能,只不过是把数据通过串口来传输给一个主机,主机再进行拓展存储,这时PC端可以与主机通信,来获得丛机的数据;并且主机可以拓展更大的存储器,来存储更多的数据,同时主机配备密码锁,与液晶屏在本机可以查看想看到的数据并可以进行删除等数据操作,与指示灯,当有数据接收时则相应代表从机的指示灯亮,等人来按任意键关闭表示知道接收数据了,考虑到时间的限制还有难度,并没有进行拓展。
3)本打算实现个密码锁功能,但发现不实用在现在想要的系统上,若继续使用矩阵按键的话,有点鸡肋,按键太多但是也没用那么多,最后选择8个独立按键,但也因此受限无法完成许多拓展功能。

3部分代码

主要是库文件的代码!方便大家移植
主要是库文件的代码!方便大家移植
主要是库文件的代码!方便大家移植
主程序代码,就给几个核心的看看吧(代码写的很乱,希望以后看到能笑出声来)

3.1 LCD1602.h(不要c文件直接能用)

#ifndef LCD_CHAR_1602_2005_4_9
#define LCD_CHAR_1602_2005_4_9
#define uchar unsigned char
#define uint unsigned int

sbit lcdrs = P2 ^ 0;
sbit lcdrw = P2 ^ 1;
sbit lcden = P2 ^ 2;

void delay(uint z)		  //延时函数,此处使用晶振为11.0592MHz
{
    uint x, y;
    for(x = z; x > 0; x--)
        for(y = 110; y > 0; y--);
}

void write_com(uchar com)   //写入指令数据到 lcd
{
    lcdrw = 0;
    lcdrs = 0;
    P0 = com;
    delay(5);
    lcden = 1;
    delay(5);
    lcden = 0;
}

void write_data(uchar date) 	//写入字符显示数据到 lcd
{
    lcdrw = 0;
    lcdrs = 1;
    P0 = date;
    delay(5);
    lcden = 1;
    delay(5);
    lcden = 0;
}

void init1602()		//1602液晶初始化设定
{
    lcdrw = 0;
    lcden = 0;
    write_com(0x3C);
    write_com(0x0c);
    write_com(0x06);
    write_com(0x01);
    write_com(0x80);
}

void write_string(uchar *pp, uint n) //采用指针的方法输入字符,n为字符数目
{
    int i;
    for(i = 0; i < n; i++)
        write_data(pp[i]);
}
#endif

3.2 DS1302.h

#ifndef TIMER_DS1302
#define TIMER_DS1302

sbit  DS1302_CLK = P2 ^ 6;            //实时时钟时钟线引脚
sbit  DS1302_IO  = P2 ^ 7;            //实时时钟数据线引脚
sbit  DS1302_RST = P2 ^ 5;            //实时时钟复位线引脚
sbit  ACC0 = ACC ^ 0;		            //定义一个ACC的最低位,和最高位,在对ACC移位操作后,用于传输数据
sbit  ACC7 = ACC ^ 7;

typedef struct  SYSTEM_TIME
{
    unsigned char Second;
    unsigned char Minute;
    unsigned char Hour;
    unsigned char Week;
    unsigned char Day;
    unsigned char Month;
    unsigned char  Year;
    unsigned char DateString[9];  //用这两个字符串来放置读取的时间
    unsigned char TimeString[9];
} SYSTEMTIME;	//定义的时间类型结构体

#define AM(X)	X
#define PM(X)	(X+12)            	  // 转成24小时制
#define DS1302_SECOND	0x80		  //片内各位数据的地址
#define DS1302_MINUTE	0x82
#define DS1302_HOUR		0x84
#define DS1302_WEEK		0x8A
#define DS1302_DAY		0x86
#define DS1302_MONTH	0x88
#define DS1302_YEAR		0x8C
#define DS1302_RAM(X)	(0xC0+(X)*2)   	//用于计算 DS1302_RAM 地址的宏 


/******内部指令**************/
void DS1302InputByte(unsigned char d) 	//实时时钟写入一字节(内部函数)
{
    unsigned char i;
    ACC = d;
    for(i = 8; i > 0; i--)
    {
        DS1302_IO = ACC0;           	//相当于汇编中的 RRC
        DS1302_CLK = 1;
        DS1302_CLK = 0;		//写数据在上升沿,且先写低位再写高位
        ACC = ACC >> 1;     //因为在前面已经定义了ACC0 = ACC^0;以便再次利用DS1302_IO = ACC0;
    }
}

unsigned char DS1302OutputByte(void) 	//实时时钟读取一字节(内部函数)
{
    unsigned char i;
    for(i = 8; i > 0; i--)
    {
        ACC = ACC >> 1;         			//相当于汇编中的 RRC
        ACC7 = DS1302_IO;				//由低位到高位传播ACC7中的信息
        DS1302_CLK = 1;	                //读信息是在下降沿
        DS1302_CLK = 0;
    }
    return(ACC);
}
/********************************/


void Write1302(unsigned char ucAddr, unsigned char ucDa)	//ucAddr: DS1302地址, ucData: 要写的数据
{
    DS1302_RST = 0;
    DS1302_CLK = 0;
    DS1302_RST = 1;
    DS1302InputByte(ucAddr);       	// 地址,命令
    DS1302InputByte(ucDa);       	// 写1Byte数据
    DS1302_CLK = 1;
    DS1302_RST = 0;
}

unsigned char Read1302(unsigned char ucAddr)	//读取DS1302某地址的数据
{
    unsigned char ucData;
    DS1302_RST = 0;
    DS1302_CLK = 0;
    DS1302_RST = 1;
    DS1302InputByte(ucAddr | 0x01);      // 上升沿,写地址,命令
    ucData = DS1302OutputByte();         // 下降沿,读1Byte数据
    DS1302_CLK = 1;
    DS1302_RST = 0;
    return(ucData);						 //在上升沿之后做写操作,在下降沿之前做读操作
}

void DS1302_SetProtect(bit flag)        //是否写保护
{
    if(flag)
        Write1302(0x8E, 0x10);
    else
        Write1302(0x8E, 0x00);
}

void DS1302_SetTime(unsigned char Address, unsigned char Value)        // 设置时间函数
{
    DS1302_SetProtect(0);
    Write1302(Address, ((Value / 10) << 4 | (Value % 10))); //将十进制数转换为BCD码
}												//在DS1302中的与日历、时钟相关的寄存器存放的数据必须为BCD码形式

void DS1302_GetTime(SYSTEMTIME *Time)
{
    unsigned char ReadValue;
    ReadValue = Read1302(DS1302_SECOND);
    Time->Second = ((ReadValue & 0x70) >> 4) * 10 + (ReadValue & 0x0F);	//将BCD码转换为十进制数,此处为结构体操作

    ReadValue = Read1302(DS1302_MINUTE);
    Time->Minute = ((ReadValue & 0x70) >> 4) * 10 + (ReadValue & 0x0F);

    ReadValue = Read1302(DS1302_HOUR);
    Time->Hour = ((ReadValue & 0x70) >> 4) * 10 + (ReadValue & 0x0F);

    ReadValue = Read1302(DS1302_DAY);
    Time->Day = ((ReadValue & 0x70) >> 4) * 10 + (ReadValue & 0x0F);

    ReadValue = Read1302(DS1302_WEEK);
    Time->Week = ((ReadValue & 0x70) >> 4) * 10 + (ReadValue & 0x0F);

    ReadValue = Read1302(DS1302_MONTH);
    Time->Month = ((ReadValue & 0x70) >> 4) * 10 + (ReadValue & 0x0F);

    ReadValue = Read1302(DS1302_YEAR);
    Time->Year = ((ReadValue & 0x70) >> 4) * 10 + (ReadValue & 0x0F);
}
unsigned char *DataToBCD(SYSTEMTIME *Time)
{
    unsigned char  D[8];

    D[0] = Time->Second / 10 << 4 + Time->Second % 10; //将相关时间信息转换成二进制码以后存入数组D[]
    D[1] = Time->Minute / 10 << 4 + Time->Minute % 10;
    D[2] = Time->Hour / 10 << 4 + Time->Hour % 10;
    D[3] = Time->Day / 10 << 4 + Time->Day % 10;
    D[4] = Time->Month / 10 << 4 + Time->Month % 10;
    D[5] = Time->Week / 10 << 4 + Time->Week % 10;
    D[6] = Time->Year / 10 << 4 + Time->Year % 10;
    return D;
}
void DateToStr(SYSTEMTIME *Time)
{
    //将十进制数转换为液晶显示的ASCII值,即变为字符型,此函数为年月日信息
    Time->DateString[0] = Time->Year / 10 + '0';
    Time->DateString[1] = Time->Year % 10 + '0';
    Time->DateString[2] = '-';
    Time->DateString[3] = Time->Month / 10 + '0';
    Time->DateString[4] = Time->Month % 10 + '0';
    Time->DateString[5] = '-';
    Time->DateString[6] = Time->Day / 10 + '0';
    Time->DateString[7] = Time->Day % 10 + '0';
    Time->DateString[8] = '\0';
}

void TimeToStr(SYSTEMTIME *Time)
{
    //将十进制数转换为液晶显示的ASCII值,此处为时间信息
    Time->TimeString[0] = Time->Hour / 10 + '0';
    Time->TimeString[1] = Time->Hour % 10 + '0';
    Time->TimeString[2] = ':';
    Time->TimeString[3] = Time->Minute / 10 + '0';
    Time->TimeString[4] = Time->Minute % 10 + '0';
    Time->TimeString[5] = ':';
    Time->TimeString[6] = Time->Second / 10 + '0';
    Time->TimeString[7] = Time->Second % 10 + '0';
    Time->DateString[8] = '\0';
    //还未实现星期的显示转换,改为使用数值显示
}

/*uchar *WeekToStr(SYSTEMTIME Time)
{
  uint i;
  uchar *z;
  i=Time.Week ;
  switch(i)
  {
    case 1:z="sun";break;
    case 2:z="mon";break;
    case 3:z="tue";break;
    case 4:z="wen";break;
    case 5:z="thu";break;
    case 6:z="fri";break;
    case 7:z="sat";break;
  }

  return z;
}*/

void Initial_DS1302(void)
{
    unsigned char Second;
    Second = Read1302(DS1302_SECOND);
    if(Second & 0x80)	 //初始化时间
    {
        DS1302_SetTime(DS1302_SECOND, 0);
    }
}

void DS1302_TimeStop(bit flag)           // 是否将时钟停止
{
    unsigned char Data;
    Data = Read1302(DS1302_SECOND);
    DS1302_SetProtect(0);
    if(flag)
        Write1302(DS1302_SECOND, Data | 0x80);
    else
        Write1302(DS1302_SECOND, Data & 0x7F);
}
#endif

3.3 IIC.h 和 IIC.c

#ifndef __IIC_H__
#define __IIC_H__


#define uchar unsigned char 
#define uint unsigned int

sbit SCL=P2^4;
sbit SDA=P2^3;
// 从机位置为0xa0
void iic_write(uchar add,uchar date); //д²Ù×÷
uchar iic_read(uchar add);//¶Á²Ù×÷


#endif

#include "reg52.h"
#include "intrins.h"
#include "iic.h"

void delay7us()		//延时7us
{   _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
}

void start() //起始信号,这时数据线电平变化在时钟高电平时,下将沿有效
{   SCL = 1; //时钟线拉高,
    delay7us();
    SDA = 1; //数据线拉高
    delay7us(); //延时,延时应在有效范围内必须 >4.7us
    SDA = 0;	//拉低
    delay7us(); //延时,延时应在有效范围内必须 >4us
}

void stop()//终止信号	  这时数据线电平变化在时钟高电平时,,上升沿有效
{   //SDA=0;
    //delay7us();
    /// SCL=1;
    //delay7us();
    // SDA=1;
    //delay7us();
    SDA = 0; // 数据线先低
    delay7us();
    SCL = 1; //	时钟线再高
    delay7us();  //延时,延时应在有效范围内必须 >4us,SDA在高
    SDA = 1;	 //数据线拉高
    delay7us();//延时,延时应在有效范围内必须 >4.7us
}

void response()//应答信号,"0"
{   uchar i;
    SCL = 1;	 //时钟线拉高,
    delay7us(); ///延时,延时应在有效范围内必须 >4us
    while((SDA == 1) && (i < 200))i++; //等待应答,在 SCL>4us 内判断 SDA是否为“0”,为“0”表示应答,
    //在i<200内,未收到应答信号,就默认收到应答,并退出。
    SCL = 0; //时钟线拉低
    delay7us();
}

void noack() //非应答信号,“1”非应答也可不进行判断
{   uchar i;
    //SDA=1;
    // delay7us();
    SCL = 1;
    delay7us();//延时>4us,
    while((SDA != 1) && (i < 100))i++; //在 SCL>4us 内判断 SDA是否为“1”,为“1”表示非应答
    // 同上,
    SCL = 0;
    delay7us();
    //SDA=0;
}

void init_iic() //SCL,SDA初始化
{   SCL = 1;
    SDA = 1;
}

void write_byte(uchar date)	//写一字节
{   uchar i;
    /*      for(i=0;i<8;i++)
    {
    	date=date<<1;//移位,高位移入CY中
    	SCL=0;
        delay7us();
    	SDA=CY;
    	delay7us();
    	SCL=1;
    	delay7us();
      }

      SCL=0;
      delay7us();
      SDA=1;
      delay7us(); */

    SCL = 0;	//SCL拉低准备写入数据,SCL低电平时允许SDA数据变化(写入),SCL高电平时数据应保持稳定
    delay7us();	//延时
    for(i = 0; i < 8; i++)	 //循环8次写入一个字节
    {
        if(date & 0x80) //高位在前,先写高位
        {
            SDA = 1;   //写入 1
        }
        else
        {
            SDA = 0;   //写入 0
        }
        SCL = 1; //SCL拉高
        delay7us();	//延时
        date = date << 1; //移位,准备下一位数据
        SCL = 0; //SCL拉低,准备写下一位
        delay7us(); //
    }
    SDA = 1; //释放SDA数据线,以备接收应答信号“ 0 ”。
    delay7us();//
}

uchar read_byte() //读一个字节,
{   uchar i, k;
    SCL = 0;	 //
    delay7us();  //
    SDA = 1;	 //释放SDA,以备通信
    delay7us();  //
    for(i = 0; i < 8; i++) //循环8次读出一个字节
    {   SCL = 1; //SCL为1时,SDA数据应保持稳定
        delay7us();
        //k=(k<<1)|SDA;
        k = k << 1; //移位
        if(SDA) //检测SDA(某位数据)是否为1,先读高位
            k++;	  //如果为 1 就 k++写入1,相当以 k 的第0位加了一个 1,下次进入时,因为k先移了一位
        // 就移入了第1位,8次就将高位移入第8位,其余位同理。
        SCL = 0; //SCL低电平时允许SDA数据变化,即将数据读出
        delay7us();//延时
    }
    return k; //返回
}

void iic_write(uchar add, uchar date) //写操作
{   //init_iic();
    start(); //先发起始信号,操作之前必须
    write_byte(0xa0);//器件地址(1010 A0 A0 A0(000) R/W(0))(访问该芯片的地址)
    //前4位为固定地址,出厂时给定。3位用户自定义地址,最后位"0"表示写入,
    response(); //给应答信号
    write_byte(add); //写入地址(将数据写入的地址,任意地址)
    response(); //给应答信号
    write_byte(date);//写入的数据
    response(); //给应答信号
    stop(); //发终止信号

}
uchar iic_read(uchar add) //读操作
{   uchar dat;
    start(); //先发起始信号,操作之前必须
    write_byte(0xa0);//访问该芯片的地址(器件地址)
    response(); //给应答信号
    write_byte(add);// 访问该芯片内部的数据地址(写入数据的地址)
    response();//给应答信号
    start();  //先发起始信号,开始读,
    write_byte(0xa1);//器件地址(1010 A0 A0 A0(000) R/W(1))(访问该芯片的地址)
    //前4位为固定地址,出厂时给定。3位用户自定义地址,最后位"1"表示读,
    response();//给应答信号
    dat = read_byte(); //读
    noack();//非应答,读数据无需应答
    stop();//终止操作
    return dat;//返回数据
}

3.4 DS18B20.h(不要c文件直接能用)

#ifndef __DS18B20_H_
#define __DS18B20_H_


#include 
#include 
sbit IO_18B20 = P3 ^ 2; //DS18B20 通信引脚
/* 软件延时函数,延时时间(t*10)us */
void DelayX10us(unsigned char t)
{
    do {
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
    } while (--t);
}
/* 复位总线,获取存在脉冲,以启动一次读写操作 */
bit Get18B20Ack()
{
    bit ack;
    EA = 0; //禁止总中断
    IO_18B20 = 0; //产生 500us 复位脉冲
    DelayX10us(50);
    IO_18B20 = 1;
    DelayX10us(6); //延时 60us
    ack = IO_18B20; //读取存在脉冲
    while(!IO_18B20); //等待存在脉冲结束
    EA = 1; //重新使能总中断
    return ack;
}
/* 向 DS18B20 写入一个字节, dat-待写入字节 */
void Write18B20(unsigned char dat)
{
    unsigned char mask;
    EA = 0; //禁止总中断
    for (mask = 0x01; mask != 0; mask <<= 1) //低位在先,依次移出 8 个 bit
    {
        IO_18B20 = 0; //产生 2us 低电平脉冲
        _nop_();
        _nop_();
        if ((mask & dat) == 0) //输出该 bit 值
            IO_18B20 = 0;
        else
            IO_18B20 = 1;
        DelayX10us(6); //延时 60us
        IO_18B20 = 1; //拉高通信引脚
    }
    EA = 1; //重新使能总中断
}
/* 从 DS18B20 读取一个字节,返回值-读到的字节 */
unsigned char Read18B20()
{
    unsigned char dat;
    unsigned char mask;
    EA = 0; //禁止总中断
    for (mask = 0x01; mask != 0; mask <<= 1) //低位在先,依次采集 8 个 bit
    {
        IO_18B20 = 0; //产生 2us 低电平脉冲
        _nop_();
        _nop_();
        IO_18B20 = 1; //结束低电平脉冲,等待 18B20 输出数据
        _nop_(); //延时 2us
        _nop_();
        if (!IO_18B20) //读取通信引脚上的值
            dat &= ~mask;
        else
            dat |= mask;
        DelayX10us(6); //再延时 60us
    }
    EA = 1; //重新使能总中断
    return dat;
}
/* 启动一次 18B20 温度转换,返回值-表示是否启动成功 */
bit Start18B20()
{
    bit ack;
    ack = Get18B20Ack(); //执行总线复位,并获取 18B20 应答
    if (ack == 0) //如 18B20 正确应答,则启动一次转换
    {
        Write18B20(0xCC); //跳过 ROM 操作
        Write18B20(0x44); //启动一次温度转换
    }
    return ~ack; //ack==0 表示操作成功,所以返回值对其取反
}
/* 读取 DS18B20 转换的温度值,返回值-表示是否读取成功 */
bit Get18B20Temp(int *temp)
{
    bit ack;
    unsigned char LSB, MSB; //16bit 温度值的低字节和高字节
    ack = Get18B20Ack(); //执行总线复位,并获取 18B20 应答
    if (ack == 0) //如 18B20 正确应答,则读取温度值
    {
        Write18B20(0xCC); //跳过 ROM 操作
        Write18B20(0xBE); //发送读命令
        LSB = Read18B20(); //读温度值的低字节
        MSB = Read18B20(); //读温度值的高字节
        *temp = ((int)MSB << 8) + LSB; //合成为 16bit 整型数
    }
    return ~ack; //ack==0 表示操作应答,所以返回值为其取反值
}
#endif

3.5 ADC8032的读取函数:

sbit CS=P3^5;      //ADC0832片选
sbit CLK=P3^6;     //ADC0832时钟
sbit DIO=P3^7;     //ADC0832输入输出

/************************************* AD转换函数******************************************************/
uchar weight_deal(uchar ch) // 封装完好,同时用于单个检测和多个的检测  
{
	uchar i,dat1=0,dat2=0;

	CS  = 0; _nop_(); _nop_();         					//片选使能,低电平有效
	CLK = 0; _nop_(); _nop_();          					//芯片时钟输入
	DIO = 1; _nop_(); _nop_();
	CLK = 1; _nop_(); _nop_();
	//第1个下降沿之前,设DI=1/0
	//选择单端/差分(SGL/DIF)模式中的单端输入模式	
	CLK = 0;DIO = 1; _nop_(); _nop_();
	CLK = 1;         _nop_(); _nop_();
	//第2个下降沿之前,设置DI=0/1,选择CHO/CH1

	CLK = 0;

	if(ch==0)
		DIO = 0; 	//通道0 内部电压测试
	else DIO = 1;	//通道1 
	
	_nop_(); _nop_(); 

	CLK = 1;		 _nop_(); _nop_();
	//第3个下降沿之前,设置DI=1
	CLK = 0;DIO = 1; _nop_(); _nop_();
   //第4-11个下降沿读数据(MSB->LSB)
	for(i=0;i<8;i++)
	{
	 	CLK = 1; _nop_(); _nop_();
		CLK = 0; _nop_(); _nop_();
		dat1 = dat1 << 1 | DIO;		
	}
	//第11-18个下降沿读数据(LSB->MSB)
	for(i=0;i<8;i++)
	{
		CLK = 1; _nop_(); _nop_();
		CLK = 0; _nop_(); _nop_();
	 	dat2 = dat2 << ((uchar)(DIO)<<i);
	}
	CS = 1;//取消片选一个周期结束
	//如果MSB->LSB和LSB->MSB读取的结果相同,则返回读取的结果,否则返回0
	return dat1;
//	return (dat1 == dat2) ? dat1:0;//取消校验
	
	
}

本来想更新我的键盘处理函数和显示函数还有发送函数的,但一看这就这么多字了- -写的还很乱,就给一个键盘处理函数吧,感兴趣可以私聊

/*******************************************按键检测处理函数**************************************************/
void key_deal()
{
	static uchar tmp1=0;
	static uchar tmp2=0;
	char i=0;
	ET0 = 1;
	TR0 = 1;
	if(k1==0)
	{
		delayms(10);
		if(k1==0 && key_status[1]==0 && key_status[2]==0 && key_status[3]==0 && key_status[7]==0)
		{	
		//		只在时间模式下进行温度采集 和温度显示模式下
				
			key_status[0]++;
			if(key_status[0]==7){key_status[0]=0;} // 最多按7次可以修改年的大小时间
			write_com(0x01); // 清屏 防止切换模式后花屏
		}
		
		while(!k1);
	}
	
	if(k2==0)
		{
		delayms(10);
		if(k2==0 && key_status[0]==0 && key_status[2]==0 && key_status[3]==0 && key_status[7]==0)
		{
			ET0 = 0;
			TR0 = 0;
			key_status[1]++;
			if(key_status[1]==2)
				{
					// 初始化  同时初始化指示灯和蜂鸣器 防止bug
					key_status[1]=0;
					led = 1;
					beep = 1;
					skin = 0;
				}
			write_com(0x01); // 清屏 防止切换模式后花屏
		}
		
		while(!k2);
	}
	if(k3==0)
		{
		delayms(10);
		if(k3==0 && key_status[0]==0 && key_status[1]==0 && key_status[3]==0 && key_status[7]==0)
		{
			
			key_status[2]++;
			if(key_status[2]==3)
			{
				  ET0 = 1;
				  TR0 = 1;
					// 打开串口,每次成功记录后,就把数据发送到PC端
					
				  skin=0; 
					sum = 0;
					bad = 0;
				  
				  
			}
			if(key_status[2]==4)
			{
				
				ET0 = 0;
				TR0 = 0;
				led = 1; // 结束后熄灭led灯

				skin=0;
				// 吧数字存储进去
				iic_write(1,sum);
				delayms(200);
				iic_write(2,bad);
				delayms(200);
				//从3-6存放结束时的时间。
				iic_write(3,adjusted.Minute);
				delayms(200);
				iic_write(4,adjusted.Hour);
				delayms(200);
				iic_write(5,adjusted.Day);
				delayms(200);
				iic_write(6,adjusted.Month);
				delayms(200);
				iic_write(7,choice_weight);
				delayms(200);
				iic_write(8,choice_error);
				 // 由显示屏的相关状态置0,
				
				
			}
			
		
			write_com(0x01); // 清屏 防止切换模式后花屏
		}
		
		while(!k3);
	}
	
	
	if(k4==0)
	{
		delayms(10);
		if(k4==0 && key_status[0]==0 && key_status[1]==0 && key_status[2]==0 && key_status[7]==0) // 判断如果现在是显示时间的模式的话,那么我就显示
		{
			ET0 = 0;
			TR0 = 0;
			key_status[3]++;
			switch(key_status[3])
			{
				case 1:{pro_month=iic_read(6);delayms(3);pro_day=iic_read(5);delayms(3);pro_hour=iic_read(4);delayms(3);pro_minute=iic_read(3);delayms(3);break;}
				case 2:{choice_error = iic_read(8);delayms(3);choice_weight=iic_read(7);delayms(3);break;}
				case 3:{sum = iic_read(1);delayms(3); bad = iic_read(2);break;}
				
				default:break;
			}
			if(key_status[3]==5)key_status[3]=0;  // 四个模式 显示当时的时间,然后再显示当时的测量的标准(误差标准及标准重量),再显示总数据;接着再按则询问是否要详细的数据,若按确定按钮,则串口发送
			write_com(0x01); // 清屏 防止切换模式后花屏
		}
	}
	
	
	
	if(k5==0) // 增加被按下
	{
		delay(10);
		if(k5==0 & (key_status[0]!=0 | key_status[1]!=0 | key_status[2]!=0))
		{
			ET0 = 0;
			TR0 = 0;
			// 一种一种情况来判断
			if(key_status[0]!=0)
			{
			switch(key_status[0])
			{
			case 1:adjusted.Second++;break;
			case 2:adjusted.Minute++;break;
			case 3:adjusted.Hour++;break;
			case 4:adjusted.Day++;break;
			case 5:adjusted.Month++;break;
			case 6:adjusted.Year++;break;
			default: break;
		  }
			}

			
			if(key_status[1]!=0)
			{
				maxweight++;
			}
			
			if(key_status[2]!=0)
			{
				switch(key_status[2])
					{
						case 1:{if(tmp1!=5){tmp1++;}choice_weight = steady_weight[tmp1];break;}
						case 2:{if(tmp2!=2){tmp2++;}choice_error = error[tmp2];break;}
						default:break;	
					}
			}

		while(!k5);
		}
	}
	
	if(k6==0)
	{
		delayms(10);
	
		if(k6==0 && (key_status[0]!=0 | key_status[1]!=0 | key_status[2]!=0))
		{ET0 = 0;
			TR0 = 0;
			if(key_status[0]!=0)
			{
				switch(key_status[0])
			{
			case 1:adjusted.Second--;break;
			case 2:adjusted.Minute--;break;
			case 3:adjusted.Hour--;break;
			case 4:adjusted.Day--;break;
			case 5:adjusted.Month--;break;
			case 6:adjusted.Year--;break;
			default: break;
			}
			}
			
			
			if(key_status[1]!=0)
			{
				maxweight--;
			}
			
			if(key_status[2]!=0)
			{
				switch(key_status[2])
				{
				case 1:{if(tmp1!=0)tmp1--;choice_weight = steady_weight[tmp1];break;}
				case 2:{if(tmp2!=0)tmp2--;choice_error = error[tmp2];break;}
				default:break;
				}
				
			}
		while(!k6);
		}
	}
	
	if(k7==0) // 去皮
	{
		delayms(10);
		if(k7==0 && key_status[1] ==1) // 只有在单次称重下去皮模式才起作用
		{
			ET0 = 0;
			TR0 = 0;
			skin = weight; // 保存皮的重量
			weight = 0; //皮给去了嘛
			key_status[6]++;
			if(key_status[6]==2)key_status[6]=0;
		
		while(!k7);
		}
	}
	if(k8==0)
	{
		delayms(10);
		if(k8==0 && (key_status[3]==4 | key_status[7]==2))
		{
			key_status[3]=0;
			key_status[7]++;
			
			if(key_status[7]==3){key_status[7]=0;}
			
			write_com(0x01); // 清屏 防止切换模式后花屏
			while(!k8);
		}
		else if(k8==0 && key_status[0]==0 && key_status[1]==0 && key_status[2]==0 && key_status[3]==0) // k8的第二种模式,只有在按下的时候才能显示温度
		{	// 为了单独区分出来一种转态
			if(key_status[7]==0)key_status[7]=6;
			else key_status[7]=0;
			while(!k8); // 直到被按下
		}
			
			write_com(0x01);
		}
	}

好了,以上就是全部了,希望其中的思路和能用的库文件可以帮到大家,也希望大家可以做一个自己喜欢的课程设计项目!思维的乐趣则是人生了去中最重要的一种这是最近我读的书中的一句话,虽原意不是如此,但我觉得学习中也有许多这样的乐趣。当时我做的时候花了俩天就弄完了,有点急,主要是忙着复习和其他期末的事情,真想再好好研究研究我的小设计,哈哈哈
有问题欢迎留言:)
2020年7月1日

你可能感兴趣的:(51单片机)