单片机的整体图
这里要注意引脚分布以及P0这排端口要接上拉电阻。单片机引脚的引用
最简单的引用[block]
sbit led1=P0^0;
[/block]
定义一排管脚并使用移位函数来进行控制
[block]
#include”intrins.h”
//使用移位函数需要引入头文件
#define led P0
//这个是循环左移函数即可以把位数向左移一位 如0001 -》 0010
led=crol(led,1);
//这个是循环移右函数
led=cror(led,1);
[/block]
单片机蜂鸣器
主要分为无源蜂鸣器和有源蜂鸣器无源蜂鸣器
有源蜂鸣器
两种蜂鸣器的详解
无源蜂鸣器是没有正负之分的,类似于喇叭,只要在两个腿上加载不同的频率的电信号就可以实现发声,根据不同的频率所发出的声音也是不一样的。有源蜂鸣器是有正负之分的,只需要在两个腿上加上电压信号就会发声,发出的声音音调单一、频率固定。
有源蜂鸣器比无源蜂鸣器内部多了振荡结构,所以有源蜂鸣器在价格上稍微贵一点。单片机可以通过IO口翻转不同频率来实现发出不同音调。
如果需要用蜂鸣器进行谱曲可以使用下面的关系。
数码管
数码管的结构A是共阴数码管,因为是LED的阴极连接在一起。B是共阳数码管。
[infobox title=”共阳数码管表”]0xco 0
0xf9 1
0xa4 2
0xb0 3
0x99 4
0x92 5
0x82 6
0xf8 7
0x80 8
0x90 9
0x88 A
0x83 B
0xc6 C
0xa1 D
0x86 E
0x8e F
0xff 无显示[/infobox]
[infobox title=”共阴数码管表”]0x3f 0
0x06 1
0x5b 2
0x4f 3
0x66 4
0x6d 5
0x7d 6
0x07 7
0x7f 8
0x6f 9
0x77 A
0x7c B
0x39 C
0x5e D
0x79 E
0x71 F
0x00 不显示
[/infobox]
把我们的数据保存在ROM中
这里可以直接加上code就可以了,比如u8 code smgd={xx,xx}独立按键
独立按键这里需要注意一下消抖。我们可以使用硬件或者软件消抖的方式,目前采用软件消抖。这里我们抖动时间大概是5毫秒。比如:
[highlight lanaguage=”C”]
Void key() { If(k1==0) { Delay(1000); If(k1==0) { 这里执行相关操作 } } }
[/highlight]
矩阵按键
这里我们采用扫描的方法来判断按下了那个按键。目前有两个方法实际测试函数
IO口扩展
这里我们会讲到两个芯片:一个是74hc165用来读取数据,还有一个74hc595用来输出数据时序图
实际的代码
[highlight lanaguage=”C”]
#include "reg52.h" //此文件中定义了单片机的一些特殊功能寄存器 #include "intrins.h" typedef unsigned int u16; //对数据类型进行声明定义 typedef unsigned char u8; //--定义使用的IO口--// #define GPIO_LED P0 sbit IN_PL = P1^6; sbit IN_Data = P1^7; //数据通过P1.7脚移进单片机内处理 sbit SCK = P3^6; u8 Read74HC165(void) { u8 i; u8 indata; IN_PL = 0; //等于0载等于1把数据放入到寄存器中 _nop_(); //短暂延时 产生一定宽度的脉冲(执行一条空指令) IN_PL = 1; //将外部信号全部读入锁存器中 _nop_(); indata=0; //保存数据的变量清0 for(i=0; i<8; i++) { indata = indata<<1; //左移一位 SCK = 0; //时钟置0 _nop_(); indata |= IN_Data; SCK = 1; //时钟置1 } return(indata); } void main() { u8 h165Value; GPIO_LED = 0; while(1) { h165Value = Read74HC165(); if(h165Value != 0xFF) { GPIO_LED = ~h165Value; } } }
[/highlight]
原理
首先管脚接受到数据,然后IN_PL置为0,这时就把数据锁在寄存器里面。然后我们通过不断翻转时钟,当时钟为0的时候我们就可以从IN_data里面读出数据。注意:我们这里是从高位开始读取的。我们管脚是从低位开始算的。所以需要把按键反接。
//这里解释下为什么按键要反接的原理
但按下1时:管脚顺序 01111 1111——》1111 1110(二进制代码)
因为那个读取是从高位读取的也就是先读到0 然后移位7此后到了第一位
后来就慢慢变成了0111 111 反一下1000 0000 0x80
还有|=的意思:移位或,即只要有一位是高位那么就是高位,因为刚开始经过移位后为0,如果接受一个高位的数据那么也会变成高位,反之
也就是为什么8亮的原因
输出芯片(74HC595)
[highlight lanaguage="C"]#include "reg51.h" //此文件中定义了单片机的一些特殊功能寄存器 #include "intrins.h" typedef unsigned int u16; //对数据类型进行声明定义 typedef unsigned char u8; //--定义使用的IO口--// #define GPIO_LED P0 //为什么SRCLK会重定义? sbit SRCLK= P3^6; sbit RCLK = P3^5; //数据通过P1.7脚移进单片机内处理 sbit SER= P3^4; void HC595send(u8 dat){ u8 i; for(i=0;i<8;i++){ //要把数据进行移位((每次传送最高位) SER=dat>>7; dat<<=1; //产生上升沿 SRCLK=0; _nop_(); _nop_(); SRCLK=1; } //这里是把数据放到寄存器里 RCLK=0; _nop_(); _nop_(); RCLK=1;}
void delay(u16 i){
while(i–);
}void main()
{
u8 ledvalue=0x01;
while(1){
HC595send(ledvalue);
ledvalue=crol(ledvalue,1);
delay(50000);}
}
[/highlight]
这个东西的主要原理和74hc165差不多。这里主要就是当时钟为0的时候把数据传进去,然后时钟变为1,反复类推。当把数据传送完毕的时候,我们这里就把RCLK置为0把数据放到锁存器里面,最后变为1,把数据发送出去。
LED点阵
Led点阵同样包括共阳和共阴的类型。这里左边是共阳的点阵右边是共阴的
单个LED点阵的原理简单,直接在对应的管脚输入电平信号就可以了。(当然我们还是需要进行动态扫描)
同样我们可以采用逐行或者逐列扫描的方式来显示对应的数据。
控制16*16的点阵
因为管脚太多,这里我们需要安装4个74hc595芯片。下面是74hc595的电路图。仿真图
这里我们使用了3个管脚来控制595芯片。这里的595芯片实际上是支持把上一个芯片多出来的数据传送给下一个芯片的。那个数据管脚对应的位置是9。
直接上源码;
[highlight lanaguage=”C”]
#include "reg51.h" #include "intrins.h" typedef unsigned char u8; typedef unsigned int u16; sbit SRCLK=P3^6; sbit RCLk=P3^5; sbit SER=P3^4; u8 code ledduan1[]={ /*-- 文字: 尼 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x00,0x00,0x00,0xFE,0x22,0x22,0xA2,0x22,0x22,0x22,0x22,0x22,0x22,0x3E,0x00,0x00, 0x80,0x40,0x30,0x0F,0x00,0x00,0x3F,0x44,0x44,0x44,0x42,0x42,0x41,0x41,0x78,0x00, }; void delay(u16 a){ while(a--); } void hc595sendbyte(u8 dat1,u8 dat2,u8 dat3,u8 dat4){ u8 a; SRCLK=1; RCLk=1; for(a=0;a<8;a++){ SER=dat1>>7; dat1<<=1; SRCLK=0; _nop_(); _nop_(); SRCLK=1; } for(a=0;a<8;a++){ SER=dat2>>7; dat2<<=1; SRCLK=0; _nop_(); _nop_(); SRCLK=1; } for(a=0;a<8;a++){ SER=dat3>>7; dat3<<=1; SRCLK=0; _nop_(); _nop_(); SRCLK=1; } for(a=0;a<8;a++){ SER=dat4>>7; dat4<<=1; SRCLK=0; _nop_(); _nop_(); SRCLK=1; } RCLk=0; _nop_(); _nop_(); RCLk=1; } void main(){ u8 i,j; while(1){ for(j=0;j<8;j++){ for(i=0;i<16;i++){ hc595sendbyte(~ledwei[i+16],~ledwei[i],ledduan1[i+16]>>j,ledduan1[i]>>j); delay(100); } delay(6000); } } }
[/highlight]
这里我们是直接一次性把所有的数据传到74hc595里面,然后在一次性显示出来。当然显示的时候要注意传值的问题。我们第一个和第二个芯片是控制位选的。我们依次显示每个点阵的第1行到第8行。(这个是四个LED同时显示的)然后3,4块芯片就是用来控制位选,把我们取模取到数数据直接传进去。
中断
中断时单片机非常重要的一个内容。51单片机的中断系统结构。(51里面有5个中断源,两个优先级。可以实现二级中断嵌套)第一个是引脚中断(IE0),可以选择引脚产生低电平还是下降沿有效。
当cpu检测到p3.2引脚上出现有效的中断信号的时候,中断标志IE0就置为1.向cpu申请中断。
第二个是定时器中断(TF0),当定时器或者计数器T0溢出的时候会置位TF0,并向cpu申请中断。
第三个是引脚中断(IE1),检测管脚是P3.3产生中断信号时,中断标志置为1.和上面的是一样的。
第四个是定时器中断(TF1),这个和上面是一样的,只不过定时器的名字叫T1而已。
第五个就是串口中断了(RI或者叫TI),当我们串行口接受完一帧数据的时候会置位RI。发送完一帧数据则置位TI。
当然,我们使用某个中断的时候不仅需要知道这些中断的标志,还需要打开对应的中断。
这里让对应的中断为1就可以开启对应的中断了。当然我们还需要设置中断的触发方式,也叫TCON。
我们可以通过给tcon赋不同的值,来达到不同的效果。当然也可以单独赋值。
中断优先级
中断的原则
这里我们以外部中断0(就是按键的中断)为例:有下面几个步骤
- 打开中断总开关(EA=1)
- 打开外部中断(EX0=1)
- 设置外部中断的触发方式(IT=0/1)
#include "reg52.h" typedef unsigned int u16; typedef unsigned char u8; //配置触发程序 sbit K3=P3^2; sbit led=P2^0; //注意两根线都要连 void int0Init(){ //触发方式(下降沿触发) IT0=1; //打开外部中断0 EX0=1; //打开cpu中中断 EA=1; } void delay(u16 i){ while(i--); }void main()
{ int0Init();
while(1){
}
}
//注意:这里0是中断的编号
void Int0()interrupt 0{
//按键消抖
delay(1000);
if(K3==0) led=~led;
}
[/highlight]
定时器和计数器
这个也是单片机的一个重点。首先我们要清楚一些cpu时序的相关知识。51定时器结构
要使用定时器我们就必须知道下面几个知识点。
- 工作方式寄存器(TMOD):这个主要用于定时器的工作方式的控制,低4位用于T0高4位用于T1。格式如下:
这里说一下定时器的几个工作方式
不同的工作方式对应的TH和TL的值是不一样的。
方式0:x=2^13-n
方式1:x=2^16-n
方式2:x=2^8-n
方式3:x=2^13-n
注意我们这里一般使用方式一来进行计时。通常我们计算初值的时候要把N算出来就可以了。这里N=t/tcy
T是我们要计时的时间,Tcy是单片机的时钟频率(也叫机器周期)。我们单片机一般是12分频,所以当我们使用12MHZ的晶振的时候tcy=12MHZ/12=1/1M=1uS。比如我们要计时1ms,那么N=1ms/1us=1000;
所以初值为65536-1000=64536=FC18(16进制)
2.控制寄存器(TCON),这个东西的高4位用于控制外部中断,低4位用于控制定时器的启动和中断申请。介绍如下
使用定时器的步骤
例程
下面代码实现了每秒led闪烁一次的效果[highlight lanaguage=”C”]
#include "reg51.h" void Timer0_Init() { TMOD|=0x01;(这里说明我们使用方式1来进行工作) TH0=56320/256; //计数起点为56320 ==10ms溢出一次 TL0=56320%256; (这里256刚好是两个16进制表示的最大的数) TR0=1;//(这里是T0的运行控制位,用于控制定时器0的开启) } void ISR_Init() //初始化中断系统 { EA=1; //启动中断系统 EX0=0; //-->IE0(外部中断0关闭) ET0=1; //-->TF0 控制位置1,表明当TF0置1时,中断系统将介入(打开定时器1) EX1=0; //-->IE1(外部中断1关闭) ET1=0; //-->TF1(定时器2关闭) ES=0; //-->RI,TI(串口中断关闭) //这些关闭的其实都可以不用加上去的 } //以下中断服务子程序,我们希望中断系统来调用,而不是我们在main函数里面调用,因此使用interrupt. */ void TF0_isr() interrupt 1 //10ms 进入一次 { static char c; TH0=56320/256; //重装初值 TL0=56320%256; //这里就是当定时器中断时继续重装值,这个时候定时器会继续进行 c++; if(c==100) { P1^=(1<<0); //这里实现1s执行一次的效果 c=0; } } void main() { Timer0_Init(); ISR_Init(); while(1) { //... //发现溢出后,中断系统根据中断号寻找中断子服务函数,并强行暂停主循环并进入子函数 //... } }
[/highlight]
串口通信
串口通信也是单片机的一个非常重要的知识点。计算机通信方式
并行通信和串行通信。并行通信就是把数据字节的每位都用一条数据线进行传输。
串行通信就是把数据字节分为一位一位的形式在一条传输线上逐个的进行传送。
串行通信的基本概念
串行通信的传输方向
串行通信的错误检测方法
传输速率(比特率)的计算
串行通信的连接方式
就是TXD连接另一边的RXD。另一根也是这样的。
51单片机的串行口
51串口配置
- 控制寄存器(SCON)
(SM0和SM1的介绍)
四种串行工作方式的介绍
其他位的介绍
PCON
Pcon只有一位SMOD与串行口工作有关(主要用于控制是否倍频)波特率的计算
我们常见的波特率和定时器的关系。(注意为了精度考虑我们一般采用晶振为11.0592的晶振)
串口如何使用
例程:
[highlight lanaguage="C"]#include "reg51.h" typedef unsigned int u16; //对数据类型进行声明定义 typedef unsigned char u8; void usarinit(){ //高次位控制T0低次位控制T1 TMOD=0x20;//这个是串口功能寄存器我们使用方式二(这个是控制定时器的) //这里是计算t1的初值(这里是给定时器赋值) TH1=0xf3; TL1=0xf3; //smod在pcon里面,我们需要倍频,因为我们计算定时器只是一半的波特率。 PCON=0x80; //这里是启动定时器t1 TR1=1; //这里是确定串行口的工作方式(我们采用方式1) SCON=0x50;//(自己看串行口的控制寄存器) //打开串口中断 ES=1; //打开总中断 EA=1; //波特率为4800,倍率了 }void main()
{
//串口中断打开
usarinit();
while(1){}
}
void usart()interrupt 4{
//把缓冲区的数据读取出来
u8 receive=SBUF;
//接受完后我们用软件清零(这个是接受位)
RI=0;
//返回数据
SBUF=receive;
//这里是等待发送完成
while(!TI);
//继续清零等待发送
TI=0;
}
[/highlight]
RS485通信
这个一般用于工业通信,我们这里就不多说了IIC总线
IIC也是一种非常重要的通信方式常用的串行总线技术
IIC总线介绍
IIC总线的数据传送(时序图介绍)
数据传送
时钟为高电平时数据要稳定这时就可以读取数据,如果时钟为低电平时信号才可以改变。
应答
数据发送方式(有三种发送方式)
总线的寻址
IIC总线的模拟
模拟程序
IIC模拟程序
[highlight lanaguage="C"]void delay(void) //这里是延时10微秒 { unsigned char a,b; for(b=1;b>0;b--) for(a=2;a>0;a--); }//这里是模拟起始信号
void I2Cstart(){
SDA=1;
//延时10微妙
delay();
SCL=1;
delay();
SDA=0;
delay();
SCL=0;
delay();
}
//这里是模拟停止信号
void I2Cstop(){
SDA=0;
//延时10微妙
delay();
SCL=1;
delay();
SDA=1;
delay();
}
//这里模拟I2C发送数据
unsigned char I2csend(unsigned char dat){
//这里是发送数据,并且会判断是否发送成功
unsigned char a=0,b;
//这里是把sda拉高,让i2c总线处于空闲状态
SDA=1;
delay();
//这里是把8位数据发送出去
for(a=0;a<8;a++){ //传送最高位(这里把其他的位都移走了) SDA=dat>>7;
//这里把次高位来移到最高位
dat=dat<<1; delay(); SCL=1; delay(); SCL=0; delay(); } //发送完数据后就要把I2c总线释放出去 SDA=1; delay(); SCL=1; //这里是等待应答 应答后SDA会拉低 while(SDA){ //如果非应答就会长时间不响应 //这里就加一个限制条件,如果再不响应的话就强制返回 b++; if(b>200){
SCL=0;
delay();
return 0;} } SCL=0; delay(); //发送成功返回1 return 1;
}
//这里是I2c的接受函数
unsigned char I2Cread(){
unsigned char a=0,dat=0;
for(a=0;a<8;a++){
SCL=1;
delay();
dat<<=1;
//这里是把发送来的数据一位位的赋值过来
dat|=SDA;
delay();
SCL=0;
delay();
}
return dat;
}//这里是写数到at24c02里面
void at24c02write(unsigned char addr,unsigned char dat){
//开始发送
I2Cstart();
//写入器件地址
I2csend(0xa0);
//这里是地址
I2csend(addr);
//这里是发送数据
I2csend(dat);
I2Cstop();}
//这里是读取at24c20
unsigned char at24c02read(unsigned char addr){
unsigned char num;
I2Cstart();
//发送首地址
I2csend(0xa0);
I2csend(addr);
// 这里是传送方向会改变后起始信号和地址都被重复产生一次
I2Cstart();
//发送读器件地址
I2csend(0xa1);
//读取保存的数据
num=I2Cread();
I2Cstop();
return num;
}
[/highlight]