目录
单片机三大资源:
FLASH <程序存储空间 ROM>
RAM <内存>
SFR <特殊功能寄存器>
STC89C52RC:8K FLASH、512字节RAM、32个IO口、3个定时器、1个UART、8个中断源。
单片机最小系统:电源电路、复位电路、晶振电路
点亮一个小灯:
#include<reg52.h>
sbit LED = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main()
{
/*使能74H138*/
ENLED = 0;
ADDR3 = 1;
/*编码值为110,对应原理图的LEDS6*/
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;
LED = 0; //控制小灯点亮,DB_0置为零,即DB0为0,LED2点亮
while(1);
}
流水灯:
方式一:
while(1)
{
P0 = 0xFE;
for(i=0; i<30000; i++);
P0 = 0xFD;
for(i=0; i<30000; i++);
P0 = 0xFB;
for(i=0; i<30000; i++);
P0 = 0xF7;
for(i=0; i<30000; i++);
P0 = 0xEF;
for(i=0; i<30000; i++);
P0 = 0xDF;
for(i=0; i<30000; i++);
P0 = 0xBF;
for(i=0; i<30000; i++);
P0 = 0x7F;
for(i=0; i<30000; i++);
}
方式二:
unsigned char cnt = 0;
while(1)
{
P0 = ~(0x01 << cnt);
for(i=0; i<30000; i++)
cnt++;
if(cnt >= 8)
{
cnt=0;
}
}
电路硬件知识:
电池干扰:静电放电(ESD)、快速瞬间脉冲群(EFT)、浪涌(Surge)
去耦电容:铝电解电容、钽电容、陶瓷电容
低频滤波电容:耐压值、容值:对电压电流起稳定作用。
高频滤波:104电容:10*10^4pf :0.1uf:放在正负极之间。
三极管:
PNP:9012,NPN:9013:b 基级,e 发射极,c 集电极
三种状态:1、截至:e和b不导通
2、饱和:e和b导通 Ib>Iec/β β为放大倍数
3、放大:e和b导通 Ib=Iec/β
导通电压:0.7v
控制端:集电极 c和基级 b之间
箭头朝内PNP,导通电压顺着箭头过,电压导通,电流控制
74HC245:
电流缓冲
定时器和计数器
时钟周期:时钟源分之一,单片机时序中的最小单位。
机器周期:单片机完成一个操作的最短时间。
定时器:机器周期是定时器的计数周期:存储寄存器的值经过一个机器周期自动加一。
定时器存储寄存器:
TH0 (0x8C),TL0 (0x8A); TH1 (0x8D),TL1 (0x8B)
TL0从0x00开始计数,计数到0xFF (255),清零,TH0进位。
定时器控制寄存器:TCON:
TF1 (7), TR1 (6),
TF0 (5), TR0 (4), TF0表示溢出标志位,TR0为运行控制位
IE1 (3), IT1 (2),
IE0 (1), IT0 (0)
定时器模式寄存器:TMOD:
GATE(T1) (7),
C/T(T1) (6), counter or timer
M1(T1) (5), M0(T1) (4),
GATE(T0) (3),
C/T(T0) (2),
M1(T0) (1), M0(T0) (0),
00 (0): TH0的8位和TL0的5位组成一个13位寄存器
01 (1): TH0与TL0组成一个16位寄存器
10 (2): 8位自动重装模式,定时器溢出之后TH0重装到TL0中
11 (3): 禁用定时器1,定时器0变成2个8位定时器。
使用定时器的方法:
TMOD -> TH0,TL0 ->TCON -> 判断TF0
#include <reg52.h>
sbit LED = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
void main()
{
unsigned char cnt = 0;
ENLED = 0;
ADDR3 = 1;
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;
TMOD = 0x01; //计时模式1
/* 寄存器从0xB800计数到0xFFFF,然后清零,经过了(0xFFFF-0xB800)个
机器周期,定时了[(0xFFFF-0xB800)*12]/11059200时间,即20ms,就是0.02s*/
TH0 = 0xB8;
TL0 = 0x00;
TR0 = 1; //打开定时器
while(1)
{
if(TF0 == 1)
{
TF0 = 0; //软件溢出置零
/*定时器重载*/
TH0 = 0xB8;
TL0 = 0x00;
cnt++;
if(cnt>=50) //定时0.02*50 s
{
cnt = 0;
LED = ~LED;
}
}
}
}
蜂鸣器频率控制:
/* 蜂鸣器启动函数,frequ-工作频率 */
void OpenBuzz(unsigned int frequ)
{
unsigned int reload; //计算所需的定时器重载值
reload = 65536 - (11059200/12)/(frequ*2); //由给定频率计算定时器重载值
T0RH = (unsigned char)(reload >> 8); //16 位重载值分解为高低两个字节
T0RL = (unsigned char)reload;
TH0 = 0xFF; //设定一个接近溢出的初值,以使定时器马上投入工作
TL0 = 0xFE;
ET0 = 1; //使能 T0 中断
TR0 = 1; //启动 T0
}
数码管静态显示:
#include<reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[]={
0xc0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86,0x8E
}; //数组存储数码管的真值表
void main()
{
unsigned char cnt = 0;
unsigned char sec = 0;
ENLED = 0;
ADDR3 = 1;
ADDR2 = 0;
ADDR1 = 0;
ADDR0 = 0;
TMOD = 0x01;
THO = 0x8B;
TL0 = 0x00;
TR0 = 1;
while(1)
{
if(TF0 == 1)
{
TF0 = 0;
TH0 = 0xB8;
TL0 = 0x00;
cnt++;
if(cnt >= 50)
{
cnt = 0;
P0 = LedChar[sec]; //数码管每秒加一
sec++;
if(sec >= 16) //显示到头清零
{
sec=0;
}
}
}
}
}
数码管动态显示:
1ms,100hz 为视觉暂停效应。
中断:
定时器TF0自动清零。
中断使能寄存器:IE:
EA (7), 中断总使能
-- (6),
ET2 (5), 定时器2中断使能,中断编号 5
ES (4), 串口中断使能,中断编号 4
ET1 (3), 定时器1中断使能,中断编号 3
EX1 (2), 外部中断1使能,中断编号 2
ET0 (1), 定时器0中断使能,中断编号 1
EX0 (0), 外部中断0使能,中断函数编号 0
中断优先级寄存器:IP:
-- (7), -- (6),
PT2 (5), 定时器2中断优先级控制位
PS (4), 串口中断控制
PT1 (3), 定时器1中断优先控制位
PX1 (2), 外部中断1中断优先控制位
PT0 (1), 定时器0中断优先控制位
PX0 (0), 外部中断0优先级控制位
不采用中断,数码管有抖动:
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4
unsigned char code LedChar[]={
0xc0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
};
unsigned char LedBuff[6]={
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
void main()
{
unsigned char cnt=0;
unsigned long sec=0;
unsigned char i=0;
ENLED = 0;
ADDR3 = 1;
TMOD = 0x01;
TH0 = 0xFC;
TL0 = 0x67;
TR0 = 1;
while(1)
{
if(TF0 == 1)
{
TF0 = 0;
TH0 = 0xFC;
TL0 = 0x67; //(0xFFFF-0xFC67)*12/11059200
cnt++;
if(cnt >= 1000)
{
cnt = 0;
sec++;
/*对sec进行拆分*/
LedBuff[0] = LedChar[sec%10];
LedBuff[1] = LedChar[sec/10%10];
LedBuff[2] = LedChar[sec/100%10];
LedBuff[3] = LedChar[Sec/1000%10];
LedBuff[4] = LedChar[Sec/10000%10];
LedBuff[5] = LedChar[Sec/100000%10];
}
/*将拆分后的sec分别显示到每个数码管上*/
P0=0xFF; //关闭数码管后再显示,防止鬼影,消影
switch(i)
{
case 0:ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];break;
case 1:ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];break;
case 2:ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];break;
case 3:ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];break;
case 4:ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];break;
case 5:ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];break;
default:break;
}
}
}
}
采用中断,数码管稳定:
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned char code LedChar[]={
0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
};
unsigned char LedBuff[6]={
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
unsigned char flags = 0; //1 秒定时标志
void main()
{
unsigned long sec=0;
ENLED = 0;
ADDR3 = 1;
TMOD = 0x01;
// TH0 = 0xFC; //定时1ms
// TL0 = 0x67;
TR0 = 1;
ET0 = 1;
EA = 1;
while(1)
{
if(flags == 1) //定时1s
{
flags = 0;
sec++;
/*对sec进行拆分*/
LedBuff[0] = LedChar[sec%10];
LedBuff[1] = LedChar[sec/10%10];
LedBuff[2] = LedChar[sec/100%10];
LedBuff[3] = LedChar[sec/1000%10];
LedBuff[4] = LedChar[sec/10000%10];
LedBuff[5] = LedChar[sec/100000%10];
}
}
}
void InterruptTime0() interrupt 1
{
static unsigned int cnt = 0; //若cnt为char类型,那么最大值为255,
//就不可能计数到1000
static unsigned char i = 0; //变量定义为局部变量,在局部使用,定义为静态,可以连续使用变量
TH0 = 0xFC;
TL0 = 0x67;
cnt++;
if(cnt >= 1000) //定时1s
{
cnt = 0;
flags = 1;
}
/*将拆分后的sec分别显示到每个数码管上*/
P0=0xFF; //关闭数码管后再显示,防止鬼影,消影
switch(i)
{
case 0:ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];break;
case 1:ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];break;
case 2:ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];break;
case 3:ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];break;
case 4:ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];break;
case 5:ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];break;
default:break;
}
}
点阵LED:
保存点阵的数组如果太大的话应当放在ROM中,加code修饰。
按键与数码管:
资源清单:
#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code LedChar[]={ //数码管编码
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6]={ //显示数码管数值
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char code KeyCodeMap[4][4]={ //矩阵键盘编码
{ 0x31, 0x32, 0x33, 0x26 }, //数字键1、数字键2、数字键3、向上键
{ 0x34, 0x35, 0x36, 0x25 }, //数字键4、数字键5、数字键6、向左键
{ 0x37, 0x38, 0x39, 0x28 }, //数字键7、数字键8、数字键9、向下键
{ 0x30, 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键
};
unsigned char KeySta[4][4]={ //矩阵键盘当前数值
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
void KeyDriver();
void main()
{
EA = 1; //中断总开关
/*选择数码管*/
ENLED = 0;
ADDR3 = 1;
TMOD = 0x01; //定时模式1
/*定时1ms*/
TH0 = 0xFC;
TL0 = 0x67;
ET0 = 1; //打开T0中断
TR0 = 1; //启动T0
LedBuff[0] = LedChar[0]; //上电显示0
while(1)
{
KeyDriver();
}
}
void ShowNumber(unsigned long number)
{
signed char i;
unsigned char buf[6]; //存储每一位要显示的数字
/*分离整数到每一位*/
for(i=0;i<6;i++)
{
buf[i]=number%10;
number=number/10;
}
/*高位为0不显示*/
for(i=5;i>=1;i--)
{
if(buf[i] == 0)
LedBuff[i]=0xFF;
else
break;
}
/*显示不为0低位*/
for(;i>=0;i--)
{
LedBuff[i]=LedChar[buf[i]];
}
}
void KeyAction(unsigned char keycode)
{
static unsigned long result = 0; //运算结果
static unsigned long addend = 0; //用于保存输入的加数
if((keycode>=0x30)&&(keycode<=0x39)) //输入0-9的数字
{
addend = (addend*10)+(keycode-0x30); //整体十进制左移,新数字进入个位,比如65,先显示5,再显示6
ShowNumber(addend); //显示运算结果
}
else if(keycode == 0x26) //向上键作加法,执行加法或连加
{
result += addend;
addend = 0;
ShowNumber(result);
}
else if(keycode==0x0D) //回车键,执行加法运算(实际效果与加法相同)
{
result += addend;
addend = 0;
ShowNumber(result);
}
else if(keycode==0x1B) //Esc键,清零结果
{
addend = 0;
result = 0;
ShowNumber(addend);
}
}
void KeyDriver()
{
unsigned char i,j;
static unsigned char backup[4][4] = { //前一次的键盘值
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
/*循环检测KeySta的值是否发生改变,若确定按下,更新数码管的显示*/
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
{
if(backup[i][j] != KeySta[i][j])
{
if(backup[i][j] != 0)
{
KeyAction(KeyCodeMap[i][j]);
}
backup[i][j] = KeySta[i][j];
}
}
}
}
void LedScan()
{
static unsigned char i = 0; //动态扫描的索引
P0 = 0xFF; //显示消隐
switch (i)
{
case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
default: break;
}
}
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0;
static unsigned char keybuf[4][4]={ //记录按键实际状态
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}
};
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1; //左移,最左边(最高位)移出,低位补零,若keybuf低位受KEY_IN_1控制
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
/*循环检测四个按键,如果连续有四个全0,则判断KeySta为0,
如果有连续四个为1,则判断KeySta为1
*/
for(i=0;i<4;i++)
{
if((keybuf[keyout][i] & 0x0F) == 0x00)
{
KeySta[keyout][i] = 0;
}
if((keybuf[keyout][i] & 0x0F) == 0x0F)
{
KeySta[keyout][i] = 1;
}
}
keyout++;
keyout = keyout & 0x03; //4(100)& 0x03(011)为0,即keyout加到4为0
switch(keyout) //由单片机内部结构控制,想要捕获输入电平,需要拉低输出引脚
{
case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default: break;
}
}
/* T0中断服务函数,用于数码管显示扫描与按键扫描 */
void InterruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
LedScan(); //调用数码管显示扫描函数
KeyScan(); //调用按键扫描函数
}
永磁式步进电机:
八拍模式: 转子转动一圈则需要 8*8=64 拍
电机启动:每秒给出 550 个步进脉冲的情况下,可以正常启动,即需要大于1s/550=1.8ms刷新节拍。
启动函数:
/* 步进电机启动函数,angle-需转过的角度 */
void StartMotor(unsigned long angle)
{
//在计算前关闭中断,完成后再打开,以避免中断打断计算过程而造成错误
EA = 0;
beats = (angle * 4076) / 360; //实测为 4076 拍转动一圈
EA = 1;
}
IO控制:
unsigned char code BeatCode[8] = { //步进电机节拍对应的 IO 控制代码
0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6
};
节拍控制代码:
tmp = P1; //用 tmp 把 P1 口当前值暂存
tmp = tmp & 0xF0; //用&操作清零低 4 位
tmp = tmp | BeatCode[index]; //用|操作把节拍代码写到低 4 位
P1 = tmp; //把低 4 位的节拍代码和高 4 位的原值送回 P1
index++; //节拍输出索引递增
index = index & 0x07; //用&操作实现到 8 归零
delay(); //延时 2ms,即 2ms 执行一拍
实例
数字秒表
定时器定时时间调整:原因:中断需要时间,进中断后需要保护之前计算的值:中断压栈。
办法:一:使用debug观察,进行补偿。二:长时间运行累积误差调整。
字节操作修改位:0和1与0进行与运算,结果是0,与1进行与运算,保持原来的值
与0进行或运算,结果保存原值,与1进行或运算,结果是1。
3-8译码器输入设计:
P1=(P1 & 0xF8) | i; //只修改P1的低3位,即三八译码器的输入位
P0 = LedBuff[i];
if(i<5)
i++;
else
i=0;
#include <reg52.h>
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4 = P2^7;
unsigned char code LedChar[]={ //数码管编码
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6]={ //显示数码管数值
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char KeySta[4]={ //键盘当前数值
1, 1, 1, 1
};
bit StopwatchRunning = 0; //秒表运行标志
bit StopWatchRefresh = 1; //秒表计数刷新标志
unsigned char DecimalPart = 0; //秒表的小数部分
unsigned int IntegerPart = 0; //秒表的整数部分
unsigned char T0RH = 0; //T0重载值的高字节
unsigned char T0RL = 0; //T0重载值的低字节
void ConfigTimer0(unsigned int ms);
void WatchDisplay();
void KeyDriver();
void main()
{
EA = 1; //中断总开关
/*选择数码管*/
ENLED = 0;
ADDR3 = 1;
P2 = 0xFE; //P2.0置0,选择第4行按键作为独立按键
ConfigTimer0(2);
while(1)
{
if(StopWatchRefresh)
{
StopWatchRefresh = 0;
WatchDisplay();
}
KeyDriver();
}
}
void ConfigTimer0(unsigned int ms)
{
unsigned long begin; //计数初值
begin = 65536-((11059200/12)*ms)/1000;
//定时了[(0xFFFF-tmp)*12]/11059200/1000 = ms 时间
begin = begin + 18; //补偿中断响应延时造成的误差
TMOD &= 0xF0; //清零控制位
TMOD |= 0x01; //配置定时模式1
T0RH = (unsigned char)(begin>>8); //定时器高位移至低位后,强制类型转换,只剩第八位
T0RL = (unsigned char) begin; //强制类型转换,只剩低位
TH0 = T0RH; //加载T0重载值
TL0 = T0RL;
ET0 = 1; //开中断
TR0 = 1; //开定时器
}
void WatchDisplay()
{
signed char i;
unsigned char buf[4];
/*显示小数部分*/
LedBuff[0] = LedChar[DecimalPart%10];
LedBuff[1] = LedChar[DecimalPart/10];
/*拆解整数部分*/
buf[0] = IntegerPart%10;
buf[1] = (IntegerPart/10)%10;
buf[2] = (IntegerPart/100)%10;
buf[3] = (IntegerPart/1000)%10;
/*高位为0不显示*/
for(i=3;i>=1;i--)
{
if(buf[i] == 0)
LedBuff[i+2] = 0xFF;
else
break;
}
for(;i>=0;i--) //点亮整数部分
{
LedBuff[i+2] = LedChar[buf[i]];
}
LedBuff[2] &= 0x7F; //点亮小数点
}
/* 秒表启停函数 */
void StopwatchAction()
{
if (StopwatchRunning) //已启动则停止
StopwatchRunning = 0;
else //未启动则启动
StopwatchRunning = 1;
}
/* 秒表复位函数 */
void StopwatchReset()
{
StopwatchRunning = 0; //停止秒表
DecimalPart = 0; //清零计数值
IntegerPart = 0;
StopWatchRefresh = 1; //置刷新标志
}
/* 秒表计数函数,每隔10ms调用一次进行秒表计数累加 */
void StopwatchCount()
{
if (StopwatchRunning) //当处于运行状态时递增计数值
{
DecimalPart++; //小数部分+1
if (DecimalPart >= 100) //小数部分计到100时进位到整数部分
{
DecimalPart = 0;
IntegerPart++; //整数部分+1
if (IntegerPart >= 10000) //整数部分计到10000时归零
{
IntegerPart = 0;
}
}
StopWatchRefresh = 1; //设置秒表计数刷新标志
}
}
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
unsigned char i;
static unsigned char backup[4] = {1,1,1,1};
for (i=0; i<4; i++) //循环检测4个按键
{
if (backup[i] != KeySta[i]) //检测按键动作
{
if (backup[i] != 0) //按键按下时执行动作
{
if (i == 1) //Esc键复位秒表
StopwatchReset();
else if (i == 2) //回车键启停秒表
StopwatchAction();
}
backup[i] = KeySta[i]; //刷新前一次的备份值
}
}
}
/* 按键扫描函数,需在定时中断中调用 */
void KeyScan()
{
unsigned char i;
static unsigned char keybuf[4] = { //按键扫描缓冲区
0xFF, 0xFF, 0xFF, 0xFF
};
//按键值移入缓冲区
keybuf[0] = (keybuf[0] << 1) | KEY1;
keybuf[1] = (keybuf[1] << 1) | KEY2;
keybuf[2] = (keybuf[2] << 1) | KEY3;
keybuf[3] = (keybuf[3] << 1) | KEY4;
//消抖后更新按键状态
for (i=0; i<4; i++)
{
if (keybuf[i] == 0x00)
{ //连续8次扫描值为0,即16ms内都是按下状态时,可认为按键已稳定的按下
KeySta[i] = 0;
}
else if (keybuf[i] == 0xFF)
{ //连续8次扫描值为1,即16ms内都是弹起状态时,可认为按键已稳定的弹起
KeySta[i] = 1;
}
}
}
/* 数码管动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
static unsigned char i = 0; //动态扫描索引
P0 = 0xFF; //关闭所有段选位,显示消隐
P1 = (P1 & 0xF8) | i; //位选索引值赋值到P1口低3位
P0 = LedBuff[i]; //缓冲区中索引位置的数据送到P0口
if (i < 5) //索引递增循环,遍历整个缓冲区
i++;
else
i = 0;
}
/* T0中断服务函数,用于数码管显示扫描与按键扫描 */
void InterruptTimer0() interrupt 1
{
static unsigned char tmr10ms = 0;
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
LedScan(); //调用数码管显示扫描函数
KeyScan(); //调用按键扫描函数
//定时10ms进行一次秒表计数
tmr10ms++;
if (tmr10ms >= 5)
{
tmr10ms = 0;
StopwatchCount(); //调用秒表计数函数
}
}
PWM实例
#include <reg52.h>
sbit PWMOUT = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
unsigned long PeriodCnt = 0; //PWM周期计数值
unsigned char HighRH = 0; //高电平重载值的高字节
unsigned char HighRL = 0; //高电平重载值的低字节
unsigned char LowRH = 0; //低电平重载值的高字节
unsigned char LowRL = 0; //低电平重载值的低字节
unsigned char T1RH = 0; //T1重载值的高字节
unsigned char T1RL = 0; //T1重载值的低字节
void ConfigTimer1(unsigned int ms);
void ConfigPWM(unsigned int fr, unsigned char dc);
void main()
{
EA = 1; //开总中断
ENLED = 0; //使能独立LED
ADDR3 = 1;
ADDR2 = 1;
ADDR1 = 1;
ADDR0 = 0;
ConfigPWM(100, 10); //配置并启动PWM
ConfigTimer1(50); //用T1定时调整占空比
while (1);
}
/* 配置并启动T1,ms-定时时间 */
void ConfigTimer1(unsigned int ms)
{
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 12; //补偿中断响应延时造成的误差
T1RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T1RL = (unsigned char)tmp;
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x10; //配置T1为模式1
TH1 = T1RH; //加载T1重载值
TL1 = T1RL;
ET1 = 1; //使能T1中断
TR1 = 1; //启动T1
}
void ConfigPWM(unsigned int fr, unsigned char dc)
{
unsigned int high,low;
PeriodCnt = (11059200/12)/fr; //单片机晶振频率11059200除以一个机器周期,即一个周期的频率,除以pwm波的频率,即一个周期的计数值
high = PeriodCnt*(dc/100); //计数周期乘以占空比,等于高电平持续的计数值
low = PeriodCnt-high; //低电平计数值等于总计数值减去高电平计数值
highsta = 65536-high+12; //高电平起始计数值
lowsta = 65536-low+12; //低电平起始计数值
HighRH = (unsigned char)(highsta>>8); //高电平高8位
HighRL = (unsigned char)highsta; //高电平低8位
LowRH = (unsigned char)(lowsta>>8); //低电平高8位
LowRL = (unsigned char)lowsta; //低电平低8位
TMOD &= 0xF0; //清零控制位
TMOD |= 0x01; //配置计数模式1
TH0 = HighRH;
TL0 = HighRL;
ET0 = 1;
TR0 = 1;
PWMOUT = 1; //输出高电平
}
/* T0中断服务函数,产生PWM输出 */
void InterruptTimer0() interrupt 1
{
if (PWMOUT == 1) //当前输出为高电平时,装载低电平值并输出低电平
{
TH0 = LowRH;
TL0 = LowRL;
PWMOUT = 0;
}
else //当前输出为低电平时,装载高电平值并输出高电平
{
TH0 = HighRH;
TL0 = HighRL;
PWMOUT = 1;
}
}
/* 占空比调整函数,频率不变只调整占空比 */
void AdjustDutyCycle(unsigned char dc)
{
unsigned int high, low;
high = (PeriodCnt*dc) / 100; //计算高电平所需的计数值
low = PeriodCnt - high; //计算低电平所需的计数值
high = 65536 - high + 12; //计算高电平的定时器重载值并补偿中断延时
low = 65536 - low + 12; //计算低电平的定时器重载值并补偿中断延时
HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
HighRL = (unsigned char)high;
LowRH = (unsigned char)(low>>8); //低电平重载值拆分为高低字节
LowRL = (unsigned char)low;
}
/* T1中断服务函数,定时动态调整占空比 */
void InterruptTimer1() interrupt 3
{
static bit dir = 0;
static unsigned char index = 0;
unsigned char code table[13] = { //占空比调整表
5, 18, 30, 41, 51, 60, 68, 75, 81, 86, 90, 93, 95
};
TH1 = T1RH; //重新加载T1重载值
TL1 = T1RL;
AdjustDutyCycle(table[index]); //调整PWM的占空比
if (dir == 0) //逐步增大占空比
{
index++;
if (index >= 12)
{
dir = 1;
}
}
else //逐步减小占空比
{
index--;
if (index == 0)
{
dir = 0;
}
}
}
51单片机RAM划分
长短按键的应用
#include <reg52.h>
sbit BUZZ = P1^6;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code LedChar[] = { //数码管显示字符转换表
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] = { //数码管+独立LED显示缓冲区
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
{ 0x31, 0x32, 0x33, 0x26 }, //数字键1、数字键2、数字键3、向上键
{ 0x34, 0x35, 0x36, 0x25 }, //数字键4、数字键5、数字键6、向左键
{ 0x37, 0x38, 0x39, 0x28 }, //数字键7、数字键8、数字键9、向下键
{ 0x30, 0x1B, 0x0D, 0x27 } //数字键0、ESC键、 回车键、 向右键
};
unsigned char KeySta[4][4] = { //全部矩阵按键的当前状态
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
unsigned long pdata KeyDownTime[4][4] = { //每个按键按下的持续时间,单位ms
{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}
};
bit enBuzz = 0; //蜂鸣器使能标志
bit flag1s = 0; //1秒定时标志
bit flagStart = 0; //倒计时启动标志
unsigned char T0RH = 0; //T0重载值的高字节
unsigned char T0RL = 0; //T0重载值的低字节
unsigned int CountDown = 0; //倒计时计数器
void ConfigTimer0(unsigned int ms);
void ShowNumber(unsigned long num);
void KeyDriver();
void main()
{
EA = 1; //使能总中断
ENLED = 0; //选择数码管和独立LED
ADDR3 = 1;
ConfigTimer0(1); //配置T0定时1ms
ShowNumber(0); //上电显示0
while (1)
{
KeyDriver(); //调用按键驱动函数
if (flagStart && flag1s) //倒计时启动且1秒定时到达时,处理倒计时
{
flag1s = 0;
if (CountDown > 0) //倒计时未到0时,计数器递减
{
CountDown--;
ShowNumber(CountDown); //刷新倒计时数显示
if (CountDown == 0) //减到0时,执行声光报警
{
enBuzz = 1; //启动蜂鸣器发声
LedBuff[6] = 0x00; //点亮独立LED
}
}
}
}
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 28; //补偿中断响应延时造成的误差
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0为模式1
TH0 = T0RH; //加载T0重载值
TL0 = T0RL;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
}
/* 将一个无符号长整型的数字显示到数码管上,num-待显示数字 */
void ShowNumber(unsigned long num)
{
signed char i;
unsigned char buf[6];
for (i=0; i<6; i++) //把长整型数转换为6位十进制的数组
{
buf[i] = num % 10;
num = num / 10;
}
for (i=5; i>=1; i--) //从最高位起,遇到0转换为空格,遇到非0则退出循环
{
if (buf[i] == 0)
LedBuff[i] = 0xFF;
else
break;
}
for ( ; i>=0; i--) //剩余低位都如实转换为数码管显示字符
{
LedBuff[i] = LedChar[buf[i]];
}
}
/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode) //按键动作函数,根据键码执行相应动作
{
if((keycode>=0x30)&&(keycode<=0x39)) //输入0-9的数字
{
CountDown = (CountDown*10)+(keycode-0x30); //整体十进制左移,新数字进入个位,比如65,先显示5,再显示6
ShowNumber(CountDown); //显示倒计时设定值
}
if (keycode == 0x26) //向上键,倒计时设定值递增
{
if (CountDown < 9999) //最大计时9999秒
{
CountDown++;
ShowNumber(CountDown);
}
}
else if (keycode == 0x28) //向下键,倒计时设定值递减
{
if (CountDown > 1) //最小计时1秒
{
CountDown--;
ShowNumber(CountDown);
}
}
else if (keycode == 0x0D) //回车键,启动倒计时
{
flagStart = 1; //启动倒计时
}
else if (keycode == 0x1B) //Esc键,取消倒计时
{
enBuzz = 0; //关闭蜂鸣器
LedBuff[6] = 0xFF; //关闭独立LED
flagStart = 0; //停止倒计时
CountDown = 0; //倒计时数归零
ShowNumber(CountDown);
}
}
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
unsigned char i, j;
static unsigned char pdata backup[4][4] = { //按键值备份,保存前一次的值
{1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}
};
static unsigned long pdata TimeThr[4][4] = { //快速输入执行的时间阈值
{1000, 1000, 1000, 1000}, {1000, 1000, 1000, 1000},
{1000, 1000, 1000, 1000}, {1000, 1000, 1000, 1000}
};
for (i=0; i<4; i++) //循环扫描4*4的矩阵按键
{
for (j=0; j<4; j++)
{
if (backup[i][j] != KeySta[i][j]) //检测按键动作
{
if (backup[i][j] != 0) //按键按下时执行动作
{
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
}
backup[i][j] = KeySta[i][j]; //刷新前一次的备份值
}
if (KeyDownTime[i][j] > 0) //检测执行快速输入
{
if (KeyDownTime[i][j] >= TimeThr[i][j])
{ //达到阈值时执行一次动作
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
TimeThr[i][j] += 200; //时间阈值增加200ms,以准备下次执行
}
}
else //按键弹起时复位阈值时间
{
TimeThr[i][j] = 1000; //恢复1s的初始阈值时间
}
}
}
}
/* 按键扫描函数,需在定时中断中调用 */
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩阵按键扫描输出索引
static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},
{0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}
};
//将一行的4个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
//消抖后更新按键状态
for (i=0; i<4; i++) //每行4个按键,所以循环4次
{
if ((keybuf[keyout][i] & 0x0F) == 0x00)
{ //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下
KeySta[keyout][i] = 0;
KeyDownTime[keyout][i] += 4; //按下的持续时间累加
}
else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
{ //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
KeyDownTime[keyout][i] = 0; //按下的持续时间清零
}
}
//执行下一次的扫描输出
keyout++; //输出索引递增
keyout &= 0x03; //索引值加到4即归零
switch (keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default: break;
}
}
/* LED动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
static unsigned char i = 0; //动态扫描索引
P0 = 0xFF; //关闭所有段选位,显示消隐
P1 = (P1 & 0xF8) | i; //位选索引值赋值到P1口低3位
P0 = LedBuff[i]; //缓冲区中索引位置的数据送到P0口
if (i < 6) //索引递增循环,遍历整个缓冲区
i++;
else
i = 0;
}
/* T0中断服务函数,完成数码管、按键扫描与秒定时 */
void InterruptTimer0() interrupt 1
{
static unsigned int tmr1s = 0; //1秒定时器
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
if (enBuzz) //蜂鸣器发声处理
BUZZ = ~BUZZ; //驱动蜂鸣器发声
else
BUZZ = 1; //关闭蜂鸣器
LedScan(); //LED扫描显示
KeyScan(); //按键扫描
if (flagStart) //倒计时启动时处理1秒定时
{
tmr1s++;
if (tmr1s >= 1000)
{
tmr1s = 0;
flag1s = 1;
}
}
else //倒计时未启动时1秒定时器始终归零
{
tmr1s = 0;
}
}
UART串口通信:
串行通信:一次只能发送一位,发送8次才能发送1个字节
通信端口:P3.0/RXD,P3.1/TXD
波特率baud:发送二进制数据的速率,收发双方的波特率应当一致。
一个完整的数据帧:1位起始位0,8位数据位,1位停止位1。
RS232:使用在工业领域,负电平代表1,正电平代表0
IO口模拟串口通信:
#include <reg52.h>
sbit PIN_RXD = P3^0; //接收引脚定义
sbit PIN_TXD = P3^1; //发送引脚定义
bit RxdOrTxd = 0; //指示当前状态为接收还是发送
bit RxdEnd = 0; //接收结束标志
bit TxdEnd = 0; //发送结束标志
unsigned char RxdBuf = 0; //接收缓冲器
unsigned char TxdBuf = 0; //发送缓冲器
void ConfigUART(unsigned int baud);
void StartTXD(unsigned char dat);
void StartRXD();
void main()
{
EA = 1; //开总中断
ConfigUART(9600); //配置波特率为9600
while (1)
{
while (PIN_RXD); //等待接收引脚出现低电平,即起始位
StartRXD(); //启动接收
while (!RxdEnd); //等待接收完成
StartTXD(RxdBuf+1); //启动发送,接收到的数据+1后,发送回去
while (!TxdEnd); //等待发送完成
}
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x02; //配置T0为模式2,8位自动重装载,TL0从0~256,后TH0自动赋值给TL0
TH0 = 256 - (11059200/12)/baud; //计算T0重载值,每1/baud进入中断
}
/* 启动串行接收 */
void StartRXD()
{
TL0 = 256 - ((256-TH0)>>1); //接收启动时的T0定时为半个波特率周期,中一个波特率中间接收数据
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
RxdEnd = 0; //清零接收结束标志
RxdOrTxd = 0; //设置当前状态为接收
}
/* 启动串行发送,dat-待发送字节数据 */
void StartTXD(unsigned char dat)
{
TxdBuf = dat; //待发送数据保存到发送缓冲器
TL0 = TH0; //T0计数初值为重载值
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
PIN_TXD = 0; //发送起始位
TxdEnd = 0; //清零发送结束标志
RxdOrTxd = 1; //设置当前状态为发送
}
/* T0中断服务函数,处理串行发送和接收 */
void InterruptTimer0() interrupt 1
{
static unsigned char cnt = 0; //位接收或发送计数
if (RxdOrTxd) //串行发送处理
{
cnt++;
if (cnt <= 8) //低位在先依次发送8bit数据位
{
PIN_TXD = TxdBuf & 0x01;
TxdBuf >>= 1;
}
else if (cnt == 9) //发送停止位
{
PIN_TXD = 1;
}
else //发送结束
{
cnt = 0; //复位bit计数器
TR0 = 0; //关闭T0
TxdEnd = 1; //置发送结束标志
}
}
else //串行接收处理
{
if (cnt == 0) //处理起始位
{
if (!PIN_RXD) //起始位为0时,清零接收缓冲器,准备接收数据位
{
RxdBuf = 0; //接收到0,作为起始位
cnt++;
}
else //起始位不为0时,中止接收
{
TR0 = 0; //关闭T0
}
}
else if (cnt <= 8) //处理8位数据位
{
RxdBuf >>= 1; //低位在先,所以将之前接收的位向右移
/*接收脚为1时,缓冲器最高位置1,而为0时不处理即仍保持移位后的0*/
if (PIN_RXD) //接收到1后的处理
{
RxdBuf |= 0x80;
}
cnt++;
}
else //停止位处理
{
cnt = 0; //复位bit计数器
TR0 = 0; //关闭T0
if (PIN_RXD) //停止位为1时,方能认为数据有效
{
RxdEnd = 1; //置接收结束标志,作为停止位
}
}
}
}
UART通信模块:
串行控制寄存器:SCON(地址0x98)
SM0<7>,SM1<6>:串口通信模式,常用模式1
SM2<5>:多机通信控制位
REN<4>:使能串行接收
TB8<3>,RB8<2>:模式2
TI<1>:发送中断标志位,当发送电路发送到停止位的中间位置时,TI
RI<0>:接收中断标志位,当接收电路接收到停止位的中间位置时,RI
波特率发生器:
计算公式:TH1=TL1=256-晶振值/12/2/16/波特率
若PCON |= 0x80; (PCON电源管理寄存器)
计算公式为:TH1=TL1=256-晶振值/12/16/波特率 //每位取16次,若16的中间位置中有两次为1,则认为接收到了1
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
SCON = 0x50; //配置串口为模式1
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x20; //配置T1为模式2
TH1 = 256 - (11059200/12/32)/baud; //计算T1重载值
TL1 = TH1; //初值等于重载值
ET1 = 0; //禁止T1中断
ES = 1; //使能串口中断
TR1 = 1; //启动T1
}
/* 串口中断服务函数 */
void InterruptUART() interrupt 4
{
if (RI) //接收到新字节
{
RI = 0; //清零接收中断标志位
if (cntRxd < sizeof(bufRxd)) //接收缓冲区尚未用完时,
{ //保存接收字节,并递增计数器
bufRxd[cntRxd++] = SBUF;
}
}
if (TI) //字节发送完毕
{
TI = 0; //清零发送中断标志位
flagTxd = 1; //设置字节发送完成标志
}
}
串口操作函数
/* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
void UartWrite(unsigned char *buf, unsigned char len)
{
while (len--) //循环发送所有字节
{
flagTxd = 0; //清零发送标志
SBUF = *buf++; //发送一个字节数据
while (!flagTxd); //等待该字节发送完成
}
}
/* 串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度 */
unsigned char UartRead(unsigned char *buf, unsigned char len)
{
unsigned char i;
if (len > cntRxd) //指定读取长度大于实际接收到的数据长度时,
{ //读取长度设置为实际接收到的数据长度
len = cntRxd;
}
for (i=0; i<len; i++) //拷贝接收到的数据到接收指针上
{
*buf++ = bufRxd[i];
}
cntRxd = 0; //接收计数器清零
return len; //返回实际读取长度
}
/* 串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔 */
void UartRxMonitor(unsigned char ms)
{
static unsigned char cntbkp = 0;
static unsigned char idletmr = 0;
if (cntRxd > 0) //接收计数器大于零时,监控总线空闲时间
{
if (cntbkp != cntRxd) //接收计数器改变,即刚接收到数据时,清零空闲计时
{
cntbkp = cntRxd;
idletmr = 0;
}
else //接收计数器未改变,即总线空闲时,累积空闲时间
{
if (idletmr < 30) //空闲计时小于30ms时,持续累加
{
idletmr += ms;
if (idletmr >= 30) //空闲时间达到30ms时,即判定为一帧接收完毕
{
flagFrame = 1; //设置帧接收完成标志
}
}
}
}
else
{
cntbkp = 0;
}
}
/* 串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用 */
void UartDriver()
{
unsigned char len;
unsigned char pdata buf[40];
if (flagFrame) //有命令到达时,读取处理该命令
{
flagFrame = 0;
len = UartRead(buf, sizeof(buf)); //将接收到的命令读取到缓冲区中
UartAction(buf, len); //传递数据帧,调用动作执行函数
}
}
命令控制:
/* 内存比较函数,比较两个指针所指向的内存数据是否相同,
ptr1-待比较指针1,ptr2-待比较指针2,len-待比较长度
返回值-两段内存数据完全相同时返回1,不同返回0 */
bit CmpMemory(unsigned char *ptr1, unsigned char *ptr2, unsigned char len)
{
while (len--)
{
if (*ptr1++ != *ptr2++) //遇到不相等数据时即刻返回0
{
return 0;
}
}
return 1; //比较完全部长度数据都相等则返回1
}
/* 串口动作函数,根据接收到的命令帧执行响应的动作
buf-接收到的命令帧指针,len-命令帧长度 */
void UartAction(unsigned char *buf, unsigned char len)
{
unsigned char i;
unsigned char code cmd0[] = "buzz on"; //开蜂鸣器命令
unsigned char code cmd1[] = "buzz off"; //关蜂鸣器命令
unsigned char code cmd2[] = "showstr "; //字符串显示命令
unsigned char code cmdLen[] = { //命令长度汇总表
sizeof(cmd0)-1, sizeof(cmd1)-1, sizeof(cmd2)-1,
};
unsigned char code *cmdPtr[] = { //命令指针汇总表
&cmd0[0], &cmd1[0], &cmd2[0],
};
for (i=0; i<sizeof(cmdLen); i++) //遍历命令列表,查找相同命令
{
if (len >= cmdLen[i]) //首先接收到的数据长度要不小于命令长度
{
if (CmpMemory(buf, cmdPtr[i], cmdLen[i])) //比较相同时退出循环
{
break;
}
}
}
switch (i) //循环退出时i的值即是当前命令的索引值
{
case 0:
flagBuzzOn = 1; //开启蜂鸣器
break;
case 1:
flagBuzzOn = 0; //关闭蜂鸣器
break;
case 2:
buf[len] = '\0'; //为接收到的字符串添加结束符
LcdShowStr(0, 0, buf+cmdLen[2]); //显示命令后的字符串
i = len - cmdLen[2]; //计算有效字符个数
if (i < 16) //有效字符少于16时,清除液晶上的后续字符位
{
LcdAreaClear(i, 0, 16-i);
}
break;
default: //未找到相符命令时,给上机发送“错误命令”的提示
UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
return;
}
buf[len++] = '\r'; //有效命令被执行后,在原命令帧之后添加
buf[len++] = '\n'; //回车换行符后返回给上位机,表示已执行
UartWrite(buf, len);
}
主函数与中断:
void main()
{
EA = 1; //开总中断
ConfigTimer0(1); //配置T0定时1ms
ConfigUART(9600); //配置波特率为9600
InitLcd1602(); //初始化液晶
while (1)
{
UartDriver(); //调用串口驱动
}
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
unsigned long tmp; //临时变量
tmp = 11059200 / 12; //定时器计数频率
tmp = (tmp * ms) / 1000; //计算所需的计数值
tmp = 65536 - tmp; //计算定时器重载值
tmp = tmp + 33; //补偿中断响应延时造成的误差
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
T0RL = (unsigned char)tmp;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0为模式1
TH0 = T0RH; //加载T0重载值
TL0 = T0RL;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
}
/* T0中断服务函数,执行串口接收监控和蜂鸣器驱动 */
void InterruptTimer0() interrupt 1
{
TH0 = T0RH; //重新加载重载值
TL0 = T0RL;
if (flagBuzzOn) //执行蜂鸣器鸣叫或关闭
BUZZ = ~BUZZ;
else
BUZZ = 1;
UartRxMonitor(1); //串口接收监控
}
1602液晶
基本操作时序:
读状态:输入:RS=L,RW=H,E=H
输出:D0~D7=状态字
写指令:输入:RS=L,RW=L,D0~D7=指令码,E=高脉冲
输出:无
读数据:输入:RS=H,RW=H,E=H
输出:D0~D7=数据
写数据:输入:RS=H,RW=L,D0~D7=数据,E=高脉冲
输出:无
读写检测:需要确保ST7为0
#include <reg52.h>
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;
void InitLcd1602();
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
void main()
{
unsigned char str[] = "Kingst Studio";
InitLcd1602();
LcdShowStr(2, 0, str);
LcdShowStr(0, 1, "Welcome to KST51");
while (1);
}
/* 等待液晶准备好 */
void LcdWaitReady()
{
unsigned char sta;
LCD1602_DB = 0xFF;
LCD1602_RS = 0;
LCD1602_RW = 1;
do {
LCD1602_E = 1;
sta = LCD1602_DB; //读取状态字
LCD1602_E = 0;
} while (sta & 0x80); //bit7等于1表示液晶正忙,重复检测直到其等于0为止
}
/* 向LCD1602液晶写入一字节命令,cmd-待写入命令值 */
void LcdWriteCmd(unsigned char cmd)
{
LcdWaitReady();
LCD1602_RS = 0;
LCD1602_RW = 0;
LCD1602_DB = cmd;
LCD1602_E = 1;
LCD1602_E = 0;
}
/* 向LCD1602液晶写入一字节数据,dat-待写入数据值 */
void LcdWriteDat(unsigned char dat)
{
LcdWaitReady();
LCD1602_RS = 1;
LCD1602_RW = 0;
LCD1602_DB = dat;
LCD1602_E = 1;
LCD1602_E = 0;
}
/* 设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */
void LcdSetCursor(unsigned char x, unsigned char y)
{
unsigned char addr;
if (y == 0) //由输入的屏幕坐标计算显示RAM的地址
addr = 0x00 + x; //第一行字符地址从0x00起始
else
addr = 0x40 + x; //第二行字符地址从0x40起始
LcdWriteCmd(addr | 0x80); //设置RAM地址
}
/* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str)
{
LcdSetCursor(x, y); //设置起始地址
while (*str != '\0') //连续写入字符串数据,直到检测到结束符
{
LcdWriteDat(*str++); //先取str指向的数据,然后str自加1
}
}
/* 初始化1602液晶 */
void InitLcd1602()
{
LcdWriteCmd(0x38); //16*2显示,5*7点阵,8位数据接口
LcdWriteCmd(0x0C); //显示器开,光标关闭
LcdWriteCmd(0x06); //文字不动,地址自动+1
LcdWriteCmd(0x01); //清屏
}
I2C与EEPROM
起始位:SDA由高到低后SCL由高到低
停止位:SCL由低到高后SDA由低到高
数据位:SCL为低时SDA变化,SCL为高时SDA可读
数据传输:起始位,先传地址1~7位ADDRESS,8位R/W读写位,ACK为高等待从机相应,
然后发送数据,ACK为高等待从机相应,发送数据,ACK为高等待从机相应,最后停止
/* 产生总线起始信号 */
void I2CStart()
{
I2C_SDA = 1; //首先确保SDA、SCL都是高电平
I2C_SCL = 1;
I2CDelay();
I2C_SDA = 0; //先拉低SDA
I2CDelay();
I2C_SCL = 0; //再拉低SCL
}
/* 产生总线停止信号 */
void I2CStop()
{
I2C_SCL = 0; //首先确保SDA、SCL都是低电平
I2C_SDA = 0;
I2CDelay();
I2C_SCL = 1; //先拉高SCL
I2CDelay();
I2C_SDA = 1; //再拉高SDA
I2CDelay();
}
/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
bit I2CWrite(unsigned char dat)
{
bit ack; //用于暂存应答位的值
unsigned char mask; //用于探测字节内某一位值的掩码变量
for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
{
if ((mask&dat) == 0) //该位的值输出到SDA上
I2C_SDA = 0;
else
I2C_SDA = 1;
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL,完成一个位周期
}
I2C_SDA = 1; //8位数据发送完后,主机释放SDA,以检测从机应答
I2CDelay();
I2C_SCL = 1; //拉高SCL
ack = I2C_SDA; //读取此时的SDA值,即为从机的应答值
I2CDelay();
I2C_SCL = 0; //再拉低SCL完成应答位,并保持住总线
return (~ack); //应答值取反以符合通常的逻辑:
//0=不存在或忙或写入失败,1=存在且空闲或写入成功
}
/* I2C寻址函数,即检查地址为addr的器件是否存在,返回值-从器件应答值 */
bit I2CAddressing(unsigned char addr)
{
bit ack;
I2CStart(); //产生起始位,即启动一次总线操作
ack = I2CWrite(addr<<1); //器件地址需左移一位,因寻址命令的最低位
//为读写位,用于表示之后的操作是读或写
I2CStop(); //不需进行后续读写,而直接停止本次总线操作
return ack;
}
EEPROM 24c02
字节写数据:
起始信号,首字节(EEPROM的地址和读写位组合,选择写),EEPROM内部存储地址,数据,停止。
字节读数据:
起始信号,首字节(EEPROM的地址和读写位组合,选择读),EEPROM内部存储地址,数据,发送ACK0继续读,发送NACK停止读
/* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
unsigned char I2CReadNAK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先确保主机释放SDA
for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
{
I2CDelay();
I2C_SCL = 1; //拉高SCL
if(I2C_SDA == 0) //读取SDA的值
dat &= ~mask; //为0时,dat中对应位清零
else
dat |= mask; //为1时,dat中对应位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使从机发送出下一位
}
I2C_SDA = 1; //8位数据发送完后,拉高SDA,发送非应答信号
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL完成非应答位,并保持住总线
return dat;
}
/* I2C总线读操作,并发送应答信号,返回值-读到的字节 */
unsigned char I2CReadACK()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1; //首先确保主机释放SDA
for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
{
I2CDelay();
I2C_SCL = 1; //拉高SCL
if(I2C_SDA == 0) //读取SDA的值
dat &= ~mask; //为0时,dat中对应位清零
else
dat |= mask; //为1时,dat中对应位置1
I2CDelay();
I2C_SCL = 0; //再拉低SCL,以使从机发送出下一位
}
I2C_SDA = 0; //8位数据发送完后,拉低SDA,发送应答信号
I2CDelay();
I2C_SCL = 1; //拉高SCL
I2CDelay();
I2C_SCL = 0; //再拉低SCL完成应答位,并保持住总线
return dat;
}
EEPROM读写:
/* E2读取函数,buf-数据接收指针,addr-E2中的起始地址,len-读取长度 */
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
do { //用寻址操作查询当前是否可进行读写操作
I2CStart();
if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
{
break;
}
I2CStop();
} while(1);
I2CWrite(addr); //写入起始地址
I2CStart(); //发送重复启动信号
I2CWrite((0x50<<1)|0x01); //寻址器件,后续为读操作
while (len > 1) //连续读取len-1个字节
{
*buf++ = I2CReadACK(); //最后字节之前为读取操作+应答
len--;
}
*buf = I2CReadNAK(); //最后一个字节为读取操作+非应答
I2CStop();
}
/* E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
while (len > 0)
{
//等待上次写入操作完成
do { //用寻址操作查询当前是否可进行读写操作
I2CStart();
if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
{
break;
}
I2CStop();
} while(1);
//按页写模式连续写入字节
I2CWrite(addr); //写入起始地址
while (len > 0)
{
I2CWrite(*buf++); //写入一个字节数据
len--; //待写入长度计数递减
addr++; //E2地址递增
if ((addr&0x07) == 0) //检查地址是否到达页边界,24C02每页8字节,
{ //所以检测低3位是否为零即可
break; //到达页边界时,跳出循环,结束本次写操作
}
}
I2CStop();
}
}
内存数据转换为16进制字符串:
/* 将一段内存数据转换为十六进制格式的字符串,
str-字符串指针,src-源数据地址,len-数据长度 */
void MemToStr(unsigned char *str, unsigned char *src, unsigned char len)
{
unsigned char tmp;
while (len--)
{
tmp = *src >> 4; //先取高4位
if (tmp <= 9) //转换为0-9或A-F
*str++ = tmp + '0';
else
*str++ = tmp - 10 + 'A';
tmp = *src & 0x0F; //再取低4位
if (tmp <= 9) //转换为0-9或A-F
*str++ = tmp + '0';
else
*str++ = tmp - 10 + 'A';
*str++ = ' '; //转换完一个字节添加一个空格
src++;
}
*str = '\0'; //添加字符串结束符
}
/* 将一字符串整理成16字节的固定长度字符串,不足部分补空格
out-整理后的字符串输出指针,in-待整理字符串指针 */
void TrimString16(unsigned char *out, unsigned char *in)
{
unsigned char i = 0;
while (*in != '\0') //拷贝字符串直到输入字符串结束
{
*out++ = *in++;
i++;
if (i >= 16) //当拷贝长度已达到16字节时,强制跳出循环
{
break;
}
}
for ( ; i<16; i++) //如不足16个字节则用空格补齐
{
*out++ = ' ';
}
*out = '\0'; //最后添加结束符
}
DS1302时钟
单字节读操作需要发送8位命令控制和8位数据
读写字节到总线
/* 发送一个字节到DS1302通信总线上 */
void DS1302ByteWrite(unsigned char dat)
{
unsigned char mask;
for (mask=0x01; mask!=0; mask<<=1) //低位在前,逐位移出
{
if ((mask&dat) != 0) //首先输出该位数据
DS1302_IO = 1;
else
DS1302_IO = 0;
DS1302_CK = 1; //然后拉高时钟
DS1302_CK = 0; //再拉低时钟,完成一个位的操作
}
DS1302_IO = 1; //最后确保释放IO引脚
}
/* 由DS1302通信总线上读取一个字节 */
unsigned char DS1302ByteRead()
{
unsigned char mask;
unsigned char dat = 0;
for (mask=0x01; mask!=0; mask<<=1) //低位在前,逐位读取
{
if (DS1302_IO != 0) //首先读取此时的IO引脚,并设置dat中的对应位
{
dat |= mask;
}
DS1302_CK = 1; //然后拉高时钟
DS1302_CK = 0; //再拉低时钟,完成一个位的操作
}
return dat; //最后返回读到的字节数据
}
DS1302的写和读
/* 用单次写操作向某一寄存器写入一个字节,reg-寄存器地址,dat-待写入字节 */
void DS1302SingleWrite(unsigned char reg, unsigned char dat)
{
DS1302_CE = 1; //使能片选信号
DS1302ByteWrite((reg<<1)|0x80); //发送写寄存器指令
DS1302ByteWrite(dat); //写入字节数据
DS1302_CE = 0; //除能片选信号
}
/* 用单次读操作从某一寄存器读取一个字节,reg-寄存器地址,返回值-读到的字节 */
unsigned char DS1302SingleRead(unsigned char reg)
{
unsigned char dat;
DS1302_CE = 1; //使能片选信号
DS1302ByteWrite((reg<<1)|0x81); //发送读寄存器指令
dat = DS1302ByteRead(); //读取字节数据
DS1302_CE = 0; //除能片选信号
return dat;
}
DS1302初始化:
/* DS1302初始化,如发生掉电则重新设置初始时间 */
void InitDS1302()
{
unsigned char i;
unsigned char code InitTime[] = { //2013年10月8日 星期二 12:30:00
0x00,0x30,0x12, 0x08, 0x10, 0x02, 0x13
};
DS1302_CE = 0; //初始化DS1302通信引脚
DS1302_CK = 0;
i = DS1302SingleRead(0); //读取秒寄存器
if ((i & 0x80) != 0) //由秒寄存器最高位CH的值判断DS1302是否已停止
{
DS1302SingleWrite(7, 0x00); //撤销写保护以允许写入数据
for (i=0; i<7; i++) //设置DS1302为默认的初始时间
{
DS1302SingleWrite(i, InitTime[i]);
}
突发模式写和读
当我们写指令到DS1302 的时候,只要我们将要写的 5 位地址全部写1,即读操作用0xBF, 写操作用 0xBE,这样的指令送给 DS1302 之后,它就会自动识别出来是 burst 模式,马上把所有的 8 个字节同时锁存到另外的 8 个字节的寄存器缓冲区内,这样时钟继续走,而我们读数据是从另外一个缓冲区内读取的。
/* 用突发模式连续写入8个寄存器数据,dat-待写入数据指针 */
void DS1302BurstWrite(unsigned char *dat)
{
unsigned char i;
DS1302_CE = 1;
DS1302ByteWrite(0xBE); //发送突发写寄存器指令
for (i=0; i<8; i++) //连续写入8字节数据
{
DS1302ByteWrite(dat[i]);
}
DS1302_CE = 0;
}
/* 用突发模式连续读取8个寄存器的数据,dat-读取数据的接收指针 */
void DS1302BurstRead(unsigned char *dat)
{
unsigned char i;
DS1302_CE = 1;
DS1302ByteWrite(0xBF); //发送突发读寄存器指令
for (i=0; i<8; i++) //连续读取8个字节
{
dat[i] = DS1302ByteRead();
}
DS1302_CE = 0;
}
突发模式DS1302初始化
/* DS1302初始化,如发生掉电则重新设置初始时间 */
void InitDS1302()
{
unsigned char dat;
unsigned char code InitTime[] = { //2013年10月8日 星期二 12:30:00
0x00,0x30,0x12, 0x08, 0x10, 0x02, 0x13, 0x00
};
DS1302_CE = 0; //初始化DS1302通信引脚
DS1302_CK = 0;
dat = DS1302SingleRead(0); //读取秒寄存器
if ((dat & 0x80) != 0) //由秒寄存器最高位CH的值判断DS1302是否已停止
{
DS1302SingleWrite(7, 0x00); //撤销写保护以允许写入数据
DS1302BurstWrite(InitTime); //设置DS1302为默认的初始时间
}
}
1602液晶显示:数字转换为字符
if (flag200ms) //每200ms读取依次时间
{
flag200ms = 0;
for (i=0; i<7; i++) //读取DS1302当前时间
{
time[i] = DS1302SingleRead(i);
}
if (psec != time[0]) //检测到时间有变化时刷新显示
{
str[0] = '2'; //添加年份的高2位:20
str[1] = '0';
str[2] = (time[6] >> 4) + '0'; //“年”高位数字转换为ASCII码
str[3] = (time[6]&0x0F) + '0'; //“年”低位数字转换为ASCII码
str[4] = '-'; //添加日期分隔符
str[5] = (time[4] >> 4) + '0'; //“月”
str[6] = (time[4]&0x0F) + '0';
str[7] = '-';
str[8] = (time[3] >> 4) + '0'; //“日”
str[9] = (time[3]&0x0F) + '0';
str[10] = '\0';
LcdShowStr(0, 0, str); //显示到液晶的第一行
str[0] = (time[5]&0x0F) + '0'; //“星期”
str[1] = '\0';
LcdShowStr(11, 0, "week");
LcdShowStr(15, 0, str); //显示到液晶的第一行
str[0] = (time[2] >> 4) + '0'; //“时”
str[1] = (time[2]&0x0F) + '0';
str[2] = ':'; //添加时间分隔符
str[3] = (time[1] >> 4) + '0'; //“分”
str[4] = (time[1]&0x0F) + '0';
str[5] = ':';
str[6] = (time[0] >> 4) + '0'; //“秒”
str[7] = (time[0]&0x0F) + '0';
str[8] = '\0';
LcdShowStr(4, 1, str); //显示到液晶的第二行
psec = time[0]; //用当前值更新上次秒数
}
}
时钟结构体
struct sTime { //日期时间结构体定义
unsigned int year;
unsigned char mon;
unsigned char day;
unsigned char hour;
unsigned char min;
unsigned char sec;
unsigned char week;
};
/* 获取实时时间,即读取DS1302当前时间并转换为时间结构体格式 */
void GetRealTime(struct sTime *time)
{
unsigned char buf[8];
DS1302BurstRead(buf);
time->year = buf[6] + 0x2000;
time->mon = buf[4];
time->day = buf[3];
time->hour = buf[2];
time->min = buf[1];
time->sec = buf[0];
time->week = buf[5];
}
/* 设定实时时间,时间结构体格式的设定时间转换为数组并写入DS1302 */
void SetRealTime(struct sTime *time)
{
unsigned char buf[8];
buf[7] = 0;
buf[6] = time->year;
buf[5] = time->week;
buf[4] = time->mon;
buf[3] = time->day;
buf[2] = time->hour;
buf[1] = time->min;
buf[0] = time->sec;
DS1302BurstWrite(buf);
}
DS1302初始化
/* DS1302初始化,如发生掉电则重新设置初始时间 */
void InitDS1302()
{
unsigned char dat;
struct sTime code InitTime[] = { //2013年10月8日 12:30:00 星期二
0x2013,0x10,0x08, 0x12,0x30,0x00, 0x02
};
DS1302_CE = 0; //初始化DS1302通信引脚
DS1302_CK = 0;
dat = DS1302SingleRead(0); //读取秒寄存器
if ((dat & 0x80) != 0) //由秒寄存器最高位CH的值判断DS1302是否已停止
{
DS1302SingleWrite(7, 0x00); //撤销写保护以允许写入数据
SetRealTime(&InitTime); //设置DS1302为默认的初始时间
}
}
红外通信
#include <reg52.h>
sbit IR_INPUT = P3^3; //红外接收引脚
bit irflag = 0; //红外接收标志,收到一帧正确数据后置1
unsigned char ircode[4]; //红外代码接收缓冲区
/* 初始化红外接收功能 */
void InitInfrared()
{
IR_INPUT = 1; //确保红外接收引脚被释放
TMOD &= 0x0F; //清零T1的控制位
TMOD |= 0x10; //配置T1为模式1
TR1 = 0; //停止T1计数
ET1 = 0; //禁止T1中断
IT1 = 1; //设置INT1为负边沿触发
EX1 = 1; //使能INT1中断
}
/* 获取当前高电平的持续时间 */
unsigned int GetHighTime()
{
TH1 = 0; //清零T1计数初值
TL1 = 0;
TR1 = 1; //启动T1计数
while (IR_INPUT) //红外输入引脚为1时循环检测等待,变为0时则结束本循环
{
if (TH1 >= 0x40)
{ //当T1计数值大于0x4000,即高电平持续时间超过约18ms时,
break; //强制退出循环,是为了避免信号异常时,程序假死在这里。
}
}
TR1 = 0; //停止T1计数
return (TH1*256 + TL1); //T1计数值合成为16bit整型数,并返回该数
}
/* 获取当前低电平的持续时间 */
unsigned int GetLowTime()
{
TH1 = 0; //清零T1计数初值
TL1 = 0;
TR1 = 1; //启动T1计数
while (!IR_INPUT) //红外输入引脚为0时循环检测等待,变为1时则结束本循环
{
if (TH1 >= 0x40)
{ //当T1计数值大于0x4000,即低电平持续时间超过约18ms时,
break; //强制退出循环,是为了避免信号异常时,程序假死在这里。
}
}
TR1 = 0; //停止T1计数
return (TH1*256 + TL1); //T1计数值合成为16bit整型数,并返回该数
}
/* INT1中断服务函数,执行红外接收及解码 */
void EXINT1_ISR() interrupt 2
{
unsigned char i, j;
unsigned char byt;
unsigned int time;
//接收并判定引导码的9ms低电平
time = GetLowTime();
if ((time<7833) || (time>8755)) //时间判定范围为8.5~9.5ms,
{ //超过此范围则说明为误码,直接退出
IE1 = 0; //退出前清零INT1中断标志
return;
}
//接收并判定引导码的4.5ms高电平
time = GetHighTime();
if ((time<3686) || (time>4608)) //时间判定范围为4.0~5.0ms,
{ //超过此范围则说明为误码,直接退出
IE1 = 0;
return;
}
//接收并判定后续的4字节数据
for (i=0; i<4; i++) //循环接收4个字节
{
for (j=0; j<8; j++) //循环接收判定每字节的8个bit
{
//接收判定每bit的560us低电平
time = GetLowTime();
if ((time<313) || (time>718)) //时间判定范围为340~780us,
{ //超过此范围则说明为误码,直接退出
IE1 = 0;
return;
}
//接收每bit高电平时间,判定该bit的值
time = GetHighTime();
if ((time>313) && (time<718)) //时间判定范围为340~780us,
{ //在此范围内说明该bit值为0
byt >>= 1; //因低位在先,所以数据右移,高位为0
}
else if ((time>1345) && (time<1751)) //时间判定范围为1460~1900us,
{ //在此范围内说明该bit值为1
byt >>= 1; //因低位在先,所以数据右移,
byt |= 0x80; //高位置1
}
else //不在上述范围内则说明为误码,直接退出
{
IE1 = 0;
return;
}
}
ircode[i] = byt; //接收完一个字节后保存到缓冲区
}
irflag = 1; //接收完毕后设置标志
IE1 = 0; //退出前清零INT1中断标志
}
DS18B20温度传感器
温度格式
初始化
sbit IO_18B20 = P3^2; //DS18B20通信引脚
/* 软件延时函数,延时时间(t*10)us */
void DelayX10us(unsigned char t)
{
do {
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
} while (--t);
}
/* 复位总线,获取存在脉冲,以启动一次读写操作 */
bit Get18B20Ack()
{
bit ack;
EA = 0; //禁止总中断
IO_18B20 = 0; //产生500us复位脉冲
DelayX10us(50);
IO_18B20 = 1;
DelayX10us(6); //延时60us
ack = IO_18B20; //读取存在脉冲
while(!IO_18B20); //等待存在脉冲结束
EA = 1; //重新使能总中断
return ack;
}
写
/* 向DS18B20写入一个字节,dat-待写入字节 */
void Write18B20(unsigned char dat)
{
unsigned char mask;
EA = 0; //禁止总中断
for (mask=0x01; mask!=0; mask<<=1) //低位在先,依次移出8个bit
{
IO_18B20 = 0; //产生2us低电平脉冲
_nop_();
_nop_();
if ((mask&dat) == 0) //输出该bit值
IO_18B20 = 0;
else
IO_18B20 = 1;
DelayX10us(6); //延时60us
IO_18B20 = 1; //拉高通信引脚
}
EA = 1; //重新使能总中断
}
读
/* 从DS18B20读取一个字节,返回值-读到的字节 */
unsigned char Read18B20()
{
unsigned char dat;
unsigned char mask;
EA = 0; //禁止总中断
for (mask=0x01; mask!=0; mask<<=1) //低位在先,依次采集8个bit
{
IO_18B20 = 0; //产生2us低电平脉冲
_nop_();
_nop_();
IO_18B20 = 1; //结束低电平脉冲,等待18B20输出数据
_nop_(); //延时2us
_nop_();
if (!IO_18B20) //读取通信引脚上的值
dat &= ~mask;
else
dat |= mask;
DelayX10us(6); //再延时60us
}
EA = 1; //重新使能总中断
return dat;
}
ROM 操作指令:
Skip ROM(跳过 ROM):0xCC。//当总线上只有一个器件的时候,可以跳过 ROM,不进行 ROM 检测。
RAM 存储器操作指令:
Read Scratchpad(读暂存寄存器):0xBE //DS18B20 的温度数据是 2 个字节,我们读取数据的时候,先读取到的是低字节的低位,读完了第一个字节后,再读高字节的低位,直到两个字节全部读取完毕。
Convert Temperature(启动温度转换):0x44 //从转换开始到获取温度,DS18B20 是需要时间的,而这个时间长短取决于 DS18B20 的精度。
/* 启动一次18B20温度转换,返回值-表示是否启动成功 */
bit Start18B20()
{
bit ack;
ack = Get18B20Ack(); //执行总线复位,并获取18B20应答
if (ack == 0) //如18B20正确应答,则启动一次转换
{
Write18B20(0xCC); //跳过ROM操作
Write18B20(0x44); //启动一次温度转换
}
return ~ack; //ack==0表示操作成功,所以返回值对其取反
}
读取温度
/* 读取DS18B20转换的温度值,返回值-表示是否读取成功 */
bit Get18B20Temp(int *temp)
{
bit ack;
unsigned char LSB, MSB; //16bit温度值的低字节和高字节
ack = Get18B20Ack(); //执行总线复位,并获取18B20应答
if (ack == 0) //如18B20正确应答,则读取温度值
{
Write18B20(0xCC); //跳过ROM操作
Write18B20(0xBE); //发送读命令
LSB = Read18B20(); //读温度值的低字节
MSB = Read18B20(); //读温度值的高字节
*temp = ((int)MSB << 8) + LSB; //合成为16bit整型数
}
return ~ack; //ack==0表示操作应答,所以返回值为其取反值
}
AD与DA
AD
第一个字节
第 3 位和第 7 位是固定的 0
第 4 位和第 5 位可以实现把 PCF8591 的 4 路模拟输入配置成单端模式和差分模式
控制字节的第 2 位是自动增量控制位
控制字节的第 0 位和第 1 位就是通道选择位了,00、01、10、11 代表了从 0 到 3 的一共4 个通道选择。
/* 读取当前的ADC转换值,chn-ADC通道号0~3 */
unsigned char GetADCValue(unsigned char chn)
{
unsigned char val;
I2CStart();
if (!I2CWrite(0x48<<1)) //寻址PCF8591,设定为写,如未应答,则停止操作并返回0
{
I2CStop();
return 0;
}
I2CWrite(0x40|chn); //写入控制字节,选择转换通道
I2CStart();
I2CWrite((0x48<<1)|0x01); //寻址PCF8591,指定后续为读操作
I2CReadACK(); //先空读一个字节,提供采样转换时间
val = I2CReadNAK(); //读取刚刚转换完的值
I2CStop();
return val;
}
/* ADC转换值转为实际电压值的字符串形式,str-字符串指针,val-AD转换值 */
void ValueToString(unsigned char *str, unsigned char val)
{
//电压值=转换结果*2.5V/255,式中的25隐含了一位十进制小数
val = (val*25) / 255;
str[0] = (val/10) + '0'; //整数位字符
str[1] = '.'; //小数点
str[2] = (val%10) + '0'; //小数位字符
str[3] = 'V'; //电压单位
str[4] = '\0'; //结束符
}
DA
第三个字节
发送给 PCF8591 的第三个字节 D/A 数据寄存器,表示 D/A 模拟输出的电压值。
一个 8 位的 D/A,从 0~255,代表了 0~2.55V 的话,那么我们用单片机给第三个字节发送 100,D/A 引脚就会输出一个 1V 的电压,发送 200 就输出一个 2V 的电压。
/* 设置DAC输出值,val-设定值 */
void SetDACOut(unsigned char val)
{
I2CStart();
if (!I2CWrite(0x48<<1)) //寻址PCF8591,如未应答,则停止操作并返回
{
I2CStop();
return;
}
I2CWrite(0x40); //写入控制字节
I2CWrite(val); //写入DA值
I2CStop();
}
/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode)
{
static unsigned char volt = 0; //输出电压值,隐含了一位十进制小数位
if (keycode == 0x26) //向上键,增加0.1V电压值
{
if (volt < 25)
{
volt++;
SetDACOut(volt*255/25); //转换为AD输出值
}
}
else if (keycode == 0x28) //向下键,减小0.1V电压值
{
if (volt > 0)
{
volt--;
SetDACOut(volt*255/25); //转换为AD输出值
}
}
}
RS485通信与Modbus协议
RS485
sbit RS485_DIR = P1^7; //RS485方向选择引脚
/* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
void UartWrite(unsigned char *buf, unsigned char len)
{
RS485_DIR = 1; //RS485设置为发送
while (len--) //循环发送所有字节
{
flagTxd = 0; //清零发送标志
SBUF = *buf++; //发送一个字节数据
while (!flagTxd); //等待该字节发送完成
}
DelayX10us(5); //等待最后的停止位完成,延时时间由波特率决定
RS485_DIR = 0; //RS485设置为接收
}
Modbus
/* 串口动作函数,根据接收到的命令帧执行响应的动作
buf-接收到的命令帧指针,len-命令帧长度 */
void UartAction(unsigned char *buf, unsigned char len)
{
unsigned char i;
unsigned char cnt;
unsigned char str[4];
unsigned int crc;
unsigned char crch, crcl;
if (buf[0] != 0x01) //本例中的本机地址设定为0x01,
{ //如数据帧中的地址字节与本机地址不符,
return; //则直接退出,即丢弃本帧数据不做任何处理
}
//地址相符时,再对本帧数据进行校验
crc = GetCRC16(buf, len-2); //计算CRC校验值
crch = crc >> 8;
crcl = crc & 0xFF;
if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))
{
return; //如CRC校验不符时直接退出
}
//地址和校验字均相符后,解析功能码,执行相关操作
switch (buf[1])
{
case 0x03: //读取一个或连续的寄存器
if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持0x0000~0x0005
{
if (buf[3] <= 0x04)
{
i = buf[3]; //提取寄存器地址
cnt = buf[5]; //提取待读取的寄存器数量
buf[2] = cnt*2; //读取数据的字节数,为寄存器数*2
len = 3; //帧前部已有地址、功能码、字节数共3个字节
while (cnt--)
{
buf[len++] = 0x00; //寄存器高字节补0
buf[len++] = regGroup[i++]; //寄存器低字节
}
}
else //地址0x05为蜂鸣器状态
{
buf[2] = 2; //读取数据的字节数
buf[3] = 0x00;
buf[4] = flagBuzzOn;
len = 5;
}
break;
}
else //寄存器地址不被支持时,返回错误码
{
buf[1] = 0x83; //功能码最高位置1
buf[2] = 0x02; //设置异常码为02-无效地址
len = 3;
break;
}
case 0x06: //写入单个寄存器
if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持0x0000~0x0005
{
if (buf[3] <= 0x04)
{
i = buf[3]; //提取寄存器地址
regGroup[i] = buf[5]; //保存寄存器数据
cnt = regGroup[i] >> 4; //显示到液晶上
if (cnt >= 0xA)
str[0] = cnt - 0xA + 'A';
else
str[0] = cnt + '0';
cnt = regGroup[i] & 0x0F;
if (cnt >= 0xA)
str[1] = cnt - 0xA + 'A';
else
str[1] = cnt + '0';
str[2] = '\0';
LcdShowStr(i*3, 0, str);
}
else //地址0x05为蜂鸣器状态
{
flagBuzzOn = (bit)buf[5]; //寄存器值转为蜂鸣器的开关
}
len -= 2; //长度-2以重新计算CRC并返回原帧
break;
}
else //寄存器地址不被支持时,返回错误码
{
buf[1] = 0x86; //功能码最高位置1
buf[2] = 0x02; //设置异常码为02-无效地址
len = 3;
break;
}
default: //其它不支持的功能码
buf[1] |= 0x80; //功能码最高位置1
buf[2] = 0x01; //设置异常码为01-无效功能
len = 3;
break;
}
crc = GetCRC16(buf, len); //计算返回帧的CRC校验值
buf[len++] = crc >> 8; //CRC高字节
buf[len++] = crc & 0xFF; //CRC低字节
UartWrite(buf, len); //发送返回帧
}
多功能电子钟
条件编译
#ifndef __REG52_H__ //如果没有定义__REG52_H__,如果定义了__REG52——H__,则不往下执行
#define __REG52_H__ //定义__REG52_H__
外部变量声明
#ifndef __TIME_C //如果没有定义__TIME_C,则可以声明外部变量,就可以防止在time.c里重复声明,还可以在别的文件里使用声明变量
//声明外部变量
#endif