前言
本文章是我在小蜜蜂B站视频下学习的,视频教学完后进行记录,此篇为视频学习笔记。
文章目录
- 前言
- LED指示灯的基本控制
- 74HC138(三八译码器)
- 74HC573锁存器
- 蜂鸣器和继电器的控制
- ULN2003
- 控制
- 三八译码器的模块化
- 共阳数码管
- 共阳数码管的静态显示
- 共阳数码管的动态显示
- 按键
- 独立按键
- 矩阵按键
- 中断系统与外部中断服务
- 中断服务函数编写
- 定时器
- 定时器综合应用
- PWM脉宽调制信号
- 操作
- 串口通信
- 基本操作
- 上位机发送数据给单片机执行
- 传输字符串
- BCD - DEC
- 存储器映射扩展技术
- 单总线温度传感器DS18B20
- 温度转换与读取流程
- Ds18b20数据处理
- 操作流程
- DS1302
- 基本信息
- 基本操作
- NE555定时器与频率测量
- 操作
- PCF8591
- 基本信息
- iic(i²c)通信协议理论讲解
- 代码编写
- 实际应用
- AT24C02存储器
- 基本信息
- 写入方式
- 读取方式
- 应用
- 超声波
- 基本信息
- 代码编写
LED指示灯的基本控制
74HC138(三八译码器)
类似于二进制的记忆方法
74HC573锁存器
蜂鸣器和继电器的控制
ULN2003
中间是非门输入1输出0,输入0输出1。
控制
关掉蜂鸣器的代码
void InitSystem(){
HC138_A2 = 1;
HC138_A1 = 0;
HC138_A0 = 1;//Y5
//或者:OutPutP0(5,0x00);
P0 = 0x00;
}
需要在N_BUZZ给低电平,蜂鸣器就会叫。回溯到输入端,需要在7B处给高电平,经过ULN2003反转后成为低电平;要想做到上述操作,就要使锁存器解锁,因此要给Y5低电平。
继电器分析过程类似,不赘述。
继电器吸合放开:
HC138_A2 = 1;
HC138_A1 = 0;
HC138_A0 = 1;//Y5
P0 = 0x10;//继电器吸合
Delay(60000);
Delay(60000);
P0 = 0x00;//放掉
三八译码器的模块化
为了后续代码整洁,把三八译码器进行了模块化。
void InitHC138(unsigned int n){
switch(n){
case 0:P2_7=0;P2_6=0;P2_5=0;break;
case 1:P2_7=0;P2_6=0;P2_5=1;break;
case 2:P2_7=0;P2_6=1;P2_5=0;break;
case 3:P2_7=0;P2_6=1;P2_5=1;break;
case 4:P2_7=1;P2_6=0;P2_5=0;break;
case 5:P2_7=1;P2_6=0;P2_5=1;break;
case 6:P2_7=1;P2_6=1;P2_5=0;break;
case 7:P2_7=1;P2_6=1;P2_5=1;break;
}
}
如果可以的话用16进制可以省略消影的延时,但是我对位操作符的运算不太熟悉。
共阳数码管
顺时针转从顶上那根开始分别是A B C D E F G小数点是 DP。
用哪个“8”是Y6锁存器控制,一个“8”的中的哪根管子亮就是Y7。
通过实验得出该数码管为共阳数码管。
共阳数码管的静态显示
0-9以及一些符号的段码(Segmented)用数组存起来:
unsigned char SEG[18] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xbf};
0~9为数字0-9,10-15为AbCdEF,16为-
然后把输出做一个函数,可以方便引用。
void ShowSMG(unsigned char pos,unsigned dat){
OutPutP0(6,0x01 << (pos-1));//选中公共端
OutPutP0(7,SEG[dat]);//输出数字
Delay(20);
}
注:此处延时为11.0592Hz以1毫秒为单位的。
至此,晶体管静态显示就变成了一个,选中、输出的过程。
做一个输出:
void SMGSTATIC(){
unsigned char i,j;
for(i = 1;i <= 8;i++){
for(j = 0;j <= 9;j++){
ShowSMG(i,j);
Delay(1000);
}
}
}
展示:
数码管静态显示
共阳数码管的动态显示
实际上是极短时间内的静态显示,由于人眼的视觉残留和发光二极管的余晖效应(灭了没完全灭),而呈现的多位数输出效果。
void main()
{
while(1){
ShowSMG(1,1);
Delay(1);
ShowSMG(2,2);
Delay(1);
ShowSMG(3,3);
Delay(1);
}
}
展示:
按键
独立按键
unsigned char Key(){
unsigned char KeyNumber = 0;
if(P3_0 == 0){Delay(20);while(P3_0 == 0);Delay(20);KeyNumber = 7;}
if(P3_1 == 0){Delay(20);while(P3_1 == 0);Delay(20);KeyNumber = 6;}
if(P3_2 == 0){Delay(20);while(P3_2 == 0);Delay(20);KeyNumber = 5;}
if(P3_3 == 0){Delay(20);while(P3_3 == 0);Delay(20);KeyNumber = 4;}
return KeyNumber;
}
松开给反馈
若要按下给反馈,则将执行的语句写入两段消抖之间。
矩阵按键
#include <REGX52.H>
#include "Delay.h"
sfr P4 = 0xC0;
sbit C1 = P4^4;
sbit C2 = P4^2;
sbit C3 = P3^5;
sbit C4 = P3^4;
sbit R1 = P3^0;
sbit R2 = P3^1;
sbit R3 = P3^2;
sbit R4 = P3^3;
/**
*@brief 矩阵按键返回键码
*@param 无
*@retval 返回键码,但是是从1-16,而非板子上的4-19.
*/
unsigned char MatrixKey(){
unsigned char KeyNumReturn;
R1 = 0;R2 = 1;R3 = 1;R4 = 1;
C1 = 1;C2 = 1;C3 = 1;C4 = 1;
if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 4;}
if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 8;}
if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 12;}
if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 16;}
R1 = 1;R2 = 0;R3 = 1;R4 = 1;
C1 = 1;C2 = 1;C3 = 1;C4 = 1;
if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 3;}
if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 7;}
if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 11;}
if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 15;}
R1 = 1;R2 = 1;R3 = 0;R4 = 1;
C1 = 1;C2 = 1;C3 = 1;C4 = 1;
if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 2;}
if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 6;}
if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 10;}
if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 14;}
R1 = 1;R2 = 1;R3 = 1;R4 = 0;
C1 = 1;C2 = 1;C3 = 1;C4 = 1;
if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 1;}
if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 5;}
if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 9;}
if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 13;}
return KeyNumReturn;
}
中断系统与外部中断服务
一般来说,51单片机有5个中断源(忽略定时/计数器2),分2个优先级,这个5个中断源按照自然优先级从高到低依次为:
void Int0_Routine(void) interrupt 0;
void Timer0_Rountine(void) interrupt 1;
void Int1_Routine(void) interrupt 2;
void Timer1_Rountine(void) interrupt 3;
void UART1_Routine(void) interrupt 4;//串口中断
void ADC_Routine(void) interrupt 5;
void LVD_Routine(void) interrupt 6;
void PCA_Routine(void) interrupt 7;
void UART2_Routine(void) interrupt 8;
void SPI_Routine(void) interrupt 9;
void Int2_Routine(void) interrupt 10;
void Int3_Routine(void) interrupt 11;
void Timer2_Routine(void) interrupt 12;
void Int4_Routine(void) interrupt 16;
每个中断源都对应着一个固定的入口地址,也就是中断向量,它们依次是:
中断服务函数编写
一般情况下,中断的处理函数有两个,其一为中断初始化函数,其二为中断服务函数。初始化函数就是一个普通的函数,而中断服务函数却有特殊的格式要求:
<1> 中断函数没有返回值,也不能带参数。
<2> 函数名后面要跟一个关键字interrupt,说明这是一个中断服务函数。
<3> 在关键字interrupt后面要跟上中断号,说明这个中断服务函数是为哪个中断服务的。
void Init_INT0(){
IT0 = 1;
EX0 = 1;
EA = 1;
}
void ServiceINT0() interrupt 0{
L8 = 0;
Delay(5000);
Delay(5000);
Delay(5000);
Delay(5000);
L8 = 1;
}
这个功能是在L1闪烁的时候,L8插进来亮一会儿。
中断演示
中断内事情要少做,因此,把任务放在中断外面,中断函数内部就转换一个自定义的状态就好了。
unsigned char TEST;
void ServiceINT0() interrupt 0{
TEST++;
}
void main()
{
Init_INT0();
InitHC138(4);
while(1){
Working();
Delay(1);
if(TEST){CheckInterrupt();TEST = 0;}
}
}
定时器
寄存器初始赋值:TH0 = (65535 - 脉冲数) / 256;
选择定时器0,因此高四位全部0;
用TR0来启动定时器,GATE为0;
选择定时功能,C/T为0;
选择16为,M1 M0为01;
因此给寄存器TMOD = 0x01;
定时器的中段初始化函数也可以在烧录软件种获取,但是所得代码还需要添加:
ET0 = 1;
EA = 1;
来打开。
示例:
void Timer0_Init(void) //10毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xF0; //设置定时初始值
TH0 = 0xD8; //设置定时初始值
ET0 = 1;
EA = 1;
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
其服务函数为:
void ServiceTimer0()interrupt 1{
count++;
if(count%10 == 0){
L1 = ~L1;
}
if(count == 100){
L8 = ~L8;
count = 0;
}
}
展示:
中断演示
定时器综合应用
#include <STC15F2K60S2.H>
#include "Delay.h"
#include "InitHC138.h"
sbit S4 = P3^3;
sbit S5 = P3^2;
unsigned char SEG[18] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xbf};
unsigned char Pos[9] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,};
unsigned char t_min,t_sec,t_50ms;//分、秒、50毫秒
unsigned char Check = 0;
void Timer0_Init(void) //50毫秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x00; //设置定时初始值
TH0 = 0x4C; //设置定时初始值
EA = 1;
ET0 = 1;
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
ShowSMG(unsigned char pos,dat){
OutPutP0(6,Pos[pos]);
OutPutP0(7,SEG[dat]);
Delay(1);
}
void Display(){
ShowSMG(7,t_50ms/10);ShowSMG(8,t_50ms%10);
ShowSMG(6,16);
ShowSMG(4,t_sec/10);ShowSMG(5,t_sec%10);
ShowSMG(3,16);
ShowSMG(1,t_min/10);ShowSMG(2,t_min%10);
}
void ScanKey(){
if(S4 == 0){//S4用于暂停、启动
Delay(10);
if(S4 == 0){
TR0 = ~TR0;
while(S4 == 0){
Display();
}
}
}
if(S5 == 0){//S5用于清零
if(S5 == 0){
t_min = 0;
t_sec = 0;
t_50ms= 0;
}
}
}
void main()
{
Timer0_Init();
while(1){
ScanKey();
Display();
}
}
void ServiceTimer0()interrupt 1{
t_50ms++;
if(t_50ms == 20){
t_sec++;
t_50ms = 0;
}
if(t_sec == 60){
t_min++;
t_sec = 0;
}
if(t_min == 90){
t_min = 0;
}
}
PWM脉宽调制信号
用途:直流电机调速 灯泡亮度 声音大小……
原理:把一个周期的PWM脉冲(比如100Hz,10000微秒)分成几份(比如100份,100微秒),让其中的50份置低电平,剩下的50份置高电平,这样在一个周期里面的LED灯就会呈现只有一半的亮度。
操作
来写一个题目:
据此写出一个100微秒的定时器,TR0不急着打开:
定义一个count来数产生中断的次数,一次中断代表100微秒,pwm_duty用来标记转换电平所在时刻,比如60,就是在6000微秒后转换电平。
每到1个周期后把count清零,并且让灯重新亮回去
unsigned char count = 0;
unsigned char pwm_duty = 0;
void Timer0_Init(void) //100微秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x9C; //设置定时初始值
TH0 = 0xFF; //设置定时初始值
EA = 1;
ET0 =1;
TF0 = 0; //清除TF0标志
}
void ServiceTimer0 ()interrupt 1{
count++;
if(count == pwm_duty){
L1 = 1;
}else if(count == 100){
L1 = 0;
count = 0;
}
}
按键相关:
sbit S7 = P3^0;
unsigned char stat = 0;//熄灭状态
void ScanKeys(){
if(S7 == 0){
Delay(5);
if(S7 == 0){
switch(stat){
case 0:L1 = 0;TR0 = 1;pwm_duty = 10;stat = 1;break;
case 1:L1 = 0;pwm_duty = 50;stat = 2;break;
case 2:L1 = 0;pwm_duty = 90;stat = 3;break;
case 3:L1 = 1;TR0 = 0;stat = 0;break;
}
while(S7 == 0);
}
}
}
串口通信
波特率:串口每秒传输的位数
基本操作
初始化可以在烧录程序上获取,但是要记得中段和总中段都要打开:
ES = 1;
EA = 1;
void Uart1_Init(void) //9600bps@12.000MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器时钟1T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xD9; //设置定时初始值
TH1 = 0xD9; //设置定时重载值
ET1 = 0; //禁止定时器中断
EA = 1;
ES = 1;
TR1 = 1; //定时器1开始计时
}
有两种发送完成的方式,可以参考这个文章:
单片机的串口通信—查询和中断
上位机发送数据给单片机执行
先自定义一个命令变量:
unsigned char Command = 0x00;
这里采用的是中断方式来接收数据:
void ServiceUART()interrupt 4{
if(RI == 1){
Command = SBUF;
RI = 0;
}
}
然后把SBUF寄存器中的值提取出来并存在Command里面。
然后做一个将值转换成命令的程序:
void Working(){
if(Command != 0x00){
switch(Command & 0xf0){
case 0xa0:P0 = ((P0 | 0x0f)&(~Command | 0xf0));Command = 0x00;break;
case 0xb0:P0 = ((P0 | 0xf0)&(~Command<<4 | 0x0f));Command = 0x00;break;
case 0xc0:SendString("GoGoGo!!!");Command = 0x00;break;
}
}
}
主函数如下:
void main()
{
InitSystem();
Uart1_Init();
SendString("HelloWorld!\0");
while(1){
Working();
}
}
效果如下:
传输字符串
void SendByte(unsigned char dat){
SBUF = dat;
while(TI == 0);
TI = 0;
}
void SendString(unsigned char *str){
while(*str != '\0'){
SendByte(*str++);
}
}
BCD - DEC
BCD码转十进制:DEC = BCD / 16 * 10 + BCD % 16 ;
详解:先将BCD的前一位提出BCD/16,再提出BCD后一位BCD%16。最后将两位数整合转换成十进制。多位BCD码同理。
十进制转BCD码:BCD = DEC / 10 * 16 + DEC % 10 ;同上分别提出高低两位,做BCD转换。
————————————————
SendByte((hour / 10 << 4) | (hour % 10));
SendByte((min / 10 << 4) | (min % 10));
SendByte((sec / 10 << 4) | (sec % 10));
存储器映射扩展技术
引入头文件:absacc.h
将J13跳线帽短接1-2后,可以点亮LED灯只用一行代码就可以实现
XBYTE[0xc000] = 0xfe;
但是在蓝桥杯单片机上用MM模式的话,P36的引脚会被占用,在使用矩阵键盘时会受影响。
单总线温度传感器DS18B20
温度转换与读取流程
微处理器读取单个DS18B20的温度数据,可参考以下步骤:
【1】DS18B20复位。
【2】写入字节0xCC,跳过ROM指令。
【3】写入字节0x44,开始温度转换。
【4】延时700~900ms。
【5】DS18B20复位。
【6】写入字节0xCC,跳过ROM指令。
【7】写入字节0xBE,读取高速暂存器。
【8】读取暂存器的第0字节,即温度数据的LSB。
【9】读取暂存器的第1字节,即温度数据的MSB。
【10】 DS18B20复位。,表示读取数据结束。
【11】将LSB和MSB整合成为一个16位数据。
【12】判断读取结果的符号,进行正负温度的数据处理。
Ds18b20数据处理
DS18B20以16位带符号位扩展的二进制***补码***形式读出。
低4位为小数部分,中间7位为整数部分。
高5位为扩展符号位,即BIT15~BIT11为00000,读出的数据为正温度,若为11111,则为负温度。在应用开发中,首先要对读出的温度数据的符号位进行判断,再根据正负温度的不同,进行相应的处理。
DS18B20的分辨率为0.0625。
读出数据为正温度时,将LSB和MSB整合成的16位整数,直接乘以0.0625即可。
读出数据为负温度时,则需要将LSB和MSB整合成的16位整数,取反加1后,再乘以0.0625,因为温度数据是以补码形式表示的。
例如:
读出结果为00A2H,温度值 = 162×0.0625 = 10.125 摄氏度。
读出结果为FF5EH,取反加1就是00A2H,温度值则为 -10.125 摄氏度。
上电复位的时候,温度寄存器中的值为0x0550,+85摄氏度
操作流程
官方给的底层驱动代码(2023)没有定义总线的地址,加上:
sbit DQ = P1^4;
根据模块化编程的格式,编辑好底层驱动代码的头文件并且在主程序中引用。
#ifndef __ONEWIRE_H
#define __ONEWIRE_H
void Delay_OneWire(unsigned int t);
void Write_DS18B20(unsigned char dat);
unsigned char Read_DS18B20(void);
bit init_ds18b20(void);
#endif
根据上面提到的操作步骤,引用合适的函数编写读取温度的代码。
void Read_DS18B20_TEMP(){
unsigned char LSB,MSB;
init_ds18b20();//【1】DS18B20复位。
Write_DS18B20(0xcc);//【2】写入字节0xCC,跳过ROM指令。
Write_DS18B20(0x44);//【3】写入字节0x44,开始温度转换。
DelaySMG(700);//【4】延时700~900ms。
init_ds18b20();//【5】DS18B20复位。
Write_DS18B20(0xcc);//【6】写入字节0xCC,跳过ROM指令。
Write_DS18B20(0xbe);//【7】写入字节0xBE,读取高速暂存器。
LSB = Read_DS18B20();//【8】读取暂存器的第0字节,即温度数据的LSB。
MSB = Read_DS18B20();//【9】读取暂存器的第1字节,即温度数据的MSB。
init_ds18b20();//【10】 DS18B20复位。,表示读取数据结束。
temp = 0x0000;
temp = MSB;
temp = (temp << 8) | LSB;//【11】将LSB和MSB整合成为一个16位数据。
if((temp & 0xf800) == 0x0000){//【12】判断读取结果的符号,进行正负温度的数据处理。
temp >>= 4;//把小数位去掉
temp = temp * 10;//整数位乘十
temp = (temp + (LSB & 0x0f)) * 0.625;//把小数位加回去,再乘分辨率
}
}
如果发现呈现的是83.5度(有可能还有缺位数,3.5之类的),就要去改官方给的底层驱动代码。
我所用的原来的驱动的测试环境是12T,12MHz,将烧录软件的晶振频率修改,并将初始化函数的延时相关数据全部乘以10~12,需要慢慢调,我乘以十二比较清晰。
bit init_ds18b20(void)
{
bit initflag = 0;
DQ = 1;
Delay_OneWire(144);
DQ = 0;
Delay_OneWire(960);
DQ = 1;
Delay_OneWire(120);
initflag = DQ;
Delay_OneWire(60);
return initflag;
}
温度计
DS1302
基本信息
日历时钟寄存器
1、秒寄存器(80H 和 81H)的位 7(CH)为时钟暂停标志。 CH为0时,时钟振荡停止; CH为1时,时钟开始运行。
2、控制寄存器(8EH和8FH)的位7(WP)为写保护位:
WP 为0时,可对任何的时钟或 RAM 寄存器进行写操作;
WP 为1时,禁止对任一寄存器进行写操作。
控制字格式与数据定义
都是用BCD码进行存储
BIT7:必须为1,若为0,则不能把数据写入 DS1302中。
BIT6:0表示存取日历时钟数据,1表示存取 RAM 数据;
BIT5~BIT1:表示操作单元的地址。
BIT0:0表示写操作,1表示读操作。
DS1302读寄存器和写寄存器的地址是不一样的。将读寄存器地址、写寄存器地址和日历时钟数据定义成三个数组。(提前定义可以直接引用)
//定义DS1302读操作的日历时钟存储器地址
unsigned char DS1302_ReadAddress[7] = {0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
//定义Ds1302写操作的日历时钟存储器地址
unsigned char DS1302_WriteAddress[7]= {0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};
//定义DS1302日历时钟的7个配置参数:18年2月17日23时50分30秒
unsianed char TIME[7] =(0x30,0x50,0x23, 0x17, 0x02, 0x06, 0x18}:
SPI接口时序
在控制字指令输入后的下一个SCLK时钟信号的下降沿,数据从DS1302读出。
在控制字指令输入后的下一个SCLK时钟信号的上升沿,数据被写入DS1302;
基本操作
创建头文件、核对引脚
#include <STC15F2K60S2.H>
#ifndef __DS1302_H
#define __DS1302_H
sbit SCK = P1^7;
sbit SDA = P2^3;
sbit RST = P1^3;
void Write_Ds1302(unsigned char temp);
void Write_Ds1302_Byte( unsigned char address,unsigned char dat );
unsigned char Read_Ds1302_Byte ( unsigned char address );
#endif
初始化以及显示函数
void DS1302_Config(){
char i;
Write_Ds1302_Byte(0x8e,0x00);//解锁,使得数据可以被写入
for(i = 0;i < 7;i++){
Write_Ds1302_Byte(Write_DS1302_adrr[i],time[i]);
}
Write_Ds1302_Byte(0x8e,0x80);//恢复保护
}
void Read_DS1302_Timer(){
char i;
for(i = 0;i < 7;i++){
time[i] = Read_Ds1302_Byte(Read_DS1302_adrr[i]);
}
}
void ShowDate(){
ShowSMG(1,Seg_Table[(time[2] / 16)]);
ShowSMG(2,Seg_Table[(time[2] % 16)]);
ShowSMG(3,Seg_Table[16]);
ShowSMG(4,Seg_Table[(time[1] / 16)]);
ShowSMG(5,Seg_Table[(time[1] % 16)]);
ShowSMG(6,Seg_Table[16]);
ShowSMG(7,Seg_Table[(time[0] / 16)]);
ShowSMG(8,Seg_Table[(time[0] % 16)]);
}
main函数部分
void main()
{
DS1302_Config();
while(1){
Read_DS1302_Timer();
ShowDate();
}
}
NE555定时器与频率测量
由电阻和电容形成的RC振荡电路,左上方的滑动变阻器(在单片机主板的右下角“频率输出旋钮”)可以改变频率,其输出的信号经过NE555成为方波后可以引发计时器0的电平变化形成计数,因此可以写一个程序来采样、读取它的的频率。
也可以采取它的频率建立数学模型做一些控制输出的操作,比如灯泡亮灭、蜂鸣器音调等等。
操作
将J3处的SIGNAL和P34短接
变量定义:
unsigned int count_f =0;//实时计数
unsigned int dat_f = 0;//采取一秒的次数
unsigned char count = 0;//50ms*20=1s
定时器的初始化:
void TimerInit(void) //50毫秒@12.000MHz
{
TMOD = 0x07; //设置定时器模式
TL0 = 0xff; //设置定时初值
TH0 = 0xff; //设置定时初值
TL1 = 0xB0; //设置定时初值
TH1 = 0x3C; //设置定时初值
TF0 = 0; //清除TF0标志
TF1 = 0; //清除TF1标志
ET1 = 1;
ET0 = 1;
TR0 = 1; //计数器0开始工作
TR1 = 1; //定时器1开始计时
EA = 1;
}
中断服务函数与显示模块:
void Timer0_Isr(void) interrupt 1
{
count_f++;
}
void Timer1_Isr(void) interrupt 3
{
count++;
if(count == 20){
dat_f = count_f;
count = 0;
count_f = 0;
}
}
void ShowHz(){
ShowSMG(1,Seg_Table[15]);
ShowSMG(2,0xff);ShowSMG(3,0xff);
if(dat_f > 9999){ShowSMG(4,Seg_Table[dat_f / 10000]);}
if(dat_f > 999){ShowSMG(5,Seg_Table[(dat_f / 1000) % 10]);}
if(dat_f > 99){ShowSMG(6,Seg_Table[(dat_f / 100) % 10]);}
if(dat_f > 9){ShowSMG(7,Seg_Table[(dat_f / 10) % 10]);}
ShowSMG(8,Seg_Table[dat_f % 10]);
}
主程序:
void main()
{
TimerInit();
while(1){
ShowHz();
}
}
展示:
555定时器与频率测量
PCF8591
用于数模转换、读取电压
基本信息
A0 A1 A2接地说明A0 = A1 = A2 = 0;
**SCL(时钟线)**连接引脚P20,**SDA(数据线)**连接引脚P21
如下图是芯片、光敏电阻以及滑动变阻器所在位置
AIN1 和 AIN3分别外接 光敏电阻 和 滑动变阻器 来采集数据
地址:前四位固定,A0 A1 A2均接地固定为0,只有最后一位可变用于读写
控制字如下图
第三第四位(从左往右)蓝桥杯单片机默认00
ANALOGUE OUTPUT ENABLE FLAG表示是否允许输出
iic(i²c)通信协议理论讲解
用两根线来进行通讯
SDA为数据线,SCL为时钟线
谁控制SCL谁就是主设备,反之就是从设备
时序
开始信号:SCL为高电平时,SDA由低到高变化,开始传输数据。
结束信号:SCL为高电平时,SDA由高到低变化,结束传输数据。
应答信号:每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
应答信号为低电平的时候为有效应答(ACK) 表示成功接收到了该字节
应答信号为高电平时规定为非应答位(NACK) 一般表示字节没接收成功
每发送一个字节(8个bit)“在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答
发送数据把byt中的数据与上0x80,读取byt的第一位,如果为一,那就把SDA此时电平拉高,传输数据1,然后进入下一个循环,将byt的数据往左移一位,读取此时的byt的第一位(原先的第二位),进行相同操作,周而复始,将八位数据通过SDA的高低电平进行传输。
读取数据先让da向左移一位,但da初始是0x00,没变化,然后从SDA读取数据,如果SDA为就让da的第一位为1,周而复始,完成数据的采集,数据采集和数据接受是一个镜像的过程,这也是为什么在发送数据的时候进行按位与操作的时候采用0x80而不是0x01,否则数据就会反过来。
A/D转换:模拟量(Analogue signal)与数字量(Digital Signal)之间的转换。
代码编写
AD读取
1.I²C传输开始信号。
2.写PCF8591地址。
3.等待PCF8591回应。
4.读PCF8591
5.等待主机回应。
6.I²C传输结束。
unsigned char AD_READ(unsigned char addr){
unsigned char temp;
I2CStart();//1.I²C传输开始信号。
I2CSendByte(0x90);//2.写PCF8591地址。
I2CWaitAck();
I2CSendByte(addr);//指向滑动变阻器0x43或者光敏电阻0x41
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);//4.读PCF8591
I2CWaitAck();//5.等待主机回应。
temp = I2CReceiveByte();//读取数据
I2CSendByte(1);//非应答信号,为了告诉主机不再读取数据
I2CStop();//I²C传输结束。
return temp;
}
DA转换
1、I2C传输开始信号。
2、写PCF8591地址。
3、等待PCF8591回应。
4、写控制字节。
5、等待PCF8591回应。
6、写DAC的值。
7、等待PCF8591回应。
(6到7步可以一直循环进行,且DAC的值可以一直改变,只要没有重新12C开始信号,或者结束信号,DAC输出就一直是最后一个输出的值。)
8、12C传输结束。
void Da_Write(unsigned char addr){//addr代表数字量,比如255(5V)
I2CStart();//1、I2C传输开始信号。
I2CSendByte(0x90);//2、写PCF8591地址。
I2CWaitAck();
I2CSendByte(0x41);//允许DA输出
I2CWaitAck();
I2CSendByte(addr);//写入模拟量数据
I2CWaitAck();
I2Cstop();
}
实际应用
初始状态晶体管无示数。
按下独立按键S4后切换到状态1,右边三位显示滑动变阻器电压示数(0~255),左边是三位显示电压示数,保留两位小数,中间用两个-隔开。
按下独立按键S4后切换到状态2,右边三位显示光敏电阻电压示数(0~255),左边是三位显示电压示数,保留两位小数,中间用两个-隔开。
按下S4循环切换状态
代码:
#include <STC15F2K60S2.H>
#include "Delay.h"
#include "InitHC138.h"
#include "iic.h"
unsigned char dat;
unsigned char Mode = 0;
unsigned int tmp = 0;
code unsigned char Seg_Table[17] =
{
0xc0, //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
0xbf //-
};
unsigned char AD_READ(unsigned char addr){
unsigned char temp;
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStart();
I2CSendByte(0x91);
I2CWaitAck();
temp = I2CReceiveByte();
I2CSendByte(1);
I2CStop();
return temp;
}
void DA_WRITE(unsigned char addr){
I2CStart();
I2CSendByte(0x90);
I2CWaitAck();
I2CSendByte(0x41);
I2CWaitAck();
I2CSendByte(addr);
I2CWaitAck();
I2CStop();
}
void Showdat1(){
dat = AD_READ(0x43);
dat = AD_READ(0x43);
dat = AD_READ(0x43);
ShowSMG(8,Seg_Table[dat % 10]);
ShowSMG(7,Seg_Table[dat / 10 % 10]);
ShowSMG(6,Seg_Table[dat / 100 % 10]);
tmp = dat * 100 / 51;
ShowSMG(5,Seg_Table[16]);
ShowSMG(4,Seg_Table[16]);
ShowSMG(3,Seg_Table[tmp % 10]);
ShowSMG(2,Seg_Table[(tmp / 10 % 10)]);
ShowSMG(1,Seg_Table[tmp / 100 % 10] & 0x7f);
}
void Showdat2(){
dat = AD_READ(0x41);
dat = AD_READ(0x41);
dat = AD_READ(0x41);
ShowSMG(8,Seg_Table[dat % 10]);
ShowSMG(7,Seg_Table[dat / 10 % 10]);
ShowSMG(6,Seg_Table[dat / 100 % 10]);
tmp = dat * 100 / 51;
ShowSMG(5,Seg_Table[16]);
ShowSMG(4,Seg_Table[16]);
ShowSMG(3,Seg_Table[tmp % 10]);
ShowSMG(2,Seg_Table[tmp / 10 % 10]);
ShowSMG(1,Seg_Table[tmp / 100 % 10] & 0x7f);
}
void ScanKey(){
if (P33 == 0){
Delay(5);
while(P33 == 0){
ShowSMG(8,Seg_Table[dat % 10]);
ShowSMG(7,Seg_Table[dat / 10 % 10]);
ShowSMG(6,Seg_Table[dat / 100 % 10]);
ShowSMG(5,Seg_Table[16]);
ShowSMG(4,Seg_Table[16]);
ShowSMG(3,Seg_Table[tmp % 10]);
ShowSMG(2,Seg_Table[(tmp / 10 % 10)]);
ShowSMG(1,Seg_Table[tmp / 100 % 10] & 0x7f);
}
Mode++;
Mode = Mode % 3;
}
}
void main(){
InitSysAll();
while(1){
ScanKey();
if(Mode == 1){
Showdat1();
}else if(Mode == 2){
Showdat2();
}else if(Mode == 0){
ShowSMG(3,0xff);
ShowSMG(2,0xff);
ShowSMG(1,0xff);
}
DA_WRITE(255);
}
}
展示:
PCF8591
AT24C02存储器
基本信息
02代表2K的内存大小,大概256字节。这就是一个内存卡
A0~A2器件地址,均接地,均为0.
SDL数据线 SCL时钟线。
WP接地不使能。
写入方式
字节写
void Write_24C02_Byte(unsigned char addr, unsigned char dat)
{
IIC_Start(); //起始信号
IIC_SendByte(0xa0); //EEPROM的写设备地址
IIC_WaitAck(); //等待从机应答
IIC_SendByte(addr); //内存单元地址
IIC_WaitAck(); //等待从机应答
IIC_SendByte(dat); //内存写入数据
IIC_WaitAck(); //等待从机应答
IIC_Stop(); //停止信号
}
页写入
//字符数组;以八位倍数的地址表示哪一页去写地址;写多少数据(不要大于8)
void Write_24C02_Page(unsigned char *EEPROM_String, unsigned char addr,unsigned char num)
{
I2CStart();//发送开启信号
I2CSendByte(0xa0); //EEPROM的写设备地址
I2CWaitAck(); //等待从机应答
I2CSendByte(addr); //写入要存储的数据地址
I2CWaitAck(); //等待从机应答
while(num--)
{
I2CSendByte(*EEPROM_String++);//将要的信息写入
I2CWaitAck(); //等待从机应答
I2C_Delay(200);
}
I2CStop(); //停止信号
}
读取方式
任意地址读取
unsigned char Read_24C02(unsigned char addr)
{
unsigned char tmp;
//首先,进行一个伪写操作
IIC_Start(); //起始信号
IIC_SendByte(0xa0); //EEPROM的写设备地址
IIC_WaitAck(); //等待从机应答
IIC_SendByte(addr); //内存单元地址
IIC_WaitAck(); //等待从机应答
//然后,开始字节读操作
IIC_Start(); //起始信号
IIC_SendByte(0xa1); //EEPROM的读设备地址
IIC_WaitAck(); //等待从机应答
tmp = IIC_RecByte(); //读取内存中的数据
IIC_SendAck(1); //产生非应答信号
IIC_Stop(); //停止信号
return tmp;
}
连续顺序读取
void Read_24C02_Page(unsigned char *Read_EEPROM_String, unsigned char addr,unsigned char num)
{
I2CStart();//发送开启信号
I2CSendByte(0xa0); //EEPROM的写设备地址
I2CWaitAck(); //等待从机应答
I2CSendByte(addr); //写入要存储的数据地址
I2CWaitAck(); //等待从机应答
while(num--)
{
*Read_EEPROM_String++ = I2CReceiveByte();//将要的信息写入
if(num)I2CSendAck(0);发送应答
else I2CSendAck(1);不应答
}
I2CStop(); //停止信号
}
应用
我用字节写的方式写一段应用。
短按独立按键S4切换编辑数据,长按S4切换编辑、写入模式。
按S5切换数据位数
按S6给所在位数加1
中间显示编辑位数,1代表十位,2代表个位。
1和7位置的晶体管代表正在编辑的数字
#include <STC15F2K60S2.H>
#include "Delay.h"
#include "InitHC138.h"
#include "iic.h"
unsigned char DataPosi = 0;//数据存储位置
unsigned char EditPosi = 0;//编辑位置
//用二维数组来存储数字,[0][0],代表要写入的第一个数据的十位数
unsigned char Dat[2][2] = {0};
unsigned char Mode = 0;//模式编号,0为编辑(写入)模式,1为读取模式
unsigned char Key_Stat = 0;//标记S4的下按与否
unsigned char count = 0;//长按计时
//***************读写驱动代码
void Write_24C02_Byte(unsigned char addr, unsigned char dat)
{
I2CStart(); //起始信号
I2CSendByte(0xa0); //EEPROM的写设备地址
I2CWaitAck(); //等待从机应答
I2CSendByte(addr); //内存单元地址
I2CWaitAck(); //等待从机应答
I2CSendByte(dat); //内存写入数据
I2CWaitAck(); //等待从机应答
I2CStop(); //停止信号
}
unsigned char Read_24C02_Byte(unsigned char addr)
{
unsigned char tmp;
//首先,进行一个伪写操作
I2CStart(); //起始信号
I2CSendByte(0xa0); //EEPROM的写设备地址
I2CWaitAck(); //等待从机应答
I2CSendByte(addr); //内存单元地址
I2CWaitAck(); //等待从机应答
//然后,开始字节读操作
I2CStart(); //起始信号
I2CSendByte(0xa1); //EEPROM的读设备地址
I2CWaitAck(); //等待从机应答
tmp = I2CReceiveByte(); //读取内存中的数据
I2CSendAck(1); //产生非应答信号
I2CStop(); //停止信号
return tmp;
}
//***************共阳晶体管段码
code unsigned char Seg_Table[17] =
{
0xc0, //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
0xbf //-
};
//***************显示函数
void ShowData(){
ShowSMG(1,Seg_Table[Dat[0][0]]);
ShowSMG(2,Seg_Table[Dat[0][1]]);
ShowSMG(7,Seg_Table[Dat[1][0]]);
ShowSMG(8,Seg_Table[Dat[1][1]]);
}
//***************模式扫描函数
void ScanMode(){
if(Mode == 1){//读取然后显示
Dat[0][0] = Read_24C02_Byte(0x00) / 10;
Dat[0][1] = Read_24C02_Byte(0x00) % 10;
Dat[1][0] = Read_24C02_Byte(0x01) / 10;
Dat[1][1] = Read_24C02_Byte(0x01) % 10;
ShowData();
}else if(Mode == 0){//根据不同情况显示编辑页面
ShowData();
switch(DataPosi){
case 0:ShowSMG(3,Seg_Table[16]);break;
case 1:ShowSMG(6,Seg_Table[16]);break;
}
switch(EditPosi){
case 0:ShowSMG(4,Seg_Table[0]);ShowSMG(5,Seg_Table[1]);break;
case 1:ShowSMG(4,Seg_Table[0]);ShowSMG(5,Seg_Table[2]);break;
}
}
}
//***************按键扫描模块
void ScanKey(){
if(P33 == 0){
Delay(5);
while(P33 ==0){
ShowData();
Key_Stat = 1;
}
Key_Stat = 0;
if(count <= 35){
EditPosi = 0;
DataPosi++;
DataPosi = DataPosi % 2;
}else if(count > 35){
Mode++;
Mode %= 2;
}
}
if(P32 == 0){
Delay(5);
while(P32 == 0){
ShowData();
}
EditPosi++;
EditPosi %= 2;
}
if(P31 == 0){
Delay(5);
while(P31 == 0){
ShowData();
}
Dat[DataPosi][EditPosi]++;
Dat[DataPosi][EditPosi] %= 9;
}
if(P30 == 0){
Delay(5);
while(P30 == 0){
ShowData();
Write_24C02_Byte((0x00 + DataPosi),(Dat[DataPosi][0] * 10) + Dat[DataPosi][1]);
}
}
}
//***************定时器与中断服务函数,用于长短按检测
void Timer0_Isr(void) interrupt 1
{
if(Key_Stat == 1){
count++;
}
}
void Timer0_Init(void) //10毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xF0; //设置定时初始值
TH0 = 0xD8; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //使能定时器0中断
EA = 1;
}
//***************主函数
void main()
{
Timer0_Init();
while(1){
ShowData();
ScanKey();
ScanMode();
}
}
展示:
AT24C02
超声波
基本信息
N A1为发送部分
CX20106A为接收部分(N B1)
使用超声波模块需要用跳线帽把短接1-3和2-4(板子上也有说明)
TX= P1^0:
RX= P1^1;
当TX=1时为成功发送信号
当TX=0时为发送信号失败
当RX=0时为成功接受信号
当RX=1时为接受信号失败
声速V=332+0.607t(m/s)
距离L = V * T / 2(m)
实现步骤
实现步骤
1-产生8个40KHz的超声波信号,通过TX引脚发射出去,
2-启动定时器,计算计数脉冲。
3-等待超声波信号返回,如果接收到反射回来的信号,RX引脚变为低电平。
4-停止定时器,读取脉冲个数,即获得时间T。
5.-根据公式,进行距离的计算
代码编写
方波是占空比为50%的矩形波,它是一种非正弦周期函数的波形。方波包括有低电平为零的方波与低电平为负的方波。本文中的方波均为低电平为负的方波。
void Ut_Wave_Init()//超声波初始化函数 产生8个40Mhz的方波信号
{
unsigned char i;
for(i = 0;i < 8;i++)
{
TX = 1;
Delay(12);
TX = 0;
Delay(12);
}
}
读取超声波
void Ut_Wave_Read()
{
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x00; //设置定时初始值
TH1 = 0x00; //设置定时初始值
Ut_Wave_Init();
TR1 = 1; //定时器1开始计时
while((RX == 1) && (TF1 == 0));
TR1 = 0; //定时器1停止计时
if(TF1 == 0){//定时器没有溢出,没有超出测量范围
Time = TH1 << 8 | TL1;
Distance = Time * 17 / 1000;//给出距离
}else{
TF1 = 0;//清楚溢出标志位
Distance = 999;
}
}
显示模块
void ShowDictance(){
if(Distance == 999){
ShowSMG(8,Seg_Table[16]);
ShowSMG(7,Seg_Table[16]);
ShowSMG(6,Seg_Table[16]);
}else {
ShowSMG(8,Seg_Table[Distance % 10]);
ShowSMG(7,Seg_Table[Distance / 10 % 10]);
ShowSMG(6,Seg_Table[Distance / 100 % 10]);
}
}
主程序
void main()
{
InitSysAll();
while(1){
Ut_Wave_Read();
ShowDictance();
}
}
展示
超声波