关于C语言的前置知识总结
变量类型
int有别于一般c语言,占两个字节,16位,char占1字节8位
C51扩充数据类型
sfr sbit
用于定义特殊功能寄存器或特殊位。
位运算:
指针与地址运算符:
函数:
中断函数
在函数形参括号后加修饰符 interrupt m,系统编译时把对应函数转化为中断函数,自动加上程序头段和尾段,并按 51 系 统中断的处理方式自动把它安排在程序存储器中的相应位置。
在该修饰符中,m 的取值为 0~31,对应的中断情况如下:
0——外部中断 0
1——定时/计数器 T0
2——外部中断 1
3——定时/计数器 T1
4——串行口中断
5——定时/计数器 T2
其它值预留。
外部函数
如果要调用的函数不在本文件内,在其他文件内,定义函数时函数开头要加 extern 修饰符。
51单片机的最小组成系统
- 晶振电路,提供时钟,相当于心脏
- 复位电路,系统运行不正常时可以重启
- 电源电路,注意单片机的供电电压要求
- 下载电路,烧入程序
LED
GPIO(general purpose input output) 即通用输入输出端口,可以通过软件控制其输入和输出.
51单片机默认IO口为高电平,因此在点亮LED灯时只需要将对于的IO口拉为低电平即可以实现电路导通进而实现LED灯点亮。
代码如下:
#include <STC89C5xRC.H>
void main()
{
P2=0xFE;//11111110
while(1)
{
}
}
关于LED闪烁,需要在一定的时间间隔内对指定LED灯的IO口进行反复置高和置低,与LED点亮相比多了一个delay延时函数,delay函数是通过让cup进行空循环而使单片机暂时停在delay函数这块,起到延时的目的,配合IO的置高和制低达到让LED灯闪烁的目的。
delay 500ms为例代码如下:
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
主函数执行以下代码即可达到目的
P2=0xFE;
Delay500ms();
P2=0xFF;
Delay500ms();
LED流水灯可采用多个delay的效果,多次赋值为P2寄存器赋值的方法,也可以采用以下方法
P2=0x01; //对P2赋值
for(i=0;i<8;i++)
{
P2=~(P2<<i) //先移动1的位置再取反等效移动0的位置,继而等效移动灯的位置
Delay500ms()
}
数码管
每个数码管都有 abcdefg 七个段
分为共阴极和共阳极两种,我所使用的STC89C52RC采用的是共阴极数码管,所以我们选中哪一个数码管阴极赋0,就会启动哪一个数码管,传入的 abcdefg 就会点亮该数码管的对应段。
P00~P07 代表控制当前数码管的 a~g 显示形式,接到 74HC245 缓冲器上而不是直接接到数码管上,使得单片机不用直接驱动数码管,Ai 连到 Bi 上。
OE 是使能,接地工作不接地不工作的原理。
DIR 是规定方向,高电平从左边读取数据传输到右边。低电平从右边读数据到左边。开发板上有个 J21 跳线帽,可以调整是 GND 与 LE 相连还是 VCC 与 LE 相连,也就是高电平输入 DIR 还是低电平输入 DIR。
数码管上面的 COM 是公共端,选中哪一个公共端(使得其=0,因为是共阴极)就是调整哪一个数码管的点亮方式。
代码如下:
#include <STC89C25xRC.H>
//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
//延时子函数
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
//数码管显示子函数
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; //段码清0,消影
}
void main()
{
while(1)
{
Nixie(1,1); //在数码管的第1位置显示1
// Delay(20);
Nixie(2,2); //在数码管的第2位置显示2
// Delay(20);
Nixie(3,3); //在数码管的第3位置显示3
// Delay(20);
}
}
矩阵键盘
为了减少IO口的占用,采用四个IO口代表行,4个IO口代表列的方式
主要存在两种扫描方法:
按行扫描:通过设置 P17 16 15 14 中的一个为低电平来选择扫描哪一行。根据 P10 P11 P12 P13 的输入判断是哪一列。但是 P15 口是蜂鸣器,不断反转会响。所以最好还是用按列扫描
按行扫描:通过设置 P17 16 15 14 中的一个为低电平来选择扫描哪一行。根据 P10 P11 P12 P13 的输入判断是哪一列。但是 P15 口是蜂鸣器,不断反转会响。所以最好还是用按列扫描
相关代码:
#include <STC89C52xRC.H>
#include "Delay.h"
/**
* @brief 矩阵键盘读取按键键码
* @param 无
* @retval KeyNumber 按下按键的键码值
如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
*/
unsigned char MatrixKey()
{
unsigned char KeyNumber=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;}
P1=0xFF;
P1_2=0;
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;
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;
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;
}
定时器中断
中断
使单片机能对外部或者内部随机发生的事件实时处理。
分时操作,实时响应,可靠性高。
中断相应条件:首先我们要确保相关配置都准备好了,CPU 允许中断,中断源允许中断,然后发生中断事件时才会正确触发中断。
中断可能还会被优先级更高的中断打断,支持这种操作的系统叫多级中断系统。
STC89C52 有8个中断,4外部,3定时器,1串口。
定时器中断结构如下:
EA ENABLE ALL:即使能所有中断。
ET:中断允许位。
PT:中断优先级。只有一个 PT 只能决定是高或低两种优先级。更多的中断优先级寄存器可以决定更多中断优先级。
TCON 部分: time controller,不属于 CPU 部分,等到定时器部分展开叙述。
EA=1;//总中断开关:打开
EX0=1;//外部中断0开关:打开。
IT0=0/1;//外部中断触发方式的选择。如下降沿触发,或低电平触发。
//如果要配置外部中断1,则改为EX1和IT1
定时器
CPU时序的相关知识
振荡周期:为单片机提供信号的振荡源的周期(晶振周期)。12MHZ 的晶振振荡周期就是1/12us, 求倒数。
状态周期:两个振荡周期=1状态周期s(时钟周期)。
机器周期:6状态周期=1机器周期。
指令周期:完成一条指令所用的全部时间,以机器周期为单位。
左上角支路是时钟功能,左下角支路是计数功能,最终实现中断功能。
TH TL 寄存器最大能存储到65535.每来一次脉冲+1,加到最大值时 flag 申请中断。
SYSCLK 是晶振周期。另一个时钟是 T0 引脚,如果启用 T0 引脚定时器就变成计数器了,每来一个脉冲+1。
默认使用12T 的分频,把 12MHZ 分成12份,每一份就是1us。这个单片机上是没有对应调整的寄存器的,如果想使用 6T 的分频需要在 STC-ISP 中选择使能 6T 模式。
CT 是一位寄存器,赋1为C,即计数器;赋0为T,即时钟(T上面的横线就代表0时)。
每个定时器主要有两种寄存器:TCON TMOD。
TCON 包括:TF, TR, IE, IT。
TF 可见上图主路,TH TL 被允许计数后周期性+1计数,加到最大值时 TF=1,并发起中断。处理完中断后恢复为0.
TR 可见上图支路,是开启中断的条件之一。
IE 是外部中断。
IT 是设置中断触发模式,比如设置为0是低电平触发,设置为1是下降沿触发。
TMOD 是不可寻址的寄存器,也就是只能整体赋值,不能像 P2 一样分开给每个变量赋值。包含:GATE, CT, M0, M1.
再上图中gate用于开启定时器,TR=1且INT1/INT0 为高(即打开中断引脚)时定时器开始工作。这一部分内容对应上图电路中的左下角。
M0M1用于选择定时时钟的模式。
#include <XTC89C52xRC.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init(void)
{
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 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}