版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wowocpp/article/details/82887244
1 函数指针 代码:
/*------------------------------------------------------------------*-
Main.C (v1.00)
------------------------------------------------------------------
函数指针的演示
-*------------------------------------------------------------------*/
#include "Main.h"
#include "Printf51.h"
#include <stdio.h>
// ------ 私有函数原型 ------------------------------
void Square_Number(int, int*);
/* ................................................................. */
/* ................................................................. */
int main(void)
{
int a = 2, b = 3;
void (* pFn)(int, int*); /* 声明 pFn 为 fn 的指针的,fn带有
int 参数和 int 指针参数
(返回 void) */
int Result_a, Result_b;
//准备在 Keil 硬件模拟器上使用printf()
Printf51_Init();
pFn = Square_Number; // pFn存放 Square_Number 的地址
printf("Function code starts at address: %u\n", (tWord) pFn);
printf("Data item a starts at address: %u\n\n", (tWord) &a);
// 以传统的方式 调用'Square_Number'
Square_Number(a,&Result_a);
// 使用函数指针调用'Square_Number'
(*pFn)(b,&Result_b);
printf("%d squared is %d (using normal fn call)\n", a, Result_a);
printf("%d squared is %d (using fn pointer)\n", b, Result_b);
while(1);
return 0;
}
/*------------------------------------------------------------------*/
void Square_Number(int a, int* b)
{
// 演示- 计算a的平方
*b = a * a;
}
/*------------------------------------------------------------------*-
---- 文件结束 -------------------------------------------------
-*------------------------------------------------------------------*/
2 简单例子代码
一个用来重复闪烁LED的调度器
一秒亮
一秒灭
如此循环
3 将简单的例子 移植到 Nu-LB-NUC140 ARM CM0 单片机上面
page210
以下代码 来自 keil freertos
/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler
SysTick_Handler
void xPortSysTickHandler( void )
{
uint32_t ulPreviousMask;
ulPreviousMask = portSET_INTERRUPT_MASK_FROM_ISR();
{
/* Increment the RTOS tick. */
if( xTaskIncrementTick() != pdFALSE )
{
/* Pend a context switch. */
*(portNVIC_INT_CTRL) = portNVIC_PENDSVSET;
}
}
portCLEAR_INTERRUPT_MASK_FROM_ISR( ulPreviousMask );
}
void prvSetupTimerInterrupt( void )
{
/* Configure SysTick to interrupt at the requested rate. */
*(portNVIC_SYSTICK_LOAD) = ( configCPU_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
*(portNVIC_SYSTICK_CTRL) = portNVIC_SYSTICK_CLK | portNVIC_SYSTICK_INT | portNVIC_SYSTICK_ENABLE;
}
#define portNVIC_SYSTICK_CTRL ( ( volatile uint32_t *) 0xe000e010 )
#define portNVIC_SYSTICK_LOAD ( ( volatile uint32_t *) 0xe000e014 )
#define __HSI (50000000UL) /*!< PLL default output is 50MHz */
uint32_t SystemCoreClock = __HSI; /*!< System Clock Frequency (Core Clock) */
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ ( ( portTickType ) 1000 )
E:\Nu_LB_Nuc140\Nu_LB_NUC140_BSP\SampleCode\Nu-LB-NUC140\Scheduler\KEIL
4 Nu-LB-NUC140 上面的代码
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();
}
}
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
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;
}
5 主要结构体
// ------ 公用变量定义 ------------------------------
// ------ 公用数据类型声明 ----------------------------
// 如果可能的话,存储在 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;
pTask ---- 该任务 的函数指针
Period ---- 该任务 间隔多长时间 执行一次
Delay ---- 设置为Period 的大小,然后一直递减 知道 等于0. 执行函数指针对应函数一次
然后重新设置为Period 的大小
RunMe — 任务是是否 需要执行
重点看:
void SCH_Update(void) ------ 定时器 tick 中断 调用,负责设置标志。
和
void SCH_Dispatch_Tasks(void) ----- 循环 while 调用,负责根据标志,执行任务。
6 注意
需要 用户 自己 手动设置 运行的任务数量
(稍后补充)