单片机综合小项目

一、单片机做项目常识

1.行业常识

2.方案选型

3.此项目定位和思路

二、单片机的小项目介绍

1.项目名称:基于51单片机的温度报警器

(1)主控:stc51;

(2)编程语言:C语言

(3)开发环境:Keil

(4)1602屏显示时间和温度;当温度超过预定值时蜂鸣器和电机工作报警

(5)系统人机界面:矩阵按键或者红外遥控器:修改时间

2.硬件资源分配

优先满足硬件上已经接好的引脚

1602屏幕 P0;P2^7  ;P2^5;P2^6

4*4按键 P2

串口 P3.0 P3.1

IR(红外遥控器):P3^2

传感器:DS18B20  P3.7 

                DS1302 P3.4 P3.6

步进电机(四线双极性)P1.0---P1.5

蜂鸣器 P1.7

3.项目流程

(1)编写,移植,封装,测试顶层硬件模块操作库

(2)梳理,定义应用层功能

(3)逐个实现各功能,并联合调试,测试功能是否正常

(4)实现测试使用,并解决bug持续维护

4.一些小问题

5.项目分层

 分2层的体系:驱动层【低层硬件】,应用层【用户交互】

分3层的体系:底层驱动层【硬件--->driver】。上层驱动层【硬件和功能--->model】。应用层---->app

三、构成建立及框架搭建

1.基本搭建

lst

src

        app:高层时序【main存放】

        driver :低层时序

        include:基本的全局变量

obj

2.端口分配检查确定

1602屏幕 P0;P1^4  ;   P1.5    ;    P 1.6

4*4按键 P2

串口 P3.0 P3.1

IR(红外遥控器):P3^2

传感器:DS18B20  P3.7 

                DS1302 P3.4 P3.6 P3.5

步进电机(四线双极性)P1.0---P1.3

蜂鸣器 P1.6

四、第一个模块:串口

1.移植并调试确认基本功能

uart.c


#include"uart.h"//串口初始化函数
//预设计一个串口条件:8位数据位,1位停止位,0校验位,波特率9600
//初始化的主要工作是设置相关的寄存器
//使用晶振为11.0592MHz
//CPU工作在12T模式下void uart_init(){//使用8bit串行接口SCON=0x50;//波特率不加倍PCON=0x00;//波特率相关设置TMOD=0x20;//设置T1在模式2TL1=249;		//设定定时初值TH1 = 249;		//设定定时器重装值TR1=1;//开启T1,开始工作ES=1;//开启串行中断允许位EA=1;//开启全部中断}//串口发送单个字符
void uart_send_byte(unsigned char a){//发送一个字节SBUF=a;//查看当然串口是否在忙//根据SCON中的TI位可以判断当前串口是否在忙//如果数据8位发送结束,则硬件自动将TI=1,则TI=0表示程序还没有发送结束if(!TI)//软件复位TITI=0;
}void uart_send_string(unsigned char *str)
{while (*str != '\0'){uart_send_byte(*str);		// 发送1个字符str++;						// 指针指向下一个字符}
}

2.封装

1.何为封装

(1)隐藏

(2)保护

2.封装低层接口实践

uart.h
//开头2行和最后1行加起来构成一种格式,这种格式利用了c语言的预处理中的条件编译技术,
//实现的效果就是防止该头文件被重复包含构成的错误
#ifndef __UART__H__
#define __UART__H__
#include<reg51.h>//串口初始化函数
//预设计一个串口条件:8位数据位,1位停止位,0校验位,波特率9600
//初始化的主要工作是设置相关的寄存器
//使用晶振为11.0592MHz
//CPU工作在12T模式下
void uart_init();//串口发送单个字符
void uart_send_byte(unsigned char a);
//串口发送字符串
void uart_send_string(unsigned char *str);//延时函数
void  Delay2000ms();		//@11.0592MHz#endif

五、DS18B20移植(温度显示)

1.static

static void Delay750us():表示只能在该文件内部使用

2.高层时序

初始化函数将复位和检测是否存在分为两个函数,方便封装

/*******************************************************************************
* 函 数 名         : ds18b20_reset
* 函数功能		   : 复位DS18B20  
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void ds18b20_reset(void)
{DS18B20_PORT=0;	//拉低DQdelay_10us(75);	//拉低750usDS18B20_PORT=1;	//DQ=1delay_10us(2);	//20US
}/*******************************************************************************
* 函 数 名         : ds18b20_check
* 函数功能		   : 检测DS18B20是否存在
* 输    入         : 无
* 输    出         : 1:未检测到DS18B20的存在,0:存在
*******************************************************************************/
u8 ds18b20_check(void)
{u8 time_temp=0;while(DS18B20_PORT&&time_temp<20)	//等待DQ为低电平{time_temp++;delay_10us(1);	}if(time_temp>=20)return 1;	//如果超时则强制返回1else time_temp=0;while((!DS18B20_PORT)&&time_temp<20)	//等待DQ为高电平{time_temp++;delay_10us(1);}if(time_temp>=20)return 1;	//如果超时则强制返回1return 0;
}/*******************************************************************************
* 函 数 名         : ds18b20_start
* 函数功能		   : 开始温度转换
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
//转换命令
void ds18b20_start(void)
{ds18b20_reset();//复位ds18b20_check();//检查DS18B20ds18b20_write_byte(0xcc);//SKIP ROMds18b20_write_byte(0x44);//转换命令	
}/*******************************************************************************
* 函 数 名         : ds18b20_init
* 函数功能		   : 初始化DS18B20的IO口 DQ 同时检测DS的存在
* 输    入         : 无
* 输    出         : 1:不存在,0:存在
*******************************************************************************/ 
u8 ds18b20_init(void)
{ds18b20_reset();return ds18b20_check();	
}/*******************************************************************************
* 函 数 名         : ds18b20_read_temperture
* 函数功能		   : 从ds18b20得到温度值
* 输    入         : 无
* 输    出         : 温度数据
*******************************************************************************/
float ds18b20_read_temperture(void)
{float temp;u8 dath=0;u8 datl=0;u16 value=0;//开始转换:开启转换命令ds18b20_start();//将各个电线置为默认电平ds18b20_reset();//复位//判断当前程序是否在忙ds18b20_check();//发送读取温度命令ds18b20_write_byte(0xcc);//SKIP ROMds18b20_write_byte(0xbe);//读存储器datl=ds18b20_read_byte();//低字节dath=ds18b20_read_byte();//高字节value=(dath<<8)+datl;//合并为16位数据if((value&0xf800)==0xf800)//判断符号位,负温度{value=(~value)+1; //数据取反再加1temp=value*(-0.0625);//乘以精度	}else //正温度{temp=value*0.0625;	}return temp;
}

3.遇到的问题

问题:double t=24.5;要用串口把24.5打印出来给串口助手去显示

串口助手显示方式有2种:二进制方式和文本方式。文本方式最直观,但是需要通过串口去发送的不是double,不是int,而是ASCII码的字符串

意思是:想要看到25.4,的uart_send_string("25.4");

所以我们需要一个函数,能够把double类型的t,给转成对应的字符串来去给串口显示

lcd1602.c

// 显示类似于24.5这种的double类型的数字
void LcdShowDouble(unsigned char x, unsigned char y, double d)     
{// 第一步:将double d转成字符串strunsigned char str[5] = {0};// 第1步:先由double的25.4得到uint的254unsigned int tmp = (unsigned int)(d * 10);	unsigned char c = 0;// 第2步:由/和%操作来得到2、5、4// 第3步:将2、5、4对应的ASCII码放到字符串中去,完成c = (unsigned char)(tmp / 100);str[0] = c + 48;tmp = tmp % 100;		// 运算后tmp=54c = (unsigned char)(tmp / 10);		// c = 5str[1] = c + 48;str[2] = '.';tmp = tmp % 10;		// 运算后tmp=4c = (unsigned char)(tmp / 1);		// c = 4str[3] = c + 48;str[4] = '\0';// 第二步:显示strLcdShowStr(x, y, str);
}

ds18b20

#include "ds18b20.h"
#include "intrins.h"/*******************************************************************************
* 函 数 名       : delay_10us
* 函数功能		 : 延时函数,ten_us=1时,大约延时10us
* 输    入       : ten_us
* 输    出    	 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{while(ten_us--);	
}/*******************************************************************************
* 函 数 名       : delay_ms
* 函数功能		 : ms延时函数,ms=1时,大约延时1ms
* 输    入       : ms:ms延时时间
* 输    出    	 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{u16 i,j;for(i=ms;i>0;i--)for(j=110;j>0;j--);
}
/*******************************************************************************
* 函 数 名         : ds18b20_reset
* 函数功能		   : 复位DS18B20  
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
void ds18b20_reset(void)
{DS18B20_PORT=0;	//拉低DQdelay_10us(75);	//拉低750usDS18B20_PORT=1;	//DQ=1delay_10us(2);	//20US
}/*******************************************************************************
* 函 数 名         : ds18b20_check
* 函数功能		   : 检测DS18B20是否存在
* 输    入         : 无
* 输    出         : 1:未检测到DS18B20的存在,0:存在
*******************************************************************************/
u8 ds18b20_check(void)
{u8 time_temp=0;while(DS18B20_PORT&&time_temp<20)	//等待DQ为低电平{time_temp++;delay_10us(1);	}if(time_temp>=20)return 1;	//如果超时则强制返回1else time_temp=0;while((!DS18B20_PORT)&&time_temp<20)	//等待DQ为高电平{time_temp++;delay_10us(1);}if(time_temp>=20)return 1;	//如果超时则强制返回1return 0;
}
/*******************************************************************************
* 函 数 名         : ds18b20_read_bit
* 函数功能		   : 从DS18B20读取一个位
* 输    入         : 无
* 输    出         : 1/0
*******************************************************************************/
u8 ds18b20_read_bit(void)
{u8 dat=0;DS18B20_PORT=0;_nop_();_nop_();DS18B20_PORT=1;	_nop_();_nop_(); //该段时间不能过长,必须在15us内读取数据if(DS18B20_PORT)dat=1;	//如果总线上为1则数据dat为1,否则为0else dat=0;delay_10us(5);return dat;
} /*******************************************************************************
* 函 数 名         : ds18b20_read_byte
* 函数功能		   : 从DS18B20读取一个字节
* 输    入         : 无
* 输    出         : 一个字节数据
*******************************************************************************/
u8 ds18b20_read_byte(void)
{u8 i=0;u8 dat=0;u8 temp=0;for(i=0;i<8;i++)//循环8次,每次读取一位,且先读低位再读高位{temp=ds18b20_read_bit();dat=(temp<<7)|(dat>>1);}return dat;	
}/*******************************************************************************
* 函 数 名         : ds18b20_write_byte
* 函数功能		   : 写一个字节到DS18B20
* 输    入         : dat:要写入的字节
* 输    出         : 无
*******************************************************************************/
void ds18b20_write_byte(u8 dat)
{u8 i=0;u8 temp=0;for(i=0;i<8;i++)//循环8次,每次写一位,且先写低位再写高位{temp=dat&0x01;//选择低位准备写入dat>>=1;//将次高位移到低位if(temp)//此时表示读入位为“1”{DS18B20_PORT=0;_nop_();_nop_();DS18B20_PORT=1;	delay_10us(6);}else{DS18B20_PORT=0;delay_10us(6);DS18B20_PORT=1;_nop_();_nop_();	}	}	
}/*******************************************************************************
* 函 数 名         : ds18b20_start
* 函数功能		   : 开始温度转换
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
//转换命令
void ds18b20_start(void)
{ds18b20_reset();//复位ds18b20_check();//检查DS18B20ds18b20_write_byte(0xcc);//SKIP ROMds18b20_write_byte(0x44);//转换命令	
}/*******************************************************************************
* 函 数 名         : ds18b20_init
* 函数功能		   : 初始化DS18B20的IO口 DQ 同时检测DS的存在
* 输    入         : 无
* 输    出         : 1:不存在,0:存在
*******************************************************************************/ 
u8 ds18b20_init(void)
{ds18b20_reset();return ds18b20_check();	
}/*******************************************************************************
* 函 数 名         : ds18b20_read_temperture
* 函数功能		   : 从ds18b20得到温度值
* 输    入         : 无
* 输    出         : 温度数据
*******************************************************************************/
float ds18b20_read_temperture(void)
{float temp;u8 dath=0;u8 datl=0;u16 value=0;//开始转换:开启转换命令ds18b20_start();//将各个电线置为默认电平ds18b20_reset();//复位//判断当前程序是否在忙ds18b20_check();//发送读取温度命令ds18b20_write_byte(0xcc);//SKIP ROMds18b20_write_byte(0xbe);//读存储器datl=ds18b20_read_byte();//低字节dath=ds18b20_read_byte();//高字节value=(dath<<8)+datl;//合并为16位数据if((value&0xf800)==0xf800)//判断符号位,负温度{value=(~value)+1; //数据取反再加1temp=value*(-0.0625);//乘以精度	}else //正温度{temp=value*0.0625;	}return temp;
}/*
double Ds18b20ReadTemp(void)
{unsigned int temp = 0;unsigned char tmh = 0, tml = 0;double t = 0;Ds18b20ChangTemp();			 	//先写入转换命令Ds18b20ReadTempCom();			//然后等待转换完后发送读取温度命令tml = Ds18b20ReadByte();		//读取温度值共16位,先读低字节tmh = Ds18b20ReadByte();		//再读高字节
//	temp = tmh;
//	temp <<= 8;
//	temp |= tml;temp = tml | (tmh << 8);t = temp * 0.0625;return t;
}*/

六、LCD1602移植

1.1602的接线

1602的引脚是事先接好的,所以不能改变

1602屏幕 P0;P2^7  ;P2^5;P2^6

2.lcd1602.c

#include <reg51.h>
#include "lcd1602.h"
// 对LCD1602的底层以及高层时序做封装/************ 低层时序 ********************************/
static void Read_Busy()           //忙检测函数,判断bit7是0,允许执行;1禁止
{unsigned char sta;      //LCD1602_DB = 0xff;LCD1602_RS = 0;LCD1602_RW = 1;do{LCD1602_EN = 1;sta = LCD1602_DB;LCD1602_EN = 0;    //使能,用完就拉低,释放总线}while(sta & 0x80);
}static void Lcd1602_Write_Cmd(unsigned char cmd)     //写命令
{Read_Busy();LCD1602_RS = 0;LCD1602_RW = 0;	LCD1602_DB = cmd;LCD1602_EN = 1;LCD1602_EN = 0;    
}static void Lcd1602_Write_Data(unsigned char dat)   //写数据
{Read_Busy();LCD1602_RS = 1;LCD1602_RW = 0;LCD1602_DB = dat;LCD1602_EN = 1;LCD1602_EN = 0;
}/************* 高层时序 ******************************/
// 本函数用来设置当前光标位置,其实就是设置当前正在编辑的位置,
// 其实就是内部的数据地址指针,其实就是RAM显存的偏移量
// x范围是0-15,y=0表示上面一行,y=1表示下面一行
static void LcdSetCursor(unsigned char x,unsigned char y)  //坐标显示
{unsigned char addr;if(y == 0)addr = 0x00 + x;elseaddr = 0x40 + x;Lcd1602_Write_Cmd(addr|0x80);
}// 函数功能是:从坐标(x,y)开始显示字符串str
// 注意这个函数不能跨行显示,因为显存地址是不连续的
// 其实我们可以封装出一个能够折行显示的函数的
void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str)     //显示字符串
{LcdSetCursor(x,y);      //当前字符的坐标while(*str != '\0'){Lcd1602_Write_Data(*str++);}
}// 初始化LCD,使之能够开始正常工作
void InitLcd1602()              //1602初始化
{Lcd1602_Write_Cmd(0x38);    //打开,5*8,8位数据//Lcd1602_Write_Cmd(0x0c);	// 打开显示并且无光标Lcd1602_Write_Cmd(0x0f);	// 打开显示并且光标闪烁Lcd1602_Write_Cmd(0x06);Lcd1602_Write_Cmd(0x01);    //清屏   
}

3.lcd1602.h

#ifndef __lcd1602__H__
#define __lcd1602__H__#include<reg51.h>// IO接口定义
#define LCD1602_DB  P0      //data bus 数据总线
// 控制总线
sbit LCD1602_RS = P2^6;  //选择读取数据/命令
sbit LCD1602_RW = P2^5;  //选择读/写
sbit LCD1602_EN = P2^7;	 //使能#define u8 unsigned char //只需要声明高层时序即可,而底层时序是不需要声明
//因为我们在头文件中声明这个函数,目的是为了让别的文件去包含这个
//从而调用这个头文件中声明的函数,所以我们只需要声明1602.c中将来
//会被外部.c文件调用的哪些函数即可,而且1602.c中自己使用的内部函数将来也
//不会被外部.c文件调用,因此就不用声明了。/************* 高层时序 ******************************/
// 本函数用来设置当前光标位置,其实就是设置当前正在编辑的位置,
// 其实就是内部的数据地址指针,其实就是RAM显存的偏移量
// x范围是0-15,y=0表示上面一行,y=1表示下面一行
void LcdSetCursor(unsigned char x,unsigned char y);// 函数功能是:从坐标(x,y)开始显示字符串str
// 注意这个函数不能跨行显示,因为显存地址是不连续的
// 其实我们可以封装出一个能够折行显示的函数的
void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str);
//void lcd1602_show_string(u8 x,u8 y,u8 *str);
// 初始化LCD,使之能够开始正常工作
void InitLcd1602();#endif

4.lcd与测温联调

1.将double转换为字符串

因为LCD中的显示函数要求输入的是字符串

此处,我们为了设置方便,要求温度只能精确到小数点后一位。

将数值转换为字符串的实质,其实就是将单独一个数值强制类型转化为unsigned char

//显示类似于24.5这种的double类型的数字
/**思路:1)先将其一位一位显示出来2)然后将其强制类型转换为unsigned char3)记得最后有一个'\0'
*/
void LcdShowDouble(unsigned char x,unsigned char y,double d){// 第一步:将double d转成字符串strunsigned char str[5] = {0};// 第1步:先由double的25.4得到uint的254unsigned int tmp = (unsigned int)(d * 10);	unsigned char c = 0;// 第2步:由/和%操作来得到2、5、4// 第3步:将2、5、4对应的ASCII码放到字符串中去,完成c = (unsigned char)(tmp / 100);str[0] = c + 48;tmp = tmp % 100;		// 运算后tmp=54c = (unsigned char)(tmp / 10);		// c = 5str[1] = c + 48;str[2] = '.';tmp = tmp % 10;		// 运算后tmp=4c = (unsigned char)(tmp / 1);		// c = 4str[3] = c + 48;str[4] = '\0';// 第二步:显示strLcdShowStr(x, y, str);
}
void main(void){double t=35.4;InitLcd1602();//LcdShowStr(0,0,"nihaoliaoxiaoyi");LcdShowDouble(0,0,t);
}

2.输出摄氏度符号

3.注意点

我们在定义摄氏度符号的时候发现,在最后的位置出现奇怪的符号,是因为我们没有手动的添加结束符【‘\0’】

5.完整代码

lcd1602.c

#include"lcd1602.h"
/**显示屏:显示温度和时间
*/void Delay2000ms()		//@11.0592MHz
{unsigned char i, j, k;_nop_();_nop_();i = 85;j = 12;k = 155;do{do{while (--k);} while (--j);} while (--i);
}/************ 低层时序 ********************************/
void Read_Busy()           //忙检测函数,判断bit7是0,允许执行;1禁止
{unsigned char sta;      //LCD1602_DB = 0xff;LCD1602_RS = 0;LCD1602_RW = 1;do{LCD1602_EN = 1;sta = LCD1602_DB;LCD1602_EN = 0;    //使能,用完就拉低,释放总线}while(sta & 0x80);
}void Lcd1602_Write_Cmd(unsigned char cmd)     //写命令
{Read_Busy();LCD1602_RS = 0;LCD1602_RW = 0;	LCD1602_DB = cmd;LCD1602_EN = 1;LCD1602_EN = 0;    
}void Lcd1602_Write_Data(unsigned char dat)   //写数据
{Read_Busy();LCD1602_RS = 1;LCD1602_RW = 0;LCD1602_DB = dat;LCD1602_EN = 1;LCD1602_EN = 0;
}/************* 高层时序 ******************************/
// 本函数用来设置当前光标位置,其实就是设置当前正在编辑的位置,
// 其实就是内部的数据地址指针,其实就是RAM显存的偏移量
// x范围是0-15,y=0表示上面一行,y=1表示下面一行
void LcdSetCursor(unsigned char x,unsigned char y)  //坐标显示
{unsigned char addr;if(y == 0)addr = 0x00 + x;elseaddr = 0x40 + x;Lcd1602_Write_Cmd(addr|0x80);
}// 函数功能是:从坐标(x,y)开始显示字符串str
// 注意这个函数不能跨行显示,因为显存地址是不连续的
// 其实我们可以封装出一个能够折行显示的函数的
void LcdShowStr(unsigned char x,unsigned char y,unsigned char *str)     //显示字符串
{LcdSetCursor(x,y);      //当前字符的坐标while(*str != '\0'){Lcd1602_Write_Data(*str++);}}// 初始化LCD,使之能够开始正常工作
void InitLcd1602()              //1602初始化
{Lcd1602_Write_Cmd(0x38);    //打开,5*8,8位数据Lcd1602_Write_Cmd(0x0c);	// 打开显示并且无光标//Lcd1602_Write_Cmd(0x0f);	// 打开显示并且光标闪烁Lcd1602_Write_Cmd(0x06);Lcd1602_Write_Cmd(0x01);    //清屏   
}/**
为了显示ds18b20中获取到的浮点数温度
使其可以在显示屏上显示
*/
// 显示类似于24.5这种的double类型的数字
void LcdShowFloat(unsigned char x, unsigned char y, float d)     
{// 第一步:将double d转成字符串strunsigned char str[5] = {0};// 第1步:先由double的25.4得到uint的254unsigned int tmp = (unsigned int)(d * 10);	unsigned char c = 0;// 第2步:由/和%操作来得到2、5、4// 第3步:将2、5、4对应的ASCII码放到字符串中去,完成c = (unsigned char)(tmp / 100);str[0] = c + 48;tmp = tmp % 100;		// 运算后tmp=54c = (unsigned char)(tmp / 10);		// c = 5str[1] = c + 48;str[2] = '.';tmp = tmp % 10;		// 运算后tmp=4c = (unsigned char)(tmp / 1);		// c = 4str[3] = c + 48;str[4] = '\0';// 第二步:显示strLcdShowStr(x, y, str);}

七、DS1302的移植和联调(实时时钟)

1.原理图和接线

DS1302 P3.4 P3.6

2.时间的封装(使用结构体)

(1)一个时间=年 月 日 分 秒 周几(相比于温度是一个简单变量)

(2)C语言提供结构体这种技巧,来处理复杂变量

(3)区分2个概念:结构体类型【不占内存】和结构体变量【占内存】

(4)结构体这种语法使用时有套路:

        第一步:先定义结构体类型

        第二步:用类型去生产结构体变量

        第三步:使用结构体变量(其实就是使用结构体变量肚子里包着的内容)

3.使用结构体读取时间前的准备工作

1.结构体的定义

//封装出来的一个表示时间的结构体类型
//类型不占内存,也不表示一个具体时间,但是类型可以用来生成时间
//每一个时间变量占一定的内存,每一个时间变量就代表一个具体的时间
struct time_t
{unsigned int year;//2023unsigned char mon;//1-12unsigned char date;//1-31【几号】unsigned char hour;//0-23unsigned char min;//0-59unsigned char sec;//0-59unsigned char day;//0-6【星期几】
};

2.宏定义

之前我们使用DS1302的时候,是使用数组来记录寄存器地址。此处,我们使用结构体,则使用宏定义来实现使得CPU运行时间得到提升

// 用来存储读取的时间的,格式是:秒分时日月周年
//unsigned char code READ_RTC_ADDR[7] = {0x81, 0x83, 0x85, 0x87, 0x89, 0x8b, 0x8d}; 
//unsigned char code WRITE_RTC_ADDR[7] = {0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c};
//用宏定义的方式来定义时间的寄存器地址
// 用宏定义的方式来定义时间的寄存器地址 格式是:秒分时日月周年
//写地址
#define REG_ADDR_YEAR_WRITE	 	0x8c
#define REG_ADDR_MON_WRITE	 	0x88
#define REG_ADDR_DATE_WRITE	 	0x86
#define REG_ADDR_HOUR_WRITE	 	0x84
#define REG_ADDR_MIN_WRITE	 	0x82
#define REG_ADDR_SEC_WRITE	 	0x80
#define REG_ADDR_DAY_WRITE	 	0x8a//读地址
#define REG_ADDR_YEAR_READ	 	(REG_ADDR_YEAR_WRITE+1)
#define REG_ADDR_MON_READ	 	(REG_ADDR_MON_WRITE+1)
#define REG_ADDR_DATE_READ	 	(REG_ADDR_DATE_WRITE+1)
#define REG_ADDR_HOUR_READ	 	(REG_ADDR_HOUR_WRITE+1)
#define REG_ADDR_MIN_READ	 	(REG_ADDR_MIN_WRITE+1)
#define REG_ADDR_SEC_READ	 	(REG_ADDR_SEC_WRITE+1)
#define REG_ADDR_DAY_READ	 	(REG_ADDR_DAY_WRITE+1)

3.读取函数

// 从ds1302的内部寄存器addr读出一个值,作为返回值
static unsigned char ds1302_read_reg(unsigned char addr)
{unsigned char i = 0;unsigned char dat = 0; 		// 用来存储读取到的一字节数据的unsigned char tmp = 0;// 第1部分: 时序起始SCLK = 0;delay();RST = 0;delay();RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始delay();// 第2部分: 写入要读取的寄存器地址,addrfor (i=0; i<8; i++){dat = addr & 0x01;	 	// SPI是从低位开始传输的DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走delay();				// 读走之后,一个小周期就完了SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备delay();addr >>= 1;	   			// 把addr右移一位}// 第3部分: 读出一字节DS1302返回给我们的值dat = 0;for (i=0; i<8; i++){// 在前面向ds1302写入addr的最后一个bit后,ds1302就会将读取到的寄存器值// 的第一个bit放入到IO引脚上,所以我们应该先读取IO再制造下降沿然后继续// 读取下一个bittmp = DSIO;dat |= (tmp << i);		// 读出来的数值是低位在前的SCLK = 1;  				// 由于上面SCLK是低,所以要先拉到高delay();SCLK = 0;				// 拉低SCLK制造一个下降沿delay();}// 第4部分: 时序结束SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的delay();RST = 0;					// RST拉低意味着一个大周期的结束delay();// 第5部分:解决读取时间是ff的问题DSIO = 0;return dat;
}// 用结构体方式来实现的读取时间的函数
// READ_RTC_ADDR格式是:秒分时日月周年
void ds1302_read_time_struct(void)
{mytime.year =  ds1302_read_reg(REG_ADDR_YEAR_READ) + 2000;mytime.mon 	=  ds1302_read_reg(REG_ADDR_MON_READ);mytime.date =  ds1302_read_reg(REG_ADDR_DATE_READ);mytime.hour =  ds1302_read_reg(REG_ADDR_HOUR_READ);mytime.min 	=  ds1302_read_reg(REG_ADDR_MIN_READ);mytime.sec 	=  ds1302_read_reg(REG_ADDR_SEC_READ);mytime.day 	=  ds1302_read_reg(REG_ADDR_DAY_READ);
}

4.日期转换为字符串

因为我们获取到的数值是一串字符串,而我们要的是4位的年,2位的日期

所以我们为了把它区分开来,同时将其转换为字符串输出到lcd1602上

lcd1602.c

我们封装了2个函数分别将数值转换为字符串

// 实现一个子函数,将十进制的4位整数转成一个字符串
void Int2Str4(unsigned int dat, unsigned char str[], unsigned char index)
{unsigned char c = 0;// 假设dat=2017c = dat / 1000;	   	// c = 2str[index+0] = c + '0';  	// 第1位入库dat %= 1000;		// dat = 017c = dat / 100;	   	// c = 0str[index+1] = c + '0';  	// 第2位入库dat %= 100;			// dat = 17c = dat / 10;	   	// c = 1str[index+2] = c + '0';  	// 第3位入库dat %= 10;			// dat = 7c = dat / 1;	   	// c = 7str[index+3] = c + '0';  	// 第4位入库
}// 实现一个子函数,将十进制的2位整数转成一个字符串
void Int2Str2(unsigned int dat, unsigned char str[], unsigned char index)
{unsigned char c = 0;// 假设dat=17c = dat / 10;	   	// c = 1str[index+0] = c + '0';  	// 第1位入库dat %= 10;			// dat = 7c = dat / 1;	   	// c = 7str[index+1] = c + '0';  	// 第2位入库
}// LCD1602上显示time_t
void LcdShowTimeT(unsigned char x, unsigned char y, struct time_t ti)     
{// 第一步:将struct time_t ti转成字符串str// 格式:20170406113515-4unsigned char str[17] = {0};// 格式化time_t里面的各个时间,然后填充str// 年的格式化,str[0]-str[3]放年字符串Int2Str4(ti.year, str, 0);// 月的格式化 ,str[4]-str[5]Int2Str2(ti.mon, str, 4);// 日的格式化 ,str[6]-str[7]Int2Str2(ti.date, str, 6);// 时的格式化 ,str[8]-str[9]Int2Str2(ti.hour, str, 8);// 分的格式化 ,str[10]-str[11]Int2Str2(ti.min, str, 10);// 秒的格式化 ,str[12]-str[13]Int2Str2(ti.sec, str, 12);// 填充了一个'-' str[14]str[14] = '-';// 周几的格式化 ,str[15]str[15] = ti.day + '0';str[16] = '\0';// 第二步:显示strLcdShowStr(x, y, str);
}

main.c

	unsigned char c=0;InitLcd1602();LcdShowStr(0,0,"temp=");//0-4//显示:°CLcdShowStr(9,0,du);while(1){//读取温度并显示t=Ds18b20ReadTemp2();//显示温度值LcdShowDouble(5,0,t);//读取时间并且显示ds1302_read_time_struct();LcdShowTimeT(0,1,mytime);}

5.进制转换

【单片机】13-实时时钟DS1302-CSDN博客

BCD码与十进制数间转换_bcd码转十进制-CSDN博客

BCD码:看起来像十进制,但是实际上是十六进制

我们想要机器可以识别十六进制,则应该将其外观修改为真正的十六进制【也就是把外观和本质都修改为十六进制】bcd---》hex

比如:我们通过ds1302获得原始数据就为BCD码【此时:0x24实际上是十进制,所以我们要将其转换为0x18(这个才是真正的十六进制)】

我们在学习ds1302的时候说到:读出的其实是BCD码,所以才会产生乱码。故我们要进行转换才可以正确的显示。

// 实现2个子函数,分别实现从bcd码转十六进制,和十六进制转bcd码
unsigned char bcd2hex(unsigned char bcd)
{// 譬如我们现在要把bcd码0x24转成24(0x18)// 思路就是分2步// 第1步,先从0x24得到2和4// ((bcd & 0xf0) >> 4) 高4位,也就是2// (bcd & 0x0f) 低4位,也就是4// 第2步,由2*10+4得到24return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f));
}unsigned char hex2bcd(unsigned char hex)
{// 就是要把24转成0x24// 第一步,先由24得到2和4// (24 / 10) 就是2, (24 % 10)就是4// 第二步,再组合成0x24return (((hex / 10) << 4) | (hex % 10));
}
// 用结构体方式来实现的读取时间的函数
// READ_RTC_ADDR格式是:秒分时日月周年
void ds1302_read_time_struct(void)
{mytime.year =  bcd2hex(ds1302_read_reg(REG_ADDR_YEAR_READ)) + 2000;mytime.mon 	=  bcd2hex(ds1302_read_reg(REG_ADDR_MON_READ));mytime.date =  bcd2hex(ds1302_read_reg(REG_ADDR_DATE_READ));mytime.hour =  bcd2hex(ds1302_read_reg(REG_ADDR_HOUR_READ));mytime.min 	=  bcd2hex(ds1302_read_reg(REG_ADDR_MIN_READ));mytime.sec 	=  bcd2hex(ds1302_read_reg(REG_ADDR_SEC_READ));mytime.day 	=  bcd2hex(ds1302_read_reg(REG_ADDR_DAY_READ));
}

6.时间设置

我们要向DS1302中写入时间,让其按照这个时间接着往下走

因为我们传入的时间是十进制,要被DS1302所识别着应该转换为BCD码才可以,所以使用hex2bcd。【因为BCD码只能识别0-256,所以我们需要在“年”后面-2000】

ds1302.c

// 向ds1302的内部寄存器addr写入一个值value
static void ds1302_write_reg(unsigned char addr, unsigned char value)
{unsigned char i = 0;unsigned char dat = 0;// 第1部分: 时序起始SCLK = 0;delay();RST = 0;delay();RST = 1;  		// SCLK为低时,RST由低变高,意味着一个大的周期的开始delay();// 第2部分: 写入第1字节,addrfor (i=0; i<8; i++){dat = addr & 0x01;	 	// SPI是从低位开始传输的DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走delay();				// 读走之后,一个小周期就完了SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备delay();addr >>= 1;	   			// 把addr右移一位}// 第3部分: 写入第2字节,valuefor (i=0; i<8; i++){dat = value & 0x01;	 	// SPI是从低位开始传输的DSIO = dat;	 			// 把要发送的bit数据丢到IO引脚上去准备好SCLK = 1;		 		// 制造上升沿,让DS1302把IO上的值读走delay();				// 读走之后,一个小周期就完了SCLK = 0;				// 把SCLK拉低,是为了给下一个小周期做准备delay();value = value >> 1;	   	// 把addr右移一位}// 第4部分: 时序结束SCLK = 0;				  	// SCLK拉低为了后面的周期时初始状态是对的delay();RST = 0;					// RST拉低意味着一个大周期的结束delay();
}//用结构体方式在实现时间的修改函数
//本函数用于向DS1302中写入一个时间t1
void ds1302_write_time_struct(struct time_t t1){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_YEAR_WRITE, (hex2bcd(t1.year - 2000)));ds1302_write_reg(REG_ADDR_MON_WRITE, (hex2bcd(t1.mon)));ds1302_write_reg(REG_ADDR_DATE_WRITE, (hex2bcd(t1.date)));ds1302_write_reg(REG_ADDR_HOUR_WRITE, (hex2bcd(t1.hour)));ds1302_write_reg(REG_ADDR_MIN_WRITE, (hex2bcd(t1.min)));ds1302_write_reg(REG_ADDR_SEC_WRITE, (hex2bcd(t1.sec)));ds1302_write_reg(REG_ADDR_DAY_WRITE, (hex2bcd(t1.day)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护}

main.c

	struct time_t t1;float t;InitLcd1602();LcdShowStr(0,0,"temp=");LcdShowStr(9,0,du);t1.year = 2023;t1.mon = 10;t1.date = 14;t1.hour = 21;t1.min = 6;t1.sec = 45;t1.day = 6;//将时间写入寄存器ds1302_write_time_struct(t1);while(1){//显示温度t=ds18b20_read_temperture();LcdShowFloat(5,0,t);// 读取时间并显示ds1302_read_time_struct();LcdShowTimeT(0, 1, mytime);}

7.单独修改时间

//只修改年份
void ds1302_write_time_year(unsigned int year){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_YEAR_WRITE, (hex2bcd(year - 2000)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护}//只修改月份
void ds1302_write_time_month(unsigned char month){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_MON_WRITE, (hex2bcd(month - 2000)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护}//只修改日
void ds1302_write_time_date(unsigned char date){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_DATE_WRITE, (hex2bcd(date - 2000)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护}//只修改时
void ds1302_write_time_hour(unsigned char hour){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_HOUR_WRITE, (hex2bcd(hour - 2000)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护}//只修改分
void ds1302_write_time_min(unsigned char min){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_MIN_WRITE, (hex2bcd(min - 2000)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护}//只修改秒
void ds1302_write_time_sec(unsigned char sec){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_SEC_WRITE, (hex2bcd(sec - 2000)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护

9.注意点:

1)从DS1302中获取到的数据是unsigned char ,所以不能超过255,但是我们年份是四位数,所以我们在读取时,应该直接在后面加上2000

2)我们从DS1302中获取到数据是BCD码,所以我们需要将其转换为十六进制

3)我们在显示屏上显示数据,因为我们获取到的是数值,但是要转换为字符串才可以进行展示,所以我们要将获取到的时间转换为字符串

// LCD1602上显示time_t
void LcdShowTimeT(unsigned char x, unsigned char y, struct time_t ti)     
{// 第一步:将struct time_t ti转成字符串str// 格式:20170406113515-4unsigned char str[17] = {0};// 格式化time_t里面的各个时间,然后填充str// 年的格式化,str[0]-str[3]放年字符串Int2Str4(ti.year, str, 0);// 月的格式化 ,str[4]-str[5]Int2Str2(ti.mon, str, 4);// 日的格式化 ,str[6]-str[7]Int2Str2(ti.date, str, 6);// 时的格式化 ,str[8]-str[9]Int2Str2(ti.hour, str, 8);// 分的格式化 ,str[10]-str[11]Int2Str2(ti.min, str, 10);// 秒的格式化 ,str[12]-str[13]Int2Str2(ti.sec, str, 12);// 填充了一个'-' str[14]str[14] = '-';// 周几的格式化 ,str[15]str[15] = ti.day + '0';str[16] = '\0';// 第二步:显示strLcdShowStr(x, y, str);
}

4)因为年份和其他时间单位的位数不同,所以我们需要将其分开,写成2个函数

// 实现一个子函数,将十进制的4位整数转成一个字符串
void Int2Str4(unsigned int dat, unsigned char str[], unsigned char index)
{unsigned char c = 0;// 假设dat=2017c = dat / 1000;	   	// c = 2str[index+0] = c + '0';  	// 第1位入库dat %= 1000;		// dat = 017c = dat / 100;	   	// c = 0str[index+1] = c + '0';  	// 第2位入库dat %= 100;			// dat = 17c = dat / 10;	   	// c = 1str[index+2] = c + '0';  	// 第3位入库dat %= 10;			// dat = 7c = dat / 1;	   	// c = 7str[index+3] = c + '0';  	// 第4位入库
}// 实现一个子函数,将十进制的2位整数转成一个字符串
void Int2Str2(unsigned int dat, unsigned char str[], unsigned char index)
{unsigned char c = 0;// 假设dat=17c = dat / 10;	   	// c = 1str[index+0] = c + '0';  	// 第1位入库dat %= 10;			// dat = 7c = dat / 1;	   	// c = 7str[index+1] = c + '0';  	// 第2位入库
}

5)写入时间,因为我们传入的是十六进制,但是为了让DS1302识别,所以需要将其转换为bcd码

//十六进制转换为bcd码
unsigned char hex2bcd(unsigned char hex)
{// 就是要把24转成0x24// 第一步,先由24得到2和4// (24 / 10) 就是2, (24 % 10)就是4// 第二步,再组合成0x24return (((hex / 10) << 4) | (hex % 10));
}

6)写入时间函数

//用结构体方式在实现时间的修改函数
//本函数用于向DS1302中写入一个时间t1
void ds1302_write_time_struct(struct time_t t1){ds1302_write_reg(0x8E, 0x00);	// 去掉写保护// 依次写各个时间寄存器ds1302_write_reg(REG_ADDR_YEAR_WRITE, (hex2bcd(t1.year - 2000)));ds1302_write_reg(REG_ADDR_MON_WRITE, (hex2bcd(t1.mon)));ds1302_write_reg(REG_ADDR_DATE_WRITE, (hex2bcd(t1.date)));ds1302_write_reg(REG_ADDR_HOUR_WRITE, (hex2bcd(t1.hour)));ds1302_write_reg(REG_ADDR_MIN_WRITE, (hex2bcd(t1.min)));ds1302_write_reg(REG_ADDR_SEC_WRITE, (hex2bcd(t1.sec)));ds1302_write_reg(REG_ADDR_DAY_WRITE, (hex2bcd(t1.day)));ds1302_write_reg(0x8E, 0x80);	// 打开写保护}

八、蜂鸣器

1.接线和原理图

P2^4

2.函数封装

1.初始化

void buzzer_init(){TMOD = 0x01;		// T0使用16bit定时器//决定中断的时间TL0 = N % 256;TH0 = N / 256;TR0 = 1; 			// T0打开开始计数ET0 = 1;	 		// T0中断允许EA = 1;				// 总中断允许BUZZER = 1;// 设置响和不响的周期时间/**我们初始化频率为:4KHZ则应该是1/4000HZ的周期1s=1000ms=1000 000us所以1/4000=1000 000/4000=1000/4=256us*/count = TIMELEN;		//600*256usflag = 0;
}

2.中断函数

//timer0的isr,在这里对引脚进行电平反反转以让蜂鸣器响
void timer0_isr(void) interrupt 1 using 1
{TL0 = N % 256;TH0 = N / 256;if (count-- == 0){// 说明到了翻转的时候了//	count = 600;if (flag == 0){// 之前是处于有声音的,说明本次是从有声音到无声音的翻转flag = 1;//*10:表示不响的时间是响的时间的10倍count = TIMELEN*10;	//这里的count数量决定蜂鸣器【不响】的时间长短}else{// 之前是处于没声音的,说明本次是从没声音到有声音的翻转flag = 0;BUZZER = !BUZZER;count = TIMELEN;		//这里的count数量决定蜂鸣器【响】的时间长短}}//时间未到else{// 常规情况,也就是不反转时if (flag == 0){BUZZER = !BUZZER;			// 4999次声音}else{// 空的就可以,因为不进行任何IO操作就是没声音}}}

3.提升

1.中断函数优化

//宏定义
//设置蜂鸣器的输出频率为XKHZ
#define XKHZ	4 					// 要定多少Khz,就直接写这里
#define US		(500/XKHZ)
#define N 		(65535-US)//N=(65535-(500/XKHZ))static unsigned char xKHZ=0; //用于获取N的数值,可以计算响或者不响

将初始化函数和中断函数中的N修改,并且将count删除

//让蜂鸣器一直响的isr
void timer0_isr(void) interrupt 1 using 1
{//N=(65535-(500/XKHZ))TL0 =(65535-(500/xKHZ)) % 256;TH0 = (65535-(500/xKHZ)) / 256;// 常规情况,也就是不反转时if (flag == 0)  //flag=0表示响{BUZZER = !BUZZER;			// 4999次声音}
}
//buzzer的初始化
void buzzer_init(){TMOD = 0x01;		// T0使用16bit定时器//决定中断的时间
//N=(65535-(500/XKHZ))TL0 =(65535-(500/xKHZ)) % 256;TH0 = (65535-(500/xKHZ)) / 256;TR0 = 1; 			// T0打开开始计数ET0 = 1;	 		// T0中断允许EA = 1;				// 总中断允许BUZZER = 1;flag = 0;  	// flag = 0表示有声音,flag = 1表示没声音xKHZ=4;//默认是4khz
}

2.一直响/不响

//让蜂鸣器开始响
void buzzer_start(void){flag=0;
}//让蜂鸣器停止响
void buzzer_stop(void){flag=1;
}//设置蜂鸣器响的频率
void buzzer_freq_set(unsigned char tmp){xKHZ=tmp;//修改蜂鸣器的频率
}

3.完整代码

#ifndef __BUZZER__H__
#define __BUZZER__H__#include<reg51.h>
//引脚定义
sbit BUZZER=P1^7;//宏定义
//设置蜂鸣器的输出频率为XKHZ
#define XKHZ	4 					// 要定多少Khz,就直接写这里
#define US		(500/XKHZ)
#define N 		(65535-US)//N=(65535-(500/XKHZ))//蜂鸣器响或者不响的时间设置
#define TIMELEN 600static unsigned int count;
static unsigned char flag = 0;			// flag = 0表示有声音,flag = 1表示没声音
static unsigned char xKHZ=0; //用于获取N的数值,可以计算响或者不响//buzzer的初始化
//初始化表示默认情况下是不响的
void buzzer_init();//让蜂鸣器开始响
void buzzer_start(void);//让蜂鸣器停止响
void buzzer_stop(void);//设置蜂鸣器响的频率
void buzzer_freq_set(unsigned char tmp);#endif
#include"buzzer.h"//buzzer的初始化
void buzzer_init(){xKHZ=4;//默认是4khzTMOD = 0x01;		// T0使用16bit定时器//决定中断的时间
//N=(65535-(500/XKHZ))TL0 =(65535-(500/xKHZ)) % 256;TH0 = (65535-(500/xKHZ)) / 256;TR0 = 1; 			// T0打开开始计数ET0 = 1;	 		// T0中断允许EA = 1;				// 总中断允许BUZZER = 1;flag = 1;  	// flag = 0表示有声音,flag = 1表示没声音}//让蜂鸣器一直响的isr
void timer0_isr(void) interrupt 1 using 1
{//N=(65535-(500/XKHZ))TL0 =(65535-(500/xKHZ)) % 256;TH0 = (65535-(500/xKHZ)) / 256;// 常规情况,也就是不反转时if (flag == 0)  //flag=0表示响{BUZZER = !BUZZER;			// 4999次声音}
}//让蜂鸣器开始响
void buzzer_start(void){flag=0;
}//让蜂鸣器停止响
void buzzer_stop(void){flag=1;
}//设置蜂鸣器响的频率
void buzzer_freq_set(unsigned char tmp){xKHZ=tmp;//修改蜂鸣器的频率
}

4.蜂鸣器频率设置(中间变量的使用)

上面的代码我们无法修改频率

原因:因为65535数值过大,如果在中断函数中进行计算,则会消耗大量时间,所以才会产生结果不同。所以我们先在初始化函数中将值计算好,然后将其赋值给全局变量,然后再中断函数中将全局变量的值给中断函数中的值。

XKHZ:宏定义

xKHZ:全局变量

解决思路:

所以我们先在初始化函数中将值计算好,然后将其赋值给全局变量,然后再中断函数中将全局变量的值给中断函数。

代码实现:

buzzer.c
#include"buzzer.h"//buzzer的初始化
void buzzer_init(){TMOD = 0x01;		// T0使用16bit定时器//决定中断的时间
//N=(65535-(500/XKHZ))tl0 =(65535-(500/xKHZ)) % 256;th0 = (65535-(500/xKHZ)) / 256;//将计算得到的值赋值给全局变量TH0=th0;TL0=tl0;TR0 = 1; 			// T0打开开始计数ET0 = 1;	 		// T0中断允许EA = 1;				// 总中断允许BUZZER = 1;flag = 1;  	// flag = 0表示有声音,flag = 1表示没声音}//让蜂鸣器一直响的isr
void timer0_isr(void) interrupt 1 using 1
{//N=(65535-(500/XKHZ))
//	
//	因为65535数值过大,如果在中断函数中进行计算,则会消耗大量时间,
//	所以才会产生结果不同。所以我们先在初始化函数中将值计算好,
//	然后将其赋值给全局变量,然后再中断函数中将全局变量的值给中断函数。//TL0 =(65535-(500/xKHZ)) % 256;//TH0 = (65535-(500/xKHZ)) / 256;TH0=th0;TL0=tl0;// 常规情况,也就是不反转时if (flag == 0)  //flag=0表示响{BUZZER = !BUZZER;			// 4999次声音}
}//让蜂鸣器开始响
void buzzer_start(void){flag=0;
}//让蜂鸣器停止响
void buzzer_stop(void){flag=1;
}//设置蜂鸣器响的频率
void buzzer_freq_set(unsigned char tmp)
{tl0 = (65535 - (500 / tmp)) % 256;th0 = (65535 - (500 / tmp)) / 256;
}
buzzer.h
#ifndef __BUZZER__H__
#define __BUZZER__H__#include<reg51.h>
//引脚定义
sbit BUZZER=P1^7;//宏定义
//设置蜂鸣器的输出频率为XKHZ
//#define XKHZ	4 					// 要定多少Khz,就直接写这里
//#define US		(500/XKHZ)
//#define N 		(65535-US)//N=(65535-(500/XKHZ))//蜂鸣器响或者不响的时间设置
//#define TIMELEN 600static unsigned int count;
static unsigned char flag = 0;			// flag = 0表示有声音,flag = 1表示没声音
static unsigned char xKHZ=1; //用于获取N的数值,可以计算响或者不响static unsigned char th0;
static unsigned char tl0;//buzzer的初始化
//初始化表示默认情况下是不响的
void buzzer_init();//让蜂鸣器开始响
void buzzer_start(void);//让蜂鸣器停止响
void buzzer_stop(void);//设置蜂鸣器响的频率
void buzzer_freq_set(unsigned char tmp);#endif

九、步进电机

1.接线和原理图

步进电机(四线双极性)P1.0---P1.3

A+---》P2.0

A- ----》P2.1

B+---》P2.2

B- ---》p2.3

2.代码的移植

motor.c

#include"motor.h"//让步进电机转
//双向四拍
void motor_alarm_once(void){//双相四拍  A/B  AB   AB/   A/B//*正序旋转逆序就是将节拍反过来*/unsigned int cnt=3000;//表示3000个节拍while(cnt--){//第一拍:  [A/B  AB    AB/   A/B/]//           1   0    0     0APositive=0;ANegative=1;BPositive=1;   BNegative=0;   delay(TIME);//第二拍:  [A/B  AB    AB/   A/B/]//           0   1    0     0APositive=1;ANegative=0;BPositive=1;   BNegative=0;   delay(TIME);//第3拍:  [A/B  AB    AB/   A/B/]//           0   0    1     0APositive=1;ANegative=0;BPositive=0;   BNegative=1;   delay(TIME);//第4拍:  [A/B  AB    AB/   A/B/]//           0   0    0   1APositive=0;ANegative=1;BPositive=0;   BNegative=1;   delay(TIME);}}

motor.h

#ifndef __MOTOR__H__
#define __MOTOR__H__#include<reg51.h>//接线定义
sbit APositive =P1^0;//A+
sbit ANegative =P1^1;//A-
sbit BPositive =P1^2;//B+
sbit BNegative =P1^3;//B-//延时时间
//延时时间越短,转速越快
//TIME宏定义步进电机的转速
#define TIME 30static void delay(unsigned char i){  //i越大,延迟时间越大unsigned char a,b;for(a=i;a>0;a--){for(b=240;b>0;b--);}
}//motor报警接口
//调用该函数导致步进电机转3000个节拍进行报警一次
void motor_alarm_once(void);#endif

十、矩阵按键

1.接线和原理图

4*4按键 P2

2.代码移植

1.Lcd1602_showInt

因为我们要在1602中显示,所以要定义一个函数来显示0-16

lcd1602.c

// 实现一个子函数,将十进制的2位整数转成一个字符串
void Int2Str2(unsigned int dat, unsigned char str[], unsigned char index)
{unsigned char c = 0;// 假设dat=17c = dat / 10;	   	// c = 1str[index+0] = c + '0';  	// 第1位入库dat %= 10;			// dat = 7c = dat / 1;	   	// c = 7str[index+1] = c + '0';  	// 第2位入库
}
//在LCD上显示1-16之间的数字【矩阵按键】
void LcdShowInt(unsigned char x,unsigned char y,unsigned char value){//第一步:将int转换为字符串strunsigned char str[3]={0};Int2Str2(value,str,0);str[2]='\0'; //这句话可有可无//显示strLcdShowStr(x, y, str);
}

2.代码移植

#include"key.h"//调用该函数后会去进行按键扫描,如果没有人按按键则返回0
//如果有人按下按键,则返回按键的键值
unsigned char GetKey(void){//定义行列unsigned char hang=0,lie=0;//显示当前按下的按键的键值unsigned char keyvalue=0;//第一步:先给keyvalue赋一个初值,才知道是否有被按下KEY=0x0f;//判断行是否有被按下if(KEY!=0x0f){//说明此时有行被按下//消除抖Delay10ms();//算出行号switch(KEY){case 0x0e:hang=1;	break;case 0x0d:hang=2;	break;case 0x0b:hang=3;	break;case 0x07:hang=4;	break;default:break;}}//【第二回合第1步】KEY=0xf0;if(KEY!=0xf0){Delay10ms();//1.算出列号switch(KEY){case 0xe0:lie=1;	break;case 0xd0:lie=2;	break;case 0xb0:lie=3;	break;case 0x70:lie=4;	break;default:break;}//经过2个回合后hang和lie都知道了,然后根据hang和lie计算出键值//(行-1)*4+列=键值keyvalue=(hang-1)*4+lie;return keyvalue;}return 0;}
#ifndef __KEY__H__
#define __KEY__H__
#include<reg51.h>//4*4矩阵按键,接线上使用P1端口
#define KEY P1static void Delay10ms()		//@11.0592MHz
{unsigned char i, j;i = 108;j = 145;do{while (--j);} while (--i);
}//调用该函数后会去进行按键扫描,如果没有人按按键则返回0
//如果有人按下按键,则返回按键的键值
unsigned char GetKey(void);#endif
//测试按键	unsigned char key=0;InitLcd1602();//LcdShowStr(0,0,"temp=");LcdShowInt(0,0,0);while(1){key=GetKey();//因为有可能没有得到键值if(key!=0){//如果为0,表示没有按下按键LcdShowInt(0,0,key);}}

十一、引脚冲突解决

十二、红外遥控器

1.接线和原理图

2.代码移植

ir.h

#ifndef __IR_H__
#define __IR_H__#include <reg51.h>// IR使用的引脚定义
sbit IRIN = P3^2;// 外部全局变量声明
// IrValue的0-3用来放原始数据,4用来放经过校验确认无误的键值
extern unsigned char IrValue[5];// 外部接口定义// IR的初始化
void IrInit(void);#endif

ir.c

#include "ir.h"unsigned char IrValue[5];  // IrValue的0-3用来放原始数据,4用来放经过校验确认无误的键值
static unsigned char Time;void IrInit(void)
{IT0=1;//下降沿触发EX0=1;//打开中断0允许EA=1;	//打开总中断IRIN=1;//初始化端口
}static void DelayMs(unsigned int x)   //0.14ms误差 0us
{unsigned char i;while(x--){for (i = 0; i<13; i++){}}
}void ReadIr() interrupt 0
{unsigned char j,k;unsigned int err;Time = 0;					 DelayMs(40);			// 136us*40if (IRIN == 0)		//确认是否真的接收到正确的信号{	 		err = 1000;				//1000*10us=10ms,超过说明接收到错误的信号/*当两个条件都为真时循环,如果有一个条件为假的时候跳出循环,免得程序出错的时侯,程序死在这里*/	while ((IRIN==0) && (err>0))	//等待前面9ms的低电平过去  		{			DelayMs(1);			// 136userr--;} if (IRIN == 1)			//如果正确等到9ms低电平{err = 500;while ((IRIN==1) && (err>0))		 //等待4.5ms的起始高电平过去{DelayMs(1);err--;}for (k=0; k<4; k++)		//共有4组数据{			for (j=0; j<8; j++)	//接收一组数据{err = 60;		while ((IRIN==0) && (err>0))//等待信号前面的560us低电平过去{DelayMs(1);err--;}err = 500;while ((IRIN==1) && (err>0))	 //计算高电平的时间长度。{DelayMs(1);//0.14msTime++;err--;if (Time > 30){EX0 = 1;return;}}IrValue[k] >>= 1;	 //k表示第几组数据if (Time >= 8)			//如果高电平出现大于565us,那么是1{IrValue[k] |= 0x80;}Time = 0;		//用完时间要重新赋值							}}}if (IrValue[2] == ~IrValue[3]){IrValue[4] = IrValue[2];return;}}			
}

3.优化

我们在上面代码红外是使用了中断,在中断的过程中是属于销毁大量时间,所以我们不使用中断

第一步:用中断的方式来解码红外接收,完成

第二步:因为中断接收红外的时间太长,所以和别的模块容易产生冲突?怎么办?

解决方法:在外面(不在isr)进行延时并接收红外的一帧数据,不在中断中做。

 难点和问题:

1.中断还要不要?因为红外遥控器信号是异步的,所以必须用中断来处理

2.但是完整接收时间又太长不能都放在中断中,所以我们在中断中启动接收开个头,然后交给外部去做整个接收工作

3.外部做的时候精确延时函数的实现是关键,不能用delay方式,而是用是定时器来实现的,最后总结:其实这里很麻烦

十三、项目功能梳理和定义

1.功能规划

(1)默认情况下显示时间和温度

(2)温度阈值显示及调整:不同用户想要调整的不一样

(3)温度超过阈值后报警

(4)时间调整

(5)扩展功能:闹钟

2.细节实现

1.时间和温度显示的格式

T:24.5-37.9

20231015-194812

2.调节方式

(1)在LCD1602上显示设置一个光标,显示分2种模式,常规模式和调整模式。常规模式下没有光标显示,调整模式下有一个光标,光标闪烁落在哪一个格子,就是要调整哪一个格子内容。

(2)按键中有一个模式切换键(比如定义键值为的按键),默认是普通模式,按一下切换为调整模式,再按切换回普通模式。

(3)调整模式下光标的移动要靠按键,按键中有4个按键分别为上下左右。

(4)当光标落在某一个格子上时候,按下+或者-按键,数字会加1或者减1处理

3.闹钟功能实现

方式一【硬件闹钟】:类似于DS1302【有时间寄存器】(询问式)

方式二【软件闹钟】:软件要再很短的时间对固定位置去查询(轮询方式)

十四、LCD1602实现按键控制光标移动和闪烁

1.控制光标是否显示和闪烁

 

//设置LCD到常规模式(不显示光标)
void Lcd_normal_mode(void){Lcd1602_Write_Cmd(0x0c);//打开显示并且无光标
}//设置LCD到调整模式(显示光标,并且光标闪烁)
void Lcd_adjust_mode(void){Lcd1602_Write_Cmd(0x0f);//打开显示并且光标闪烁
}

2.设置光标初始化

设置光标显示的位置

3.按键控制工作模式

因为按键控制着LCD,但是又不能再LCD或者Key里面直接写。所以再单独写一个。

C语言的按位取反【!和~】

c语言中!与~的区别_c语言~和!-CSDN博客

工作模式的切换初值

定义:keyvalue=1时候,这个按键定义为模式调整按键

//调用该函数后会去进行按键扫描,如果没有人按按键则返回0
//如果有人按下按键,则返回按键的键值
unsigned char GetKey(void){//定义行列unsigned char hang=0,lie=0;//显示当前按下的按键的键值unsigned char keyvalue=0;//第一步:先给keyvalue赋一个初值,才知道是否有被按下KEY=0x0f;//判断行是否有被按下if(KEY!=0x0f){//说明此时有行被按下//消除抖Delay10ms();//算出行号switch(KEY){case 0x0e:hang=1;	break;case 0x0d:hang=2;	break;case 0x0b:hang=3;	break;case 0x07:hang=4;	break;default:break;}}//【第二回合第1步】KEY=0xf0;if(KEY!=0xf0){Delay10ms();//1.算出列号switch(KEY){case 0xe0:lie=1;	break;case 0xd0:lie=2;	break;case 0xb0:lie=3;	break;case 0x70:lie=4;	break;default:break;}//经过2个回合后hang和lie都知道了,然后根据hang和lie计算出键值//(行-1)*4+列=键值keyvalue=(hang-1)*4+lie;return keyvalue;}return 0;}

adjustmode.c

//workMode表示工作模式,为0表示常规模式,1表示调整模式
static unsigned char workMode=0;//实现工作模式的调整
void workModeAjust(){//使用C语言中都逻辑取反workMode=!workMode;if(workMode==0){//此时是要调整到的模式是:常规模式Lcd_normal_mode();}else{//此时是要调整到的模式:调整模式Lcd_adjust_mode();//如果你想要让光标出现再固定的位置,那么就再这里去设置}}//调用Key中GetKey
void KeyWorkModeAjust(){unsigned char key=GetKey();if(key==1){//如果按下”0“则表示要进行调整workModeAjust();}
}

main.c

	unsigned char key=0;InitLcd1602();LcdShowStr(0,0,"temp=");while(1){KeyWorkModeAjust();}

按键不灵敏

我们上面功能实现后,通过按键控制是否显示光标明显显示过慢。可能要按好几次才可以出现或者销毁光标。

思路:我们从结果可以知道其实是可以转换模式,但是就是时间上有问题。说明我们刚刚实现的[adjustmode.c]是没有问题的,所以问题可能出现再调用的Getkey。GetKey获取时间很快【100-200us】,但是我们手动按下时间可能赶不上GetKey的执行

解决方法:

我们预估人按键按下是需要400ms,原程序执行速度可能需要100ms左右,所以我们加上一个延时300ms左右的函数。使得按下按键一次,程序就检测1次

但是此时我测试时候,不加延时函数才可以正确显示。但是加上延时函数反而不正确

4.光标移动

绝对坐标系,相对坐标系

setCursor是绝对坐标系

但是我们要使用到的是相对坐标系,所以我们要先读取原来的x和y。

记录原始坐标

使用全局变量,并且再刚刚进入调整模式下,设置默认的坐标位置

//进入到调整模式,默认光标的位置
static unsigned char xCursor=0,yCursor=0;//实现工作模式的调整
void workModeAjust(){//使用C语言中都逻辑取反workMode=!workMode;if(workMode==0){//此时是要调整到的模式是:常规模式Lcd_normal_mode();}else{//此时是要调整到的模式:调整模式Lcd_adjust_mode();//如果你想要让光标出现再固定的位置,那么就再这里去设置//我们设置刚进入调整模式,默认光标再0,0LcdSetCursor(xCursor,yCursor);}}

控制光标

//进入到调整模式,默认光标的位置
static unsigned char xCursor=0,yCursor=0;//实现工作模式的调整
void workModeAjust(){//使用C语言中都逻辑取反workMode=!workMode;if(workMode==0){//此时是要调整到的模式是:常规模式Lcd_normal_mode();}else{//此时是要调整到的模式:调整模式Lcd_adjust_mode();//如果你想要让光标出现再固定的位置,那么就再这里去设置//我们设置刚进入调整模式,默认光标再0,0LcdSetCursor(xCursor,yCursor);}}//检测按键,并且根据按键来移动光标
//该模式只再调整模式下有效果
void KeyCursorMove(void){//第一步:先获取键值unsigned char key=GetKey();//判断键值,选择方向switch(key){//上,下case 3:if(yCursor==0){yCursor++;}else{yCursor--;}break;//左case 2:if(xCursor==0){xCursor=15;//将光标从最左边移动到最右边}else{xCursor-=1;}LcdSetCursor(xCursor,yCursor);break;//右case 4:if(xCursor==15){xCursor=0;//将光标从最左边移动到最右边}else{xCursor+=1;}LcdSetCursor(xCursor,yCursor);break;//无关按键default:break;}}// 本函数用来设置当前光标位置,其实就是设置当前正在编辑的位置,
// 其实就是内部的数据地址指针,其实就是RAM显存的偏移量
// x范围是0-15,y=0表示上面一行,y=1表示下面一行
void LcdSetCursor(unsigned char x,unsigned char y)  //坐标显示
{unsigned char addr;if(y == 0)addr = 0x00 + x;elseaddr = 0x40 + x;Lcd1602_Write_Cmd(addr|0x80);
}

十五、程序流程梳理及其基本显示功能实现

1.程序流程梳理

1.按键获取与其他代码的合理安排

//本函数为按键事件处理器,专门处理按键的各种事件
void KeyEventHandler(unsigned char keyvalue){switch(keyvalue){//切换模式case 1:KeyWorkModeAjust();case 2:if(xCursor==0){xCursor=15;//将光标从最左边移动到最右边}else{xCursor-=1;}LcdSetCursor(xCursor,yCursor);break;//上,下case 3:if(yCursor==0){yCursor++;}else{yCursor--;}break;//左//右case 4:if(xCursor==15){xCursor=0;//将光标从最左边移动到最右边}else{xCursor+=1;}LcdSetCursor(xCursor,yCursor);break;//无关按键default:break;}}unsigned char key=0;//按键键值InitLcd1602();LcdShowStr(0,0,"temp=");while(1){//第一步:调用GetKey获取按键键值key=GetKey();//第二步:再去根据程序逻辑处理键值对应的代码KeyEventHandler(key);}

2.一个单片机程序只有且只能有一个主程序

2.基本显示功能的实现

(1)温度显示

	double t;//存温度值unsigned char key=0;//按键键值InitLcd1602();//T:23.5-37.9LcdShowStr(0,0,"T:");//坐标(0,0)-(1,0)while(1){//第一行:温度有关的显示t=ds18b20_read_temperture();LcdShowFloat(2,0,t);//   x:2-5  y:0LcdShowStr(6,0,du);//x:6-7  y:0LcdShowStr(8,0,"-");//   x:8  y:0LcdShowStr(13,0,du);}

(2)阈值温度显示

(3)时间显示

		//第二行:时间有关的显示// 格式:20170406113515-4sds1302_read_time_struct();// 全局变量声明:mytimeLcdShowTimeT(0, 1, mytime);

(4)完整测试代码

//阈值温度
double tempTh=38.0;void main(){double t;//存温度值unsigned char key=0;//按键键值InitLcd1602();//T:23.5-37.9LcdShowStr(0,0,"T:");//坐标(0,0)-(1,0)while(1){//第一行:温度有关的显示t=ds18b20_read_temperture();LcdShowFloat(2,0,t);//   x:2-5  y:0LcdShowStr(6,0,du);//x:6-7  y:0LcdShowStr(8,0,"-");//   x:8  y:0LcdShowFloat(9,0,tempTh);//x:7-12  y:0   阈值温度LcdShowStr(13,0,du);//第二行:时间有关的显示// 格式:20170406113515-4sds1302_read_time_struct();// 全局变量声明:mytimeLcdShowTimeT(0, 1, mytime);//第一步:调用GetKey获取按键键值key=GetKey();//第二步:再去根据程序逻辑处理键值对应的代码KeyEventHandler(key);}

3.bug

1.代码调整

上面的测试代码有问题

问题:如果我们从基本模式---》调整模式【那我们的时间应该定住不能动】

我们从调整模式---》基本模式【那么时间应该按照修改后的去动】

代码逐层递进,发现我们应该在

2.程序运行逻辑

1)由常规模式--》调整模式

        a)停止温度和时间的刷新(此时LCD1602上显示的内容就是刚才正在显示的)

        b)响应按键事件(主要有2种:2,3,4按键是调整光标位置;5,6按键是调整光标所在位置的数字加减)

2)由调整模式--》常规模式

        a)恢复温度和时间的刷新

        b)停止响应按键2,3,4,5,6,只能响应按键1.同时要把刚才调整模式下左做的调整结果生效(比如温度阈值变化了要更新,比如时间调整了要写入ds1302内部去生效)

3.要在按键前先判断当前处于哪一个模式下

只有在进入调整模式下才可以调节2,3,4

//本函数为按键事件处理器,专门处理按键的各种事件
void KeyEventHandler(unsigned char keyvalue){switch(keyvalue){//切换模式case 1:KeyWorkModeAjust();break;//左case 2://只有在调整模式下才可以操作if(workMode){if(xCursor==0){xCursor=15;//将光标从最左边移动到最右边}else{xCursor-=1;}LcdSetCursor(xCursor,yCursor);}break;//上,下case 3:if(workMode){if(yCursor==0){yCursor++;}else{yCursor--;}}break;//右case 4:if(workMode){if(xCursor==15){xCursor=0;//将光标从最左边移动到最右边}else{xCursor+=1;}LcdSetCursor(xCursor,yCursor);}break;//加:case 5:if(workMode){}//减:case 6:if(workMode){}//无关按键default:break;}}

4.初入显示的调整

此时进入default中时间和温度并不会变化

所以我们应该switch加case 0:并在里面调用刷新温度和时间的函数

十六、数字加减

1.分析

1. 设置xCursor和yCursor的位置

是否会超出范围

2.提取全局的阈值

//处理数字加1和减1的问题
//isAdd为0时减1,isAdd为1时加1
void NumAddSub(unsigned char isAdd){//第一步:要判断当前的光标位置是否合法//第二步:根据xCursor和yCursor来判断当前光标在哪一个值上//LcdShowFloat(9,0,tempTh);//x:7-12  y:0   阈值温度if(yCursor==0){//上一行,显示温度if(xCursor==7){//十位if(isAdd){//此时isAdd=1表示要加1tempTh+=10;}else{tempTh-=10;}}else if(xCursor==8){//个位if(isAdd){//此时isAdd=1表示要加1tempTh+=1;}else{tempTh-=1;}}else if(xCursor==10){//小数点后面一位if(isAdd){//此时isAdd=1表示要加1tempTh+=0.1;}else{tempTh-=0.1;}}}//用户按下5,6按键调整之后进行更新LcdShowFloat(9,0,tempTh);//x:7-12  y:0
}

 

2.问题解决

1.修改值后,光标移动

解决方法:

2.按一下,跳动很多格子

在每一个case后面加上delay

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/161604.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

00TD时尚儿童穿搭,这件小熊毛衣太好看了吧

寒冷的秋冬季怎么少的了毛衣呢 软糯亲肤又时尚百搭的款谁不爱 除了纯色还有条纹设计 可爱小熊图案可爱又吸睛 经典时尚的款怎么穿都好看哦&#xff01;

css 特别样式记录

一、 这段代码神奇的地方在于&#xff0c; 本来容器的宽度只有1200px&#xff0c;如果不给img赋予宽度100%&#xff0c;那么图片 会超出盒子&#xff0c;如果给了img赋予了宽度100%&#xff0c;多个图片会根据自己图片大小的比例&#xff0c;去分完那1200px&#xff0c;如图二。…

Netty P1 NIO 基础,网络编程

Netty P1 NIO 基础&#xff0c;网络编程 教程地址&#xff1a;https://www.bilibili.com/video/BV1py4y1E7oA https://nyimac.gitee.io/2021/04/25/Netty%E5%9F%BA%E7%A1%80/ 1. 三大组件 1.1 Channel & Buffer Channel 类似 Stream&#xff0c;它是读写数据的双向通道…

华为9.20笔试 复现

第一题 丢失报文的位置 思路&#xff1a;从数组最小索引开始遍历 #include <iostream> #include <vector> using namespace std; // 求最小索引值 int getMinIdx(vector<int> &arr) {int minidx 0;for (int i 0; i < arr.size(); i){if (arr[i] …

【k8s总结】

资源下载&#xff1a;http://www.ziyuanwang.online/912.html Kubernetes(K8s) 一、Openstack&VM 1、认识虚拟化 1.1、什么是虚拟化 在计算机中&#xff0c;虚拟化&#xff08;英语&#xff1a;Virtualization&#xff09;是一种资源管理技术&#xff0c;是将计算机的…

力扣每日一题43:字符串相乘

题目描述&#xff1a; 给定两个以字符串形式表示的非负整数 num1 和 num2&#xff0c;返回 num1 和 num2 的乘积&#xff0c;它们的乘积也表示为字符串形式。 注意&#xff1a;不能使用任何内置的 BigInteger 库或直接将输入转换为整数。 示例 1: 输入: num1 "2"…

多模态及图像安全的探索与思考

前言 第六届中国模式识别与计算机视觉大会&#xff08;The 6th Chinese Conference on Pattern Recognition and Computer Vision, PRCV 2023&#xff09;已于近期在厦门成功举办。通过参加本次会议&#xff0c;使我有机会接触到许多来自国内外的模式识别和计算机视觉领域的研究…

【云计算网络安全】DDoS 攻击类型:什么是 ACK 洪水 DDoS 攻击

文章目录 一、什么是 ACK 洪水 DDoS 攻击&#xff1f;二、什么是数据包&#xff1f;三、什么是 ACK 数据包&#xff1f;四、ACK 洪水攻击如何工作&#xff1f;五、SYN ACK 洪水攻击如何工作&#xff1f;六、文末送书《AWD特训营》内容简介读者对象 一、什么是 ACK 洪水 DDoS 攻…

【MyBatis系列】- 什么是MyBatis

【MyBatis系列】- 什么是MyBatis 文章目录 【MyBatis系列】- 什么是MyBatis一、学习MyBatis知识必备1.1 学习环境准备1.2 学习前掌握知识二、什么是MyBatis三、持久层是什么3.1 为什么需要持久化服务3.2 持久层四、Mybatis的作用五、MyBatis的优点六、参考文档一、学习MyBatis知…

JavaWeb-10月16笔记

JavaWeb 现实生活中的互联网项目都是javaWeb项目, 包含网络, 多线程, 展示: HTML等其他的前端技术, 界面窗体展示(Swing包,AWT包 窗体), C#,… ** JAVAWeb架构: ** - B/S: 浏览器/服务器 优点: 以浏览器作为客户端, 使用这个软件, 用户不需要下载客户端, 程序更新,不需要…

2000年至2017年LandScan全球人口分布数据(1KM分辨率)

简介&#xff1a; LandScan全球人口分布数据来自于East View Cartographic&#xff0c;由美国能源部橡树岭国家实验室(ORNL)开发。LandScan运用GIS和遥感等创新方法&#xff0c;是全球人口数据发布的社会标准&#xff0c;是全球最为准确、可靠&#xff0c;基于地理位置的&…

【Overload游戏引擎细节分析】视图投影矩阵计算与摄像机

本文只罗列公式&#xff0c;不做具体的推导。 OpenGL本身没有摄像机(Camera)的概念&#xff0c;但我们为了产品上的需求与编程上的方便&#xff0c;一般会抽象一个摄像机组件。摄像机类似于人眼&#xff0c;可以建立一个本地坐标系。相机的位置是坐标原点&#xff0c;摄像机的朝…

AMEYA360-罗姆ROHM马来西亚工厂新厂房竣工

全球知名半导体制造商罗姆为了加强模拟IC的产能&#xff0c;在其马来西亚制造子公司ROHM-Wako Electronics (Malaysia) Sdn. Bhd.(以下简称“RWEM”)投建了新厂房&#xff0c;近日新厂房已经竣工&#xff0c;并举行了竣工仪式。 RWEM此前主要生产二极管和LED等小信号产品&#…

自己写spring boot starter问题总结

1. Unable to find main class 创建spring boot项目写自己的starterxi写完之后使用install出现Unable to find main class&#xff0c;这是因为spring boot打包需要一个启动类&#xff0c;按照以下写法就没事 <plugins><plugin><groupId>org.springframewo…

二、K8S之Pods

Pod 一、概念 K8S作为一个容器编排管理工具&#xff0c;它可以自动化容器部署、容器扩展、容器负载均衡等任务&#xff0c;并提供容器的自愈能力等功能。在Kubernetes中&#xff0c;Pod是最基本的调度单元&#xff0c;它是一组共享存储和网络资源的容器集合&#xff0c;通常是…

数字孪生与智慧城市:重塑未来城市生活的奇迹

今天&#xff0c;我们将探讨数字孪生和智慧城市两个颠覆性技术&#xff0c;它们正引领着未来城市生活的巨大变革。随着科技的飞速发展&#xff0c;数字孪生和智慧城市成为实现可持续发展和提升居民生活质量的关键策略。 数字孪生&#xff1a;实现现实与虚拟的完美融合 数字孪生…

使用RCurl和R来爬虫视频

以下是一个使用RCurl和R来爬虫视频的示例代码&#xff0c;代码中使用了https://www.duoip.cn/get_proxy来获取代理IP&#xff1a; # 引入必要的库 library(RCurl) library(rjson)# 获取代理IP proxy_url <- "https://www.duoip.cn/get_proxy" proxy <- getURL…

Java 反射

目录 反射机制&#x1f6a9;一个需求引出反射&快速入门反射机制反射的扩展-反射相关类反射的优点和缺点反射调用时会造成性能的一些降低->反射调用性能优化&#xff08;虽然优化程度不高&#xff0c;但是也是可以起到适当的优化的作用&#xff09; Class类&#x1f6a9;…

基于Springboot实现在线答疑平台系统项目【项目源码+论文说明】

基于Springboot实现在线答疑平台系统演示 摘要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大师生的喜爱&#xff0c;也逐渐进入了每个学生的使用。互联网具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本…

第五届芜湖机器人展,正运动助力智能装备“更快更准”更智能!

■展会名称&#xff1a; 第十一届中国(芜湖)科普产品博览交易会-第五届机器人展 ■展会日期 2023年10月21日-23日 ■展馆地点 中国ㆍ芜湖宜居国际博览中心B馆 ■展位号 B029 正运动技术&#xff0c;作为国内领先的运动控制企业&#xff0c;将于2023年10月21日参加芜湖机…