【优秀课设】基于光敏电阻的C51单片机LCD显示照度计(含成品硬件演示)

【优秀课设】基于光敏电阻的C51单片机LCD显示照度计(含成品硬件演示)

前言

基于光敏电阻的单片机LCD显示照度计。顾名思义就是基于CdS光敏电阻的特性,利用51单片机,测量出所接CdS光敏电阻两端电压,并通过CdS光敏电阻的特性,换算为光照度,在LCD1602显示屏上显示CdS光敏电阻电压和所对应的光照度。

资源:

download.csdn.net/download/weixin_53403301/87106286
download.csdn.net/download/weixin_53403301/15492326

在这里插入图片描述
在这里插入图片描述

硬件展示

在这里插入图片描述
在这里插入图片描述

#include <reg52.h>
#define uint unsigned int 
#define uchar unsigned char
sbit _st= P2^5;
sbit _eoc=P2^6;
sbit _oe=P2^7;

uchar ad_result=0;
int vol,lux;
float realv;

sbit rs=P2^0;		  //¶¨ÒåLCD½Ó¿Ú
sbit rw=P2^1;
sbit en=P2^2;

uchar code t1[]={"GD20181109010ZJR"};		  //³õʼLCDÆÁÄ»ÉÏÐÐÏÔʾ °à¼¶ ÐÕÃû
uchar code t2[]={"     Lux .   Vol"};		  //ÏÂÐÐÏÔʾ ѧºÅ µ¥Î»
void delay(uint ms)		  //ÑÓʱ
{
 uint i,j;
 for(i=ms;i>0;i--)
 for(j=110;j>0;j--);
}

void writelcd_cmd(uchar cmd)//Ïò1602Òº¾§ÏÔʾÆ÷ÀïдÈëÃüÁîµÄº¯Êý
{
en=0;
rs=0;
rw=0;
delay(1);
P0=cmd;		  //P0¿ÚÊä³ö
en=1;
delay(1);
en=0;
}

void writelcd_dat(uchar dat)//Ïò1602Òº¾§ÏÔʾÆ÷ÀïдÈëÊý¾ÝµÄº¯Êý
{
en=0;
rs=1;
rw=0;
delay(1);
P0=dat;
en=1;
delay(1);
en=0;
}

void lcd_init()	 //³õʼ»¯1602Òº¾§ÏÔʾÆ÷µÄº¯Êý
{
uchar i,j;
writelcd_cmd(0x38);	 //ÇåÆÁÏÔʾ
delay(5);
writelcd_cmd(0x38);
delay(5);
writelcd_cmd(0x38);
writelcd_cmd(0x08);
writelcd_cmd(0x01);
writelcd_cmd(0x06);
writelcd_cmd(0x0c);
writelcd_cmd(0x80);
	
for(i=0;i<16;i++)
{
writelcd_dat(t1[i]);		  //ÿһλÒԴ˶ÁÈ¡×ÖÄ£t1²¢ÏÔʾ×Ö·û
}
writelcd_cmd(0xc0);
for(j=0;j<16;j++)		  //ÒÀ´Î¶ÁÈ¡t2²¢ÏÔʾ
{
writelcd_dat(t2[j]);
}
}

void display(uint dat,uchar add)
{
uchar bai,shi ,ge,sfen;
bai=dat/1000;
shi=dat/100%10;
ge=dat/10%10;
sfen=dat%10;	
writelcd_cmd(add);
writelcd_dat(bai+0x30);
writelcd_cmd(add+2);
writelcd_dat(shi+0x30);
writelcd_dat(ge+0x30);
writelcd_dat(sfen+0x30);
}

void display2(uint dat,uchar add)
{
uchar bai,shi ,ge,sfen,wan;
wan=dat/10000;
bai=dat/1000&10;
shi=dat/100%10;
ge=dat/10%10;
sfen=dat%10;	
writelcd_cmd(add);
writelcd_dat(wan+0x30);
writelcd_dat(bai+0x30);
writelcd_dat(shi+0x30);
writelcd_dat(ge+0x30);
writelcd_dat(sfen+0x30);
}

void main(void)
{
	lcd_init();
	EA=1;
	TH0=(65536-50000)/256;    //ÄÚ²¿ÖжÏT0³õʼ»¯
    TL0=(65536-50000)%256;    
    TR0=1;    //ÄÚ²¿ÖжÏT0¿ªÆô
    ET0=1;
	while(1)
	{
		
		_st=0;_st=1;_st=0;
		while(!_eoc);
		_oe=1;
		ad_result=P1;
		_oe=0;
		
		vol=5000-(100.0*ad_result/5.1);
		realv=vol/1000.0;
		lux=864/((10*realv)/(5-realv)-1.36);
		if(lux<=30000 && lux>=1)
		{	
		lux=864/((10*realv)/(5-realv)-1.36);
		display2(lux,0xc0);
		display(vol,0xc8);
		}
		else
		{		
		display(vol,0xc8);
		}
	}
}

void count0() interrupt 1  		  //T0ÖÐ¶Ï Ê±¼ä¼Æʱ³ÌÐò
{  
	P3=0xff;
	
	P3=0xfe;
	
}

将芯片、显示器等硬件按照电路原理图放置、焊接好,如《附录3 硬件实物图》所示。
由于没有引脚插座,故所有走线都是用导线焊接的,比较麻烦且不够美观,但还是可以完全实现光照度计的功能。
用单片机开发板和STC-ISP软件将Keil软件编译的.HEX文件烧写到89C52RC芯片中,插入芯片槽中,利用杜邦线连接开发板上的VCC和GND口,即可开始硬件测试。
利用螺丝刀调节位于LCD1602显示屏VL端的电位器,使其能清晰地显示图像。
开始测试时,其显示的光照度为当前室内光照度,光照度值为136lx,对应电压值为2.275V,如图3-12所示。
在这里插入图片描述

图3-12 硬件测试
用一张白色A4纸折叠一次后,盖住光敏电阻,显示的光照度为最低光照度值,光照度值为1lx,对应电压值为4.941V,如图3-13所示。
在这里插入图片描述

图3-13 遮住光敏电阻的硬件测试
用三个手机的手电筒近距离照射光敏电阻后,显示的光照度为较高的光照度值,光照度值为2210lx,对应电压值为0.745V,如图3-14所示。
在这里插入图片描述

图3-14 用三个手机手电筒照射光敏电阻的硬件测试

附录:压缩字符串、大小端格式转换

压缩字符串

首先HART数据格式如下:
在这里插入图片描述
在这里插入图片描述
重点就是浮点数和字符串类型
Latin-1就不说了 基本用不到

浮点数

浮点数里面 如 0x40 80 00 00表示4.0f

在HART协议里面 浮点数是按大端格式发送的 就是高位先发送 低位后发送

发送出来的数组为:40,80,00,00

但在C语言对浮点数的存储中 是按小端格式来存储的 也就是40在高位 00在低位
浮点数:4.0f
地址0x1000对应00
地址0x1001对应00
地址0x1002对应80
地址0x1003对应40

若直接使用memcpy函数 则需要进行大小端转换 否则会存储为:
地址0x1000对应40
地址0x1001对应80
地址0x1002对应00
地址0x1003对应00

大小端转换:

void swap32(void * p)
{
   uint32_t *ptr=p;
   uint32_t x = *ptr;
   x = (x << 16) | (x >> 16);
   x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);

   *ptr=x;
}

压缩Packed-ASCII字符串

本质上是将原本的ASCII的最高2位去掉 然后拼接起来 比如空格(0x20)
四个空格拼接后就成了
1000 0010 0000 1000 0010 0000
十六进制:82 08 20
对了一下表 0x20之前的识别不了
也就是只能识别0x20-0x5F的ASCII表
在这里插入图片描述

压缩/解压函数后面再写:

//传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
uint8_t Trans_ASCII_to_Pack(uint8_t * str,uint8_t * buf,const uint8_t str_len)
{
   if(str_len%4)
   {
      return 0;
   }
	 
   uint8_t i=0;
   memset(buf,0,str_len/4*3);	  
   for(i=0;i<str_len;i++)
   {
      if(str[i]==0x00)
      {
         str[i]=0x20;
      }
   }

   for(i=0;i<str_len/4;i++)
   {
      buf[3*i]=(str[4*i]<<2)|((str[4*i+1]>>4)&0x03);
      buf[3*i+1]=(str[4*i+1]<<4)|((str[4*i+2]>>2)&0x0F);
      buf[3*i+2]=(str[4*i+2]<<6)|(str[4*i+3]&0x3F);
   }

   return 1;
}

//传入的字符串和数字必须提前声明 且字符串大小至少为str_len 数组大小至少为str_len%4*3 str_len必须为4的倍数
uint8_t Trans_Pack_to_ASCII(uint8_t * str,uint8_t * buf,const uint8_t str_len)
{
   if(str_len%4)
   {
      return 0;
   }

   uint8_t i=0;

   memset(str,0,str_len);

   for(i=0;i<str_len/4;i++)
   {
      str[4*i]=(buf[3*i]>>2)&0x3F;
      str[4*i+1]=((buf[3*i]<<4)&0x30)|(buf[3*i+1]>>4);
      str[4*i+2]=((buf[3*i+1]<<2)&0x3C)|(buf[3*i+2]>>6);
      str[4*i+3]=buf[3*i+2]&0x3F;
   }

   return 1;
}


大小端转换

在串口等数据解析中 难免遇到大小端格式问题

什么是大端和小端

所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。

简单来说:大端——高尾端,小端——低尾端

举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:

1)大端模式:

低地址 -----------------> 高地址

0x12 | 0x34 | 0x56 | 0x78

2)小端模式:

低地址 ------------------> 高地址

0x78 | 0x56 | 0x34 | 0x12

可见,大端模式和字符串的存储模式类似。

数据传输中的大小端

比如地址位、起止位一般都是大端格式
如:
起始位:0x520A
则发送的buf应为{0x52,0x0A}

而数据位一般是小端格式(单字节无大小端之分)
如:
一个16位的数据发送出来为{0x52,0x0A}
则对应的uint16_t类型数为: 0x0A52

而对于浮点数4.0f 转为32位应是:
40 80 00 00

以大端存储来说 发送出来的buf就是依次发送 40 80 00 00

以小端存储来说 则发送 00 00 80 40

由于memcpy等函数 是按字节地址进行复制 其复制的格式为小端格式 所以当数据为小端存储时 不用进行大小端转换
如:

uint32_t dat=0;
uint8_t buf[]={0x00,0x00,0x80,0x40};
   memcpy(&dat,buf,4);
   float f=0.0f;
   f=*((float*)&dat); //地址强转
   printf("%f",f);

或更优解:

   uint8_t buf[]={0x00,0x00,0x80,0x40};   
   float f=0.0f;
   memcpy(&f,buf,4);

而对于大端存储的数据(如HART协议数据 全为大端格式) 其复制的格式仍然为小端格式 所以当数据为小端存储时 要进行大小端转换
如:

uint32_t dat=0;
uint8_t buf[]={0x40,0x80,0x00,0x00};
   memcpy(&dat,buf,4);
   float f=0.0f;
   swap32(&dat); //大小端转换
   f=*((float*)&dat); //地址强转
   printf("%f",f);

或:

uint8_t buf[]={0x40,0x80,0x00,0x00};
   memcpy(&dat,buf,4);
   float f=0.0f;
   swap32(&f); //大小端转换
   printf("%f",f);

或更优解:

uint32_t dat=0;
uint8_t buf[]={0x40,0x80,0x00,0x00};
   float f=0.0f;
   dat=(buf[0]<<24)|(buf[0]<<16)|(buf[0]<<8)|(buf[0]<<0)
   f=*((float*)&dat);

总结

固 若数据为小端格式 则可以直接用memcpy函数进行转换 否则通过移位的方式再进行地址强转

对于多位数据 比如同时传两个浮点数 则可以定义结构体之后进行memcpy复制(数据为小端格式)

对于小端数据 直接用memcpy写入即可 若是浮点数 也不用再进行强转

对于大端数据 如果不嫌麻烦 或想使代码更加简洁(但执行效率会降低) 也可以先用memcpy写入结构体之后再调用大小端转换函数 但这里需要注意的是 结构体必须全为无符号整型 浮点型只能在大小端转换写入之后再次强转 若结构体内采用浮点型 则需要强转两次

所以对于大端数据 推荐通过移位的方式来进行赋值 然后再进行个别数的强转 再往通用结构体进行写入

多个不同变量大小的结构体 要主要字节对齐的问题
可以用#pragma pack(1) 使其对齐为1
但会影响效率

大小端转换函数

直接通过对地址的操作来实现 传入的变量为32位的变量
中间变量ptr是传入变量的地址

void swap16(void * p)
{
   uint16_t *ptr=p;
   uint16_t x = *ptr;
   x = (x << 8) | (x >> 8);

   *ptr=x;
}

void swap32(void * p)
{
   uint32_t *ptr=p;
   uint32_t x = *ptr;
   x = (x << 16) | (x >> 16);
   x = ((x & 0x00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF);

   *ptr=x;
}

void swap64(void * p)
{
   uint64_t *ptr=p;
   uint64_t x = *ptr;
   x = (x << 32) | (x >> 32);
   x = ((x & 0x0000FFFF0000FFFF) << 16) | ((x >> 16) & 0x0000FFFF0000FFFF);
   x = ((x & 0x00FF00FF00FF00FF) << 8) | ((x >> 8) & 0x00FF00FF00FF00FF);

   *ptr=x;
}
  • 14
    点赞
  • 122
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

网易独家音乐人Mike Zhou

光电帝国,光联万物!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值