51单片机学习(2)
1.模块化编程
1.1模块化编程的意义
在学习单片机的途中,随着我们的知识不断扩展,我们能写出的代码也越来越复杂,越来越长了,有时我们自己写出的很长的代码,出现了错误,需要调试,但奈何自己的代码实在是太长了,真的不好分析到底是哪里出错了。这是因为我们将太多的函数和代码放在一个main.c的文件里,导致代码过度堆积。如果我们能够条理清晰地去将不同作用的代码和函数放在不同的xxx.c文件里面,等我们需要用的时候直接调用它,不是会方便很多吗?
像上面说的这种方法被称为模块化编程,大致就是创建一些其他文件,将一些函数放在这个文件里面,并创建一个同名的可以被引入的头文件,在main.c里只需要引入头文件,就可以在主函数里使用,达到简化的目的。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。
1.2模块化编程的操作
模块化编程的操作也很简单。我们打开keil 5后,像往常一样创建新的工程,在新的工程中创建新的main.c文件,再创建一个xxx.c,将平常要放到main.c的大把大把函数直接放进xxx.c里面,等这些都完成后,我们再点击创建一个新的文件,一个xxx.h文件,再把函数的原型声明放进来,像下面这样:
#ifndef __DELAY_H_
#define __DELAY_H_
void Delay(unsigned int h);//注意原型声明末尾有分号。
#endif
我们来解释以下,除了函数声明以外,还有很多其他的东西:
#ifndef __XXX_H_
/* 其实这是if not define的缩写,
目的是判断之前有没有定义过__DELAY_H_,
避免重复定义,如果已经定义过了,
那就不执行以后的语句。 */
#define __XXX_H_
/*用于定义一个头文件,定义完之后,
可以在其它文件里引入头文件,
然后可以直接使用头文件里面的函数。*/
#endif
// 一个结束语句的判断符,相当于一个 '}'
头文件格式是__XXX_H_
,双下滑线+头文件名+下滑线+H+下滑线。
当定义完了头文件后,在main.c里面,我们只需要#include "XXX.H"
,就可以调用头文件里面的函数了。
在头文件里面,为了方便阅读,一般会给出较为详细的注释,以便于读懂函数。
2.LCD1602(液晶显示屏)
2.1液晶显示屏的介绍
LCD1602是一块两排的液晶显示屏,可以输出字母,数字或者一些符号,相比数码管,它可以显示的信息更多,但也使它更加难以解释。
这是一个原理简单,但驱动操作比较复杂的外设。
这是关于驱动液晶显示屏的代码。
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,可延时1ms
* @param 无
* @retval 无
*/
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column)