一、基础知识&点亮LED灯
1 byte(字节)=8 bit(位)
编码表,使用编码表程序会放在程序存储器中,也可以指定内存位置,不用编码表的话会放在RAM里,RAM内存是有限的
二、流水灯、蜂鸣器继电器
有一个用来控制流水灯的循环函数(之后补),流水灯:循环左移
锁存器
OE:Output Enable,上面的——代表低电平有效
LE:锁存器的所存端
Z:高阻态,介于高电平和低电平之间的状态
DB1-DB8接在P1口上,点灯的时候,给DIOLA一个高电平,让锁存器保持在直通状态,D是什么Q就是什么,但是DB1-DB8同时也和AD转换芯片接在一起了,在用AD转换的时候就需要用锁存器锁住转换出的值以便控制led灯
三、数码管的显示原理
数码管显示原理
U1控制段选,U2控制位选
复位电路
晶振
四、数码管的动态显示&中断
TCON
中断三条代码没对上
C52中中断请求由TCON的IT0控制
原因是低电平触发是默认的,单片机上电后都是低电平,中断请求可以不用写
如果是跳变沿触发,就需要设置IT0,就和中断条件对应上了
中断函数(不需要声明)
无返回值 函数名 interrupt 中断序号
定时器
TH0和TL0里如何存放数据? 为什么除以256?
方式一是16位定时器,所以一共可以放2^16也就是65536个数,最终对应65ms
我们想要的50MS在加上一个多少就能等于65536,也就是65536和50000的差值,这个值就是我们说的初值。也就是说定时器里存放的是初值。
除以256是因为2^8=256,低8位满一次高8位加1,那么高8位装多少,低8位就满了几次,算法就是用差值除一下256取其整数,剩下的,就是还剩了小于256次数的就放在低8位,就是差值 的余数。如果我要放一个超过256的数,肯定是超过先放高八位,把剩下的部分放在低八位中了,TH0=(65536-50000)/256就是把TL0中放不下的部分放在TH0中,再把剩下的部分放在TH0中。
假设我们要50ms进行一次中断,TH0=(65536-50000)/256
TL0=(65536-50000)%256
为什么再装初值?
如果只装一次初值的话,当定时器记满清零后,初值就为零了,为了保证时间还是我们想要的,所以需要再装初值。
如果想要1s怎么办呢?
main函数里有一个变量tt,让tt自加1,在主程序检测tt是否为20,当tt=20的时候,定时器走一次50ms,这样就是1s了
中断函数后的using
现象:流水灯在工作,中断里的数码管很久才动一下
这是为什么呢?
在延时函数里的时候就已经到了中断的时间了,错过了进入中断的时间
中断从TR0=0开始记两秒,很有可能程序还在delay中的时候aa已经到了40,
假设从中断开始计时到delay,aa刚好是20,到if会多出if之前两条程序的时间,从第一次到if开始就不是正确的时间,后面我再走20个中断也是从岔开的时间开始走,那么在走完delay来到if判断的时候必然进不去数码管的程序,只有在恰好等于40的时候才会走数码管的程序
所以不将if判断放在主函数内,delay会有影响,放到终端函数里,到时间直接走数码管程序
中断:如果到了两秒,数码管就显示对应的数,不到就执行主函数里的流水灯
五、独立键盘&矩阵键盘
独立键盘
键盘是有抖动的,按下一次大概有20us,当按下的时候,由于抖动,在这个时间内,num已经++好几次了,此时按键是一直按着的,所以num会一直在这个if里++,等松手来到else以后,num其实已经不止++一次了。所以为了避免抖动,在松手之前,让程序一直在while里循环,这样可以保证num只++一次
为了避免按下松开时的抖动,需用延时函数进行消抖
矩阵键盘
原理没写,可能需要理解一下
再加一嘴,到第九讲的时候郭天祥说按下检测没写,可以从这直接跳到第九讲看完,不然后面可能会忘了。
为什么判断两遍?
六、AD/DA的工作原理
1.DA
D7=1时,D7后面所有电阻为2R,与D7上的电阻均分电流,所以I7=I/2,而I6又以同样的方式均分I7,所以I6=I7/2=I/4,后面所有电流同理
时序图:
网格部分代表无所谓,两个箭头包起来的部分表示有效数据
什么是数字量,什么是模拟量?
数字量是脉冲,只有01两种,模拟量是电流电压
DA灯由暗到亮,在由暗到亮思路,定时器内P0由0++到0xff;再由0xff--到0
2. AD
是I7+I6的和和VIN比吗?还是一位一位的比呢
先转换CS=0,WR给一个低脉冲(1->0->1)
在读数据,CS=0,RD也给一个低脉冲(1->0->1)
郭天祥代码思路:首先看AD0804这个芯片,把转换完的数据通过DB口传到锁存器,这个锁存器正好还和LED连着,如下图
为什么要初始化P0?
因为锁存器D0-D7是和P0口连着的
为什么是0x7f?
要进行AD转换,首先要片选,设置SCAD的值,CSAD是和D7连在一起的,将SCAD设置为低电平
作业2:AD转换完后的数据换成二进制在数码管前三位显示
段选的值就是转换后的数据,那转化后的数据怎么获取呢?锁存器的值怎么给P0呢?
init(){
P0=0x7f;//CSAD置零
}
strat(){
wr=1;
wr=0;
wr=1;//wr一个低脉冲开始转换
}
void main(){
init();
while(1){
strat();
delay(5);
rd=0;//开始读
delay(20);
rd=1;
delay(20);
temp=P0;//例程里都没有设置LED灯,我觉得是因为LED直接和锁存器连着,那数码管也是直接和
锁存器连着的啊,那也不用选吗,先暂且接受一下P0的数据吧
wela=1;
P0=0x1f;//前三个数码管
wela=0;
dula=1;
P0=temp;
dula=0;
}
}
作业3:AD的值读回来送给DA
DA和AD的CS与52的接口不是同一个位置,DA是P3.2,AD是在锁存器上,需要设置P0口来将CS置零。
init(){
P0=0x7f;//CSAD置零
}
strat(){
wr=1;
wr=0;
wr=1;//wr一个低脉冲开始转换
}
void main(){
init();
while(1){
strat();
delay(5);
rd=0;//开始读
delay(20);
rd=1;
delay(20);
temp=P0;
scda=0;
wr=0;
P0=temp;
while(1);
}
七、串口通讯原理
举例:P0口发送八位数据
八位收起以后记为一个字节
异步通信:
同步通信(了解)
奇偶校验
单片机接收信号程序
法一(查询法):单片机接收到数据以后,TI会置位,查询TI是否置位,手动给TI清零(郭天祥是这 么说的,但是接收用什么TI啊,不应该是用RI吗,气死我了,后面他代码里写的也 是RI,破案了,就是RI)这种方法主要利用下图所讲的原理
(1)确定T1的工作方式:串口通讯的话TMOD要选择工作方式2(8位自动重装定时/ 计数器)
为什么是0x20?
高四位是T1,高四位的M0,M1选择10的方式 0010 0000
(2)计算T1的初值,装载TH1,TL1,波特率前面算过了,9600Bd对应的TH1=0xfd;
TH1是常数寄存器
(3)确定串行口控制,将SM0,SM1的工作方式为方式一01,也就是十位异步收发 器
(4)根据上图原理,首先得将REN置一
(5)启动定时器,TR1=1;
(6)说下原理,当RI=1的时候,像CPU请求中断,我们查询的就是RI是不是等于 1,请求中断后,需手动清零,当RI=0的时候,就将接收到的数据装入SBUF 里,并用P1接收了SBUF的值
#include <reg52.h>
void main(){
TMOD=0x20;
TH1=0xfd;
TL1=0xfd;
SM0=0;
SM1=1;
REN=1;
TR1=1;
while(1){
if(RI==1){
RI=0;
p1=SBUF;
}
}
}
为什么用P1接收SBUF?
去看芯片原理图,流水灯接的是DB0-7,在52上对应P1口,
数码管接的是D0-7,在52上是P1口
法二(中断法):TI置位后会像单片机申请中断,利用中断函数自动置位
(1)确定T1的工作方式:串口通讯的话TMOD要选择工作方式2(8位自动重装定时/ 计数器)
(2)计算T1的初值,装载TH1,TL1,波特率前面算过了,9600Bd对应的TH1=0xfd;
TH1是常数寄存器
(3)确定串行口控制,将SM0,SM1的工作方式为方式一01,也就是十位异步收发 器
(4)得将REN置一
(5)打开总中断和串口中断
(6)启动定时器,TR1=1;
(7)写一个中断函数,让RI=0,SBUF里有数据,让P1接收数据
#include <reg52.h>
void main(){
TMOD=0x20;
TH1=0xfd;
TL1=0xfd;
SM0=0;
SM1=1;
REN=1;
TR1=1;
EA=1;
ES=1;//串口中断
while(1);
}
}
void serial() interrupt 1 {
RI=0;
p1=SBUF;
}
发送数据程序
#include <reg52.h>
uint8_t flag,a;
void main(){
TMOD=0x20;
TH1=0xfd;
TL1=0xfd;
SM0=0;
SM1=1;
REN=1;
TR1=1;
EA=1;
ES=1;//串口中断
while(1){
if(flag==1){
flag=0;
ES=0;
SBUF=a;
while(!TI);//郭天祥说判断是否发完,只要发了不就是1了吗,这行代码有必要吗
TI=0;
ES=1;
}
}
}
}
void serial() interrupt 1 {
RI=0;
SBUF=a;
flag=1;
}
while(!TI);如果不写ES=0呢,直接写TI=0能不能达到同样的效果?
液晶没看,我之后的板子用不到,就没看,之后有时间在补吧,这个笔记里还有我未解决的疑问,也要等到后面有空在解答了,或者谁帮我解答一下
九、IIC
先传最高位
地址:7位地址位+1位方向位
不管应不应答,主机要从从机接收数据了
需要延时函数
移位操作:
左移时,最低位补零,最高位一如PSW的CY位;PSW:状态寄存器 CY位:溢出位
右移时,最低位移除,最高位补零
起始位,设备地址,写入的地址,数据,stop
起始位,设备地址,数据,stop
随即地址读需要虚拟写入,因为先要告诉它哪个内部寄存器是你想要读取的
为什么需要虚拟写入?
因为不仅要指定读取的从机设备,还要告诉从机是从哪个位置开始读的,所以要把位置写入到从机
起始位,从机地址,读取的位置地址,在一个开始,从这个开始主机要从写变为读,所以再一个从机地址,后面是所要读取的数据
#include<reg52.h>
typedef unsigned char uint8_t
typedef unsigned int uint32_t
sbit SDA=P2^0;
sbit SCL=P2^1;
uint8_t i=0,temp;
void Mydelay(int k){//about 5us
for(i=0;i++;i<k){
for(j=0;j++00;j<200)
}
}
void IICStrat(){
SDA=1;
Mydelay(50);
SCL=1;
Mydelay(50);
SCL=0;
}
void IICStop(){
SDA=0;
Mydelay(50);
SCL=1;
Mydelay(50);
SDA=1;
}
void IICRespond(){
//如何确定第九个时钟让ACK=1
//ACK这个变量就是数据线上的,ACK=1就是SDA=1
SCL=1;
Mydelay(50);
while((SDA=1)&&(i<250))i++;//郭天祥的经验,i到250差不多就是第九个时钟
SCL=0;
Mydelay(50);
}
void init(){//初始化,总线状态可以任意定义,但是一般定义为1,方便后续操作
SCL=1;
SDA=1;
}
void write_byte(uint8_t data){
//如何完成一位一位的送数据的操作呢?
//将要传输的数据一位一位的左移送到PSW寄存器里的CY中
SCL=0;
Mydelay(50);
temp=data;
for(i=0;i<8;i++){
temp=temp<<1;
SAD=CY;//error:CY=temp;传数据的话要放到SDA上
SCL=1;
Mydelay(50);
//别忘了把SCL再置零
SCL=0;
Mydelay(50);
}
SDA=1//释放总线
Mydelay(50);
}
int read_byte(){
uint8_t temp,data;
SCl=0;
Mydelay(50);
for(i=0;i<8;i++){
SCL=1;
Mydelay(50);
SDA=1;
temp=SDA;
data=(data<<1)|temp;
SCL=0;
Mydelay(50);
}
return data;
}
void write_add(uint8_t address,uint8_t data){
//写地址
//start、从机地址、ACK=1、写入的地址、ACK=1、写入的数据、ACK=0、stop
IICStrat();
write_byte(0xa0);
respond();
write_byte(address);
respond();
write_byte(data);
respond();
IICStop();
}
void write_add(uint8_t address){
//读数据(随机读)
//start、从机地址、ACK=1、写入的地址、ACK=1、写入的数据、ACK=0、stop
uint8_t data;
IICStrat();
write_byte(0xa0);
respond();
write_byte(address);
respond();
write_byte(0xa1);
respond();
data=read_byte();
IICStop();
return data;
}
//void random_read(){
//}
void mian(){
init();
write_add(23,0xaa);
Mydelay(100);
P1=read_add(23);;
while(1);
}