GPIO、外部中断与定时器综合实验
基于STC8H8K64U的多文件系统工程实验指导书
—— GPIO、外部中断与定时器综合实验
一、实验目的
- 掌握Keil C51环境下多文件系统工程(头文件 + 源文件)的创建方法。
- 掌握STC8H8K64U单片机GPIO口的配置与使用方法。
- 掌握外部中断(INT0/INT1)的配置与中断服务函数的编写。
- 掌握定时器(Timer0)的配置方法,实现定时中断控制功能。
- 学习模块化编程思想,理解头文件中声明与源文件中定义分离的原则。
二、实验器材
| 序号 | 器材名称 | 规格/型号 | 数量 |
|---|---|---|---|
| 1 | 开天斧三开发板 | STC8H8K64U核心板 | 1块 |
| 2 | USB Type-C数据线 | 用于供电和下载 | 1条 |
| 3 | 计算机 | Windows系统,已安装Keil C51 | 1台 |
开天斧三开发板以STC8H8K64U为核心芯片,采用LQFP64封装,板上板载P2口8个LED灯(低电平点亮)和P3.2、P3.3两个用户按键,分别对应外部中断INT0和INT1。
三、实验原理
3.1 多文件系统工程的基本结构
在单片机程序开发中,随着程序功能越来越复杂,将所有代码写在一个源文件中会使程序臃肿、难以维护。多文件编程将代码按功能模块划分到不同的C源文件中,并通过H头文件进行声明和引用,能大大提高程序代码的移植率和可读性。
多文件系统的基本规则:
- 头文件(.h):存放函数声明、全局变量声明(需加extern关键字)、宏定义和IO口宏定义,不进行函数定义和变量定义。
- 源文件(.c):存放函数的具体实现和全局变量的定义,同时必须包含对应的头文件。
- 防重复包含:每个头文件必须使用
#ifndef、#define、#endif模板,防止多次包含出错。
3.2 GPIO口原理
STC8H8K64U的每个IO口均可独立配置为4种工作模式之一:准双向口(传统8051模式)、推挽输出、高阻输入和开漏输出。配置方式通过PxM0和PxM1两个寄存器组合实现。
开天斧三开发板上,LED灯通过P2口控制,LED正极接VCC,负极经限流电阻接P2口。因此P2口输出低电平(0)时LED点亮,输出高电平(1)时LED熄灭。
3.3 外部中断原理
STC8H8K64U支持5个外部中断源:INT0(P3.2)、INT1(P3.3)、INT2(P3.6)、INT3(P3.7)、INT4(P3.0)。每个中断可独立配置为下降沿触发或上升沿/下降沿均可触发。
外部中断的配置步骤如下:
- 设置触发方式(
IT0、IT1寄存器)。 - 使能对应中断(
EX0、EX1寄存器)。 - 开启总中断(
EA = 1)。 - 编写中断服务函数(使用
interrupt关键字指定中断号)。
3.4 定时器原理
STC8H8K64U内置5个16位定时器(T0~T4)。定时器本质上是一个计数器,对系统时钟脉冲进行计数,当计数值溢出时产生中断。STC8H系列支持1T模式(每个机器周期为1个时钟周期),比传统8051的12T模式快12倍。
以系统时钟24MHz、1T模式为例,要产生1ms定时中断:
- 1ms内系统时钟计数值 = 24000000 / 1000 = 24000
- 定时器初值 = 65536 - 24000 = 41536
定时器0中断号为1,配置步骤包括:
- 设置定时器工作模式(
TMOD寄存器)。 - 设置1T模式(
AUXR寄存器)。 - 装入初值(
TH0、TL0寄存器)。 - 使能定时器中断(
ET0 = 1)。 - 开启总中断(
EA = 1)。 - 启动定时器(
TR0 = 1)。
四、项目文件结构
实验项目目录/ ├── main.c # 主程序文件(主函数、系统初始化) ├── main.h # 主程序头文件 ├── gpio.c # GPIO初始化与操作函数 ├── gpio.h # GPIO头文件 ├── exti.c # 外部中断初始化与中断服务函数 ├── exti.h # 外部中断头文件 ├── timer.c # 定时器初始化与中断服务函数 ├── timer.h # 定时器头文件 ├── config.h # 系统配置文件(时钟频率、全局宏定义) └── stc8h.h # STC8H系列官方头文件(需从STC-ISP获取)
五、实验步骤
5.1 新建Keil工程
- 打开Keil C51软件,点击菜单栏 Project → New μVision Project。
- 在弹出的对话框中,选择工程文件保存路径,输入工程名称(建议用英文命名,例如
STC8H_Demo),点击保存。 - 在弹出的芯片选择对话框中,选择 STC → STC8H8K64U(如果列表中没有该芯片,需先安装STC-ISP中提供的Keil仿真驱动)。
- 芯片选择完毕后,点击 OK 完成工程创建。
5.2 添加源文件组
- 在工程窗口中,右键点击 Target 1,选择 Manage Project Items。
- 在弹出的对话框中,通过 New (Insert) 按钮添加文件组:
Source(源文件组)和Header(头文件组)。 - 确认后关闭对话框。
5.3 创建源文件和头文件
方法一(推荐):先新建文件夹整理项目文件
在工程项目文件夹下创建两个子文件夹:
src:存放所有.c源文件inc:存放所有.h头文件
方法二:在Keil中直接创建
- 点击工具栏的新建文件按钮(或 File → New),创建一个空白文本文件。
- 按 Ctrl+S 保存,选择保存路径到项目文件夹下的对应子文件夹,文件名按功能命名(如
main.c、gpio.h等),保存类型选择 All Files。 - 重复以上步骤,创建所有需要的源文件和头文件。
- 在工程窗口中右键点击 Source 文件组,选择 Add Existing Files to Group,将所有.c文件添加到工程。
- 同样方法将.h头文件添加到 Header 文件组。
5.4 配置工程属性
- 点击菜单栏 Project → Options for Target ‘Target 1’(或点击魔术棒图标)。
- 在 Target 选项卡中,设置 Xtal (MHz) 为
24.0(IRC频率)。 - 在 Output 选项卡中,勾选 Create HEX File,确保编译后生成HEX烧录文件。
- 在 C51 选项卡的 Include Paths 中,添加头文件所在文件夹的路径(例如
..\inc),使编译器能找到自定义头文件。 - 点击 OK 保存设置。
5.5 编写各模块代码
5.5.1 系统配置文件 config.h
#ifndef __CONFIG_H__
#define __CONFIG_H__
#include "stc8h.h" // STC8H系列官方头文件
/* 系统时钟频率(Hz) */
#define MAIN_Fosc 24000000UL
/* 数据类型重定义 */
typedef unsigned char u8;
typedef unsigned int u16;
typedef unsigned long u32;
#endif /* __CONFIG_H__ */
5.5.2 GPIO模块——gpio.h
#ifndef __GPIO_H__
#define __GPIO_H__
#include "config.h"
/* LED引脚宏定义 —— P2口8个LED,低电平点亮 */
#define LED_PORT P2 // LED数据端口
#define LED0 P20 // P2.0对应LED
#define LED1 P21 // P2.1对应LED
#define LED2 P22 // P2.2对应LED
#define LED3 P23 // P2.3对应LED
/* 函数声明 */
void GPIO_Init(void); // GPIO初始化函数
#endif /* __GPIO_H__ */
5.5.3 GPIO模块——gpio.c
#include "gpio.h"
/**
* 函数名称: GPIO_Init
* 功能描述: 初始化GPIO口模式
* P2口配置为准双向口,用于驱动LED
* 使能扩展寄存器(XFR)访问
*/
void GPIO_Init(void)
{
P_SW2 |= 0x80; // 使能扩展寄存器(XFR)访问
P2M1 = 0x00; // P2口配置为准双向口模式
P2M0 = 0x00;
LED_PORT = 0xFF; // 初始状态:所有LED熄灭(高电平)
}
5.5.4 外部中断模块——exti.h
#ifndef __EXTI_H__
#define __EXTI_H__
#include "config.h"
/* 按键引脚宏定义 */
#define KEY_INT0 P32 // P3.2 外部中断0按键
#define KEY_INT1 P33 // P3.3 外部中断1按键
/* 函数声明 */
void EXTI_Init(void); // 外部中断初始化函数
#endif /* __EXTI_H__ */
5.5.5 外部中断模块——exti.c
#include "exti.h"
#include "gpio.h"
/**
* 函数名称: EXTI_Init
* 功能描述: 初始化外部中断INT0和INT1
* INT0: P3.2,下降沿触发
* INT1: P3.3,下降沿触发
*/
void EXTI_Init(void)
{
IT0 = 1; // INT0下降沿触发
IT1 = 1; // INT1下降沿触发
EX0 = 1; // 使能INT0中断
EX1 = 1; // 使能INT1中断
EA = 1; // 开启总中断
}
/**
* 函数名称: INT0_ISR
* 功能描述: 外部中断0服务函数(中断号0)
* 每次按键触发,翻转LED0状态
*/
void INT0_ISR(void) interrupt 0
{
LED0 = ~LED0; // 翻转LED0(P2.0)状态
}
/**
* 函数名称: INT1_ISR
* 功能描述: 外部中断1服务函数(中断号2)
* 每次按键触发,翻转LED1状态
*/
void INT1_ISR(void) interrupt 2
{
LED1 = ~LED1; // 翻转LED1(P2.1)状态
}
5.5.6 定时器模块——timer.h
#ifndef __TIMER_H__
#define __TIMER_H__
#include "config.h"
/* 函数声明 */
void Timer0_Init(void); // 定时器0初始化函数
#endif /* __TIMER_H__ */
5.5.7 定时器模块——timer.c
#include "timer.h"
#include "gpio.h"
/**
* 函数名称: Timer0_Init
* 功能描述: 初始化定时器0,配置1ms定时中断
* 系统时钟:24MHz,1T模式
* 计算公式:初值 = 65536 - (MAIN_Fosc / 1000)
*/
void Timer0_Init(void)
{
AUXR |= 0x80; // 定时器0设置为1T模式
TMOD &= 0xF0; // 清除T0模式位
TMOD |= 0x00; // T0工作模式0:16位自动重装载
/* 装入1ms定时的初值 */
TL0 = (65536 - (MAIN_Fosc / 1000)); // 低8位
TH0 = (65536 - (MAIN_Fosc / 1000)) >> 8; // 高8位
TF0 = 0; // 清除溢出标志
ET0 = 1; // 使能定时器0中断
EA = 1; // 开启总中断
TR0 = 1; // 启动定时器0
}
/**
* 函数名称: Timer0_ISR
* 功能描述: 定时器0中断服务函数(中断号1)
* 每1ms触发一次,累计500次(500ms)后翻转LED3
*/
void Timer0_ISR(void) interrupt 1
{
static u16 count = 0; // 静态变量,记录中断次数
count++;
if(count >= 500) // 500ms到达
{
count = 0; // 清零计数器
LED3 = ~LED3; // 翻转LED3(P2.3)状态
}
}
5.5.8 主程序模块——main.h
#ifndef __MAIN_H__
#define __MAIN_H__
#include "config.h"
/* 函数声明 */
void System_Init(void); // 系统总初始化函数
#endif /* __MAIN_H__ */
5.5.9 主程序模块——main.c
#include "main.h"
#include "gpio.h"
#include "exti.h"
#include "timer.h"
/**
* 函数名称: System_Init
* 功能描述: 系统总初始化,调用各模块的初始化函数
*/
void System_Init(void)
{
GPIO_Init(); // GPIO初始化
EXTI_Init(); // 外部中断初始化
Timer0_Init(); // 定时器0初始化
}
/**
* 函数名称: main
* 功能描述: 主函数——程序入口
*
* 实验现象说明:
* 1. 系统上电后,LED0~LED3全部熄灭。
* 2. 按P3.2按键(INT0),触发外部中断0,LED0状态翻转(亮/灭切换)。
* 3. 按P3.3按键(INT1),触发外部中断1,LED1状态翻转(亮/灭切换)。
* 4. LED3由定时器0控制,以1秒为周期自动闪烁(500ms亮、500ms灭)。
*/
void main(void)
{
System_Init(); // 系统初始化
while(1)
{
/* 主循环空闲,所有功能由中断驱动 */
}
}
5.6 编译与下载
- 点击Keil工具栏的 Build 按钮(或按 F7)编译整个工程,确认 Build Output 窗口显示
0 Error(s), 0 Warning(s)。 - 连接USB Type-C数据线到开天斧三开发板。
- 打开STC-ISP下载软件,选择芯片型号为 STC8H8K64U,IRC频率设置为 24MHz。
- 按住开发板上的 P3.2按键,同时按一下 OFF(断电)按键,然后先松开OFF键,再松开P3.2键,使MCU进入USB下载模式。
- 在STC-ISP软件中点击 扫描串口,找到对应的USB串口。
- 点击 打开程序文件,选择编译生成的
.hex文件。 - 点击 下载/编程,等待下载完成。
六、预期实验结果
程序烧录完成后,观察开发板上的LED指示灯:
- 上电初始状态:P2口所有LED灯熄灭(由于
GPIO_Init()中将LED_PORT初始化为0xFF)。 - 外部中断0测试:按下开发板上的P3.2按键(对应INT0),LED0(P2.0口)的亮灭状态会翻转一次(由于按键有机械抖动,偶尔可能出现多次翻转)。
- 外部中断1测试:按下开发板上的P3.3按键(对应INT1),LED1(P2.1口)的亮灭状态会翻转一次。
- 定时器测试:LED3(P2.3口)以1秒为周期自动闪烁(亮500ms,灭500ms),由定时器0中断控制。
七、实验报告要求
- 画出项目文件结构图,说明每个文件的功能。
- 简述多文件系统工程中头文件和源文件的分工原则。
- 分析GPIO口的4种工作模式及其适用场景。
- 计算定时器初值,说明1ms定时的推导过程。
- 描述外部中断的配置流程,说明INT0和INT1的中断号分别是多少。
- 附上完整的程序代码(各模块分别列出)。
八、思考题
- 如果要将LED的闪烁周期改为2秒(亮1秒、灭1秒),应如何修改代码?请写出修改后的关键代码。
- 当前外部中断未做按键消抖处理,请尝试在中断服务函数中加入简单的软件消抖逻辑。
- 如果要在main.c中访问
timer.c中定义的count变量以实现其他功能,应该如何修改代码?说明extern关键字的作用。 - 请尝试将定时器改为12T模式,重新计算定时初值,并比较与1T模式的区别。
九、多文件编程注意事项总结
- 头文件防重复包含:每个
.h文件必须使用#ifndef-#define-#endif结构,例如#ifndef __GPIO_H__和#define __GPIO_H__。 - 声明与定义分离:函数声明和全局变量声明(加
extern关键字)放在.h头文件中,函数定义和全局变量定义放在.c源文件中。编译器对声明不分配内存空间,对定义才分配内存空间。 - 包含对应头文件:每个
.c源文件必须包含其对应的.h头文件,以及它调用的其他模块的头文件。 - extern关键字使用:声明全局变量时必须加
extern关键字,且声明时不能赋初始值,例如extern u8 flag;是正确的,extern u8 flag = 0;是错误的。 - 工程文件管理:建议将源文件和头文件分别放在
src和inc文件夹中,并在Keil的工程设置中添加头文件的搜索路径。
本站所有文章、数据、图片来源于网络,仅供学习使用,如有侵权,联系删除!