我们在知道STC-B板的焊接一系列过程之后,我们现在就将使用一系列的代码及原理来使用我们的板子,我们就最先来接触最简单的流水灯案例吧
其步骤为:
后面的一系列工程都是如此来观察实验现象的
知道了实验现象,激起了我们的兴趣之后,我们来了解一下其原理
这一部分,我们不仅要了解部分接口,同时也要知道代码的含义及其编写,改进
芯片引脚对应图
(这幅图对于我们理解代码以及明白交互式有非常重要的作用)
数码管与发光二极管硬件电路图
由实验现象可以知道,是从L7-L0依次闪烁的,而观察其电路图(上面第二幅图)P0 的八个接口对应了8个发光二极管L0-L7的阳极,而他们拥有共同的阴极,且通过反相器,其端口为P2.3,且信号为LED_SEL
发光二极管具有单向导通性,(即阳极为高,阴极为0才会导通发光,然而有个反相器,故LED_SEL(P2.3)应为高电平)
之后我们可以设置其P0的8个端口来设置哪个灯亮
既然这样
我们好好了解一下P0口的设置及工作原理吧!
STC系列芯片有5组8位输入口,分别为P0到P5,其中P5口仅P5.0~P5.5用于输入输出。
STC芯片的所有I/O口都可以配置为四种工作模式之一:
STC15系列单片机上电复位后为准双向口/弱上拉工作模式
注意:
每个I/O口的工作模式由2个控制寄存器中的相应位控制(PnM0和PnM1,n=0、1、2、3、4、5)。也就是说P0口的具体工作模式由P0M0和P0M1控制。
可参看下表
P0M1[1: 0]该寄存器地址为93H | P0M0[1: 0]该寄存器地址为94H | I/O口模式 |
---|---|---|
0 | 0 | 准双向口,灌电流可达20mA,拉电流为270微安,由于制造误差,实际为270-150 |
0 | 1 | 推挽输出(可达20mA,要加限流电阻) |
1 | 0 | 仅为输入(高阻) |
1 | 1 | 开漏,内部上拉电阻断开,其模式即可读外部状态,也可对外输出(高或低电平),e.g.要正确读取外部状态或需要对外输出高电平,需外加上拉电阻,否则读不到外部状态,也对外不输出高电平 |
具体模式说明:
准双向口
真正的双向口指的是具有输入和输出两种模式的端口,在不同模式之间需要进行转换;如果从输入改为输出,需要对某些控制寄存器进行设定,才能完成
而51系列单片机的I/O口线在输入和输出之间没有明确的模式区别。相应端口在同样模式下,既可以作为输入,又可以作为输出。P3口除外,因为它需要连接外设
51单片机的I/O口如果要读必须先写1才可以,因此称为“准”双向口
需要大电流高电平输出能力的场合和高速场合不能使用该模式
推挽电路输入输出(push-pull)模式
推挽电路的输出端好像有两个“臂”(两组放大元件),一个“臂”的电流增加时,另一个“臂”的电流则减小,二者的状态轮流转换。对负载而言,好象是一个“臂”在推,一个“臂”在拉,共同完成电流输出任务
该电路模式的主要作用是增强驱动能力,为外部设备提供大电流,可以直接输出高电平电压
输入/高阻模式
仅用于输入
开漏电路
I/O口的开漏就是没有连接上拉电阻
故对于流水灯
需要将P0的8个引脚和P2.3都设置为推挽输出
下面解析其代码(我们后面将详细涉及其代码的编写)
一个UV文件都涉及三个基础文件,但其放置的对应子文件夹不同,我们现在只解析.c版本的代码(因为我学的是C++)
设计思路
为点亮发光二极管
即需要三大函数(初始化设置端口,延时,主函数的永真循环)
可参照P0的工作模式表,编写这部分代码
void Init(){
P0M1=0x00;
P0M0=0xff;
//p0口设置为推挽输出,16进制
//改写为2进制,就表示M1中8位全为0,M0中8位全为1
//即我们需要的P0口的八位均为推挽输出
P2M1=0x00;
P2M0=0x08;
//P2.3口也需设置推挽输出,
//其余默认为准双向口
//即P2的第4位设定为1即表示为16进制08
led_sel=1;
//即p2.3端口一直为高电平,保证发光二极管会亮
}
故我们先来了解单片机是如何控制延时的
单片机工作时,是在统一的时钟脉冲控制下有序进行的。
这个脉冲是由单片机控制器中的时钟电路产生的。
时钟电路由振荡器和分频器组成,
振荡器产生基本的振荡信号,然后进行分频,得到相应的时钟。振荡电路通常有内部振荡和外部振荡两种方式。
STC15F2K60S2单片机内部集成高精度R/C时钟,工作时钟可以使用内部振荡器或者外部晶体振荡器(简称晶振)产生的时钟。外部振荡信号通过内部时钟电路,经过分频,得到相应的时钟信号。
我们下面了解一些基本的概念
振荡周期:
晶体振荡器的周期。
状态周期:
振荡信号经二分频后形成的时钟脉冲信号,用S表示。
一个状态周期的两个振荡周期作为两个节拍分别称为节拍P1和节拍P2。
P1有效时,通常完成算术逻辑操作;
P2有效时,一般进行内部寄存器之间的传输。
机器周期:
完成一个基本操作所需的时间称为机器周期。
一个机器周期包含6个状态周期,用S1、S2、….、S6表示;共12个节拍,依次可表示为S1P1、S1P2、S2P1、S2P2、……、S6P1、S6P2。
指令周期:
CPU执行一条指令所需要的时间。
CPU执行指令是在时钟脉冲控制下一步一步进行的,由于指令的功能和长短各不相同,因此,指令执行所需的时间也不一样。
一个指令周期通常含有1~4个机器周期。
举个例子
若MCS-51单片机外接晶振为12MHz时,则单片机的四个周期的具体值为:
振荡周期=1/12MHz=1/12μs=0.0833μs
时钟周期=1/6μs=0.167μs
机器周期=1μs
指令周期=1~4μs
单片机晶体振荡器M的频率可以在4MHz~48MHz之间选择,典型值是11.0592MHz(因为使用这个频率的晶振可以准确地得到9600bits/s和19200bits/s的波特率)
后面11.0592MHz这个数值我们将频繁遇到
既然我们已经知道指令周期等数值,那如果我们期望延迟200ms,那是不是要通过计算才能确定我们代码实际需要执行多少次的空指令呢
代码如下:
void Delay200ms() //@11.0592MHz
{
unsigned char i, j, k;
//_nop_();
//_nop_();
i = 9;
j = 104;
k = 139;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
不了解的人可能不能明白为啥200ms是这样的,也更不理解这是计算出来的结果
故我们还是用高端一点的方式来实现延迟吧!
还是我们的这个窗口,但是我们很明显的看到我们选择的已经不是原来的菜单栏了,而是我们的软件延时计算器(往右查找即能找到)
已经确定其系统频率为我们熟悉的数字
然后就是我们自己设定的延迟时间了
点击生成自己熟悉的代码,再复制粘贴到自己的源代码中
一切就大功告成了!
void main()
{
Init();
//调用初始化函数部分
led=0x01;
//设置最低位为高电平
while(1) //永真循环
{
P0=led; //设置L0灯亮
delay_ms(200); //调用延时函数
if(led==0x80) //如果最高位亮,设置最低位为高电平
led=0x01;
else
led=led<<1; //否则左移一位
}
}
看到这我们就恍然大悟啦,整个程序实现并不困难,那我们后面就慢慢接触难一点的代码原理,并尝试写出来吧!