单片机开发教程4——多文件编程
文章目录
- 前言
- 文件结构
- 创建工程
- 多文件编程
- 头文件
- 源文件
- 全局变量
- 示例代码
- 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文件
创建工程
点Project,再点New uVision Project,新建一个工程
给创建的工程命名,并存到USER文件夹中
选择单片机型号,这里随便选择AT89C52,不重要
直接点否,这个文件没啥用
点Manage Project Items,有两个打开的方式
创建Groups,以文件夹命名,确定
Ctrl+N添加新文件,Ctrl+S命名并保存到各自的路径
上一步保存的文件只是创建在文件夹里了,还没有添加到项目里,下面需要把文件添加到相应Groups
c文件只能include同一文件夹下的h文件,因此必须把各个h文件的路径添加到
Include Paths
,若文件如第一张图,则无需此步骤右击h文件名,可以看到
open document ""
,表示include的h文件路径没问题可以打开最后点击魔法棒,勾选生成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——开发环境的搭建
文章目录 开发环境介绍 Keil安装教程 下载安装包 安装keil 运行注册机 STC-ISP 单片机型号 串口驱动 串口助手 开发环境介绍 51单片机的开发只需要用到两个软件--keil和stc-i ...
- windows驱动开发教程 滴水_滴水编程达人全套
滴水编程达人全套app,专为想学习编程的伙伴们提供的学习软件.在滴水编程达人全套app中你可查看各类关于IT的相关信息,体验一对一即时在线答疑咨询服务,以及在线上课.看教学视频等. 基本简介 滴水编程 ...
- C语言模块化开发,深入多文件编程
目录 文章目录 目录 多文件编程 项目分割 避免命名冲突 项目生成的过程 预处理 编译 汇编 链接 语言发展的过程 机器语言 汇编语言 C语言 高级语言 编译的本质 目标文件里藏着什么 可执行文件 链 ...
- 单片机开发教程3——串口发送MPU6050姿态角
文章目录 1. 简介 1.1 模块原理图 1.2 引脚说明 1.3 接线方式 2. IIC通信 2.1 IIC介绍 2.2 例程讲解 3. 姿态解算 3.1 欧拉角 3.2 解算方法 3.3 一阶互补 ...
- 辉芒微单片机开发教程_辉芒微单片机笔记004:IO寄存器配置,点亮一只LED
刚开始对开发软件环境和仿真器的使用还不算很熟,先点亮一只LED看一下软硬件的操作有没有问题.电子芯片 在点亮LED之前,要弄懂二个寄存器的工作原理: 1.TRISA方向寄存器. 2.PORTA控制寄存 ...
- 单片机开发教程5——51单片机驱动TFT彩屏
文章目录 代码例程 TFT模块 介绍 使用要点 减少刷新像素 坐标系 游戏设计 对象 VS Code 代码例程 TFT.zip 开发资料中的例程有些瑕疵,上面是整理后的例程,修改了一些参数,也添加了不 ...
- 51单片机实战教程(28 人机界面编程五)
2本文将介绍Usart HMI常用指令 1 page指令 用于页面切换,使用格式 : page obj obj指页面名称或id, 示例: 有下面两个页面, 要从page0 切换到main 代码如下: ...
- 51单片机实战教程(32 人机界面编程9)
人机界面(Usart HMI)屏可以轻松设计漂亮友好的人机界面,也可以通过Usart串口与机器CPU通信.但仅有这些是不够的.还需要自定义 通信协议.通信协议包含波特率设置,数据格式,编码解码,数据打 ...
- 视频教程-spring+springMVC+mybatis(ssm框架)在线考试系统实战开发教程-Java
spring+springMVC+mybatis(ssm框架)在线考试系统实战开发教程 软件工程硕士毕业,目前就职于上海电信研究院,有三年Java开发经验,五年PHP开发经验. 李礼强 ¥368.00 ...
最新文章
- (NO.00003)iOS游戏简单的机器人投射游戏成形记(二)
- vsftp的安装或升级
- Latex注释快捷键
- Java实现海明距离简单计算
- python 关闭 os.popen()
- CDS view里inner join, left outer join和association的区别
- 冗余的阿里云实例开启和停止API
- 2017 开源软件排行_2017年最佳开源教程
- linux ext4的块大小,linux – ext3 / ext4物理块大小视图
- 微信小程序不支持打开非业务域名_开达应用五端合一:抖音/头条小程序基础配置...
- 中国专业开发者最多,最受 Web 服务青睐,Java 8 为最受欢迎版本 | 2020 年 Java 开发现状大调查...
- UTF-8 编码里,一个汉字占用多少个字节 -转
- 高性能的服务器的架设
- 行政区划简称(包括别称)
- 非对称加密技术中,iFace人脸密钥技术排名第三
- Go http Server
- 徐波 博士 计算机,徐波医生(博士 广州市第一人民医院主任医师)简介
- 2010.4.24更新 windows 7 x86/x64 应用全面导航(菜鸟老鸟全兼容)
- 帆软报表(FineReport)版本9打开版本10的报表
- php,ajax -->Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>)