GPIO、外部中断与定时器综合实验

基于STC8H8K64U的多文件系统工程实验指导书

—— GPIO、外部中断与定时器综合实验


一、实验目的

  1. 掌握Keil C51环境下多文件系统工程(头文件 + 源文件)的创建方法。
  2. 掌握STC8H8K64U单片机GPIO口的配置与使用方法。
  3. 掌握外部中断(INT0/INT1)的配置与中断服务函数的编写。
  4. 掌握定时器(Timer0)的配置方法,实现定时中断控制功能。
  5. 学习模块化编程思想,理解头文件中声明与源文件中定义分离的原则。

二、实验器材

序号器材名称规格/型号数量
1开天斧三开发板STC8H8K64U核心板1块
2USB Type-C数据线用于供电和下载1条
3计算机Windows系统,已安装Keil C511台

开天斧三开发板以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模式)、推挽输出、高阻输入和开漏输出。配置方式通过PxM0PxM1两个寄存器组合实现。

开天斧三开发板上,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)。每个中断可独立配置为下降沿触发或上升沿/下降沿均可触发。

外部中断的配置步骤如下:

  1. 设置触发方式(IT0IT1寄存器)。
  2. 使能对应中断(EX0EX1寄存器)。
  3. 开启总中断(EA = 1)。
  4. 编写中断服务函数(使用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,配置步骤包括:

  1. 设置定时器工作模式(TMOD寄存器)。
  2. 设置1T模式(AUXR寄存器)。
  3. 装入初值(TH0TL0寄存器)。
  4. 使能定时器中断(ET0 = 1)。
  5. 开启总中断(EA = 1)。
  6. 启动定时器(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工程

  1. 打开Keil C51软件,点击菜单栏 Project → New μVision Project
  2. 在弹出的对话框中,选择工程文件保存路径,输入工程名称(建议用英文命名,例如STC8H_Demo),点击保存。
  3. 在弹出的芯片选择对话框中,选择 STC → STC8H8K64U(如果列表中没有该芯片,需先安装STC-ISP中提供的Keil仿真驱动)。
  4. 芯片选择完毕后,点击 OK 完成工程创建。

5.2 添加源文件组

  1. 在工程窗口中,右键点击 Target 1,选择 Manage Project Items
  2. 在弹出的对话框中,通过 New (Insert) 按钮添加文件组:Source(源文件组)和 Header(头文件组)。
  3. 确认后关闭对话框。

5.3 创建源文件和头文件

方法一(推荐):先新建文件夹整理项目文件

在工程项目文件夹下创建两个子文件夹:

  • src:存放所有.c源文件
  • inc:存放所有.h头文件

方法二:在Keil中直接创建

  1. 点击工具栏的新建文件按钮(或 File → New),创建一个空白文本文件。
  2. Ctrl+S 保存,选择保存路径到项目文件夹下的对应子文件夹,文件名按功能命名(如main.cgpio.h等),保存类型选择 All Files
  3. 重复以上步骤,创建所有需要的源文件和头文件。
  4. 在工程窗口中右键点击 Source 文件组,选择 Add Existing Files to Group,将所有.c文件添加到工程。
  5. 同样方法将.h头文件添加到 Header 文件组。

5.4 配置工程属性

  1. 点击菜单栏 Project → Options for Target ‘Target 1’(或点击魔术棒图标)。
  2. Target 选项卡中,设置 Xtal (MHz)24.0(IRC频率)。
  3. Output 选项卡中,勾选 Create HEX File,确保编译后生成HEX烧录文件。
  4. C51 选项卡的 Include Paths 中,添加头文件所在文件夹的路径(例如..\inc),使编译器能找到自定义头文件。
  5. 点击 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 编译与下载

  1. 点击Keil工具栏的 Build 按钮(或按 F7)编译整个工程,确认 Build Output 窗口显示 0 Error(s), 0 Warning(s)
  2. 连接USB Type-C数据线到开天斧三开发板。
  3. 打开STC-ISP下载软件,选择芯片型号为 STC8H8K64U,IRC频率设置为 24MHz
  4. 按住开发板上的 P3.2按键,同时按一下 OFF(断电)按键,然后先松开OFF键,再松开P3.2键,使MCU进入USB下载模式。
  5. 在STC-ISP软件中点击 扫描串口,找到对应的USB串口。
  6. 点击 打开程序文件,选择编译生成的.hex文件。
  7. 点击 下载/编程,等待下载完成。

六、预期实验结果

程序烧录完成后,观察开发板上的LED指示灯:

  1. 上电初始状态:P2口所有LED灯熄灭(由于GPIO_Init()中将LED_PORT初始化为0xFF)。
  2. 外部中断0测试:按下开发板上的P3.2按键(对应INT0),LED0(P2.0口)的亮灭状态会翻转一次(由于按键有机械抖动,偶尔可能出现多次翻转)。
  3. 外部中断1测试:按下开发板上的P3.3按键(对应INT1),LED1(P2.1口)的亮灭状态会翻转一次。
  4. 定时器测试:LED3(P2.3口)以1秒为周期自动闪烁(亮500ms,灭500ms),由定时器0中断控制。

七、实验报告要求

  1. 画出项目文件结构图,说明每个文件的功能。
  2. 简述多文件系统工程中头文件和源文件的分工原则。
  3. 分析GPIO口的4种工作模式及其适用场景。
  4. 计算定时器初值,说明1ms定时的推导过程。
  5. 描述外部中断的配置流程,说明INT0和INT1的中断号分别是多少。
  6. 附上完整的程序代码(各模块分别列出)。

八、思考题

  1. 如果要将LED的闪烁周期改为2秒(亮1秒、灭1秒),应如何修改代码?请写出修改后的关键代码。
  2. 当前外部中断未做按键消抖处理,请尝试在中断服务函数中加入简单的软件消抖逻辑。
  3. 如果要在main.c中访问timer.c中定义的count变量以实现其他功能,应该如何修改代码?说明extern关键字的作用。
  4. 请尝试将定时器改为12T模式,重新计算定时初值,并比较与1T模式的区别。

九、多文件编程注意事项总结

  1. 头文件防重复包含:每个.h文件必须使用#ifndef-#define-#endif结构,例如#ifndef __GPIO_H__#define __GPIO_H__
  2. 声明与定义分离:函数声明和全局变量声明(加extern关键字)放在.h头文件中,函数定义和全局变量定义放在.c源文件中。编译器对声明不分配内存空间,对定义才分配内存空间。
  3. 包含对应头文件:每个.c源文件必须包含其对应的.h头文件,以及它调用的其他模块的头文件。
  4. extern关键字使用:声明全局变量时必须加extern关键字,且声明时不能赋初始值,例如extern u8 flag;是正确的,extern u8 flag = 0;是错误的。
  5. 工程文件管理:建议将源文件和头文件分别放在srcinc文件夹中,并在Keil的工程设置中添加头文件的搜索路径。

本站所有文章、数据、图片来源于网络,仅供学习使用,如有侵权,联系删除!