发光二极管(LED)
标注:
- 大旗子表示负极,小旗子表示正极
- 长的一端为正极,短的为负极
- 正极内电极较小,负极内电极较大
电阻上的数字如何看?
102 是分两部分看 10 2 --> 1K 1000
473 47 3 --> 47K 47000
1001 100 1 --> 1K 1000
单片机工作原理:
CPU --> 寄存器 --> 驱动器 --> IO口
二进制转十六进制:
1. 四位一段
2. 8421码
点亮第1个LED
- 需要控制P2口
- 给P2^0 低电平 其他 1 2 3 4 5 6 7 为高电平
#include <REGX52.H>
void main()
{
//P2 = 0xFE;// 1111 1110
P2 = 0x55;// 表示 0101 0101 LED间隔点亮
}
LED闪烁
- while死循环,使得程序不断执行。
- 需要延时函数,使得P2^0口的高低电平切换有时间差
#include <reg52.h>
#include <intrins.h> // 声明_nop_函数
void Delay500ms() //@12.000MHz
{
unsigned char i, j, k;
_nop_(); // 执行空语句
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
P2 = 0xFE; // 1111 1110
Delay500ms();
P2 = 0xFF; //1111 1111
Delay500ms();
}
}
LED流水灯
- 流水的需要依次点亮每个led,也就是一个挨一个的高低电平的切换
- 同时需要延时函数
#include <regx52.h>
#include <intrins.h>
void Delay500ms() //@12.000MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
P2 = 0xFE;// 1111 1110
Delay500ms();
P2 = 0xFD;// 1111 1101
Delay500ms();
P2 = 0xFB;// 1111 1011
Delay500ms();
P2 = 0xF7;// 1111 0111
Delay500ms();
P2 = 0xEF;// 1110 1111
Delay500ms();
P2 = 0xDF;// 1101 1111
Delay500ms();
P2 = 0xBF;// 1011 1111
Delay500ms();
P2 = 0x7F;// 0111 1111
Delay500ms();
}
流水灯plus
数据类型:
存数据的小盒子
unsigned int 16位(单片机) 0~65535
int 16位(单片机) -32768~32767
#include <regx52.h>
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
}while (--i);
xms--;
}
}
void main()
{
P2 = 0xFE; // 1111 1110
Delay1ms(100);
P2 = 0xFD; // 1111 1101
Delay1ms(100);
P2 = 0xFB; // 1111 1011
Delay1ms(100);
P2 = 0xF7; // 1111 0111
Delay1ms(100);
P2 = 0xEF; // 1110 1111
Delay1ms(100);
P2 = 0xDF; // 1101 1111
Delay1ms(100);
P2 = 0xBF; // 1011 1111
Delay1ms(100);
P2 = 0x7F; // 0111 1111
Delay1ms(100);
}
延时程序的函数
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
}while (--i);
xms--;
}
}
其实最佳写法为把8个数据放到数组之中,省去这样的大篇幅
独立按键
轻触按键:相当于一种电子开关,按下接通,松开断开,实现原理是通过按键内部的金属弹片受力弹动来实现接通和断开
声明单独io
口
#include <reg52.h>
sbit P2_0 = P2^0;
引用<regx52.h>,就不需要声明
#include <regx52.h>
P2_0 = 0;
独立按键控制led亮灭
- 查电路引脚图可知,控制第一个独立按键的为
P3.1
IO口,引入regx52.h
文件后,可以不需要声明,直接使用P3_1
- 独立按键在按下是,贴片接通,为
P3_1
为低电平,松开时为高电平if else
判断语句,对P3_1
进行判断
#include <regx52.h>
void main()
{
while(1)
{
if(P3_1 == 0)
{
P2_0 = 0;
}
else
P2_0 = 1;
}
}
移位运算法 << 左移运算符,高位移出,低位补0
>> 右移运算符,低位移出,高位补0
0011 0010>>1 --> 0001 1001
0011 0010<<2 --> 1100 1000
按位与
0001 1000 & 0010 1010 --> 0001 1000
按位或
0001 1000 | 0010 1010 --> 0011 1010
按位异或(0与0 为0 1与0为1,1与1为0)
0001 1000 ^ 0010 1010 --> 0011 0010
按位取反
~0001 1000 --> 1110 0111
按键的抖动
对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,所以在开关闭合以及断开时的瞬间会伴随一连串的抖动。人话来讲就是,高电平与低电平切换时,不会立即进入低电平,而是在波折
如何消除抖动?
- 硬件消抖,加元器件,对抖动进行过滤
- 软件消抖,按下按键后加延时函数
独立按键控制led状态
通过延时函数与while循环来模拟抖动的过程,以消除抖动。
取反运算符,使得P2_0
口按键改变高低电平
#include <regx52.h>
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
void main()
{
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
P2_0 = ~P2_0;
}
}
}
独立按键控制led显示二进制
1 0000 0001 1111 1110
2 0000 0010 1111 1101
3 0000 0011 1111 1100
4 0000 0100 1111 1011
5 0000 0101 1111 1010
6 0000 0110 1111 1001
7 0000 0111 1111 1000
通过按键使得num
从1~n,
二进制取反后,赋值给P2
整个io
口,实现二进制
#include <regx52.h>
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
void main()
{
unsigned char LEDnum;
while(1)
{
if(P3_1 == 0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDnum++;
P2= ~LEDnum;
}
}
}
独立按键控制led移位
使用移位运算符实现下面的情形
0000 0001 0x01<<0
0000 0010 0x01<<1
0000 0100 0x01<<2
0000 1000 0x01<<3
0001 0000 0x01<<4
0010 0000 0x01<<5
0100 0000 0x01<<6
1000 0000 0x01<<7
#include <regx52.h>
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
void main()
{
unsigned char num=0;
P2 = ~(0x01);
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1 ==0);
Delay(20);
if(num==7)
{
num =0;
P2 = ~(0x01<<num);
}
else
{
num++;
P2 = ~(0x01<<num);
}
}
if(P3_0==0)
{
Delay(20);
while(P3_0 ==0);
Delay(20);
if(num==0)
{
num =7;
P2 = ~(0x01<<num);
}
else
{
num--;
P2 = ~(0x01<<num);
}
}
}
}
数码管
led数码管 :通常是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成"8"字型的器件
一位数码管:
共阴极连接、共阳极连接 两种连接方式
******A****** 分布图 10 9 8 7 6 引脚图
* *
F B
* *
******G******
* *
E C
* * ·DP
******D****** 1 2 3 4 5
A --> 7
B --> 6
C --> 4
D --> 2
E --> 1
F --> 9
G --> 10
DP --> 5
共阴极
数字6的段码 为 1011 1110
共阳极
数字6的段码 为0100 0001
四位数码管:
12 11 10 9 8 7 引脚图
1 2 3 4 5 6
动态数码管显示:
利用人眼的分辨率小,不断进行数码管扫描
138译码器
p2_4
、p2_3
、p2_2
CBA三个对应io口
C | B | A | Y |
---|---|---|---|
0 | 0 | 0 | Y0 = 0 其他为1 LED1 |
0 | 0 | 1 | Y1=0 其他为1 LED2 |
0 | 1 | 0 | Y2 = 0 其他为1 LED3 |
0 | 1 | 1 | Y3 = 0其他为1 LED4 |
1 | 0 | 0 | Y4=0 其他为1 LED5 |
1 | 0 | 1 | Y5=0 其他为1 LED6 |
1 | 1 | 0 | Y6=0 其他为1 LED7 |
1 | 1 | 1 | Y7=0 其他为1 LED8 |
CBA二进制转为十进制后对应Y后面的数字
74HC245 --> 双向数据缓冲器
高位对端口的高位
io口0
对端口g
、io口7
对端口a
,也就是abcdefgdp
部分的数据,在传输的时候要反着读
静态数码管显示
// 显示6
#include <regx52.h>
void main()
{
P2_4 = 1;P2_3 =0;P2_2 =1;
P0 = 0x7D;
while(1)
{
}
}
动态显示需要消影
同时需要在段选与下一次位选之间,加一个清零
位选 段选 清零 位选 段选 位选 段选
#include<regx52.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;
}
void main()
{
while(1)
{
Nixie(1,1);
Nixie(2,2);
Nixie(3,3);
}
}
数字0-9的字段:
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F
A-F的字段:
0x77,0x7C,0x39,0x5E,0x79,0x71
数码管驱动方式:
- 单片机直接扫描:硬件设备简单,但会耗费大量的单片机
CPU
时间 - 专用芯片扫描:内部自带显存、扫描电路、单片机质询告诉它显示什么即可
模块化编程
// 在头文件中的声明
// .h
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
// 在模块函数文件中的定义
// .c
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
// 在main函数中的引用
#include "Delay.h"
void main()
{
Delay(10);
}
// 常用的预编译命令
#include <REGX52.H> 把REGX52.H文件的内容搬到此处
#define PI 3.14 定义PI,将PI替换为3.14
#define ABC 定义ABC
#ifndef __XX_H__ 如果没有定义__XX_H__
#endif 与#ifndef,#if匹配,组成“括号”
此外还有#ifdef,#if,#else,#elif,#undef等
LCD模块化代码调用
LCD_Init(); 初始化
LCD_ShowChar(1,1,'A'); 显示一个字符
LCD_ShowString(1,3,"Hello"); 显示字符串
LCD_ShowNum(1,9,123,3); 显示十进制数字
LCD_ShowSignedNum(1,13,-66,2); 显示有符号十进制数字
LCD_ShowHexNum(2,1,0xA8,2); 显示十六进制数字
LCD_ShowBinNum(2,4,0xAA,8); 显示二进制数字
在本地中有关于LCD1602的.c和.h文件,需要时引用即可,在主函数中直接调用以上函数,达到实现LCD屏的作用
矩阵键盘
- 在键盘中按键较多时,为了减少
I/O
口的占用,通常将键盘排列成矩阵形式 - 采取逐行或者逐列的扫描,就可以读出任何设置的按键状态
我们这里矩阵键盘采取逐列扫描
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rEC4LPLq-1681634737177)(E:\单片机笔记\assets\1678604686710.png)]
首先 矩阵键盘受P1 i/o口控制,给整个io口高电平 0-3,给低电平,进行列的测试,当矩阵键盘按下时,4-7 会出现变为低电平
本函数模块的逻辑基于此
另外矩阵键盘与独立按键一样需要消抖
// MatrixKey.c
#include <regx52.h>
#include "Delay.h"
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;
}
// main.c
#include<regx52.h>
#include"MatrixKey.h"
#include"LCD1602.h"
void main()
{
LCD_Init();
LCD_ShowString(1,1,"hello world");
while(1)
{
unsigned KeyNumber = 0;
KeyNumber = MatrixKey();
if(KeyNumber)
{
LCD_ShowNum(2,1,KeyNumber,2);
}
}
}
矩阵键盘密码锁
s1到s9为1-9 – > 这里可以直接采用按键的返回值,s1 - s9 本身按键返回值就是1-9
s10为0 – > 这里可以采取取模的方法,使得10%10 = 0
s11为确认键 – > 密码正确反馈 ok 密码错误显示 err
s12为返回键 --> 返回设置为清零
s13位退出键 – > 退出设定为初始化
// main.c
#include<regx52.h>
#include"MatrixKey.h"
#include"LCD1602.h"
unsigned int Password;
int count = 0;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"PASSWORD:");
while(1)
{
unsigned KeyNum = 0;
KeyNum = MatrixKey();
if(KeyNum)
{
if(KeyNum <= 10)
{
if(count<4)
{
Password *= 10; // 密码左移一位
Password += KeyNum % 10; // 加上一位密码
count++; // 计数加1次
}
LCD_ShowNum(2,1,Password,4);
}
if(KeyNum ==11)
{
if(Password==2345)
{
LCD_ShowString(1,14,"OK ");
Password = 0;
count = 0;
LCD_ShowNum(2,1,Password,4);
}
else
{
LCD_ShowString(1,14,"ERR");
Password = 0;
count = 0;
LCD_ShowNum(2,1,Password,4);
}
}
if(KeyNum == 12)
{
Password = 0;
count = 0;
LCD_ShowNum(2,1,Password,4);
}
if(KeyNum == 13)
{
LCD_Init();
LCD_ShowString(1,1,"PASSWORD:");
}
}
}
}
定时器
介绍:51单片机的定时器属于单片机内部资源,其电路的连接和运转,都在单片机内部完成
定时器的作用:
- 用于计时系统,可实现软件的计时,或者使程序每隔一段时间完成一项操作
- 代替长时间的Delay,提高CPU运行效率和处理速度
定时器的个数:3个(T0,T1,T2),T0,T1与传统51单片机兼容
注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的
框图:
定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行
1.时钟 --> 2.计数单元 --> 3.中断系统
1.提供计数单元的时钟脉冲
2.时钟计数
3.产生中断执行定时的任务
工作模式:
模式0:13位定时器/计数器
**模式1:16位定时器/计数器(常用)**
模式2:8位自动重装模式
模式3:两个8位计数器
中断程序的流程:
主程序 --> 中断请求 继续执行主程序
↓ ↑
中断响应 --> 执行中断处理程序 --> 中断返回
定时器需要配置初始信息
//此函数为定时器与中断初始化函数
void Timer0Init() //1毫秒@12.000MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; // 使得中断连通
EA = 1; // 使得中断连通
PT0 = 0; // 选择低优先级的中断
}
中断程序的模板
void Timer0_Rountine() interrupt 1
{
static unsigned int T0cnt;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0cnt++;
if(T0cnt>=1000)
{
T0cnt = 0;
}
}
定时器控制流水灯代码
// main.c
#include <REGX52.H>
#include <intrins.h>
#include "Timer0.h"
#include "Key.h"
unsigned char KeyNumber;
unsigned char LEDmode;
void main()
{
P2 = 0xFE;
Timer0Init();
while(1)
{
KeyNumber = Key();
if(KeyNumber)
{
if(KeyNumber==1)
{
LEDmode++;
if(LEDmode>=2)
LEDmode=0;
}
}
}
}
void Timer0_Rountine() interrupt 1
{
static unsigned int T0cnt;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0cnt++;
if(T0cnt>=500)
{
T0cnt=0;
if(LEDmode==0)
P2 =_crol_(P2,1);
if(LEDmode==1)
P2 =_cror_(P2,1);
}
}
定时器闹钟代码
// main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "Timer0.h"
unsigned int Sec=55,Min=59,Hour=23;
void main()
{
Timer0Init();
LCD_Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,1," : :");
while(1)
{
LCD_ShowNum(2,1,Hour,2);
LCD_ShowNum(2,4,Min,2);
LCD_ShowNum(2,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int Tcnt;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
Tcnt++;
if(Tcnt>=1000)
{
Tcnt=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
Hour++;
if(Hour>=24)
{
Sec=0;
Min=0;
Hour=0;
}
}
}
}
}
串口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnIn1Ju9-1681634737178)(E:\单片机笔记\assets\1678243236002.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BSAXlhy6-1681634737178)(E:\单片机笔记\assets\1678243260581.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TfZVzrLB-1681634737178)(E:\单片机笔记\assets\1678243283848.png)]
串口配置初始化函数
/**
* @brief 串口初始化函数
* @param 无
* @retval 无
*/
void UART_Init() //4800bps@12.000MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
EA = 1; // 配置中断
ES = 1; // 配置中断
}
串口发送字节信息函数
/**
* @brief 串口发送字节信息函数
* @param Byte 发送的信息
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF = Byte;
}
串口中断函数
/* 串口中断程序模板*/
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2 = SBUF;
UART_SendByte(SBUF);
RI = 0;
}
}
串口向电脑发送数据程序
// main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main()
{
UART_Init();
while(1)
{
UART_SendByte(Sec);
Sec++;
Delay(1000);
}
}
电脑向串口发送数据控制LED
#include <REGX52.H>
#include "UART.h"
void main()
{
UART_Init();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2 = SBUF;
UART_SendByte(SBUF);
RI = 0;
}
}
LED矩阵
原理与矩阵键盘一样
74HC595芯片工作原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gFtLmxUG-1681634737179)(E:\单片机笔记\assets\1678242178331.png)]
RCLK --> RCK 上升沿锁存,一旦给上升沿会把下图中左边的值移到右边,要初始化为0
SERCLK --> SCK 上升沿移位,一旦是上升沿就会向下移位储存SER 中的数值,要初始化为0
SER --> SER 用来接收高低电平的数据Data
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q29Mtck1-1681634737179)(E:\单片机笔记\assets\1678242260199.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C5NldOV6-1681634737179)(E:\单片机笔记\assets\1678243206725.png)]
// 定义引脚
sbit RCK = P3^5; // RCLK
sbit SCK = P3^6; // SERCLK
sbit SER = P3^4; // SER
// 初始化函数
void Init()
{
SCK = 0;
RCK = 0;
}
// 控制74HC595写数据
void _74HC595_Write_Byte(unsigned char Byte)
{
unsigned char i;
for (i = 0; i < 8; i++)
{
SER = Byte&(0x80>>i); // Byte与上0x80 会得到第一位的数据,每次移位就可以得到Byte的每一位
// SER 是一位寄存器 只会储存一个值,所以 = 后面有值就为1,无值就为0
SCK = 1;
SCK = 0;
}
RCK = 1;
RCK = 0;
}
// 矩阵书写行和列的函数
void MatrixLED_ShowColumn(unsigned char Column,unsigned char Data)
{
_74HC595_Write_Byte(Data);
P0 = ~(0x80 >> Column);// 确定哪一列显示
Delay(1); //
P0 = 0xFF;// 这两句是为了消影
}
// main
#include <REGX52.H>
#include "MatrixLED.h"
// 取模软件生成的矩阵编号
unsigned char code Anmiation[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x7F,0x08,0x08,0x08,0x7F,0x00,0x0E,0x15,
0x15,0x0D,0x00,0x7E,0x01,0x02,0x00,0x7E,
0x01,0x02,0x00,0x06,0x09,0x09,0x06,0x00,
0x00,0x7D,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
// 这是流动矩阵的main函数
void main()
{
//Offset是偏移量,Cnt是计数器
unsigned char i,Offset=0,Cnt=0;
Init();
while(1)
{
for(i = 0; i < 8; i++)
{
MatrixLED_ShowColumn(i,Anmiation[i+Offset]);
}
Cnt++;
if(Cnt>10)
{
Cnt=0;
Offset++;
if(Offset>40)
{
Offset = 0;
}
}
}
}
DS1302时钟
DS1302
时钟芯片模式与年月日时分秒星期的接口
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wQuX5lbJ-1681634737179)(E:\单片机笔记\assets\1678605621225.png)]DS1302
的引脚图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NasfizS8-1681634737179)(E:\单片机笔记\assets\1678605738882.png)]
时序定义图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VwKKIz1f-1681634737180)(E:\单片机笔记\assets\1678606299234.png)]
显示时间程序
// DS1302.h
#ifndef __DS1302_H__
#define __DS1302_H__
extern unsigned int DS1302_time[];
void DS1302_Settime(void);
void DS1302_Readtime(void);
void DS1302_WriteByte(unsigned char Command,unsigned char Data);
unsigned char DS1302_ReadByte(unsigned char Command);
#endif
// DS1302.c
#include <REGX52.H>
// 宏定义时钟控制的年月日时分秒星期的各位置的数据写入接口
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8a
#define DS1302_YEAR 0x8c
#define DS1302_WP 0x8e
// 定义引脚 DS1302_SCLK 是上升与下降沿 DS1302_IO 读取数据的io口 DS1302_CE 使能
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO = P3^4;
sbit DS1302_CE = P3^5;
// 外部可引用数组,用来存时间的各个量
unsigned int DS1302_time[]={23,3,10,20,13,55,5};
/**
* @brief DS1302初始化函数 DS1302_SCLK 上电后为0 DS1302_CE 使能上电后也要为0
* @param 无
* @retval 无
*/
void DS1302_Init(void)
{
DS1302_SCLK = 0;
DS1302_CE = 0;
}
/**
* @brief DS1302 写命令与传数据的函数
* @param Command 传入要进行的命令
* @param Data 传入要写入的数据
* @retval 无
*/
void DS1302_WriteByte(unsigned char Command,Data)
{
unsigned char i;
DS1302_CE=1; // 使能给高电平
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i); // 每次写入要进行的命令 把最低位放在前面
DS1302_SCLK=1; // 给上升沿,读入数据
DS1302_SCLK=0; // 恢复为0
}
for(i=0;i<8;i++)
{
DS1302_IO=Data&(0x01<<i); // 写入时间数据 低位在前
DS1302_SCLK=1; // 给上升沿读入数据
DS1302_SCLK=0; // 恢复为0
}
DS1302_CE=0; // 使能恢复为低电平
}
/**
* @brief DS1302读数据的函数
* @param Command 读数据的命令
* @retval 返回读出来的数据
*/
unsigned char DS1302_ReadByte(unsigned char Command)
{
unsigned char i,Data = 0x00;
DS1302_CE = 1; // 使能给高电平
Command |= 0x01; // 为了减少引脚的定义,所以 将写的命令或上1 就会把最低位置1,变为读的模式
for (i = 0;i < 8;i++)
{
DS1302_IO = Command&(0X01<<i); // 每次写入要进行的命令 与1是清零
DS1302_SCLK = 0; // 这里这样写,可以保证最后时序没有超过读的时序
DS1302_SCLK = 1; //
}
for (i = 0;i < 8;i++)
{
DS1302_SCLK = 1; // 在这里继续给sclk置1,是因为 读数据时少了一个上升沿,让它停留一会
DS1302_SCLK = 0; // 给下降沿 读数据
if (DS1302_IO){Data |= (0x01<<i);} // 如果io口上有数据1,Data与上0x01 会变为1
}
DS1302_CE = 0; // 使能恢复低电平
DS1302_IO = 0; // 读完数据后,io口清零,否则读取数据可能有错误
return Data;
}
/**
* @brief DS1302设置时间函数
* @param 无
* @retval 无
*/
void DS1302_Settime(void)
{
// 这里调用写入数据函数,先传入命令吗,在传入要写的时间数据 // 十进制转为BCD码
DS1302_WriteByte(DS1302_WP,0X00);
DS1302_WriteByte(DS1302_YEAR,DS1302_time[0]/10*16+DS1302_time[0]%10);
DS1302_WriteByte(DS1302_MONTH,DS1302_time[1]/10*16+DS1302_time[1]%10);
DS1302_WriteByte(DS1302_DATE,DS1302_time[2]/10*16+DS1302_time[2]%10);
DS1302_WriteByte(DS1302_HOUR,DS1302_time[3]/10*16+DS1302_time[3]%10);
DS1302_WriteByte(DS1302_MINUTE,DS1302_time[4]/10*16+DS1302_time[4]%10);
DS1302_WriteByte(DS1302_SECOND,DS1302_time[5]/10*16+DS1302_time[5]%10);
DS1302_WriteByte(DS1302_DAY,DS1302_time[6]/10*16+DS1302_time[6]%10);
DS1302_WriteByte(DS1302_WP,0X80);
}
/**
* @brief 读时间数据的函数
* @param 无
* @retval 无
*/
void DS1302_Readtime(void)
{
// 这里调用读数据函数,直接传入读的命令,然后BCD码转为10进制
unsigned char TEMP = 0;
TEMP = DS1302_ReadByte(DS1302_YEAR);
DS1302_time[0] = TEMP/16*10+TEMP%16;
TEMP = DS1302_ReadByte(DS1302_MONTH);
DS1302_time[1] = TEMP/16*10+TEMP%16;
TEMP = DS1302_ReadByte(DS1302_DATE);
DS1302_time[2] = TEMP/16*10+TEMP%16;
TEMP = DS1302_ReadByte(DS1302_HOUR);
DS1302_time[3] = TEMP/16*10+TEMP%16;
TEMP = DS1302_ReadByte(DS1302_MINUTE);
DS1302_time[4] = TEMP/16*10+TEMP%16;
TEMP = DS1302_ReadByte(DS1302_SECOND);
DS1302_time[5] = TEMP/16*10+TEMP%16;
TEMP = DS1302_ReadByte(DS1302_DAY);
DS1302_time[6] = TEMP/16*10+TEMP%16;
}
#include <REGX52.H>
#include "DS1302.h"
#include "LCD1602.h"
unsigned char Second;
void main()
{
LCD_Init();
DS1302_Init();
DS1302_Settime();
LCD_ShowString(1,1," - -");
LCD_ShowString(2,1," : :");
while(1)
{
DS1302_Readtime();
LCD_ShowNum(1,1,DS1302_time[0],2);
LCD_ShowNum(1,4,DS1302_time[1],2);
LCD_ShowNum(1,7,DS1302_time[2],2);
LCD_ShowNum(2,1,DS1302_time[3],2);
LCD_ShowNum(2,4,DS1302_time[4],2);
LCD_ShowNum(2,7,DS1302_time[5],2);
LCD_ShowNum(2,9,DS1302_time[6],2);
}
}
可调时钟程序
// main.c
#include <REGX52.H>
#include "DS1302.h"
#include "LCD1602.h"
#include "Timer0.h"
#include "Key.h"
unsigned char KeyNumber,MODE,TimeSetSelect,TimeSetFlashFlag;
void TimeShow(void)
{
DS1302_ReadTime();
LCD_ShowNum(1,1,DS1302_Time[0],2);
LCD_ShowNum(1,4,DS1302_Time[1],2);
LCD_ShowNum(1,7,DS1302_Time[2],2);
LCD_ShowNum(2,1,DS1302_Time[3],2);
LCD_ShowNum(2,4,DS1302_Time[4],2);
LCD_ShowNum(2,7,DS1302_Time[5],2);
}
void TimeSet(void)//时间设置功能
{
if(KeyNumber==2)//按键2按下
{
TimeSetSelect++;//设置选择位加1
TimeSetSelect%=6;//越界清零
}
if(KeyNumber==3)//按键3按下
{
DS1302_Time[TimeSetSelect]++;//时间设置位数值加1
if(DS1302_Time[0]>99){DS1302_Time[0]=0;}//年越界判断
if(DS1302_Time[1]>12){DS1302_Time[1]=1;}//月越界判断
if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 ||
DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断
{
if(DS1302_Time[2]>31){DS1302_Time[2]=1;}//大月
}
else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11)
{
if(DS1302_Time[2]>30){DS1302_Time[2]=1;}//小月
}
else if(DS1302_Time[1]==2)
{
if(DS1302_Time[0]%4==0)
{
if(DS1302_Time[2]>29){DS1302_Time[2]=1;}//闰年2月
}
else
{
if(DS1302_Time[2]>28){DS1302_Time[2]=1;}//平年2月
}
}
if(DS1302_Time[3]>23){DS1302_Time[3]=0;}//时越界判断
if(DS1302_Time[4]>59){DS1302_Time[4]=0;}//分越界判断
if(DS1302_Time[5]>59){DS1302_Time[5]=0;}//秒越界判断
}
if(KeyNumber==4)//按键3按下
{
DS1302_Time[TimeSetSelect]--;//时间设置位数值减1
if(DS1302_Time[0]<0){DS1302_Time[0]=99;}//年越界判断
if(DS1302_Time[1]<1){DS1302_Time[1]=12;}//月越界判断
if( DS1302_Time[1]==1 || DS1302_Time[1]==3 || DS1302_Time[1]==5 || DS1302_Time[1]==7 ||
DS1302_Time[1]==8 || DS1302_Time[1]==10 || DS1302_Time[1]==12)//日越界判断
{
if(DS1302_Time[2]<1){DS1302_Time[2]=31;}//大月
if(DS1302_Time[2]>31){DS1302_Time[2]=1;}
}
else if(DS1302_Time[1]==4 || DS1302_Time[1]==6 || DS1302_Time[1]==9 || DS1302_Time[1]==11)
{
if(DS1302_Time[2]<1){DS1302_Time[2]=30;}//小月
if(DS1302_Time[2]>30){DS1302_Time[2]=1;}
}
else if(DS1302_Time[1]==2)
{
if(DS1302_Time[0]%4==0)
{
if(DS1302_Time[2]<1){DS1302_Time[2]=29;}//闰年2月
if(DS1302_Time[2]>29){DS1302_Time[2]=1;}
}
else
{
if(DS1302_Time[2]<1){DS1302_Time[2]=28;}//平年2月
if(DS1302_Time[2]>28){DS1302_Time[2]=1;}
}
}
if(DS1302_Time[3]<0){DS1302_Time[3]=23;}//时越界判断
if(DS1302_Time[4]<0){DS1302_Time[4]=59;}//分越界判断
if(DS1302_Time[5]<0){DS1302_Time[5]=59;}//秒越界判断
}
//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1," ");}
else {LCD_ShowNum(1,1,DS1302_Time[0],2);}
if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4," ");}
else {LCD_ShowNum(1,4,DS1302_Time[1],2);}
if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7," ");}
else {LCD_ShowNum(1,7,DS1302_Time[2],2);}
if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1," ");}
else {LCD_ShowNum(2,1,DS1302_Time[3],2);}
if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4," ");}
else {LCD_ShowNum(2,4,DS1302_Time[4],2);}
if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7," ");}
else {LCD_ShowNum(2,7,DS1302_Time[5],2);}
}
void main()
{
LCD_Init();
DS1302_Init();
Timer0Init();
LCD_ShowString(1,1," - - ");//静态字符初始化显示
LCD_ShowString(2,1," : : ");
DS1302_SetTime();//设置时间
while(1)
{
KeyNumber=Key();//读取键码
if(KeyNumber==1)//按键1按下
{
if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换
else if(MODE==1){MODE=0;DS1302_SetTime();}
}
switch(MODE)//根据不同的功能执行不同的函数
{
case 0:TimeShow();break;
case 1:TimeSet();break;
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=500)//每500ms进入一次
{
T0Count=0;
TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
}
}
AT24C02与I2C总线
I2C的时序结构
起始与终止条件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eCSBSZYt-1681634737180)(E:\单片机笔记\assets\1679188178440.png)]
/**
* @brief I2C总线起始函数
* @param 无
* @retval 无
*/
void I2C_Start()
{
I2C_SCL=1; // 时钟线置1
I2C_SDA=1; // 数据线置1
I2C_SDA=0; // 数据线产生下降沿
I2C_SCL=0; // 时钟线恢复0
}
/**
* @brief I2C总线终止函数
* @param 无
* @retval 无
*/
void I2C_Stop()
{
I2C_SDA=0; // 数据线置0
I2C_SCL=1; // 时钟线置1
I2C_SDA=1; // 数据线产生上升沿
}
I2C发送数据函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rF0WZDBQ-1681634737180)(E:\单片机笔记\assets\1679188770934.png)]
/**
* @brief I2C总线发送数据函数
* @param 要发送的数据
* @retval 无
*/
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
I2C_SDA=Byte&(0x80>>i); //从高位发送
I2C_SCL=1;
I2C_SCL=0;
}
}
接收数据函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7PI60u8-1681634737180)(E:\单片机笔记\assets\1679188806721.png)]
/**
* @brief I2C接收数据函数
* @param 无
* @retval 返回接收到的数据
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA)
{Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
发送与接收应答函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AbQSBNbw-1681634737180)(E:\单片机笔记\assets\1679188977713.png)]
/**
* @brief I2C发送应答函数
* @param 要发送的应答指令
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
/**
* @brief I2C接收应答函数
* @param 无
* @retval 返回接收到的应答 0为应答,1为非应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
AT24C02发送数据逻辑
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xeyUTaJr-1681634737181)(E:\单片机笔记\assets\1679189161124.png)]
注意:AT24C02的固定地址为1010,可配置地址本开发板上为000
所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1
/**
* @brief AT24C02写入数据函数
* @param 写入地址 写入数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}
/**
* @brief AT24C02读出数据函数
* @param 要读取数据的地址
* @retval 读取到的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveAck();
Data=I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Data;
}
注意:51是存在写周期的,所以瞬间写入,瞬间读出,会导致数据存储失败
考虑延时函数,延时5us
rief I2C接收数据函数
- @param 无
- @retval 返回接收到的数据
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA)
{Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
发送与接收应答函数
[外链图片转存中...(img-AbQSBNbw-1681634737180)]
```c
/**
* @brief I2C发送应答函数
* @param 要发送的应答指令
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
/**
* @brief I2C接收应答函数
* @param 无
* @retval 返回接收到的应答 0为应答,1为非应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
AT24C02发送数据逻辑
[外链图片转存中…(img-xeyUTaJr-1681634737181)]
注意:AT24C02的固定地址为1010,可配置地址本开发板上为000
所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1
/**
* @brief AT24C02写入数据函数
* @param 写入地址 写入数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}
/**
* @brief AT24C02读出数据函数
* @param 要读取数据的地址
* @retval 读取到的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveAck();
Data=I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Data;
}
注意:51是存在写周期的,所以瞬间写入,瞬间读出,会导致数据存储失败
考虑延时函数,延时5us