今天带大家从底层看一下移植FreeRTOS过程,刚好我手上只有S3C2440的开发板,刚好官方不支持ARM9架构(因为ARM9直接上Linux,用于FreeRTOS有点浪费),所以从看懂这篇文章,你将学会如何修改portable部分文件将FreeRTOS移植到官方不支持的芯片上。
FreeRTOS作为入门级实时操作系统,无论你是从事单片机还是嵌入式Linux,学习一下都大有好处。如果你手上是stm32之类的芯片,亦或者是官方portable文件夹中有的,那移植过程将会很简单,没有怎么办?
1. 简要介绍需要的文件
去官网下载源码,如果下不了,可以留言发给你。
压缩包里只有FreeRTOS这个文件夹是我们需要的
FreeRTOS文件夹中也只有1个文件夹是我们需要的
我们只要include文件夹 ,加上5个文件。
还需要FreeRTOS/Source/portable/MemMang下的内存管理文件
源码中默认提供了5个文件,对应内存管理的5种方法,选一个就行,我们选heap_4.c
文件 优点 缺点
heap_1.c 分配简单,时间确定 只分配、不回收
heap_2.c 动态分配、最佳匹配 碎片、时间不定
heap_3.c 调用标准库函数 速度慢、时间不定
heap_4.c 相邻空闲内存可合并 可解决碎片问题、时间不定
heap_5.c 在heap_4基础上支持分隔的内存块 可解决碎片问题、时间不定
最后是一个用于全局配置的头文件FreeRTOSConfig.h
可以看这篇文章,了解如何配置:FreeRTOSConfig.h-FreeRTOS配置函数详解
2. portable硬件相关代码
可以参考freertos\Source\portable\GCC\ARM7_LPC2000的代码,但是我们是ARM9,和ARM7还是有区别的,需要一些修改。
2.1 port.c
static void prvSetupTimerInterrupt( void )
{
uint32_t ulCompareMatch;
extern void ( vTickISR )( void );
// /* A 1ms tick does not require the use of the timer prescale. This is
// defaulted to zero but can be used if necessary. */
// T0_PR = portPRESCALE_VALUE;
// /* Calculate the match value required for our wanted tick rate. */
// ulCompareMatch = configCPU_CLOCK_HZ / configTICK_RATE_HZ;
// /* Protect against divide by zero. Using an if() statement still results
// in a warning - hence the #if. */
// #if portPRESCALE_VALUE != 0
// {
// ulCompareMatch /= ( portPRESCALE_VALUE + 1 );
// }
// #endif
// T0_MR0 = ulCompareMatch;
// /* Generate tick with timer 0 compare match. */
// T0_MCR = portRESET_COUNT_ON_MATCH | portINTERRUPT_ON_MATCH;
// /* Setup the VIC for the timer. */
// VICIntSelect &= ~( portTIMER_VIC_CHANNEL_BIT );
// VICIntEnable |= portTIMER_VIC_CHANNEL_BIT;
// /* The ISR installed depends on whether the preemptive or cooperative
// scheduler is being used. */
// VICVectAddr0 = ( int32_t ) vTickISR;
// VICVectCntl0 = portTIMER_VIC_CHANNEL | portTIMER_VIC_ENABLE;
// /* Start the timer - interrupts are disabled when this function is called
// so it is okay to do this here. */
// T0_TCR = portENABLE_TIMER;
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
INTMSK &= ~(1<<10); /* enable timer0 int */
TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
TCFG1 &= ~0xf;
TCFG1 |= 3; /* MUX0 : 1/16 */
/* 设置TIMER0的初值 */
TCNTB0 = 31; /* 31250,1s中断一次 */
/* 加载初值, 启动timer0 */
TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
/* 设置为自动加载并启动 */
TCON &= ~(1<<1);
TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
}
port.c文件中只需要修改prvSetupTimerInterrupt( void )这一个函数,这个函数用于启动系统定时中断,相当于启动FreeRTOS的心跳。
涉及到的内容其实就是开启time0定时器中断,并设置为1ms中断一次。注释掉的部分为原来的代码。
2.2 portUSR.c
void vTickISR( void )
{
// /* Save the context of the interrupted task. */
portSAVE_CONTEXT();
// /* Increment the RTOS tick count, then look for the highest priority
// task that is ready to run. */
__asm volatile
(
" bl xTaskIncrementTick \t\n" \
" cmp r0, #0 \t\n" \
" beq SkipContextSwitch \t\n" \
" bl vTaskSwitchContext \t\n" \
"SkipContextSwitch: \t\n"
);
/* Ready for the next interrupt. */
// T0_IR = portTIMER_MATCH_ISR_BIT;
// VICVectAddr = portCLEAR_VIC_INTERRUPT;
SRCPND = (1<<10);
INTPND = (1<<10);
// /* Restore the context of the new task. */
portRESTORE_CONTEXT();
}
这个文件也只需要改一个函数:vTickISR( void )为定时器中断处理函数
主要功能为检测是否需要切换任务,如果需要切换则切换任务,在这里我们要清除中断,以便继续心跳。
也就是这两行代码:
SRCPND = (1<<10);
INTPND = (1<<10);
3. 启动代码
启动代码的关键在于进入irq中断
发生定时器中断时,pc会自动跳到中断向量表中irq中断所指示位置执行
我们需要让pc跳到vTickISR( void ),也就是上文所说的定时器中断处理函数:
b vTickISR
这一句就够了,为什么直接跳进处理函数?不应该保存现场吗?
回到上文看定时器中断处理函数vTickISR( void )中:
portSAVE_CONTEXT();
这一句的宏定义在portmacro.h中,他可以保存所有寄存器(如果移植到别的芯片,需要修改,无非就是异常保存现场那一套,特别简单,自己可以写出来)
而恢复现场也有:
portRESTORE_CONTEXT();
这一句帮我们做了。注意是b,而不是bl,因为加上l将改变lr寄存器。
但是这样做有个问题,会导致,无论什么irq中断都跳到定时器中断处理函数中,所以还要简单修改一下,分辨一下中断源是什么
do_irq:
stmdb sp!, {r0-r12}
ldr r0,=0X4A000014/*INTOFFSET*/
ldr r1,[r0]
cmp r1,#10
beq tick
sub lr, lr, #4
stmdb sp!, {lr}
/* 处理irq异常 */
bl handle_irq_c
/* 恢复现场 */
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
tick:
ldmia sp!, {r0-r12}
b vTickISR
INTOFFSET如果为10则代表了timer0中断。
4. Makefile
objs = boot.o main.o tasks.o timers.o list.o queue.o event_groups.o sdram.o uart.o interrupt.o
objs += ./portable/MemMang/heap_4.o
objs += ./portable/ARM920T/port.o
objs += ./portable/ARM920T/portISR.o
CC = arm-linux-gcc
CFLAGS = -I /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/
CFLAGS += -I ./portable/ARM920T
CFLAGS += -I ./include
CFLAGS += -I .
all: $(objs)
arm-linux-ld -T s3c2440.lds $^ /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/lib/libc.a /usr/local/arm/4.3.2/lib/gcc/arm-none-linux-gnueabi/4.3.2/armv4t/libgcc.a -o s3c2440.elf
arm-linux-objcopy -O binary -S s3c2440.elf s3c2440.bin
clean:
rm *.bin *.o *.elf *.dis
distclean:
rm $(dep_files)
%.o : %.c
$(CC) -march=armv4t -c $(CFLAGS) -o $@ $<
%.o : %.S
$(CC) -c -o $@ $<
Makefile也很简单,无非就是把所有文件连起来。
5. 创建任务与测试
创建两个简单的任务用于测试,一个打印T2 run,一个打印T1 run和一个变量,成功则交替执行。
void vTask1( void *pvParameters )
{
const char *pcTaskName = "T1 run\r\n";
char i='A';
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
i++;
/* 打印任务1的信息 */
puts( pcTaskName );
//GPFDAT=~GPFDAT;
put_char(i);
puts("\r\n");
delay(500000);
}
}
void vTask2( void *pvParameters )
{
const char *pcTaskName = "T2 run\r\n";
/* 任务函数的主体一般都是无限循环 */
for( ;; )
{
/* 打印任务1的信息 */
puts( pcTaskName );
//GPFDAT=~GPFDAT;
delay(500000);
}
}
测试结果:
完整代码已上传,发文时还未过审,有需要至我主页查看自取。