STM32HAL库--NVIC和EXTI

news2024/11/19 5:53:30

1. 外部中断实验

1.1 NVIC和EXTI简介

1.1.1 NVIC简介

        NVIC 即嵌套向量中断控制器,全称 Nested vectored interrupt controller。是ARM Cortex-M处理器中用于管理中断的重要组件。负责处理中断请求,分配优先级,并协调中断的触发和响应。
它是内核的器件,所以它的更多描述可以看内核有关的资料。

        M3/M4/M7 内核都是支持 256 个中断,其中包含了 16 个系统中断240 个外部中断,并且具有 256 级的可编程中断设置。芯片厂商一般不会把内核的这些资源全部用完,如 STM32F429 的系统中断有 10 个,外部中断有 86 个。

中断的宏定义可以在stm32fxxx.h里面找,中断向量表在startup_stm32fxxxx.s启动文件里面找

 1.1.2 NVIC寄存器

        NVIC 相关的寄存器定义了可以在 core_cm4.h 文件中找到。

typedef struct
{
     __IO uint32_t ISER[8U]; /* 中断使能寄存器 */
     uint32_t RESERVED0[24U];
     __IO uint32_t ICER[8U]; /* 中断除能寄存器 */
     uint32_t RSERVED1[24U];
     __IO uint32_t ISPR[8U]; /* 中断使能挂起寄存器 */
     uint32_t RESERVED2[24U];
     __IO uint32_t ICPR[8U]; /* 中断解挂寄存器 */
     uint32_t RESERVED3[24U];
     __IO uint32_t IABR[8U]; /* 中断有效位寄存器 */
     uint32_t RESERVED4[56U];
     __IOM uint8_t IP[240U]; /* 中断优先级寄存器(8Bit 位宽) */
     uint32_t RESERVED5[644U];
     __O uint32_t STIR; /* 中断触发中断寄存器 */
} NVIC_Type;

         STM32F429 的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才
能方便的使用 STM32F429 的中断。下面重点介绍这几个寄存器:

        ISER[8]:ISER 全称是:Interrupt Set Enable Registers,这是一个中断使能寄存器组,包含8个32位寄存器,每个位控制一个中断。但是STM32F429 的可屏蔽中断最多只有 86 个,所以只用到前4个(ISER[0~3]),总共可以表示 128 个中断。要使能某个中断,必须设置相应的ISER位为1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置)。具体每一位对应哪个中断,请参考 STM32F429xx.h文件。

        ICER[8]:全称是:Interrupt Clear Enable Registers,是一个中断除能寄存器组。该寄存器组与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ICER 一样。这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。

        ISPR[8]:全称是:Interrupt Set Pending Registers,是一个中断挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。

        ICPR[8]:全称是:Interrupt Clear Pending Registers,是一个中断解挂控制寄存器组。其作
用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断解挂。写 0 无效。

         IABR[8]:全称是:Interrupt Active Bit Registers,是一个中断激活标志位寄存器组。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。

        IPR[240]:全称是:Interrupt Priority Registers,是一个中断优先级控制的寄存器组IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而STM32F429只用到了其中的 86个。IP[85]~IP[0]分别对应中断 85~0。而每个可屏蔽中断占用的8bit 并没有全部使用,而是只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。

NVIC寄存器总结

        NVIC,嵌套向量中断控制器,ARM Cortex-M内核的组件,

用来中断使能挂起记录中断激活标志位配置中断优先级

        分别对应寄存器组有

        中断使能和除能  ISER[8]  ICER[8]                   8个*32位寄存器

        中断挂起和解挂  ISPR[8]  ICPR[8]

        记录中断激活标志位 IABR[8]                                   Interrupt Active Bit Registers

        配置中断优先级        IP[240]               240*8位寄存器,仅用高4位,放抢占优先级和子优先级

        SCB->AIRCR的中断分组设置决定抢占优先级和子优先级占几位      

1.1.3 中断优先级

        STM32 中的中断优先级可以分为:抢占式优先级和响应优先级,响应优先级也称子优先
级,每个中断源都需要被指定这两种优先级。

        抢占优先级:抢占优先级高的中断 可以 打断正在执行的抢占优先级低的中断。
        
响应优先级:抢占优先级相同,响应优先级高的中断 不能 打断响应优先级低的中断。但可以按响应优先级顺序先后执行。

        当多个中断的抢占式优先级和响应优先级相同时,那么就遵循自然优先级,看中断向量表的中断排序,数值越小,优先级越高。

        在NVIC中有 IPR[60] 控制中断优先级。每个 IPR 寄存器每8位又分为一组,可以分4组,所以就有了 240 组宽度为 8bit 的中断优先级控制寄存器,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。但是实际上 M3 /M4 /M7 芯片为了精简设计,只使用了高四位[7:4],低四位取零,这样以至于最多只有 16 级中断嵌套,即 2^4=16。

正点原子开发指南V1.0该章节所说的 IP[240]和 IPR0~IPR59 其实是一个东西,指的是中断优先级控制的寄存器组,60个32位 IPR 寄存器,8位一组,只使用高4位,由SCB->AIRCR设置抢占优先级和响应优先级分别占几位

1.1.4 NVIC相关函数

        ST 公司把 core_cm4.h 文件的 NVIC 相关函数封装到 stm32f4xx_hal_cortex.c 文件中。

1.1.4.1 中断优先级分组

        首先要讲解的是中断优先级分组函数 NVIC_SetPriorityGrouping,其声明如下:

    void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
    函数形参:
    NVIC_PRIORITYGROUP_0 到 NVIC_PRIORITYGROUP_4(共5组)

        这个函数唯一目的就是通过设置 SCB->AIRCR 寄存器来设置中断优先级分组。

1.1.4.2 中断优先级设置

        中断优先级设置函数 NVIC_SetPriority,其声明如下:

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, 
                          uint32_t SubPriority);
    函数形参:
    形参1  中断号, IRQn_Type 定义的枚举类型,定义在 stm32f429xx.h
    形参2  抢占优先级
    形参3  子优先级

        用于设置中断的抢占优先级和子优先级

1.1.4.3 中断使能

        中断使能函数 NVIC_EnableIRQ,其声明如下:

    void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
1.1.4.4 中断除能

        中断除能函数 NVIC_DisableIRQ,其声明如下:

    void HAL_NVIC_disableIRQ(IRQn_Type IRQn);
1.1.4.5 系统复位

        系统复位函数 HAL_NVIC_SystemReset 函数,其声明如下:

    void HAL_NVIC_SystemReset(void);

//SCB(System Control Block)寄存器中的特定位,例如SCB->AIRCR寄存器的位[2]:SYSRESETREQ。
//通过将这个位设置为1,可以触发系统的软件复位(Software System Reset)。

        用于软件复位系统。

1.1.5 EXTI简介

        EXTI 即是外部中断和事件控制器,它由三个部分组成:APB 接口访问的寄存器模块事件输入触发模块屏蔽模块。其中寄存器块包含了所有 EXTI 寄存器,事件输入触发模块提供事件输入边沿触发逻辑,屏蔽模块为不同的唤醒·、中断和事件输出及其屏蔽功能提供事件分配。EXTI 的功能框图如下图所示:

        从 EXTI 功能框图可以看到有两条主线,一条是由输入线到 NVIC 中断控制器,一条是由输入线到脉冲发生器。这就恰恰是 EXTI 的两大部分功能,产生中断与产生事件,两者从硬件上就存在不同。 

        EXTI 功能框图的产生中断的线路,最终信号是流入 NVIC 中断控制器中。

        下面让我们看一下 EXTI 功能框图的产生中断的线路,最终信号是流入 NVIC 控制器中。输入线是线路的信息输入端,它可以通过配置寄存器设置为任何一个 GPIO 口,或者是一些外设事件。

        标号①是一个边沿检测电路,包括边沿检测电路,上升沿触发选择寄存器(EXTI_RTSR)和
下降沿触发选择寄存器(EXTI_FTSR)。

        标号②是一个或门电路,它的两个信号输入端分别是软件中断事件寄存器(EXTI_SWIER)和边沿检测电路的输入信号。或门电路只要输入端有信号‘1’,就会输出‘1’,所以就会输出‘1’到标号③电路和标号④电路。

        标号③是一个与门电路,它的两个信号输入端分别是中断屏蔽寄存器(EXTI_IMR)和标号②电路输出信号。与门电路要求输入都为‘1’才输出‘1’,这样子的情况下,如果中断屏蔽寄存器(EXTI_IMR)设置为 0 时,不管从标号②电路输出的信号特性如何,最终标号③电路输出的信号都是 0;假如中断屏蔽寄存器(EXTI_IMR)设置为 1 时,最终标号③电路输出的信号才由标号②电路输出信号决定,这样子就可以简单控制 EXTI_IMR来实现中断的目的。标号④电路输出‘1’就会把请求挂起寄存器(EXTI_PR)对应位置 1。

        最后,请求挂起寄存器(EXTI_PR)的内容就输出到 NVIC 内,实现系统中断事件的控制。

        接下来我们看看 EXTI 功能框图的产生事件的线路。

        产生事件线路是从标号 2 (软件中断事件寄存器)之后与中断线路有所不用,之前的线路都是共用的。标号④是一个与门,输入端来自标号 2 电路以及来自于事件屏蔽寄存器(EXTI_EMR)。

        标号④电路输出有效信号 1 就会使脉冲发生器电路产生一个脉冲,而无效信号就不会使其产生脉冲信号。脉冲信号产生可以给其他外设电路使用,例如定时器,模拟数字转换器等,这样的脉冲信号一般用来触发 TIM 或者 ADC 开始转换。

        产生中断线路目的使把输入信号输入到 NVIC,进一步运行中断服务函数,实现功能。而产生事件线路目的是传输一个脉冲信号给其他外设使用,属于硬件级功能。

EXTI功能框图总结

        EXTI功能框图有两条线路,

        一条是中断发生线路:

                GPIO 输入脉冲信号,经过

                1. 边沿检测电路

                2. 上升沿、下降沿触发选择器

                3. 软件中断事件寄存器

                4. 中断屏蔽寄存器

                5. 中断挂起请求寄存器

                最后输入 NVIC,进行中断控制

        一条是事件发生线路:

                GPIO 输入脉冲信号,经过

                1. 边沿检测电路

                2. 上升沿、下降沿触发选择器

                3. 软件中断事件寄存器

                4. 事件屏蔽寄存器

                5. 脉冲发生器

                最后产生脉冲信号,给其他外设使用

        EXTI支持 22个外部中断/事件请求,这些都是信息输入端,也就是上面提及到了输入线,具体如下:

        EXTI 线 0~15:对应外部 IO 口的输入中断
        EXTI 线 16:连接到 PVD 输出
        EXTI 线 17:连接到 RTC 闹钟事件
        EXTI 线 18:连接到 USB OTG FS 唤醒事件
        EXTI 线 19:连接到以太网唤醒事件
        EXTI 线 20:连接到 USB OTG HS(在 FS 中配置)唤醒事件
        EXTI 线 21:连接到 RTC 入侵和时间戳事件
        EXTI 线 22:连接到 RTC 唤醒事件

STM32F4 供给 IO 口使用的中断线只有 16 个,每个中断线对应了最多 9 个 IO 口,以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0、GPIOH.0、GPIOI.0。而中断线每次只能连接到 1 个 IO 口上,这样就需要通过配置决定对应的中断线配置到哪个 GPIO 上了

EXTI 程序设计

        例程功能:

        通过外部中断的方式,让开发板上的独立按键控制LED灯。

        思路:

        一个按键按下,信号输入GPIO,触发外部中断点灯。

        EXTI 外部中断配置步骤:

        1)使能按键的 IO 口时钟

    __HAL_RCC_GPIOH_CLK_ENABLE()

        2)设置按键的 IO 口模式,触发条件,开启 SYSCFG 时钟,设置 IO 口与中断线的映射关系

        3) 配置中断优先级并使能中断

    //HAL_INIT函数中设置中断优先级分组,配置Systic时钟
    //GPIO_INIT函数配置了SYSCFG时钟

    GPIO_InitTypeDef gpio_init_struct; 

    gpio_init_struct.Pin = GPIO_PIN_3;                       /* KEY0引脚 */
    gpio_init_struct.Mode = GPIO_MODE_INPUT;                    /* 输入 */
    gpio_init_struct.Pull = GPIO_PULLUP;                        /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;              /* 高速 */
    gpio_init_struct.Mode = GPIO_MODE_IT_FALLING;               /* 下降沿触发 */
    HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct);           /* KEY0引脚模式设置,上拉输入 

    HAL_NVIC_SetPriority(KEY0_INT_IRQn, 0, 2);               /* 抢占0,子优先级2 */
    HAL_NVIC_EnableIRQ(EXTI3_IRQn);                       /* 使能中断线3 */

        4)编写中断服务函数

        每开启一个中断,就必须编写其对应的中断服务函数,否则将导致死机(CPU 将找不到中断服务函数)。中断服务函数接口厂家已经在 startup_stm32f429xx.s 中做好了。STM32F4 的 IO 口外部中断函数只有 7 个,分别为:

        void EXTI0_IRQHandler();
        void EXTI1_IRQHandler();
        void EXTI2_IRQHandler();
        void EXTI3_IRQHandler();
        void EXTI4_IRQHandler();
        void EXTI9_5_IRQHandler();
        void EXTI15_10_IRQHandler();

        中断线 0-4,每个中断线对应一个中断函数,中断线 5-9 用中断函数 EXTI9_5_IRQHandler,中断线10-15共用中断函数 EXTI15_10_IRQHandler。一般情况下,我们可以把中断控制逻辑直接编写在中断服务函数中,但是 HAL 库把中断处理过程进行了简单封装,请看下面步骤 5 讲解。

        5) 编写中断处理回调函数 GPIO_EXTI_Callback

        HAL 库为了用户使用方便,提供了一个中断通用入口函数 HAL_GPIO_EXTI_IRQHandler,
在该函数内部直接调用回调函数 GPIO_EXTI_Callback。

        我们先来看一下 HAL_GPIO_EXTI_IRQHandler 函数定义:

void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
     if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00U)
     {
     __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); /* 清中断标志位 */
     HAL_GPIO_EXTI_Callback(GPIO_Pin); /* 外部中断回调函数 */
     }
}

        该函数实现的作用非常简单,通过入口参数 GPIO_Pin 判断中断来自哪个 IO 口,然后清除相应的中断标志位,最后调用回调函数 HAL_GPIO_EXTI_Callback()实现控制逻辑。在所有的外部中断服务函数中直接调用外部中断共用处理函数 HAL_GPIO_EXTI_IRQHandler,然后在回调函数 HAL_GPIO_EXTI_Callback 中通过判断中断是来自哪个 IO 口编写相应的中断服务控制逻辑。

void KEY0_INT_IRQHandler(void)
{
     HAL_GPIO_EXTI_IRQHandler(KEY0_INT_GPIO_PIN); 
    /* 调用中断处理公用函数 清除 KEY0 所在中断线 的中断标志位 */
     __HAL_GPIO_EXTI_CLEAR_IT(KEY0_INT_GPIO_PIN); 
    /* HAL 库默认先清中断再处理回调,退出时再清一次中断,避免按键抖动误触发 */
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
     delay_ms(20); /* 消抖 */
     switch(GPIO_Pin)
     {
         case KEY0_INT_GPIO_PIN:
         if (KEY0 == 0)
         {
         LED1_TOGGLE(); /* LED1 状态取反 */
         LED0_TOGGLE(); /* LED0 状态取反 */
         }
         break;
         default : break;
     }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1838569.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Scikit-Learn支持向量机回归

Scikit-Learn支持向量机回归 1、支持向量机回归1.1、最大间隔与SVM的分类1.2、软间隔最大化1.3、支持向量机回归1.4、支持向量机回归的优缺点2、Scikit-Learn支持向量机回归2.1、Scikit-Learn支持向量机回归API2.2、支持向量机回归初体验2.3、支持向量机回归实践(加州房价预测…

氮化铝与氧化铍用于大功率电阻器产品

在过去的几十年里,氧化铍(BeO)一直是用于高功率应用的射频电阻器和端接的主要基板材料。虽然BeO非常适合电子行业的大功率应用,但其粉尘颗粒是有毒的;如果吸入BeO颗粒,它们可能会导致铍中毒,即肺部炎症。由…

202406最新manjaro安装sogou输入法解决方案(采用aur本地package+sogo deb包解决方案)

本地执行安装方法 1.拉取源码 git clone https://gitee.com/liushuai05/fcitx-sogoupinyin.git cd fcitx-sogoupinyin 2.获取sogo下载地址并替换到源码中 - 下载地址:https://pinyin.sogou.com/linux/ - 点击立即下载->x86_64->下载,然后右键复制…

vue+echarts实现tooltip轮播

效果图如下: 实现步骤如下: 定义一个定时器 timer:null, len: 0,页面一加载就清空定时器,此操作是为了防止重复加载时会设置多个定时器在setOption后设置定时器 this.myChart.clear() this.myChart.setOption(option); this.autoShowTool…

AbMole带你探索颅内压力与肌肉生长的联系:一项突破性研究

在生物医学领域,颅内压力(ICP)的调控机制一直是研究的热点。最近,一项发表在《PLOS ONE》上的研究为我们揭示了颅内压力与后颅窝肌肉生长之间的潜在联系,为我们理解某些慢性头痛的成因提供了新的视角。 颅内压力的异常…

Ubuntu-24.04-live-server-amd64启用ssh

系列文章目录 Ubuntu-24.04-live-server-amd64安装界面中文版 Ubuntu安装qemu-guest-agent Ubuntu乌班图安装VIM文本编辑器工具 文章目录 系列文章目录前言一、输入安装命令二、使用私钥登录(可选)1.创建私钥2.生成三个文件说明3.将公钥复制到服务器 三…

抖音新规发布后,做抖音外卖代理需要注意什么?

近日,抖音发布了关于新增《【到家外卖】内容服务商开放准入公告》的意见征集通知(以下简称“通知”),吹响了抖音外卖全面开放的号角,抖音外卖代理怎么做等问题也随即成为了众多创业者交流群中的热议话题。 根据通知要求…

【MySQL】(基础篇十二) —— 子查询

分组数据 本文介绍什么是子查询以及如何使用它们。 SQL允许我们创建子查询(subquery),即嵌套在其他查询中的查询。这样可以实现更复杂的查询,理解这个概念的最好方法是考察几个例子。 利用子查询进行过滤 需求:查询…

Web后端Javaee企业级开发之定时任务 Springboot整合任务框架Quartz和Task详解

定时任务 在Java EE企业级开发中,定时任务(也称为后台调度或周期性任务)是非常常见的一种功能,主要用于执行那些不需要用户交互,但需要按照预定时间间隔或事件触发的任务。Java EE提供了几个框架和API来处理这种需求&…

使用API有效率地管理Dynadot域名,为文件夹中的域名统一设置whois信息

关于Dynadot Dynadot是通过ICANN认证的域名注册商,自2002年成立以来,服务于全球108个国家和地区的客户,为数以万计的客户提供简洁,优惠,安全的域名注册以及管理服务。 Dynadot平台操作教程索引(包括域名邮…

RabbitMQ实践——在Ubuntu上安装并启用管理后台

大纲 环境安装启动管理后台 RabbitMQ是一款功能强大、灵活可靠的消息代理软件,为分布式系统中的通信问题提供了优秀的解决方案。无论是在大规模数据处理、实时分析还是微服务架构中,RabbitMQ都能发挥出色的性能,帮助开发者构建高效、稳定的系…

如何用python调用C++处理图片

一. 背景 用pyhton可直接调用C,减少重写的工作量;部分逻辑运算,C的执行效率高,可进行加速。 下面就一个简单的C滤镜(彩色图转灰度图)为例,展示python调用C 二. 代码实现 代码结构如下&#x…

别再全网找了,这四款良心软件,还你一个清爽的电脑桌面

现在电脑桌面上软件多得吓人。 要是不整理,看着就闹心;整理起来呢,又累得够呛。 所以,很多人干脆就不用那些“用着没意思,删了又觉得可惜”的软件了。 但不管你怎么删,有些软件还是得留着,就像…

每日复盘-202406018

今日关注: 20240617 20240618 六日涨幅最大: ------1--------300868--------- 杰美特 五日涨幅最大: ------1--------300868--------- 杰美特 四日涨幅最大: ------1--------300868--------- 杰美特 三日涨幅最大: ------1--------300462--------- 华铭智能 二日涨…

线程安全问题的原因与解决方案

目录 1.线程在系统中是随机调度,抢占式执行; 2.多个线程同时修改同一个变量 3.线程对变量的修改操作指令不是“原子” 4.内存可见性,引起的线程不安全 拓展 小结 5.指令重排序,引起的线程不安全 为了可以更好的解释&#x…

儿童运动app开发,赢在“起跑线”

我国很早就发出了“体育强国”的口号,随着新高考的改革,体育成绩的占比也越来越大,这对同学们的高考分数也发挥着不可小觑的作用。为此,很多家长开始从小培养孩子的体育运动,力求在强身健体的同时,为考试助…

【大数据】大数据平台建设整体解决方案(Word源文件)

建设大数据管理中台,按照统一的数据规范和标准体系,构建统一数据采集﹣治理﹣共享标准、统一技术开发体系、统一接口 API ,实现数据采集、平台治理,业务应用三层解耦,并按照统一标准格式提供高效的…

PMP考试难度大吗?没基础能过吗?

由于目前的PMP考试主要基于新的大纲,许多内容已经发生了变化,因此学习新的知识,适应新的变化仍然非常重要。 一、新版考试的题量和答题时间有什么变化? 总题量从200道减少到180道,因此答题时间相对变得更充裕一些。 …

计算机网络:运输层 - TCP首部格式 连接的创建与释放

计算机网络:运输层 - TCP首部格式 & 连接的创建与释放 TCP首部格式源端口 目的端口序号确认号数据偏移保留控制位窗口检验和紧急指针 TCP连接创建 - 三次握手TCP传输过程TCP连接释放 - 四次挥手 TCP首部格式 TCP的首部如下: 首部的前20 byte是固定的…

在Python项目中自定义日志工具

在Python项目中自定义日志工具 日志记录是软件开发中的一个关键部分,它可以帮助开发人员调试代码、监控运行状况以及记录重要事件。在Python中,logging 模块提供了强大的日志记录功能。本文将介绍如何创建一个日志工具,使其能够同时将日志输…