前言
为了确保程序的准确性,总结了以下要点
- 保证程序逻辑正确,其次再是“基于现象上”的正确,保证程序不会出现隐性问题
- 在逻辑正确的前提下,保证数据读取的实时性,尽量优化代码执行效率
- 对数据的处理,在确保程序完全正常运行的前提下,最大限度地提高内存空间的利用率
- 编写程序时,先写主框架,验证逻辑后,再依次验证其他模块
数码管/LED模块工作原理
例如:数码管的段选/位选输出由两个74HC573锁存器控制
锁存器送出数据的过程
-
P0输出8位数据
-
锁存器不使能,Q0~Q7为之前的数据,D0 ~ D7等待被送出
-
锁存器使能,数据送出,Q0~Q7即为P00 ~ P07的信号
注意:OE此处默认接GND
**如何控制锁存器送出数据的顺序/使锁存器单个送出数据? **
- 由一个或非门(U25)的四个输出控制四个锁存器的输入使能信号(Y7C~Y4C)
- 或非门的输入Y7 ~ Y4由一个译码器的输出控制,或非门的一端WR默认为低电平,那么这里的或非门就相当于一个输入为Y(n)的非门
- 其中74HC138(U24)的输入为P2口的P27~P25
- 通过推导可知,P27 ~ P25对应的三位二进制数即为后面会使能的锁存器,即111使得Y7=0,继而使得与非门输出Y7C=1,使得控制数码管段选的锁存器使能,送出数据。
总结:P27~P25对应组成的二进制值即为使能的锁存器, LED/数码管/外设控制都是由锁存器控制,锁存器的输入共用P0口,这样通过多个锁存器,以及逻辑电路,节省了单片机的IO口
程序设计与分析
CT107D初始化
上电时,默认所有IO口为高电平,未被使能过的锁存器默认输出低电平,这会导致全部LED被点亮,有源蜂鸣器响(也是低电平有效),这就需要我们写一个程序初始化函数,关闭这些影响后续功能的现象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
void System_Init(void) { P0 = 0xFF; P2 = P2 & 0x1F | 0x80; P2 &= 0x1F; P0=0x00; P2 = P2 & 0x1F | 0xA0; P2 &= 0x1F; }
|
注意:锁存器输出会经过ULN2003,相当于(增强驱动能力)反相器,预载值(P2)应为全低电平,这样最终输出的是高电平。
ULN2003:【常用芯片】ULN2003工作原理及中文资料(实例:STM32驱动28BYJ48步进电机)_uln2003驱动板说明书-CSDN博客
由于单片机的引脚输出电流太低了,无法驱动大部分的设备。而ULN2003只相当于是一个开关,设备(负载)的供电是在外围电路上,而它能够通过微弱的单片机输出电流来控制外围电路的开闭。这某种程度上也可以说是,放大电流,增加驱动能力
LED模块
原理图
LED/蜂鸣器/继电器
ps:~(0x01<<addr); 一定要加括号,否则会出现逻辑错误,如 0000 0001–> 1110 1111 变成 1111 1110 – > 1110 0000 ,最终经过一系列运行导致LED亮度降低(待考究)。
ps:一定要仔细注意逻辑,不能记忆性写程序!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <LED.h>
static unsigned char temp_1; static unsigned char temp_old_1;
void LED_Disp(unsigned addr,enable) { static unsigned char temp=0x00; static unsigned char temp_old=0xFF; if(enable) { temp|=0x01<<addr; } else { temp&=~(0x01<<addr); } if(temp!=temp_old) { P0=~temp; P2 = P2 & 0x1F | 0x80; P2 &= 0x1F; temp_old=temp; } }
void Beep(unsigned char flag) { if(flag) temp_1 |= 0x40; else temp_1 &= ~0x40; if(temp_1 != temp_old_1) { P0 = temp_1; P2 = P2 & 0x1f | 0xa0; P2 &= 0x1f; temp_old_1 = temp_1; } }
void Relay(unsigned char flag) { if(flag) temp_1 |= 0x10; else temp_1 &= ~0x10; if(temp_1 != temp_old_1) { P0 = temp_1; P2 = P2 & 0x1f | 0xa0; P2 &= 0x1f; temp_old_1 = temp_1; } }
|
PS:按位取或/按位取与,通常用于对单个BIT位进行操作
数码管模块
ps:蓝桥杯单片机开发板使用的是共阳极数码管,注意段码和位码,以及Disp函数的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <Nixie.h>
unsigned char Nixie_seg[] = { 0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xFF, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e };
unsigned char Nixie_Location[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
void Nixie_Disp(unsigned char Location,Seg,Point) { P0=0xFF; P2 = P2 & 0x1F | 0xE0; P2 &= 0x1F; P0=Nixie_Location[Location]; P2 = P2 & 0x1F | 0xC0; P2 &= 0x1F; P0=Nixie_seg[Seg]; if(Point){P0 &= 0x7F;} P2 = P2 & 0x1F | 0xE0; P2 &= 0x1F; }
|
定时器模块
注意,STC15单片机的定时器是支持自动重装载的,这里不需要在中断子程序中赋初值,否则会出现定时器定时值的混乱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <Timer0.h>
void Timer0_Init(void) { AUXR &= 0x7F; TMOD &= 0xF0; TL0 = 0x18; TH0 = 0xFC; TF0 = 0; TR0 = 1; ET0 = 1; EA = 1; PT0 = 1; }
|
按键模块
ps:键值读取函数中定义局部变量temp一定要赋初值!!!否则会出现在没有按键按下时键值读取异常值的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <Key.h>
unsigned char Key_Read(void) { unsigned char temp=0; ET0=0; P44=0;P42=1;P35=1;P34=1; if(P33==0){temp=4;} if(P32==0){temp=5;} if(P31==0){temp=6;} if(P30==0){temp=7;} P44=0;P42=1;P35=1;P34=1; if(P33==0){temp=8;} if(P32==0){temp=9;} if(P31==0){temp=10;} if(P30==0){temp=11;} P44=0;P42=1;P35=1;P34=1; if(P33==0){temp=12;} if(P32==0){temp=13;} if(P31==0){temp=14;} if(P30==0){temp=15;} P44=0;P42=1;P35=1;P34=1; if(P33==0){temp=16;} if(P32==0){temp=17;} if(P31==0){temp=18;} if(P30==0){temp=19;} ET0=1; P3=0xff; return temp; }
|
主函数模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #include <Key.h> #include <Nixie.h> #include <LED.h> #include <Timer0.h> #include <Init.h>
unsigned char Nixie_Buf[8]={10,10,10,10,10,10,10,10}; unsigned char Nixie_Pos; unsigned char Nixie_Point[8]={0,0,0,0,0,0,0,0};
unsigned char Key; unsigned char Key_Up,Key_Down,Key_Val,Key_Old;
unsigned char ucLED[8]={0,0,0,0,0,0,0,0};
unsigned int Nixie_Timer; unsigned int Key_Timer;
void Key_Proc(void) { if(Key_Timer) return; Key_Timer=1; Key_Val=Key_Read(); Key_Down=Key_Val & (Key_Old ^ Key_Val); Key_Up=~Key_Val & (Key_Old ^ Key_Val); Key_Old=Key_Val; }
void Nixie_Proc(void) { if(Nixie_Timer) return; Nixie_Timer=1; }
void LED_Proc(void) {
}
void Uart_Proc(void) { }
void main() { Timer0_Init(); System_Init(); while(1) { Key_Proc(); Nixie_Proc(); LED_Proc(); } }
void Uart_Server(void) interrupt 4 { }
void Timer0_Server(void) interrupt 1 { if(++Nixie_Timer==500){Nixie_Timer=0;} if(++Key_Timer==20){Key_Timer=0;} if(++Nixie_Pos==8){Nixie_Pos=0;} Nixie_Disp(Nixie_Pos,Nixie_Buf[Nixie_Pos],Nixie_Point[Nixie_Pos]); LED_Disp(Nixie_Pos,ucLED[Nixie_Pos]); }
|
超声波模块(使用PCA)
ps:返回值的单位是cm,可以根据题目要求更改底层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <ultrasound.h> #include "intrins.h"
sbit Tx = P1^0; sbit Rx = P1^1;
void Delay12us() { unsigned char i;
_nop_(); _nop_(); i = 38; while (--i); }
void Ut_Wave_Init() { unsigned char i; for(i=0;i<8;i++) { Tx = 1; Delay12us(); Tx = 0; Delay12us(); } }
unsigned char Ut_Wave_Data() { unsigned int time; CCON = 0x00; CH = CL = 0; EA=0; Ut_Wave_Init(); EA=1; CR = 1; while((Rx == 1) && (CF == 0)); CR = 0; if(CF == 0) { time = CH << 8 | CL; return (time * 0.017); } else { CF = 0; return 0; } }
|
串口模块(putchar重定向)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #include <Uart.h>
void Uart1_Init(void) { SCON = 0x50; AUXR |= 0x01; AUXR |= 0x04; T2L = 0xC7; T2H = 0xFE; AUXR |= 0x10; ES = 1; EA = 1; }
extern unsigned char putchar(unsigned char ch) { SBUF= ch; while(TI == 0); TI = 0; return (ch); }
|
串口中断服务程序
1 2 3 4 5 6 7 8 9 10 11 12 13
| void Uart_Server() interrupt 4 { if(RI == 1 && Uart_RecData_Index <8) { Uart_RecData[Uart_RecData_Index++]=SBUF; RI=0; } if(Uart_RecData_Index == 6) { Uart_RecData_Index=0; } }
|
定时器使用注意事项:
- 定时器0:主要工作定时器.NE555方波发生器
- 定时器1:一般用于PWM,使用NE555时,主要定时器选为定时器1
- 定时器2:串口中断1绑定
- PCA:用于超声波模块
#补充
一、IIC模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
#include "iic.h"
#include "intrins.h"
#define DELAY_TIME 5
#define Photo_Res_Channel 0x41 #define Adj_Res_Channel 0x43
sbit SDA = P2^1; sbit SCL = P2^0;
void IIC_Delay(unsigned char i) { do{_nop_();} while(i--); }
void Delay200us(void) { unsigned char data i, j;
i = 3; j = 82; do { while (--j); } while (--i); }
void IIC_Start(void) { SDA = 1; SCL = 1; IIC_Delay(DELAY_TIME); SDA = 0; IIC_Delay(DELAY_TIME); SCL = 0; }
void IIC_Stop(void) { SDA = 0; SCL = 1; IIC_Delay(DELAY_TIME); SDA = 1; IIC_Delay(DELAY_TIME); }
void IIC_SendAck(bit ackbit) { SCL = 0; SDA = ackbit; IIC_Delay(DELAY_TIME); SCL = 1; IIC_Delay(DELAY_TIME); SCL = 0; SDA = 1; IIC_Delay(DELAY_TIME); }
bit IIC_WaitAck(void) { bit ackbit; SCL = 1; IIC_Delay(DELAY_TIME); ackbit = SDA; SCL = 0; IIC_Delay(DELAY_TIME); return ackbit; }
void IIC_SendByte(unsigned char byt) { unsigned char i;
for(i=0; i<8; i++) { SCL = 0; IIC_Delay(DELAY_TIME); if(byt & 0x80) SDA = 1; else SDA = 0; IIC_Delay(DELAY_TIME); SCL = 1; byt <<= 1; IIC_Delay(DELAY_TIME); } SCL = 0; }
unsigned char IIC_RecByte(void) { unsigned char i, da; for(i=0; i<8; i++) { SCL = 1; IIC_Delay(DELAY_TIME); da <<= 1; if(SDA) da |= 1; SCL = 0; IIC_Delay(DELAY_TIME); } return da; }
|
基于IIC的AD/DA转换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| unsigned char AD_Read(unsigned char addr) { unsigned char temp; IIC_Start(); IIC_SendByte(0x90); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck(); IIC_Start(); IIC_SendByte(0x91); IIC_WaitAck(); temp = IIC_RecByte(); IIC_SendAck(1); IIC_Stop(); return temp; }
void DA_Write(unsigned char Data) { IIC_Start(); IIC_SendByte(0x90); IIC_WaitAck(); IIC_SendByte(0x41); IIC_WaitAck(); IIC_SendByte(Data); IIC_WaitAck(); IIC_Stop(); }
|
基于IIC的AT24C02(EPPROM)储存器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| void EEPROM_Write(unsigned char *EEPROM_String,unsigned char addr,unsigned char num) { IIC_Start(); IIC_SendByte(0xA0); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck();
while(num--) { IIC_SendByte(*EEPROM_String++); IIC_WaitAck(); IIC_Delay(200); } IIC_Stop(); }
void EEPROM_Read(unsigned char *EEPROM_String,unsigned char addr,unsigned char num) { IIC_Start(); IIC_SendByte(0xA0); IIC_WaitAck(); IIC_SendByte(addr); IIC_WaitAck();
IIC_Start(); IIC_SendByte(0xA1); IIC_WaitAck(); while(num--) { *EEPROM_String++=IIC_RecByte(); if(num) { IIC_SendAck(0); } else { IIC_SendAck(1); } } IIC_Stop(); }
|
二、onewire模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| #include "onewire.h" #include "reg52.h"
sbit DQ = P1^4;
void Delay_OneWire(unsigned int t) { t *= 12; while(t--); }
void Write_DS18B20(unsigned char dat) { unsigned char i; for(i=0;i<8;i++) { DQ = 0; DQ = dat&0x01; Delay_OneWire(5); DQ = 1; dat >>= 1; } Delay_OneWire(5); }
unsigned char Read_DS18B20(void) { unsigned char i; unsigned char dat; for(i=0;i<8;i++) { DQ = 0; dat >>= 1; DQ = 1; if(DQ) { dat |= 0x80; } Delay_OneWire(5); } return dat; }
bit init_ds18b20(void) { bit initflag = 0; DQ = 1; Delay_OneWire(12); DQ = 0; Delay_OneWire(80); DQ = 1; Delay_OneWire(10); initflag = DQ; Delay_OneWire(5); return initflag; }
float rd_temperature(void) { unsigned char low,high; init_ds18b20(); Write_DS18B20(0xcc); Write_DS18B20(0x44);
init_ds18b20(); Write_DS18B20(0xcc); Write_DS18B20(0xBe); low = Read_DS18B20(); high = Read_DS18B20(); return (high << 8 | low)/16.0; }
|
#DS1302模块
DS1302(时钟芯片)底层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <reg52.h> #include <intrins.h> #include "ds1302.h" sbit SCK=P1^7; sbit SDA=P2^3; sbit RST = P1^3;
void Write_Ds1302(unsigned char temp) { unsigned char i; for (i=0;i<8;i++) { SCK=0; SDA=temp&0x01; temp>>=1; SCK=1; } }
void Write_Ds1302_Byte( unsigned char address,unsigned char dat ) { RST=0; _nop_(); SCK=0; _nop_(); RST=1; _nop_(); Write_Ds1302(address); Write_Ds1302(dat); RST=0; }
unsigned char Read_Ds1302_Byte ( unsigned char address ) { unsigned char i,temp=0x00; RST=0; _nop_(); SCK=0; _nop_(); RST=1; _nop_(); Write_Ds1302(address); for (i=0;i<8;i++) { SCK=0; temp>>=1; if(SDA) temp|=0x80; SCK=1; } RST=0; _nop_(); SCK=0; _nop_(); SCK=1; _nop_(); SDA=0; _nop_(); SDA=1; _nop_(); return (temp); }
|
基于底层的时序编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| void Set_Rtc(unsigned char* ucRtc) { unsigned char i; Write_Ds1302_Byte(0x8e,0x00); for(i=0;i<3;i++) Write_Ds1302_Byte(0x84-i*2,ucRtc[i]); Write_Ds1302_Byte(0x8e,0x80); }
void Read_Rtc(unsigned char* ucRtc) { unsigned char i; for(i=0;i<3;i++) ucRtc[i] = Read_Ds1302_Byte(0x85-i*2); }
void Set_Date(unsigned char* ucDate) { Write_Ds1302_Byte(0x8e,0x00); Write_Ds1302_Byte(0x8c,ucDate[0]); Write_Ds1302_Byte(0x88,ucDate[1]); Write_Ds1302_Byte(0x86,ucDate[2]); Write_Ds1302_Byte(0x8e,0x80); }
void Read_Date(unsigned char* ucDate) { ucDate[0] = Read_Ds1302_Byte(0x8d); ucDate[1] = Read_Ds1302_Byte(0x89); ucDate[2] = Read_Ds1302_Byte(0x87); }
|
总结
进阶
EEPROM存入多字节大小变量
方法一 多个变量值传递
1 2
| unsigned int change_counter=0; unsigned char EEPROM_Data[2]={0,0};
|
1 2
| EEPROM_Read(EEPROM_Data,0x00,2); change_counter=EEPROM_Data[0] | EEPROM_Data[1]<<8;
|
缺点:浪费了内存空间,逻辑繁琐
方法二 联合体
使用联合体,联合体内的元素公用一段内存空间。指定的变量只有在调用时才会使用内存空间,节省资源同时逻辑简洁直观
1 2 3 4 5
| union { unsigned int change_counter; unsigned char Temp[2]; }EEPROM_DATA;
|
对比
方法一
1 2 3 4 5
| .change_counter++;
EEPROM_Data[0] = (unsigned char)(change_counter & 0xFF); EEPROM_Data[1] = (unsigned char)(change_counter>>8 & 0xFF); EEPROM_Write(&EEPROM_Data[0],0x00,2);
|
方法二
1 2
| EEPROM_Data.change_counter++; EEPROM_Write(&EEPROM_Data.Temp[0],0x00,2);
|
EEPROM
EEPROM读取数据需要在开中断之前,本身时序较长,容易被打断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void main() { EEPROM_Read(EEPROM_Data_T.Temp,0x00,2); System_Init(); Timer0_Init(); Uart1_Init(); Ultrasound_Init(); rd_temperature(); Delay(750); while(1) { Key_Proc(); Nixie_Proc(); LED_Proc(); Uart_Proc(); } }
|