普中科技A3开发板
吐槽一下客服给的代码是真的辣鸡
都没备份他们的代码 直接覆盖了
一、LED
二、蜂鸣器
三、动态数码管
四、独立按键
五、矩阵按键
六、I/O拓展–74HC595
七、LED点阵
八、直流电机
九、外部中断
十、定时器
十一、EEPROM-IIC总线
单灯点亮:P2^n ,n∈[0,7]。分别对应8个灯
#include "reg52.h"
sbit led1=P2^0;
sbit led2=P2^1;
sbit led8=P2^7;
void main()
{
led1=0;
led2=0;//0011 1110
led8=0;//第1、2、8灯亮
while(1){}
}
多灯点亮:给P2赋值,将其转换为8位二进制
#include "reg52.h"
#define led P2
void main()
{
led=17; //第1、5灯熄灭
while(1){} //17对应二进制的 0001 0001
} //对应led灯位置 即为 1000 1000
//1、5位为高电平
Q1:如果led=0x11,哪个灯点亮?
A1:当然效果一样。十进制是17,十六进制是0x11。
Q2:对于单灯点亮的例子,用多灯点亮的方式,给P2赋值什么?
A2:第1、2、8灯亮,即0011 1110,倒推回去,0111 1100的十六进制为0x7c,效果一样。
一开始我是转化成二进制,然后从后看,这样1就对应了熄灭的灯(0对应点亮)的位置。
其实在正面 从右往左看,不就是从后面 由左向右吗?当然也可以把板子倒过来,自己能分清是哪个灯就可以。
也就是:
P2.0的数值(第一个灯)对应二进制的最低(右)位;
P2.7的数值(第八个灯)对应二进制的最高(左)位。
闪烁:定义并使用delay(延迟)函数,delay只是个习惯命名,完全可以取别的,这里延迟时间可以自定义。
#include "reg52.h"
sbit led=P2^0; //选择第1个灯
void delay(int i)
{
while(i--);
}
void main()
{
while(1)
{
led=0;
delay(50000); //大约延时450ms
led=1;
delay(50000); //大约延时450ms
}
}
这里每次输入时间参数有些繁琐,如果你多次要用延迟1s、2s,可以这样定义并调用。其他时间以此类推,时间延迟的间隔只是大概,并不精准。
void delay_1s()
{
while(100000--);
}
void delay_2s()
{
while(200000--);
}
delay_1s(); //延迟一秒
delay_2s(); //延迟两秒
流水灯
#include "reg52.h"
#include "intrins.h"
#define led P2
void delay(int i)
{
while(i--);
}
void main()
{
int i;
led=~0x01;
delay(50000);
while(1)
{
for(i=0;i<8;i++)
{
led=~(0x01<<i); //效果等同于led=_crol_(led,1);
delay(50000); //注意:该方法(_crol_)要用intrins库
}
}
}
注意:
led=~0x01; // 1111 1110
_crol_(led,1); //结果为 1111 1101
led=~0x01; // 1111 1110
led<<1; //结果为 1111 1100
可以理解为:
第一种移动后高位补低位
第二种移动后只补0
而我用<<实现同样的效果,是这样一个过程。
0000 0001→→→→0000 0010→→→→1111 1101
<<1(左移1) ~(取反)
_cror_(led,2) 即为右移2位 移动几位由第二个形参决定
返回顶部
#include "reg52.h"
sbit beep=P2^5;
void delay(int i)
{
while(i--);
}
void main()
{
while(1)
{
beep=~beep;
delay(100); //通过修改此延时时间达到不同的发声效果
}
}
可以演奏音乐,并不难,自行探索。
返回顶部
一、位选:选择你要第几个数码管点亮
对P2^4、3、2的二进制
取反后+1即为对应第n个灯
#include "reg52.h"
sbit LSA=P2^4;
sbit LSB=P2^3;
sbit LSC=P2^2;
LSA=1;LSB=0;LSC=0; //显示第4位
当时按普中的程序走了个小弯路。。。
首先将8个数码管看作0-7个
LSA=1;LSB=1;LSC=1; //显示第0个
LSA=0;LSB=1;LSC=1; //显示第1个
LSA=1;LSB=1;LSC=0; //显示第4个
如何知道是第几个数码管?
旧方法:对110 取反得001 从后看 得对应二进制数值 100(十进制4) 则显示第4位。其实完全可以先定义LSC=0 ,也可以把sbit LSA=P2^4。
无论如何,怎么方便怎么来。这样只需取反,不必从后看(从右向左看)了
二、段选:
如果我要表示sdau四个字母。
以s为例子(其实s也就相当于5)
由于数码管表示的"8"是由7段 以及右下角一个小数点组成
我要点亮AFGCD,则
.(dp) G F E D C B A
对应 0 1 1 0 1 1 0 1 二进制表示
十六进制为6d
位选后将P0赋值为6d即可
#include "reg52.h"
sbit LSA=P2^4;
sbit LSB=P2^3;
sbit LSC=P2^2;
void delay(int i)
{
while(i--);
}
void main()
{ while(1){
LSA=1;LSB=1;LSC=1; //显示第1位
P0=0x6d; //赋值段码
delay(100); //延迟
P0=0x00; //消隐
LSA=1;LSB=1;LSC=0; //显示第2位
P0=0x5e;
delay(100);
P0=0x00;
LSA=1;LSB=0;LSC=1; //显示第3位
P0=0x77;
delay(100);
P0=0x00;
LSA=1;LSB=0;LSC=0; //显示第4位
P0=0x3e;
delay(100);
P0=0x00;
}
}
三、延迟与消隐:
应用如上图,缺一不可,否则造成问题。
1、不消隐位选灯重叠或混乱
2、不delay延迟导致亮度暗
PS:要写入while循环,否则或许第一个灯全段点亮。(至少我的板子如此)
代码重复的地方可以写入for循环,将不同的内容依次放入数组,如下依次显示0-7
#include "reg52.h"
sbit LSA=P2^4;
sbit LSB=P2^3;
sbit LSC=P2^2;
int code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,
0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~9和A~F的值
void delay(int i)
{
while(i--);
}
void main()
{
while(1)
{
int i;
for(i=0;i<8;i++)
{
switch(i) //位选,选择点亮的数码管,
{
case(0):
LSA=1;LSB=1;LSC=1; break;//显示第1位
case(1):
LSA=1;LSB=1;LSC=0; break;//显示第2位
case(2):
LSA=1;LSB=0;LSC=1; break;//显示第3位
case(3):
LSA=1;LSB=0;LSC=0; break;
case(4):
LSA=0;LSB=1;LSC=1; break;
case(5):
LSA=0;LSB=1;LSC=0; break;
case(6):
LSA=0;LSB=0;LSC=1; break;
case(7):
LSA=0;LSB=0;LSC=0; break;//显示第7位
}
P0=smgduan[i];//发送段码
delay(100); //间隔一段时间扫描
P0=0x00;//消隐
}
}
}
返回顶部
delay延时是为了消除抖动的影响,如图,可以理解为直接跳过抖动部分(10ms~20ms)。否则可能0的瞬间又到了1,最后稳定又到了0,进行了两次取反,由于太快看不到,最终好像没有效果。
以下代码效果为:按下时,第一个灯取相反状态,松开时,第二个灯取相反状态。(两个等初始状态为高电平1熄灭)
#include "reg52.h"
sbit key1=P3^1; //定义第一个独立按键为key1
sbit led1=P2^0; //定义第1个LED灯
sbit led2=P2^1; //定义第2个LED灯
void delay(int i)
{
while(i--);
}
void keypress()
{
if(key1==0) //检测按键key1是否按下
{
delay(1000); //消除抖动 一般大约10ms
if(key1==0) //再次判断按键是否按下
{
led1=~led1; //led1状态取反
}
while(!key1); //检测按键是否松开
if(key1==1) //检测按键key1是否松开
{
delay(1000);
if(key1==1)
{
led2=~led2;
}
}
}
}
void main()
{
while(1) //一直进行检测
{
keypress(); //按键处理函数
}
}
至于不消抖的后果,本人亲测,疯狂按键测试。会有很大几率误判,总之,消抖就对了。
返回顶部
先给P1口赋值0x0f(0000 1111)
按下按键变成0x0b(0000 1101)
我们把0x0b定义为第二列
这样就可以知道按键在第二列
先给P1口赋值0xf0 (1111 0000)
按下按键变成0xb0 (1011 0000)
我们把0xb0定义为第二行
这样就可以知道按键在第二行
导线有一端为0则点位为0。所以不会变成1111 0100
#include "reg52.h"
#define GPIO_DIG P0
#define MATRIX_KEY P1
int KeyValue; //用来存放读取到的键值
int code smgduan[17]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,
0x88,0x83,0xc6,0xa1,0x86,0x8e};//显示0~9和A~F的值(共阳极)
void delay(int i)
{
while(i--);
}
void KeyDown(void)
{
char a=0;
MATRIX_KEY=0x0f;
if(MATRIX_KEY!=0x0f) //检测按键是否按下
{
delay(1000); //延时10ms进行消抖
if(MATRIX_KEY!=0x0f)//再次检测键盘是否按下
{
//检测列
MATRIX_KEY=0X0F;
switch(MATRIX_KEY)
{
case(0X07): KeyValue=0;break;
case(0X0b): KeyValue=1;break;
case(0X0d): KeyValue=2;break;
case(0X0e): KeyValue=3;break;
}
//检测行
MATRIX_KEY=0XF0;
switch(MATRIX_KEY)
{
case(0X70): KeyValue=KeyValue;break;
case(0Xb0): KeyValue=KeyValue+4;break;
case(0Xd0): KeyValue=KeyValue+8;break;
case(0Xe0): KeyValue=KeyValue+12;break;
}
}
}
while((a<50)&&(MATRIX_KEY!=0xf0)) //检测按键松手检测
{
delay(100);
a++;
}
}
void main()
{
while(1)
{
KeyDown(); //按键判断函数
GPIO_DIG=~smgduan[KeyValue]; //按位取反 变为共阴极对应代码
}
}
返回顶部
学习过程中看到的每篇文章都提到过它的特点:串行输入,并行输出。
究竟是什么意思?
有一篇文章将其比作手枪弹夹和霰弹枪
上膛是弹夹串行输入,而霰弹枪发射则是并行输出
比喻很生动,但又缺少一些解释,所以再加一些我自己的理解。
切记,OE要接GND才有效。
剩下的3个脚可以看作…
引脚 | 比喻 | 上升沿作用 |
---|---|---|
SER (P34) | 子弹 | \ |
SRCLK (P36) | 装弹 | 子弹塞入弹夹,之前的子弹下移 |
RCLK (P35) | 扳机 | 发射8颗子弹 |
装弹与扳机都是上升沿有效。
那么,究竟什么是上升沿有效?
其实就是,当电位由低到高,就会激发作用。
相反,还记得独立按键吗?从高到低的过程,就是下降沿。(不记得点我)
但它不用延迟,为什么?因为它不是按键,没有抖动(maybe),理想下这样。
Q:我们如何发送一个字节呢?
A:一个字节有八位,看作8个子弹。最高位为第一颗子弹,最低为为第八颗。如,0111 1111 第一个子弹为0。
先装入最高位。这个过程跟进栈类似。
每次要给SER赋值0或1,也就是让这颗子弹为0或1。然后SRCLK就是装弹。
如此循环,8颗装完之后,使用RCLK发射!
要注意,SRCLK和RCLK都要经历一个过程,即电平由低到高的变化,才能实现。
以下代码直接引用普中科技的, 我只修改了注释和一些变量名, 没有进行精简。
这里例程为矩阵led效果展示,请先学习下一节。
#include "reg51.h"
#include "intrins.h"
char ledNum;
//--定义使用的IO口--//
sbit SRCLK=P3^6; //定义:扳机
sbit RCLK=P3^5; // 装弹
sbit SER=P3^4; // 弹夹
sbit LED=P0^7;
void delay(int i)
{
while(i--);
}
void Hc595SendByte(char dat)
{
char i;
SRCLK = 1;
RCLK = 1;
for(i=0;i<8;i++) //发送8位数
{
SER = dat >> 7; //子弹准备塞入弹夹
dat <<= 1; //第一颗已经填入,把第一位丢掉,到最后一位,这里是位运算,如1234 5678 变为 2345 6780
SRCLK = 0; //先变为低电平
_nop_();
_nop_();
SRCLK = 1; //从低到高上升沿,填入子弹,第一颗子弹下移。
//如此循环,填入8个子弹。
}
RCLK = 0;
_nop_();
_nop_();
RCLK = 1; //发射
}
void main()
{
LED=0;
ledNum = ~0x01; //1111 1110 最下面熄灭
while(1)
{
Hc595SendByte(ledNum); //发送数据 点亮LED灯
ledNum = _crol_(ledNum, 1);//变为1111 1110 以此类推
delay(50000);
}
}
实验现象:矩阵LED灯从下往上熄灭,类似流水灯效果
返回顶部
例如左下角点亮,只需让ROW8=1(高电平),ROW1到ROW7为0 。 COLUMN1=0(低电平),COLUMN2到8为0或1(二极管导电特性)。
点亮列COLUMN:由P0口控制
使第一列亮,可以P0^7=0
P0^7对应二进制最左/高位(参考点亮二极管)
或P0=0x7f(0111 1111)即可。
直观:最高位控制最左边一列
点亮行ROW:由74Hc595输出口控制
输入0xfe
输出1111 1110
二级制从左到右
对应ROW由上到下
此时左下角为熄灭
#include "reg51.h"
#include "intrins.h"
sbit SRCLK=P3^6;
sbit RCLK=P3^5;
sbit SER=P3^4;
sbit LED_Column_1=P0^7;
void Hc595SendByte(char dat1)
{
char a;
SRCLK = 1;
RCLK = 1;
for(a=0;a<8;a++)
{
SER = dat1 >> 7;
dat1 <<= 1;
SRCLK = 0;
_nop_();
_nop_();
SRCLK = 1;
}
RCLK = 0;
_nop_();
_nop_();
RCLK = 1;
}
void main()
{ LED_Column_1=0; //使第一列为低电平。
while(1)
{
Hc595SendByte(0xfe);
}
}
进阶:
显示数字零
#include "reg51.h"
#include
sbit SRCLK=P3^6;
sbit RCLK=P3^5;
sbit SER=P3^4;
char ledduan[]={0x00,0x00,0x3e,0x41,0x41,0x41,0x3e,0x00};
char ledwei[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};
void delay(int i)
{
while(i--);
}
void Hc595SendByte(char dat)
{
char a;
SRCLK=0;
RCLK=0;
for(a=0;a<8;a++)
{
SER=dat>>7;
dat<<=1;
SRCLK=1;
_nop_();
_nop_();
SRCLK=0;
}
RCLK=1;
_nop_();
_nop_();
RCLK=0;
}
void main()
{
char i;
while(1)
{
P0=0x7f;
for(i=0;i<8;i++)
{
P0=ledwei[i]; //位选
Hc595SendByte(ledduan[i]); //发送段选数据
delay(100); //延时
Hc595SendByte(0x00); //消隐
}
}
}
返回顶部
一端接地,一端接5V就能转。
可以接一个控制口,如下图所示。
#include "reg52.h"
#include
sbit moto=P1^0; //控制步进电机模块的01口
void delay(u16 i)
{
while(i--);
}
void main()
{
while(1)
{
char i;
for(i=0;i<100;i++) //循环100次,也就是大约5S
{
moto=1; //开启电机
delay(5000); //大约延时50ms
}
for(i=0;i<100;i++) //循环100次,也就是大约5S
{
moto=0; //关闭电机
delay(5000); //大约延时50ms
}
}
}
返回顶部
一直没看明白普中给的例程,不是看不明白代码,而是根本感觉不到中断的作用!先放一下他们的代码。
#include "reg52.h"
sbit k3=P3^2; //定义按键 K3
sbit led=P2^0; //定义 P20 口是 led
void delay(u16 i)
{
while(i--);
}
void Int0Init()
{
//设置 INT0
IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开 INT0 的中断允许。
EA=1;//打开总中断
}
void main()
{
Int0Init(); // 设置外部中断 0
while(1);
}
void Int0() interrupt 0 //外部中断 0 的中断函数
{
delay(1000); //延时消抖
if(k3==0)
{
led=~led;
}
}
所以,为什么要用中断?这和我用if判断k3是否按下,然后,对led灯操作有什么区别?根本无法体现中断的优越,就这个代码困扰了我半天,让我一直觉得中断就仅仅是个函数。
Q:到底什么是中断?
A:就是让单片机暂停执行main()函数而去执行"中断函数"。
既然让单片机暂停,那必须给它一个信号。
来看看他们的PPT怎么说的。
(P3.2)可由IT0(TCON.0)选择其为低电平有效还是下降沿有效。
当CPU检测到P3.2引脚上出现有效的中断信号时,中断标志IE0(TCON.1)置1,向CPU申请中断。
好了,还是看我解释吧。
IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开 INT0 的中断允许。
EA=1;//打开总中断
首先,EX0和EA暂且当做两个开关,都打开,才能用中断。
IT0则是,监听这个信号的方式。
又要去看独立按键那了
(点我去看)
IT0=1;
每次一个下降沿就是一个信号
IT0=0;
低电平是一个信号
不想多说了,看一下我修改的代码,感受一下什么是中断吧。
根本无需定义k3按键,而且采用IT0=1为什么还要消抖?真的无法理解。
#include "reg52.h"
sbit led=P2^0;
void main()
{
//设置 INT0
IT0=1;//跳变沿出发方式(下降沿)
EX0=1;//打开 INT0 的中断允许。
EA=1; //打开总中断
while(1);
}
void Int0() interrupt 0 //外部中断 0 的中断函数
{
led=~led;
}
回去看看普中的,这个是不是简单明了。
返回顶部
振荡脉冲频率为12MHz时,那么它的时钟周期为1/12MHz,一个机器周期=时钟周期X12=1μs。
我的板子振荡脉冲频率11.018398MHz,那么它的时钟周期为1/11.018398MHz,一个机器周期=12/11.018398MHz≈1.08μs
所以为了方便,用12MHz举例。
这有两种方式计时:
第一种,计数位数时16位,由TH0作为高八位,TL0 作为低八位,组成16位加一计数器。
在TR0 =1后单片机开始计时,每经过一个机器周期单片机输出一个脉冲使定时器加一,加到16位全为1时会溢出,使TF0 =1,利用此性质可以去执行相应的功能。
TF0=1当做一个信号。类似于上一节P3^2中断里,将它的下降沿作为信号。
设置初值:
我们要的时间间隔都是1s、2s之类,那么,我们让开始计时到TF0 置1这个时间间隔变成1ms,然后for循环1000次,以此实现计时1s。
若 高八位 TH0 和 低八位 TL0 的初值都为0(即00000000 00000000),当16位全为1时,单片机一共经历了65536个机器周期,时间经过了65536μs。
以单次定时1ms(1000μs)为例。假设16位定时器的初值为x,由于单片机定时固定到达65536溢出,那么(65536-x)*12/12M = 1000,可计算得到x = 64536。
对应16进制为0x3cb0。那么高八位TH0 = 0x3c, 低八位TL0 = 0xb0。
#include "reg52.h"
sbit led=P2^0; //定义P20口是led
void Timer0Init()
{
TMOD|=0X01; //选择为定时器0模式,工作方式1,仅用TR0打开启动。
TH0=0XFC; //给定时器赋初值,定时1ms
TL0=0X18;
ET0=1; //打开定时器0中断允许
EA=1; //打开总中断
TR0=1; //打开定时器
}
void main()
{
Timer0Init(); //定时器0初始化
while(1);
}
void Timer0() interrupt 1
{
static int i;
TH0=0XFC; //给定时器赋初值,定时1ms
TL0=0X18; //11111100 00011000 (64536)
//65536-64536=1000μs
i++;
if(i==1000) //循环1000次 为1000ms=1s
{
i=0;
led=~led;
}
}
实现效果:led灯持续点亮1s,熄灭1s.
#include "reg52.h"
char code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};//显示0~F的值
char n=0;
void Timer1Init()
{
TMOD|=0X10; //选择为定时器1模式,工作方式1,仅用TR1打开启动。
TH1=0XFC; //给定时器赋初值,定时1ms
TL1=0X18;
ET1=1; //打开定时器1中断允许
EA=1; //打开总中断
TR1=1; //打开定时器
}
void delay(int i)
{
while(i--);
}
void main()
{
Timer1Init(); //定时器1初始化
while(1);
}
void Timer1() interrupt 3
{
int i;
TH1=0XFC; //给定时器赋初值,定时1ms
TL1=0X18;
i++;
if(i==1000)
{
i=0;
P0=smgduan[n++];
if(n==16)n=0;
}
}
实验效果:第一位数码管进行16进制计时
返回顶部
返回顶部