51单片机学习笔记-3模块化编程

news/2025/1/12 20:38:51/

3 模块化编程

[toc]

注:笔记主要参考B站江科大自化协教学视频“51单片机入门教程-2020版 程序全程纯手打 从零开始入门”。
注:工程及代码文件放在了本人的Github仓库。


3.1 模块化编程

传统方式编程:所有的函数均放在main.c里,若使用的模块比较多,则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路。
模块化编程: 把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。

比如下面这张图,将Dealy函数进行了头文件声明、源文件定义,便可以在其他的源文件中调用了。

图3-1 模块化编程示意图

注意事项:

  • .c文件:函数、变量的定义。
  • .h文件:可被外部调用的函数、变量的声明。
  • 任何自定义的变量、函数,在某个.c文件中被调用前必须有定义或声明。
  • 使用到的自定义函数的.c文件必须添加到工程参与编译。
  • 使用到的.h文件必须要放在编译器可寻找到的地方(工程文件夹根目录、安装目录、自定义)。
表3-1 C语言预编译举例
预编译意义
#include <REGX52.H>把REGX52.H文件的内容搬到此处。
< >一般是用在系统自带,“ ”一般是用在自己写的。
#define PI 3.14定义PI,将PI替换为3.14
#define ABC定义ABC
#ifndef __XX_H__ 如果没有定义__XX_H__
#endif与#ifndef,#if匹配,组成“括号”

C语言的预编译以#开头,作用是在真正的编译开始之前,对代码做一些处理(预编译),如上表所示。此外还有#ifdef,#if,#else,#elif,#undef等。这些预编译指令最重要的作用就是避免头文件被多次包含而失效(保证头文件只被包含一次)。而最常用的预编译指令结构如下(如上图3-1演示的那样):

#ifndef MY_HEADER_FILE _H_
#define MY_HEADER_FILE _H_
    //头文件内容
#endif

3.2 LCD1602调试工具

3.2.1 LCD1602液晶屏介绍
1602液晶也叫1602字符型液晶,能显示 16个字符*2行 的信息。它是由若干个 5x7 或者 5x10 的点阵字符位组成,每个点阵字符位都可以用显示一个字符,每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用。正因如此,它不能很好的显示图片,而专门用来显示字母、数字、符号。

使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。本视频提供的LCD1602代码属于模块化的代码,使用者只需要知道所提供函数的作用和使用方法就可以很容易的使用LCD1602。
注:当然也可以通过数码管来进行显示,但是数码管需要不断扫描,占用CPU资源较多所以不推荐。另外也可以通过串口通信的方式进行调试,但是其原理更加复杂且需要外接电脑不是很方便,所以也不推荐。

图3-2 LCD1602接口原理图
  • LCD RS:数据/命令选择端(H/L)。
  • LCD WR:写读选择端(L/H)。
  • LCD EN:使能信号。
  • 在第12部分会详细的介绍其工作原理及相应的代码编写。
表3-2 由UP主提供的LCD相关函数
函数作用
LCD_Init();初始化
LCD_ShowChar(1,1,'A');在指定位置显示一个字符
LCD_ShowString(1,3,"Hello");在指定位置显示字符串
LCD_ShowNum(1,9,123,3);在指定位置显示十进制数字
LCD_ShowSignedNum(1,13,-66,3);在指定位置显示有符号十进制数字
LCD_ShowHexNum(2,1,0xA8,2);在指定位置显示十六进制数字
LCD_ShowBinNum(2,4,0xAA,8);在指定位置显示二进制数字

注:Keil5在创建头文件后,不会自动将其添加进工程中来,但是文件会放在当前目录下,可以右键手动添加,以方便管理。

3.2.2 代码展示
需求:试一试表3-1中所有的功能,然后使用液晶屏观察中间变量的变化。

图3-3 函数调用关系

上图给出了本工程的函数调用关系,下面是代码展示:
- main源文件

#include <REGX52.H>
#include"LCD1602.h"void main(){int a = 0; // 注意若编译器较老,那么不能中途定义变量//  // 注意下面的代码不要放在while(1)里面,否则LCD会闪
//  LCD_Init();
//  LCD_ShowChar(1,1,'A');
//  LCD_ShowString(1,2,"HelloWorld");
//  LCD_ShowNum(1,12,666,3);
//  LCD_ShowSignedNum(1,15,-9,2);	
//  LCD_ShowHexNum(2,1,0xA8,2);
//  LCD_ShowBinNum(2,5,0xAA,8);// 下面是实际想用的场景:展示代码运行结果LCD_Init();while(1){LCD_ShowNum(1,1,a++,8);}
}

- LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);#endif

- LCD1602.c

 #include <REGX52.H>//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0//函数定义:
/*** @brief  LCD1602延时函数,12MHz调用可延时1ms* @param  无* @retval 无*/
void LCD_Delay()
{unsigned char i, j;i = 2;j = 239;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);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');}
}/*** @brief  在LCD1602指定位置开始以有符号十进制显示所给数字* @param  Line 起始行位置,范围:1~2* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:-32768~32767* @param  Length 要显示数字的长度,范围:1~5* @retval 无*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{unsigned char i;unsigned int Number1;LCD_SetCursor(Line,Column);if(Number>=0){LCD_WriteData('+');Number1=Number;}else{LCD_WriteData('-');Number1=-Number;}for(i=Length;i>0;i--){LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');}
}/*** @brief  在LCD1602指定位置开始以十六进制显示所给数字* @param  Line 起始行位置,范围:1~2* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~0xFFFF* @param  Length 要显示数字的长度,范围:1~4* @retval 无*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i,SingleNumber;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){SingleNumber=Number/LCD_Pow(16,i-1)%16;if(SingleNumber<10){LCD_WriteData(SingleNumber+'0');}else{LCD_WriteData(SingleNumber-10+'A');}}
}/*** @brief  在LCD1602指定位置开始以二进制显示所给数字* @param  Line 起始行位置,范围:1~2* @param  Column 起始列位置,范围:1~16* @param  Number 要显示的数字,范围:0~1111 1111 1111 1111* @param  Length 要显示数字的长度,范围:1~16* @retval 无*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{unsigned char i;LCD_SetCursor(Line,Column);for(i=Length;i>0;i--){LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');}
}

http://www.ppmy.cn/news/20664.html

相关文章

IB课程为何号称全球最难国际课程?

在读国际学校的同学们&#xff0c;一定对大名鼎鼎的IB课程不陌生&#xff0c;可是他为什么被称作是它号称最难的国际课程呢&#xff1f;今天就来给大家全面解析一下IB课程&#xff5e; IB课程最开始是IBO为外交官子女开设全球统一标准的课程。IB课程为全球学生开设从幼儿园到大…

Java开发环境配置 “IntelliJ IDEA”(超详细整理,适合新手入门)

前言 &#x1f4dc; “ 作者 久绊A ” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴 目录 前言 一、IDEA的介绍 1、大概介绍 2、详细介绍 二、Intelli…

typedef的作用

typedef的作用分为三种&#xff1a; 1.为基本数据类型定义新的类型名 typedef int myINT;//将基本的数据类型定义为新的类型名myint2.为自定义数据类型&#xff08;结构体&#xff0c;共用体&#xff0c;枚举类型&#xff09;定义简洁类型名 typedef struct Point3d{float x;…

【GIS前沿】什么是新型基础测绘、内容、产品体系、特征?

《测绘法》指出&#xff0c;基础测绘是建立和维护全国统一的测绘基准和测绘系统&#xff0c;进行航天航空影像获取&#xff0c;建立和更新维护基础地理信息数据库&#xff0c;提供测绘地理信息应用服务等。 文章目录一、什么是新型基础测绘&#xff1f;二、新型基础测绘的特征三…

JAVA练习34

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、题目-验证回文串 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 1月29日练习…

【计算机网络】第一章 计算机网络结构

文章目录第一章 体系结构1.1 计算机网络概述1.1.1 计算机网络的概念1.1.2 计算机网络的组成1.1.3 计算机网络的功能1.1.4 计算机网络的分类*1.1.5 计算机网络的标准化工作1.1.6 计算机网络的性能指标1.2 计算机网络体系结构与参考模型1.2.1 计算机网络分层结构1.2.2 计算机网络…

【ZooKeeper】

1.ZooKeeper是什么&#xff1f; 答&#xff1a; ZooKeeper是一个开放源码的分布式协调服务&#xff0c;它是集群的管理者&#xff0c;监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终&#xff0c;将简单易用的接口和性能高效、功能稳定的系统提供给用户…

uniapp和微信小程序的区别

1、页面标签基本相同 view&#xff0c;text、scroll-view&#xff0c;input、picker、swiper等等 2、api基本相同,wx换成uni即可 原生写法&#xff1a;wx.request、wx.showModal、wx.showToast、wx.showLoading、wx.chooseImage、wx.switchTab、wx.navigateo、wx.setStorage…