参考网址:

时间触发嵌入式系统设计模式 第14章 笔记_abc-CSDN博客_时间触发嵌入式系统设计模式

SimpleTimer

[Linux下C实现的自定义定时器](voidAspire/Timer: Linux下C实现的自定义定时器 (github.com))

[CppTimer](berndporr/cppTimer: C++ timer: wrapper around the standard Linux C timer to make your life easier (github.com))

[simple-timer-for-c-language](ielife/simple-timer-for-c-language: high performance timer for linux (github.com))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// main.c
// GPIO_LED : GPIO output to control an on-board red LED
//
// EVB : Nu-LB-NUC140
// MCU : NUC140VE3CN

// low-active output control by GPC12

#include <stdio.h>
#include "NUC100Series.h"
#include "MCU_init.h"
#include "SYS_init.h"
#include "scheduler.h"


extern void SCH_Init(void) ;

extern void SCH_Update(void) ;

void SysTick_Handler(void)
{

SCH_Update();

}


void InitSysTickClk()
{

SCH_Init();

SysTick->LOAD = 1000 *CyclesPerUs -1;
SysTick->VAL = (0x00);

NVIC_EnableIRQ(SysTick_IRQn);

SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk |SysTick_CTRL_TICKINT_Msk;

}

void LED_Flash_Update(void)
{

GPIO_TOGGLE(PC14);

}

void LED_Flash_UpdateD(void)
{

GPIO_TOGGLE(PC12);

}


int main(void)
{
SYS_Init();
UART_Open(UART0, 115200);
printf("Hello World \r\n");

GPIO_SetMode(PC, BIT12, GPIO_MODE_OUTPUT);
GPIO_SetMode(PC, BIT14, GPIO_MODE_OUTPUT);
GPIO_SetMode(PC, BIT15, GPIO_MODE_OUTPUT);

InitSysTickClk();
SCH_Add_Task(LED_Flash_Update, 0, 1000);
SCH_Add_Task(LED_Flash_UpdateD, 0, 2000);


while(1)
{
SCH_Dispatch_Tasks();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// scheduler.h
#ifndef _SCHEDULER_H
#define _SCHEDULER_H


// ------ 公用的函数原型 -------------------------------

// 调度器内核函数
void SCH_Dispatch_Tasks(void);
unsigned char SCH_Add_Task(void (*) (void), const unsigned int, const unsigned int);
unsigned char SCH_Delete_Task(const unsigned char);
void SCH_Report_Status(void);

// ------ 公用的常数 -----------------------------------------

// 在程序的运行期间任一时刻请求的任务最大数目

// 在程序的运行期间
// 每个新建项目都必须调整
#define SCH_MAX_TASKS (3)

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
// scheduler.c
/*------------------------------------------------------------------*-

SCH51.C (v1.00)

------------------------------------------------------------------

/// 这里是调度器内核函数 ///

*** 这里是调度器内核函数 ***
--- 这些函数可以用于所有 8051 芯片 ---

*** hSCH_MAX_TASKS 必须由用户设置 ***
--- 参见 "Sch51.h" ---

*** 包括省电模式***
--- 必须确认省电模式被修改以适用于所选定的芯片(通常只有在使用扩展8051----)
--- 诸如 c515c, c509,等等才需要 ---

-*------------------------------------------------------------------*/




#include "scheduler.h"

typedef unsigned char tByte;
typedef unsigned int tWord;
typedef unsigned long tLong;


#define ERROR_SCH_TOO_MANY_TASKS (1)
#define ERROR_SCH_CANNOT_DELETE_TASK (2)

#define ERROR_SCH_WAITING_FOR_SLAVE_TO_ACK (3)
#define ERROR_SCH_WAITING_FOR_START_COMMAND_FROM_MASTER (3)

#define ERROR_SCH_ONE_OR_MORE_SLAVES_DID_NOT_START (4)
#define ERROR_SCH_LOST_SLAVE (5)

#define ERROR_SCH_CAN_BUS_ERROR (6)

#define ERROR_I2C_WRITE_BYTE (10)
#define ERROR_I2C_READ_BYTE (11)
#define ERROR_I2C_WRITE_BYTE_AT24C64 (12)
#define ERROR_I2C_READ_BYTE_AT24C64 (13)
#define ERROR_I2C_DS1621 (14)

#define ERROR_USART_TI (21)
#define ERROR_USART_WRITE_CHAR (22)

#define ERROR_SPI_EXCHANGE_BYTES_TIMEOUT (31)
#define ERROR_SPI_X25_TIMEOUT (32)
#define ERROR_SPI_MAX1110_TIMEOUT (33)

#define ERROR_ADC_MAX150_TIMEOUT (44)


#define RETURN_NORMAL 0
#define RETURN_ERROR 1

// ------ 公用变量定义 ------------------------------

// ------ 公用数据类型声明 ----------------------------

// 如果可能的话,存储在 DATA 区, 以供快速存取
// 每个任务的存储器总和是 7个字节
typedef struct
{
// 指向任务的指针 (必须是 'void (void)' 函数)
void (* pTask)(void);

//延迟 (时标) 直到函数将 (下一次) 运行
// - 详细说明参见 SCH_Add_Task()
unsigned int Delay;

// 在连续的运行之间的间隔 (时标)
// - 详细说明参见 SCH_Add_Task()
unsigned int Period;

// 当任务需要运行时 (由调度器) 加1
unsigned char RunMe;

} sTask;



// 任务队列
sTask SCH_tasks_G[SCH_MAX_TASKS];

// 用来显示错误代码
// 错误代码的详细资料参见 Main.H
// 关于错误端口的详细资料参见 Port.H

unsigned char Error_code_G = 0;

// ------ 私有函数原型 ------------------------------

static void SCH_Go_To_Sleep(void);

// ------ 私有变量 ----------------------------------------

// 跟踪自从上一次记录错误以来的时间 (见下文)
static unsigned int Error_tick_count_G;

// 上次的错误代码 (在1分钟之后复位)
static unsigned char Last_error_code_G;


/*------------------------------------------------------------------*-

SCH_Dispatch_Tasks()

这是“调度”函数. 当一个任务 (函数)需要运行时, SCH_Dispatch_Tasks() 将运行它.
这个函数必须被主循环 (重复)调用.

-*------------------------------------------------------------------*/
void SCH_Dispatch_Tasks(void)
{
unsigned char Index;

// 调度 (运行) 下一个任务 (如果有任务就绪)
for (Index = 0; Index < SCH_MAX_TASKS; Index++)
{

if (SCH_tasks_G[Index].RunMe > 0)
{
(*SCH_tasks_G[Index].pTask)(); // 运行任务

SCH_tasks_G[Index].RunMe -= 1; // RunMe 标志复位/减1

// 周期性的任务将自动的再次运行
// - 如果这是个'单次' 任务, 将它从队列中删除
if (SCH_tasks_G[Index].Period == 0)
{
SCH_Delete_Task(Index);
}
}


}

// 报告系统状况
SCH_Report_Status();

// 这里调度器进行空闲模式
SCH_Go_To_Sleep();

}


/*------------------------------------------------------------------*-

SCH_Add_Task()

使用任务 (函数) 每隔一定时隔或在用户定义的延迟之后 运行

Fn_P - 将被调度的函数的名称.
注意: 所有被调度的函数必须是 'void, void' -
即函数没有参数, 并且返回类型为 void

DELAY - 在任务第一次被运行之前的间隔(时标)

PERIOD - 'PERIOD' 如果为 0, 则该函数u将在由“DELAY”g确定的时间被调用一次.
'PERIOD' 如果非 0, 那么该函数将按PERIOD的值所确定的间隔被重复调用(下面的例子将有助于理解这些)


//PERIOD
返回值 : 返回被添加任务在任务队列中的位置.如果返回值是SCH_MAX_TASKS ,那么该任务不能被加到队列中
(空间不够). 如果返回值 < SCH_MAX_TASKS, 那么该任务被成功添加。

注意: 如果以后要删除任务, 将需要这个返回值,参见 SCH_Delete_Task().


例子:

Task_ID = SCH_Add_Task(Do_X,1000,0,0);
使函数 Do_X() 在1000 个调度器时标之后运行一次

Task_ID = SCH_Add_Task(Do_X,0,1000,1);
使函数 Do_X() 每隔1000 个调度器时标运行一次


Task_ID = SCH_Add_Task(Do_X,300,1000,0);
使函数 Do_X() 每隔1000 个时标定时运行一次。任务将首先在T=300个时标时被执行,然后是1300个时标,
2300个时标 ,等等

-*------------------------------------------------------------------*/
unsigned char SCH_Add_Task(void (* pFunction)(),
const unsigned int DELAY,
const unsigned int PERIOD)
{
unsigned char Index = 0;

// 首先在队列中找到一个空隙(如果有的话)
while ((SCH_tasks_G[Index].pTask != 0) && (Index < SCH_MAX_TASKS))
{
Index++;
}

// 是否已经到达队列的结尾 ??
if (Index == SCH_MAX_TASKS)
{
// 任务队列已满
//
// 设置全局错误变量
Error_code_G = ERROR_SCH_TOO_MANY_TASKS;

// 同时返回错误代码
return SCH_MAX_TASKS;
}

// 如果能运行到这里,说明任务队列中有空间
SCH_tasks_G[Index].pTask = pFunction;

SCH_tasks_G[Index].Delay = DELAY;
SCH_tasks_G[Index].Period = PERIOD;

SCH_tasks_G[Index].RunMe = 0;

return Index; // 返回任务的位置 (以便以后删除)
}

/*------------------------------------------------------------------*-

SCH_Delete_Task()

从调度器删除任务. 注意:并不从存储器中删除相关的函数。仅仅是不再由调度器调用这个函数
参数: TASK_INDEX - 任务索引. 由 SCH_Add_Task()提供.

返回值: RETURN_ERROR or RETURN_NORMAL

-*------------------------------------------------------------------*/
unsigned char SCH_Delete_Task(const tByte TASK_INDEX)
{
unsigned char Return_code;

if (SCH_tasks_G[TASK_INDEX].pTask == 0)
{
// 这里没有任务
//
// 设置全局错误变量
Error_code_G = ERROR_SCH_CANNOT_DELETE_TASK;

// ...同时返回错误代码
Return_code = RETURN_ERROR;
}
else
{
Return_code = RETURN_NORMAL;
}

SCH_tasks_G[TASK_INDEX].pTask = 0x0000;
SCH_tasks_G[TASK_INDEX].Delay = 0;
SCH_tasks_G[TASK_INDEX].Period = 0;

SCH_tasks_G[TASK_INDEX].RunMe = 0;

return Return_code; // 返回状态
}


/*------------------------------------------------------------------*-

SCH_Report_Status()

用来显示错误代码的简单的函数.

这个版本将在连接到端口的LED上显示错误代码,
如果需要的话,可以修改为通过串行连接等方式报告错误。
错误只在有限的时间内显示(在 1ms 时标间隔时,60000 时标 = 1 分钟 。).
此后错误代码被复位为0.

这些代码可以很容易的修改为“永远”显示最近的错误。这对于系统可能更为合理。

更加详尽的资料参见第10章。

-*------------------------------------------------------------------*/
void SCH_Report_Status(void)
{
#ifdef SCH_REPORT_ERRORS
// 只在需要报告错误时适用
// 检查新的错误代码
if (Error_code_G != Last_error_code_G)
{
// 假定LED采用负逻辑
Error_port = 255 - Error_code_G;

Last_error_code_G = Error_code_G;

if (Error_code_G != 0)
{
Error_tick_count_G = 60000;
}
else
{
Error_tick_count_G = 0;
}
}
else
{
if (Error_tick_count_G != 0)
{
if (--Error_tick_count_G == 0)
{
Error_code_G = 0; // 复位错误代码
}
}
}
#endif
}


/*------------------------------------------------------------------*-

SCH_Go_To_Sleep()

本调度器在时钟时标之间将进入空闲模式来节省功耗。下一个时钟时标将使处理器返回到正常工作状态。
注意: 如果这个函数由宏来实现,或简单地将这里的代码粘贴到“调度”函数中,可以有少量的性能改善。
然而,通过采用函数调用的方式来实现,可以在开发期间更容易的使用Keil硬件模拟器中的“性能分析器”来估计
调度器的性能。这方面的例子参见第14章。

*** 如果使用看门狗的话,可能需要禁止这个功能 ***

*** 根据硬件的需要修改 ***

-*------------------------------------------------------------------*/
void SCH_Go_To_Sleep()
{

}



/*------------------------------------------------------------------*-

SCH_Update 中断调用

这是调度器的中断服务程序. 初始化函数 SCH_Init_T1()中的定时器设置决定了它的调用频率。
这个版本由定时器1中断触发

-*------------------------------------------------------------------*/
void SCH_Update(void)
{
tByte Index;

// 重装定时器
// 注意:计算单位为“时标”(不是毫秒)
for (Index = 0; Index < SCH_MAX_TASKS; Index++)
{
// 检测这里是否有任务
if (SCH_tasks_G[Index].pTask)
{
if (SCH_tasks_G[Index].Delay == 0)
{
// 任务需要运行
SCH_tasks_G[Index].RunMe += 1; // 'RunMe' 标志加1

if (SCH_tasks_G[Index].Period)
{
// 调度定期的任务再次运行
SCH_tasks_G[Index].Delay = SCH_tasks_G[Index].Period;
}
}
else
{
//还没有准备好运行,延迟减 1
SCH_tasks_G[Index].Delay -= 1;
}
}

}

}
void SCH_Init(void)
{
unsigned char i;

for (i = 0; i < SCH_MAX_TASKS; i++)
{
SCH_Delete_Task(i);
}

// 复位全局错误变量
// - SCH_Delete_Task() 将产生一个错误代码
// (因为任务队列是空的)
Error_code_G = 0;

}