一、前言
承接上文,本文将继续介绍剩余蓝桥杯涉及模块(PCF8591、AT24C02、PWM、串口通信、NE555以及超声波测距),为大家做出详细编程指导。
二、PCF8591 A/D 转换模块
笔者将基于蓝桥杯官方给的赛点资源包(蓝桥杯单片机设计与开发_赛点资源数据包)进行编程,为大家讲解编程调用各语句的含义,帮助大家编写温度传感器的程序,也便于大家记忆。
1、导入底层驱动代码
读者可下载赛点资源包,解压后可得到以下图片所示文件。
我们双击打开文件“3-底层驱动代码参考”文件,将得到以下
.c
和 .h
文件。接下来将 iic.c
和 iic.h
文件拷贝到工程所在文件夹中,并在编译器中将这两个文件导入,便可以开始编写程序了。
2、PCF8591 A/D转换函数封装
首先,在编写驱动函数之前,我们需要在底层驱动代码 iic.c
文件中编写 A/D转换函数,即 模拟量转化为数字量 和 数字量转化为模拟量。我们可以打开赛点资源数据包中的文件“5-竞赛板芯片资料”。
打开“PCF8591”芯片手册。笔者将借助芯片手册中的资料编写温度读取函数并为大家讲解。
模拟量转化成数字量:编写AD转换函数,具体每条代码的含义都已经注释,在这里将不再赘述。
// 模拟量转化成数字量
unsigned char PCF8591_ADC(unsigned char addr)
{
unsigned char date;
IIC_Start(); //开启总线
IIC_SendByte(0x90); //0x90: 1001 0000 向PCF8091写数据
IIC_WaitAck(); //等待应答
IIC_SendByte(addr); //写数据
IIC_WaitAck(); //等待应答
IIC_Stop(); //关闭总线
IIC_Start(); //开启总线
IIC_SendByte(0x91); //0x91: 1001 0001 向PCF8091读数据
IIC_WaitAck(); //等待应答
date = IIC_RecByte(); //接受数据
IIC_SendAck(1); //发送fei应答:已经读完数据
IIC_Stop(); //关闭总线
return date;
}
其中,在编写模数转换函数指令可通过查阅芯片资料手册,不需要死记硬背。
- PCF8591芯片地址前四位默认为
1001
即9
- 后四位
A2\A1\A0
视具体的接线而定,最低位为0
则表示向芯片写数据,为1
则表示从芯片读数据。
数字量转化成模拟量:编写DA转换函数。
// 数字量转化成模拟量
void PCF8591_DAC(unsigned char date)
{
IIC_Start(); //开启总线
IIC_SendByte(0x90); //0x90: 1001 0000 向PCF8091写数据
IIC_WaitAck(); //等待应答
IIC_SendByte(0x40); //告诉芯片要输出模拟量,即DA
IIC_WaitAck(); //等待应答
IIC_SendByte(date); //
IIC_WaitAck(); //等待应答
IIC_Stop(); //关闭总线
}
同样可以查阅上述芯片资料手册,第六位为 1
时即表示输出模拟量,以及其他位的含义在芯片中都可以查找到,在这里还是想强调一下:初学者一定要学会看芯片手册,学会看手册对于你的代码水平一定会有提升。
在编写完时间读写函数后务必要在 iic.h
文件中声明函数,才可在主函数中调用。
#ifndef _IIC_H
#define _IIC_H
#include "STC15F2K60S2.h"
#include "intrins.h"
sbit SDA = P2^1; /* 数据线 */
sbit SCL = P2^0; /* 时钟线 */
unsigned char PCF8591_ADC(unsigned char addr);
void PCF8591_DAC(unsigned char date);
#endif
3、主函数调用
在主函数中调用时间模块底层函数,首先需要在主函数开头包含驱动 iic.h
头文件。
#include "STC15F2K60S2.h"
#include "iic.h"
main主函数 主程序调用比较简单,下文中实现了 采用PCF8591 D/A模块输出电压,并通过按键修改输出电压值;并通过PCF8591 A/D模块Rb2电压和A/D脚电压,并通过数码管显示。由于考虑初学者基础较为薄弱,因此将完整的 main.cpp 代码贴出来,包含中断函数、矩阵键盘、数码管显示等,并且在关键处进行了注释,希望对大家有所帮助。
// 包含头文件
#include "STC15F2K60S2.h"
#include "iic.h"
// 数据类型定义
#define u8 unsigned char
#define u16 unsigned int
// 8位数码管状态
u8 dspbuf[8] = {10,10,10,10,10,10,10,10};
u8 code tab[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff,0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0x89,0xc1,0xbf};
// 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 灭 , 0. , 1. , 2. , 3. , 4. , 5. , 6. , 7. , 8. , 9. , H , U , - ;
// 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 , 20 , 21 , 22 , 23 ;
u8 dspcom = 0,i;
bit key_flag = 0,iic_flag1 = 0,iic_flag2 = 0;
// 定时器初始化
void Timer0Init(void) //2毫秒@12.000MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x40; //设置定时初值
TH0 = 0xA2; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //开定时中断
EA= 1; //开总中断
}
// 573锁存器封装成函数
void door(u8 choose,u8 input)
{
P2 = (P2 & 0x1f) | choose;
P0 = input;
P2 &= 0x1f;
}
// 关数码管
void clo_num()
{
dspbuf[0] = 10;
dspbuf[1] = 10;
dspbuf[2] = 10;
dspbuf[3] = 10;
dspbuf[4] = 10;
dspbuf[5] = 10;
dspbuf[6] = 10;
dspbuf[7] = 10;
}
// 关灯、蜂鸣器、数码管
void all_init()
{
door(0x80,0xff);
door(0xa0,0xaf);
clo_num();
}
// 数码管显示函数
void display()
{
door(0xe0,0xff); //消隐
door(0xc0,0x01<<dspcom);
door(0xe0,tab[dspbuf[dspcom++]]);
if(dspcom >= 8) //or dspcom &= 0x07;
dspcom = 0;
}
// 矩阵键盘,接KBD
u8 keypress = 0,keyvalue = 0xff,keyread = 0;
u8 Read_key(void)
{
u8 key_m,cal;
P3 = 0xf0;P42=1;P44=1;
P36=P42;P37=P44; //变量替换
key_m = (P3 & 0xf0);
if(key_m != 0xf0)
keypress++;
else
keypress = 0;
if(keypress == 3)
{
keypress = 0;
keyread = 1;
switch(key_m)
{
case 0x70:cal = 0;break;
case 0xb0:cal = 1;break;
case 0xd0:cal = 2;break;
case 0xe0:cal = 3;break;
}
P3 = 0x0f;P42=0;P44=0;
P36=P42;P37=P44; //变量替换
key_m = (P3 & 0x0f);
switch(key_m)
{
case 0x0e:keyvalue = (4*cal+7);break;
case 0x0d:keyvalue = (4*cal+6);break;
case 0x0b:keyvalue = (4*cal+5);break;
case 0x07:keyvalue = (4*cal+4);break;
}
}
P3 = 0x0f;P42=0;P44=0;
P36=P42;P37=P44; //变量替换
key_m = (P3&0x0f);
if((keyread == 1) && (key_m == 0x0f))
{
keyread = 0;
return keyvalue;
}
return 0xff;
}
//````````````````````````````````主函数````````````````````````````````````
void main()
{
u8 key_re,iic_w = 0,vol = 0;
int date_1,date_2;
all_init();
Timer0Init();
// 数字量转为模拟量,此时可以在单片机D/A角上测到 200/255*5V 电压
PCF8591_DAC(200);
while(1)
{
if(key_flag)
{
key_flag = 0;
key_re = Read_key();
if(key_re != 0xff)
{
switch(key_re)
{
// 修改电压输出
case 18:vol +=10;break;
case 17:iic_w = 1;;break;
case 16:iic_w = 0;
clo_num();break;
default:break;
}
// 设置修改的电压输出值
PCF8591_DAC(vol);
}
}
// 定时采集Rb2电压大小
if((iic_flag1) && (iic_w))
{
iic_flag1 = 0;
date_1 = PCF8591_ADC(0); //测Rb2电压
date_1 = 5*date_1/255.0*100;
dspbuf[0] = date_1/100+11;
dspbuf[1] = date_1%100/10;
dspbuf[2] = date_1%10;
}
// 定时采集 A/D 引脚电压大小
if((iic_flag2) && (iic_w))
{
iic_flag2 = 0;
date_2 = PCF8591_ADC(3); // 用A/D引脚测电压
date_2 = 5*date_2/255.0*100;
dspbuf[5] = date_2/100+11;
dspbuf[6] = date_2%100/10;
dspbuf[7] = date_2%10;
}
}
}
// 定时器0中断服务函数
void timer0() interrupt 1
{
static u8 t_20ms = 0,t_100ms = 0; //MAX = 500 ms
//不需要重载装载值
display(); //数码管显示函数放里面
t_20ms++;
t_100ms++;
if(t_20ms >= 10) //每20ms扫描一次按键
{
t_20ms = 0;
key_flag = 1;
}
if(t_100ms == 100)
{
iic_flag1 = 1;
}
if(t_100ms == 200)
{
t_100ms = 0;
iic_flag2 = 1;
}
}
至此,本次 PCF8591 A/D转换模块已介绍完毕。