基于S32K144平台实现两种软件定时器


在开发嵌入式软件时,定时器是必不可少的,或用于实时系统的心跳、延时,或用于裸机系统中的周期性任务等。因为硬件定时器资源有限,并且还有可能用于PWM,输入捕获等功能,所以更多的人会基于一个硬件定时器实现多个软件定时器,用于对时间精度要求没那么高的场合,如发送周期性CAN报文,定时采集传感器信号等。本文基于S32K144平台介绍笔者常用的两种软件定时器,希望对读者有用。

1.网红软件定时器MultiTimer

1.1 MultiTimer 简介

MultiTimer是最近比较火的一个开源项目,作者为0x1abin,github地址为:GitHub - 0x1abin/MultiTimer at master,遵循MIT开源许可协议,目前已收获502 stars。

MultiTimer是一个软件定时器扩展模块,可无限扩展你所需的定时器任务,取代传统的标志位判断方式, 更优雅更便捷地管理程序的时间触发时序。

1.2 准备工作

  • S32K144EVB-Q100
  • S32DS For ARM 2.2
  • PC端的串口调试部助手

1.3 MultiTimer 使用

MultiTimer的README文档对于如何使用MultiTimer介绍的非常详细。本文基于S32K144平台并参考README文档介绍如何使用MultiTimer。

1.3.1 新建工程
  1. 选择从例程中新建工程,如下图:
  2. 选择lpit_periodic_interrupt_s32k144的例程,并重命名为lpit_multitimer_s32k144,如下图,点击Finish:
  3. 配置lpit组件,设置定时器周期为1ms,并使能中断,如下图:
  4. multi_timer.cmulti_timer.h复制到工程所在的文件夹,并按F5刷新一下,就能在工程树看到了,如下图所示(串口打印相关的文件后面再介绍):
  5. 确认multi_timer.h中设置的tick值,如下图:
/*
It means 1 tick for 1ms. 
Your can configurate for your tick time such as 5ms/10ms and so on.
*/
#define CFG_TIMER_1_TICK_N_MS   1

源代码此处的注释有误,如果想要tick为5ms,宏定义改为5是不行的,需要将硬件定时器的中断周期改为5ms。如果此处改为5,会导致软件定时器的超时时间变为原来的1/5。

1.3.2 修改主函数
  1. 首先在main.c中包含multi_timer.h,并创建两个软件定时器,如下:
#include "multi_timer.h"

/* 创建两个软件定时器 */
struct Timer timer1;
struct Timer timer2;
  1. 然后对两个软件定时器进行初始化并启动,如下:
    /* 软件定时器初始化*/
	timer_init(&timer1, timer1_callback, 1000, 2000, NULL); //1s loop
	timer_start(&timer1);

	timer_init(&timer2, timer2_callback, 50, 0, NULL); //50ms delay
	timer_start(&timer2);

	printf("timer start!\r\n");

timer_init函数的定义如下,其中第三个函数表示第一次的超时时间,第四个参数表示第2次到第N次的超时时间。第4个参数如果为0,那么该软件定时器只发生一次超时,对应的回调函数也只执行一次。

void timer_init(struct Timer* handle, void (*timeout_cb)(void *arg), uint32_t timeout, uint32_t repeat, void *arg)
  1. 接着在1ms的硬件定时器中断里调用timer_ticks(),如下:
/*!
 * @brief: LPIT interrupt handler.
 *         When an interrupt occurs clear channel flag and toggle LED0
 */
void LPIT_ISR(void)
{
    /* Clear LPIT channel flag */
    LPIT_DRV_ClearInterruptFlagTimerChannels(INST_LPIT1, (1 << LPIT_CHANNEL));
    /* 软件定时器的ticks,此处为1ms*/
    timer_ticks();
}
  1. 最后实现两个软件定时器的回调函数,注意不能有耗时过长的操作,如下:
/* 每个软件定时器的回调函数,不能在回调函数做耗时操作,否则会导致其他定时器无法正常超时 */
void timer1_callback(void *arg)
{
    /* Toggle LED0 */
    PINS_DRV_TogglePins(LED_GPIO_PORT, (1 << LED0_PIN_INDEX));

    printf("timer1 timeout! arg: %p\r\n", arg);
}

void timer2_callback(void *arg)
{
    printf("timer2 timeout! arg: %p\r\n", arg);
}
1.3.3 增加串口打印功能
  1. 首先在工程中增加lpuart组件,在组件库中找到lpuart,双击即可添加,如下图:
  2. 然后点击添加到工程的lpuart组件,选择lpuart1,波特率设为115200,如下:
  3. 配置下lpuart对应的pin脚,RX选择PTC6,TX选择PTC7,如下:
  4. 工程中需要增加四个文件,如下所示:

    其中,printf.c,uart_console_io.c,uart.h在S32DS For ARM 2.2安装目录下,uart.c需要自己编写。
  • printf.c地址如下:
  • uart_console_io.c地址如下:
  • uart.h地址如下:
  • uart.c需要自己实现如下四个函数
#include "uart.h"
#include "lpuart1.h"
UARTError InitializeUART(UARTBaudRate baudRate)
{
	//lpuart1_InitConfig0.baudRate = baudRate;

	/* Initialize LPUART instance */
	LPUART_DRV_Init(INST_LPUART1, &lpuart1_State, &lpuart1_InitConfig0);
	return kUARTNoError;
}

UARTError ReadUARTPoll(char* c)
{
	UARTError err = kUARTNoError;
	err = LPUART_DRV_ReceiveDataBlocking(INST_LPUART1, (uint8_t *)c, 1, 1000);
	return err;
}

UARTError ReadUARTN(void* bytes, unsigned long length)
{
	UARTError err = kUARTNoError;
	err = LPUART_DRV_ReceiveDataBlocking(INST_LPUART1, (uint8_t *)bytes, length, 1000);
	return err;
}

UARTError WriteUARTN(const void* bytes, unsigned long length)
{
	UARTError err = kUARTNoError;
	uint32_t bytesRemaining;
	err = LPUART_DRV_SendData(INST_LPUART1, (const uint8_t *const)bytes, length);
  /* Wait for transfer to be completed */
  while(LPUART_DRV_GetTransmitStatus(INST_LPUART1, &bytesRemaining) != STATUS_SUCCESS);
  return err;
}
  1. 工程的属性中的库需要更改为ewl_c no I/O
  2. 工程属性中的linker的库需要全部删除,否则编译时会发生错误
  3. 在main.c中包含stdio.h之后,就可以调用printf函数在串口打印信息了。

1.4 MultiTimer功能测试

将程序下载到S32K144EVB中,在串口助手打印的信息如下:

从打印信号可以看出,各个定时器的运行符合预期。

1.5 MultiTimer借鉴

MultiTimer使用了单链表操作,且代码量不大,可以借鉴该项目学习单链表。网络上对该项目的解读文章非常多,笔者推荐阅读如下这个:

MultiTimer | 一款可无限扩展的软件定时器

2.个人常用软件定时器

除了网红定时器MultiTimer,笔者再推荐一个自己经常使用的软件定时器。

2.1 个人常用软件定时器介绍

  1. 需要将上一章节的工程中的multi_timer.cmulti_timer.h换成自己编写的soft_timer.csoft_timer.h,工程树如下:
  2. soft_timer.h主要定义定时器相关的枚举和结构体,如下所示:
typedef enum 
{
	TIMER1,
	TIMER2,
	VTIMER_NUM,
} VtimerName_t;

typedef struct
{
	unsigned char 	enable;
	unsigned int 	msec;
} Vtimer_t,*PVtimer;
  1. soft_timer.c主要实现定时器的初始化,启动,判断以及心跳函数等,如下所示:
Vtimer_t sVtimer[VTIMER_NUM];

void vtimer_Init()
{
	unsigned char i;
	for (i=0; i<VTIMER_NUM; i++)
	{
		sVtimer[i].msec = 0;
		sVtimer[i].enable = false;
	}
    vtimer_SetTimer(TIMER1, TIMER1_PERIOD);
    vtimer_SetTimer(TIMER2, TIMER2_PERIOD);
}

void vtimer_SetTimer(VtimerName_t name, unsigned int msec) 
{
    if(false == sVtimer[name].enable)
    {
        sVtimer[name].msec = msec;
        sVtimer[name].enable = true;
    }
}
bool vtimer_TimerElapsed(VtimerName_t name)
{
	if((sVtimer[name].msec == 0) && (sVtimer[name].enable == true))
	{
		sVtimer[name].enable = false;

		return true;
	}
	else
	{
		return false;
	}
}

void vtimer_UpdateHandler(void)
{
	/*Enter each 1ms*/
	unsigned char i;
	
	for (i=0; i<VTIMER_NUM; i++)
	{
		if (sVtimer[i].msec && (sVtimer[i].enable == true))
		{
			sVtimer[i].msec--;    
		}
		else
		{
			sVtimer[i].msec = 0;
		}
	}
}
  1. main.c中在硬件定时器中断中调用心跳函数,如下所示:
/*!
 * @brief: LPIT interrupt handler.
 *         When an interrupt occurs clear channel flag and toggle LED0
 */
void LPIT_ISR(void)
{
    /* Clear LPIT channel flag */
    LPIT_DRV_ClearInterruptFlagTimerChannels(INST_LPIT1, (1 << LPIT_CHANNEL));
    /* 软件定时器的ticks,此处为1ms*/
    vtimer_UpdateHandler();
}
  1. 在main函数中调用初始化函数和判断函数,如下所示:
/* 软件定时器初始化*/
vtimer_Init();
printf("timer start!\r\n");

while(1)
{
	if(vtimer_TimerElapsed(TIMER1) == true)
	{
		vtimer_SetTimer(TIMER1, TIMER1_PERIOD);
		/* Toggle LED0 */
		PINS_DRV_TogglePins(LED_GPIO_PORT, (1 << LED0_PIN_INDEX));
		printf("timer1 timeout!\r\n");
	}

	if(vtimer_TimerElapsed(TIMER2) == true)
	{
		vtimer_SetTimer(TIMER2, TIMER2_PERIOD);
		printf("timer2 timeout!\r\n");
	}
}

2.2 功能测试

将程序下载到S32K144EVB,串口助手打印信息如下:

打印信息符合预期。

3.例程分享

本文使用的两个例程已上传百度网盘,分享如下:

  • 链接:https://pan.baidu.com/s/1u6ja9roGR8_Krg7YrMpOsQ
  • 提取码:yrlw

如果觉得本文对你有用,不妨给个一键三连!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Auto FAE进阶之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值