前述两节主要讨论GPIO端口的一般使用方法,本节讨论GPIO的另一个常用用途:总线模拟。
嵌入式系统应用场合是多种多样的,需要的设备也是多种多样,而微控制器本身提供的设备一定是有限的,而设备本身和MCU之间的通讯方式也是各有不同,在这种情况下我们就可以考虑用GPIO模拟设备与MCU之间的接口。
目前比较典型的有采用74HC594实现串转并功能、DS18B20实现单总线功能、SPI总线功能等
1.采用74HC595实现串转并功能
在第一节中我们利用8个管脚实现了流水灯功能,但是我们也可以看到每一个LED占据一条口线,而系统的口线资源是非常宝贵的,74HC595是带输出锁存的8位移位寄存器,其内部结构如下:
其控制端口:
1)SH_CP(11脚):移位时钟脉冲输入端。在上升沿时移位寄存器将数据移位
2)DS(14脚):串行数据输入端。通过移位运算将每次移位的数据值传递给DS引脚,8次移位后完成一个字符的串行传送。
3)ST_CP(12脚):锁存脉冲控制端,在上升沿时移位寄存器的数据被传入存储寄存器,这时如果OE端为低电平,传入存储器的数据会直接输出到输出端Q0-Q7。在一个字节的移位操作完成后,通过在ST_CP端产生一个上升沿将数据送出。
4)/MR(10脚):低电平时将移位寄存器数据请0.一般情况下接VCC
5)/OE(13脚):高电平时输出端禁止输出(高阻态)。低电平时允许数据输出
(吐槽一下:74HC595的datasheet和proteus中的管脚名称不知为什么不一致,头疼)
#include"reg51.h"
#include"intrins.h"
sbit SH_CP=P2^0;//输入时钟信号口
sbit DS=P2^1; //数据输入口
sbit ST_CP=P2^2;//数据输出锁存口
void sendOneByte(unsigned char msg){
unsigned char i;
ST_CP=0;//为上升沿信号,将1byte数据锁存输出
for(i=0;i<8;i++){//将一个字节的8位从高到低一位一位的输出
SH_CP=0;//先是低电平
DS=msg>>7;
msg<<=1;
SH_CP=1;//然后是高电平,产生上升沿信号,将1bit数据输出
_nop_();//延迟一段时间
_nop_();
}
ST_CP=1;//上升沿信号,将1byte数据锁存输出
_nop_();//延迟一段时间
_nop_();
}
#include"74hc595.h"
unsigned char code pattern[]={1<<0,1<<1,1<<2,1<<3,1<<4,1<<5,1<<6,1<<7};
extern void sendOneByte(unsigned char msg);
void delay(int t){
while(--t);
}
void main(){
unsigned char i=0;
while(1){
for(i=0;i<8;i++){
sendOneByte(pattern[i]);
delay(5000);
sendOneByte(0xFF);
delay(5000);
}
}
}
2.DS18B20
DS18B20是所谓的单总线数字式温度传感器,一般MCU并不支持单总线,这时候就需要考虑用GPIO口线模拟单总线。DS18B20单总线原理在此不必详述了,关于它的资料网上有很多(链接:https://pan.baidu.com/s/11iNJ3YNRfl-a7qyqDC8szQ 提取码:gfqr )。模拟单总线的关键难点在于时间及时序的准确控制。
DS18B20的驱动如下:
/*
注意:晶振为12MHZ
*/
#include"reg51.h"
#include<intrins.h>
sbit DQ=P2^0;
extern void delay_ms(int num);
void Init_DS18B20(void){//初始化ds18b20
DQ = 1;//DQ复位
delay_ms(8);//稍做延时
DQ = 0;//单片机将DQ拉低
delay_ms(80);//精确延时大于480us
DQ = 1;//拉高总线
delay_ms(14);
delay_ms(20);
}
unsigned char ReadOneChar(void){//读一个字节,从低位到高位
unsigned char i=0;
unsigned char dat = 0;
for (i=8;i>0;i--){
DQ = 0; // 将总线拉低,给脉冲信号
dat>>=1;//做一个小小的延迟,并为下一位数据接收做准备
DQ = 1; // 释放总线,给脉冲信号,上拉电阻会将总线拉低,或DS18B20将总线拉低
if(DQ)//总线是否被dS18B20拉低,拉低则为0,否则为高
dat|=0x80;
delay_ms(4);
}
return(dat);
}
void WriteOneChar(unsigned char dat){//写一个字节,从低位到高位一位一位输出
unsigned char i=0;
for (i=8; i>0; i--){
DQ = 0;
DQ = dat&0x01;
delay_ms(5);
DQ = 1;
dat>>=1;
}
}
//返回值是温度值的补码,有效位数为12位
//最高位为1时表示为负值
unsigned int ReadTemperature(void){//读取温度
unsigned char tml=0;//温度值的低字节
unsigned char tmh=0;//温度值的高字节
unsigned int tvalue;//温度值补码
Init_DS18B20();
WriteOneChar(0xCC); //跳过读序号列号的操作
WriteOneChar(0x44);//转换温度,这时温度值才能保存在存储区
Init_DS18B20();
WriteOneChar(0xCC);
WriteOneChar(0xBE); //读取温度寄存器
tml=ReadOneChar();//读低8位
tmh=ReadOneChar();//读高8位
tvalue=tmh;
tvalue<<=8;
tvalue|=tml;
return tvalue;
//tvalue=tvalue*0.0625;//读取的数据保留到个位
}
*将DS18B20的温度值转换为浮点数
//正数的补码是原码
//负数的补码是负数的绝对值的原码按位取反加1
//将DS18B20的12位温度值转换为浮点数
float u16ToFloat(u16 dat){
float result=0.0;
u8 tmh=(dat&0xFF00)>>8;
result=tmh>7?(~(dat-1)) *(-1.0):dat;
return result*0.00625;
}
数码管的驱动如下:
#include"reg51.h"
#include<intrins.h>
#define Segment P1
#define Position P3
extern void delay_ms(int num);
static unsigned char code _Table[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
unsigned char _buff[]={0,0,0,0,0,0,0,0};//显示缓冲区
static unsigned char code _Pos[]={1<<0,1<<1,1<<2,1<<3,1<<4,1<<5,1<<6,1<<7};
static unsigned char pos=0;//显示的位置
void display(){
Segment=0xFF;
Segment=_Table[_buff[pos]];
Position=_Pos[pos];
delay_ms(1);
pos++;
if(8==pos)
pos=0;
}
应用程序如下:
#include"ds18b20.h"
#include"display.h"
void delay_ms(int num){ //延时函数
while(num--);
}
void main(){
unsigned int tmp=0;
while(1){
tmp=ReadTemperature();
_buff[3]=(tmp/10000)%10;
_buff[4]=(tmp/1000)%10;
_buff[5]=(tmp/100)%10;
_buff[6]=(tmp/10)%10;
_buff[7]=tmp%10;
display();
}
}