目标:
0、要点:
1、函数声明的注意点:
2、函数跨文件声明不成熟方法1:头文件间接声明
3、函数跨文件声明不成熟方法2:分函数(在.h文件中)
4、函数跨文件声明不成熟方法3:分函数(在.c文件中)
5、函数的分文件原理:将上述三种方法(主要是方法1和方法3)的优势进行结合,可以总结出自定义函数的分文件方法。如图示例:
1、变量的三特性:我们之前学过变量的四要素,现在看看变量具有哪三个特性。
2、内存的分区:这个内存我们一般认为是虚拟内存(32位平台下的虚拟内存有4G大小,gcc编译器就是64位平台),对其划分区域有利于程序员管理变量。
malloc()
、calloc()
等函数手动分配的内部存储。堆区的内部分配和释放是由程序显式控制的,具有更长且可控的生命周期。堆区变量的生长方向是向上的。
5、验证变量的生长方向:
#include
#include
int a;
int b;
static int c;
static int d;
const int e;
const int f;
int main(int argc, char const *argv[])
{
int g;
int h;
static int i;
static int j;
const int k;
const int l;
int *p1;
p1 = (int *)malloc(3*4);
int *p2;
p2 = (int *)malloc(3*4);
/* 测试全局变量的生长方向 */
printf("quanju:\n");
printf("%p\n", &a);
printf("%p\n", &b);
/* 测试static修饰变量(全局)的生长方向定义的生长方向 */
printf("static xiushi(quanju):\n");
printf("%p\n", &c);
printf("%p\n", &d);
/* 测试局部变量的生长方向 */
printf("jubu:\n");
printf("%p\n", &g);
printf("%p\n", &h);
/* 测试static修饰变量(局部)的生长方向 */
printf("static xiushi(jubu):\n");
printf("%p\n", &i);
printf("%p\n", &j);
/* 测试const修饰变量(全局)的生长方向 */
printf("const xiushi(quanju):\n");
printf("%p\n", &e);
printf("%p\n", &f);
/* 测试const修饰变量(局部)的生长方向 */
printf("const xiushi(jubu):\n");
printf("%p\n", &k);
printf("%p\n", &l);
/* 测试堆区变量的生长方向 */
printf("duiqu:\n");
printf("%p\n", p1);
printf("%p\n", p2);
return 0;
}
6、变量的重名问题与就近原则:
0、要点:
1、全局变量声明的注意点:
2、全局变量的分文件原理:
1、C语言编译过程回顾:
2、预处理之头文件包含:头文件的包含有两种方式。
3、预处理之定义宏:定义宏要用#define 宏名 值,宏是在预处理的时候进行替换的。宏定义能方便编程。
4、带参宏与带参函数的区别:
5、预处理之选择性编译:选择性编译有三种形式,选择性编译可以在源文件和头文件中使用。
#ifdef 宏名
代码段1 //如果在当前的.c程序中的#ifdef这句话的上面定义过该宏,则编译器编译代码段1
#else //可有可无
代码段2 //如果在当前的.c程序中的#ifdef这句话的上面没有定义过该宏,则编译器编译代码段2
#endif //最后加#endif结尾
设置宏名用于选择编译的开关,比如:
#define AAA 0
#if 表达式(一般用宏名)
代码段1 //如果表达式为真,则编译器编译代码段1
#else //可有可无
代码段2 //如果表达式为假,则编译器编译代码段2
#endif //最后加#endif结尾
#ifndef 宏名
代码段1 //如果在当前的.h程序中的#ifndef这句话的上面定义过该宏,则编译器编译代码段1
#else //可有可无
代码段2 //如果在当前的.h程序中的#ifdef这句话的上面没有定义过该宏,则编译器编译代码段2
#endif //最后加#endif结尾
/* ------------------------------------------------------------------------------------
1.注意在不同的.h文件中,上述宏名不可以重名,因为另外的.h会由于条件编译的原因就无法被包含进来
2.宏名的起名一般用:“__(两个下划线) + 头文件名(大写) + _(下划线) + H + __(两个下划线)”,
比如:__FUN_H__
------------------------------------------------------------------------------------ */
通常在.h文件中的做法(假设文件名是fun.h):
#ifndef __FUN_H__
#define __FUN_H__
头文件代码
#endif
1、分文件的目的:通过包含头文件的方式,避免编程代码过长的问题,实现模块化编程设计。
2、KEIL4中分文件建立流程:
3、分文件的原理:我以本项目作为背景,深入讨论一下如何实现分文件以及注意点。下面仅列举几个分文件说明问题。
1、接线示意图和实物图:
2、信号传输路线:
0、基本控制逻辑:温湿度数据管理系统
1、主程序:main.c
全局变量:
1. sbit指令找到P1这个I/O口组的第6位P1^6,它与继电器的IN引脚相连,控制风扇电路通断:
sbit relay = P1^6;
2. 定义用于保存湿度字符串的全局数组,将来显示在LCD和串口上,格式为 "RH(%)=xx.xx": char r_Humidity[17];
// r_Humidity的传递路线为:
//路线1: main.c ——> uart.c
//路线2:main.c ——> extern LCD1602_showLine(char row, char column, char *str);
3. 定义用于保存温度字符串的全局数组,将来显示在LCD和串口上,格式为 "T(celcius)=xx.xx": char temperature[17];
// temperature的传递路线为:
//路线1: main.c ——> uart.c
//路线2:main.c ——> extern LCD1602_showLine(char row, char column, char *str);
全局变量的声明:
1. 声明从串口模块中获取表示强制关闭风扇的标志位: extern char forcedClose_Flag;
2. 声明从DHT11模块中获取表示温湿度有效数据的全局数组: extern char dataDHT[5];
1. 调用串口模块函数,初始化串口: UartInit();
2. 调用延时模块函数,延时1秒,增加LCD成功显示数据的几率: Delay1000ms();
3. 调用LCD模块函数,初始化LCD: LCD1602_Init();
4. 调用延时模块函数,延时2秒,因为在DHT11传感器上电后,要等待1s以上 以越过不稳定状态:
Delay1000ms();
Delay1000ms();
5. while(1)死循环,每隔1秒进行数据读取,并在串口、LCD上显示
5.1 调用延时模块函数,延时1秒: Delay1000ms();
5.2 调用DHT11模块函数,与DHT11完成一次通讯,读取DHT11模块数据(40bit)放在数组dataDHT[5]中:
Read_Data_From_DHT11();
5.3 判断温度是否大于30度,或者是否强制关闭 //判据是: dataDHT[2]>=30 || forcedClose_Flag==0
5.3.1 如果是,那么
连通继电器,打开风扇: relay = 0;
5.3.2 否则,如果温度是否小于30度,或者不强制关闭,那么 //判据是: dataDHT[2]<30 || forcedClose_Flag==1
关闭继电器,关闭风扇: relay = 1;
5.4 调用API1. 通过dataDHT[]构建要显示的温湿度字符串,结果保存在温/湿度字符串中:
build_Datas_Show();
5.5 调用串口模块函数,将DHT11的温/湿度字符串发送给串口: Send_Data_From_DHT11();
5.6 调用LCD模块函数,将DHT11的湿度字符串显示在LCD上: LCD1602_showLine(1,0,r_Humidity);
5.7 调用LCD模块函数,将DHT11的温度字符串显示在LCD上: LCD1602_showLine(2,0,temperature);
/* 一级函数:f1 */
f1. 封装通过dataDHT[]构建温湿度字符串的API: void build_Datas_Show();
f1.1 逐字符往字符数组r_Humidity[]中写入湿度字符串,格式为"RH(%):xx.xx":
f1.1.1 写入字符子串"RH(%):":
r_Humidity[0] = 'R';
...................
r_Humidity[5] = ':';
f1.1.2 根据数字到字符的转化方式,保存湿度整数位(2位),它们在字符数组dataDHT的第0个元素:
r_Humidity[6] = dataDHT[0]/10 + 0x30;
r_Humidity[7] = dataDHT[0]%10 + 0x30;
f1.1.3 写入字符'.',小数点: r_Humidity[8] = '.';
f1.1.4 根据数字到字符的转化方式,保存湿度小数位(2位),它们在字符数组dataDHT的第1个元素:
r_Humidity[9] = dataDHT[1]/10 + 0x30;
r_Humidity[10] = dataDHT[1]%10 + 0x30;
f1.2 逐字符往字符数组r_Humidity[]中写入湿度字符串,格式为"T(celcius):xx.xx":
f1.1.1 写入字符子串"T(celcius):":
temperature[0] = 'T';
...................
temperature[10] = ':';
f1.1.2 根据数字到字符的转化方式,保存温度整数位(2位),它们在字符数组dataDHT的第2个元素:
temperature[11] = dataDHT[2]/10 + 0x30;
temperature[12] = dataDHT[2]%10 + 0x30;
f1.1.3 写入字符'.',小数点: temperature[13] = '.';
f1.1.4 根据数字到字符的转化方式,保存温度小数位(2位),它们在字符数组dataDHT的第3个元素:
temperature[14] = dataDHT[3]/10 + 0x30;
temperature[15] = dataDHT[3]%10 + 0x30;
#include "reg52.h"
#include "intrins.h"
#include "main.h"
#include "uart.h"
#include "lcd1602.h"
#include "delay.h"
#include "dht11.h"
sbit relay = P1^6; //继电器的IN引脚(低电平触发),控制风扇电路通断
char r_Humidity[17]; //将来显示在LCD和串口上的湿度字符串
char temperature[17]; //将来显示在LCD和串口上的温度字符串
extern char forcedClose_Flag; //From:"uart.c"
extern char dataDHT[5]; //From:"dht11.c"
void main(void)
{
UartInit();
Delay1000ms();//测试:增加LCD成功显示数据的几率
LCD1602_Init();
//DHT11传感器上电后,要等待1s以上 以越过不稳定状态
Delay1000ms();
Delay1000ms();
while(1){ //每隔一秒进行数据读取,并串口显示
Delay1000ms();
Read_Data_From_DHT11();
if(dataDHT[2] >= 30 || forcedClose_Flag == 0){ //这里手动开关风扇有差不多1s的延时,若想消除可以用外部中断(略)
relay = 0;
}else if(dataDHT[2] < 30 || forcedClose_Flag == 1){
relay = 1;
}
build_Datas_Show();
Send_Data_From_DHT11();
LCD1602_showLine(1,0,r_Humidity);
LCD1602_showLine(2,0,temperature);
}
}
void build_Datas_Show()
{
/* 湿度显示:RH(%):xx.xx */
r_Humidity[0] = 'R';
r_Humidity[1] = 'H';
r_Humidity[2] = '(';
r_Humidity[3] = '%';
r_Humidity[4] = ')';
r_Humidity[5] = ':';
r_Humidity[6] = dataDHT[0]/10 + 0x30; //湿度整数位
r_Humidity[7] = dataDHT[0]%10 + 0x30;
r_Humidity[8] = '.';
r_Humidity[9] = dataDHT[1]/10 + 0x30; //湿度小数位
r_Humidity[10] = dataDHT[1]%10 + 0x30;
r_Humidity[11] = '\0';
/* 温度显示:T(celcius):xx.xx */
temperature[0] = 'T';
temperature[1] = '(';
temperature[2] = 'c';
temperature[3] = 'e';
temperature[4] = 'l';
temperature[5] = 'c';
temperature[6] = 'i';
temperature[7] = 'u';
temperature[8] = 's';
temperature[9] = ')';
temperature[10] = ':';
temperature[11] = dataDHT[2]/10 + 0x30; //温度整数位
temperature[12] = dataDHT[2]%10 + 0x30;
temperature[13] = '.';
temperature[14] = dataDHT[3]/10 + 0x30; //温度小数位
temperature[15] = dataDHT[3]%10 + 0x30;
temperature[16] = '\0';
}
2、“DHT11”模块:DHT11模块相关的分文件:dht11.c
全局变量:
1. sbit指令找到P3这个I/O口组的第3位P3^3,它与DHT11模块的DATA数据线相连,
用来输出主机启动信号和接收数据: sbit dht11 = P3^3;
2. 用于保存DHT11和单片机一次通讯所传输的有效数据,一共40bit: char dataDHT[5];
//dataDHT[5]的传输路线为:
//路线1:dht11(P3.3) ——> API2: Read_Data_From_DHT11() ——> bitFlag ——> temp ——> dataDHT[i]
//路线2:dataDHT[] ——> main.c ——> 温/湿度字符串 ——> uart.c ——> SBUF ——> PC的串口助手,蓝牙app
//路线3:dataDHT[] ——> main.c ——> 温/湿度字符串 ——> LCD显示屏
/* 一级函数:f1、f2 */
f1. 封装启动DHT11模块的API: void DHT11_Start();
f1.1 根据DHT11模块的时序逻辑分析,总结出如下过程:
dht11 = 1;
dht11 = 0;
调用API2. 软件延时30ms: Delay30ms();
dht11 = 1;
空循环体,卡d点,直到DATA变成低电平:while(!(dht11==0));
空循环体,卡e点,直到DATA变成高电平:while(!dht11);
空循环体,卡f点,直到DATA变成低电平:while(!(dht11==0));
f2. 封装读取DHT11模块数据(40bit)放在数组dataDHT[5]中的API: void Read_Data_From_DHT11();
f2.1 定义局部变量temp,用于移位存放1bit数据: char temp;
f2.2 定义1bit数据的标志位,1代表读到的是1,0代表读到的是0: char bitFlag;
f2.3 调用API1. 每次数据采集(40bit)前都要发送开始信号: DHT11_Start();
f2.4 for循环嵌套,实现40bit数据的采集,代表数组下标的外层循环变量i从0开始,<5时进入循环
f2.4.1 for循环,循环变量j从0开始,<8时进入循环
f2.4.1.1 根据DHT11模块的时序逻辑分析,总结出如下过程:
空循环体,卡g点,直到DATA变成高电平:while(!dht11);
看时序图, 调用API4. 软件延时50微秒: Delay50us();
f2.4.1.2 紧接着判断数据线DATA上的电平是否是高电平,判据是dht11 == 1
f2.4.1.2.1 如果是,说明该bit是高电平,那么:
将该位数据以标志位形式保存在标志位变量bitFlag中: bitFlag = 1;
空循环体,卡f’点,直到DATA变成低电平:while(!(dht11==0));
f2.4.1.2.2 否则,说明该bit是低电平,那么:
将该位数据以标志位形式保存在标志位变量bitFlag中: bitFlag = 0;
f2.4.1.3 通过移位和位运算,将1bit数据保存在变量temp中:
temp = tem << 1;
temp |= bitFlag;
f2.4.2 经过内层循环,将已经获取到的8bit数据依次保存到字符数组dataDHT中:
dataDHT[i] = temp;
#include "reg52.h"
#include "delay.h"
#include "dht11.h"
sbit dht11 = P3^3; //DHT11模块的DATA数据线
char dataDHT[5]; //温湿度数据
void DHT11_Start()
{
dht11 = 1;
dht11 = 0;
Delay30ms();
dht11 = 1;
while(!(dht11==0)); //卡d点
while(!dht11); //卡e点
while(!(dht11==0)); //卡f点
}
void Read_Data_From_DHT11()
{
char i; //轮
char j; //每轮读多少次
char temp; //移位
char bitFlag; //该bit的标志位,1代表读到的是1,0代表读到的是0
DHT11_Start(); //每次数据采集(40bit)前都要发送开始信号
for(i=0; i<5; i++){
for(j=0; j<8; j++){
while(!dht11); //卡g点
Delay50us();
if(dht11 == 1){ //说明该bit是高电平
bitFlag = 1;
while(!(dht11==0));
}else{ //说明该bit是低电平
bitFlag = 0;
}
temp = temp << 1; //temp <<= 1;
temp |= bitFlag;
}
dataDHT[i] = temp;
}
}
3、“LCD”模块:LCD1602相关的分文件:lcd1602.c
宏定义:
1. 定义符号dataBuffer,用它代表P0这个I/O口组: #define dataBuffer P0
//dataBuffer的传递路线为:
//路线1:API1. LCD_write_cmd(char cmd); ——> cmd ——> dataBuffer ——> LCD
//路线2:API2. LCD_write_data(char datashow); ——> datashow ——> dataBuffer ——> LCD
//路线3:API4. check_busy() ——> LCD ——> dataBuffer ——> temp
全局变量:
1. sbit指令找到P1这个I/O口组的第0位P1^0,把它与LCD的RS(LCD的数据/指令寄存器选择位)相连,用来输出指令给LCD: sbit RS = P1^0;
2. sbit指令找到P1这个I/O口组的第0位P1^1,把它与LCD的RW(LCD的读/写选择位)相连,用来输出指令给LCD: sbit RW = P1^1;
3. sbit指令找到P1这个I/O口组的第0位P1^4,把它与LCD的E(LCD的使能信号)相连,用来输出指令给LCD: sbit EN = P1^4;
/*一级函数:f1、f2、f3、f5*/
f1. 封装往LCD液晶显示模块写指令的API: void LCD_write_cmd(char cmd);
//形参cmd是要写入的指令(地址)
f1.1 调用API4,对LCD操作前检测忙信号: check_busy();
f1.2 RS低电平时,指令寄存器选择: RS = 0;
f1.3 RW低电平,表示写操作: RW = 0;
f1.4 根据写操作的时序分析,总结出如下过程:
EN = 0;
_nop_();
dataBuffer = cmd;
_nop_();
EN = 1;
_nop_();
EN = 0;
_nop_();
f2. 封装往LCD液晶显示模块写内容的API: void LCD_write_data(char datashow);
//形参datashow是要写入的内容
f2.1 调用API4,对LCD操作前检测忙信号: check_busy();
f2.2 RS高电平时,数据寄存器选择: RS = 1;
f2.3 RW低电平,表示写操作: RW = 0;
f2.4 根据写操作的时序分析,总结出如下过程:
EN = 0;
_nop_();
dataBuffer = datashow;
_nop_();
EN = 1;
_nop_();
EN = 0;
_nop_();
f3. 封装初始化LCD液晶显示模块的API: void LCD1602_Init();
f3.1 调用延时模块函数,软件延时15ms: Delay15ms();
f3.2 调用API1,写指令38H(不检测忙信号): LCD_write_cmd(0x38);
f3.3 调用延时模块函数,软件延时5ms: Delay5ms();
f3.4 显示模式设置:
调用API4,检测忙信号: check_busy();
调用API1,写指令38H: LCD_write_cmd(0x38);
f3.5 显示关闭:
调用API4,检测忙信号: check_busy();
调用API1,写指令08H: LCD_write_cmd(0x08);
f3.6 显示清屏:
调用API4,检测忙信号: check_busy();
调用API1,写指令01H: LCD_write_cmd(0x01);
f3.7 显示光标移动设置:
调用API4,检测忙信号: check_busy();
调用API1,写指令06H: LCD_write_cmd(0x06);
f3.8 显示开机光标设置:
调用API4,检测忙信号: check_busy();
调用API1,写指令0CH: LCD_write_cmd(0x0C);
f5. 封装在LCD液晶上显示一行字符串的API: void LCD1602_showLine(char row,char column,char *str);
//形参row是行(1~2),column是列(0~15),str是要显示的字符串
f5.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;
f5.2 switch选择语句,表达式为row
f5.2.1 当row为1时:表示在第一行显示字符串
f5.2.1.1 往LCD显示模块中写起始地址:
调用API4,检测忙信号: check_busy();
调用API1,告知显示地址为第1行第column列,LCD_write_cmd(0x80+column);
f5.2.1.2 while循环,控制循环的变量是*p,当*p != '\0' 时进入循环,发送要显示的内容:
调用API4,检测忙信号: check_busy();
调用API2. 往LCD显示模块中写当前字符指针p所在位置的字符: LCD_write_data(*p);
修改循环变量p的值,让指针p偏移: p++;
f5.2.1.3 break提前退出当前选择控制语句: break;
f5.2.2 当row为2时:表示在第二行显示字符串
f5.2.2.1 往LCD显示模块中写起始地址:
调用API4,检测忙信号: check_busy();
调用API1,告知显示地址为第2行第column列,LCD_write_cmd(0x80+0x40+column);
f5.2.2.2 while循环,控制循环的变量是*p,当*p != '\0' 时进入循环,发送要显示的内容:
调用API4,检测忙信号: check_busy();
调用API2. 往LCD显示模块中写当前字符指针p所在位置的字符: LCD_write_data(*p);
修改循环变量p的值,让指针p偏移: p++;
f5.2.2.3 break提前退出当前选择控制语句: break;
/* 二级函数:f4*/
f4. 封装检测(读取)忙信号的API: void check_busy();
f4.1 将从P0这个I/O口组获取到的LCD的8位数据线的数据,保存在字符变量temp中: char temp = 0x80;
//内在逻辑:temp中包含了忙信号的标志位(bit7),标志位是1则表示LCD正忙,我们还没读取前
//就让51单片机认为LCD正忙,所以初始化为0x80(1000 0000),这样方便一会儿进入循环
f4.2 把LCD的busy这个状态做的更彻底一点,让P0这个I/O口组的bit7是1: dataBuffer = 0x80;
f4.3 while循环,一直检测忙信号,直到检测到不忙,判据是:!((temp & 0x80)==0)
//语法逻辑:用!表示“直到”,用 (temp & 0x80)==0 表示不忙。也就是说不忙就退出循环,不再检测
f4.3.1 RS低电平时,指令寄存器选择: RS = 1;
f4.3.2 RW高电平,表示读操作: RW = 1;
f4.3.3 根据读操作的时序分析,总结出如下过程:
EN = 0;
_nop_();
EN = 1;
_nop_();
temp = dataBuffer;
_nop_();
EN = 0;
_nop_();
#include "reg52.h"
#include "intrins.h"
#include "delay.h"
#include "lcd1602.h"
#define dataBuffer P0 //LCD的8位数据线,刚好用dataBuffer这个I/O口组
sbit RS = P1^0; //LCD的数据/指令寄存器选择位
sbit RW = P1^1; //LCD的读/写选择位
sbit EN = P1^4; //LCD的使能信号
void LCD_write_cmd(char cmd)
{
check_busy();
RS = 0; //RS低电平时,指令寄存器选择,将1个字符写在数据线上告诉LCD这是指令
RW = 0;
EN = 0;
_nop_();
dataBuffer = cmd;
_nop_();
EN = 1;
_nop_();
EN = 0;
_nop_();
}
void LCD_write_data(char datashow)
{
check_busy();
RS = 1; //RS高电平时,数据寄存器选择,将1个字符写在数据线上告诉LCD这是内容
RW = 0;
EN = 0;
_nop_();
dataBuffer = datashow;
_nop_();
EN = 1;
_nop_();
EN = 0;
_nop_();
}
void LCD1602_Init()
{
Delay15ms(); //(1)延时15ms
LCD_write_cmd(0x38); //(2)写指令38H(不检测忙信号)
Delay5ms(); //(3)延时5ms
//(4)以后每次写指令,读/写数据操作均需要检测忙信号
check_busy();
LCD_write_cmd(0x38); //(5)写指令38H:显示模式设置
check_busy();
LCD_write_cmd(0x08); //(6)写指令08H:显示关闭
check_busy();
LCD_write_cmd(0x01); //(7)写指令01H:显示清屏
check_busy();
LCD_write_cmd(0x06); //(8)写指令06H:显示光标移动设置
check_busy();
LCD_write_cmd(0x0C); //(9)写指令0CH:显示开机光标设置
}
void check_busy()
{
char temp = 0x80; //一开始就busy
dataBuffer = 0x80;
while( !((temp & 0x80)==0) ){ //一直检测忙信号,直到检测到不忙(temp的bit7为高电平代表忙)
RS = 0;
RW = 1;
EN = 0;
_nop_();
EN = 1;
_nop_();
temp = dataBuffer;
_nop_();
EN = 0;
_nop_();
}
}
void LCD1602_showLine(char row, char column, char *str)
{
char *p = str;
switch(row){
case 1:
check_busy();
LCD_write_cmd(0x80+column); //选择要显示的地址
while(*p != '\0'){
check_busy();
LCD_write_data(*p); //发送要显示的字符(不用发指令让光标移动,光标会自动后移)
p++;
}
break;
case 2:
check_busy();
LCD_write_cmd(0x80+0x40+column); //选择要显示的地址
while(*p != '\0'){
check_busy();
LCD_write_data(*p); //发送要显示的字符(不用发指令让光标移动,光标会自动后移)
p++;
}
break;
default :
break;
}
}
#include "reg52.h"
#include "intrins.h"
#include "delay.h"
#include "lcd1602.h"
#define dataBuffer P0 //LCD的8位数据线,刚好用dataBuffer这个I/O口组
sbit RS = P1^0; //LCD的数据/指令寄存器选择位
sbit RW = P1^1; //LCD的读/写选择位
sbit EN = P1^4; //LCD的使能信号
void LCD_write_cmd(char cmd)
{
check_busy();
RS = 0; //RS低电平时,指令寄存器选择,将1个字符写在数据线上告诉LCD这是指令
RW = 0;
EN = 0;
_nop_();
dataBuffer = cmd;
_nop_();
EN = 1;
_nop_();
EN = 0;
_nop_();
}
void LCD_write_data(char datashow)
{
check_busy();
RS = 1; //RS高电平时,数据寄存器选择,将1个字符写在数据线上告诉LCD这是内容
RW = 0;
EN = 0;
_nop_();
dataBuffer = datashow;
_nop_();
EN = 1;
_nop_();
EN = 0;
_nop_();
}
void LCD1602_Init()
{
Delay15ms(); //(1)延时15ms
LCD_write_cmd(0x38); //(2)写指令38H(不检测忙信号)
Delay5ms(); //(3)延时5ms
//(4)以后每次写指令,读/写数据操作均需要检测忙信号
check_busy();
LCD_write_cmd(0x38); //(5)写指令38H:显示模式设置
check_busy();
LCD_write_cmd(0x08); //(6)写指令08H:显示关闭
check_busy();
LCD_write_cmd(0x01); //(7)写指令01H:显示清屏
check_busy();
LCD_write_cmd(0x06); //(8)写指令06H:显示光标移动设置
check_busy();
LCD_write_cmd(0x0C); //(9)写指令0CH:显示开机光标设置
}
void check_busy()
{
char temp = 0x80; //一开始就busy
dataBuffer = 0x80;
while( !((temp & 0x80)==0) ){ //一直检测忙信号,直到检测到不忙(temp的bit7为高电平代表忙)
RS = 0;
RW = 1;
EN = 0;
_nop_();
EN = 1;
_nop_();
temp = dataBuffer;
_nop_();
EN = 0;
_nop_();
}
}
void LCD1602_showLine(char row, char column, char *str)
{
char *p = str;
switch(row){
case 1:
check_busy();
LCD_write_cmd(0x80+column); //选择要显示的地址
while(*p != '\0'){
check_busy();
LCD_write_data(*p); //发送要显示的字符(不用发指令让光标移动,光标会自动后移)
p++;
}
break;
case 2:
check_busy();
LCD_write_cmd(0x80+0x40+column); //选择要显示的地址
while(*p != '\0'){
check_busy();
LCD_write_data(*p); //发送要显示的字符(不用发指令让光标移动,光标会自动后移)
p++;
}
break;
default :
break;
}
}
4、“串口”模块:串口相关的分文件:uart.c
宏定义:
1. 定义符号常量len,用它代表用于接收SBUF中缓冲字符串的全局数组的长度: #define len 12
全局变量:
1. sfr指令直接找到AUXR寄存器: sfr AUXR = 0X8E; //因为AUXR没有在reg52.h中声明
2. sbit指令找到P1这个I/O口组的第6位P1^6,它与继电器的IN引脚相连,控制风扇电路通断:
sbit relay = P1^6;
3. sbit指令找到P3这个I/O口组的第6位P3^6,它代表D6这个LED: sbit ledD6 = P3^6;
4. 定义一个用于接收串口缓冲区字符串的全局数组serial_buffer: char serial_buffer[len];
//serial_buffer的传递路线为:SBUF ——> 串口中断(中断4)——> 临时字符变量temp
5. 定义表示强制关闭风扇的标志位,并默认强制关闭: char forcedClose_Flag = 1;
//forcedClose_Flag的传递路线是:uart.c ——> main.c
全局变量的声明:
1. 声明从main.c中获取的湿度字符串: extern char r_Humidity[17];
2. 声明从main.c中获取的温度字符串: extern char temperature[17];
中断:
中断4: 封装串口中断的中断服务程序, void Uart_Routine() interrupt 4
4.1 定义一个静态全局区的静态变量,用来表示数组serial_buffer的下标: static int i = 0;
4.2 定义一个临时字符变量temp,用于检测关键字眼,保证我们的字符串是从字符数粗的第0位开始存放的。
char temp;
4.3 中断处理程序中,对于接收中断的响应,判据是RI == 1
4.3.1 在接受到1字节数据后,程序复位RI: RI = 0;
4.3.2 串口缓冲区接收到的字符先存放在临时变量temp中: temp = SBUF;
4.3.3 从数据缓冲寄存器SBUF中读到字符后,根据我们提前设计好的关键字眼,关心:
"cmd:open"的':',判据是temp==':'
4.3.3.1 如果是,那么需要从头开始存放: i = 0;
4.3.3.2 否则,那么什么也不做,继续往下执行
4.3.4 将temp的值保存在数组serial_buffer的第i个元素中: serial_buffer[i] = temp;
4.3.5 偏移数组下标: i++;
4.3.6 判断字符数组serial_buffer是否存满了,判据是 i == len
//内在逻辑:由于serial_buffer长度的限制,当字符串超过len时,我们需要覆盖掉原先的字符
4.3.6.1 如果是,那么需要从头开始存放: i = 0;
4.3.6.2 否则,那么什么也不做,继续往下执行
4.3.7 通过字符数组的第0位和第2位捕捉关键字眼,判断蓝牙模块是否收到透传数据":open",判据是
serial_buffer[0]==':' && serial_buffer[1]=='o' && serial_buffer[2]=='p'
4.3.7.1 如果是,
修改代表强制关闭风扇的标志位,0代表不关闭,也就打开风扇: forcedClose_Flag = 0;
点亮D6: ledD6 = 0;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.3.8.2 否则,如果蓝牙模块收到透传数据":close"
修改代表强制关闭风扇的标志位,1代表关闭,也就是关闭风扇: forcedClose_Flag = 1;
熄灭D6: ledD6 = 1;
有效指令后清空字符数组: memset(serial_buffer,'\0',len);
4.4 中断处理程序中,对于发送中断的响应,判据是TI == 1
暂时不做任何事情
/* 一级函数:f1、f4 */
f1. 封装初始化串口的API: void UartInit(void);
f1.1 禁用ALE信号: AUXR = 0X01;
f1.2 让串口以方式1工作(8位UART,可变波特率),并允许串口接收: SCON = 0x50;
f1.3 让定时器1以8位重载工作模式工作:
TMOD &= 0xDF;
TMOD |= 0x20;
f1.4 根据波特率为9600,波特率不翻倍,设置定时器1的初值:
TH1 = 0xFD;
TL1 = 0xFD;
f1.5 定时器开始数数: TR1 = 1;
f1.6 开启串口中断:
EA = 1;
ES = 1;
f4. 封装将DHT11模块测得的温湿度数据发送给串口的API: void Send_Data_From_DHT11();
f4.1 调用API3. 将湿度字符串r_Humidity发送给给串口:sendString(r_Humidity);
f4.2 调用调用API3. 给串口发送字符串"\r\n",表示换行: sendString("\r\n");
f4.3 调用API3. 将温度字符串temperature发送给给串口:sendString(temperature);
f4.4 调用API3. 给串口发送字符串"\r\n\r\n",表示显示空行: sendString("\r\n\r\n");
/* 二级函数:f3 */
f3. 封装给PC发送字符串的API: void sendString(char *str); //形参是字符串的地址
f3.1 定义一个字符指针变量p用来保存字符串首地址: char *p = str;
f3.2 while循环,控制循环的变量是*p,当*p != '\0' 时,进入循环,进行单个字符的发送
f3.2.1 通过指针间接访问字符串字符,再调用API2. 发送单个字符: sendByte(*p);
f3.2.2 修改循环变量p的值,让指针p偏移: p++;
/* 三级函数:f2 */
f2. 封装定时给PC发送一个字符的API: void sendByte(char data_msg); //形参是字符值
f2.1 往SBUF寄存器中写入字符data_msg: SBUF = data_msg;
f2.2 根据串口发送中断触发位TI,利用空循环体暂停程序,直到TI变成1: while(!TI);
f2.3 程序复位TI: TI = 0;
#include "reg52.h"
#include
#include "uart.h"
#include "main.h"
sfr AUXR = 0x8E;
sbit relay = P1^6;
sbit ledD6 = P3^6;
char serial_buffer[len]; //"uart.h"中宏名len = 12
char forcedClose_Flag = 1; //风扇标志位,默认关
extern char r_Humidity[17]; //From:"main.c"
extern char temperature[17]; //From:"main.c"
void UartInit(void) //[email protected]
{
AUXR = 0x01;
SCON = 0x50; //8位UART,允许串口接收
TMOD &= 0xDF;
TMOD |= 0x20; //定时器8位重载工作模式
TH1 = 0xFD;
TL1 = 0xFD; //9600波特率初值
TR1 = 1;
EA = 1;
ES = 1; //开启串口中断
}
void sendByte(char data_msg)
{
SBUF = data_msg;
// Delay10ms();
while(TI == 0);
TI = 0;
}
void sendString(char *str)
{
char *p = str;
while(*p != '\0'){
sendByte(*p);
p++;
}
}
void Send_Data_From_DHT11()
{
sendString(r_Humidity);
sendString("\r\n");
sendString(temperature);
sendString("\r\n\r\n");
}
void Uart_Routine() interrupt 4
{
static int i = 0;//静态变量,被初始化一次
char temp;
/* 中断处理程序中,对于接收中断的响应 */
if(RI)//中断处理函数中,对于接收中断的响应
{
RI = 0;//清除接收中断标志位
temp = SBUF;
if(temp == ':'){
i = 0;
}
serial_buffer[i] = temp;
i++;
if(i == len) i = 0;
//风控指令
if(serial_buffer[0] == ':' && serial_buffer[1] == 'o' && serial_buffer[2]=='p'){
forcedClose_Flag = 0;//风扇转
ledD6 = 0;//点亮D6
memset(serial_buffer, '\0', len);
}else if(serial_buffer[0] == ':' && serial_buffer[1] == 'c' && serial_buffer[2]=='l'){
forcedClose_Flag = 1;//风扇不转
ledD6 = 1;//熄灭D6
memset(serial_buffer, '\0', len);
}
}
/* 中断处理程序中,对于发送中断的响应 */
if(TI == 1){
// 暂时不做任何事情
}
}
5、“软件延时”模块:软件延时相关的分文件:delay.c
f1. 封装软件延时30ms的API,用于API1的时序分析: void Delay30ms();
f2. 封装软件延时50微秒的API,用于DHT11传输数据中每1bit的时序分析: void Delay50us();
f3. 封装软件延时1s的API,用于DHT11模块上电后的稳定,以及每隔1s读取DHT11数据: void Delay1000ms();
f4. 封装软件延时15ms的API,用于LCD初始化: void Delay15ms();
f5. 封装软件延时5ms的API,用于LCD初始化: void Delay5ms();
#include "intrins.h"
void Delay30ms() //@11.0592MHz
{
unsigned char i, j;
i = 54;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delay50us() //@11.0592MHz
{
unsigned char i;
_nop_();
i = 20;
while (--i);
}
void Delay1000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 8;
j = 1;
k = 243;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay15ms() //@11.0592MHz
{
unsigned char i, j;
i = 27;
j = 226;
do
{
while (--j);
} while (--i);
}
void Delay5ms() //@11.0592MHz
{
unsigned char i, j;
i = 9;
j = 244;
do
{
while (--j);
} while (--i);
}