• 51单片机入门


    一、单片机以及开发板介绍

    写在前面:本文为作者自学笔记,课程为哔哩哔哩江协科技51单片机入门教程,感兴趣可以看看,适合普中A2开发板或者HC6800-ES+V2.0江协科技课程所用开发板。
    工具安装请另行搜索,这里不做介绍,keil和stc-isp

    1.基本知识点碎碎念

    MCU:单片机简称MCU,内部集成CPU、RAM、ROM、定时器、中断系统、通讯接口等
    单片机工作原理:单片机通过配置寄存器来控制内部线路的连接,不同内部连接形成不同的电路,不同的电路完成不同的功能。
    单片机的作用:信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机,LED等)的控制
    RAM:随机存取存储器
    ROM:只读存储器
    电容:电源可能不稳定,作用:电源滤波
    排阻:限流
    复位电路:上电复位
    ds1302:时钟芯片
    74HC595:扩展io口
    138译码器:驱动数码管
    24c02:rom,掉点不丢失
    74hc245:缓冲芯片,双向数据缓冲器,增大驱动能力, OE接地工作,接VCC不工作
    LED:大的是负极,小的是正极
    长的是正极,短的是负极
    电阻上面的编号:102,代表:10102;1001,代表:100101
    单片机是TTL电平:高电平是5V,低电平是0V
    CPU怎么控制引脚:CPU直接访问寄存器,寄存器八个分为一组,八位,每一位后面连接一个驱动器,增大电流(增大驱动能力)一个寄存器刚好八个引脚
    为什么用十六进制:十六进制可以完美的表达二进制,0000-1111刚好对应0-F
    可位寻址/不可位寻址:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作

    命名规则
    在这里插入图片描述

    单片机别插反!!!
    AT89C52
    在这里插入图片描述

    2.编程碎碎念

    #include :这个里面定义了一些接口,比如P1这个IO口
    在这里插入图片描述
    <>和""的区别:<>是在安装目录里面找,而""是在程序目录,也就是我们自己的目录里面找
    !和~的区别: ! 是逻辑去反, ~ 是按位取反
    #include >:里面定义着寄存器,比如sfr P1 = 0x90;就表示了一个地址
    sfr:特殊功能寄存器声明。例如:sfr P0 = 0x80;
    sbit:特殊位声明。例如:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1;

    3.电路和其他知识

    三极管:放大电路。NPN高电平导通,PNP低电平导通。

    二、LED

    发光二极管
    51单片机所以引脚为高电平,我们只需要把亮的LED所连接引脚置为低电平即可。
    以下是一个11.0592MHz晶振的延时x毫秒的delay函数。

    #include 
    #include 
    void delay(unsigned int xms)	//@11.0592MHz
    {
    	unsigned char data i, j;
    	while(xms--)
    	{
    		_nop_();
    		i = 2;
    		j = 199;
    		do
    		{
    			while (--j);
    		} while (--i);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    1.STC-ISP代码生成

    延时函数生成:注意晶振,单位,8051指令集选择STC-Y1,生成的代码_nop_报错,要引入:#include
    定时器0代码生成:注意晶振,定时器0,16位不自动重载,12T

    三、按键

    1.按键介绍

    轻触按键:相当于是一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开。

    由于这种金属弹片受力,当机械触点断开、闭合时,由于机械触点的弹性作用,在按下和松开时,不会一下子接通或者断开,会伴随一连串的抖动,所以我们就要进行按键消抖。

    2.按键消抖

    消抖:硬件消抖,软件消抖
    软件消抖一般有两种:

    • 通过delay()和死循环实现(影响主函数执行,一直死循环无法执行别的)
      • 按键按下接通,对应IO口变成低电平。
      • 按下和松开分别延时20us,中间监测如果没松开(IO口一直为低电平),就一直死循环。
    • 通过定时器中断实现(定时器需要一直中断扫描,占用CPU资源)
      • 定时器中断每20ms进入
      • 设置一个静态变量记录上一次的状态,如果上一个状态为0,这一个状态为键码,说明对应按键按下,返回键码
    // delay和死循环实现软件消抖,获取按键键码
    unsigned char Key()
    {
    	unsigned char KeyNumber = 0;
    	
    	if(P3_1==0){delay(20);while(P3_1==0);delay(20);KeyNumber=1;}
    	if(P3_0==0){delay(20);while(P3_0==0);delay(20);KeyNumber=2;}
    	……
    	
    	return KeyNumber;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // 定时器中断实现软件消抖,获取按键键码
    unsigned char Key_KeyNumber;
    
    // 返回键码并且把键码置0
    unsigned char Key()
    {
    	unsigned char Temp = 0;
    	Temp = Key_KeyNumber;
    	Key_KeyNumber = 0;
    	return Temp;
    }
    // 获取键码
    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;
    }
    // 把比较上次和这次的按键值,设置键码
    void Key_Loop()
    {
    	static unsigned char NowState=0,LastState=0; // 用到静态局部变量记忆性
    	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;}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    四、数码管

    1.数码管介绍

    有共阴极和共阳极,我是用的是共阴极
    51单片机低电平点亮更亮
    在这里插入图片描述

    下图这种叫四位一体数码管
    在这里插入图片描述
    段选:决定了显示什么
    位选:决定了哪个显示

    2.138译码器

    74LS138、74HC138,功能一致:三位驱动八位,左边三位,二进制数对应的十进制为右边对应编号的引脚为低电平。

    3.扫描的概念

    • 数码管扫描(输出扫描)
      原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果

    • 矩阵键盘扫描(输入扫描)
      原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果

    以上两种扫描方式的共性:节省I/O口

    4.数码管驱动方式

    专用驱动芯片和单片机直接扫描两种:

    • 单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间
    • 专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可

    单片机直接扫描:
    通过38译码器,3个引脚控制8个引脚,来选择亮哪个LED(也就是位选),然后通过控制P0口来显示对应数字

    下面是一个案例,用来指定某个数码管显示数字:

    • 传入两个参数,一个位选,一个段选,为快速的段选和位选的过程中,需要消影,delay是为了防止数码管显示亮度变暗。
    // 
    void nixie(unsigned char location , number)
    {
    	unsigned char nixieTable[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
    	0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00,}; // 0~9,A-F,最后一个空
    	switch(location)
    	{
    		case 1 : P2_2 = 1 ; P2_3 = 1 ; P2_4 = 1; break;
    		case 2 : P2_2 = 1 ; P2_3 = 1 ; P2_4 = 0; break;
    		case 3 : P2_2 = 1 ; P2_3 = 0 ; P2_4 = 1; break; 
    		case 4 : P2_2 = 1 ; P2_3 = 0 ; P2_4 = 0; break; 
    		case 5 : P2_2 = 0 ; P2_3 = 1 ; P2_4 = 1; break; 
    		case 6 : P2_2 = 0 ; P2_3 = 1 ; P2_4 = 0; break;
    		case 7 : P2_2 = 0 ; P2_3 = 0 ; P2_4 = 1; break;
    		case 8 : P2_2 = 0 ; P2_3 = 0 ; P2_4 = 0; break; 
    	}
    
    	P0 = nixieTable[number];
    	delay(1);
    	P0 = 0x00;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    上面的代码也是比较占用CPU的,需要main不断的去调用数码管扫描,所以我们也可以用定时器来实现

    • 调用只需要给缓冲区设置要现实的位码和断码,定时器自动扫描
    • 每隔2ms定时器中断扫描缓冲区动态刷新
    unsigned char Nixie_Buf[9] = {0,16,16,16,16,16,16,16,16}; // 显示缓存区 设置9位是为了第一个数码管对应数字1
    void Nixie_SetBuf(unsigned char Location,Number)
    {
    	Nixie_Buf[Location]=Number; 
    }
    
    
    void Nixie_Scan(unsigned char Location , Number)
    {
    	unsigned char NixieTable[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
    	0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00,}; // 0~9,A-F,最后一个空
    	P0 = 0x00;
    	switch(Location)
    	{
    		case 1 : P2_2 = 1 ; P2_3 = 1 ; P2_4 = 1; break;
    		case 2 : P2_2 = 1 ; P2_3 = 1 ; P2_4 = 0; break;
    		case 3 : P2_2 = 1 ; P2_3 = 0 ; P2_4 = 1; break; 
    		case 4 : P2_2 = 1 ; P2_3 = 0 ; P2_4 = 0; break; 
    		case 5 : P2_2 = 0 ; P2_3 = 1 ; P2_4 = 1; break; 
    		case 6 : P2_2 = 0 ; P2_3 = 1 ; P2_4 = 0; break;
    		case 7 : P2_2 = 0 ; P2_3 = 0 ; P2_4 = 1; break;
    		case 8 : P2_2 = 0 ; P2_3 = 0 ; P2_4 = 0; break; 
    	}
    	P0 = NixieTable[Number];
    }
    
    void Nixie_Loop() // 2ms刷新一次
    {
    	static unsigned char i=1;
    	Nixie_Scan(i,Nixie_Buf[i]);
    	i++;
    	if(i>=9)i=1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    五.模块化编程

    传统方式编程:所有函数都在main.c里面
    模块化编程:各个模块的代码放在不同的.c文件里面,#include引入,大大提高代码的可阅读性,可维护性,可移植性
    .c文件必须在工程目录中参与编译
    .h文件可以在工程目录中,也可以自定义,自定义方式如下
    在这里插入图片描述

    下图是一个图解的一个简单案例
    在这里插入图片描述

    1.预编译

    C语言的预编译以#开头,作用:在编译之前,对代码进行一些处理(预编译)
    #inlucde :把xxxx.h的内容搬到此处,这个不区分大小写
    #define PI 3.14 :定义PI,把PI替换为3.14
    #ifndef xx_H : 如果没有定义__xx_H__
    #endif:与#ifndef,#if匹配
    还有#ifdef,#if,#else,#elif,#undef

    简单来说:就是吧所有的引用的别的地方的东西替换过来。

    六.LCD1602调试工具

    1.介绍

    • LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符
    • 显示容量:16×2个字符,每个字符为5*7点阵
      提供调试窗口,提供类似printf函数的功能
      开发板有引脚冲突,P0和P2_5,P2_6,P2_7会失效

    2.引脚及应用电路

    引脚功能
    VSS
    VDD电源正极(4.5~5.5V)
    VO对比度调节电压
    RS数据/指令选择,1为数据,0为指令
    RW读/写选择,1为读,0为写
    E使能,1为数据有效,下降沿执行命令
    D0–D7数据输入/输出
    A背光灯电源正极
    K背光灯电源负极

    3.内部结构框图

    在这里插入图片描述

    4.存储器结构

    • DDRAM(数据显示区)

    在这里插入图片描述

    • CGRAM+CGROM(字模库)

    在这里插入图片描述
    从上图我们可以看出,读写的时候,地址为:纵坐标移到横坐标前面。

    5.时序结构

    在这里插入图片描述
    以写指令为例,RS为0,RW为0,E为1,然后DB0-DB7写入数据,之后把E置为0(下降沿执行命令)。LCD1602执行的最小时间为us,指令也有其对应持续时间(见下表),所以LCD1602跟不上单片机的变化,所以要在高电平之后和低电平之后加一段延时。

    6.LCD1602指令集

    在这里插入图片描述

    7.LCD1602操作流程

    • 初始化:
      6.功能设置:发送指令0x38 //八位数据接口,两行显示,5*7点阵
      4.现实开关控制:发送指令0x0C //显示开,光标关,闪烁关
      5.光标、画面移位:发送指令0x06 //数据读写操作后,光标自动加一,画面不动
      1.清屏:发送指令0x01 //清屏

    • 显示字符:
      发送指令0x80|AC //设置光标位置,对照上表,最高位为1,其他的
      发送数据 //发送要显示的字符数据
      发送数据 //发送要显示的字符数据
      ……

    8.代码思维和示例

    • 代码思维

      • 写数据和写指令函数根据时序结构控制引脚即可
      • 初始化函数:发送操作流程中初始化的指令
      • 设置光标位置:我们输入的是某行某列这种,要转换成发送指令对应的DDRAM地址
      • 显示字符:字符可以是'字符'或者ASCII码,发送数据即可
      • 显示字符串:实际上是字符数组,连续发送数据,监测到'\0'停止
      • 显示无符号数:只需理解这个公式:Number/10Length-1 %10;即可依次取到第Length位,然后用0的ASCII码加上公式所得数(也就是说把每个数字当成字符显示,需要发送ASCII码)
      • 显示有符号数:判断一下正负,显示’+'或者‘-’,然后把数转换为正数,当成无符号数显示
      • 显示16进制数:用该公式Number/16Length-1 %16取出每一位,0-9加上’0’,A-F加上’A’
      • 显示2进制数:Number/2Length-1 %2,用这个公式取出每一位+'0’显示
    • LCD1602输出模块代码如下:

    #include 
    
    //引脚配置:
    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');
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224

    七.矩阵按键

    1.介绍

    在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式
    采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态

    可以逐行扫描也可以逐列扫描

    2.51单片机弱上拉

    51单片机的P1,P2,P3都是弱上拉
    单片机的IO口的驱动模式是一种弱上拉模式,又叫准双向口,IO口既可以输出也可以输入。0驱动能力强,1驱动能力弱

    如下图,IO口内部通过一个开关决定输出高低电平,假如说IO口输出为高电平,外部什么都不接,就输出高电平,也读高电平。当接入GND的时候,强GND让他无法保持高电平,就会读进一个低电平。

    在这里插入图片描述

    3.代码实现

    代码思路:把所有的设置为1,然后把某个行或者列设置为0,按下某个按键就可以监测到。
    代码示例如下,一下只展示第一列按列扫描,因为在此开发板中,按行扫描因为引脚问题,蜂鸣器会响。

    	P1=0xFF;
    	P1_3=0;
    	if(P1_7==0){delay(20);while(P1_7==0);delay(20);KeyNumber=1;}
    	if(P1_6==0){delay(20);while(P1_6==0);delay(20);KeyNumber=5;}
    	if(P1_5==0){delay(20);while(P1_5==0);delay(20);KeyNumber=9;}
    	if(P1_4==0){delay(20);while(P1_4==0);delay(20);KeyNumber=13;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    八.定时器

    51单片机定时器是单片机内部资源,其电路的连接和运转均在单片机内部完成。
    定时器个数:3个(T0,T1,T2),T0和T1与传统的51单片机兼容,T2是STC89C52单片机增加的资源,定时器0有四个工作模式。
    定时器资源和单片机的型号关联在一起,不同的型号定时器个数和操作方式不同,T0和T1的操作方式是大部分单片机共有的。

    定时器工作原理:
    根据时钟的输出信号,每隔一段时间,计数单元+1,计满时,计数单元发出中断请求,使程序跳到中断服务函数中执行

    在这里插入图片描述

    时钟可以由内部System clock 提供,也可以通过TO引脚外部提供
    SYSclk:系统时钟,即晶振周期,我本人使用的晶振为11.0592MHz
    晶振:内部是一个压电陶瓷,这个可以产生固定频率的震动
    M:百万,12Mhz表示每秒钟有12百万个周期完成
    频率(Frequency)是指在单位时间内发生的周期数。它用赫兹(Hz)作为单位,表示每秒钟内发生的周期数。例如,1Hz表示每秒钟发生一次周期。
    周期(Period)是指一个波形、信号或事件从开始到结束所经历的时间间隔。它用秒(s)作为单位,表示完成一个周期所需的时间。例如,一个周期为1秒的信号,其频率就是1Hz。
    简而言之:周期就是完成一个周期所用的时间,频率就是单位时间的周期数。

    计数器分为TL0和TH0两个,TL0为低位,TH0为高位,一个里面可以存8位,共16位。只能存0~65535

    定时器初值计算方式:

    • 1us的机器周期=晶振频率/12(12分频)。

    • 1ms的机器周期=1us的机器周期*1000

    • Xms的机器周期=1ms的机器周期*X

    • 十进制的定时器初值=65536-Xms的机器周期

      举例如下:
      11.0592MHz/12=921600Hz,就是一毫秒921.6次机器周期,50ms=46080次机器周期。
      65536-46080=19456(4c00)
      TH0=0x4c,TL0=0x00

    执行原理图如下:
    在这里插入图片描述
    寄存器

    • 寄存器是连接软硬件的媒介
    • 在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式
    • 寄存器相当于一个复杂机器的“操作按钮”
      可位寻址:可以给寄存器的每一位单独复制
      不可位寻址:只能给寄存器整体赋值

    常用的寄存器请参考STC89C52手册

    定时器高低位计算:高八位:初值/256。低八位:初值%256

    九.中断系统

    中断:使CPU放弃当前工作,去处理中断的工作,中断完成之后,又回到原来的地方。
    中断源:请求CPU中断的请求源。eg:定时器溢出
    中断优先级:微型机可以有多个中断源,规定每一个中断源有一个优先级,先响应优先级最高的中断请求。
    允许中断嵌套:高优先级的中断可以打断低优先级的中断。
    在这里插入图片描述
    定时器中断代码实现LED1每隔一秒亮灭:

    #include 
    
    void timer0_Init()
    {
    //	TMOD = 0x01; // 定时器0,模式1
    	TMOD &= 0xF0; //把TMOD低四位清零,高四位不变
    	TMOD |= 0x01; //把TMOD低四位赋值,高四位不变
    	TF0 = 0; // 中断标志位置为0
    	TR0 = 1; // 运行控制位,GATE为0,TR0 = 1时才允许计数。
    	TH0 = 0xFC; // 定时器低8位
    	TL0 = 0x66; // 定时器高8位
    	ET0 = 1; // T0外部中断允许位
    	EA = 1; // 总中断允许位
    	PT0 = 0; // 优先级为低优先级
    }
    
    void main()
    {
    	timer0_Init();
    	while(1)
    	{
    	}
    }
    
    void timer0_Routine() interrupt 1 // 有这个小尾巴代表是中断程序
    {
    	static unsigned int t0Count = 0;
    	TH0 = 0xFC; // 定时器低8位重新赋初值
    	TL0 = 0x66; // 定时器高8位重新赋初值
    	t0Count++;
    	if(t0Count >= 1000)
    	{
    		t0Count = 0;
    		P2_1 = ~P2_1;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    十.串口通信

    串口是一种应用十分广泛的通讯接口,串口成本低,容易使用,通信线路简单,可以实现两个设备的相互通信(单片机与单片机、电脑等各种模块)。
    51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。

    简单双向串口通信有两根通信线,RXD和TXD交叉连接即可实现,注意电平标准不一致时,要加电平转换芯片。

    电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
    TTL电平:+5V表示1,0V表示0
    RS232电平:-3 ~ -15V表示1,+3~+15V表示0
    RS485电平:两线压差+2 ~ +6V表示1,-2 ~ -6V表示0(差分信号)

    常见通信接口比较

    名称引脚定义通信方式特点
    UAERTXD、RXD全双工、异步点对点通信
    I2CSCL、SDA半双工、同步可挂载多个设备
    SPISCLK、MOSI、MISO、CS全双工、同步可挂载多个设备
    1-WireDQ半双工、同步可挂载多个设备

    此外还有:CAN、USB等
    相关术语:

    • 全双工:通信双方可以在同一时刻互相传输数据

    • 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线

    • 单工:通信只能有一方发送到另一方,不能反向传输

    • 异步:通信双方各自约定通信速率

    • 同步:通信双方靠一根时钟线来约定通信速率

    • 总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)

    STC89C52有一个UART
    STC89C52的UART有四种工作模式:
    模式0:同步移位寄存器
    模式1:8位UART,波特率可变(常用)
    模式2:9位UART,波特率固定
    模式3:9位UART,波特率可变
    串口模式图
    在这里插入图片描述
    串口和中断系统可以参照中断系统中的中断系统图,只需要把ES和EA置为1即可打开中断,PS设置中断优先级。

    串行口相关寄存器:
    在这里插入图片描述

    波特率和定时器控制,写到SBUF就会自动发回去

    TI发送数据时为0,发送成功硬件置1,需要软件置0,下次发送完成才能被监测到。

    一个函数不能既在主函数,又在中断函数,这样会导致函数的重用。

    T1溢出率计算:13us溢出一次,溢出率就是1/13 = 0.07692MHz,
    假如加倍,0.07692MHz/16就是波特率

    发送数据:直接发不需要中断
    接收数据:通过中断接收

    十一.LED点阵屏

    1.74HC595

    8位串行输入、并行输出
    ###
    串行读入,SERCLK挨个移位,完成后RCLK推入输出缓存并行输出,常用语端口扩展。

    2.LED点阵屏

    可以理解为数码管,段选和位选,段选之后也要消影和delay(1),段选位选快递切换利用人眼视觉暂留消影实现。

    SER写进一个字节,SERCLK逐个移位,软件置1一次,移动一次,移动完置0;RCLK则需要在移位完成要推到输出缓存的时候软件置1,然后再置0;

    取模软件:高位在上,就不用字节倒序,如果高位在下,那就需要字节倒叙。

    显示动画:定义一个偏移量,如果是流动式的,则偏移量每次+1,如果是逐帧的,则每次+8

    取模的数组,可以放在flash里面,关键字code,这样不存在RAM里面浪费内存。

    十二、DS1302时钟芯片

    在这里插入图片描述
    CH:给1时钟暂停
    WP:写保护
    命令字:一个字节,有8位,决定在哪个位置,读还是写。

    有时候无法写入:写入(0x8e,0x00)关闭写保护。

    在这里插入图片描述
    时序:时钟的上升沿给DS1302写入数据,下降沿从DS1302读出数据。
    在这里插入图片描述
    CE:使能线
    SCLK:时钟线
    I/O:数据线
    读数据的时候,命令字最后一个上升沿写完,下降沿立马读出数据。(可以数,读数据只有15个上升沿,而写数据有16个)
    I/O发两段,第一段是命令字,第二段是数据

    BCD码

    DS1302就是以BCD码存的。
    用4位二进制数表示1位十进制数
    把8位二进制分开,4位代表十进制的1位,高4位代表十位,低4位代表个位

    • BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
    • 十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)

    十三、蜂鸣器

    1.蜂鸣器介绍

    将电信号转换为声信号的器件。有源无源代表震荡源。
    有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
    无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音。注意无源蜂鸣器不要始终通电。

    实现逻辑:通过定时器设置周期,即可实现频率,通过中断控制IO口翻转。

    自己设计用三极管开关即可,开发板使用的是ULN2003

    500us翻转一次为标准提示音

    2.ULN2003

    达林顿晶体管阵列,一种驱动器
    特点:

    • 500mA 额定集电极电流(单个输出)
    • 高电压输出: 50V
    • 输入和各种逻辑类型兼容
    • 继电器驱动器
      只能低电平驱动,不能高电平驱动

    3.钢琴

    每相邻两个键相差半音,c1 和c2 相差8度
    简谱1代表中央C
    在这里插入图片描述
    白键:数字加点
    黑键:#升音符号,b半音符号。eg:#1:1升高本音,b1:1降低半音
    一般四分音符:500ms,二分音符1000ms,八分音符250ms
    后面加"-“:减音。
    数字下面”_“:增音。
    数字后面” . ":增加原来的一半。
    曲线是连一起的

    C调与频率对照表:
    在这里插入图片描述

    以频率为440的低音6为基准,中音6平流层乘2,高音6再乘2,中间等分。
    以下是计算公式:
    频率=440*2n/12 ,或者440/2n/12
    周期(s)=1/频率(HZ)
    周期(us)=周期(s)*1000 000
    电平翻转时间:周期/2
    定时器初值:65536-电平翻转时间

    十四、AT24C02(I2 C总线)

    1.存储器介绍

    在这里插入图片描述

    2.AT24C02

    AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息。
    存储介质:E2PROM
    通讯接口:I2C总线
    容量:256字节

    3.I2C总线介绍

    I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
    两根通信线:SCL(Serial Clock,时钟线)、SDA(Serial Data,数据线)
    同步、半双工,带数据应答
    通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度

    4.I2C时序结构

    6种时序结构,3中数据帧
    开漏输出和上拉电阻解决了多机通信相互干扰的问题。
    I2 C 时序结构:
    1.起始条件:SCL高电平期间,SDA从高电平切换到低电平
    在这里插入图片描述
    2、终止条件:SCL高电平期间,SDA从低电平切换到高电平
    在这里插入图片描述
    3、发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节
    在这里插入图片描述
    4、接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)
    在这里插入图片描述
    5、发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
    在这里插入图片描述

    6、接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
    在这里插入图片描述
    SCL线的控制权一直都在主机手上,在接收数据之前,要把释放SDA,把控制权交给从机,也就是要把SDA置1。

    5.I2C数据帧

    1、发送一帧数据
    在这里插入图片描述

    2、接收一帧数据
    在这里插入图片描述

    3、先发送再接收数据帧(复合格式)
    在这里插入图片描述

    6.AT24C02数据帧

    1、字节写:在WORD ADDRESS处写入数据DATA
    在这里插入图片描述

    2、随机读:读出在WORD ADDRESS处的数据DATA
    在这里插入图片描述

    • AT24C02的固定地址为1010,可配置地址本开发板上为000
    • 所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1

    十五、DS18B20温度传感器

    1.DS18B20介绍

    DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点
    测温范围:-55°C 到 +125°C
    通信接口:1-Wire(单总线)
    其它特征:可形成总线结构、内置温度报警功能、可寄生供电(数据线和GND就可以实现,不用VCC)

    1.1.内部结构图

    在这里插入图片描述

    64-BIT ROM(全球唯一id号):作为器件地址,用于总线通信的寻址
    SCRATCHPAD(暂存器):用于总线的数据交互
    EEPROM:用于保存温度触发阈值和配置参数

    1.2.存储器结构

    在这里插入图片描述
    Byte2、3:高报警和低报警。
    Configuration Register:设置分辨率,设置精度
    写在暂存器里面,然后发送一条指令复制到EEPROM中(上电自动发送到暂存器中)

    2.单总线

    2.1.介绍

    单总线(1-Wire BUS)是由Dallas公司开发的一种通用数据总线
    一根通信线:DQ
    异步、半双工
    单总线只需要一根通信线即可实现数据的双向传输,当采用寄生供电时,还可以省去设备的VDD线路,此时,供电加通信只需要DQ和GND两根线

    2.2.单总线时序结构

    • 初始化:主机将总线拉低至少480us,然后释放总线,等待1560us后,存在的从机会拉低总线60240us以响应主机,之后从机将释放总线
    • 发送一位:主机将总线拉低60120us,然后释放总线,表示发送0;主机将总线拉低115us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us
    • 接收一位:主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us
    • 发送一个字节:连续调用8次发送一位的时序,依次发送一个字节的8位(低位在前)
      在这里插入图片描述
      接收一个字节:连续调用8次接收一位的时序,依次接收一个字节的8位(低位在前)
      在这里插入图片描述

    3.DS18B20操作流程

    • 初始化:从机复位,主机判断从机是否响应
    • ROM操作:ROM指令+本指令需要的读写操作
    • 功能操作:功能指令+本指令需要的读写操作
    ROM指令ROM指令功能功能指令功能指令功能
    SEARCH ROM [F0h]搜寻ROMCONVERT T [44h]温度写到暂存区
    READ ROM [33h]读ROMWRITE SCRATCHPAD [4Eh]写暂存区(把温度接下来的数据写到暂存区)
    MATCH ROM [55h]匹配ROMREAD SCRATCHPAD [BEh]把暂存区的数据依次读出
    SKIP ROM [CCh]跳过ROMCOPY SCRATCHPAD [48h]把暂存器复制到EEPROM
    ALARM SEARCH [ECh]报警搜索RECALL E2 [B8h]EEPROM三个字节复制到暂存器(除了温度)
    READ POWER SUPPLY [B4h]读取是不是寄生供电,1位

    数据帧

    温度变换:初始化→跳过ROM →开始温度变换
    在这里插入图片描述
    温度读取:初始化→跳过ROM →读暂存器→连续的读操作
    在这里插入图片描述

    温度存储格式

    在这里插入图片描述
    TLSB和TMSB合在一起,TMSB放在高位,TLSB放在低位,并且强制类型转化为int型,自动获取正负号。
    Temp=(TMSB<<8)|TLSB;

    定时器会打断时序。在delay前后,加EA=0,EA=1;在延时的过程中屏蔽中断。

    十六、直流电机

    1.直流电机介绍

    • 直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极,当电极正接时,电机正转,当电极反接时,电机反转
    • 直流电机主要由永磁体(定子)、线圈(转子)和换向器组成
    • 除直流电机外,常见的电机还有步进电机、舵机、无刷电机、空心杯电机等

    2.点击驱动电路

    • 大功率器件直接驱动

      给P10口给1,P16口为0
      在这里插入图片描述
      在这里插入图片描述

    • H桥驱动
      在这里插入图片描述

    3.PWD介绍

    • PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域
    • PWM重要参数:
      频率 = 1 / TS
      占空比 = TON / TS
      精度 = 占空比变化步距
      在这里插入图片描述
      在这里插入图片描述

    4.产生PWM方法

    设置一个比较值,计数器在周期内变化,和比较值比较,小于输出0,大于输出1。
    在这里插入图片描述
    程序实现方法:

    • 周期:定时器时间乘以计数器最大值
    • 占空比:比较值/计数器最大值,通过设置比较值的大小来设置占空比
    • 精度:定时器时间/计数器最大值

    部分代码如下:

    //这样设置就是1和0的时间相等
    
    unsigned char Counter,Compare=50; // 计数器,比较值
    
    void timer0_Routine() interrupt 1 
    {
    	TH0 = 0xFF; 
    	TL0 = 0xA4; // 100ns	
    	Counter++;
    	Counter%=100; // 如果Counter=100,100=0;
    	if(Counter<Compare)
    	{
    		Motor=1;
    	}
    	else
    	{
    		Motor=0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    十七、AD/DA

    1.AD/DA介绍

    • AD(Analog to Digital):模拟-数字转换,将模拟信号转换为计算机可操作的数字信号
    • DA(Digital to Analog):数字-模拟转换,将计算机输出的数字信号转换为模拟信号
    • AD/DA转换打开了计算机与模拟信号的大门,极大的提高了计算机系统的应用范围,也为模拟信号数字化处理提供了可能

    2.硬件电路模型

    在这里插入图片描述

    • AD转换通常有多个输入通道,用多路选择开关连接至AD转换器,以实现AD多路复用的目的,提高硬件利用率
    • AD/DA与单片机数据传送可使用并口(速度快、原理简单),也可使用串口(接线少、使用方便)
    • 可将AD/DA模块直接集成在单片机内,这样直接写入/读出寄存器就可进行AD/DA转换,单片机的IO口可直接复用为AD/DA的通道

    3.运算放大器

    • 运算放大器(简称“运放”)是具有很高放大倍数的放大电路单元。内部集成了差分放大器、电压放大器、功率放大器三级放大电路,是一个性能完备、功能强大的通用放大电路单元,由于其应用十分广泛,现已作为基本的电路元件出现在电路图中

    • 运算放大器可构成的电路有:电压比较器、反相放大器、同相放大器、电压跟随器、加法器、积分器、微分器等

    • 运算放大器电路的分析方法:虚短、虚断(负反馈条件下)
      常见的运算放大器电路有以下四种

    • 电压比较器
      在这里插入图片描述

    • 反向放大器

    • 在这里插入图片描述

    • 同向放大器
      在这里插入图片描述

    • 电压跟随器
      在这里插入图片描述

    4.AD/DA原理

    电路知识薄弱,没看懂,以后再补

    5.AD/DA性能指标

    • 分辨率:指AD/DA数字量的精细程度,通常用位数表示。例如,对于5V电源系统来说,8位的AD可将5V等分为256份,即数字量变化最小一个单位时,模拟量变化5V/256=0.01953125V,所以,8位AD的电压分辨率为0.01953125V,AD/DA的位数越高,分辨率就越高

    • 转换速度:表示AD/DA的最大采样/建立频率,通常用转换频率或者转换时间来表示,对于采样/输出高速信号,应注意AD/DA的转换速度

    6.SPI和XPT2046时序

    在这里插入图片描述

    • CS:片选,不共用,多个芯片用多个片选,片选来选芯片
    • DCLK:时钟,上升沿写输入,下降沿读数据
    • DIN:写数据
    • DOUT:读数据

    除CS线外,其他线共用

    7.代码实现

    AD

    通过SPI的方式,发送和读数据,参考上面时序。
    控制字的值参考下表来选择
    在这里插入图片描述
    在这里插入图片描述
    下面代码示例实现电位器,光敏电阻,热敏电阻的AD转换:

    • 首先在XPT2056.c模块控制XPT2056通过SPI方式读写数据,把控制字传给XPT2056,XPT2056读到后根据控制字,把模拟信号转换成数字信号后写到数据线上
    • 通过上表获取数据字发送给XPT2056,即可获得数字信号
    //XPT2056.c
    
    #include 
    
    sbit XPT2045_CS=P3^5;
    sbit XPT2045_DCLK=P3^6;
    sbit XPT2045_DIN=P3^4;
    sbit XPT2045_DOUT=P3^7;
    
    unsigned int XPT2046_ReadAD(unsigned char Command)
    {
    	unsigned char i;
    	unsigned int ADVAlue=0;
    	XPT2045_DCLK=0;
    	XPT2045_CS=0;
    	
    	for(i=0;i<8;i++)
    	{
    		XPT2045_DIN=Command&(0x80>>i);
    		XPT2045_DCLK=1;
    		XPT2045_DCLK=0;
    	}
    	for(i=0;i<16;i++)
    	{
    		XPT2045_DCLK=1;
    		XPT2045_DCLK=0;
    		if(XPT2045_DOUT==1){ADVAlue|=(0x8000>>i);}
    	}
    	XPT2045_CS=1;
    	if(Command&0x08)
    	{
    		return ADVAlue>>8;	
    	}
    	else
    	{
    		return ADVAlue>>4;	
    	}
    	
    }
    
    //XPT2056.H
    #ifndef __XPT2046_H_
    #define __XPT2046_H_
    
    #define XPT2046_XP_8   0x9C      //0x8C   
    #define XPT2046_YP_8   0xDC              
    #define XPT2046_VBAT_8 0xAC 						
    #define XPT2046_AUX_8  0xEC 							
    
    #define XPT2046_XP_12   0x94      //0x88
    #define XPT2046_YP_12   0xD4 
    #define XPT2046_VBAT_12 0xA4 
    #define XPT2046_AUX_12  0xE4 
    
    unsigned int XPT2046_ReadAD(unsigned char Command);
    
    #endif
    
    //main
    #include 
    #include "delay.h"
    #include "LCD1602.h"
    #include "XPT2046.h"
    
    unsigned int ADValue;
    
    void main()
    {
    	LCD_Init();
    	LCD_ShowString(1,1,"ADJ NTG RG");
    
    
    	while(1)
    	{
    		ADValue=XPT2046_ReadAD(XPT2046_XP_8); // 控制字
    		LCD_ShowNum(2,1,ADValue,3);
    		delay(10);
    		ADValue=XPT2046_ReadAD(XPT2046_YP_8); // 控制字
    		LCD_ShowNum(2,5,ADValue,3);
    		delay(10);
    		ADValue=XPT2046_ReadAD(XPT2046_VBAT_8); // 控制字
    		LCD_ShowNum(2,9,ADValue,3);
    		delay(10);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    DA

    DA其实就是PWD实现,只是加了一个低通滤波。可以稳定在一个赋值上,而PWD在1/0切换,就不稳定。
    以下代码实现呼吸灯,和PWD代码一致

    //XPT2046.c
    
    #include 
    #include "Delay.h"
    #include "Key.h"
    #include "Nixie.h"
    #include "Timer0.h"
    
    sbit DA=P2^1;
    
    unsigned char Counter,Compare; // 计数器,比较值
    unsigned char i;
    
    void main()
    {
    	timer0_Init();
    	while(1)
    	{
    		for(i=0;i<100;i++)
    		{
    			Compare=i;
    			delay(10);
    		}
    		for(i=100;i>0;i--)
    		{
    			Compare=i;
    			delay(10);
    		}
    	}
    }
    
    
    void timer0_Routine() interrupt 1 // 有这个小尾巴代表是中断程序
    {
    	TH0 = 0xFF; 
    	TL0 = 0xA4; // 100ns	
    	Counter++;
    	Counter%=100; // 如果Counter=100,100=0;
    	if(Counter<Compare)
    	{
    		DA=1;
    	}
    	else
    	{
    		DA=0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    十八、红外遥控(外部中断)

    1.红外遥控介绍

    红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出
    通信方式:单工,异步
    红外LED波长:940nm
    通信协议标准:NEC标准

    2.硬件电路

    在这里插入图片描述

    • 发送端这里有两个低电平导通的三极管,IN输入高低电平,用38kHz的调制频率。这种频率被称为红外载波频率。(低电平闪着亮,可抗干扰,和自然红外光区分)
    • 接收端使用红外接收管,接单片机外部中断口

    3.基本发送与接收

    • 空闲状态:红外LED不亮,接收头输出高电平
    • 发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平
    • 发送高电平:红外LED不亮,接收头输出高电平
      怎么区分:发送高电平会有波形
      在这里插入图片描述

    4.NEC编码

    在这里插入图片描述

    以下是用示波器接收的波形图:
    在这里插入图片描述

    5.遥控器键码

    在这里插入图片描述

    6.51单片机的外部中断

    • STC89C52有4个外部中断,这里只引出了INT0和INT1
    • STC89C52的外部中断有两种触发方式:
      下降沿触发和低电平触发
    • 中断号:
      在这里插入图片描述
      在这里插入图片描述

    7.外部中断寄存器

    在这里插入图片描述
    在这里插入图片描述
    IT0=1;下降沿触发
    IT0=0;低电平触发

    8.代码实现

    • 中断模块:外部中断下降沿触发中断,进入中断函数,使用下降沿触发
    • 定时器模块:设置定时器0初始化初值为0,并实现以下功能函数
      • 设置定时器0的计数器值:设置TH0和TL0
      • 获取定时器0的计数器值:把TH0和TL0合并用一个int接收
      • 定时器启停:给TR0设置值控制启停
    • 红外模块
      • 初始化:初始化外部中断和定时器
      • 收到数据帧标志位:收到数据帧后把该标志置1
      • 收到连发数据帧标志位:收到连发数据帧把该标志置1
      • 获取收到的地址
      • 获取收到的命令
      • 中断函数
        • 定义一个状态机,0代表空闲,1代表等待开始或者重发信号,2代表接收数据,每次进入中断判断状态机
        • 如果为0:把定时器清零,启动定时器,并把状态机置1
        • 如果为1:获取上一次中断到这次的时间,并把定时器清零,根据时间判断是开始信号还是连发信号,开始信号把状态机置2,连发信号把连发标志位置1,状态机置0。
        • 如果为2:获取上一次中断到这次的时间,并把定时器清零,根据时间判断是高电平还是低电平,给数据按位赋值。如果接收到了32位,验证数据并把数据帧标志位置1。
    //初始化外部中断0 Int0
    void Int0_Init(void)
    {
    	IT0=1;
    	IE0=0;
    	EX0=1;
    	EA=1;
    	PX0=1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    //IR.c  红外接收程序
    
    #include 
    #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
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
  • 相关阅读:
    C++求解一元一次方程——LeetCode 640
    深入Scikit-learn:掌握Python最强大的机器学习库
    FFmpeg源码走读之内存管理模型
    SpringCloud的Consul
    注解及其使用
    MyBatis-Plus用法
    【Java 进阶篇】创建 JavaScript 轮播图:让网页焕发生机
    4 种策略让 MySQL 和 Redis 数据保持一致
    浪潮java面经总结
    C/C++常用语法复习(输入、输出、判断、循环)
  • 原文地址:https://blog.csdn.net/weixin_52234593/article/details/133988284