分类 Learn 下的文章

前言

必读

51单片机入门教程(上篇)(代码+个人理解) – Echo (liveout.cn)

GitHub仓库链接:https://github.com/PGwind/51code

这篇文章是记录我粗略学习51单片机的一些代码,我会加些个人理解以及注释在里面。

因为是囫囵吞枣式学习,所以质量不是很好,后期我会慢慢优化 ?

如果你想要学习单片机,可以观看下面的B站教程并配合本文档学习

本文章使用的51单片机是 普中STC89C52RC

教程

推荐B站视频: 【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】 https://www.bilibili.com/video/BV1Mb411e7re/?share_source=copy_web&vd_source=55024add0415795a359bd7b29ca21142(应该都知道吧)。

资源

B站江科大资源 链接:https://pan.baidu.com/s/1dLED_1VqL66qYItLl5ic4A?pwd=1111 提取码:1111

普中 链接:https://pan.baidu.com/s/1dNCHm9lLMP8pe3rZu3ktZQ?pwd=1111 提取码:1111


9. LED点阵屏

原理

88LED

88LED引脚
88LED引脚

74HC595

74HC595是串行输入并行输出的移位寄存器,3根线输入串行数据,8根线输出比并行数据,多片级后,可输出16为、24为、32位等,常用于IO口扩展

9.1 LED点阵显示图形

#include <REGX52.H>
#include "Delay.h"
sbit RCK=P3^5;  //RCLK 上升沿锁存
sbit SCK=P3^6;  //SRCLK 上升沿移位
sbit SER=P3^4;  //SER

#define MATRIX_LED_PORT     P0

//Function Definition
/**
  * @brief 74HC595写入一个字节
  * @param  要写入的字节
  * @retval 无
  */


void _74HC595_WriteByte(unsigned char Byte) 
{
	unsigned char i;
	for (i=0;i<8;i++)  
	{
		SER=Byte&(0x80>>i);  //1000 0000 
	  SCK=1;
	  SCK=0;
	}
	RCK=1;
	RCK=0;
}

//Function Definition
/**
  * @brief LED点阵屏显示一列数据
  * @param  Column 要选择的列,范围:0~7,0在最左边
  * @param  Data 选择列显示的数据,高位在上,1位亮,0位灭
  * @retval 无          
  */

void MatrixLED_ShowColumn(unsigned char Column,Data)
{
	_74HC595_WriteByte(Data);  //Data==Byte
	MATRIX_LED_PORT=~(0x80>>Column); //0选中,1不选中
  Delay(1); //延迟
  MATRIX_LED_PORT=0xFF;  //全灭  消影
}


void main()
{
	SCK=0; //刚开始是高电平,所以要置0,使其为低电平
	RCK=0;
	
	while(1)
	{
		MatrixLED_ShowColumn(0,0x3C); //1亮0不亮
		MatrixLED_ShowColumn(1,0x42);
		MatrixLED_ShowColumn(2,0xA9);
		MatrixLED_ShowColumn(3,0x85);
		MatrixLED_ShowColumn(4,0x85);
		MatrixLED_ShowColumn(5,0xA9);
		MatrixLED_ShowColumn(6,0x42);
		MatrixLED_ShowColumn(7,0x3C);
	}
}

9.2 LED点阵显示动画

头文件

//MatrixLED.h
#ifndef __MATRIX_LED_H_
#define __MATRIX_LED_H_
void MatrixLED_Init();
void _74HC595_WriteByte(unsigned char Byte);
void MatrixLED_ShowColumn(unsigned char Column,Data);
#endif

//Delay.h
#ifndef __DELAY_H_
#define __DELAY_H_
void Delay(unsigned int xms);
#endif

函数

//MatrixLED.c
#include <REGX52.H>
#include "Delay.h"

sbit RCK=P3^5;  //RCLK 上升沿锁存
sbit SCK=P3^6;  //SRCLK 上升沿移位
sbit SER=P3^4;  //SER

#define MATRIX_LED_PORT     P0

//Function Definition
/**
  * @brief 74HC595写入一个字节
  * @param  要写入的字节
  * @retval 无
  */


void _74HC595_WriteByte(unsigned char Byte) 
{
	unsigned char i;
	for (i=0;i<8;i++)  
	{
		SER=Byte&(0x80>>i);  //1000 0000 
	  SCK=1;
	  SCK=0;
	}
	RCK=1;
	RCK=0;
}


//Function Definition
/**
  * @brief 点阵屏初始化
  * @param  无
  * @retval 无
  */
void MatrixLED_Init()
{
	SCK=0; //刚开始是高电平,所以要置0,使其为低电平
	RCK=0;
}

//Function Definition
/**
  * @brief LED点阵屏显示一列数据
  * @param  Column 要选择的列,范围:0~7,0在最左边
  * @param  Data 选择列显示的数据,高位在上,1位亮,0位灭
  * @retval 无          
  */

void MatrixLED_ShowColumn(unsigned char Column,Data)
{
	_74HC595_WriteByte(Data);  //Data==Byte
	MATRIX_LED_PORT=~(0x80>>Column); //0选中,1不选中
  Delay(1); //延迟
  MATRIX_LED_PORT=0xFF;  //全灭  消影
}

//Delay.c
//Function Definition
/**
  * @brief 延迟函数
  * @param  无
  * @retval 1ms
  */
void Delay(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms)
	{
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);	
			xms--;
	}
} 

1. 通过流动实现动画

//main.c  通过流动实现动画
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"

unsigned char Animation[]={0x00,0x00,0x00,0x81,0xFF,0x81,0x00,0x00,
	0x7E,0x02,0x02,0x00,0x3C,0x42,0x42,0x3C,
	0x00,0x30,0x0C,0x02,0x0C,0x30,0x00,0x3E,
	0x2A,0x2A,0x00,0x00,0x60,0x18,0x06,0x18,0x60,
	0x00
};

void main()
{
	unsigned char i,Offset=1,Count=0;  //Offset偏移量,即数组第几列 Count控制移动速度
	MatrixLED_Init();
	while(1)
	{
		for (i=0; i<8; i++)
		{
			MatrixLED_ShowColumn(i,Animation[i+Offset]);
		}
		Count++;
		if (Count>12)
		{
			Count=0;
			Offset++;
			if(Offset>24) //防止数组溢出
			{
				Offset=0;
			}	
		}
	}
}

2. 通过每次刷新(即每帧)实现动画

//main.c  通过每次刷新(即每帧)实现动画
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"

unsigned char code Animation[]={
	0x00,0x0C,0x1C,0x21,0xDE,0xE0,0x3F,0x10,
	0x00,0x06,0x16,0x21,0xDE,0xE0,0x3F,0x10,
	0x00,0x03,0x13,0x21,0xDE,0xE0,0x3F,0x10,
	0x0C,0x0C,0x10,0x21,0xDE,0xE0,0x3F,0x10,
	0x00,0x0C,0x1C,0x21,0xDE,0xE0,0x3F,0x10,
}; //加code这些数据会放在flash里面,不然在RAM里。flash内存较大
//但是后面数组无法更改

void main()
{
	unsigned char i,Offset=0,Count=0;  //Offset偏移量,即数组第几列 Count控制变换速度
	MatrixLED_Init();
	while(1)
	{
		for (i=0; i<8; i++)
		{
			MatrixLED_ShowColumn(i,Animation[i+Offset]);
		}
		Count++;
		if (Count>15)
		{
			Count=0;
			Offset+=8;
			if(Offset>32) //防止数组溢出
			{
				Offset=0;
			}		
		}
	}
}


10. DS1302时钟

DS1302:低功耗实时时钟芯片,可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能

RTC:实时时钟,是一种集成电路,通常称为时钟芯片(51单片机不带)

BCD码:用4位二进制数来表示1位十进制数 如:0001 0011 表示13,而 0001 1010 则不合法

原理

阅读厂家提供的DS1302中文手册

DS1302时钟

10.1 DS1302时钟

头文件

//DS1302.h
#ifndef __DS1302_H__
#define __DS1302_H__

//外部可调用时间数组,索引0~6分别为年、月、日、时、分、秒、星期
extern unsigned char DS1302_Time[];

void DS1302_Init(void);
void DS1302_WriteByte(unsigned char Command,Data);
unsigned char DS1302_ReadByte(unsigned char Command);
void DS1302_SetTime(void);
void DS1302_ReadTime(void);

#endif

函数

//DS1302.c
#include <REGX52.H>

//引脚定义
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;

//寄存器写入地址/指令定义
#define DS1302_SECOND		0x80
#define DS1302_MINUTE		0x82
#define DS1302_HOUR			0x84
#define DS1302_DATE			0x86
#define DS1302_MONTH		0x88
#define DS1302_DAY			0x8A
#define DS1302_YEAR			0x8C
#define DS1302_WP			0x8E

//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={19,11,16,12,59,55,6};

/**
  * @brief  DS1302初始化
  * @param  无
  * @retval 无
  */
void DS1302_Init(void)
{
	DS1302_CE=0;
	DS1302_SCLK=0;
}

/**
  * @brief  DS1302写一个字节
  * @param  Command 命令字/地址
  * @param  Data 要写入的数据
  * @retval 无
  */
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	for(i=0;i<8;i++)
	{
		DS1302_IO=Data&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	DS1302_CE=0;
}

/**
  * @brief  DS1302读一个字节
  * @param  Command 命令字/地址
  * @retval 读出的数据
  */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	Command|=0x01;	//将指令转换为读指令
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;
		DS1302_SCLK=1;
	}
	for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	DS1302_CE=0;
	DS1302_IO=0;	//读取后将IO设置为0,否则读出的数据会出错
	return Data;
}

/**
  * @brief  DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
  * @param  无
  * @retval 无
  */
void DS1302_SetTime(void)
{
	DS1302_WriteByte(DS1302_WP,0x00);
	DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);//十进制转BCD码后写入
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	DS1302_WriteByte(DS1302_WP,0x80);
}

/**
  * @brief  DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
  * @param  无
  * @retval 无
  */
void DS1302_ReadTime(void)
{
	unsigned char Temp;
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取
	Temp=DS1302_ReadByte(DS1302_MONTH);
	DS1302_Time[1]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DATE);
	DS1302_Time[2]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[3]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[4]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[5]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=Temp/16*10+Temp%16;
}
//main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"

void main()
{
	LCD_Init();
	DS1302_Init();
	LCD_ShowString(1,1,"  -  -  ");//静态字符初始化显示
	LCD_ShowString(2,1,"  :  :  ");
	
	DS1302_SetTime();//设置时间
	
	while(1)
	{
		DS1302_ReadTime();//读取时间
		LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年
		LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月
		LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日
		LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时
		LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分
		LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒
	}
}

10.2 DS1302可调时钟

#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Key.h"
#include "Timer0.h"

unsigned char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;

void TimeShow(void)//时间显示功能
{
	DS1302_ReadTime();//读取时间
	LCD_ShowNum(1,1,DS1302_Time[0],2);//显示年
	LCD_ShowNum(1,4,DS1302_Time[1],2);//显示月
	LCD_ShowNum(1,7,DS1302_Time[2],2);//显示日
	LCD_ShowNum(2,1,DS1302_Time[3],2);//显示时
	LCD_ShowNum(2,4,DS1302_Time[4],2);//显示分
	LCD_ShowNum(2,7,DS1302_Time[5],2);//显示秒
}

void TimeSet(void)//时间设置功能
{
	if(KeyNum==2)//按键2按下
	{
		TimeSetSelect++;//设置选择位加1
		TimeSetSelect%=6;//越界清零
	}
	if(KeyNum==3)//按键3按下
	{
		DS1302_Time[TimeSetSelect]++;//时间设置位数值加1
		if(DS1302_Time[0]>99){DS1302_Time[0]=0;}//年越界判断
		if(DS1302_Time[1]>12){DS1302_Time[1]=1;}//月越界判断
		if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || 
			DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断
		{
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}//大月
		}
		else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11)
		{
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}//小月
		}
		else if(DS1302_Time[1]==2)
		{
			if(DS1302_Time[0]%4==0)
			{
				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}//闰年2月
			}
			else
			{
				if(DS1302_Time[2]>28){DS1302_Time[2]=1;}//平年2月
			}
		}
		if(DS1302_Time[3]>23){DS1302_Time[3]=0;}//时越界判断
		if(DS1302_Time[4]>59){DS1302_Time[4]=0;}//分越界判断
		if(DS1302_Time[5]>59){DS1302_Time[5]=0;}//秒越界判断
	}
	if(KeyNum==4)//按键3按下
	{
		DS1302_Time[TimeSetSelect]--;//时间设置位数值减1
		if(DS1302_Time[0]<0){DS1302_Time[0]=99;}//年越界判断
		if(DS1302_Time[1]<1){DS1302_Time[1]=12;}//月越界判断
		if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 || 
			DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=31;}//大月
			if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
		}
		else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11)
		{
			if(DS1302_Time[2]<1){DS1302_Time[2]=30;}//小月
			if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
		}
		else if(DS1302_Time[1]==2)
		{
			if(DS1302_Time[0]%4==0)
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=29;}//闰年2月
				if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
			}
			else
			{
				if(DS1302_Time[2]<1){DS1302_Time[2]=28;}//平年2月
				if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
			}
		}
		if(DS1302_Time[3]<0){DS1302_Time[3]=23;}//时越界判断
		if(DS1302_Time[4]<0){DS1302_Time[4]=59;}//分越界判断
		if(DS1302_Time[5]<0){DS1302_Time[5]=59;}//秒越界判断
	}
	//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
	if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1,"  ");}
	else {LCD_ShowNum(1,1,DS1302_Time[0],2);}
	if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4,"  ");}
	else {LCD_ShowNum(1,4,DS1302_Time[1],2);}
	if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7,"  ");}
	else {LCD_ShowNum(1,7,DS1302_Time[2],2);}
	if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1,"  ");}
	else {LCD_ShowNum(2,1,DS1302_Time[3],2);}
	if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4,"  ");}
	else {LCD_ShowNum(2,4,DS1302_Time[4],2);}
	if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7,"  ");}
	else {LCD_ShowNum(2,7,DS1302_Time[5],2);}
}

void main()
{
	LCD_Init();
	DS1302_Init();
	Timer0Init();
	LCD_ShowString(1,1,"  -  -  ");//静态字符初始化显示
	LCD_ShowString(2,1,"  :  :  ");
	
	DS1302_SetTime();//设置时间
	
	while(1)
	{
		KeyNum=Key();//读取键码
		if(KeyNum==1)//按键1按下
		{
			if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换
			else if(MODE==1){MODE=0;DS1302_SetTime();}
		}
		switch(MODE)//根据不同的功能执行不同的函数
		{
			case 0:TimeShow();break;
			case 1:TimeSet();break;
		}
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=500)//每500ms进入一次
	{
		T0Count=0;
		TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
	}
}


11. 蜂鸣器

原理

蜂鸣器

 C调音符与频率对照表

11.1 蜂鸣器播放提示音

头文件

//Buzzer.h
#ifndef __BUZZER_H__
#define __BUZZER_H__

void Buzzer_Time(unsigned int ms);

#endif

函数

//Buzzer.c
#include <REGX52.H>
#include <INTRINS.H>

//蜂鸣器端口:
sbit Buzzer=P2^5;

/**
  * @brief  蜂鸣器私有延时函数,延时500us
  * @param  无
  * @retval 无
  */
void Buzzer_Delay500us()		//@12.000MHz
{
	unsigned char i;

	_nop_();
	i = 247;
	while (--i);
}

/**
  * @brief  蜂鸣器发声
  * @param  ms 发声的时长,范围:0~32767
  * @retval 无
  */
void Buzzer_Time(unsigned int ms)
{
	unsigned int i;
	for(i=0;i<ms*2;i++)
	{
		Buzzer=!Buzzer;
		Buzzer_Delay500us();
	}
}
//mian.c
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Buzzer.h"

sbit Buzzer=P2^5;

unsigned char KeyNum;
unsigned int i;

void main()
{
	 Nixie(1,0);
	while(1)
	{
		KeyNum=Key();
		if(KeyNum)
		{
			for(i=0; i<100; i++)
			{
				Buzzer=!Buzzer;
				Delay(1);
			}
			Buzzer_Time(100);
			Nixie(1,KeyNum);
		}
	}
}

11.2 蜂鸣器播放音乐

#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"

//蜂鸣器端口定义
sbit Buzzer=P2 ^5;

//播放速度,值为四分音符的时长(ms)
#define SPEED	500

//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P	0
#define L1	1
#define L1_	2
#define L2	3
#define L2_	4
#define L3	5
#define L4	6
#define L4_	7
#define L5	8
#define L5_	9
#define L6	10
#define L6_	11
#define L7	12
#define M1	13
#define M1_	14
#define M2	15
#define M2_	16
#define M3	17
#define M4	18
#define M4_	19
#define M5	20
#define M5_	21
#define M6	22
#define M6_	23
#define M7	24
#define H1	25
#define H1_	26
#define H2	27
#define H2_	28
#define H3	29
#define H4	30
#define H4_	31
#define H5	32
#define H5_	33
#define H6	34
#define H6_	35
#define H7	36

//索引与频率对照表
unsigned int FreqTable[]={
	0,
	63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
	64580,64633,64684,64732,64777,64820,64860,64898,64934,64968,65000,65030,
	65058,65085,65110,65134,65157,65178,65198,65217,65235,65252,65268,65283,
};

//乐谱
unsigned char code Music[]={
	//音符,时值,
	
	//1
	P,	4,
	P,	4,
	P,	4,
	M6,	2,
	M7,	2,
	
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	M7,	4+4+4,
	M3,	2,
	M3,	2,
	
	//2
	M6,	4+2,
	M5,	2,
	M6, 4,
	H1,	4,
	
	M5,	4+4+4,
	M3,	4,
	
	M4,	4+2,
	M3,	2,
	M4,	4,
	H1,	4,
	
	//3
	M3,	4+4,
	P,	2,
	H1,	2,
	H1,	2,
	H1,	2,
	
	M7,	4+2,
	M4_,2,
	M4_,4,
	M7,	4,
	
	M7,	8,
	P,	4,
	M6,	2,
	M7,	2,
	
	//4
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	M7,	4+4+4,
	M3,	2,
	M3,	2,
	
	M6,	4+2,
	M5,	2,
	M6, 4,
	H1,	4,
	
	//5
	M5,	4+4+4,
	M2,	2,
	M3,	2,
	
	M4,	4,
	H1,	2,
	M7,	2+2,
	H1,	2+4,
	
	H2,	2,
	H2,	2,
	H3,	2,
	H1,	2+4+4,
	
	//6
	H1,	2,
	M7,	2,
	M6,	2,
	M6,	2,
	M7,	4,
	M5_,4,
	
	
	M6,	4+4+4,
	H1,	2,
	H2,	2,
	
	H3,	4+2,
	H2,	2,
	H3,	4,
	H5,	4,
	
	//7
	H2,	4+4+4,
	M5,	2,
	M5,	2,
	
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	H3,	4+4+4+4,
	
	//8
	M6,	2,
	M7,	2,
	H1,	4,
	M7,	4,
	H2,	2,
	H2,	2,
	
	H1,	4+2,
	M5,	2+4+4,
	
	H4,	4,
	H3,	4,
	H3,	4,
	H1,	4,
	
	//9
	H3,	4+4+4,
	H3,	4,
	
	H6,	4+4,
	H5,	4,
	H5,	4,
	
	H3,	2,
	H2,	2,
	H1,	4+4,
	P,	2,
	H1,	2,
	
	//10
	H2,	4,
	H1,	2,
	H2,	2,
	H2,	4,
	H5,	4,
	
	H3,	4+4+4,
	H3,	4,
	
	H6,	4+4,
	H5,	4+4,
	
	//11
	H3,	2,
	H2,	2,
	H1,	4+4,
	P,	2,
	H1,	2,
	
	H2,	4,
	H1,	2,
	H2,	2+4,
	M7,	4,
	
	M6,	4+4+4,
	P,	4,
	
	0xFF	//终止标志
};

unsigned char FreqSelect,MusicSelect;

void main()
{
	Timer0Init(); //定时器0初始化
	while(1)
	{
		if(Music[MusicSelect]!=0xFF)	//如果不是停止标志位
		{
			FreqSelect=Music[MusicSelect];	//选择音符对应的频率
			MusicSelect++;
			Delay(SPEED/4*Music[MusicSelect]);	//选择音符对应的时值
			MusicSelect++;
			TR0=0;
			Delay(5);	//音符间短暂停顿
			TR0=1;
		}
		else	//如果是停止标志位
		{
			TR0=0;
			while(1);
		}
	}
}

void Timer0_Routine() interrupt 1
{
	if(FreqTable[FreqSelect])	//如果不是休止符
	{
		/*取对应频率值的重装载值到定时器*/
		TL0 = FreqTable[FreqSelect]%256;		//设置定时初值
		TH0 = FreqTable[FreqSelect]/256;		//设置定时初值
		Buzzer=!Buzzer;	//翻转蜂鸣器IO口
	}
}


12. AT24C02(存储器)

原理

EEPROM

12.1 AT24C02数据存储

头文件

//12C.h
#ifndef __I2C_H_
#define __I2C_H_

void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);

#endif
//AT24C02.h
#ifndef __AT24C02_H_
#define __AT24C02_H_

void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);

#endif

函数

//I2C.c
#include <REGX52.H>

sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;

//Function Definition
/**
  * @brief I2C开始
  * @param  无
  * @retval 无
  */
void I2C_Start(void)
{
	I2C_SDA=1; //高
	I2C_SCL=1;
 	I2C_SDA=0; //低
	I2C_SCL=0;
}

//Function Definition
/**
  * @brief I2C停止
  * @param  无
  * @retval 无
  */
void I2C_Stop(void)
{
	I2C_SDA=0; //低
	I2C_SCL=1; //高
	I2C_SDA=1; //高
}

//Function Definition
/**
  * @brief I2C发送一个字节
  * @param  Byte 要发送的字节
  * @retval 无
  */
void I2C_SendByte(unsigned char Byte)
{
	unsigned char i;
	for (i=0;i<8;i++)
	{
		I2C_SDA=Byte&(0x80>>i);
		I2C_SCL=1;
		I2C_SCL=0;
	}
	
}

//Function Definition
/**
  * @brief I2C接收一个字节
  * @param  无
  * @retval 接收到的一个字节数据 
  */
unsigned char I2C_ReceiveByte(void)
{
	unsigned char Byte,i;
	
	I2C_SDA=1;
	
	for (i=0;i<8;i++)
	{
		I2C_SCL=1;
		if(I2C_SDA) {Byte|=(0x80>>i);}
		I2C_SCL=0;
	}
	return Byte;
}

//Function Definition
/**
  * @brief  I2C发送应答位
  * @param  AckBit 应答位,0为应答,1为非应答
  * @retval 无
  */
void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA=AckBit;
	I2C_SCL=1;
	I2C_SCL=0;
}

//Function Definition
/**
  * @brief  I2C接收应答位
  * @param  无
  * @retval 接收到的应答位,  0为应答,1为非应答
  */
unsigned char I2C_ReceiveAck(void)
{
	unsigned char AckBit;
	I2C_SDA=1;
	I2C_SCL=1;
	AckBit=I2C_SDA;
	I2C_SCL=0;
	return AckBit;
}
//AT24C02.c
#include <REGX52.H>
#include "I2C.h"

#define AT24C02_ADDRESS  0xA0

//Function Definition
/**
  * @brief AT24C02写入一个字节
  * @param  WordAddress  要写入字节的地址   Data  要写入的数据
  * @retval 无
  */
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_SendByte(Data);
	I2C_ReceiveAck();
	I2C_Stop();
}

//Function Definition
/**
  * @brief  AT24C02读取一个字节
  * @param   WordAddress  要读出字节的地址  
  * @retval  Data  要读出的数据
  */
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS|0x01);
	I2C_ReceiveAck();
	Data=I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	return Data;
}
//main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"

unsigned char KeyNum;
unsigned int Num;

void main()
{
	LCD_Init();
	LCD_ShowNum(1,1,Num,5);
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)
		{
			Num++;
			LCD_ShowNum(1,1,Num,5);
		}
		
		if(KeyNum==2)
		{
			Num--;
			LCD_ShowNum(1,1,Num,5);
		}
		
		if(KeyNum==3)
		{
			AT24C02_WriteByte(0,Num%256); //低8位
			Delay(5);
			AT24C02_WriteByte(1,Num/256); //高8位
			Delay(5);
			LCD_ShowString(2,1,"Write Ok");
			Delay(1000);
			LCD_ShowString(2,1,"         ");
		}
		
		if(KeyNum==4)
		{
			Num=AT24C02_ReadByte(0); //o地址
			Num|=AT24C02_ReadByte(1)<<8; //1地址
			LCD_ShowNum(1,1,Num,5);
			LCD_ShowString(2,1,"Read OK");
			Delay(1000);
			LCD_ShowString(2,1,"         ");
		}
	}
}

12.2 秒表(定时器扫描按键数码管)

头文件

//Nixie.h
#ifndef __NIXIE_H__
#define __NIXIE_H__

void Nixie_SetBuf(unsigned char Location,Number);
void Nixie_Scan(unsigned char Location,Number);
void Nixie_Loop(void);

#endif
//Key.h
#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key(void);
void Key_Loop(void);

#endif

函数

//Nixie.c
#include <REGX52.H>
#include "Delay.h"

//数码管显示缓存区
unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};

//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};

/**
  * @brief  设置显示缓存区
  * @param  Location 要设置的位置,范围:1~8
  * @param  Number 要设置的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_SetBuf(unsigned char Location,Number)
{
	Nixie_Buf[Location]=Number;
}

/**
  * @brief  数码管扫描显示
  * @param  Location 要显示的位置,范围:1~8
  * @param  Number 要显示的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_Scan(unsigned char Location,Number)
{
	P0=0x00;				//段码清0,消影
	switch(Location)		//位码输出
	{
		case 1:P2_4=1;P2_3=1;P2_2=1;break;
		case 2:P2_4=1;P2_3=1;P2_2=0;break;
		case 3:P2_4=1;P2_3=0;P2_2=1;break;
		case 4:P2_4=1;P2_3=0;P2_2=0;break;
		case 5:P2_4=0;P2_3=1;P2_2=1;break;
		case 6:P2_4=0;P2_3=1;P2_2=0;break;
		case 7:P2_4=0;P2_3=0;P2_2=1;break;
		case 8:P2_4=0;P2_3=0;P2_2=0;break;
	}
	P0=NixieTable[Number];	//段码输出
}

/**
  * @brief  数码管驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Nixie_Loop(void)
{
	static unsigned char i=1;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i>=9){i=1;}
}
//Key.c
#include <REGX52.H>
#include "Delay.h"

unsigned char Key_KeyNumber;

/**
  * @brief  获取按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char Temp=0;
	Temp=Key_KeyNumber;
	Key_KeyNumber=0;
	return Temp;
}

/**
  * @brief  获取当前按键的状态,无消抖及松手检测
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key_GetState()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){KeyNumber=1;}
	if(P3_0==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}
	
	return KeyNumber;
}

/**
  * @brief  按键驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Key_Loop(void)
{
	static unsigned char NowState,LastState;
	LastState=NowState;				//按键状态更新
	NowState=Key_GetState();		//获取当前按键状态
	//如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测
	if(LastState==1 && NowState==0)
	{
		Key_KeyNumber=1;
	}
	if(LastState==2 && NowState==0)
	{
		Key_KeyNumber=2;
	}
	if(LastState==3 && NowState==0)
	{
		Key_KeyNumber=3;
	}
	if(LastState==4 && NowState==0)
	{
		Key_KeyNumber=4;
	}
}
//main.c
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "AT24C02.h"

unsigned char KeyNum;
unsigned char Min,Sec,MiniSec;
unsigned char RunFlag;

void main()
{
	Timer0_Init();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)			//K1按键按下
		{
			RunFlag=!RunFlag;	//启动标志位翻转
		}
		if(KeyNum==2)			//K2按键按下
		{
			Min=0;				//分秒清0
			Sec=0;
			MiniSec=0;
		}
		if(KeyNum==3)			//K3按键按下
		{
			AT24C02_WriteByte(0,Min);	//将分秒写入AT24C02
			Delay(5);
			AT24C02_WriteByte(1,Sec);
			Delay(5);
			AT24C02_WriteByte(2,MiniSec);
			Delay(5);
		}
		if(KeyNum==4)			//K4按键按下
		{
			Min=AT24C02_ReadByte(0);	//读出AT24C02数据
			Sec=AT24C02_ReadByte(1);
			MiniSec=AT24C02_ReadByte(2);
		}
		Nixie_SetBuf(1,Min/10);	//设置显示缓存,显示数据
		Nixie_SetBuf(2,Min%10);
		Nixie_SetBuf(3,11);
		Nixie_SetBuf(4,Sec/10);
		Nixie_SetBuf(5,Sec%10);
		Nixie_SetBuf(6,11);
		Nixie_SetBuf(7,MiniSec/10);
		Nixie_SetBuf(8,MiniSec%10);
	}
}

/**
  * @brief  秒表驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Sec_Loop(void)
{
	if(RunFlag)
	{
		MiniSec++;
		if(MiniSec>=100)
		{
			MiniSec=0;
			Sec++;
			if(Sec>=60)
			{
				Sec=0;
				Min++;
				if(Min>=60)
				{
					Min=0;
				}
			}
		}
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1,T0Count2,T0Count3;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
		T0Count1=0;
		Key_Loop();	//20ms调用一次按键驱动函数
	}
	T0Count2++;
	if(T0Count2>=2)
	{
		T0Count2=0;
		Nixie_Loop();//2ms调用一次数码管驱动函数
	}
	T0Count3++;
	if(T0Count3>=10)
	{
		T0Count3=0;
		Sec_Loop();	//10ms调用一次数秒表驱动函数
	}
}


13. DS18B20温度传感器

数字温度传感器

原理

DS18B02

13.1 温度读取

头文件

//OneWire.h
#ifndef __ONEWIRE_H__
#define __ONEWIRE_H__

unsigned char OneWire_Init(void);
void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit(void);
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveByte(void);

#endif
//DS18B02.h
#ifndef __DS18B20_H__
#define __DS18B20_H__

void DS18B20_ConvertT(void);
float DS18B20_ReadT(void);

#endif

函数

//OneWire.c
#include <REGX52.H>

//引脚定义
sbit OneWire_DQ=P3^7;

/**
  * @brief  单总线初始化
  * @param  无
  * @retval 从机响应位,0为响应,1为未响应
  */
unsigned char OneWire_Init(void)
{
	unsigned char i;
	unsigned char AckBit;
	OneWire_DQ=1;
	OneWire_DQ=0;
	i = 247;while (--i);		//Delay 500us
	OneWire_DQ=1;
	i = 32;while (--i);			//Delay 70us
	AckBit=OneWire_DQ;
	i = 247;while (--i);		//Delay 500us
	return AckBit;
}

/**
  * @brief  单总线发送一位
  * @param  Bit 要发送的位
  * @retval 无
  */
void OneWire_SendBit(unsigned char Bit)
{
	unsigned char i;
	OneWire_DQ=0;
	i = 4;while (--i);			//Delay 10us
	OneWire_DQ=Bit;
	i = 24;while (--i);			//Delay 50us
	OneWire_DQ=1;
}

/**
  * @brief  单总线接收一位
  * @param  无
  * @retval 读取的位
  */
unsigned char OneWire_ReceiveBit(void)
{
	unsigned char i;
	unsigned char Bit;
	OneWire_DQ=0;
	i = 2;while (--i);			//Delay 5us
	OneWire_DQ=1;
	i = 2;while (--i);			//Delay 5us
	Bit=OneWire_DQ;
	i = 24;while (--i);			//Delay 50us
	return Bit;
}

/**
  * @brief  单总线发送一个字节
  * @param  Byte 要发送的字节
  * @retval 无
  */
void OneWire_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		OneWire_SendBit(Byte&(0x01<<i));
	}
}

/**
  * @brief  单总线接收一个字节
  * @param  无
  * @retval 接收的一个字节
  */
unsigned char OneWire_ReceiveByte(void)
{
	unsigned char i;
	unsigned char Byte=0x00;
	for(i=0;i<8;i++)
	{
		if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
	}
	return Byte;
}
//DS18B02.c
#include <REGX52.H>
#include "OneWire.h"

//DS18B20指令
#define DS18B20_SKIP_ROM          0xCC
#define DS18B20_CONVERT_T         0x44
#define DS18B20_READ_SCRATCHPAD 	0xBE

/**
  * @brief  DS18B20开始温度变换
  * @param  无
  * @retval 无
  */
void DS18B20_ConvertT(void)
{
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_CONVERT_T);
}

/**
  * @brief  DS18B20读取温度
  * @param  无
  * @retval 温度数值
  */
float DS18B20_ReadT(void)
{
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
	TLSB=OneWire_ReceiveByte();
	TMSB=OneWire_ReceiveByte();
	Temp=(TMSB<<8)|TLSB;
	T=Temp/16.0;
	return T;
}
//main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"

float T;

void main()
{
	DS18B20_ConvertT();		//上电先转换一次温度,防止第一次读数据错误
	Delay(1000);			//等待转换完成
	LCD_Init();
	LCD_ShowString(1,1,"Temperature:");
	while(1)
	{
		DS18B20_ConvertT();	//转换温度
		T=DS18B20_ReadT();	//读取温度
		if(T<0)				//如果温度小于0
		{
			LCD_ShowChar(2,1,'-');	//显示负号
			T=-T;			//将温度变为正数
		}
		else				//如果温度大于等于0
		{
			LCD_ShowChar(2,1,'+');	//显示正号
		}
		LCD_ShowNum(2,2,T,3);		//显示温度整数部分
		LCD_ShowChar(2,5,'.');		//显示小数点
		LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示温度小数部分
	}
}

13.2 温度报警器

头文件

//Key.h
#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key(void);
void Key_Loop(void);

#endif
//OneWire.h
#ifndef __DS18B20_H__
#define __DS18B20_H__

void DS18B20_ConvertT(void);
float DS18B20_ReadT(void);

#endif

函数

//Key.c
#include <REGX52.H>
#include "Delay.h"

unsigned char Key_KeyNumber;

/**
  * @brief  获取按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char Temp=0;
	Temp=Key_KeyNumber;
	Key_KeyNumber=0;
	return Temp;
}

/**
  * @brief  获取当前按键的状态,无消抖及松手检测
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key_GetState()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){KeyNumber=1;}
	if(P3_0==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}
	
	return KeyNumber;
}

/**
  * @brief  按键驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Key_Loop(void)
{
	static unsigned char NowState,LastState;
	LastState=NowState;				//按键状态更新
	NowState=Key_GetState();		//获取当前按键状态
	//如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测
	if(LastState==1 && NowState==0)
	{
		Key_KeyNumber=1;
	}
	if(LastState==2 && NowState==0)
	{
		Key_KeyNumber=2;
	}
	if(LastState==3 && NowState==0)
	{
		Key_KeyNumber=3;
	}
	if(LastState==4 && NowState==0)
	{
		Key_KeyNumber=4;
	}
}
//OneWire.c  函数改动部分(跳出中断)单总线不能中断
#include <REGX52.H>

//引脚定义
sbit OneWire_DQ=P3^7;

/**
  * @brief  单总线初始化
  * @param  无
  * @retval 从机响应位,0为响应,1为未响应
  */
unsigned char OneWire_Init(void)
{
	unsigned char i;
	unsigned char AckBit;
	EA=0; 					//关闭中断
	OneWire_DQ=1;
	OneWire_DQ=0;
	i = 247;while (--i);		//Delay 500us
	OneWire_DQ=1;
	i = 32;while (--i);			//Delay 70us
	AckBit=OneWire_DQ;
	i = 247;while (--i);		//Delay 500us
	EA=1; 					//打开中断
	return AckBit;
}
.
.
.
//main
#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"
#include "Timer0.h"
#include "Buzzer.h"

float T,TShow;
char TLow,THigh;
unsigned char KeyNum;

void main()
{
	DS18B20_ConvertT();		//上电先转换一次温度,防止第一次读数据错误
	Delay(1000);			//等待转换完成
	THigh=AT24C02_ReadByte(0);	//读取温度阈值数据
	TLow=AT24C02_ReadByte(1);
	if(THigh>125 || TLow<-55 || THigh<=TLow)
	{
		THigh=20;			//如果阈值非法,则设为默认值
		TLow=15;
	}
	LCD_Init();
	LCD_ShowString(1,1,"T:");
	LCD_ShowString(2,1,"TH:");
	LCD_ShowString(2,9,"TL:");
	LCD_ShowSignedNum(2,4,THigh,3);
	LCD_ShowSignedNum(2,12,TLow,3);
	Timer0_Init();
	
	while(1)
	{
		KeyNum=Key();
		
		/*温度读取及显示*/
		DS18B20_ConvertT();	//转换温度
		T=DS18B20_ReadT();	//读取温度
		if(T<0)				//如果温度小于0
		{
			LCD_ShowChar(1,3,'-');	//显示负号
			TShow=-T;		//将温度变为正数
		}
		else				//如果温度大于等于0
		{
			LCD_ShowChar(1,3,'+');	//显示正号
			TShow=T;
		}
		LCD_ShowNum(1,4,TShow,3);		//显示温度整数部分
		LCD_ShowChar(1,7,'.');		//显示小数点
		LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);//显示温度小数部分
		
		/*阈值判断及显示*/
		if(KeyNum)
		{
			if(KeyNum==1)	//K1按键,THigh自增
			{
				THigh++;
				if(THigh>125){THigh=125;}
			}
			if(KeyNum==2)	//K2按键,THigh自减
			{
				THigh--;
				if(THigh<=TLow){THigh++;}
			}
			if(KeyNum==3)	//K3按键,TLow自增
			{
				TLow++;
				if(TLow>=THigh){TLow--;}
			}
			if(KeyNum==4)	//K4按键,TLow自减
			{
				TLow--;
				if(TLow<-55){TLow=-55;}
			}
			LCD_ShowSignedNum(2,4,THigh,3);	//显示阈值数据
			LCD_ShowSignedNum(2,12,TLow,3);
			AT24C02_WriteByte(0,THigh);		//写入到At24C02中保存
			Delay(5);
			AT24C02_WriteByte(1,TLow);
			Delay(5);
		}
		if(T>THigh)			//越界判断
		{
			LCD_ShowString(1,13,"OV:H");
			Buzzer_Time(100); //报警器函数
		}
		else if(T<TLow)
		{
			LCD_ShowString(1,13,"OV:L");
			Buzzer_Time(100);
		}
		else
		{
			LCD_ShowString(1,13,"    ");
		}
	}
}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=20)
	{
		T0Count=0;
		Key_Loop();	//每20ms调用一次按键驱动函数
	}
}


14. LCD1602

LCD与数码管引脚冲突

原理(图片)

代码

头文件

//LCD_1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_Init(void);
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,unsigned char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_WriteCommand(unsigned char Command);

#endif

函数

//LCD_1602.c
#include <REGX52.H>

//引脚定义
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_E=P2^7;
#define LCD_DataPort P0

/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()		//@12.000MHz 1ms
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_E=1;
	LCD_Delay();
	LCD_E=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_E=1;
	LCD_Delay();
	LCD_E=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init(void)
{
	LCD_WriteCommand(0x38);
	LCD_WriteCommand(0x0C);
	LCD_WriteCommand(0x06);
	LCD_WriteCommand(0x01);
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else
	{
		LCD_WriteCommand(0x80|(Column-1)+0x40);
	}
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,unsigned char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,unsigned char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData('0'+Number/LCD_Pow(10,i-1)%10);
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData('0'+Number1/LCD_Pow(10,i-1)%10);
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	unsigned char SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData('0'+SingleNumber);
		}
		else
		{
			LCD_WriteData('A'+SingleNumber-10);
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData('0'+Number/LCD_Pow(2,i-1)%2);
	}
}
//main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"

void main()
{
	LCD_Init();						//LCD初始化
	LCD_ShowChar(1,1,'A');			//在1行1列显示字符A
	LCD_ShowString(1,3,"Hello");	//在1行3列显示字符串Hello
	LCD_ShowNum(1,9,66,2);			//在1行9列显示数字66,长度为2
	LCD_ShowSignedNum(1,12,-88,2);	//在1行12列显示有符号数字-88,长度为2
	LCD_ShowHexNum(2,1,0xA5,2);		//在2行1列显示十六进制数字0xA5,长度为2
	LCD_ShowBinNum(2,4,0xA5,8);		//在2行4列显示二进制数字0xA5,长度为8
	LCD_ShowChar(2,13,0xDF);		//在2行13列显示编码为0xDF的字符 
	LCD_ShowChar(2,14,'C');			//在2行14列显示字符C
	LCD_ShowString(1,16,"Welcome to China!"); //字符往左移动
	while(1)
	{
		LCD_WriteCommand(0x18);
		Delay(500);
	}
}


15. 直流电机驱动(PWM)

原理

五线四项步进电机

15.1 LED呼吸灯

#include <REGX52.H>

sbit LED=P2^0;

void Delay(unsigned int t)
{
	while(t--);
}

void main()
{
	unsigned char Time,i;
	
	while(1)
	{
		for(Time=0;Time<100;Time++) //暗->亮
		{
			for(i=0;i<20;i++)
			{
				LED=0;
				Delay(Time);
				LED=1;
				Delay(100-Time);				
			}		
		}
		
		for(Time=100;Time>0;Time--) //亮->暗
		{
			for(i=0;i<20;i++)
			{
				LED=0;
				Delay(Time);
				LED=1;
				Delay(100-Time);				
			}		
		}
	}
}

15.2 直流电机调速

//Timer0.c
#include <REGX52.H>

/**
  * @brief  定时器0初始化,100us@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0_Init(void)
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x9C;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
	PT0=0;
}
//main.c
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Timer0.h"

sbit Motor=P1^0;  //给1电机转

unsigned char Counter,Compare;
unsigned char KeyNum,Speed;

void main()
{
	Timer0_Init();
	Compare=90;
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)
		{
			Speed++;
			Speed%=4;
			if(Speed==0) {Compare=0;} 
			if(Speed==1) {Compare=50;}
			if(Speed==2) {Compare=75;}
			if(Speed==3) {Compare=100;}
		}
		Nixie(1,Speed); //Speed越大,Compare越大,所占比也就越大
	}
}

void Timer0_Routine() interrupt 1
{
	TL0 = 0x9C;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	Counter++;
	Counter%=100;
	if(Counter<Compare)
	{
		Motor=1;
	}
	else
	{
		Motor=0;
	}
}


16. AD/DA

原理

AD:模拟--数字转换,模拟信号变为计算机可操作的数字信号

DA:数字--模拟转换

ADC

DAC

AD模数转换

头文件

//XPT2046.h
#ifndef __XPT2046_H_
#define __XPT2046_H_
//8位
#define XPT2046_XP_8    0x9C
#define XPT2046_YP_8    0xDC
#define XPT2046_VBAT_8  0xAC
#define XPT2046_AUX_8   0xEC
//12位
#define XPT2046_XP_12    0x94
#define XPT2046_YP_12    0xD4
#define XPT2046_VBAT_12  0xA4
#define XPT2046_AUX_12   0xE4

unsigned int XPT2046_ReadAD(unsigned char Command);

#endif

函数

//XPT2046.c
#include <REGX52.H>
//引脚定义
sbit XPT2046_CS=P3^5;
sbit XPT2046_DCLK=P3^6;
sbit XPT2046_DIN=P3^4;
sbit XPT2046_DOUT=P3^7;

/**
  * @brief  ZPT2046读取AD值
  * @param  Command 命令字,范围:头文件内定义的宏,结尾的数字表示转换的位数
  * @retval AD转换后的数字量,范围:8位为0~255,12位为0~4095
  */
unsigned int XPT2046_ReadAD(unsigned char Command)
{
	unsigned  char i;
	unsigned int ADValue=0;
	XPT2046_DCLK=0;
	XPT2046_CS=0;
	
	for(i=0;i<8;i++)
	{
		XPT2046_DIN=Command&(0x80>>i);
		XPT2046_DCLK=1;
		XPT2046_DCLK=0;
	}

	for(i=0;i<16;i++)
	{
		
	  XPT2046_DCLK=1;
		XPT2046_DCLK=0;
		if(XPT2046_DOUT) {ADValue|=(0x8000>>i);}
	}
	XPT2046_CS=1;
	if(Command&0x08)
	{
		return ADValue>>8; //8位显示
	}
	else return ADValue>>4; //12位显示
}
//main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "XPT2046.h"

unsigned int ADValue;

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"ADJ NTC RG");
	while(1)
	{
		ADValue= XPT2046_ReadAD(XPT2046_XP_8); //读取AIN0,可调电阻
		LCD_ShowNum(2,1,ADValue,3);
		ADValue= XPT2046_ReadAD(XPT2046_YP_8); //读取AIN1,热敏电阻
		LCD_ShowNum(2,5,ADValue,3);
		ADValue= XPT2046_ReadAD(XPT2046_VBAT_8);  //读取AIN2,光敏电阻
		LCD_ShowNum(2,9,ADValue,3);
		Delay(10);
		
	}
}

DA数模转换

//main.c 直流电机微改
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"

sbit DA=P2^1;

unsigned char Counter,Compare;	//计数值和比较值,用于输出PWM
unsigned char i;

void main()
{
	Timer0_Init();
	while(1)
	{
		for(i=0;i<100;i++)
		{
			Compare=i;			//设置比较值,改变PWM占空比
			Delay(10);
		}
		for(i=100;i>0;i--)
		{
			Compare=i;			//设置比较值,改变PWM占空比
			Delay(10);
		}
	}
}

void Timer0_Routine() interrupt 1
{
	TL0 = 0x9C;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	Counter++;
	Counter%=100;	//计数值变化范围限制在0~99
	if(Counter<Compare)	//计数值小于比较值
	{
		DA=1;		//输出1
	}
	else				//计数值大于比较值
	{
		DA=0;		//输出0
	}
}


17. 红外遥控

原理

红外接收模块

遥控器键码

遥控器

17.1 红外遥控

头文件

//Int0.h
#ifndef __INT0_H__
#define __INT0_H__

void Int0_Init(void);

#endif
//Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);
void Timer0_SetCounter(unsigned int Value);
unsigned int Timer0_GetCounter(void);
void Timer0_Run(unsigned char Flag);

#endif
//IR.h
#ifndef __IR_H__
#define __IR_H__

#define IR_POWER		0x45
#define IR_MODE			0x46
#define IR_MUTE			0x47
#define IR_START_STOP	0x44
#define IR_PREVIOUS		0x40
#define IR_NEXT			0x43
#define IR_EQ			0x07
#define IR_VOL_MINUS	0x15
#define IR_VOL_ADD		0x09
#define IR_0			0x16
#define IR_RPT			0x19
#define IR_USD			0x0D
#define IR_1			0x0C
#define IR_2			0x18
#define IR_3			0x5E
#define IR_4			0x08
#define IR_5			0x1C
#define IR_6			0x5A
#define IR_7			0x42
#define IR_8			0x52
#define IR_9			0x4A

void IR_Init(void);
unsigned char IR_GetDataFlag(void);
unsigned char IR_GetRepeatFlag(void);
unsigned char IR_GetAddress(void);
unsigned char IR_GetCommand(void);

#endif

函数

//Int0.c
#include <REGX52.H>

/**
  * @brief  外部中断0初始化
  * @param  无
  * @retval 无
  */
void Int0_Init(void)
{
	IT0=1;
	IE0=0;
	EX0=1;
	EA=1;
	PX0=1;
}

/*外部中断0中断函数模板
void Int0_Routine(void) interrupt 0
{
	
}
*/
//Timer0.c
#include <REGX52.H>

/**
  * @brief  定时器0初始化
  * @param  无
  * @retval 无
  */
void Timer0_Init(void)
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0;		//设置定时初值
	TH0 = 0;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 0;		//定时器0不计时
}

/**
  * @brief  定时器0设置计数器值
  * @param  Value,要设置的计数器值,范围:0~65535
  * @retval 无
  */
void Timer0_SetCounter(unsigned int Value)
{
	TH0=Value/256;
	TL0=Value%256;
}

/**
  * @brief  定时器0获取计数器值
  * @param  无
  * @retval 计数器值,范围:0~65535
  */
unsigned int Timer0_GetCounter(void)
{
	return (TH0<<8)|TL0;
}

/**
  * @brief  定时器0启动停止控制
  * @param  Flag 启动停止标志,1为启动,0为停止
  * @retval 无
  */
void Timer0_Run(unsigned char Flag)
{
	TR0=Flag;
}
//IR.c
#include <REGX52.H>
#include "Timer0.h"
#include "Int0.h"

unsigned int IR_Time;
unsigned char IR_State;

unsigned char IR_Data[4];
unsigned char IR_pData;

unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;

/**
  * @brief  红外遥控初始化
  * @param  无
  * @retval 无
  */
void IR_Init(void)
{
	Timer0_Init();
	Int0_Init();
}

/**
  * @brief  红外遥控获取收到数据帧标志位
  * @param  无
  * @retval 是否收到数据帧,1为收到,0为未收到
  */
unsigned char IR_GetDataFlag(void)
{
	if(IR_DataFlag)
	{
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}

/**
  * @brief  红外遥控获取收到连发帧标志位
  * @param  无
  * @retval 是否收到连发帧,1为收到,0为未收到
  */
unsigned char IR_GetRepeatFlag(void)
{
	if(IR_RepeatFlag)
	{
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}

/**
  * @brief  红外遥控获取收到的地址数据
  * @param  无
  * @retval 收到的地址数据
  */
unsigned char IR_GetAddress(void)
{
	return IR_Address;
}

/**
  * @brief  红外遥控获取收到的命令数据
  * @param  无
  * @retval 收到的命令数据
  */
unsigned char IR_GetCommand(void)
{
	return IR_Command;
}

//外部中断0中断函数,下降沿触发执行
void Int0_Routine(void) interrupt 0
{
	if(IR_State==0)				//状态0,空闲状态
	{
		Timer0_SetCounter(0);	//定时计数器清0
		Timer0_Run(1);			//定时器启动
		IR_State=1;				//置状态为1
	}
	else if(IR_State==1)		//状态1,等待Start信号或Repeat信号
	{
		IR_Time=Timer0_GetCounter();	//获取上一次中断到此次中断的时间
		Timer0_SetCounter(0);	//定时计数器清0
		//如果计时为13.5ms,则接收到了Start信号(判定值在12MHz晶振下为13500,在11.0592MHz晶振下为12442)
		if(IR_Time>12442-500 && IR_Time<12442+500)
		{
			IR_State=2;			//置状态为2
		}
		//如果计时为11.25ms,则接收到了Repeat信号(判定值在12MHz晶振下为11250,在11.0592MHz晶振下为10368)
		else if(IR_Time>10368-500 && IR_Time<10368+500)
		{
			IR_RepeatFlag=1;	//置收到连发帧标志位为1
			Timer0_Run(0);		//定时器停止
			IR_State=0;			//置状态为0
		}
		else					//接收出错
		{
			IR_State=1;			//置状态为1
		}
	}
	else if(IR_State==2)		//状态2,接收数据
	{
		IR_Time=Timer0_GetCounter();	//获取上一次中断到此次中断的时间
		Timer0_SetCounter(0);	//定时计数器清0
		//如果计时为1120us,则接收到了数据0(判定值在12MHz晶振下为1120,在11.0592MHz晶振下为1032)
		if(IR_Time>1032-500 && IR_Time<1032+500)
		{
			IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));	//数据对应位清0
			IR_pData++;			//数据位置指针自增
		}
		//如果计时为2250us,则接收到了数据1(判定值在12MHz晶振下为2250,在11.0592MHz晶振下为2074)
		else if(IR_Time>2074-500 && IR_Time<2074+500)
		{
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));	//数据对应位置1
			IR_pData++;			//数据位置指针自增
		}
		else					//接收出错
		{
			IR_pData=0;			//数据位置指针清0
			IR_State=1;			//置状态为1
		}
		if(IR_pData>=32)		//如果接收到了32位数据
		{
			IR_pData=0;			//数据位置指针清0
			if((IR_Data[0]==~IR_Data[1]) && (IR_Data[2]==~IR_Data[3]))	//数据验证
			{
				IR_Address=IR_Data[0];	//转存数据
				IR_Command=IR_Data[2];
				IR_DataFlag=1;	//置收到连发帧标志位为1
			}
			Timer0_Run(0);		//定时器停止
			IR_State=0;			//置状态为0
		}
	}
}
//main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "IR.h"

unsigned char Num;
unsigned char Address;
unsigned char Command;

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"ADDR  CMD  NUM");
	LCD_ShowString(2,1,"00    00   000");
	
	IR_Init();
	
	while(1)
	{
		if(IR_GetDataFlag() || IR_GetRepeatFlag())	//如果收到数据帧或者收到连发帧
		{
			Address=IR_GetAddress();		//获取遥控器地址码
			Command=IR_GetCommand();		//获取遥控器命令码
			
			LCD_ShowHexNum(2,1,Address,2);	//显示遥控器地址码
			LCD_ShowHexNum(2,7,Command,2);	//显示遥控器命令码
			
			if(Command==IR_VOL_MINUS)		//如果遥控器VOL-按键按下
			{
				Num--;						//Num自减
			}
			if(Command==IR_VOL_ADD)			//如果遥控器VOL+按键按下
			{
				Num++;						//Num自增
			}
			
			LCD_ShowNum(2,12,Num,3);		//显示Num
		}
	}
}

17.2 红外遥控电机调速

头文件

//Timer1.h
#ifndef __TIMER1_H__
#define __TIMER1___

void Timer1_Init(void);

#endif
//Motor.h
#ifndef __MOTOR_H__
#define __MOTOR_H__

void Motor_Init(void);
void Motor_SetSpeed(unsigned char Speed);

#endif
//Int0.h
#ifndef __INT0_H__
#define __INT0_H__

void Int0_Init(void);

#endif
//Timer0.h
#ifndef __TIMER0_H__
#define __TIMER0_H__

void Timer0_Init(void);
void Timer0_SetCounter(unsigned int Value);
unsigned int Timer0_GetCounter(void);
void Timer0_Run(unsigned char Flag);

#endif

函数

//Timer1.c
#include <REGX52.H>

/**
  * @brief  定时器1初始化,100us@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer1_Init(void)
{
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;		//设置定时器模式
	TL1 = 0x9C;		//设置定时初值
	TH1 = 0xFF;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
	ET1=1;
	EA=1;
	PT1=0;
}

/*定时器中断函数模板
void Timer1_Routine() interrupt 3
{
	static unsigned int T1Count;
	TL1 = 0x9C;		//设置定时初值
	TH1 = 0xFF;		//设置定时初值
	T1Count++;
	if(T1Count>=1000)
	{
		T1Count=0;
		
	}
}
*/
//Motor.c
#include <REGX52.H>
#include "Timer1.h"

//引脚定义
sbit Motor=P1^0;

unsigned char Counter,Compare;

/**
  * @brief  电机初始化
  * @param  无
  * @retval 无
  */
void Motor_Init(void)
{
	Timer1_Init();
}

/**
  * @brief  电机设置速度
  * @param  Speed 要设置的速度,范围0~100
  * @retval 无
  */
void Motor_SetSpeed(unsigned char Speed)
{
	Compare=Speed;
}

//定时器1中断函数
void Timer1_Routine() interrupt 3
{
	TL1 = 0x9C;		//设置定时初值
	TH1 = 0xFF;		//设置定时初值
	Counter++;
	Counter%=100;	//计数值变化范围限制在0~99
	if(Counter<Compare)	//计数值小于比较值
	{
		Motor=1;		//输出1
	}
	else				//计数值大于比较值
	{
		Motor=0;		//输出0
	}
}
//Int0.c
#include <REGX52.H>

/**
  * @brief  外部中断0初始化
  * @param  无
  * @retval 无
  */
void Int0_Init(void)
{
	IT0=1;
	IE0=0;
	EX0=1;
	EA=1;
	PX0=1;
}

/*外部中断0中断函数模板
void Int0_Routine(void) interrupt 0
{
	
}
*/
#include <REGX52.H>

/**
  * @brief  定时器0初始化
  * @param  无
  * @retval 无
  */
void Timer0_Init(void)
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0;		//设置定时初值
	TH0 = 0;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 0;		//定时器0不计时
}

/**
  * @brief  定时器0设置计数器值
  * @param  Value,要设置的计数器值,范围:0~65535
  * @retval 无
  */
void Timer0_SetCounter(unsigned int Value)
{
	TH0=Value/256;
	TL0=Value%256;
}

/**
  * @brief  定时器0获取计数器值
  * @param  无
  * @retval 计数器值,范围:0~65535
  */
unsigned int Timer0_GetCounter(void)
{
	return (TH0<<8)|TL0;


/**
  * @brief  定时器0启动停止控制
  * @param  Flag 启动停止标志,1为启动,0为停止
  * @retval 无
  */
void Timer0_Run(unsigned char Flag)
{
	TR0=Flag;
}
//main.c
#include <REGX52.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "Motor.h"
#include "IR.h"

unsigned char Command,Speed;

void main()
{
	Motor_Init();
	IR_Init();
	while(1)
	{
		if(IR_GetDataFlag())	//如果收到数据帧
		{
			Command=IR_GetCommand();		//获取遥控器命令码
			
			if(Command==IR_0){Speed=0;}		//根据遥控器命令码设置速度
			if(Command==IR_1){Speed=1;}
			if(Command==IR_2){Speed=2;}
			if(Command==IR_3){Speed=3;}
			
			if(Speed==0){Motor_SetSpeed(0);}	//速度输出
			if(Speed==1){Motor_SetSpeed(50);}
			if(Speed==2){Motor_SetSpeed(75);}
			if(Speed==3){Motor_SetSpeed(100);}
		}
		Nixie(1,Speed);						//数码管显示速度
	}
}

芜湖,本文章于 2022-12-14 完结散花~~~ 后续会持续优化本文

下面开启STM32的旅途?

--> [alert]本文章已完结,持续优化中[/alert]前言必读51单片机入门教程(上篇)(代码+个人理解) – Echo (liveout.cn)GitHub仓库链接:https://github.com/PGwind/51code这篇文章是记录我粗略学习51单片机的一些代码,我会加些个人理解以及注释在里面。因为是囫囵吞枣式学习,所以质量不是很好,后期我会慢慢优化 ?如果你想要学习单片机,可...

前言

必读

这篇文章是记录我粗略学习51单片机的一些代码,我会加些个人理解以及注释在里面。

因为是囫囵吞枣式学习,所以质量不是很好,后期我会慢慢优化 ?

如果你想要学习单片机,可以观看下面的B站教程并配合本文档学习

本文章使用的51单片机是 普中STC89C52RC

51单片机入门教程(下篇)(代码+个人理解) – Echo (liveout.cn)

GitHub仓库链接:https://github.com/PGwind/51code

教程

推荐B站视频: 【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】 https://www.bilibili.com/video/BV1Mb411e7re/?share_source=copy_web&vd_source=55024add0415795a359bd7b29ca21142(应该都知道吧)。

资源

B站江科大资源 链接:https://pan.baidu.com/s/1dLED_1VqL66qYItLl5ic4A?pwd=1111 提取码:1111

普中 链接:https://pan.baidu.com/s/1dNCHm9lLMP8pe3rZu3ktZQ?pwd=1111 提取码:1111


1. 入门

原理: 单片机核心

cpu通过配置寄存器来控制驱动器,来控制硬件电路

寄存器:连接软硬件的媒介

单片机核心

数据类型

C51数据类型

2进制----16进制转换

16base


2. 点灯

原理

LED模块

2.1 点亮第一个LED

#include <REGX52.H> //右键添加头文件
void main()
{   //2进制转16进制,前缀为 0x 87654321 8个灯序号对应进制
    // 8个1分别为8个led,0则是负极点亮。最后一个0是D1,第一个是D8
    P2 = 0xFE; //1111 1110 点亮第1个led
    P2 = 0x7F; //0111 1111 点亮第8个led
    P2 = 0x5F; //0101 1111 led 6,8亮
    P2 = 0xAA; //1010 1010 led 1,3,5,7亮
    P2 = 0x55; //0101 0101 led 2,4,6,8亮
	while (1) //程序一直运行
	{
		
	}
}

2.2 LED闪烁

#include <REGX52.H>
#include <INTRINS.H>
void Delay500ms()		//@11.0592MHz 500ms延迟函数
{
	unsigned char i, j, k;
    
	_nop_(); //去掉nop不用加头文件#include <INTRINS.H>
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void main()
{
	
	while(1)
	{
		P2 = 0xFE; 
		Delay500ms(); //延迟500ms
	    P2 = 0xFF;  
		Delay500ms();
	} 
}

2.3 LED流水灯

#include <REGX52.H>

void Delay500ms()		//@11.0592MHz 延迟500ms
{
	unsigned char i, j, k;
    
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void main()
{
		while(1)
		{
			P2 = 0xFE; //1111 1110
			Delay500ms();
			P2 = 0xFD; //1111 1101
			Delay500ms();
			P2 = 0xFB; //1111 1011
			Delay500ms();
			P2 = 0xF7; //1111 0111
			Delay500ms();
			P2 = 0xEF; //1110 1111
			Delay500ms();
			P2 = 0xDF; //1101 1111
			Delay500ms();
			P2 = 0xBF; //1011 1111
			Delay500ms();
			P2 = 0x7F; //0111 1111
			Delay500ms();
		}
}

2.4 LED流水灯Plus

#include <REGX52.H>
void Delay1ms(unsigned int xms)		//@11.0592MHz
{ //xms 为延迟秒数
	unsigned char i, j;
  while(xms)
	{
		
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
		xms--;
	}
}

void main()
{
	while(1)
		{
			P2 = 0xFE; //1111 1110
			Delay1ms(500); //延迟500ms
			P2 = 0xFD; //1111 1101
			Delay1ms(100); //100ms
			P2 = 0xFB; //1111 1011
			Delay1ms(1000); //1000ms
			P2 = 0xF7; //1111 0111
			Delay1ms(500);
			P2 = 0xEF; //1110 1111
			Delay1ms(200);
			P2 = 0xDF; //1101 1111
			Delay1ms(500);
			P2 = 0xBF; //1011 1111
			Delay1ms(900);
			P2 = 0x7F; //0111 1111
			Delay1ms(500);
		}
	Delay1ms(500);
}


3.独立按键控制LED

原理(高电平、低电平)

电平和电压是有差别的,高电平指的是与低电平相对的高电压,是电工程上的一种说法。

在逻辑电平中,保证逻辑门的输入为高电平时所允许的最小输入高电平,当输入电平高于输入高电压(Vih)时,则认为输入电平为高电平。

在电子和自动化控制中,分为模拟信号和数字信号,在数字逻辑电子电路中,数字信号是二进制的,即只有0信号和1信号。

高电平表示电压高的状态,记为1,一般规定高电平为3.5~5V
低电平表示电压低的状态, 记为0,一般规定低电平为0~0.25V

按键松开 高电平 1

按键按下 低电平 0

3.1 独立按键控制LED灯灭

#include <REGX52.H>
//P2_0就是单片机上面的一个端口,这个端口就是链接右边第一个led灯的
//等于左边七个LED直接不给信号了,只给右边第一个一个亮的信号
void main()
{
	//P2 = 0xFE;
	P2_0 = 1;
	while(1)
	{ // 按下亮,松开灭
		if(P3_1==0) //P3_1控制第一个按键,即P31
		{
			P2_0 = 0; //P2_0是第一个LED,0则亮
		}
		else
		{
			P2_0=1;
		}
        
        if (P3_0==0)
		{
			P2_1 = 0;
		}
        else
		{
			P2_1 = 1;
		}	
	}
}

3.2 独立按键控制LED状态

#include <REGX52.H>
void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms)
	{
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);	
			xms--;
	}
}

void main()
{  //按一次亮,再按一次灭
		while(1)
		{
				if(P3_1==0)
				{
					Delay1ms(20); //按下时消抖
					while(P3_1==0); //松开跳出while循环,不松开一直循环,所以灯不会有反应
					Delay1ms(20); //松开时消抖
					
					P2_0=~P2_0; //取反,灭变亮,亮变灭
				}
		}
}

3.3 独立按键控制LED显示二进制

#include <REGX52.H>
void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms)
	{
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);	
			xms--;
	}
}
void main()
{
	  unsigned char LEDNum = 0; 
		while(1)
		{
			if(P3_1==0)
			{
				Delay1ms(20);
				while(P3_1==0)
				Delay1ms(20);
				
				LEDNum++; //0000 0001
				P2 = ~LEDNum; //取反, 1111 1110,第一个LED灯亮
			}
		}
}

3.4 独立按键控制LED移位

0000 0001 -> 0000 0010 -> 0000 0100 ....

0x01<<0 0x01<<1 0x01<<2 ....

#include <REGX52.H>
void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms)
	{
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);	
			xms--;
	}
} 
void main()
{
	unsigned char LEDNum = 0;
	P2 = ~0x01; //LED初始化,不然第一次led1不亮
	while(1)
	{
        //按键1右移
		if(P3_1==0) //按键1按下
		{
			Delay1ms(20);
			while(P3_1==0);
			Delay1ms(20);
			
			LEDNum++; //右移
			if (LEDNum>=8)
				LEDNum=0; //重置
			
			P2=~(0x01<<LEDNum);
		}
		
        //按键2左移
		if(P3_0==0) //按键1按下
		{
			Delay1ms(20);
			while(P3_0==0);
			Delay1ms(20);
			
			if(LEDNum==0)
				LEDNum=7; //重置
			else
				LEDNum--; //左移
			
			P2=~(0x01<<LEDNum);
		}
	}
}



4.数码管

LED数码管:由多个发光二极管封装在一起组成“8”字型

输出扫描:显示第一位->显示第二位->显示第三位......,然后快速循环这个过程,最终实现所有数码管同时显示的效果

原理

138验码器

//这里灯的顺序排列我的理解有错误,还没仔细研究。我的单片机111时是Y0,即LED1亮
C(P24)  B(P23)    A(P22)  Y		 LED 8 -> 1
0(4)	0(2)	0(1)	Y7		0 1 1 1 1 1 1 1  LED8
0		0		 1   	 Y6		 1 0 1 1 1 1 1 1  LED7
1		0		 1		 Y2		 1 1 1 1 1 0 1 1  LED3
1		1		 1		 Y0		 1 1 1 1 1 1 1 0  LED1    

 

动态数码管

上面是共阴极,公共端为0则亮。如LED7(引脚9)为0,其他为1。即 1011 1111,则LED7亮,其他不亮。

下面是阴码。LED显示7,则b、c为1,其他0。即 0110 0000

4.1 静态数码管显示

点亮一个

#include <REGX52.H>

void main()
{
    //100,即4,LED4亮
	P2_4 = 1;
	P2_3 = 0;
	P2_2 = 0;
    //P07->P00  0000 0111 ,即0x07,
    //a、b、c为1,即a、b、c亮,显示为7
	P0 = 0x07;
	while(1)
	{
		
	}
}

模块化

#include <REGX52.H>
//显示数字的段码存储在数组中
//0~9 -> 0~9
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};

void Nixie(unsigned char Location,Number)
{   //Location为LED灯序号,Number为显示数字
	switch(Location)
	{
		case 1:P2_4=1;P2_3=1;P2_2=1;break;
		case 2:P2_4=1;P2_3=1;P2_2=0;break;
		case 3:P2_4=1;P2_3=0;P2_2=1;break;
		case 4:P2_4=1;P2_3=0;P2_2=0;break;
		case 5:P2_4=0;P2_3=1;P2_2=1;break;
		case 6:P2_4=0;P2_3=1;P2_2=0;break;
		case 7:P2_4=0;P2_3=0;P2_2=1;break;
		case 8:P2_4=0;P2_3=0;P2_2=0;break;
	}
	P0=NixieTable[Number];
}

void main()
{
	Nixie(6,2); //第6个LED显示2
	while(1)
	{
		
	}
}

4.2 动态数码管显示

个人理解:就像早期电影一样,通过视觉残留动态显示。实际上只能有一个LED灯亮

消影:位选 段选 清零 位选 段选 清零

利用延时来抵消人的视觉暂留现象达到消影

#include <REGX52.H>

void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms)
	{
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);	
			xms--;
	}
} 

unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};

void Nixie(unsigned char Location,Number)
{   
	switch(Location)
	{
		case 1:P2_4=1;P2_3=1;P2_2=1;break;
		case 2:P2_4=1;P2_3=1;P2_2=0;break;
		case 3:P2_4=1;P2_3=0;P2_2=1;break;
		case 4:P2_4=1;P2_3=0;P2_2=0;break;
		case 5:P2_4=0;P2_3=1;P2_2=1;break;
		case 6:P2_4=0;P2_3=1;P2_2=0;break;
		case 7:P2_4=0;P2_3=0;P2_2=1;break;
		case 8:P2_4=0;P2_3=0;P2_2=0;break;
	}
	P0=NixieTable[Number]; //段选
	Delay1ms(1); 
	P0=0x00; //清零
}

void main()
{

	while(1)
	{
			Nixie(1,1); 
			//Delay1ms(500);  //此延迟用于从第一个led1依次显示数字
			Nixie(2,2);
			//Delay1ms(500); 
			Nixie(3,3);
			//Delay1ms(500);
 			Nixie(4,4);
//			Delay1ms(500);
   		Nixie(5,5);
//   		Delay1ms(500);
			Nixie(6,6);
//			Delay1ms(500);
			Nixie(7,7);
//			Delay1ms(500);
			Nixie(8,8);
//			Delay1ms(500);
	}
}


5 .1模块化编程

视频

https://www.bilibili.com/video/BV1Mb411e7re?p=13&vd_source=a1234589a3616351986bc6d13bcbd8f8

代码

头文件

//Delay.h
#ifndef __DELAY_H_
#define __DELAY_H_
void Delay(unsigned int xms)		;
#endif
//Nixie.h
#ifndef __NIXIE_H___
#define __NIXIE_H__
void Nixie(unsigned char Location,Number);
#endif

函数

//main.c
#include <REGX52.H>
#include "Delay.h"
#include "Nixie.h"
void main()
{
	while(1)
	{
		Nixie(1,1);
		Nixie(2,2);
		Nixie(3,3);
	}
}

其他函数

//Delay.c
void Delay(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms)
	{
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);	
			xms--;
	}
} 

//Nixe.c
#include <REGX52.H>
#include "Delay.h"

unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};

void Nixie(unsigned char Location,Number)
{   
	switch(Location)
	{
		case 1:P2_4=1;P2_3=1;P2_2=1;break;
		case 2:P2_4=1;P2_3=1;P2_2=0;break;
		case 3:P2_4=1;P2_3=0;P2_2=1;break;
		case 4:P2_4=1;P2_3=0;P2_2=0;break;
		case 5:P2_4=0;P2_3=1;P2_2=1;break;
		case 6:P2_4=0;P2_3=1;P2_2=0;break;
		case 7:P2_4=0;P2_3=0;P2_2=1;break;
		case 8:P2_4=0;P2_3=0;P2_2=0;break;
	}
	P0=NixieTable[Number]; 
	Delay(1); 
	P0=0x00; 
}

 


5.2 LCD1602调试工具

LCD与数码管引脚冲突

原理

LCD1602接口

LCD调试

代码

记得添加之前定义过的函数和头文件

//LCD1602.h  分别为显示各种进制数组以及字符
#ifndef __LCD1602_H__
#define __LCD1602_H__
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

 

//输出固定值
#include <REGX52.H>
#include "LCD1602.h"
void main()
{
	LCD_Init();
	LCD_ShowChar(1,1,'A');
	LCD_ShowString(1,3,"Hello");
	LCD_ShowNum(1,9,123,3);
	LCD_ShowSignedNum(1,13,-66,2);
	LCD_ShowHexNum(2,1,0xA8,2);
	LCD_ShowBinNum(2,4,0xAA,8);
	while(1)
	{
	}
}
//输出变量值
#include <REGX52.H>
#include "LCD1602.h"
int Result;
void main()
{
	LCD_Init();
	Result = 1+1;
	LCD_ShowNum(1,1,Result,3);
	while(1)
	{
	}
}
//输出变量值Plus 从0开始显示秒数
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
int Result=0;
void main()
{
	LCD_Init();
	
	while(1)
	{
		Result++;
		Delay(1000); //1000ms == 1s
		LCD_ShowNum(1,1,Result,3);
	}
}

6. 矩阵键盘

输入扫描:读取第一行(列)->读取第二(列)->读取第三行(列).....,然后快速循环这个过程,最终实现所有按键同时检测的效果

原理

矩阵按键

6.1矩阵键盘

记得添加之前定义过的函数和头文件

头文件

//MatrixKey.h
#ifndef __MATRIXKEY_H_
#define __MATRIXKEY_H_
unsigned char MatrixKey();
#endif

函数

//main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"Hello Echo"); //第一行显示字符串
	while(1)
	{
		KeyNum=MatrixKey();
		if (KeyNum) //如何没有if,按下一瞬间显示数字,但是松手会重置为0
		{
			LCD_ShowNum(2,1,KeyNum,2);//第2行显示KeyNum,即按下按键键码值
		}
	}
}                      
//MatrixKey.c
#include <REGX52.H>
#include "Delay.h"
//Function Definition:
/**
  * @brief 矩阵键盘读取按键码
  * @param  无
  * @retval KeyNumber 按下按键的键码值
  如果按键按下不放,程序会停留在此函数,松手一瞬间,返回按键键码。没有按键按下时返回0
  */

unsigned char MatrixKey()
{
	unsigned char KeyNumber=0;
	P1 = 0xFF; //初始化
	P1_3=0;  //第1列
	if(P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}//第1行
	if(P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}//第2行
	if(P1_5==0) {Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}//第3行
	if(P1_4==0) {Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}//第4行
	
	P1 = 0xFF;
	P1_2=0;  //第2列
	if(P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
	if(P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
	if(P1_5==0) {Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
	if(P1_4==0) {Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
	
	P1 = 0xFF;
	P1_1=0;  //第3列
	if(P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
	if(P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
	if(P1_5==0) {Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
	if(P1_4==0) {Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
	
	P1 = 0xFF;
	P1_0=0;  //第4列
	if(P1_7==0) {Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
	if(P1_6==0) {Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
	if(P1_5==0) {Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
	if(P1_4==0) {Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
	
	return KeyNumber; //返回按下按键键码值
}                    

6.2 密码锁

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

//unsigned char没有符号位,因此能表示0~255,但是密码肯定不能这么短。
//B站视频教程是unsigned char类型,但是我输入时只能到256,改为int类型就行了
int KeyNum;
int Password,Count;

void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"PassWord");
	while(1)
	{
		KeyNum=MatrixKey();
		if (KeyNum)
		{
			if(KeyNum<=10) //如果S1~S2按键按下,输入密码
            {
                 if(Count<4)
				Password*=10; //密码左移一位
				Password+=KeyNum%10; //获取一位密码
            }
             Count++; 。//计次加一
			LCD_ShowNum(2,1,Password,4); //更新显示
		}
        if(KeyNum==11)   //按下S11按键确认
		{
			if (Password==2345) //密码正确
			{
				LCD_ShowString(1,12,"OK"); //显示OK
                  Password=0; //密码清理
				Count=0;  //计数清理
                  LCD_ShowNum(2,1,Password,4); //更新显示
			}
            else
            {
                 LCD_ShowString(1,12,"ERROR");
				//Delay(2000); //延时2s,然后空格替代,不然OK后面会显示ROR
				Password=0;
				Count=0;
				LCD_ShowNum(2,1,Password,4);
				//LCD_ShowString(1,12,"     "); //空格替代
            }
		}
        if (KeyNum==12) //按键S12手动清零
		{
		   		Password=0; //密码清零
				Count=0;   //计次清零
            	 LCD_ShowString(1,12,"     "); //空格替代
				LCD_ShowNum(2,1,Password,4); //更新显示
                  
		}
	}
}                      


7. 定时器

原理

这章比较难,得多看几遍视频

视频教程:https://www.bilibili.com/video/BV1Mb411e7re/?p=17&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=a1234589a3616351986bc6d13bcbd8f8


定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源

SySclk:系统时钟,即晶振周期,本开发板上的晶振为11.0592MHz

时钟:给0定时器,给1计数器


基础理解代码:实现led1灯每1s闪烁一次

0~65535 每隔1us计数加一 总共定时时间65535us

给初值6435 离计数器65535 差1000us 所以计数时间为1ms

#include <REGX52.H>

//定时器0初始化
void Timer0_Init() //这个函数可以通过软件直接生成的!!!,但是没有中断,即最后三个
{
    //定时器
	//TMOD=0x01; //0000 0001  高四位  低四位
    TMOD=TMOD&0xF0; //把TMOD的低四位清0,高四位保持不变
    TMOD=TMOD|0x01; //把TMOD的最低位 置为1,高四位保持不变
	TF0=0; //TF0是进入中断程序后,硬件自动清零
	TR0=1; //定时器从0开始计时
    
    //TH0,TL0为二进制8位,单独可计256次   低8位计满256后高8位进1
    //设置定时初值
	TH0=64535/256; //高8位次数 
	TL0=64535%256; //低8位次数
    
    //中断
    ET0=1;
    EA=1;
    PT0=0; //设置优先级
}

//主程序
void main()
{
	Timer0_Init();
	while(1)
	{
		
	}
}

//中断服务程序B
unsigned int T0Count;
void Timer0_Routine() interrupt 1
{
	TH0=64535/256;
	TL0=64535%256; //重新赋值
	T0Count++; //每次加1us,1000次就是1s
	if (T0Count>=1000)
	{
		  T0Count=0; //重新赋值
		  P2_0=~P2_0; //每1s闪烁一次
	}

}

7.1 按键控制LED流水灯模式

头文件

//Delay.h
#ifndef __DELAY_H_
#define __DELAY_H_
void Delay(unsigned int xms);
#endif
//Key.h
#ifndef __KEY_H_
#define __KEY_H_
usigned char Key();
#endif

//Timer0.h
#ifndef __TIMER0_H_
#define __TIMER0_H_

void Timer0_Init();

#endif

函数

//Delay.c
//Function Definition
/**
  * @brief 延迟函数
  * @param  无
  * @retval 1ms
  */
void Delay(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms)
	{
			i = 2;
			j = 199;
			do
			{
				while (--j);
			} while (--i);	
			xms--;
	}
} 
	
//Key.c
#include <REGX52.H>
#include "Delay.h"

//Function Definition
/**
  * @brief 获取独立按键键码
  * @param  无
  * @retval 按下按键的键码,范围0~4,无按键按下的返回值为0
  */

unsigned char Key()
{
	unsigned char KeyNum=0;
	
	if(P2_1==0) {Delay(20);while(P2_1==0);Delay(20);KeyNum=1;}
	if(P2_0==0) {Delay(20);while(P2_0==0);Delay(20);KeyNum=2;}
	if(P2_3==0) {Delay(20);while(P2_3==0);Delay(20);KeyNum=3;}
	if(P2_4==0) {Delay(20);while(P2_4==0);Delay(20);KeyNum=4;}
	
	return KeyNum;
}
//Timer0.c
#include <REGX52.H>

//Function Definition
/**
  * @brief 定时器0初始化
  * @param  无
  * @retval 1ms
  */
void Timer0_Init()	//1毫秒@12.000MHz	
{
	TMOD &= 0xF0;	//设置定时器模式	
	TMOD |= 0x01;	//设置定时器模式	
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;        //清除TF0标志 
	TR0 = 1;		//定时器0开始计时
	
	ET0=1;
	EA=1;
	PT0=0;
}

//mian.c (测试) 按键控制灯亮
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"

unsigned char KeyNum;

void main()
{
    Timer0_Init(); //定时器0初始化
	while(1)
	{
		KeyNum=Key();
		if (KeyNum)
		{
			if(KeyNum==1) P2_0=~P2_0; //按K1,led1亮,再按一次则熄灭
			if(KeyNum==2) P2_1=~P2_1;
			if(KeyNum==3) P2_2=~P2_2;
			if(KeyNum==4) P2_3=~P2_3;
		}
	}
}
//main.c  流水灯,按下K1改变方向
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include "INTRINS.H"

unsigned char KeyNum,LEDMode;

void main()
{
	P2=0xFE;
    Timer0_Init(); //定时器0初始化
	while(1)
	{
		KeyNum=Key();
		if (KeyNum)
		{
			if(KeyNum==1) //按下K1,改变方向
			{
				LEDMode++;
				if(LEDMode>=2) LEDMode=0;
			}
			
		}
	}
}

void Timer0_Routine() interrupt 1
{
    static unsigned int T0Count;
	TL0 = 0x18; //重新赋值1us
	TH0 = 0xFC;
	T0Count++;
	if (T0Count>=500) //0.5s
	{
		  T0Count=0;
		  if(LEDMode==0)
			  P2=_crol_(P2,1);  //向左移位,并且循环
		  if(LEDMode==1)
			  P2=_cror_(P2,1);  //向右移位,并且循环
	}
}

7.2 定时器时钟

头文件之前都定义过了

#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec,Min,Hou;

void main()
{
	LCD_Init();
	Timer0_Init();
	
	LCD_ShowString(1,1,"Clock:");
	LCD_ShowString(2,1,"  :  :");
	
	while(1)
	{
        //LCD显示
		LCD_ShowNum(2,1,Hour,2);
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowNum(2,7,Sec,2);
	}
}

void Timer0_Routine() interrupt 1
{
    static unsigned int T0Count;
	TL0 = 0x18; //重新赋值1us
	TH0 = 0xFC;
	T0Count++;
	if (T0Count>=1000) //1s
	{
	    T0Count=0;
		Sec++;  //每秒递增
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
				Min=0;
				Hour++;
				if(Hour>=24)
				{
					Hour=0;
				}
			}
		}
	}
}


8. 串口

串口是一种应用十分广泛的通讯接口,成本低,容易使用,通讯线路简单,可以实现两个设备的互通性。

51单片机内部自带UART(通用异步收发器),可实现单片机的串口通信。串口接口是DB9接口

STC89C52有一个UART,STC89C52URAT有四种工作模式

原理

USBTTl

串口寄存器

数据显示模式

HEX模式/十六进制模式/二进制模式:以原始数据的形式显示

文本模式/字符模式:以原始数据编码后的形式显示

8.1 串口向电脑发送数据

头文件

//UART.h
#ifndef __UART_H_
#define __UART_H_
void UART_Init(); //初始化
void UART_SendByte(unsigned char Byte);//只发送,不接收	
#endif

函数

//UART.c
#include <REGX52.H>
//Function Definition
/**
  * @brief 串口初始化 4800bps@11.0592MHz
  * @param  无
  * @retval 无
  */

void UART_Init()  //初始化 4800bps@11.0592MHz
{
	SCON = 0x40; //0100 0000
	PCON |= 0x80; 
	
	TMOD &= 0x0F;	//设置定时器1模式  0000 1111
	TMOD |= 0x20;	//设置定时器1模式  0010 0000
	TL1 = 0xF4;		//设定定时初值
	TH1 = 0xF4;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1

}

//Function Definition
/**
  * @brief 串口发送一个字节数据
  * @param  Byte 要发送一个字节数字
  * @retval 无
  */

void UART_SendByte(unsigned char Byte) //只发送,不接收 
{
	SBUF = Byte;
  while(TI==0); //发送成功跳出循环,TI变为1
	TI = 0;    //重置为0
}

//串口中断函数模板(用在下一个例子中)
//void UART_Routine() interrupt 4 //串口中断号为4
//{ //RI:接收中断请求标志位 
//	if(RI==1) //RI=1向主机请求中断响
//	{
//		RI=0;//应中断后必须用软件复位,即RI=0  
//	}
//}
//main.c
//每一秒发送一个字节
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"

unsigned char Sec;

void main()
{
	UART_Init(); //初始化
	
	while(1)
	{
		UART_SendByte(Sec); //发送 16进制格式
		Sec++;
		Delay(1000);
	}
}

8.2 电脑通过串口控制LED

#include <REGX52.H>
#include "Delay.h"
#include "UART.h"

void main()
{
	UART_Init();
	
	while(1)
	{
		
	}
}

void UART_Routine() interrupt 4 //串口中断号为4
{ //RI:接收中断请求标志位 
	if(RI==1) //RI=1向主机请求中断响
	{
		P2=~SBUF; 
		UART_SendByte(SBUF);
		RI=0;//应中断后必须用软件复位,即RI=0  
	}
}


上篇已完结,将会持续优化

下篇链接:51单片机入门教程(下篇)(代码+个人理解) – Echo (liveout.cn)

--> [alert]本文章已完结,持续优化中[/alert]前言必读这篇文章是记录我粗略学习51单片机的一些代码,我会加些个人理解以及注释在里面。因为是囫囵吞枣式学习,所以质量不是很好,后期我会慢慢优化 ?如果你想要学习单片机,可以观看下面的B站教程并配合本文档学习本文章使用的51单片机是 普中STC89C52RC51单片机入门教程(下篇)(代码+个人理解) – Echo (liveout.cn)...

Java期末知识点 (上)

http://space.eyescode.top/blog/details/85

Java期末知识点 (下)

http://space.eyescode.top/blog/details/88

Java期末题型复习

http://space.eyescode.top/blog/details/89

转载标注

以上文章均为 瞳孔大佬所写,其博客地址为:

瞳孔空间 | 首页 (eyescode.top)

--> Java期末知识点 (上)http://space.eyescode.top/blog/details/85Java期末知识点 (下)http://space.eyescode.top/blog/details/88Java期末题型复习http://space.eyescode.top/blog/details/89转载标注以上文章均为 瞳孔大佬所写,其博客地址为:瞳孔空间 | 首页 (ey...

输入

gets()

不保留换行符

读取整行输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并且在末尾添加空字符 \0


fgets()

保留换行符

从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。

char *fgets(char *str, int n, FILE *stream)
  • str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
  • n -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
  • stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流
#include <stdio.h>

int main()
{
   FILE *fp;
   char str[60];

   /* 打开用于读取的文件 */
   fp = fopen("file.txt" , "r");
   if(fp == NULL) {
      perror("打开文件时发生错误");
      return(-1);
   }
   if( fgets (str, 60, fp)!=NULL ) {
      /* 向标准输出 stdout 写入内容 */
      puts(str);
   }
   fclose(fp);
   
   return(0);
}


输出

put

显示字符串时会自动在末尾添加换行符

fputs

末尾不会添加换行符


1. 统计长度

strlen():

int a;
char str[n];
a = strlen(str);


2. 拼接字符串

strcat()

src 所指向的字符串追加到 dest 所指向的字符串的结尾

char *strcat(char *dest, const char *src)
  • dest -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。
  • src -- 指向要追加的字符串,该字符串不会覆盖目标字符串。

该函数返回一个指向最终的目标字符串 dest 的指针

char src[50], dest[50];
strcpy(src,  "This is source");
strcpy(dest, "This is destination");
strcat(dest, src);

最终的目标字符串: This is destinationThis is source|


strncat()

src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。

char *strncat(char *dest, const char *src, size_t n)
  • dest -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。
  • src -- 要追加的字符串。
  • n -- 要追加的最大字符数。

该函数返回一个指向最终的目标字符串 dest 的指针。

 char src[50], dest[50];
strcpy(src,  "This is source");
strcpy(dest, "This is destination");
strncat(dest, src, 15);

最终的目标字符串: This is destinationThis is source|


3. 比较字符串

strcmp()

int strcmp(const char *str1, const char *str2)
  • str1 -- 要进行比较的第一个字符串。
  • str2 -- 要进行比较的第二个字符串。

该函数返回值如下:

  • 如果返回值< 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2。


strncmp()

int strncmp(const char *str1, const char *str2, size_t n)
  • str1 -- 要进行比较的第一个字符串。
  • str2 -- 要进行比较的第二个字符串。
  • n -- 要比较的最大字符数。

该函数返回值如下:

  • 如果返回值 < 0,则表示 str1 小于 str2。
  • 如果返回值 > 0,则表示 str1 大于 str2。
  • 如果返回值 = 0,则表示 str1 等于 str2。


4. 复制

strcpy

src 所指向的字符串复制到 dest

char *strcpy(char *dest, const char *src)
  • dest -- 指向用于存储复制内容的目标数组。
  • src -- 要复制的字符串。

该函数返回一个指向最终的目标字符串 dest 的指针。

   char src[40];
   char dest[100];
   strcpy(src, "This is runoob.com");
   strcpy(dest, src);


strncpy

src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充

char *strncpy(char *dest, const char *src, size_t n)
  • dest -- 指向用于存储复制内容的目标数组。
  • src -- 要复制的字符串。
  • n -- 要从源中复制的字符数。

该函数返回最终复制的字符串。

 

--> 输入gets()不保留换行符读取整行输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并且在末尾添加空字符 \0fgets()保留换行符从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。char *fgets(char *str, int n, FILE *stream...

1. 单字符I/O:getchar() 和 putchar()

getchar() & putchar 每次只处理一个字符

头文件 stdio.h 中包含这两个,他们被定义为供预处理器使用的宏

2. 缓冲区

老式系统,无缓冲区,输入 hello 时,显示为 hheelloo。即正在等待的程序可立即使用输入的字符

 缓冲输入和无缓冲

好坏

缓冲区好处:

  1. 若干字符作为一个块进行传输比逐个发送节约时

    1. 用户打错可直接修改

无缓冲区好处:游戏指令

缓冲分类

缓冲分为:

  1. 完全缓冲I/O:缓冲区填满时刷新缓冲区
  2. 行缓冲I/O:出现换行符时刷新缓冲区

无缓冲

ANSI C 和 后续的 C标准都规定输入是缓冲的:一些计算机不允许无缓冲输入

conino.h:包含无缓冲输入函数

getchae();回显无缓冲输入(回显输入意味着用户输入字符直接显示在屏幕上)

getch():无回显无缓冲输入 (无回显输入意味着击键后对应的字符不显示)

3. 结束键盘输入

3.1 文件、流和键盘输入

文件

文件:存储器中存储信息的区域。通常文件保存在某种永久存储器中

I/O:C存在对文件进行操作的库函数。从较低层面上,C 可以使用主机操作系统基本文件工具直接处理文件,这些直接调用操作系统的函数被称为底层 I/O。

由于计算机系统各不相同(差异),不能为普通的底层I/O函数创建标准库,但从较高层面上,C 还可以通过标准I/O包来处理文件。

这里的差异有多种。例如,不同系统存储文件的方式不同。有些系统把文件内容和文件相关信息分开存储;另一些系统在文件中创建一份文件描述。

在处理文件方面,有些使用单个换行符标记行末尾,其他系统可能使用回车符和换行符组合来表示行末尾。如果使用标准I/O包则不用考虑这些差异。

C程序处理的是流而不是文件。Java也是的(个人觉得)

流 是一个实际输入或输出映射的理想化数据流。这意味着不同属性和不同种类的输入,由属性更统一的流来表示。于是打开文件过程就是把流与文件关联,读写都通过流来完成.

重点:理解 C 把输入和输出设备视为存储设备上的普通文件,尤其是把键盘和显示设备视为每个C程序自动打开的文件。

stdin流表示键盘输入,stdout流表示屏幕输出,getchar()、putchar()、printf()、scanf() 函数都是标准I/O包的成员,处理这两个流。

综上所述:可以用处理文件的方式处理键盘输入。C的输入函数内置了文件结尾检测器。既然可以把键盘输入视为文件,那么也应该能使用文件结尾检测器结束键盘输入。

3.2 文件结尾

计算机操作系统要以某种方式判断文件的开始和结束。检测文件结尾的一种方法是,在文件末尾放一个特殊的字符标记文件结尾。

如今。这些操作系统可以使用内嵌的Ctrl+Z字符来标记结尾。曾经是操作系统使用的唯一标记,不过现在有其他选择,例如记录文件的大小。

带文件结尾标记的文件

操作系统使用的另一种方法是存储文件大小的信息。如果文件有三千字节,则读到三千字节时到达末尾。MS-DOS及其相关系统使用此方法处理二进制文件,,因为这种方法可以在文件中存储所有字符,包括 Ctrl+Z。新版的DOS也使用这种方法处理文本文件。UNIX 使用这种方法处理所有文件。

但是无论操作系统实际使用哪种方法,在C语言中,用 getchar()/scanf() 读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file)。

EOF定义在 stdio.h 文件中 #define EOF (-1)

为-1原因:字符集在 0——255。-1不对应任何字符。

一些系统也许把EOF定义为其他,但是定义的值一定与输入字符所产生的返回值不同。

如何在程序中使用EOF? 把 getchar() 值和EOF比较。如果两值不同,就说明没有到达文件结尾。即如下表达式

while ( (ch = getchar()) != EOF)

4. 重定向和文件

输入和输出设计函数、数据和设备。如果输入函数和输入数据不变,仅改变程序查找数据的位置,如何完成?

程序可以通过两种方法使用文件

  1. 显式使用特定的函数打开文件、关闭文件、读取我文件、写入文件
  2. 设计能与键盘交互的程序,通过不同渠道重定向输入至文件和从文件输出。即把stdin流重新赋给文件,继续使用getchar() 函数从输入流中获取数据

--> 1. 单字符I/O:getchar() 和 putchar()getchar() & putchar 每次只处理一个字符头文件 stdio.h 中包含这两个,他们被定义为供预处理器使用的宏2. 缓冲区老式系统,无缓冲区,输入 hello 时,显示为 hheelloo。即正在等待的程序可立即使用输入的字符好坏缓冲区好处:若干字符作为一个块进行传输比逐个发送节约时用户打错可直接修改无缓冲...