前言:
本次Timer0模块改装了一下,注意!!!注意本次定时器1和定时器0均出现使用
演示视频:
红外遥控直流电机
源代码:
如上图将13个文放在Keli5 中即可,然后烧录在单片机中就行了
烧录软件用的是STC-ISP,不知道怎么安装的可以去看江科大的视频:
【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】https://www.bilibili.com/video/BV1Mb411e7re?p=2&vd_source=ada7b122ae16cc583b4add52ad89fd5e
源代码:
头文件要记得宏定义和重定义,避免重复调用:
#ifndef _Timer0_h_//名字根据文件名定义即可
#define _Timer0_h_
//声明函数……
#endif
main.c
#include <STC89C5xRC.H>
#include "Delay.h"
#include "Key.h"
#include "Nixie.h"
#include "IR.h"
#include "Timer1.h"
#include "Timer0.h"
sbit Motor=P1^0;//电机引脚
unsigned char Counter,Compare; //计数值和比较值,用于输出PWM
unsigned char Command,Speed;//获取按键,档位1-3
void main()
{
Timer1_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){Compare=0;} //设置比较值,改变PWM占空比
if(Speed==1){Compare=50;}
if(Speed==2){Compare=75;}
if(Speed==3){Compare=100;}
}
Nixie(1,Speed);//动态数码管显示
}
}
void Timer1_Routine() interrupt 3
{
TL1 = 0xA4; //设置定时初值
TH1 = 0xFF; //设置定时初值
Counter++;
if(Counter>=100){
Counter=0;
}
if(Counter<Compare) //计数值小于比较值
{
Motor=1;//直流电机通电
}
else //计数值大于比较值
{
Motor=0;//直流电机不通电
}
}
Nixiec
#include "Delay.h"
#include <STC89C5xRC.H>
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Nixie(unsigned char Location,Number){
switch(Location){
case 1:P24=1;P23=1;P22=1;break;
case 2:P24=1;P23=1;P22=0;break;
case 3:P24=1;P23=0;P22=1;break;
case 4:P24=1;P23=0;P22=0;break;
case 5:P24=0;P23=1;P22=1;break;
case 6:P24=0;P23=1;P22=0;break;
case 7:P24=0;P23=0;P22=1;break;
case 8:P24=0;P23=0;P22=0;break;
}
P0=NixieTable[Number];
}
Nixie.h
//Nixie.h
#ifndef __Nixie_H__
#define __Nixie_H__
unsigned char NixieTable[];
void Nixie(unsigned char Location,Number);
#endif
Delay.c
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
Int0.c
#include <STC89C5xRC.H>
void Int0_Init(){
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
////外部中断函数模版
//void Int0_Routine() interrupt 0{
// Num++;
//}
Int0.h
#ifndef __Int0_H__
#define __Int0_H__
void Int0_Init();
#endif
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;
}
Timer0.h
#ifndef _Timer0_h_
#define _Timer0_h_
void Timer0_Init();
void Timer0_SetCounter(unsigned int Value);
unsigned int Timer0_GetCounter();
void Timer0_Run(unsigned char Flag);
#endif
Timer1.c
#include <STC89C5xRC.H>
//void Timer0_Init()
//{
// TF0=0;TR0=1;//TCON,寄存器
// //TMOD=0x01;//0000 0001,寄存器
// TMOD=TMOD&0xF0;//把TMOD的低四位清零,高四位不变,方便使用两个定时器
// TMOD=TMOD&0x01;//把TMOD的最低位置1,高四位不变,方便使用两个定时器
// TH0=64535/256;//高电位,寄存器,1毫秒
// TL0=64535%256;//低电位,寄存器,1毫秒
// ET0=1;EA=1;PT0=0;//打开中断开关
//
//}
////定时器0初始化函数
//void Timer0_Init() //1毫秒@11.0592MHz
//{
//// AUXR &= 0x7F; //定时器时钟12T模式
// TMOD &= 0xF0; //设置定时器模式
// TMOD |= 0x01; //设置定时器模式
// TL0 = 0x66; //设置定时初值
// TH0 = 0xFC; //设置定时初值
// TF0 = 0; //清除TF0标志
// TR0 = 1; //定时器0开始计时
// ET0=1;//允许中断
// EA=1;//允许总中断
// PT0=0;//低优先级
//}
//定时器1初始化函数
void Timer1_Init() //100微秒@11.0592MHz
{
// AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TL1 = 0xA4; //设置定时初值
TH1 = 0xFF; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1=1;//允许中断
EA=1;//允许总中断
PT1=0;//低优先级
}
////中断程序函数
//中断函数模版 1ms
//void Timer1_Routine() interrupt 3
//{
// static unsigned int T1Count;
// TL1 = 0x66; //设置定时初值
// TH1 = 0xFC; //设置定时初值
// T1Count++;
// if(T1Count>=1000){
// T1Count=0;
// P20=~P20;
// }
//}
////中断程序函数
//中断函数模版 100us
//void Timer1_Routine() interrupt 3
//{
// static unsigned int T1Count;
// TL1 = 0xA4; //设置定时初值
// TH1 = 0xFF; //设置定时初值
// T1Count++;
// if(T1Count>=1000){
// T1Count=0;
// P20=~P20;
// }
//}
Timer1.h
#ifndef _Timer1_h_
#define _Timer1_h_
void Timer1_Init();
#endif
IR.c
#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Int0.h"
unsigned int IR_Time;//外部中断计时
unsigned char IR_State;//红外遥控现阶段状态
unsigned char IR_Data[4];//数据(共32位,分皮来储存和表示)
unsigned char IR_pData;//分批数据0-31,用来IR_Data[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
}
}
}
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();
unsigned char IR_GetDataFlag();
unsigned char IR_GetRepeatFlag();
unsigned char IR_GetAddress();
unsigned char IR_GetCommand();
#endif
代码解析与教程:
Timer0模块
- 包含源代码与头文件,需要知道怎么实现,会用
- 51单片机的定时器和计数器十分重要,要理解怎么用,要知道原理是什么,要结合原理图来分析怎么做,先看代码
#include <STC89C5xRC.H> //void Timer0_Init() //{ // TF0=0;TR0=1;//TCON,寄存器 // //TMOD=0x01;//0000 0001,寄存器 // TMOD=TMOD&0xF0;//把TMOD的低四位清零,高四位不变,方便使用两个定时器 // TMOD=TMOD&0x01;//把TMOD的最低位置1,高四位不变,方便使用两个定时器 // TH0=64535/256;//高电位,寄存器,1毫秒 // TL0=64535%256;//低电位,寄存器,1毫秒 // ET0=1;EA=1;PT0=0;//打开中断开关 // //} //定时器0初始化函数 void Timer0_Init() //1毫秒@11.0592MHz { // AUXR &= 0x7F; //定时器时钟12T模式 TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置定时器模式 TL0 = 0x66; //设置定时初值 TH0 = 0xFC; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0=1;//允许中断 EA=1;//允许总中断 PT0=0;//低优先级 } ////中断程序函数 //中断函数模版 //void Timer0_Routine() interrupt 1 //{ // static unsigned int T0Count; // TL0 = 0x66; //设置定时初值 // TH0 = 0xFC; //设置定时初值 // T0Count++; // if(T0Count>=1000){ // T0Count=0; // //下面是代码区 // } //}
- 最上面注释掉的代码是要求理解的;中间的代码是STC-ISP软件生成的;最下面的代码是中断函数模版,拿到main.c中可直接使用,但是也要了解原理:
定时器/计数器、中断教程(重点!!!!)
- 首先,要理解原理,会认原理图:
(本篇均是我自己理解的,只是帮助大家理解,若像深学深究,请去自行找资源,如有不对,希望大家指出)
先看官方解释:
- 红色部分是定时器,作用是自己设定一个最大值,让计数器达到时,完成什么什么,比如执行中断
- 黄色部分是计数器,作用是自己设定,让其变化,比如+1,毫秒,微妙,完成时继续怎么怎
- 蓝色部分是中断器,中断当前执行,执行自己设定的东西,中断这部分可以嵌套,像if函数一样,低优先级让高优先级
他们三者的关系非常微妙,仅仅相连,相辅相成:
- 定时器就是设定一个时间,计数器开始计时,到点了中断器开始执行;举个例子:你订一个10.00的闹钟(定时器)提醒你起床,时间一点一点的过去(计数器),10.00的时候闹钟提醒你(定时器),随后你开始起床(中断器);那么他们是怎么实现的呢
定时器/计数器(模式一)
- 先看原理图:
- 相关寄存器:
- 官方解释
- 模式一官方解释
- 下面开始来解释我的理解(再看原理图):
- 序号1是非门:反向输出,例如:GATE是1,输出0,是0,输入1。
- 序号2是或门:符号为梯形(或弧形缺口),逻辑上满足 “有 1 出 1,全 0 出 0”。
- 序号3是与门:符号为矩形缺口,逻辑上满足 “全 1 出 1,有 0 出 0”。
1,2,3序号均是TMOD里的,请看原理图:
- 代码TMOD=0x01,是16进制,转化为二进制为0000 0001,前(高)四位对应着定时器1;后(低)四位对应着定时器0;本代码使用的是定时器0, 0001分别对应GATE,C/T,M1,M0,由上面的官方解释可得,M1=0,M0=1时,就是使用并启动本寄存器。
但是这样定义有弊端,也就是定时器1和定时器0不能一起使用,因次,使用下面的代码:TMOD &=0xF0,也就是TMOD = TMOD & 0xF0(1111 0000)。看不懂没关系,举个例子:1010 0101 & 1111 0000 = 1010 0000,也就是有0出0;
1010 0101 | 1111 0000 = 1111 0101,也就是有1出1;因此使用代码:就可以定义定时器0;
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
- 因此,当GATE=0时,通过序号1,输出1;然后通过序号2,输出1;因此,只需要将TR0设定成1,输出就是1,就可以启动寄存器了。
- 序号4是高电位和低电位寄存器:用来设置定时初值,如图:
- 先解释上面的代码,可帮助理解,下面的代码可以不理解(后续可生成):TH0和TL0,最大位就是65535,为了分开储存,用TH0和TL0分别储存,例如:现在有一个数123,但是一个盒子只能装进2位数,因此分成123/100=1和123%100=23储存,同理身为16进制,就要用256来做除数;代码中设定为64535的目的是因为1000毫秒就是1秒,65535-64535=1000,用来表示1秒,计时器每次加1秒。
- 序号7.8(图中忘记标了(TR0))是TCON(定时器控制)寄存器:TF0=0时,可以理解成初始化;TR0=1时,表示允许计时;反之两个就是反义理解
- 序号5.6是TMOD(定时器模式)寄存器:用来控制定时器和计数器的模式,本篇只讲用的多的模式一
中断器
- 先看原理图:
这里我们定义好定时器0后,TF0=1,ET0=1,打开中断,随后PT0=0,接入低级优先:理解上面的东西后,再看中断函数:
运用静态局部变量T0Count,来表示定时区间,达到1000毫秒(1秒)后重新执行该函数
P20行代码是代码区,也就是放你想每1秒就重复执行的代码。
Dealy模块
- 包含源代码与头文件,不需要知道怎么实现的会用即可,后续使用,直接将头文件和源代码拿过来用即可;
xms是定义的毫秒,1000毫秒就是1秒;模版生成的是1毫秒的,因此xms等于1000
Int0和Timer0外部中断模块
- 先来了解一下图:
之前讲过定时器中断就是第二个,今天来讲一下第一个0,外部中断,对应引脚是P32;
先初始化:
上边这条INT0全通就可以初始化成功:#include <STC89C5xRC.H> void Int0_Init(){ IT0=1; IE0=0; EX0=1; EA=1; PX0=1; } ////外部中断函数模版 //void Int0_Routine() interrupt 0{ // Num++; //}
下面配合Timer0来实现外部中断:
void Timer0_Init(void) { TMOD &= 0xF0; //设置定时器模式 TMOD |= 0x01; //设置定时器模式 TL0 = 0; //设置定时初值 TH0 = 0; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 0; //定时器0不计时 }
将初值TL0,TH0设定为0,TR0定时器不计时设置为0;
设置定时器0计数器值:
/** * @brief 定时器0设置计数器值 * @param Value,要设置的计数器值,范围:0~65535 * @retval 无 */ void Timer0_SetCounter(unsigned int Value) { TH0=Value/256; TL0=Value%256; }
获取定时器0计数器值:
/** * @brief 定时器0获取计数器值 * @param 无 * @retval 计数器值,范围:0~65535 */ unsigned int Timer0_GetCounter(void) { return (TH0<<8)|TL0; }
控制定时器0的启动和关闭:
/** * @brief 定时器0启动停止控制 * @param Flag 启动停止标志,1为启动,0为停止 * @retval 无 */ void Timer0_Run(unsigned char Flag) { TR0=Flag; }
现在就完成了外部中断的配置,在外部函数中书写代码即可;
LCD1602模块
- LCD1602相关重要知识:
- LCD1602有两上下两行显示屏,每行各有16个小显示屏,如上图中的LCD_ShowString(1,3,"Hello"),第一个参数是第一行还是第二行,第2个参数是对应第几行的第几个小显示屏,最后一个是输出的东西,同理,到LCD_ShowNum(1,9,123,3)里,前三个和前面一样,最后一个参数是显示的位数,不够就在前面补0,例如输入1,参数为4,显示就是0001,输入23,参数为3,显示就是023
- 先看原理图
VO是调节显示亮度的,让你的显示屏显示更清楚。
RS,WR,EN(E)是引脚定义,看单片机核心,对应着P25,P26,P27:(下述所有代码WR都写成了RW,不过不影响,引脚对就行)
再看D0-D7,也就是数据,看单片机核心可知,对应着P00-P07,也就是P0,因此宏定义LCD_DataPort=P0;
//引脚配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0
LCD1602,屏幕是16*2的,每一个小格格就是CGRAM+CGROM(字模库)组成,这个东西就是显示数据的,这些数据是DDPAN(数据显示区)来存储的,然后映射到屏幕上,一一对应;但是DDRAM有40*2个小格格,因此,LCD1602,其实可以滚动显示;然后DDRAM是由控制器来决定的,AC就是小格格的地址;
DDRAM部分
看第8个DDRAM部分:前两个是00
上图就是DDRAM的地址,也就是小格格的,如果是第一行显示,A6前面那个等于0就行了,A6前面等于1就是第二行显示,剩下的A6-A0就是小格格的地址,比如第一个,0000 0000,因此简写成00H,第二行第一个,0100 0000,0x40,简写40H,以此类推;
CGRAM+CGROM部分
这部分可以理解成二维数组,比如你想显示A,在图中找到A,A的列是0100,行是0001,把列放到行的前面组成二进制就是他的地址,0100 0001,就是0x41;
来看编码表,A确实是0x41;
- 掌握上述知识,就可以写显示数据了,这里只讲写数据/指令:
写指令/数据部分
可以看到,写入一个数据要将RS变化(默认为1),R/W变化(默认为1),EN(E)也要变化,因此有:
/** * @brief LCD1602延时函数,12MHz调用可延时1ms * @param 无 * @retval 无 */ void LCD_Delay() //@11.0592MHz { unsigned char i, j; i = 11; j = 190; 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(); }
注意:EN(E)操作的时候需要时间,数据读取需要时间,因此延时1ms;RS在写数据和指令的时候不一样,前者1,后者0;
屏幕显示部分
由上控制指令,完成操作,就可以初始化屏幕:
/** * @brief LCD1602初始化函数 * @param 无 * @retval 无 */ void LCD_Init() { LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵 LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关 LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动 LCD_WriteCommand(0x01);//光标复位,清屏 }
然后是显示字符的操作,先用指令确定AC地址:
/** * @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 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); }
确定AC地址,然后用函数直接写数据;
字符串:
/** * @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]); } }
这部分就是C语言程序部分,看看代码很好理解
数字:
/** * @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'); } }
这部分也是C语言代码部分,将数字各位分开,例如:
这样操作。
其他部分:
/** * @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'); } }
和数字部分差不多,也是C语言程序,认真看代码即可;
IR红外遥控模块
- 先来认识一下
初始化和定义变量:
使用前先初始化外部中断模块,和定义变量:
/** * @brief 红外遥控初始化 * @param 无 * @retval 无 */ void IR_Init(void) { Timer0_Init(); Int0_Init(); }
unsigned int IR_Time;//外部中断计时 unsigned char IR_State;//红外遥控现阶段状态 unsigned char IR_Data[4];//数据(共32位,分皮来储存和表示) unsigned char IR_pData;//分批数据0-31,用来IR_Data[IR_pData]表示数据 unsigned char IR_DataFlag;//数据帧信号 unsigned char IR_RepeatFlag;//连发帧信号 unsigned char IR_Address;//地址 unsigned char IR_Command;//命令
检查红外线标志位(是否接收成功):
/** * @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 } } }
红外线遥控有三个状态:
其中State=0是空闲状态;State=1是等待Start信号或Repeat信号;State=2是接收数据;
空闲状态的时候,要把定时器0清空并打开(IR_State=0):
if(IR_State==0) //状态0,空闲状态 { Timer0_SetCounter(0); //定时计数器清0 Timer0_Run(1); //定时器启动 IR_State=1; //置状态为1 }
State=1是等待Start信号或Repeat信号,要判断当前定时器0计数器的值在哪个区间来判断接收Data数据区间,如图:
如果定时器0计数器在9+4.5=13.5ms,13500us的时候,就是Start(接收数据)状态,于是准备接收Data,然后将State=2,(置为状态2);在9+2.25=11.25ms,11250us的时候,就是Repeat(连续接收数据)状态,这时Data已经被接收了,只需要重新从State=0开始即可重复接收;如果接收失败,将State=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 } }
State=2是接收数据;如果状态1通过,进入状态2(接收数据);先获取上次的中断定时器0计数器值,判断接收的是高电平还是低电平;
如果定时器0计数器在560=560us=1120us的时候,就是接收低电平,然后将数据(IR_Data[])对应位置为0,然后数组数据(IR_pData)增加;在560+1690us=2250us,就是接收高电平,然后将数据(IR_Data[])对应位置为0,然后数组数据(IR_pData)增加;如果接收失败,将数组指针(IR_pData)和State=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 }
然后判断数据是否达到32位:
如果达到就进行数据验证,验证方法是看数据第一位和第二位的补码是否相等,看数据第三位和第四位的补码是否相等;验证通过的话,转存数据:
Address对应IR_Data[0];Command对应IR_Data[2],存储之后,IR_DataFlag=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 }
然后停止定时器0,置红外线状态为空闲状态0;
PWM(直流电机)教程
- 先看原理图:
引脚IO口是在P10,因此:
sbit Motor=P1^0;//电机引脚
电机是高电平工作,为1时会通电,0时不过电;
要想实现电机换挡操作,就要设置PWM占比
- 来看PWM占比图:
由图可知,计数器<比较值时输出0;相反输出1;我们只需要定义比较值,就可以控制PWM输出的周期,例如图中的,低电平输出就比高电平输出时间长,所以,电机转的就慢;当然也可以使计数器<比较值时输出1;相反输出0;结果就反过来了,因此就有:
#include <STC89C5xRC.H> #include "Delay.h" #include "Key.h" #include "Nixie.h" #include "Timer0.h" sbit Motor=P1^0;//电机引脚 unsigned char Counter,Compare; //计数值和比较值,用于输出PWM unsigned char KeyNum,Speed;//获取按键,档位1-3 void main() { Timer0_Init();//初始化定时器 while(1) { KeyNum=Key();//获取按键值 if(KeyNum==1)//如果按键1摁下 { Speed++;//档位增加 if(Speed>=4){//档位边界判断 Speed=0; } if(Speed==0){Compare=0;} //设置比较值,改变PWM占空比 if(Speed==1){Compare=50;} if(Speed==2){Compare=75;} if(Speed==3){Compare=100;} } Nixie(1,Speed);//动态数码管显示 } } void Timer0_Routine() interrupt 1 { TL0 = 0xA4; //设置定时初值 TH0 = 0xFF; //设置定时初值 Counter++; if(Counter>=100){ Counter=0; } if(Counter<Compare) //计数值小于比较值 { Motor=1;//直流电机通电 } else //计数值大于比较值 { Motor=0;//直流电机不通电 } }
便可实现;
Nixie模块(74HC138)
- 先看原理图:
- 先看第二个图,数码管一共有8个,要想第几个亮,直接选择第几个,比如第一个亮,LED8;如何打开LED8呢,看第一个图,对应Y7,想控制Y7,就是让P22(A),P23(B),P24(C)二进制CBA等于7(01(C)1(B)1(A))也就是P24(C)=1,P23(B)=1,P22(A)=1,Y6就是(01(C)1(B)0(A))P24(C)=1,P23(B)=1,P22(A)=0;以此类推;
- 控制完第几个亮,在控制每一个小的管,dp始终是0,并让他们倒过来进行二进制控制,比如想使第一个灯管亮并显示1,就是选择LED8,然后让小管bc亮,然后让二进制(0gfe dcba)来控制谁亮,比如bc亮二进制就是000 0110=0x06;让显示0,除了g都亮,二进制就是0011 1111=0x3F;以此类推,这些小管子由P0控制二进制;
#include "Delay.h" #include <STC89C5xRC.H> unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; void Nixie(unsigned char Location,Number){ switch(Location){ case 1:P24=1;P23=1;P22=1;break; case 2:P24=1;P23=1;P22=0;break; case 3:P24=1;P23=0;P22=1;break; case 4:P24=1;P23=0;P22=0;break; case 5:P24=0;P23=1;P22=1;break; case 6:P24=0;P23=1;P22=0;break; case 7:P24=0;P23=0;P22=1;break; case 8:P24=0;P23=0;P22=0;break; } P0=NixieTable[Number]; }
Nixie(1,0);//初始化数码管
来看代码,Nixie(1,0)此时Location=1,Number=0,前者为1,P24,P23,P22都是1,加起来等于7,也就是Y7,就是LED8,就是第一个灯亮;后者为0,就是P0等于NixieTable[0]=0x3F,显示0;以此类推;
main模块
- 注释写的很清楚,这里不做解释了
#include <STC89C5xRC.H> #include "Delay.h" #include "Key.h" #include "Nixie.h" #include "IR.h" #include "Timer1.h" #include "Timer0.h" sbit Motor=P1^0;//电机引脚 unsigned char Counter,Compare; //计数值和比较值,用于输出PWM unsigned char Command,Speed;//获取按键,档位1-3 void main() { Timer1_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){Compare=0;} //设置比较值,改变PWM占空比 if(Speed==1){Compare=50;} if(Speed==2){Compare=75;} if(Speed==3){Compare=100;} } Nixie(1,Speed);//动态数码管显示 } } void Timer1_Routine() interrupt 3 { TL1 = 0xA4; //设置定时初值 TH1 = 0xFF; //设置定时初值 Counter++; if(Counter>=100){ Counter=0; } if(Counter<Compare) //计数值小于比较值 { Motor=1;//直流电机通电 } else //计数值大于比较值 { Motor=0;//直流电机不通电 } }
注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!!