描述
开 本: 32开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302462187丛书名: 清华开发者书库
全书共分9章:第1章为基础篇,着重MSP430单片机工作原理以及IAR编译软件的应用;第2~9章为单片机设计,包括硬件系统设计和软件编程。全书叙述简洁、概念清晰,提供了大量应用实例,具备完整的硬件电路图和软件清单,涵盖了MSP430F169单片机设计的诸多内容。
本书适合作为高等院校电气、自动化专业高年级本科生、研究生的及高校教师教学参考书,还可以供相关工程技术人员参考。
目录
第1章单片机原理概述及C编程语言
1.1MSP430单片机概述
1.2初步认识MSP430单片机
1.3MSP430F169单片机小系统
1.4C语言概述
1.4.1C的变量与数据类型
1.4.2C的运算符和表达式
1.5常用的I/O相关寄存器及操作
1.6C语言的程序结构
1.6.1顺序结构
1.6.2选择结构
1.6.3循环结构
1.7C语言的函数
1.8I/O端口常用操作C语言描述及常用C语言解析
1.9把51单片机的C语言转换成MSP430单片机的C语言
1.10MSP430编译软件使用
1.11自制(头)文件方法
第2章单片机输出电路设计
2.1单片机控制系统设计概述
2.2液晶1602的显示
2.3液晶12864的显示
2.3.1液晶12864并行显示
2.3.2液晶12864串行显示
2.4LED点阵的显示
2.5液晶12232的显示
2.62.4in彩屏TFT的显示
2.6.12.4in彩屏TFT简介
2.6.2显存地址指针与窗口工作模式
2.6.3常用寄存器设置
第3章单片机输入电路设计
3.1键盘的输入电路
3.2带函数和小数点的计算器设计
3.3电子密码锁设计
3.4步进电机控制系统设计
3.5温度检测系统设计
3.6温湿度传感器的设计
3.7电子秤的设计
第4章定时器/计数器和外部中断系统设计
4.1MSP430单片机时钟源
4.2定时器/计数器概述
4.3TIMER_A工作模式
4.4定时器A模块捕获/比较工作原理
4.5定时器/计数器A与PWM
4.6外部中断的概述
4.7秒表设计
4.8红外遥控设计
4.9超声波测距系统设计
4.10定时器/计数器B
4.11定时器/计数器B与PWM
4.12直流电机控制系统设计
第5章串行通信
5.1串行通信概述
5.2USART相关寄存器
5.3串行通信协议
5.4串行通信系统设计
第6章I2C接口的应用
6.1I2C通信协议概述
6.2I2C模式操作
6.3I2C寄存器说明
6.4具有断电保护的电子密码锁设计
6.4.1AT24C02芯片简介
6.4.2具有断电保护的电子密码锁设计
第7章同步串行SPI接口
7.1同步串行SPI接口概述
7.2SPI相关寄存器
7.3SPI通信设计举例——无线模块通信设计
第8章AD与DA转换器
8.1AD转换器概述
8.2ADC12结构及特点
8.3ADC相关寄存器设置
8.4ADC12转换模式
8.5AD应用实例
8.6DA转换器概述
8.7DAC12结构与性能
8.8DAC相关寄存器设置
8.9DAC12的操作及设置和应用
第9章单片机综合系统设计
9.1两路温度检测系统设计
9.2红外遥控直流电机调速系统设计
9.3无线通信直流电机调速系统设计
9.4用VB语言编制串行助手界面控制步进电机调速系统设计
9.5门禁控制系统设计
9.6蓝牙控制系统设计
9.7彩屏和摄像头控制系统设计
参考文献
前言
盐城工学院
2017年4月
3.1键盘的输入电路
键盘是常用的单片机输入电路,键盘主要分为两类,一类是独立式键盘,另一类是矩阵式键盘。如图31(a)所示为独立式键盘,虽然MSP430单片机工作电压为3.3V,其独立式键盘接的上拉电阻仍为4.8~10kΩ。其工作原理是将对应端口设置为输入并上拉,当键盘未按下,相应的端口为高电平,当键盘被按下时,相应的端口被拉为低电平。在程序中通过查询方法判断端口是否为低电平,如果是,就进入此键盘处理程序。独立式键盘适合键盘数量较少的场合,因为占用单片机的端口较多,比如8个键盘都采用独立式键盘,就需要占用单片机8个端口,而矩阵式键盘可以节省单片机端口。矩阵式键盘见图31(b)。
图31独立式键盘和矩阵式键盘
对于矩阵式键盘通常采用扫描法,设矩阵式键盘接单片机P1口相应的端口。其工作原理是,先将P1.7~P1.4口设置为输出,其中有一位设置为低电平,其余设置为高电平。例如将P1.4设置为输出且低电平,将P1.7~P1.5设置为输出且为高电平,将P1.3~P1.0口设置为输入并上拉,用C语言描述,即P1DIR=0xf0; P1OUT=0xef。 然后读取P1口数值,即检测P1电压,如果没有按钮按下,则P1口电压(数值)保持不变,其数值仍为0xef,如果在矩阵式键盘第4列有按键按下,则P1电压(数值)发生变化,不再为0xef,假定标志为“0”的键盘按下,按键造成短路,则单片机P1.0端口就变成低电平,用C语言描述,即“if((P1IN&0x01)==0)”条件成立,把这个按键取键值为0(keyvalue=0); 如果是标志为“4”的键盘按下,按键造成短路,则单片机P1.1端口就变成低电平; 即“if((P1IN&0x02)==0)”条件成立,把这个按键取键值为4(keyvalue=4); 同理可以确定标志为“8”或“12”的键盘。然后再将P1.5设置为输出且低电平,P1.7、P1.6、P1.4设置为输出且高电平,将P1.3~P1.0口设置为输入并上拉,用同样的方法就能确定第3列键盘哪个键盘按下。同理就可以确定第2列、第1列哪个键盘被按下。对于矩阵式键盘,一般情况下,用9个键盘代表0~9的数值,称为数值键,而大于9的数值键盘可以代表其他功能,称为功能键。对于键盘操作还有一个重要问题,即键抖动现象,简单说来,虽然只按一下按键然后松开,但由于单片机运行程序速度很快,它能多次运行键盘程序,往往会认为按了多次键盘。对于消除键盘抖动,有硬件消抖和软件消抖,常用的是软件消抖,有多种方法。其中一种方法是在程序中检测到按键按下后,延迟一段时间,在按键处理程序结束后,再延时一段时间,实践表明能够很好地解决键盘抖动问题。
3.2带函数和小数点的计算器设计设计要求有带函数和小数点的计算器设计,设计采用1602作为显示。此设计有一个小数点的位置问题,如果小数点位置是变化的,比如显示3.4或3.48,那么小数点就发生变化,称之为“浮动”小数点; 如果小数点的位置保持不变,比如显示3.40或3.48,称之为“固定”小数点。市面上的计算器都是采用“浮动”小数点,编程复杂得多,本次设计采用“浮动”小数点方法。第二个设计特点就是具有函数运算,把功能键复用,另加一个独立键,当按下独立键盘S1时,表明键盘当作了sina、cos、tang等功能用。其设计思路是,独立键S1未按下时,由于接上拉电阻,P3.0设置为输入,则P3.0输入为高电平,就运行四则运算程序; 独立键S1按下时,则P3.0输入为低电平,就运行函数程序了。按下复位键盘后,重新开始计算。当出现被除数为零时,液晶显示“error input”。函数的运算,好的方法是加个头文件“math.h”,该头文件包含sina、cos、tang、ctang函数等运算。带函数和小数点的计算器硬件电路图如图32所示。
图32带函数和小数点的计算器设计电路图
采用8MHz晶振作为时钟源。P4口接矩阵式键盘,P3.0接独立式键盘。程序清单如下:
#include
#include
#define uchar unsigned char
#define uint unsigned int
#define set_rs P1OUT|=BIT3
#define clr_rs P1OUT&=~BIT3
#define set_lcden P1OUT|=BIT5
#define clr_lcden P1OUT&=~BIT5
#define dataout P2DIR=0XFF
#define dataport P2OUT
#define anjian (P3IN&BIT0)
uchar keyvalue;
uchar wei0=0,fuhao=0,flag1=0,flag2=0;
uchar tab1[]={“sin”};
uchar tab2[]={“cos”};
uchar tab3[]={“tan”};
uchar tab4[]={“=”};
uchar dis_flag=0,time_1s_ok;
uint num=0;
uint time=0;
uchar dis_flag;
uint i,j;
int t,a,shu,k;
int jd,jd1,result,result1;
uint time_counter;
void int_clk()
{
unsigned char i;
BCSCTL1&=~XT2OFF; //打开XT振荡器
BCSCTL2|=SELM1 SELS; //MCLK为8MHz,SMCLK为1MHz
do
{
IFG1&=~OFIFG; //清除振荡器错误标志
for(i=0; i<100; i )
_NOP(); //延时等待
}
while((IFG1&OFIFG)!=0); //如果标志为1,则继续循环等待
IFG1&=~OFIFG;
}
void delay5ms(void)
{
unsigned int i=40000;
while (i != 0)
{
i–;
}
}
void write_com(uchar com) //1602写命令
{
P1DIR|=BIT3 ;
P1DIR|=BIT5 ;
P2DIR=0xff;
clr_rs;
clr_lcden;
P2OUT=com;
delay5ms( );
delay5ms( );
set_lcden;
delay5ms( );
clr_lcden;
}
void write_date(uchar date) //1602写数据
{
P1DIR|=BIT3 ;
P1DIR|=BIT5 ;
P2DIR=0xff;
set_rs;
clr_lcden;
P2OUT=date;
delay5ms( );
delay5ms( );
set_lcden;
delay5ms( );
clr_lcden;
}
void disp(unsigned char *s)
{
while(*s > 0)
{
write_date(*s);
s ;
}
}
void lcddelay( )
{
unsigned int j;
for(j=400; j>0; j–);
}
void lcd_pos(unsigned char x,unsigned char y)
{
dataport=0x80 0x40*x y;
P1DIR|=BIT3 ;
P1DIR|=BIT5 ;
P2DIR=0xff;
clr_rs;
clr_lcden;
delay5ms( );
delay5ms( );
set_lcden;
delay5ms( );
clr_lcden;
}
void init( )
{
clr_lcden;
write_com(0x38);
delay5ms( );
write_com(0x0c);
delay5ms( );
write_com(0x06);
delay5ms( );
write_com(0x01);
}
void display(unsigned long int num)
{
uchar dis_flag=0;
uchar table[7];
if(num<=9&num>0)
{
dis_flag=1;
table[0]=num%10 ‘0’;
}
else if(num<=99&num>9)
{
dis_flag=2;
table[0]=num/10 ‘0’;
table[1]=num%10 ‘0’;
}
else if(num<=999&num>99)
{
dis_flag=3;
table[0]=num/100 ‘0’;
table[1]=num/10%10 ‘0’;
table[2]=num%10 ‘0’;
}
else if(num<=9999&num>999)
{
dis_flag=4;
table[0]=num/1000 ‘0’;
table[1]=num/100%10 ‘0’;
table[2]=num/10%10 ‘0’;
table[3]=num%10 ‘0’;
}
else if(num<=99999& num>9999)
{
dis_flag=5;
table[0]=num/10000 ‘0’;
table[1]=num/1000%10 ‘0’;
table[2]=num/100%10 ‘0’;
table[3]=num/10%10 ‘0’;
table[4]=num%10 ‘0’;
}
else if(num<=999999& num>99999)
{
dis_flag=6;
table[0]=num/100000 ‘0’;
table[1]=num/10000%10 ‘0’;
table[2]=num/1000%10 ‘0’;
table[3]=num/100%10 ‘0’;
table[4]=num/10%10 ‘0’;
table[5]=num%10 ‘0’;
}
else if(num<=9999999& num>999999)
{
dis_flag=7;
table[0]=num/1000000 ‘0’;
table[1]=num/100000%10 ‘0’;
table[2]=num/10000%10 ‘0’;
table[3]=num/1000%10 ‘0’;
table[4]=num/100%10 ‘0’;
table[5]=num/10%10 ‘0’;
table[6]=num%10 ‘0’;
}
if((fuhao==4)&&(flag1==1))
{
if(dis_flag<4)
{
write_date(‘0’);
delay5ms();
write_date(‘.’);
delay5ms();
for(i=0; i{
write_date(‘0’);
delay5ms();
}
}
}
for(i=0; i{
if((fuhao==1)||(fuhao==2))
{
if(i==dis_flag-wei0)
{
write_date(‘.’);
delay5ms();
}
}
if(fuhao==3)
{
if(i==dis_flag-wei0*2)
{
write_date(‘.’);
delay5ms();
}
}
if(fuhao==4)
{
if(dis_flag>3)
{
if(i==dis_flag-3)
{
write_date(‘.’);
delay5ms();
}
}
}
write_date(table[i]);
delay5ms();
}
}
uchar keyscan(void)
{
P4OUT=0xef;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=1;
if((P4IN&0x02)==0)
keyvalue=2;
if((P4IN&0x04)==0)
keyvalue=3;
if((P4IN&0x08)==0)
keyvalue=4;
while((P4IN&0x0f)!=0x0f);
}
}
P4OUT=0xdf;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=5;
if((P4IN&0x02)==0)
keyvalue=6;
if((P4IN&0x04)==0)
keyvalue=7;
if((P4IN&0x08)==0)
keyvalue=8;
while((P4IN&0x0f)!=0x0f);
}
}
P4OUT=0xbf;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=9;
if((P4IN&0x02)==0)
keyvalue=0;
if((P4IN&0x04)==0)
keyvalue=10;
if((P4IN&0x08)==0)
keyvalue=11;
while((P4IN&0x0f)!=0x0f);
}
}
P4OUT=0x7f;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=12;
if((P4IN&0x02)==0)
keyvalue=13;
if((P4IN&0x04)==0)
keyvalue=14;
if((P4IN&0x08)==0)
keyvalue=15;
while((P4IN&0x0f)!=0x0f);
}
}
return keyvalue;
}
void main()
{
long int shu=0,shu1=0,shu1_1=0,shu2=0,shu2_1=0,shu3=0,shu4=0,shu5=0,i=0,a=0,result=0,jd=0;
uchar flag=1,mode=0;
uchar wei1=0,wei2=0;
float shu6=0,sj=0,pi=3.14159;
WDTCTL = WDTPW WDTHOLD; //关闭看门狗
int_clk();
dataout;
init();
P1DIR|=BIT3 BIT5;
P4DIR=0xf0;
while(1)
{
keyvalue=17;
keyscan();
shu=keyvalue;
lcd_pos(0,0);
if(anjian==0)
{
mode=1;
}
if(mode==1)
{
lcd_pos(0,0);
write_date(‘*’);
delay5ms();
if(shu<10)
{
jd=jd*10 shu;
result =jd;
display(result);
}
if(shu==10)
{
lcd_pos(1,0);
sj=sin(jd*pi/180) 0.0005;
disp(tab1);
display(jd);
disp(tab4);
fuhao=4;
flag1=1;
display((long)(sj*1000));
}
if(shu==11)
{
lcd_pos(1,0);
sj=cos(jd*pi/180) 0.0005;
disp(tab2);
display(jd);
disp(tab4);
fuhao=4;
flag1=1;
display((long)(sj*1000));
}
if(shu==12)
{
lcd_pos(1,0);
sj=tan(jd*pi/180) 0.0005;
disp(tab3);
display(jd);
disp(tab4);
fuhao=4;
flag1=1;
display((long)(sj*1000));
}
}
if(mode==0)
{
lcd_pos(0,0);
if(shu<=9)
{
if(flag==1)
{
shu1=shu1*10 shu;
result =shu1;
}
if(flag==2)
{
wei1 ;
shu1_1=shu1_1*10 shu;
result =shu1_1;
}
if(flag==3)
{
shu2=shu2*10 shu;
result =shu2;
}
if(flag==4)
{
wei2 ;
shu2_1=shu2_1*10 shu;
result =shu2_1;
}
lcd_pos(0,a);
write_date(shu ‘0’);
delay5ms();
a ;
}
if(shu==10)
{
lcd_pos(0,a);
if(flag==1)
{
write_date(‘.’);
delay5ms();
flag=2;
}
if(flag==3)
{
write_date(‘.’);
delay5ms();
flag=4;
}
a ;
}
if((shu>10)&&(shu<15))
{
lcd_pos(0,a);
switch(shu)
{
case 11: write_date(‘ ‘); flag=3; fuhao=1; break;
case 12: write_date(‘-‘); flag=3; fuhao=2; break;
case 13: write_date(‘*’); flag=3; fuhao=3; break;
case 14: write_date(‘/’); flag=3; fuhao=4; break;
default: break;
}
a ;
}
if(shu==15)
{
lcd_pos(1,0);
write_date(‘=’);
delay5ms();
flag1=1;
if(wei1>wei2)
{
wei0=wei1;
for(i=0; i{
shu1=shu1*10;
shu2=shu2*10;
}
for(i=0; i{
shu2_1=shu2_1*10;
}
shu3=shu1 shu1_1;
shu4=shu2 shu2_1;
}
else
{
wei0=wei2;
for(i=0; i{
shu1=shu1*10;
shu2=shu2*10;
}
for(i=0; i{
shu1_1=shu1_1*10;
}
shu3=shu1 shu1_1;
shu4=shu2 shu2_1;
}
if((shu4==0)&&(fuhao==4))
{
fuhao=5;
lcd_pos(1,0);
delay5ms();
}
switch(fuhao)
{
case 1: shu5=shu3 shu4; break;
case 2: shu5=shu3-shu4; break;
case 3: shu5=shu3*shu4; break;
case 4: shu6=(float)shu3/shu4 0.0005; break;
default: break;
}
if(shu5<0)
{
write_date(‘-‘);
delay5ms();
shu5=0-shu5;
}
if(fuhao<4)
{
display(shu5);
}
else if(fuhao==4)
{
display((long)(shu6*1000));
}
}
}
}
}
操作过程如下:对于如图32所示的矩阵式键盘,在本程序中,从右到左,行键盘的标号从右到左依次为1、5、9、12,第二行键盘标号从右到左依次为2、6、0、13,第三行键盘标号从右到左依次为3、7、10、14,第四行键盘标号从右到左依次为4、8、11、15,标号小于10都是数值键,而标号大于或等于10的都是功能键。在进行四则运算时,标号为10的键盘是小数点,标号为11的键盘是“ ”,标号为12的键盘是“-”,标号为13的键盘是“×”,标号为14的键盘是“÷”,标号为15的键盘是“=”。在进行函数运算时,先按下S1,1602显示一个“*”号,然后按下数值,此时标号为10的键盘代表“Sin”,标号为11的键盘代表“Cos”,标号为12的键盘代表“Tan”,按对应的功能键会计算出正余弦值,正切值。这里需要指出的是由于程序不完善,本设计没有小数的正余弦值和正切值的计算。实验板上带函数和小数点的结果如图33所示,从图33可以看出设计的正确性。
图33实验板上带函数和小数点的结果
3.3电子密码锁设计基于单片机的电子密码锁设计主要功能是,当单片机上电后,四位数码管显示一位数为零,按数字键设定密码再按确认键盘,数码管显示“8888”以保护密码。然后按数字键,再按确认键,如果密码正确,发光二极管亮; 如果输入密码错误,蜂鸣器响。只有在密码正确的情况下,才能修改密码。具体操作步骤是,输入正确密码后,再按确认键。这时再按“修改密码”键盘,输入新的密码,按确认键后新的密码就确定了。同时,三次输入错误,单片机锁死,按任何键盘都不起作用。必须按下复位键盘,才能重新输入。硬件电路图如图34所示。可以看出采用矩阵式4×4键盘,除包括0~9的数值外,还包括确认键,数码管段选通过限流电阻接P1口,而位选接P2口。发光二极管串限流电阻接P3.7端口,而控制蜂鸣器接P3.0端口。
图34基于单片机的电子密码锁设计
完整程序清单如下:
#include
#include
#define uchar unsigned char
#define uint unsigned int
#define CPU_F ((double)8000000)
#define delayus(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0)) //宏定义延时函数
#define delayms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))
uchar keyvalue;
uchar wei0=0,fuhao=0,flag1=0,flag2=0;
uchar tab[]={0,0,0,0};
uchar tab1[]={0,0,0,0};
uchar LED[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
#define LE P3OUT
#define LED0 P3OUT&=~BIT7
#define LED1 P3OUT|=BIT7
#define FMQ P2OUT
#define FMQ0 P3OUT&=~BIT0
#define FMQ1 P3OUT|=BIT0
uchar dis_flag=0,time_1s_ok;
uint num=0;
uint time=0;
uchar dis_flag;
uint i,j;
int t,a,shu,k;
int jd,jd1,result,result1;
uint time_counter;
void int_clk()
{
unsigned char i;
BCSCTL1&=~XT2OFF; //打开XT振荡器
BCSCTL2|=SELM1 SELS; //MCLK为8MHz,SMCLK为1MHz
do
{
IFG1&=~OFIFG; //清除振荡器错误标志
for(i=0; i<100; i )
_NOP(); //延时等待
}
while((IFG1&OFIFG)!=0); //如果标志为1,则继续循环等待
IFG1&=~OFIFG;
}
void delay5ms(void)
{
unsigned int i=40000;
while (i != 0)
{
i–;
}
}
void display(int num)
{
tab[0]=num%10;
tab[1]=num/10%10;
tab[2]=num/100%10;
tab[3]=num/1000%10;
if(num<=9)
{
P1OUT=LED[tab[0]];
P2OUT=0xdc;
delayms(1);
}
if(num>9&&num<=99)
{
P1OUT=LED[tab[0]];
P2OUT=0xdc;
delayms(1);
P2OUT=0xfC;
P1OUT=LED[tab[1]];
P2OUT=0xec;
delayms(1);
P2OUT=0xfC;
}
if(num>99&&num<=999)
{
P1OUT=LED[tab[0]];
P2OUT=0xdc;
delayms(1);
P2OUT=0xfC;
P1OUT=LED[tab[1]];
P2OUT=0xec;
delayms(1);
P2OUT=0xfc;
P1OUT=LED[tab[2]];
P2OUT=0xf4;
delayms(1);
P2OUT=0xfc;
}
if(num>999&&num<=9999)
{
P1OUT=LED[tab[0]];
P2OUT=0xdc;
delayms(1);
P2OUT=0xfc;
P1OUT=LED[tab[1]];
P2OUT=0xec;
delayms(1);
P2OUT=0xfc;
P1OUT=LED[tab[2]];
P2OUT=0xf4;
delayms(1);
P2OUT=0xfc;
P1OUT=LED[tab[3]];
P2OUT=0xf8;
delayms(1);
P2OUT=0xfc;
}
}
uchar keyscan(void)
{
P4OUT=0xef;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=1;
if((P4IN&0x02)==0)
keyvalue=2;
if((P4IN&0x04)==0)
keyvalue=3;
if((P4IN&0x08)==0)
keyvalue=4;
while((P4IN&0x0f)!=0x0f);
}
}
P4OUT=0xdf;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=5;
if((P4IN&0x02)==0)
keyvalue=6;
if((P4IN&0x04)==0)
keyvalue=7;
if((P4IN&0x08)==0)
keyvalue=8;
while((P4IN&0x0f)!=0x0f);
}
}
P4OUT=0xbf;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=9;
if((P4IN&0x02)==0)
keyvalue=0;
if((P4IN&0x04)==0)
keyvalue=10;
if((P4IN&0x08)==0)
keyvalue=11;
while((P4IN&0x0f)!=0x0f);
}
}
P4OUT=0x7f;
if((P4IN&0x0f)!=0x0f)
{
delay5ms();
if((P4IN&0x0f)!=0x0f)
{
if((P4IN&0x01)==0)
keyvalue=12;
if((P4IN&0x02)==0)
keyvalue=13;
if((P4IN&0x04)==0)
keyvalue=14;
if((P4IN&0x08)==0)
keyvalue=15;
while((P4IN&0x0f)!=0x0f);
}
}
return keyvalue;
}
void main()
{
WDTCTL = WDTPW WDTHOLD; //关闭看门狗
int num=0;
int i,shu,shu2=0,aa=0,flag=0,flag1=0,shu1=0,shu3=0,shu4=0,shu5=0,shu6=0;
shu=0;
int_clk();
P1DIR=0xff;
P1OUT=0xff;
P2DIR=0xff;
P2OUT=0xff;
P3DIR=0xff;
P3OUT=0xff;
P4DIR=0xf0;
while(1)
{
keyvalue=17;
keyscan();
num=keyvalue;
if(num<=9&&flag==0)
{
shu1=shu1*10 num;
shu=shu1;
}
if(num>=0&&num<=9&&flag==1)
{
shu2=shu2*10 num;
shu=shu2;
}
if(num==11&&flag==0)
{
shu=8888;
flag=1;
num=17;
}
else if(num==11&&flag==1)
{
num=17;
if(shu1==shu2)
{
aa=0;
flag1=1;
LED1;
delayms(2000);
LED0;
shu2=0;
}
else
{
flag1=0;
shu2=0;
aa=aa 1;
FMQ1;
delayms(5);
FMQ0;
}
shu=8888;
}
if(aa==3)
{
shu=0;
while(1)
{
P1OUT=0x00;
}
}
if((num==12)&&(flag1==1))
{
shu1=0;
shu2=0;
flag=0;
}
display(shu);
}
}
下面对一些程序进行解释:
if(num==11&&flag==0)
{
shu=8888;
flag=1;
num=17;
}
num数值为11是确认键,当确认键次按下后,之所以再写个num=17是为了防止蜂鸣器响,是因为num赋值为11后,即使没按任何键盘,从后面程序:
else
{
flag1=0;
shu2=0;
aa=aa 1;
FMQ1;
delayms(5);
FMQ0;
}
shu=8888;
}
可以看出,shu1肯定不等于shu2,则蜂鸣器就响了。而写了num=17后,单片机就不再执行if(num==11)条件的程序,保证了操作正确。再看下面程序:
if(aa==3)
{
shu=0;
while(1)
{
P1OUT=0x00;
}
}
这是第二次按下确认键时,对第二次的确认键计数,当第二次确认键达到3次后,就执行while(1)程序,进入了死循环,从而把电子密码锁锁死。电子密码锁锁死后,再按任何键都不起作用,数码管无显示。实际上这条语句可以再进行改进,在while(1)中可以再写两行程序,使得数码管显示“E”表明密码锁已经锁死。思路是将显示“E”的十六进制数赋值给P1口,位选使得个数码管亮即可,读者可以自己试一下。实物图如图35所示,
图(a)为实物通电前的状态; 图(b)为实物通电后的状态; 图(c)为设定密码,密码为: 1256; 图(d)为密码确定后显示; 图(e)为输入密码正确发光二极管灯亮。
图35实物展示图
3.4步进电机控制系统设计设计要求: 由按键把步进电机需要转动的圈数显示在12864上,按下某个键后,步进电机旋转,每减小1圈,都在液晶上实时显示出来。同时还有步进电机高低速切换、正反转控制功能。遇到紧急情况按下停止按钮时,电机停止,液晶圈数显示为零。步进电机28BYJ48是四相五线八拍电机,电压在DC5V~DC12V之间。当给步进电机施加一系列连续不间断的控制脉冲时,步进电机可以连续不断地旋转。每一个脉冲信号对应的是步进电机的某一相或两相绕组的通电状态的改变,也就使得对应的转子转过一定的角度(即为一个步距角)。当通电状态的改变完成一个完整的循环时,转子就能刚好转过一个完整的齿距。四相步进电机可以在不同的通电方式下运行,常见的通电方式有单(单相绕组通电)四拍(ABCDA…),双(双相绕组通电)四拍(ABBCCDDAAB…),八拍(AABBBCCCDDDAA…)等。本设计使用的5V步进电机是4相5线式的减速步进电机,型号为28BYJ48。它是一种将电脉冲转化为角位移的执行机构。通俗一点讲: 当步进驱动器接收到一个脉冲信号,它就驱动步进电机按设定的方向转动一个固定的角度(步进角)。我们可以通过控制脉冲来控制角位移量,从而达到准确定位的目的; 同时我们也可以通过控制脉冲频率来控制电机转动的速度和加速度,从而达到调速的目的。由于单片机接口信号不够大,需要通过ULN2003放大再连接到相应的电机接口。ULN2003AN介绍: ULN2003AN是一个7路反向器电路,即当输入端为高电平时,ULN2003AN对应输出端为低电平; 当输入端为低电平时,ULN2003AN对应输出端为高电平。ULN2003AN的引脚排列如图36所示。
有些读者很奇怪,为什么步进电机要接ULN2003AN芯片,而不能直接接单片机端口,ULN2003AN芯片是如何工作的?下面来详细说明。由于单片机端口的输出或输入(灌输)电流较小,当负载需要较大电流时,就不能带动负载。以图37为例,假如单片机端口直接接步进电机,当PD5端口为低电平时,通过步进电机的电流路线如图37所示,由于输入到单片机端口的电流较小,就不能带动步进电机。
图36ULN2003AN引脚图
图37单片机控制步进电机示意图
那么单片机接口接ULN2003AN芯片,再看如何工作的。以图38为例来进行说明。
图38单片机通过ULN2003AN芯片控制步进电机示意图
当单片机端口P1为输出状态且P1.6为高电平时,ULN2003AN芯片的反向作用,对应的端口OUT4为低电平,则电流如图虚线所示。由于ULN2003AN芯片可以输入较大的电流,从而带动步进电机运行。实际上单片机正是因为端口的输入或输出电流较小,带功率较大负载时往往需要加驱动电路,很多驱动的工作原理与以上相同。整个硬件电路图如图39所示。
图39基于单片机的控制步进电机电路图
矩阵式键盘接P5口,液晶采用串行控制方式。整个程序清单如下:
#include
typedef unsigned char uchar;
typedef unsigned int uint;
uchar keyvalue,key_shu,shu5,v,k,dis_flag;
int a;
uchar table[7]=””;
uchar tab0[]={“步进电机调速系统”};
uchar tab1[]={“设定圈数:”};
uchar tab2[]={“高速”};
uchar tab3[]={“低速”};
uchar tab4[]={“正转”};
uchar tab5[]={“反转”};
#define SID BIT1
#define SCLK BIT2
#define CS BIT0
#define LCDPORT P4OUT
#define SID_1LCDPORT |= SID //SID置高P4.1
#define SID_0 LCDPORT &= ~SID //SID置低
#define SCLK_1 LCDPORT |= SCLK //SCLK置高P4.2
#define SCLK_0 LCDPORT &= ~SCLK //SCLK置低
#define CS_1 LCDPORT |= CS //CS置高P4.3
#define CS_0 LCDPORT &= ~CS //CS置低
void int_clk()
{
unsigned char i;
BCSCTL1&=~XT2OFF; //打开XT振荡器
BCSCTL2|=SELM1 SELS; //MCLK为8MHz,SMCLK为1MHz
do
{
IFG1&=~OFIFG; //清除振荡器错误标志
for(i=0;i<100;i )
_NOP(); //延时等待
}
while((IFG1&OFIFG)!=0); //如果标志为1,则继续循环等待
IFG1&=~OFIFG;
}
/*******************************************
函数名称:Delay_1ms
功能:延时约1ms的时间
参数:无
返回值:无
********************************************/
void delay(unsigned char ms)
{
unsigned char i,j;
for(i=ms;i>0;i–)
for(j=120;j>0;j–);
}
void delay_ms(uint aa)
{
uint ii;
for(ii=0;ii__delay_cycles(8000);
}
void delay_us(uint aa)
{
uint ii;
for(ii=0;ii__delay_cycles(8);
}
/*键盘扫描*/
uchar keyscan(void)
{
P5OUT=0xef;
if((P5IN&0x0f)!=0x0f)
{
delay_ms(10);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=1;
if((P5IN&0x02)==0)
keyvalue=2;
if((P5IN&0x04)==0)
keyvalue=3;
if((P5IN&0x08)==0)
keyvalue=4;
while((P5IN&0x0f)!=0x0f);
}
}
P5OUT=0xdf;
if((P5IN&0x0f)!=0x0f)
{
delay_ms(10);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=5;
if((P5IN&0x02)==0)
keyvalue=6;
if((P5IN&0x04)==0)
keyvalue=7;
if((P5IN&0x08)==0)
keyvalue=8;
while((P5IN&0x0f)!=0x0f);
}
}
P5OUT=0xbf;
if((P5IN&0x0f)!=0x0f)
{
delay_ms(10);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=9;
if((P5IN&0x02)==0)
keyvalue=0;
if((P5IN&0x04)==0)
keyvalue=10;
if((P5IN&0x08)==0)
keyvalue=11;
while((P5IN&0x0f)!=0x0f);
}
}
P5OUT=0x7f;
if((P5IN&0x0f)!=0x0f)
{
delay_ms(10);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=12;
if((P5IN&0x02)==0)
keyvalue=13;
if((P5IN&0x04)==0)
keyvalue=14;
if((P5IN&0x08)==0)
keyvalue=15;
while((P5IN&0x0f)!=0x0f);
}
}
return keyvalue;
}
void sendbyte(uchar zdata) //数据传送函数
{
uint i;
for(i=0;i<8;i )
{
if((zdata<{
SID_1;
}
else
{
SID_0;
}
delay(1);
SCLK_0;
delay(1);
SCLK_1;
delay(1);
}
}
/***********************************************************
*名称:LCD_Write_cmd()
*功能:写一个命令到LCD12864
*入口参数:cmd为待写入的命令,无符号字节形式
*出口参数:无
*说明:写入命令时,RW=0,RS=0 扩展成24位串行发送
*格式:11111 RW0 RS 0 xxxx0000 xxxx0000
*|的字节 |命令的bit7~4|命令的bit3~0|
***********************************************************/
void write_cmd(uchar cmd)
{
CS_1;
sendbyte(0xf8);
sendbyte(cmd&0xf0);
sendbyte((cmd<<4)&0xf0);
}
/***********************************************************
*名称:LCD_Write_Byte()
*功能:向LCD12864写入一个字节数据
*入口参数:byte为待写入的字符,无符号形式
*出口参数:无
*范例:LCD_Write_Byte(‘F’) //写入字符’F’
***********************************************************/
void write_dat(uchar dat)
{
CS_1;
sendbyte(0xfa);
sendbyte(dat&0xf0);
sendbyte((dat<<4)&0xf0);
}
/***********************************************************
*名称:LCD_pos()
*功能:设置液晶的显示位置
*入口参数:x为第几行,1~4对应第1行~第4行
*y为第几列,0~15对应第1列~第16列
*出口参数:无
*范例:LCD_pos(2,3) //第2行,第4列
***********************************************************/
void lcd_pos(uchar x,uchar y)
{
uchar pos;
if(x==0)
{x=0x80;}
else if(x==1)
{x=0x90;}
else if(x==2)
{x=0x88;}
else if(x==3)
{x=0x98;}
pos=x y;
write_cmd(pos);
}
/****************************************************/
//LCD12864初始化
void LCD_init(void)
{
write_cmd(0x30); //基本指令操作
delay(5);
write_cmd(0x0C); //显示开,关光标
delay(5);
write_cmd(0x01); //清除LCD的显示内容
delay(5);
write_cmd(0x02); //将AC设置为00H,且游标移到原点位置
delay(5);
}
void hzkdis(uchar *S)
{
while (*S>0)
{
write_dat(*S);
S ;
}
}
void display(int num)
{
if(num<=9&&num>=0)
{
dis_flag=1;
table[0]=num%10 ‘0’;
table[1]=’ ‘;
for(k=0;k<2;k )
{
write_dat(table[k]);
delay(5);
}
}
else if(num<=99&num>9)
{
dis_flag=2;
table[0]=num/10 ‘0’;
table[1]=num%10 ‘0’;
table[2]=’ ‘;
for(k=0;k<3;k )
{
write_dat(table[k]);
delay(5);
}
}
else if(num<=999&num>99)
{
dis_flag=3;
table[0]=num/100 ‘0’;
table[1]=num/10%10 ‘0’;
table[2]=num%10 ‘0’;
table[3]=’ ‘;
for(k=0;k<4;k )
{
write_dat(table[k]);
delay(5);
}
}
else if(num<=9999&num>999)
{
dis_flag=4;
table[0]=num/1000 ‘0’;
table[1]=num/100%10 ‘0’;
table[2]=num/10%10 ‘0’;
table[3]=num%10 ‘0’;
table[4]=’ ‘;
for(k=0;k<4;k )
{
write_dat(table[k]);
delay(5);
}
}
}
void main()
{
uchar flag,mode ;
uint d,shu5=0,shu,shu1;
WDTCTL = WDTPW WDTHOLD;
int_clk();
P1DIR = 0xff;
P4OUT=0x0f;
P4DIR = BIT0 BIT1 BIT2;
LCD_init();
P5DIR=0xf0;
v=3;
lcd_pos(0,0);
hzkdis(tab0);
lcd_pos(1,0);
hzkdis(tab1);
while(1)
{
keyvalue=17;
keyscan();
key_shu=keyvalue;
if(key_shu<10)
{
shu5=shu5*10 key_shu;
shu=shu5;
}
if(shu>0)
{
mode=1;
}
else
{
mode=0;
flag=0;
}
if(key_shu==10)
{
flag=1;
shu1=0;
}
if(key_shu==11) {v=1; lcd_pos(2,6);hzkdis(tab2);}
if(key_shu==12) {v=4; lcd_pos(2,6); hzkdis(tab3);}
if(key_shu==13) {d=0; lcd_pos(2,0); hzkdis(tab4);}
if(key_shu==14){d=1; lcd_pos(2,0);hzkdis(tab5);}
if(key_shu==15)
{
shu=0;
P1OUT|=0x60;
}
if(mode==1&&flag==1)
{
if(d==0)
{
P1OUT=0x1c;
delay_ms(v);
P1OUT=0x0c;
delay_ms(v);
P1OUT=0x2c;
delay_ms(v);
P1OUT=0x24;
delay_ms(v);
P1OUT=0x34;
delay_ms(v);
P1OUT=0x30;
delay_ms(v);
P1OUT=0x38;
delay_ms(v);
P1OUT=0x18;
delay_ms(v);
a ;
}
else
{
P1OUT=0x18;
delay_ms(v);
P1OUT=0x38;
delay_ms(v);
P1OUT=0x30;
delay_ms(v);
P1OUT=0x34;
delay_ms(v);
P1OUT=0x24;
delay_ms(v);
P1OUT=0x2c;
delay_ms(v);
P1OUT=0x0c;
delay_ms(v);
P1OUT=0x1c;
delay_ms(v);
a ;
}
}
if(a==512)
{
P1OUT=0x00;
a=0;
shu–;
}
lcd_pos(1,5);
display(shu);
}
}部分程序说明: 首先看步进电机通电方式。首先将P1口设置为输出(P1DIR = 0xff)。语句P1OUT=0x1c表明除P1.5输出低电平外,P1.4~P1.2输出均是高电平,对应ULN2003AN的OUT4就输出高电平,步进电机A相绕组通电。delay1(v)作用是调速,根据v的不同,步进电机的转速就发生变化。语句P1OUT=0x0c表明除P1.5、P1.4输出低电平外,P1.3~P1.2输出均是高电平。对应ULN2003AN的OUT4、OUT3就输出高电平,步进电机A相、B相绕组通电。延时后P1OUT=0x2c表明除P1.4输出低电平外,P1.5、P1.3、P1.2输出均是高电平。对应ULN2003AN的OUT3就输出高电平,步进电机B相绕组通电。依此类推,可以看出步进电机采用八拍(AABBBCCCDDDAA…)供电方式。if(key_shu==11)和if(key_shu==12)的区别是延时函数的参数(v)不同,造成了步进电动机转速改变,并在液晶上显示。由于步进电机28BYJ48内部机械结构,通电循环了512次电机才旋转一圈,所以if(a==512),把液晶显示圈数减1。
图310实物展示图
if(key_shu==13) 和 if(key_shu==14){…}的作用是正反转切换并在液晶上显示。if(key_shu==15)的作用是急停,当按下对应的按钮后,步进电机停止,同时液晶显示的圈数为0。本次设计实物展示如图310所示。设定好圈数后,按正(反)转和高(低)速按钮后,再按确认键,步进电机按照设定的控制方式运行。
3.5温度检测系统设计DS18B20数字温度计提供9~12位(二进制)温度读数,以指示器件温度。数据经过单线接口送入DS18B20或从DS18B20送出,因此从主机CPU到DS18B20仅需一条线。每一个DS18B20在出厂时,就设定了的64位长的序号,因此可以将多个DS18B20连接在单线总线上,这就可以在许多不同的地方放置DS18B20,并使用一条总线连接在一起。DS18B20的测量范围为-55℃~ 125℃,小分辨率为0.0625℃。DS18B20采用与常用的小功率三极管相同的TO92封装方式。DS18B20内部地址分配如表31所示的9字节RAM。其中字节0和字节1存放DS18B20的温度测量值,字节4存放配置字节,用于设定温度测量的分辨率等参数,字节8是DS18B20自己生成的循环余校验码,在CPU读取DS18B20数据时,用于检查读取数据的正确性。
表31DS18B20内部RAM分配
字节0温度值低字节(TL)字节5保留字节1温度值高字节(TH)字节6保留字节2TL或用户字节1字节7保留字节3TH或用户字节2字节8CRC校验字节字节4配置字节
主CPU经DQ向DS18B20发送温度测量等变换命令,DS18B20将测量的温度值存放在DS18B20的RAM的字节0和字节1中,除温度变换命令外,再介绍几个命令,见表32。
表32DS18B20的部分命令
指令代码(十六进制)指令代码(十六进制)
Skip ROM(跳过ROM)CChRead Scratchpad(读RAM)BEhConvert Temperature(温度变换)44hWrite Scratchpad(写RAM)4Eh
命令CCh,跳过ROM。该命令跳过ROM中的64位长的序号,即不关心每一个DS18B20中序号,因此该命令只能在一总线上仅接有一个DS18B20时应用。在仅使用单个DS18B20时,使用该命令可以简化程序。命令44h,温度变换。DS18B20接收到该命令后将触发温度测量,收到命令数百毫秒后,温度才能测量完毕,将测量的值存入RAM的字节0和字节1中。命令BEh,读RAM存储器。该命令读取DS18B20内部RAM中的数据。读取数据中的头两个字节就是测量的温度值。DS18B20收到BEh命令后,将内部RAM中的数据释放到一总线DQ上。设定DS18B20使用默认的12位转换,DS18B20内部RAM中温度值存放在字节0(TL)和字节1(TH)中,TL和TH的格式如下:
TLbit7bit6
bit5
bit4
bit3
bit2
bit1
bit0
232221202-12-22-32-4
THbit15bit14bit13bit12bit11bit10bit9bit8SSSSS262524
存储器TH中的bit15~bit11为符号位,如果温度为负数,则bit15~bit11全为1,否则全为0。存储器TH的bit10~bit8及存储器TL共11位存储温度值。TL的bit3~bit0存储温度的小数部分TLLSB的“1”的表示0.0625℃,如果温度是负数,将存储器中的二进制求反加1,再分别将整数部分和小数部分转换成十进制数合并后就得到被测温度值。比如,当DS18B20的数据为0000 0000 1010 0010,即TH=0000 0000、TL=1010 0010,根据TL和TH的格式计算温度值为26×0 25×0 24×0 23×1 22×0 21×1 20×0 2-1×0 2-2×0 2-3×1 2-4×0=10.125℃当DS18B20的数据为1111 1111 0101 1110,即TH=1111 1111、TL=0101 1110,表明所测温度为负值,取反得加1得0000 0000 1010 0010,根据TL和TH的格式计算温度值为26×0 25×0 24×0 23×1 22×0 21×1 20×0 2-1×0 2-2×0 2-3×1 2-4×0=-10.125℃DS18B20中测试数据与温度值对应关系如表33所示。
表33DS18B20中测量数据与温度值对应关系的例子
温度二进制输出十六进制输出
125℃0000 0111 1101 000007D0h 85℃0000 0101 0101 00000550h 25.0626℃000 0001 1001 00010191h 10.125℃0000 0000 1010 001000A2h
0.5℃0000 0000 0000 10000008h0℃0000 0000 0000 00000000h-0.5℃1111 1111 1111 1000FFF8h-10.125℃1111 1111 0101 111FF5Eh-25.0625℃1111 111 0110 1111FE6Fh-55℃1111 1100 1001 0000FC90h
由于单片机与DS18B20通过一总线进行数据交换,无论读和写均是从位开始,数据线DQ是双向的,既承担单片机DS18B20传输命令,也是DS18B20向单片机回送温度等数据的通道,因此时序关系十分重要,有3个关键时序需要掌握。
1. DS18B20的初始化DS18B20的初始化是由单片机控制的,是DS18B20一切命令的初始条件,DS18B20的初始化时序如图311所示,主机发送一个复位脉冲接着释放总线并进入接收状态,DS18B20会在检测到上升沿后等待,然后发送一个低电平的存在脉冲告知主机,主机在60~240μs的期间接收到低电平,即表示DS18B20存在,并已初始化成功。
图311DS18B20初始化时序图
图311看起来比较晦涩,下面把这图标注一下,如图312所示。
图312DS18B20初始化时序图标注
用程序描述如下。假设DS18B20的DQ引脚接单片机P6.5端口。
void DS18B20_RESET(void)//复位
{
SDAOUT;
DS18B20_DQ_L;
delay_us(550); //至少480μs的低电平信号
DS18B20_DQ_H; //拉高等待接收18B20的存在脉冲信号
delay_us(60);
SDAIN;
delay_us(1);
DS18B20_Presence=SDADATA; //检测DQ电压,如果DQ是低电平,就表明DS18B20存在或正常,如果//DQ是高电平,就表明没有DS18B20或损坏
delay_us(200);
}
2. DS18B20的写时序如图313所示,整个写时间隙需要持续至少,连续写2位数据的间隙小,主机将总线由高电平拉至低电平后就触发了一个写时间间隙,主机必须在15μs内将所写的位送到总线上。DS18B20在15~60μs间开始对总线进行采样,如果此时总线为低电平,写入的位是0; 若为高电平,写入的位是1。
同样也把这图标注一下,如图314所示。
用程序描述如下:
void Write_DS18B20_OneChar(uchar dat) //写一个字节
{
uchar i=0;
for(i=8; i>0; i–)
{
SDAOUT;
DS18B20_DQ_L;
delay_us(6);
if((dat&0x01)>0) DS18B20_DQ_H;
Else DS18B20_DQ_L;
delay_us(50);
DS18B20_DQ_H;
dat>>=1;
delay_us(10);
}
}
图313DS18B20的写时序
图314DS18B20的写时序标注
现在假如我们希望向DS18B20写数dat为1101 0001(D7~D0),注意在写的过程中是低位在前,高位在后,i=0,一开始DS18B20_DQ_L(拉低总线),因为dat位(D0)是高电平1,满足if((dat&0x01)>0)条件。DS18B20采样是高电平1,就知道写的是“1”。延时后,又把总线拉高(DS18B20_DQ_H),表明1写入。dat >>= 1的作用是右移,把dat的数据变为0110 1000。i=1,DS18B20_DQ_L,不满足if((dat&0x01)>0)条件。DS18B20采样是低电平0,就知道写的是“0”。延时后,又把总线拉高(DS18B20_DQ_H),表明“0”写入。这样重复8次,就把1101 0001写入DS18B20。3. DS18B20的读时序如图315所示主机将总线由高电平拉至低电平并保持1后释放总线就产生了一个读时间隙。读时间隙产生后DS18B20会将1或0传至总线,若传送0则拉低总线,若传送1则保持总线为高电平。在读时间隙产生后的15μs内为主机采集数据时间。
图315DS18B20的读时序
把图315标注一下,如图316所示。
图316DS18B20的读时序标注
图316就比较容易理解DS12B“读”的时序图,一开始由主机把DQ拉高,然后把DQ拉低,再释放总线(DQ=1),再检测DQ的电压,如果DQ电压为0,就表明写的是“0”,如果DQ电压为高电平,就表明读的是“1”,再把总线拉高,表明一位数据读完。用程序描述如下:
uchar Read_DS18B20_OneChar(void) //读一个字节
{
uchar date=0;
uchar j=0;
for(j=8; j>0; j–)
{
DS18B20_DQ_L;
date>>=1;
delay_us(2);
DS18B20_DQ_H;
delay_us(5);
SDAIN;
delay_us(1);
if((SDADATA)>0)
date|=0x80;
delay_us(30);
SDAOUT;
DS18B20_DQ_H;
delay_us(5);
}
return date;
}
现在假如我们读DS18B20的数为1101 0101(D7~D0),注意“读”还是低字节在前,高字节在后,j=0时,一开始DS18B20_DQ_L(拉低总线),初始date为0000 0000,然后date右移一位,仍为0000 0000,DS18B20_DQ_H就是释放总线,被读的数位是1(因为DQ输出了高电平)。if((SDADATA)>0)的含义是如果DQ不为0,主机采样就成立,date |= 0X80后,date数据就是1000 0000,延时delay_us(30),再把总线输出并拉高SDAOUT,DS18B20_DQ_H,位采样就完成。j=1, DS18B20_DQ_L(拉低总线),右移位后,date数据就变为0100 0000,释放总线(DS18B20_DQ_H),因为被读的数第二位是0(表明DQ输出是低电平),主机采样是低电平,不满足if((SDADATA)>0的条件,date数据保持不变,仍为0100 0000。再把总线输出并拉高SDAOUT,DS18B20_DQ_H,第二位采样就完成。j=2,DS18B20_DQ_L; (拉低总线),右移位后,date数据就变为0010 0000,释放总线(DS18B20_DQ_H),因为被读的数第三位是1(因为DQ输出了高电平),主机采样是高电平,满足if((SDADATA)>0)的条件,date |= 0X80后,date就变为1010 0000。再把总线输出并拉高SDAOUT,DS18B20_DQ_H,第三位采样就完成……这样重复8次后,读DS18B20的数就为1101 0101。由于单片机仅接一个DS18B20,所以可以省掉读取序列号及匹配等过程,即直接使用命令[CCh]跳过ROM。硬件电路图如图317所示。
图317基于单片机的温度检测控制系统设计电路图
从电路图可以看出,DS18B20的DQ端接单片机P6.5口,液晶数据端口接P2口,RS接单片机P1.3口,RW接地,E接单片机P1.5口。DS18B20的DQ端与电源端之间的电阻4.7kΩ即为上拉电阻。特别需要注意的是DS18B20电源接3.3V,而不能接5V。这是因为DS18B20工作电压在2.0~5.5V之间,而MSP430单片机端口电压是3.3V,如果DS18B20接5V工作电源,就不能读出数据。整个程序清单如下:
#include
#define CPU_F ((double)8000000)
#define delay_us(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0))
#define delay_ms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))
#define uchar unsigned char
#define uchar unsigned char
#define uint unsigned int
#define set_rs P1OUT|=BIT3
#define clr_rs P1OUT&=~BIT3
#define set_lcden P1OUT|=BIT5
#define clr_lcden P1OUT&=~BIT5
#define dataout P2DIR=0XFF
#define dataport P2OUT
#define DS18B20_DQ_L P6OUT&=~BIT5
#define DS18B20_DQ_H P6OUT|=BIT5
#define SDAOUT P6DIR|=BIT5
#define SDAIN P6DIR&=~BIT5
#define SDADATA (P6IN&BIT5)
uchar Tem_dispbuf[5]={0,0,0,0,0}; //显示数据暂存
uchar Tem_dispbuf1[5]={0,0,0,0,0}; //显示数据暂存
uchar DS18B20_Temp_data[4]={0x00,0x00,0x00,0x00}; //储存温度值的数组
uchar DS18B20_Temp_data1[4]={0x00,0x00,0x00,0x00}; //储存温度值的数组
uchar DS18B20_TEM_Deccode[16]={0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x04, //温度小数位查表数组
0x05,0x06,0x06,0x07,0x08,0x08,0x09,0x09};
uchar DS18B20_Presence,state; //18b20复位成功标示位,=0 成功,=1 失败
uchar tab[]={“TEMP: “};
void int_clk()
{
unsigned char i;
BCSCTL1&=~XT2OFF; //打开XT振荡器
BCSCTL2|=SELM1 SELS; //MCLK为8MHz,SMCLK为1MHz
do
{
IFG1&=~OFIFG; //清除振荡器错误标志
for(i=0; i<100; i )
_NOP(); //延时等待
}
while((IFG1&OFIFG)!=0); //如果标志为1,则继续循环等待
IFG1&=~OFIFG;
}
void delay5ms(void)
{
unsigned int i=40000;
while (i != 0)
{
i–;
}
}
void DS18B20_RESET(void) //复位
{
SDAOUT;
DS18B20_DQ_L;
delay_us(550); //至少480μs的低电平信号
DS18B20_DQ_H; //拉高等待接收18B20的存在脉冲信号
delay_us(60);
SDAIN;
delay_us(1);
DS18B20_Presence=SDADATA;
delay_us(200);
}
void Write_DS18B20_OneChar(uchar dat) //写一个字节
{
uchar i=0;
for(i=8; i>0; i–)
{
SDAOUT;
DS18B20_DQ_L;
delay_us(6);
if((dat&0x01)>0)
DS18B20_DQ_H;
else
DS18B20_DQ_L;
delay_us(50);
DS18B20_DQ_H;
dat>>=1;
delay_us(10);
}
}
uchar Read_DS18B20_OneChar(void) //读一个字节
{
uchar date=0;
uchar j=0;
for(j=8; j>0; j–)
{
DS18B20_DQ_L;
date>>=1;
delay_us(2);
DS18B20_DQ_H;
delay_us(5);
SDAIN;
delay_us(1);
if((SDADATA)>0)
date|=0x80;
delay_us(30);
SDAOUT;
DS18B20_DQ_H;
delay_us(5);
}
return date;
}
/***********读DS18B20温度**************/
void Read_18B20_Temperature1()
{
DS18B20_RESET(); //复位18B20
if(DS18B20_Presence==0) //复位成功
{
Write_DS18B20_OneChar(0XCC); //跳过读序列号
Write_DS18B20_OneChar(0X44); //启动温度转换
//delay_us(5); //等待温度转换时间500μs左右
delay_us(620);
DS18B20_RESET(); //复位18B20
Write_DS18B20_OneChar(0xCC); //发送匹配ROM指令
Write_DS18B20_OneChar(0xBE);
delay_us(620);
DS18B20_Temp_data[0]=Read_DS18B20_OneChar(); //Temperature LSB
DS18B20_Temp_data[1]=Read_DS18B20_OneChar(); //Temperature MSB
Tem_dispbuf[0]=DS18B20_TEM_Deccode[DS18B20_Temp_data[0]&0x0f]; //小数位
Tem_dispbuf[4]=((DS18B20_Temp_data[1]&0x0f)<<4)|((DS18B20_Temp_data[0]&0xf0)>>4); //取出温度值的整数位
Tem_dispbuf[3]=Tem_dispbuf[4]/100;
Tem_dispbuf[2]=Tem_dispbuf[4]%100/10;
Tem_dispbuf[1]=Tem_dispbuf[4]%10;
delay_us(800);
}
delay_us(5);
}
void write_com(uchar com) //1602写命令
{
P1DIR|=BIT3 ;
P1DIR|=BIT5 ;
P2DIR=0xff;
clr_rs;
clr_lcden;
P2OUT=com;
delay5ms();
delay5ms();
set_lcden;
delay5ms();
clr_lcden;
}
void write_date(uchar date) //1602写数据
{
P1DIR|=BIT3 ;
P1DIR|=BIT5 ;
P2DIR=0xff;
set_rs;
clr_lcden;
P2OUT=date;
delay5ms();
delay5ms();
set_lcden;
delay5ms();
clr_lcden;
}
void lcddelay()
{
unsigned int j;
for(j=400; j>0; j–);
}
void init()
{
clr_lcden;
write_com(0x38);
delay5ms();
write_com(0x0c);
delay5ms();
write_com(0x06);
delay5ms();
write_com(0x01);
}
void lcd_pos(unsigned char x,unsigned char y)
{
dataport=0x80 0x40*x y;
P1DIR|=BIT3 ;
P1DIR|=BIT5 ;
P2DIR=0xff;
clr_rs;
clr_lcden;
delay5ms();
delay5ms();
set_lcden;
delay5ms();
clr_lcden;
}
void display(unsigned long int num)
{
uchar k;
uchar dis_flag=0;
uchar table[7];
if(num<=9&num>0)
{
dis_flag=1;
table[0]=num%10 ‘0’;
}
else if(num<=99&num>9)
{
dis_flag=2;
table[0]=num/10 ‘0’;
table[1]=num%10 ‘0’;
}
else if(num<=999&num>99)
{
dis_flag=3;
table[0]=num/100 ‘0’;
table[1]=num/10%10 ‘0’;
table[2]=num%10 ‘0’;
}
else if(num<=9999&num>999)
{
dis_flag=4;
table[0]=num/1000 ‘0’;
table[1]=num/100%10 ‘0’;
table[2]=num/10%10 ‘0’;
table[3]=num%10 ‘0’;
}
for(k=0; k{
write_date(table[k]);
delay5ms();
}
}
void disp(unsigned char *s)
{
while(*s > 0)
{
write_date(*s);
s ;
}
}
void main()
{
WDTCTL = WDTPW WDTHOLD; //关闭看门狗
int_clk();
dataout;
init();
P1DIR|=BIT3 BIT5;
DS18B20_DQ_H;
SDAOUT;
while(1)
{
Read_18B20_Temperature1();
lcd_pos(0,0);
disp(tab);
lcd_pos(0,6);
display(Tem_dispbuf[4]);
write_date(‘.’);
display(Tem_dispbuf[0]);
write_date(0xdf);
write_date(‘C’);
delay_us(500);
}
}
效果图如图318所示。
图318基于单片机的温度检测控制系统设计效果图
3.6温湿度传感器的设计设计要求: 由温湿度传感器(DHT11)检测的环境温度及湿度信号送入单片机,通过12864显示出环境温度和湿度并校验。显示环境温湿度的设计关键是温湿度传感器(DHT11)的应用,DHT11与DS18B20的功能类似,均通过单线制串行接口与单片机进行数据传输,两者对于时序要求也相当严格。DHT11工
图319DHT11引脚图
作原理是先由单片机发送一个复位脉冲,释放总线并进入接收状态,在检测到单片机发出的上升沿后发送一个低电平返回主机,初始化完成。此后,由低功耗模式转变为高速模式,将所测量的40Bit数据送回单片机内部,测量完成。DHT11是具有自校验功能的数字信号输出的温湿度复合信号传感器,信号具有极高的稳定性,传输距离可达将近20m,内部自带“自校验”功能,是在没有温湿度标准下用来判断所测数据是否正确的一个重要的评判标准。其引脚图如图319所示。
DHT11芯片引脚简介: 引脚1VCC: 连接单片机电源,工作电压为3.3~5V,不超过5.5V; 引脚2DATA: 数据传送口,单总线制,串口一共向单片机发送5组8位二进制数据,具体形式为: 温度高8位 温度低8位 湿度高8位 湿度低8位 校验8位,其中低8位数值默认值为0; 引脚3悬空,有的元器件无此引脚; 引脚4GND: 与单片机的电源地相连。
温湿度传感器的相关的时序如图320所示。
图320DHT11芯片时序图
图320为DHT11整体的通讯时序图。首先,在空闲状态下,DATA为高电平,单片机发送开始信号,主机拉低总线至少18ms,然后又拉高总线20~40μs,再等待DHT11响应,过程为DHT11把总线拉低80μs,再拉高总线80μs后就开始传输数据。具体时序如图321所示。
图321时序图
综上所述,DHT11对时序要求比较严格,这是正确接收DHT11数据的关键。数据一共五组,每组数据为8位二进制数。对于每位的数据,都相隔有50μs低电平间隙,高电平的时间长短决定了数据是0还是1。如果高电平的时间为26~28μs,那么就是0,如果高电平的时间约为70μs,那么就是1。采集数据完成后,数据发送返回单片机芯片存储。数字0和数字1信号如图322、图323所示。
图322数字0的表示
图323数字1的表示
上述所提及的关键延时如18ms、80μs以及50μs是编写传感器DHT11的关键,如果不能确定程序的延时是否正确,可以先编写延时函数,通过示波器观测,修改延时函数符合要求后加入程序中。本程序中delay_us(x)和delay_ms(x)是非常精确的,直接调用即可。那么在程序中如何判断接收的数据是1还是0?从图321可以看出,DHT11拉高总线80μs后有50μs的低电平,这段50μs时间可以用语句while(!(D7_R))表示,当进入高电平时,再延时40μs,从图322和图323可以看出,如果此时采样的数值是低电平,那么该位就是0; 如果此时采样的数值为高电平,那么该位就是1。循环8次,就把一个字节的数值读出来。这样重复5次,依次读出温度高8位,温度低8位(都是0),湿度高8位,湿度低8位(都是0)以及校验和。如果温度数值与湿度数值之和等于校验和,就说明温湿度传感器输出数据正确。基于单片机的温湿度传感器的电路图如图324所示。
图324基于单片机的温湿度传感器电路图
#include
#define CPU_F ((double)8000000)
#define delay_us(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0))
#define delay_ms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))
#define uint unsigned int
#define uchar unsigned char
#define ulong unsigned long
#define D7_IN P1DIR&=~BIT7
#define D7_OUT P1DIR|=BIT7 //D7口设置为输出
#define D7_CLR P1OUT&=~BIT7 //输出0
#define D7_SET P1OUT|=BIT7 //输出1
#define D7_R P1IN&(0x80)//读
#define SID BIT1
#define SCLK BIT2
#define CS BIT0
#define LCDPORT P3OUT
#define SID_1 LCDPORT |= SID //SID置高P3.1
#define SID_0 LCDPORT &= ~SID //SID置低
#define SCLK_1 LCDPORT |= SCLK //SCLK置高P3.2
#define SCLK_0 LCDPORT &= ~SCLK //SCLK置低
#define CS_1 LCDPORT |= CS //CS置高P3.0
#define CS_0 LCDPORT &= ~CS //CS置低
uchar dis_flag,k,U8FLAG,U8cont,U8temp,
U8T_data_H,U8T_data_L,U8RH_data_H,U8RH_data_L,t,i,
U8checkdata,U8comdata,U8T_data_H_temp,U8T_data_L_temp,U8RH_data_H_temp,U8RH_data_L_temp,U8checkdata_temp;
#define SCL_H P3OUT|=BIT3
#define SCL_L P3OUT&=~BIT3
#define SDA_H P3OUT|=BIT1
#define SDA_L P3OUT&=~BIT1
#define SCL_read P3IN&BIT3
#define SDA_read P3IN&BIT1
uchar table[7]=” “;
uchar tab0[]={“温湿度检测系统”};
uchar tab1[]={“温度: “};
uchar tab2[]={“湿度”};
uchar tab3[]={“校验和”};
void Clk_Init()
{
unsigned char i;
BCSCTL1&=~XT2OFF; //打开XT2振荡器
BCSCTL2|=DIVS0 DIVS1; //MCLK为8MHz, SMCLK为8MHz
BCSCTL2|=SELM1 SELS;
do
{
IFG1 &= ~OFIFG; //清除振荡错误标志
for(i=0; i<100; i )
_NOP();
}
while ((IFG1 & OFIFG) != 0); //如果标志为1,继续循环等待
IFG1&=~OFIFG;
}
////////////////////////////////////////////////
void RH(void)//检测子程序
{
D7_OUT;
D7_CLR;
delay_ms(18);
D7_SET;
delay_us(30); //拉高20~40μs,取30μs,关键所在
D7_IN;
while(D7_R);
if(!(D7_R))
{
while(!(D7_R)); //低电平80μs
while((D7_R)); //高电平80μs
for(t=0; t<8; t )
{
while(!(D7_R));
U8temp=0;
delay_us(40); //延时40μs
if(D7_R)//判断是数据0还是1
{
U8temp=1;
}
else
{
U8temp=0;
}
U8comdata=U8comdata<<1;
U8comdata|=U8temp;
for(i=0; i<1; i );
while(D7_R);
}
U8T_data_H_temp=U8comdata; //温度高8位
U8comdata=0; //清空
for(t=0; t<8; t )
{
while(!(D7_R));
U8temp=0;
delay_us(40);
if(D7_R)
{
U8temp=1;
}
else
{
U8temp=0;
}
U8comdata=U8comdata<<1;
U8comdata|=U8temp;
for(i=0; i<1; i );
while(D7_R);
}
U8T_data_L_temp=U8comdata;
U8comdata=0;
for(t=0; t<8; t )
{
while(!(D7_R));
U8temp=0;
delay_us(40);
if(D7_R)
{
U8temp=1;
}
else
{
U8temp=0;
}
U8comdata=U8comdata<<1;
U8comdata|=U8temp;
for(i=0; i<1; i );
while(D7_R);
}
U8RH_data_H_temp=U8comdata;
U8comdata=0;
for(t=0; t<8; t )
{
while(!(D7_R));
U8temp=0;
delay_us(40);
if(D7_R)
{
U8temp=1;
}
else
{
U8temp=0;
}
U8comdata=U8comdata<<1;
U8comdata|=U8temp;
while(D7_R);
}
}
U8RH_data_L_temp=U8comdata;
U8comdata=0;
for(t=0; t<8; t )
{
while(!(D7_R));
U8temp=0;
delay_us(40);
if(D7_R)
{
U8temp=1;
}
else
{
U8temp=0;
}
U8comdata=U8comdata<<1;
U8comdata|=U8temp;
while(D7_R);
}
U8checkdata_temp=U8comdata;
D7_OUT;
D7_SET;
U8temp=(U8T_data_H_temp U8T_data_L_temp U8RH_data_H_temp U8RH_data_L_temp);
if(U8temp==U8checkdata_temp)//自校验功能
{
U8RH_data_H=U8RH_data_H_temp;
U8RH_data_L=U8RH_data_L_temp;
U8T_data_H=U8T_data_H_temp;
U8T_data_L=U8T_data_L_temp;
U8checkdata=U8checkdata_temp;
}
}
///////////////////////////////////////////////////
void delay(unsigned char ms)
{
unsigned char i,j;
for(i=ms; i>0; i–)
for(j=120; j>0; j–);
}
void sendbyte(uchar zdata) //数据传送函数
{
uint i;
for(i=0; i<8; i )
{
if((zdata<{
SID_1;
}
else
{
SID_0;
}
delay(1);
SCLK_0;
delay(1);
SCLK_1;
delay(1);
}
}
/***********************************************************
*名称: LCD_Write_cmd()
*功能: 写一个命令到LCD12864
*入口参数: cmd为待写入的命令,无符号字节形式
*出口参数: 无
*说明: 写入命令时,RW=0,RS=0 扩展成24位串行发送
*格式: 11111 RW0 RS 0 xxxx0000 xxxx0000
* |的字节 |命令的bit7~4|命令的bit3~0|
***********************************************************/
void write_cmd(uchar cmd)
{
CS_1;
sendbyte(0xf8);
sendbyte(cmd&0xf0);
sendbyte((cmd<<4)&0xf0);
}
/***********************************************************
*名称: LCD_Write_Byte()
*功能: 向LCD12864写入一个字节数据
*入口参数: byte为待写入的字符,无符号形式
*出口参数: 无
*范例: LCD_Write_Byte(‘F’) //写入字符’F’
***********************************************************/
void write_dat(uchar dat)
{
CS_1;
sendbyte(0xfa);
sendbyte(dat&0xf0);
sendbyte((dat<<4)&0xf0);
}
/***********************************************************
*名称: LCD_pos()
*功能: 设置液晶的显示位置
*入口参数: x为第几行,1~4对应第1行~第4行
* y为第几列,0~15对应第1列~第16列
*出口参数: 无
*范例: LCD_pos(2,3) //第2行,第4列
***********************************************************/
void lcd_pos(uchar x,uchar y)
{
uchar pos;
if(x==0)
{x=0x80; }
else if(x==1)
{x=0x90; }
else if(x==2)
{x=0x88; }
else if(x==3)
{x=0x98; }
pos=x y;
write_cmd(pos);
}
/****************************************************/
//LCD12864初始化
void LCD_init(void)
{
write_cmd(0x30); //基本指令操作
delay(5);
write_cmd(0x0C); //显示开,关光标
delay(5);
write_cmd(0x01); //清除LCD的显示内容
delay(5);
write_cmd(0x02); //将AC设置为00H,且游标移到原点位置
delay(5);
}
void hzkdis(uchar *S)
{
while (*S>0)
{
write_dat(*S);
S ;
}
}
void display(int num)
{
if(num<=9&&num>=0)
{
dis_flag=1;
table[0]=num%10 ‘0’;
for(k=0; k<1; k )
{
write_dat(table[k]);
delay(5);
}
}
else if(num<=99&num>9)
{
dis_flag=2;
table[0]=num/10 ‘0’;
table[1]=num%10 ‘0’;
for(k=0; k<2; k )
{
write_dat(table[k]);
delay(5);
}
}
else if(num<=999& num>99)
{
dis_flag=3;
table[0]=num/100 ‘0’;
table[1]=num/10%10 ‘0’;
table[2]=num%10 ‘0’;
for(k=0; k<3; k )
{
write_dat(table[k]);
delay(5);
}
}
else if(num<=9999& num>999)
{
dis_flag=4;
table[0]=num/1000 ‘0’;
table[1]=num/100%10 ‘0’;
table[2]=num/10%10 ‘0’;
table[3]=num%10 ‘0’;
for(k=0; k<4; k )
{
write_dat(table[k]);
delay(5);
}
}
}
//***************************************************************
int main(void)
{
WDTCTL = WDTPW WDTHOLD;
Clk_Init();
P3OUT=0x0f;
P3DIR = BIT0 BIT1 BIT2;
LCD_init();
delay(40);
D7_OUT;
D7_CLR;
while(1)
{
lcd_pos(0,0);
hzkdis(tab0);
lcd_pos(1,0);
hzkdis(tab1);
RH();
lcd_pos(1,3);
display(U8T_data_H);
lcd_pos(1,5);
hzkdis(tab2);
lcd_pos(1,7);
display(U8RH_data_H);
lcd_pos(2,0);
hzkdis(tab3);
lcd_pos(2,4);
display(U8checkdata);
}
}
实物图如图325所示。
图325设计效果图
可能有读者认为温度检测的数值有误,这是由于本次购买的温湿度传感器质量不稳定造成的,那么如何判定硬件和软件设计正确呢?通过校验和就可以看出,温度和湿度之和等于校验和,就说明系统设计的正确性。
3.7电子秤的设计设计要求; 由压力传感器检测的物品重量信号送入单片机,矩阵式键盘设定价格,按下确认按键后,显示出物品的价格。电子秤的设计,关键是称重传感器专用模拟/数字转换芯片,数据采集电路包括压力传感器和A/D转换模块。其中HX711采用了海芯科技集成电路专利技术,是专为24位A/D转换芯片设计的高精度电子秤芯片,其典型电路如图326所示。与同类型的其他芯片相比,该芯片集成了外围电路,包括电源供应器、片内时钟振荡器和其他类似类型的芯片,具有集成度高、响应速度快、抗干扰等优点,使电子秤制作成本极大降低,其整体性和可靠性大大增加。该芯片与后端MCU芯片的接口和编程非常简单,所有控制信号由引脚驱动,无须对芯片内部的寄存器编程。
图326电子秤应用典型电路
该芯片含有通道A或通道B,并且与其内部可编程低噪声放大器连接。通道A的可编程增益为128或64,相应的差分输入信号的满额度幅值为±20mV或±40mV。通道B的作用是对系统参数进行检测,是一个增益为32的固定通道。在工作板上不需要添加额外电源,因为芯片本身就可以提供稳定的电源。
图327HX711引脚图
片内时钟振荡器不需要任何外部设备。自动上电复位简化了初始化过程启动。
HX711两路可选择差分输入,片内低噪声可编程放大器,可选增益为64和128。片内稳压电路可直接向外部传感器和芯片内A/D转换器提供电源。片内时钟振荡器无须任何外接器件,必要时也可使用外接晶振或时钟,上电自动复位电路,简单的数字控制和串口通信。所有控制由引脚输入,芯片内寄存器无须编程。可选择10Hz或80Hz的输出数据速率。同步抑制50Hz和60Hz的电源干扰。耗电量(含稳压电源电路): 典型工作电流小于1.7mA,断电电流小于1μA,工作电压为2.6~5.5V,工作温度范围-20~85℃,16引脚的SOP16封装。HX711引脚如图327所示。性能如表34所示。
表34HX711引脚与性能
引脚号名称性能描述
1VSUP电源稳压电路供电电源: 2.6~5.5V(不用稳压电源时应接AVDD)2BASE模拟输出稳压电路控制输出(不用稳压电源时无连接)3AVDD电源模拟电源4VFB模拟输入稳压电路控制输入(不用稳压电源时无连接)5AGND地模拟地6VBG模拟输出参考电源输出7INNA模拟输入通道A负输入端8INPA模拟输入通道A正输入端9INNB模拟输入通道B负输入端10INPB模拟输入通道B正输入端11PD_SCK数字输入断电控制(高电平有效)和串口时钟输入12DOUT数字输出串口数据输出13XO数字输入晶振输入(不用晶振时无连接)14XI数字输入外部时钟或晶振输入,0: 使用片内振荡器15RATE数字输入输出数据速率控制,0: 10Hz,1: 80Hz16DVVD电源数字电源: 2.6~5.5V
HX711主要电气参数如表35所示。
表35HX711主要电气参数
参数条件及说明小值 典型值 值单位
满额度差分输入范围Vinp-Vinn±0.5(AVDD/GAIN)V输入共模电压范围
AGND 0.6AVVD-0.6V
输出数据速率
使用片内振荡器,RATE=010使用片内振荡,RATE=DVDD80外部时钟或晶振,RATE=0fclk/1105920外部时钟或晶振,RATE=DVDDfclk/138240Hz
输出数据编码二进制补码8000007FFFFF(HEX)
输出稳定时间RATE=0400RATE=DVDD50ms输入零点漂移
增益=1280.2增益=640.8mV
输入噪声增益=128,RATE=050增益=128,RATE=DVDD90nV(rms)温度系数输入零点漂移(增益=128)±7nV/℃增益漂移(增益=128)±3ppm/℃
续表
参数条件及说明小值 典型值 值单位
输入共模信号抑制比增益=128,RATE=0100dB电源干扰抑制比增益=128,RATE=0100dB输出参考电压(VBG)
1.25
外部时钟或晶振频率
1,11.0592,30MHz电源电压DVDD2.6,5.5AVDD,VSUP2.6,5.5V模拟电源电流(含稳压电路)正常工作1600断电0.3μA数字电源电流正常工作100断电0.2μA
1. 模拟输入通道A模拟差分输入可直接与桥式传感器的差分输出相接。由于桥式传感器输出的信号较小,为了充分利用A/D转换器的输入动态范围,该通道的可编程增益较大,为128或64。这些增益所对应的满量程差分输入电压分别为±20mV或±40mV。通道B为固定的64增益,所对应的满量程差分输入电压为±40mV。通道B应用于包括电池在内的系统参数检测。2. 供电电源数字电源(DVDD)应使用与MCU芯片相同的数字供电电源。HX711芯片内的稳压电路可同时向A/D转换器和外部传感器提供模拟电源。稳压电源的供电电压(VSUP)可与数字电源(DVDD)相同。稳压电源的输出电压值(VAVDD)由外部分压电阻R1、R2和芯片的输出参考电压VBG决定,VAVDD=VBG(R1 R2)/R2。应选择该输出电压比稳压电源的输入电压(VSUP)低少至100mV。如果不使用芯片内的稳压电路,引脚VSUP和引脚AVDD应相连,并接到电压为2.6~ 5.5V的低噪声模拟电源。引脚VBG上不需要外接电容,引脚VFB应接地,引脚BASE为无连接。3. 时钟选择如果将引脚XI接地,HX711将自动选择使用内部时钟振荡器,并自动关闭外部时钟输入和晶振的相关电路。这种情况下,典型输出数据速率为10Hz或80Hz。如果需要准确的输出数据速率,可将外部输入时钟通过一个20pF的隔直电容连接到引脚XI上,或将晶振连接到引脚XI和XO上。这种情况下,芯片内的时钟振荡器电路会自动关闭晶振时钟或外部输入时钟电路被采用。此时,若晶振频率为11.0592MHz,输出数据速率为准确的10Hz或80Hz。输出数据速率与晶振频率跟上述关系按比例增加或减少。使用外部输入时钟时,外部时钟信号不一定需要为方波。可将MCU芯片的晶振输出引脚上的时钟信号通过20pF的隔直电容连接到引脚XI上作为外部时钟输入。外部时钟输入信号的幅值可低至150mv。4. 串口通信串口通信线由引脚PD_SCK和DOUT组成,用来输出数据,选择输入通道和增益。当数据输出引脚DOUT为高电平时,表明A/D转换器还未准备好输出数据,此时串口时钟输入信号PD_SCK应为低电平。当DOUT从高电平变低电平后,PD_SCK应输入25~27个不等的时钟脉冲。其中个时钟脉冲的上升沿将读出输出24位数据的位(MSB),直至第24个时钟脉冲完成,24位输出数据从位至位逐位输出完成。第25~27个时钟脉冲用来选择下一次A/D转换的输入通道和增益,见表36。
表36PD_SCK的脉冲数与增益
PD_SCK脉冲数输入通道增益25A12826B6427A64
值得注意的是,PD_SCK的输入时钟脉冲数不应少于25或多于27,否则会造成串口通信错误。当A/D转换器的输入通道或增益改变时,A/D转换器需要4个数据输出周期才能稳定。DOUT在4个数据输出周期后才会从高电平变为低电平,输出有效数据。HX711时序图见图328。对于图328的时序图用表37来说明。
图328HX711时序图
表37HX711延时时间取值范围
符号说明小值典型值值单位
T1DOUT下降沿到PD_SCK脉冲上升沿0.1
μsT2PD_SCK脉冲上升沿到DOUT数据有效
0.1μsT3PD_SCK正脉冲电平时间0.2
50μsT4PD_SCK负脉冲电平时间0.2
μs
5. 复位和断电当芯片上电时,芯片内的上电自动复位电路会使芯片自动复位。引脚PD_SCK输入用来控制HX711的断电。当PD_SCK为低电平时,芯片处于正常工作状态。如果PD_SCK从低电平变高电平并保持在高电平的时间超过60μs,HX711即进入断电状态,如使用片内稳压电源电路,断电时,外部传感器和片内A/D转换器会被同时断电。当PD_SCK重新回到低电平时,芯片会自动复位后进入正常工作状态。芯片从复位或断电状态进入正常工作状态后,通道A和增益128会被自动选择作为次A/D转换的输入通道和增益。随后的输入通道和增益选择由PD_SCK的脉冲数决定,参见串口通信一节。芯片从复位或断电状态进入正常工作状态后,A/D转换器需要4个数据输出周期才能稳定。DOUT在4个数据输出周期后才会从高电平变成低电平,输出有效数据。网购的称重传感器一般情况都是HX711和外围电路已经合成好的,与压力传感器只要接四根不同颜色线,如图329所示。
而合成后的HX711的输出也是四根线,电源正、电源地以及DT端口、SCL端口。其实物图如图330所示。
图329压力传感器与HX711连接
图330压力传感器及HX711实物图
基于单片机的电子秤的电路图如图331所示。
图331基于单片机的电子秤的电路图
12864采用串行工作方式,在HX711与外围电路合成模块的输出端,连接两个10kΩ的上拉电阻。完整程序清单如下:
#include
#define CPU_F ((double)8000000)
#define delay_us(x) __delay_cycles((long)(CPU_F*(double)x/1000000.0))
#define delay_ms(x) __delay_cycles((long)(CPU_F*(double)x/1000.0))
#define uint unsigned int
#define uchar unsigned char
#define ulong unsigned long
#define HX P4OUT
#define HX711_DOUT0 HX&0Xef
#define HX711_DOUT1 HX|0X10
#define HX711_SCK0 HX&0Xdf
#define HX711_SCK1 HX|0X20
#define SID BIT1
#define SCLK BIT2
#define CS BIT0
#define LCDPORT P3OUT
#define SID_1 LCDPORT |= SID //SID置高P3.1
#define SID_0 LCDPORT &= ~SID //SID置低
#define SCLK_1 LCDPORT |= SCLK //SCLK置高P3.2
#define SCLK_0 LCDPORT &= ~SCLK //SCLK置低
#define CS_1 LCDPORT |= CS //CS置高P3.0
#define CS_0 LCDPORT &= ~CS //CS置低
int shu8;
uchar keyvalue,lcd_bus;
uchar table[ ];
uchar dis2[ ]={“重量:”};
uchar dis3[ ]={“单价:”};
uchar dis4[ ]={“总价:”};
uchar dis5[ ]={“元”};
uchar dis6[ ]={“克”};
int num=0;
int i,j;
void int_clk( )
{
unsigned char i;
BCSCTL1&=~XT2OFF; //打开XT振荡器
BCSCTL2|=SELM1 SELS; // MCLK为8MHz,SMCLK为1MHz
do
{
IFG1&=~OFIFG; //清除振荡器错误标志
for(i=0;i<100;i )
_NOP( ); //延时等待
}
while((IFG1&OFIFG)!=0); //如果标志为1,则继续循环等待
IFG1&=~OFIFG;
}
void delay(unsigned char ms)
{
unsigned char i,j;
for(i=ms;i>0;i–)
for(j=120;j>0;j–);
}
void sendbyte(uchar zdata) //数据传送函数
{
for(i=0;i<8;i )
{
if((zdata<{
SID_1;
}
else
{
SID_0;
}
delay(1);
SCLK_0;
delay(1);
SCLK_1;
delay(1);
}
}
/***********************************************************
*名称:LCD_Write_cmd( )
*功能:写一个命令到LCD12864
*入口参数:cmd为待写入的命令,无符号字节形式
*出口参数:无
*说明:写入命令时,RW=0,RS=0扩展成24位串行发送
*格式:11111 RW0 RS 0 xxxx0000 xxxx0000
*|的字节 |命令的bit7~4|命令的bit3~0|
***********************************************************/
void write_cmd(uchar cmd)
{
CS_1;
sendbyte(0xf8);
sendbyte(cmd&0xf0);
sendbyte((cmd<<4)&0xf0);
}
/***********************************************************
*名称:LCD_Write_Byte()
*功能:向LCD12864写入一个字节数据
*入口参数:byte为待写入的字符,无符号形式
*出口参数:无
*范例:LCD_Write_Byte(‘F’) //写入字符’F’
***********************************************************/
void write_dat(uchar dat)
{
CS_1;
sendbyte(0xfa);
sendbyte(dat&0xf0);
sendbyte((dat<<4)&0xf0);
}
/***********************************************************
*名称:LCD_pos( )
*功能:设置液晶的显示位置
*入口参数:x为第几行,1~4对应第1行~第4行
*y为第几列,0~15对应第1列~第16列
*出口参数:无
*范例:LCD_pos(2,3) //第2行,第4列
***********************************************************/
void lcd_pos(uchar x,uchar y)
{
uchar pos;
if(x==0)
{x=0x80;}
else if(x==1)
{x=0x90;}
else if(x==2)
{x=0x88;}
else if(x==3)
{x=0x98;}
pos=x y;
write_cmd(pos);
}
/****************************************************/
//LCD12864初始化
void LCD_init(void)
{
write_cmd(0x30); //基本指令操作
delay(5);
write_cmd(0x0C); //显示开,关光标
delay(5);
write_cmd(0x01); //清除LCD的显示内容
delay(5);
write_cmd(0x02); //将AC设置为00H,且游标移到原点位置
delay(5);
}
void hzkdis(unsigned char *S)
{
while (*S>0)
{
write_dat(*S);
S ;
}
}
void display(int num)
{
uchar k;
uchar dis_flag=0;
uchar table[7];
if(num<=9&num>=0)
{
dis_flag=1;
table[0]=num%10 ‘0’;
}
else if(num<=99&num>9)
{
dis_flag=2;
table[0]=num/10 ‘0’;
table[1]=num%10 ‘0’;
}
else if(num<=999&num>99)
{
dis_flag=3;
table[0]=num/100 ‘0’;
table[1]=num/10%10 ‘0’;
table[2]=num%10 ‘0’;
}
else if(num<=9999&num>999)
{
dis_flag=4;
table[0]=num/1000 ‘0’;
table[1]=num/100%10 ‘0’;
table[2]=num/10%10 ‘0’;
table[3]=num%10 ‘0’;
}
for(k=0;k{
write_dat(table[k]);
delay_ms(5);
}
}
uchar keyscan(void)
{
P5OUT=0xef;
if((P5IN&0x0f)!=0x0f)
{
delay_us(100);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=1;
if((P5IN&0x02)==0)
keyvalue=2;
if((P5IN&0x04)==0)
keyvalue=3;
if((P5IN&0x08)==0)
keyvalue=4;
while((P5IN&0x0f)!=0x0f);
}
}
P5OUT=0xdf;
if((P5IN&0x0f)!=0x0f)
{
delay_us(100);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=5;
if((P5IN&0x02)==0)
keyvalue=6;
if((P5IN&0x04)==0)
keyvalue=7;
if((P5IN&0x08)==0)
keyvalue=8;
while((P5IN&0x0f)!=0x0f);
}
}
P5OUT=0xbf;
if((P5IN&0x0f)!=0x0f)
{
delay_us(100);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=9;
if((P5IN&0x02)==0)
keyvalue=0;
if((P5IN&0x04)==0)
keyvalue=10;
if((P5IN&0x08)==0)
keyvalue=11;
while((P5IN&0x0f)!=0x0f);
}
}
P5OUT=0x7f;
if((P5IN&0x0f)!=0x0f)
{
delay_us(100);
if((P5IN&0x0f)!=0x0f)
{
if((P5IN&0x01)==0)
keyvalue=12;
if((P5IN&0x02)==0)
keyvalue=13;
if((P5IN&0x04)==0)
keyvalue=14;
if((P5IN&0x08)==0)
keyvalue=15;
while((P5IN&0x0f)!=0x0f);
}
}
return keyvalue;
}
int HX711_Read(void) //增益128
{
unsigned long count=0;
count=count&0x0000000; //第25个脉冲下降沿来时,转换数据
P4DIR|=0xef;
HX=HX711_DOUT1; //数据口
delay_us(4);
HX=HX711_SCK0; //控制口
while((P4IN&0X10)==0X10);
delay_us(40);
for(j=0;j<24;j )
{
HX=HX711_SCK1;
count=count<<1;
HX=HX711_SCK0;
if((P4IN&0X10)==0X10)
count ;
}
HX=HX711_SCK1;
count=count&0x007FFFFF; //第25个脉冲下降沿来时,转换数据
delay_us(840);
HX=HX711_SCK0;
delay_us(4);
return(count/405); //校正
}
void main( )
{
int shu2=0,key2,shu9=0;
shu8=0;
WDTCTL = WDTPW WDTHOLD;
int_clk( );
P3OUT=0x0f;
P3DIR = BIT0 BIT1 BIT2;
LCD_init( );
delay(40);
P5DIR=0xf0;
while(1)
{
shu8=HX711_Read();
shu8=shu8-378; //去皮
keyvalue=17;
keyscan( );
key2=keyvalue;
if(key2<=9)
{
shu2=shu2*10 key2;
}
if(key2==11)
{
shu9=shu8*shu2;
}
write_cmd(0x80);
for(num=0;num<5;num )
{
write_dat(dis2[num]);
delay(5);
}
write_cmd(0x83);
display(shu8);
write_cmd(0x87);
for(num=0;num<2;num )
{
write_dat(dis6[num]);
delay(5);
}
write_cmd(0x90);
for(num=0;num<6;num )
{
write_dat(dis3[num]);
delay(5);
}
write_cmd(0x93);
display(shu2);
write_cmd(0x97);
for(num=0;num<2;num )
{
write_dat(dis5[num]);
delay(5);
}
write_cmd(0x88);
for(num=0;num<6;num )
{
write_dat(dis4[num]);
delay(5);
}
write_cmd(0x88 0x03);
display(shu9);
write_cmd(0x8f);
for(num=0;num<2;num )
{
write_dat(dis5[num]);
delay(5);
}
}
}
效果图如图332所示,图(a)是电子秤未放物品的图片; 图(b)是设定物品单价为3,电子秤放50g物品后的图片。
图332基于单片机的电子秤设计效果图
评论
还没有评论。