文章目录

  • 前言
  • 文件结构
  • 创建工程
  • 多文件编程
    • 头文件
    • 源文件
    • 全局变量
  • 示例代码
    • main.c
    • sys.c
    • sys.hh
    • iic.c
    • iic.h
    • mpu6050.c
    • mpu6050.h
    • uart.c
    • uart.h
  • 编译设置

前言

什么是多文件编程?这里要讲到工程项目的概念,项目往往可以分成硬件层和应用层,硬件层包含项目各个硬件的基础接口,而应用层则是在这众多硬件之间牵线搭桥,完成用户与硬件之间的交互。如果你只是写个点灯的程序,那大可不必谈及什么工程项目,但是如果你想要DIY一个小项目,那么就很有必要了解一下,因为这是一个很好的练手机会。

文件结构

下面的图片是一个MPU6050的项目,项目被细分成多个部分,各个部分又单独有.c文件和.h文件,从而大大提高了程序代码移植率,避免程序看起来臃肿复杂

对于硬件不多的情况,所有文件散乱在一起也无伤大雅,但如果项目有几十甚至上百个文件时,这样就不适合使用和管理了,尤其在多人维护代码时,这样会严重拉低效率,我们还需要一个框架,可以把文件分门别类地存放到各个文件夹,方便区别和查找。下面以一个常用的文件框架为例:

首先,我们创建一个工程文件夹,在该文件夹内建立3个文件夹,分别是HARDWARE、SYSTEM和USER

HARDWARE :用于存放各种外设模块
SYSTEM:用于定义引脚以及一些系统程序
USER:用于存放工程文件和main.c文件

创建工程

  1. 点Project,再点New uVision Project,新建一个工程

  2. 给创建的工程命名,并存到USER文件夹中

  3. 选择单片机型号,这里随便选择AT89C52,不重要

  4. 直接点否,这个文件没啥用

  5. 点Manage Project Items,有两个打开的方式

  6. 创建Groups,以文件夹命名,确定

  7. Ctrl+N添加新文件,Ctrl+S命名并保存到各自的路径

  8. 上一步保存的文件只是创建在文件夹里了,还没有添加到项目里,下面需要把文件添加到相应Groups

  9. c文件只能include同一文件夹下的h文件,因此必须把各个h文件的路径添加到 Include Paths,若文件如第一张图,则无需此步骤

  10. 右击h文件名,可以看到 open document "" ,表示include的h文件路径没问题可以打开

  11. 最后点击魔法棒,勾选生成HEX文件

多文件编程

头文件

头文件的写法非常讲究,它是变量和函数声明的地方,每个声明相当于一条“链接”,其他文件想要使用到某个变量或函数时,只需包含对应头文件,就能通过对应“链接”实现访问了

感兴趣的可以学习模仿官方的一些头文件,只需右击include所在的行,就可以打开它的头文件

源文件

对于源文件(.c),主要用来定义变量和函数,值得注意的是,main函数只能写在一个文件,一般选择main.c

这里和头文件的关系在于,源文件主要负责定义,而头文件只能作声明(不包括宏定义)。那么,定义和声明有什么区别呢?

  • 定义
    这是一个定义:int i = 0;
    当然,如果定义的是全局变量/静态变量,默认赋值0也算是定义了

  • 声明
    这是一个声明:extern int i;
    是不是区别不大?但是如果声明写成定义,往往会出现重定义的错误
    对于函数的声明:void delay(unsigned i);
    同样的,函数定义的内容也被省去了

全局变量

说到全局变量,有必要再复习一下变量的分类,变量可以分为全局变量、局部变量、静态全局变量和静态局部变量

  • 全局变量
    也叫外部变量,定义在函数外,可以被定义位置之后的函数访问,可以跨文件访问,外部文件只需使用extern声明变量即可

  • 局部变量
    定义在函数内,只能在函数内被使用,跳出函数后,变量内存被释放(一次性变量)

  • 静态全局变量
    在全局变量前加上关键字static,变量只能作用于定义的文件,不能被跨文件访问(限制作用域)

  • 静态局部变量
    在局部变量前加上关键字static,变量还是只能在函数内使用,但是存储空间不同,变量不会在跳出函数后被释放了(延长保质期)

示例代码

以开发资料的MPU6050例程为例,还添加了上一节教程的内容

main.c

#include "sys.h"void main()
{while(1){unsigned int time;    // 保存定时器计数值float halfT; // 每次采样的时间int Ax,Ay,Az,Gx,Gy,Gz;    // 加速度计和陀螺仪的原始数据 unsigned long Gravity;float AngleX1,AngleY1,AngleZ1,AngleX2,AngleY2,AngleZ2=0,dx,dy,dz;float Filter;Filter=0.8; //互补滤波系数delay(500);      //上电延时init_uart();InitMPU6050();    初始化MPU6050delay(150);while(1){Ax=GetData(ACCEL_XOUT_H);Ay=GetData(ACCEL_YOUT_H);Az=GetData(ACCEL_ZOUT_H);Gx=GetData(GYRO_XOUT_H);Gy=GetData(GYRO_YOUT_H);Gz=GetData(GYRO_ZOUT_H);TR0 = 0;time = (TH0<<8)|TL0;halfT = (time/1000000.)*(12/11.0592);// Display10BitData((int)(halfT*1000000)); //显示采样时间TH0 = 0;TL0 = 0;TR0 = 1;Gravity=sqrt((float)Ax*Ax+(float)Ay*Ay+(float)Az*Az);  //Ax*Ax+Ay*Ay+Az*AzAngleX1=acos((float)Ax/Gravity)*180.0/3.14-90;;AngleY1=acos((float)Ay/Gravity)*180.0/3.14-90;AngleZ1=acos((float)Az/Gravity)*180.0/3.14;dy=halfT*Gx/-16.4; //陀螺仪测的转角ydx=halfT*Gy/16.4; //陀螺仪测的转角xdz=halfT*Gz/16.4; //陀螺仪测的转角z//x和y轴数据是融合加速度计和陀螺仪数据, z轴只采用陀螺仪数据AngleX2=Filter*(AngleX2+dx)+(1-Filter)*AngleX1;        AngleY2=Filter*(AngleY2+dy)+(1-Filter)*AngleY1;    // z轴数据有两种方式,一种是只使用陀螺仪的数据,舍弃z轴加速度(z轴零飘严重):AngleZ2=AngleZ2+dz;        // 注意MPU6050必须芯片正面朝上SeriPushSend(0x20);SeriPushSend('X'); SeriPushSend(':');Display10BitData((int)AngleX2);     //显示X轴角度SeriPushSend(0x20);SeriPushSend('Y'); SeriPushSend(':');Display10BitData((int)AngleY2);     //显示Y轴角度SeriPushSend(0x20);SeriPushSend('Z'); SeriPushSend(':');Display10BitData((int)AngleZ2);     //显示Z轴角度SeriPushSend(0x0d); SeriPushSend(0x0a);//换行,回车delay(500);    // 控制采样频率}}
}

sys.c

#include "sys.h"//*************************************************************************************************
//************************************延时*********************************************************
//*************************************************************************************************
void delay(unsigned int k)
{                       unsigned int i,j;               for(i=0;i<k;i++){         for(j=0;j<121;j++);}
}
//************************************************************************************************
//延时5微秒(STC90C52RC@12M)
//不同的工作环境,需要调整此函数
//注意当改用1T的MCU时,请调整此延时函数
//************************************************************************************************
void Delay5us()
{_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();
}

sys.hh

#ifndef __SYS_H_
#define __SYS_H_typedef unsigned char  uchar;
typedef unsigned short ushort;
typedef unsigned int   uint;//****************************************
#include <REG52.H>
#include <math.h>    //Keil library
#include <stdio.h>   //Keil library
#include <INTRINS.H>#include "iic.h"
#include "mpu6050.h"
#include "uart.h"//**************************************************************************************************
//函数声明
//**************************************************************************************************
void  Delay5us();
void  delay(unsigned int k);                                        //延时#endif

iic.c

#include "iic.h"//****************************************
// 定义51单片机端口
//****************************************
sbit    SCL=P1^5;          //IIC时钟引脚定义
sbit    SDA=P1^4;          //IIC数据引脚定义
//****************************************//*************************************************************************************************
//I2C起始信号
//*************************************************************************************************
void I2C_Start()
{SDA = 1;                    //拉高数据线SCL = 1;                    //拉高时钟线Delay5us();                 //延时SDA = 0;                    //产生下降沿Delay5us();                 //延时SCL = 0;                    //拉低时钟线
}
//*************************************************************************************************
//I2C停止信号
//*************************************************************************************************
void I2C_Stop()
{SDA = 0;                    //拉低数据线SCL = 1;                    //拉高时钟线Delay5us();                 //延时SDA = 1;                    //产生上升沿Delay5us();                 //延时
}
//**************************************************************************************************
//I2C发送应答信号
//入口参数:ack (0:ACK 1:NAK)
//**************************************************************************************************
void I2C_SendACK(bit ack)
{SDA = ack;                  //写应答信号SCL = 1;                    //拉高时钟线Delay5us();                 //延时SCL = 0;                    //拉低时钟线Delay5us();                 //延时
}
//****************************************************************************************************
//I2C接收应答信号
//****************************************************************************************************
bit I2C_RecvACK()
{SCL = 1;                    //拉高时钟线Delay5us();                 //延时CY = SDA;                   //读应答信号SCL = 0;                    //拉低时钟线Delay5us();                 //延时return CY;
}
//*****************************************************************************************************
//向I2C总线发送一个字节数据
//*****************************************************************************************************
void I2C_SendByte(uchar dat)
{uchar i;for (i=0; i<8; i++)         //8位计数器{dat <<= 1;              //移出数据的最高位SDA = CY;               //送数据口SCL = 1;                //拉高时钟线Delay5us();             //延时SCL = 0;                //拉低时钟线Delay5us();             //延时}I2C_RecvACK();
}
//*****************************************************************************************************
//从I2C总线接收一个字节数据
//******************************************************************************************************
uchar I2C_RecvByte()
{uchar i;uchar dat = 0;SDA = 1;                    //使能内部上拉,准备读取数据,for (i=0; i<8; i++)         //8位计数器{dat <<= 1;SCL = 1;                //拉高时钟线Delay5us();             //延时dat |= SDA;             //读数据               SCL = 0;                //拉低时钟线Delay5us();             //延时}return dat;
}
//*****************************************************************************************************
//向I2C设备写入一个字节数据
//*****************************************************************************************************
void Single_WriteI2C(uchar REG_Address,uchar REG_data)
{I2C_Start();                  //起始信号I2C_SendByte(SlaveAddress);   //发送设备地址+写信号I2C_SendByte(REG_Address);    //内部寄存器地址,I2C_SendByte(REG_data);       //内部寄存器数据,I2C_Stop();                   //发送停止信号
}
//*******************************************************************************************************
//从I2C设备读取一个字节数据
//*******************************************************************************************************
uchar Single_ReadI2C(uchar REG_Address)
{uchar REG_data;I2C_Start();                   //起始信号I2C_SendByte(SlaveAddress);    //发送设备地址+写信号I2C_SendByte(REG_Address);     //发送存储单元地址,从0开始  I2C_Start();                   //起始信号I2C_SendByte(SlaveAddress+1);  //发送设备地址+读信号REG_data=I2C_RecvByte();       //读出寄存器数据I2C_SendACK(1);                //接收应答信号I2C_Stop();                    //停止信号return REG_data;
}

iic.h

#ifndef __IIC_H_
#define __IIC_H_#include "sys.h"void I2C_Start();
void I2C_Stop();
void I2C_SendACK(bit ack);
bit I2C_RecvACK();
void I2C_SendByte(uchar dat);
uchar I2C_RecvByte();
void Single_WriteI2C(uchar REG_Address,uchar REG_data);
uchar Single_ReadI2C(uchar REG_Address);#endif

mpu6050.c

#include "mpu6050.h"//******************************************************************************************************
//初始化MPU6050
//******************************************************************************************************
void InitMPU6050()
{Single_WriteI2C(PWR_MGMT_1, 0x00); //解除休眠状态Single_WriteI2C(SMPLRT_DIV, 0x07);Single_WriteI2C(CONFIG, 0x06);Single_WriteI2C(GYRO_CONFIG, 0x18);Single_WriteI2C(ACCEL_CONFIG, 0x01);
}
//******************************************************************************************************
//合成数据
//******************************************************************************************************
int GetData(uchar REG_Address)
{uchar H,L;H=Single_ReadI2C(REG_Address);L=Single_ReadI2C(REG_Address+1);return ((H<<8)+L);   //合成数据
}

mpu6050.h

#ifndef __MPU6050_H_
#define __MPU6050_H_#include "sys.h"//****************************************
// 定义MPU6050内部地址
//****************************************
#define SMPLRT_DIV      0x19    //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG          0x1A    //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG     0x1B    //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define ACCEL_CONFIG    0x1C    //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#define ACCEL_XOUT_H    0x3B
#define ACCEL_XOUT_L    0x3C
#define ACCEL_YOUT_H    0x3D
#define ACCEL_YOUT_L    0x3E
#define ACCEL_ZOUT_H    0x3F
#define ACCEL_ZOUT_L    0x40
#define TEMP_OUT_H      0x41
#define TEMP_OUT_L      0x42
#define GYRO_XOUT_H     0x43
#define GYRO_XOUT_L     0x44
#define GYRO_YOUT_H     0x45
#define GYRO_YOUT_L     0x46
#define GYRO_ZOUT_H     0x47
#define GYRO_ZOUT_L     0x48
#define PWR_MGMT_1      0x6B    //电源管理,典型值:0x00(正常启用)
#define WHO_AM_I        0x75    //IIC地址寄存器(默认数值0x68,只读)
#define SlaveAddress    0xD0    //IIC写入时的地址字节数据,+1为读取
//**************************************************************************************************void  InitMPU6050();
int GetData(uchar REG_Address);#endif

uart.c

#include "uart.h"//**************************************************************************************************
//定义类型及变量
//**************************************************************************************************
uchar dis[6];                   //显示数字(-511至512)的字符数组
int dis_data;                   //变量
//**************************************************************************************************//********************************************************************************
//整数转字符串
//********************************************************************************
void lcd_printf(uchar *s,int temp_data)
{if(temp_data<0){temp_data=-temp_data;*s='-';}else *s=' ';*++s =temp_data/10000+0x30;temp_data=temp_data%10000;     //取余运算*++s =temp_data/1000+0x30;temp_data=temp_data%1000;     //取余运算*++s =temp_data/100+0x30;temp_data=temp_data%100;     //取余运算*++s =temp_data/10+0x30;temp_data=temp_data%10;      //取余运算*++s =temp_data+0x30;
}
//******************************************************************************************************
//串口初始化
//*******************************************************************************************************
void init_uart()
{TMOD=0x21;                TH1=0xfd;      //实现波特率9600(系统时钟11.0592MHZ)       TL1=0xfd;      SCON=0x50;PS=1;      //串口中断设为高优先级别//TR0=1;      //启动定时器          TR1=1;//ET0=1;     //打开定时器0中断         ES=1;  EA=1;
}
//*************************************************************************************************
//串口发送函数
//*************************************************************************************************
void  SeriPushSend(uchar send_data)
{SBUF=send_data;  while(!TI);TI=0;
}
//******************************************************************************************************
//超级终端(串口调试助手)上显示10位数据
//******************************************************************************************************
void Display10BitData(int value)
{  uchar i;
//  value/=64;                         //转换为10位数据lcd_printf(dis, value);           //转换数据显示for(i=0;i<6;i++){SeriPushSend(dis[i]);}//     DisplayListChar(x,y,dis,4); //启始列,行,显示数组,显示长度
}

uart.h

#ifndef __UART_H_
#define __UART_H_#include "sys.h"void lcd_printf(uchar *s,int temp_data);
void init_uart();
void  SeriPushSend(uchar send_data);
void Display10BitData(int value);#endif

编译设置

单片机开发教程4——多文件编程相关推荐

  1. 单片机开发教程1——开发环境的搭建

    文章目录 开发环境介绍 Keil安装教程 下载安装包 安装keil 运行注册机 STC-ISP 单片机型号 串口驱动 串口助手 开发环境介绍 51单片机的开发只需要用到两个软件--keil和stc-i ...

  2. windows驱动开发教程 滴水_滴水编程达人全套

    滴水编程达人全套app,专为想学习编程的伙伴们提供的学习软件.在滴水编程达人全套app中你可查看各类关于IT的相关信息,体验一对一即时在线答疑咨询服务,以及在线上课.看教学视频等. 基本简介 滴水编程 ...

  3. C语言模块化开发,深入多文件编程

    目录 文章目录 目录 多文件编程 项目分割 避免命名冲突 项目生成的过程 预处理 编译 汇编 链接 语言发展的过程 机器语言 汇编语言 C语言 高级语言 编译的本质 目标文件里藏着什么 可执行文件 链 ...

  4. 单片机开发教程3——串口发送MPU6050姿态角

    文章目录 1. 简介 1.1 模块原理图 1.2 引脚说明 1.3 接线方式 2. IIC通信 2.1 IIC介绍 2.2 例程讲解 3. 姿态解算 3.1 欧拉角 3.2 解算方法 3.3 一阶互补 ...

  5. 辉芒微单片机开发教程_辉芒微单片机笔记004:IO寄存器配置,点亮一只LED

    刚开始对开发软件环境和仿真器的使用还不算很熟,先点亮一只LED看一下软硬件的操作有没有问题.电子芯片 在点亮LED之前,要弄懂二个寄存器的工作原理: 1.TRISA方向寄存器. 2.PORTA控制寄存 ...

  6. 单片机开发教程5——51单片机驱动TFT彩屏

    文章目录 代码例程 TFT模块 介绍 使用要点 减少刷新像素 坐标系 游戏设计 对象 VS Code 代码例程 TFT.zip 开发资料中的例程有些瑕疵,上面是整理后的例程,修改了一些参数,也添加了不 ...

  7. 51单片机实战教程(28 人机界面编程五)

    2本文将介绍Usart HMI常用指令 1 page指令 用于页面切换,使用格式 : page obj obj指页面名称或id, 示例: 有下面两个页面, 要从page0 切换到main 代码如下: ...

  8. 51单片机实战教程(32 人机界面编程9)

    人机界面(Usart HMI)屏可以轻松设计漂亮友好的人机界面,也可以通过Usart串口与机器CPU通信.但仅有这些是不够的.还需要自定义 通信协议.通信协议包含波特率设置,数据格式,编码解码,数据打 ...

  9. 视频教程-spring+springMVC+mybatis(ssm框架)在线考试系统实战开发教程-Java

    spring+springMVC+mybatis(ssm框架)在线考试系统实战开发教程 软件工程硕士毕业,目前就职于上海电信研究院,有三年Java开发经验,五年PHP开发经验. 李礼强 ¥368.00 ...

最新文章

  1. (NO.00003)iOS游戏简单的机器人投射游戏成形记(二)
  2. vsftp的安装或升级
  3. Latex注释快捷键
  4. Java实现海明距离简单计算
  5. python 关闭 os.popen()
  6. CDS view里inner join, left outer join和association的区别
  7. 冗余的阿里云实例开启和停止API
  8. 2017 开源软件排行_2017年最佳开源教程
  9. linux ext4的块大小,linux – ext3 / ext4物理块大小视图
  10. 微信小程序不支持打开非业务域名_开达应用五端合一:抖音/头条小程序基础配置...
  11. 中国专业开发者最多,最受 Web 服务青睐,Java 8 为最受欢迎版本 | 2020 年 Java 开发现状大调查...
  12. UTF-8 编码里,一个汉字占用多少个字节 -转
  13. 高性能的服务器的架设
  14. 行政区划简称(包括别称)
  15. 非对称加密技术中,iFace人脸密钥技术排名第三
  16. Go http Server
  17. 徐波 博士 计算机,徐波医生(博士 广州市第一人民医院主任医师)简介
  18. 2010.4.24更新 windows 7 x86/x64 应用全面导航(菜鸟老鸟全兼容)
  19. 帆软报表(FineReport)版本9打开版本10的报表
  20. php,ajax -->Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>)

热门文章

  1. Visual Studio 2017 Intro
  2. 基本数据结构--字典和常用内置序列函数
  3. bluehost虚拟主机有什么用?适合做什么网站呢?
  4. SQL 多表联合查询
  5. matplotlib之绘制散点图
  6. 线性规划之二 —— 单纯形算法(详解)
  7. 微信小程序开发需要什么前提条件?
  8. 应用层——HTTP协议
  9. Qt 信号与槽基础操作
  10. 1.HTTP网络编程