单片机综合小项目

news2024/11/25 2:45:28

一、单片机做项目常识

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.一些小问题

三、构成建立及框架搭建

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在模式2
	
	TL1=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)
		//软件复位TI
	TI=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;	//拉低DQ
	delay_10us(75);	//拉低750us
	DS18B20_PORT=1;	//DQ=1
	delay_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;	//如果超时则强制返回1
	else time_temp=0;
	while((!DS18B20_PORT)&&time_temp<20)	//等待DQ为高电平
	{
		time_temp++;
		delay_10us(1);
	}
	if(time_temp>=20)return 1;	//如果超时则强制返回1
	return 0;
}


/*******************************************************************************
* 函 数 名         : ds18b20_start
* 函数功能		   : 开始温度转换
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/
//转换命令
void ds18b20_start(void)
{
	ds18b20_reset();//复位
	ds18b20_check();//检查DS18B20
	ds18b20_write_byte(0xcc);//SKIP ROM
    ds18b20_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 ROM
    ds18b20_write_byte(0xbe);//读存储器

	datl=ds18b20_read_byte();//低字节
	dath=ds18b20_read_byte();//高字节
	value=(dath<<8)+datl;//合并为16位数据

	if((value&0xf800)==0xf800)//判断符号位,负温度
	{
		value=(~value)+1; //数据取反再加1
		temp=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转成字符串str
	unsigned char str[5] = {0};
	// 第1步:先由double的25.4得到uint的254
	unsigned 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=54
	c = (unsigned char)(tmp / 10);		// c = 5
	str[1] = c + 48;

	str[2] = '.';

	tmp = tmp % 10;		// 运算后tmp=4
	c = (unsigned char)(tmp / 1);		// c = 4
	str[3] = c + 48;

	str[4] = '\0';

	// 第二步:显示str
	LcdShowStr(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;	//拉低DQ
	delay_10us(75);	//拉低750us
	DS18B20_PORT=1;	//DQ=1
	delay_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;	//如果超时则强制返回1
	else time_temp=0;
	while((!DS18B20_PORT)&&time_temp<20)	//等待DQ为高电平
	{
		time_temp++;
		delay_10us(1);
	}
	if(time_temp>=20)return 1;	//如果超时则强制返回1
	return 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,否则为0
	else 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();//检查DS18B20
	ds18b20_write_byte(0xcc);//SKIP ROM
    ds18b20_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 ROM
    ds18b20_write_byte(0xbe);//读存储器

	datl=ds18b20_read_byte();//低字节
	dath=ds18b20_read_byte();//高字节
	value=(dath<<8)+datl;//合并为16位数据

	if((value&0xf800)==0xf800)//判断符号位,负温度
	{
		value=(~value)+1; //数据取反再加1
		temp=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;
    else
        addr = 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 char
		3)记得最后有一个'\0'
*/
void LcdShowDouble(unsigned char x,unsigned char y,double d){
	
	// 第一步:将double d转成字符串str
	unsigned char str[5] = {0};
	// 第1步:先由double的25.4得到uint的254
	unsigned 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=54
	c = (unsigned char)(tmp / 10);		// c = 5
	str[1] = c + 48;

	str[2] = '.';

	tmp = tmp % 10;		// 运算后tmp=4
	c = (unsigned char)(tmp / 1);		// c = 4
	str[3] = c + 48;

	str[4] = '\0';

	// 第二步:显示str
	LcdShowStr(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;
    else
        addr = 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转成字符串str
	unsigned char str[5] = {0};
	// 第1步:先由double的25.4得到uint的254
	unsigned 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=54
	c = (unsigned char)(tmp / 10);		// c = 5
	str[1] = c + 48;

	str[2] = '.';

	tmp = tmp % 10;		// 运算后tmp=4
	c = (unsigned char)(tmp / 1);		// c = 4
	str[3] = c + 48;

	str[4] = '\0';

	// 第二步:显示str
	LcdShowStr(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;//2023
	unsigned char mon;//1-12
	unsigned char date;//1-31【几号】
	unsigned char hour;//0-23
	unsigned char min;//0-59
	unsigned char sec;//0-59
	unsigned 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部分: 写入要读取的寄存器地址,addr
	for (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再制造下降沿然后继续
	// 读取下一个bit
		tmp = 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=2017
	c = dat / 1000;	   	// c = 2
	str[index+0] = c + '0';  	// 第1位入库

	dat %= 1000;		// dat = 017
	c = dat / 100;	   	// c = 0
	str[index+1] = c + '0';  	// 第2位入库

	dat %= 100;			// dat = 17
	c = dat / 10;	   	// c = 1
	str[index+2] = c + '0';  	// 第3位入库

	dat %= 10;			// dat = 7
	c = dat / 1;	   	// c = 7
	str[index+3] = c + '0';  	// 第4位入库
}


// 实现一个子函数,将十进制的2位整数转成一个字符串
void Int2Str2(unsigned int dat, unsigned char str[], unsigned char index)
{
	unsigned char c = 0;

	// 假设dat=17
	c = dat / 10;	   	// c = 1
	str[index+0] = c + '0';  	// 第1位入库

	dat %= 10;			// dat = 7
	c = dat / 1;	   	// c = 7
	str[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-4
	unsigned 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';

	// 第二步:显示str
	LcdShowStr(x, y, str);
}

main.c

	unsigned char c=0;
	
	
		InitLcd1602();
	

		LcdShowStr(0,0,"temp=");//0-4
	
	//显示:°C
		LcdShowStr(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得到24
	return (((bcd & 0xf0) >> 4) * 10 + (bcd & 0x0f));
}

unsigned char hex2bcd(unsigned char hex)
{
  	// 就是要把24转成0x24
	// 第一步,先由24得到2和4
	// (24 / 10) 就是2, (24 % 10)就是4
	// 第二步,再组合成0x24
	return (((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字节,addr
	for (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字节,value
	for (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-4
	unsigned 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';

	// 第二步:显示str
	LcdShowStr(x, y, str);
}

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

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

	dat %= 1000;		// dat = 017
	c = dat / 100;	   	// c = 0
	str[index+1] = c + '0';  	// 第2位入库

	dat %= 100;			// dat = 17
	c = dat / 10;	   	// c = 1
	str[index+2] = c + '0';  	// 第3位入库

	dat %= 10;			// dat = 7
	c = dat / 1;	   	// c = 7
	str[index+3] = c + '0';  	// 第4位入库
}


// 实现一个子函数,将十进制的2位整数转成一个字符串
void Int2Str2(unsigned int dat, unsigned char str[], unsigned char index)
{
	unsigned char c = 0;

	// 假设dat=17
	c = dat / 10;	   	// c = 1
	str[index+0] = c + '0';  	// 第1位入库

	dat %= 10;			// dat = 7
	c = dat / 1;	   	// c = 7
	str[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
	// 第二步,再组合成0x24
	return (((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.接线和原理图

P1^7

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*256us
	flag = 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.完整代码

#include"buzzer.h"

//buzzer的初始化
void buzzer_init(){
	xKHZ=4;//默认是4khz
	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 = 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.蜂鸣器频率设置

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构 优先级队列(堆)

数据结构 优先级队列(堆) 文章目录 数据结构 优先级队列(堆)1. 优先级队列1.1 概念 2. 优先级队列的模拟实现2.1 堆的概念2.2 堆的存储方式2.3 堆的创建2.3.1 堆向下调整2.3.2 堆的创建2.3.3 建堆的时间复杂度 2.4 堆的插入与删除2.4.1 堆的插入2.4.2 堆的删除 2.5 用堆模拟实现…

基于微服务+Java+Spring Cloud开发的建筑工地智慧平台源码 云平台多端项目源码

建筑工地智慧平台源码&#xff0c;施工管理端、项目监管端、数据大屏端、移动APP端全套源码 技术架构&#xff1a;微服务JavaSpring Cloud VueUniApp MySql自主版权实际应用案例演示 建筑工地智慧平台已应用于线上巡查、质量管理、实名制管理、危大工程管理、运渣车管理、绿色…

非连续分配管理方式之基本分页存储管理

连续分配&#xff1a;为用户进程分配的必须是一个连续的内存空间。 非连续分配&#xff1a;为用户进程分配的可以是一些分散的内存空间。 基本分页存储管理的思想∶把进程分页&#xff0c;各个页面可离散地放到各个的内存块中。 1.分页存储 1.内存空间分区 将内存空间分为一…

数据库 MySql快速导入外部数据库流程

适用于新安装MySql本地没有数据情况 外部MySql数据库文件 任务管理器停用Mysql进程 将外部文件替换本地默认文件即可 重启电脑导入完成。

MyLife - Docker安装rabbitmq

Docker安装rabbitmq 个人觉得像rabbitmq之类的基础设施在线上环境直接物理机安装使用可能会好些。但是在开发测试环境用docker容器还是比较方便的。这里学习下docker安装rabbitmq使用。 1. rabbitmq 镜像库地址 rabbitmq 镜像库地址&#xff1a;https://hub.docker.com/_/rabbi…

并联谐振DCDC变换器的设计与仿真

摘 要 在我们日常生活中&#xff0c;并联谐振变换器随处可见&#xff0c;因为其相比其他变换器而言结构相对简单&#xff0c;运行稳定且便于维修等优势&#xff0c;最重要的是并联谐振变换器在并网方面具有很好的优势。随着自动控制技术和微电子技术的不断革新&#xff0c;目前…

并联机器人结构分析与领域应用

并联机器人早在20世纪的90年代就已经崭露头角&#xff0c;具有刚度高、速度快、柔性强、重量轻等优点&#xff0c;是工业机器人的新生代力量。并联机器人与串联机器人一起构成了工业机器人的重要部分。在食品、医药、电子等轻工业中应用最为广泛&#xff0c;在物料的搬运、包装…

记录Bug:VScode中无法识别万能头文件#include<bits/stdc++.h>

问题&#xff1a; 在VScode中使用万能头文件#include<bits/stdc.h>编写程序时报错&#xff1a;“检测到 #include 错误。请更新 includePath。已为此翻译单元(D:\Code_C\desC。。。。”。但是普通的c语言头文件#include <stdio.h>等可以正常运行。 原因&#xff1…

Linux网络编程系列之UDP组播

一、什么是UDP组播 UDP组播是指使用用户数据报协议&#xff08;UDP&#xff09;实现的组播方式。组播是一种数据传输方式&#xff0c;允许单一数据包同时传输到多个接收者。在UDP组播中&#xff0c;一个数据包可以被多个接收者同时接收&#xff0c;这样可以降低网络传输的负载和…

P1433 吃奶酪

#include <iostream> #include <cmath> using namespace std; #define M 15 #define S(n) ((n) * (n)) double indx[M 5], indy[M 5], ans 0, sum 0;//坐标数组&#xff0c;从下标为1开始记录 int n, vis[M 5] { 0 };//vis数组&#xff0c;选过的数字标记为1…

N点复序列求2个N点实序列的快速傅里叶变换

一、方法简介 通过一个点复数序列求出两个点实数序列的离散傅里叶变换&#xff0c;进一步提升快速傅里叶变换的效率。 二、方法详解 和是实数序列&#xff0c;且长度都为&#xff0c;定义复数序列&#xff1a; &#xff0c; 则序列和可表示为&#xff1a; 的离散傅…

openssl学习——消息认证码原理

消息认证码原理 消息认证码&#xff08;Message Authentication Code, MAC&#xff09;是一种技术&#xff0c;它的原理是通过对消息和密钥进行特定的处理&#xff0c;生成一个固定长度的数据&#xff0c;这个数据就是消息认证码&#xff08;MAC&#xff09;。这个过程可以看作…

【10】基础知识:React - DOM的diffing算法

一、虚拟 DOM 中 key 的作用 react/vue 中的 key 有什么作用&#xff1f;key的内部原理是什么&#xff1f; 简单来说&#xff1a; key 是虚拟 DOM 对象的标识&#xff0c;在更新显示时 key 起着极其重要的作用&#xff0c;提高渲染效率&#xff0c;防止渲染错误。 详细的说…

和琪宝的深圳,香港之旅~

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作)&#xff0c;由 李兆龙 确认&#xff0c;转载请注明版权。 引言 国庆期间原定和琪宝的旅游计划是深圳-香港-厦门。但是奈何在去厦门的前一天&#xff0c;…

STM32 BootLoader设置

编写bootloader程序&#xff1a; 直接复制下面代码到自己程序中。 typedef void (*iapfun)(void); //定义一个函数类型的参数. iapfun jump2app; //设置栈顶地址 //addr:栈顶地址 __asm void MSR_MSP(u32 addr) {MSR MSP, r0 //set Main Stack valueBX r14 }//跳转到…

TikTok国际版 使用特网科技Bluestacks模拟器安装方法

特网科技Bluestacks模拟器主机 桌面自带Bluestacks模拟器 TikTok国际版Bluestacks模拟器搜索tiktot 登录google应用商店-安装TikTok 安装过程可能需要3-5分钟不等-配置过低可能会导致安装失败&#xff0c;建议升级更高内存。 安装完成-打开 安装成功APP-我的游戏查看 打开国际版…

phpcms_v9模板制作及二次开发常用代码

0:调用最新文章&#xff0c;带所在版块 {pc:get sql"SELECT a.title, a.catid, b.catid, b.catname, a.url as turl ,b.url as curl,a.id FROM v9_news a, v9_category b WHERE a.catid b.catid ORDER BY a.id DESC " num"15" cache"300"} {lo…

光电柴微电网日前调度报告

摘要 微电网是目前国内外应用较为广泛的一种绿色可再生能源&#xff0c;近几年我国微电网产业的发展十分迅速。然后&#xff0c;越来越多的微电网系统建立并网&#xff0c;微电网产生的电能受外界因素影响较大&#xff0c;具有一定的随机性和波动性&#xff0c;给并网后的电力系…

leetCode 72. 编辑距离 动态规划

72. 编辑距离 - 力扣&#xff08;LeetCode&#xff09; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 编辑距离的应用场景&#xff1a;…

【内网攻击】DHCP协议概念——地址池耗尽攻击

目录 前言 DHCP 服务概念 1&#xff09;客户端发送DHCP Discovery广播包 2&#xff09;服务器响应DHCP Offer广播包 3&#xff09;客户机发送DHCP Request广播包 4&#xff09;服务器发送DHCP ACK广播包 部署DHCP服务器 dhcp地址池消耗攻击 攻击防御 前言 现在思考我们…