报告5
——终极计划完成
1. 简介终极计划准备移植一个KEIL MDK 版本的FreeRTOS操作系统,并完成以太网级别的外部设备管理,包括UART,CAN1和CAN2。
2. 协议协议仍然遵循初级计划中的通讯协议。
3. FreeRTOS移植3.1. 准备工作FreeRTOS目前研究的人挺多的,官方给出的例子和支持的芯片都非常多,但是遗憾的是,官方的移植中不包含基于MDK版本的LPC1768芯片的支持。
首先我们来看一下FreeRTOS的目录结构,目录中包含FreeRTOS和FreeRTOS-Plus两个目录。
FreeRTOS目录中包含的文件是我们本次移植的重点内容,FreeRTOS-Plus中包含所有FreeRTOS支持的功能附件,包括以太网,文件系统等,但这个本次不是我们的重点。我们下面的移植重点针对FreeRTOS。
FreeRTOS的目录树:
.
├── FreeRTOS
├── Demo
│ ├── ARM7_AT91FR40008_GCC
│ ├── ARM7_AT91SAM7S64_IAR
│ ├── ARM7_AT91SAM7X256_Eclipse
│ ├── ARM7_LPC2106_GCC
│ ├── WizNET_DEMO_TERN_186
… …
│ └── Xilinx_FreeRTOS_BSP
├── License
│ └── license.txt
├── links_to_doc_pages_for_the_demo_projects.url
├── readme.txt
└── Source
├── croutine.c
├── event_groups.c
├── include
│ ├── croutine.h
│ ├── deprecated_definitions.h
│ ├── event_groups.h
│ ├── FreeRTOS.h
│ ├── list.h
│ ├── message_buffer.h
│ ├── mpu_prototypes.h
│ ├── mpu_wrappers.h
│ ├── portable.h
│ ├── projdefs.h
│ ├── queue.h
│ ├── semphr.h
│ ├── stack_macros.h
│ ├── StackMacros.h
│ ├── stdint.readme
│ ├── stream_buffer.h
│ ├── task.h
│ └── timers.h
├── list.c
├── portable
│ ├── ARMv8M
│ ├── BCC
│ ├── CCS
│ ├── CodeWarrior
│ ├── Common
│ ├── GCC
│ ├── IAR
│ ├── Keil
│ ├── MemMang
│ ├── MikroC
│ ├── MPLAB
│ ├── MSVC-MingW
│ ├── oWatcom
│ ├── Paradigm
│ ├── readme.txt
│ ├── Renesas
│ ├── Rowley
│ ├── RVDS
│ ├── SDCC
│ ├── Softune
│ ├── Tasking
│ ├── ThirdParty
│ └── WizC
├── queue.c
├── readme.txt
├── stream_buffer.c
├── tasks.c
└── timers.c
通过目录树我们可以看到,FreeRTOS本身支持的芯片种类非常多,支持的编译器种类也非常丰富,其中包括Keil的支持,这说明移植本身应该不难。
3.2. 构建自己的目录结构为了脱离FreeRTOS的目录结构,简化目录层数,我打算自己构建一个目录结构。
如图中所示, app: 放置我们自己开发的应用程序;
Freertos: 放置FreeRTOS中不需要我们编辑的源码;
Lib: 放置LPC1768的驱动库和头文件;
Portable: 放置FreeRTOS与移植有关的部分,
Prj: keil工程文件,输出等
Uip: 放置uip源码。
3.3. 移植过程步骤1,工程建立和文件添加
按照正常的mdk方式建立一个空的工程,并添加工程文件如下图所示:
步骤2,freertoscofig.h的配置
这个文件用来配置FreeRTOS将按照什么形式配置和运行。其中值得注意的地方有:
1、 CPU时钟
#define configCPU_CLOCK_HZ ( ( unsigned long ) 100000000 )
CPU时钟的选定需要和真实CPU所执行的时钟相同。IRD-LPC1768-DEV的CPU晶振为12MHz,因此选定的PLL0的参数如下。
2、 任务切换速率
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
这个数值配置的越大,意味着任务切换的次数在单位时间内越多,如果设置为1000,表示1秒时间内,会切换1000此认为,也就是任务的实时效果为1ms。数值太大会造成任务切换导致的开销太大,数值太小导致任务间切换时间上升,需要根据实际情况自己选择。
步骤3,startup_LPC17xx.s的移植
在
__Vectors DCD __initial_sp ; Top of Stack部分,我们需要用freertos的调度函数替换原有的中断入口函数。
EXTERN vPortSVCHandler
DCD vPortSVCHandler ; SVCall Handler
; DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
EXTERN xPortPendSVHandler
DCD xPortPendSVHandler ; PendSV Handler
EXTERN xPortSysTickHandler
DCD xPortSysTickHandler ; SysTick Handler
; DCD PendSV_Handler ; PendSV Handler
; DCD SysTick_Handler ; SysTick Handler
步骤4,内存管理的选择
Freertos中提供了5中管理方法,
- heap_1 - the very simplest, does not permit memory to be freed
- heap_2 - permits memory to be freed, but does not coalescence adjacent free blocks.
- heap_3 - simply wraps the standard malloc() and free() for thread safety
- heap_4 - coalescences adjacent free blocks to avoid fragmentation. Includes absolute address placement option
- heap_5 - as per heap_4, with the ability to span the heap across multiple non-adjacent memory areas
根据官方对于这5种管理方法的说明,先选择了第1种,最简单的,所有东西都自己定义。
4. 软件基本应用简单介绍一下我的应用中所需的几种多任务需要。
1) 基本过程:
硬件初始化->任务创建->启动调度->开始任务调度执行。
2) 任务:
任务就是一个个的死循环(也有执行一次就退出的),我们就考虑在任务内执行的任务和任务间配合就可以了。例如下面的代码。
- <font size="3">static void prvFlashTask( void *pvParameters )
- {
- TickType_t xLastFlashTime;
- /* We need to initialise xLastFlashTime prior to the first call to
- vTaskDelayUntil(). */
- xLastFlashTime = xTaskGetTickCount();
- for(;;) //死循环
- {
- /* Simply toggle the LED between delays. */
- vTaskDelayUntil( &xLastFlashTime, mainLED_TOGGLE_RATE ); 延时等待
- vParTestToggleLED( 0 ); 小灯闪烁
- }
- }</font>
复制代码
3) 软件定时器:
配置定时器服务任务 程序中需要使用到软件定时器, 需要先在 FreeRTOSConfig.h 中正确配置如下宏 :
#define configUSE_TIMERS 1 //编译定时器相关代码, 如需要使用定时器, 设置为 1
#define configTIMER_TASK_PRIORITY 1
//设置定时器Daemon 任务优先级, 如果优先级太低, 可能导致定时器无法及时执行
#define configTIMER_QUEUE_LENGTH 10
//设置定时器Daemon 任务的命令队列深度, 设置定时器都是通过发送消息到该队列实现的。
#define configTIMER_TASK_STACK_DEPTH ( configMINIMAL_STACK_SIZE * 2 )
//设置定时器Daemon 任务的栈大小
创建 启动 停止定时器 示例代码TimerHandle_t xTimerUser; // 定义句柄
// 定时器回调函数格式
void vTimerCallback( TimerHandle_t xTimer )
{
// do something no block
// 获取溢出次数
static uin32_t ulCount = ( uint32_t ) pvTimerGetTimerID( xTimer );
// 累积溢出次数
++ulCount;
// 更新溢出次数
vTimerSetTimerID( xTimer, ( void * ) ulCount );
if (ulCount == 10) {
// 停止定时器
xTimerStop( xTimer, 0 );
}
}
void fun()
{
// 申请定时器, 配置
xTimerUser = xTimerCreate
/*调试用, 系统不用*/
("Timer's name",
/*定时溢出周期, 单位是任务节拍数*/
100,
/*是否自动重载, 此处设置周期性执行*/
pdTRUE,
/*记录定时器溢出次数, 初始化零, 用户自己设置*/
( void * ) 0,
/*回调函数*/
vTimerCallback);
if( xTimerUser != NULL ) {
// 启动定时器, 0 表示不阻塞
xTimerStart( xTimerUser, 0 );
}
}
如上所示, 调用函数 xTimerCreate申请,配置定时器, 通过 xTimerStart 启动定时器, 当定时器计数溢出时, 系统回调注册的函数。
定时器可以设置为一次性 One-shot 或者自动重载 Auto-reload 两种, 第一种溢出后停止定时器, 第二种溢出后会再次启动定时器。
修改定时器
在申请定时器的时候设置的定时器周期, 可以通过函数 xTimerChangePeriod 修改, 如下示例 :
void vAFunction_2( TimerHandle_t xTimer )
{
// 判断定时器是否处于运行状态
if( xTimerIsTimerActive( xTimer ) != pdFALSE )
{
/* xTimer is active, do something. */
}
else
{
// 处于这个状态的定时器, 可能由于 :
// 1 定时器 create 后没有start
// 2 一次性定时器执行溢出后
// 修改定时器周期
if( xTimerChangePeriod( xTimer,
/*修改定时周期*/
500 / portTICK_PERIOD_MS,
/*允许阻塞最大时间 100 ticks*/
100 ) == pdPASS )
{
// update fail
// 阻塞 100 tick 仍然无法发送命令
// 删除定时器 释放对应内存!
xTimerDelete( xTimer );
}
else
{
// 定时器配置更新成功, 并已经启动 !!
}
}
}
如上, 该函数会修改定时器并使定时器 开始运行!!!
另外, 可以通过函数 xTimerReset 重启定时器, 如果已经启动计数, 重新开始计数; 如果没有启动,启动定时器。
获取定时器状态 // 获取名称 , 申请是设置的字符串
pcTimerGetName()
// 定时器溢出周期
xTimerGetPeriod()
// 返回定时器溢出的时间点 (--> xTaskGetTickCount())
xTimerGetExpiryTime()