单片机综合小项目

一、单片机做项目常识

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

cpp 复制代码
#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
cpp 复制代码
//开头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.高层时序

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

cpp 复制代码
/*******************************************************************************
* 函 数 名         : 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

cpp 复制代码
// 显示类似于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

cpp 复制代码
#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

cpp 复制代码
#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

cpp 复制代码
#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

cpp 复制代码
//显示类似于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);
}
cpp 复制代码
void main(void){

	double t=35.4;
	InitLcd1602();
	//LcdShowStr(0,0,"nihaoliaoxiaoyi");
	LcdShowDouble(0,0,t);
}

2.输出摄氏度符号

3.注意点

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

5.完整代码

lcd1602.c

cpp 复制代码
#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.结构体的定义

cpp 复制代码
//封装出来的一个表示时间的结构体类型
//类型不占内存,也不表示一个具体时间,但是类型可以用来生成时间
//每一个时间变量占一定的内存,每一个时间变量就代表一个具体的时间
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运行时间得到提升

cpp 复制代码
// 用来存储读取的时间的,格式是:秒分时日月周年
//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.读取函数

cpp 复制代码
// 从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个函数分别将数值转换为字符串

cpp 复制代码
// 实现一个子函数,将十进制的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

cpp 复制代码
	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码,所以才会产生乱码。故我们要进行转换才可以正确的显示。

cpp 复制代码
// 实现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));
}
cpp 复制代码
// 用结构体方式来实现的读取时间的函数
// 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

cpp 复制代码
// 向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

cpp 复制代码
	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.单独修改时间

cpp 复制代码
//只修改年份
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)我们在显示屏上显示数据,因为我们获取到的是数值,但是要转换为字符串才可以进行展示,所以我们要将获取到的时间转换为字符串

cpp 复制代码
// 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个函数

cpp 复制代码
// 实现一个子函数,将十进制的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码

cpp 复制代码
//十六进制转换为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)写入时间函数

cpp 复制代码
//用结构体方式在实现时间的修改函数
//本函数用于向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.初始化

cpp 复制代码
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.中断函数

cpp 复制代码
//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.中断函数优化

cpp 复制代码
//宏定义
//设置蜂鸣器的输出频率为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删除

cpp 复制代码
//让蜂鸣器一直响的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.一直响/不响

cpp 复制代码
//让蜂鸣器开始响
void buzzer_start(void){
	
	flag=0;
}


//让蜂鸣器停止响
void buzzer_stop(void){
	flag=1;
}


//设置蜂鸣器响的频率
void buzzer_freq_set(unsigned char tmp){
	xKHZ=tmp;//修改蜂鸣器的频率
}

3.完整代码

cpp 复制代码
#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.蜂鸣器频率设置

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

相关推荐
国科安芯3 小时前
ASP4644芯片低功耗设计思路解析
网络·单片机·嵌入式硬件·安全
充哥单片机设计3 小时前
【STM32项目开源】基于STM32的智能厨房火灾燃气监控
stm32·单片机·嵌入式硬件
CiLerLinux10 小时前
第四十九章 ESP32S3 WiFi 路由实验
网络·人工智能·单片机·嵌入式硬件
时光の尘10 小时前
【PCB电路设计】常见元器件简介(电阻、电容、电感、二极管、三极管以及场效应管)
单片机·嵌入式硬件·pcb·二极管·电感·三极管·场效应管
Lu Zelin10 小时前
单片机为什么不能跑Linux
linux·单片机·嵌入式硬件
宁静致远202111 小时前
stm32 freertos下基于hal库的模拟I2C驱动实现
stm32·嵌入式硬件·freertos
Wave84516 小时前
STM32--智能小车
stm32·单片机·嵌入式硬件
wdfk_prog18 小时前
[Linux]学习笔记系列 -- lib/timerqueue.c Timer Queue Management 高精度定时器的有序数据结构
linux·c语言·数据结构·笔记·单片机·学习·安全
充哥单片机设计1 天前
【STM32项目开源】基于STM32的智能家居环境(空气质量)检测系统
stm32·单片机·嵌入式硬件
夜月yeyue1 天前
ART 加速器、流水线与指令预测的关系详解
linux·服务器·c语言·单片机·嵌入式硬件·性能优化·嵌入式高阶技巧