LED 闪烁程序 v1.0 —— 不使用定时器

创建一个 Empty C Project,其它文件不用动,直接修改 app.c 就行:

// app.c

#include "em_cmu.h"
#include "em_gpio.h"

typedef struct
{
    GPIO_Port_TypeDef port;
    uint8_t pin;
} LED;

LED led_0;

// LED 闪烁函数
// 通过循环实现延时
void led_blink(LED *led)
{
    for (int i = 0; i < 10000; i++)
    {
        GPIO_PinOutToggle(led->port, led->pin);
    }
}

void app_init(void)
{
    led_0.port = gpioPortA;
    led_0.pin = 1;

    CMU_ClockEnable(cmuClock_GPIO, true);                            // 开启 GPIO 时钟
    GPIO_PinModeSet(led_0.port, led_0.pin, gpioModePushPull, 0);
}

void app_process_action(void)
{
    led_blink(&led_0);
}

没啥好说的,很简单,GPIO 相关 API 可参考官方文档

LED 闪烁程序 v2.0 —— 使用定时器

前面的程序采用循环来实现延时功能,不仅难以精确控制时间,阻塞式的代码也会显著降低系统的响应性,下面采用定时器来重写上述程序。

这里不准备直接使用 EFR32 内部硬件 Timer,而选择 Gecko SDK 中提供的抽象程度更高的 Service —— Sleep Timer。

The Sleeptimer driver provides software timers, delays, timekeeping and date functionalities using a low-frequency real-time clock peripheral.

All Silicon Labs microcontrollers equipped with the RTC or RTCC peripheral are currently supported. Only one instance of this driver can be initialized by the application.

Sleep Timer Official Documents1

在使用前,首先要在 Simplicity Studio 中安装 Sleep Timer Components:

Install Sleep Timer

安装完成后,Project_DIR\gecko_sdk_4.2.2\platform\service 文件夹下多了一个文件夹 sleeptimer

Project Explorer

接下来,修改 app.c 中的程序,其它文件保持不变:

//app.c

#include "em_cmu.h"
#include "em_gpio.h"
#include "sl_sleeptimer.h"   // 增加头文件

typedef struct
{
    GPIO_Port_TypeDef port;
    uint8_t pin;
} LED;

LED led_0;
bool timeout_flag;
unsigned int delay_ms;
sl_sleeptimer_timer_handle_t timer;

// Sleep Timer 超时回调函数
void timeout(sl_sleeptimer_timer_handle_t *handle, void *data)
{
    (void)&handle;           // 因为我们的回调根本用不到这两个参数,如果不写这两行
    (void)&data;             // 会提示 warning: unused parameter 'handle' 'data'
    timeout_flag = true;
}

void app_init(void)
{
    led_0.port = gpioPortA;
    led_0.pin = 1;
    delay_ms = 500;

    CMU_ClockEnable(cmuClock_GPIO, true);                            // 开启 GPIO 时钟
    GPIO_PinModeSet(led_0.port, led_0.pin, gpioModePushPull, 0);

    // 启用 sleep timer 定时器
    sl_sleeptimer_start_periodic_timer_ms(&timer, delay_ms, timeout, NULL, 0, 
                                          SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
}

void app_process_action(void)
{
    if (timeout_flag == true)
    {
        GPIO_PinOutToggle(led_0.port, led_0.pin);
        timeout_flag = false;
    }
}

编译完成后,烧录程序,将看到 LED 灯闪烁。

为什么没有手动初始化 Sleep Timer Service,程序就可以正确运行?

笔者在直接调用 sl_sleeptimer_start_periodic_timer_ms() 函数时产生了上述疑问,于是带着疑问去翻阅了官方文档。根据官方文档,Sleep Timer 的初始化函数 sl_sleeptimer_init() 会在启动代码中被调用。

那么我们回到 main.c 文件:

// main.c

......

  // Initialize Silicon Labs device, system, service(s) and protocol stack(s).
  // Note that if the kernel is present, processing task(s) will be created by
  // this call.
  sl_system_init();

......

继续查看 sl_system_init() 的实现:

// sl_system_init.c

#include "sl_event_handler.h"

void sl_system_init(void)
{
  sl_platform_init();
  sl_driver_init();
  sl_service_init();         // 这里有一行 service 初始化程序
  sl_stack_init();
  sl_internal_app_init();
}

继续查看 sl_service_init() 的实现:

// sl_event_handler.c

void sl_service_init(void)
{
  sl_sleeptimer_init();      // 多了这一行
}

会发现 Simplicity Studio 已经在安装时帮我们增加了 Sleep Timer 的初始化代码,因此无需手动调用初始化函数 sl_sleeptimer_init()

LED 闪烁程序 v3.0 —— 优化项目结构

在这一节内容中,不打算给程序增加任何新功能,而是模仿 Gecko SDK Programming Model 的风格,重写前述程序。

项目文件夹下新建文件:blink.cblink.h

Project Explorer

然后写入以下内容:

// blink.h

#ifndef BLINK_H
#define BLINK_H

void blink_init(void);

void blink_process_action(void);

#endif // BLINK_H
// blink.c
#include "em_cmu.h"
#include "em_gpio.h"
#include "sl_sleeptimer.h"

typedef struct
{
    GPIO_Port_TypeDef port;
    uint8_t pin;
} LED;

LED led_0;
bool timeout_flag;
unsigned int delay_ms;
sl_sleeptimer_timer_handle_t timer;

// Sleep Timer 超时回调函数
void timeout(sl_sleeptimer_timer_handle_t *handle, void *data)
{
    (void)&handle;           // 因为我们的回调根本用不到这两个参数,如果不写这两行
    (void)&data;             // 会提示 warning: unused parameter 'handle' 'data'
    timeout_flag = true;
}

void blink_init(void)
{
    led_0.port = gpioPortA;
    led_0.pin = 1;
    delay_ms = 500;

    CMU_ClockEnable(cmuClock_GPIO, true);                            // 开启 GPIO 时钟
    GPIO_PinModeSet(led_0.port, led_0.pin, gpioModePushPull, 0);

    // 启用 sleep timer 定时器
    sl_sleeptimer_start_periodic_timer_ms(&timer, delay_ms, timeout, NULL, 0, 
                                          SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
}

void blink_process_action(void)
{
    if (timeout_flag == true)
    {
        GPIO_PinOutToggle(led_0.port, led_0.pin);
        timeout_flag = false;
    }
}

接着修改 app.c 文件:

// app.c

#include "blink.h"

void app_init(void)
{
    blink_init();
}

void app_process_action(void)
{
    blink_process_action();            // 有一点 RTOS 的感觉了
}

简单总结一下,app.capp.h 是用户程序的顶层入口,考虑到每一个程序都可以拆分为若干子程序,这里将每个(本例仅一个)子程序单独形成文件,有点类似若干不同的“进程”。

除了上述内容外,还可以做进一步的修改,待更。