使用timer定时器控制LED亮灭
- 实验目的
本节实验目的为实现定时器的功能应用。这一节计划采取使用定时器中断来实现LED状态反转,实现LED的亮灭循环。计划实现功能为:使用定时器0的两个通道,通道0设置定时1s,通道2设置2s,在通道0中实现红色LED的状态反转,通道2中实现绿色LED的状态反转。
- 实验准备
1)、带发光二极管(LED)的K210开发板一块,用于实践并查看实验现象;
2)、官方裸机编程指导手册:kendryte_standalone_programming_guide,用于查阅SDK中接口说明。
- 实验原理
定时器实质上是一个加1计数器,每过一个机器周期,计数器自动加1,当达到我们设定值时,计数器溢出,产生中断,执行中断回调函数。由于每个机器周期的时长是固定的,所以,根据每个机器周期的时长,我们很方便的便能计算出我们想要设定的时间的计数设定值,从实现定时的目的。
- 硬件设计
硬件电路图如下:
- 软件设计
软件流程图如下:
- 软件实现
根据硬件设计和软件设计可知,本节应用实现步骤如下:
1)、设置引脚复用功能:由硬件原理图可知:我们需要将IO12、IO13设置为GPIOHS功能,如下图:
2)、LED初始化,如下图:
3)、timer0初始化,如下图:
4)、实现timer0通道0和通道1的中断回调函数,在中断回调函数中,实现LED状态信息更新,如下图:
5)、检测是否更新LED状态,如果更新,进入更新,将LED更新状态设置为不更新,并控制LED状态,如下图:
根据上述实现步骤,最终代码如下:
#include <fpioa.h>
#include <gpiohs.h>
#include <timer.h>
#include <plic.h>
#include <sysctl.h>
#include <sleep.h>
#define TIMER_1s (1e9)
#define LED_R_PIN (12)
#define LED_G_PIN (13)
#define LED_R_GPIOHSNUM (0)
#define LED_G_GPIOHSNUM (1)
#define LED_R_FUNC (FUNC_GPIOHS0+LED_R_GPIOHSNUM)
#define LED_G_FUNC (FUNC_GPIOHS0+LED_G_GPIOHSNUM)
enum LEDNUM {
LED_R = 0,
LED_G,
};
struct UNITLED {
enum LEDNUM led_num;
gpio_pin_value_t led_value;
char ledst_updata;
};
struct LEDINFO {
struct UNITLED led_r;
struct UNITLED led_g;
};
/*********************************
* 管脚功能初始化
********************************/
void init_hardware(void)
{
fpioa_set_function(LED_R_PIN, LED_R_FUNC);
fpioa_set_function(LED_G_PIN, LED_G_FUNC);
}
/*********************************
* LED初始化
********************************/
void init_led()
{
gpiohs_set_drive_mode(LED_R_GPIOHSNUM, GPIO_DM_OUTPUT);
gpiohs_set_drive_mode(LED_G_GPIOHSNUM, GPIO_DM_OUTPUT);
gpiohs_set_pin(LED_R_GPIOHSNUM, GPIO_PV_HIGH);
gpiohs_set_pin(LED_G_GPIOHSNUM, GPIO_PV_HIGH);
}
/*********************************
* 控制LED亮灭
********************************/
void ctl_led(enum LEDNUM led, gpio_pin_value_t value)
{
gpiohs_set_pin(led, value);
}
/*********************************
* 定时器0通道0 中断回调函数
********************************/
int irq_timer00_callback_t(void *ctx)
{
struct UNITLED *led = (struct UNITLED *)ctx;
led->led_num = LED_R;
led->led_value = !led->led_value;
led->ledst_updata = 1;
return 0;
}
/*********************************
* 定时器0通道1 中断回调函数
********************************/
int irq_timer01_callback_t(void *ctx)
{
struct UNITLED *led = (struct UNITLED *)ctx;
led->led_num = LED_G;
led->led_value = !led->led_value;
led->ledst_updata = 1;
return 0;
}
/*********************************
* 定时器初始化
********************************/
void init_timer(struct LEDINFO *ledinfo)
{
plic_init();
timer_init(TIMER_DEVICE_0);
timer_set_interval(TIMER_DEVICE_0, TIMER_CHANNEL_0, TIMER_1s);
timer_irq_register(TIMER_DEVICE_0, TIMER_CHANNEL_0, 0, 1, irq_timer00_callback_t, &ledinfo->led_r);
timer_set_enable(TIMER_DEVICE_0, TIMER_CHANNEL_0, 1);
timer_set_interval(TIMER_DEVICE_0, TIMER_CHANNEL_1, 2*TIMER_1s);
timer_irq_register(TIMER_DEVICE_0, TIMER_CHANNEL_1, 0, 1, irq_timer01_callback_t, &ledinfo->led_g);
timer_set_enable(TIMER_DEVICE_0, TIMER_CHANNEL_1, 1);
sysctl_enable_irq();
}
int main(void)
{
init_hardware();
init_led();
struct LEDINFO led_info = {
.led_r = {
.led_num = LED_R,
.led_value = GPIO_PV_HIGH,
.ledst_updata = 0,
},
.led_g = {
.led_num = LED_G,
.led_value = GPIO_PV_HIGH,
.ledst_updata = 0,
},
};
init_timer(&led_info);
while(1)
{
if(led_info.led_r.ledst_updata == 1)
{
led_info.led_r.ledst_updata = 0;
ctl_led(led_info.led_r.led_num, led_info.led_r.led_value);
}
if(led_info.led_g.ledst_updata == 1)
{
led_info.led_g.ledst_updata = 0;
ctl_led(led_info.led_g.led_num, led_info.led_g.led_value);
}
usleep(2000);
}
}
- 编译
1)、同上一节类似,在SDK中创建timer文件夹,在新建的文件夹中创建一个main.c文件,然后将本节代码输入到main.c文件中,如下图:
2)、同上一节的编译方式类似,打开vscode终端,在终端中进入上一节创建的build文件夹,然后输入:cmake ../ -DPROJ=timer -G "MinGW Makefiles" ,生成makefile文件,如下图:
3)、生成makefile文件后,输入:make ,开始编译,如下图:
4)、编译完成后,会在build目录下生成烧录文件:timer.bin,如下图:
- 烧录
同上一节的烧录方式类似,注意:Firmware那一项选择我们刚编译出的timer.bin文件。
- 实验现象
红色LED亮1s,灭1s;同时绿色LED亮2s,灭2s;以此循环。
- 实验总结
1)、K210总有三个定时器,每个定时器有四个通道,每个通道都拥有独立中断,用户可根据需求*选择;
2)、K210定时器可使用timer_set_enable函数来开启和关闭定时器,初始化完成之后,开启和关闭定时器不需要重新配置。