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模式)、推挽输出、高阻输入和开漏输出。配置方式通过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)。每个中断可独立配置为下降沿触发或上升沿/下降沿均可触发。

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

  1. 设置触发方式(IT0、IT1寄存器)。
  2. 使能对应中断(EX0、EX1寄存器)。
  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. 装入初值(TH0、TL0寄存器)。
  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.c、gpio.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. 工程文件管理:建议将源文件和头文件分别放在src和inc文件夹中,并在Keil的工程设置中添加头文件的搜索路径。

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