• FreeRTOS移植-教你修改portable(S3C2440、ARM9、gcc)


    今天带大家从底层看一下移植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

    1. static void prvSetupTimerInterrupt( void )
    2. {
    3. uint32_t ulCompareMatch;
    4. extern void ( vTickISR )( void );
    5. // /* A 1ms tick does not require the use of the timer prescale. This is
    6. // defaulted to zero but can be used if necessary. */
    7. // T0_PR = portPRESCALE_VALUE;
    8. // /* Calculate the match value required for our wanted tick rate. */
    9. // ulCompareMatch = configCPU_CLOCK_HZ / configTICK_RATE_HZ;
    10. // /* Protect against divide by zero. Using an if() statement still results
    11. // in a warning - hence the #if. */
    12. // #if portPRESCALE_VALUE != 0
    13. // {
    14. // ulCompareMatch /= ( portPRESCALE_VALUE + 1 );
    15. // }
    16. // #endif
    17. // T0_MR0 = ulCompareMatch;
    18. // /* Generate tick with timer 0 compare match. */
    19. // T0_MCR = portRESET_COUNT_ON_MATCH | portINTERRUPT_ON_MATCH;
    20. // /* Setup the VIC for the timer. */
    21. // VICIntSelect &= ~( portTIMER_VIC_CHANNEL_BIT );
    22. // VICIntEnable |= portTIMER_VIC_CHANNEL_BIT;
    23. // /* The ISR installed depends on whether the preemptive or cooperative
    24. // scheduler is being used. */
    25. // VICVectAddr0 = ( int32_t ) vTickISR;
    26. // VICVectCntl0 = portTIMER_VIC_CHANNEL | portTIMER_VIC_ENABLE;
    27. // /* Start the timer - interrupts are disabled when this function is called
    28. // so it is okay to do this here. */
    29. // T0_TCR = portENABLE_TIMER;
    30. INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
    31. INTMSK &= ~(1<<10); /* enable timer0 int */
    32. TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
    33. TCFG1 &= ~0xf;
    34. TCFG1 |= 3; /* MUX0 : 1/16 */
    35. /* 设置TIMER0的初值 */
    36. TCNTB0 = 31; /* 312501s中断一次 */
    37. /* 加载初值, 启动timer0 */
    38. TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
    39. /* 设置为自动加载并启动 */
    40. TCON &= ~(1<<1);
    41. TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
    42. }

    port.c文件中只需要修改prvSetupTimerInterrupt( void )这一个函数,这个函数用于启动系统定时中断,相当于启动FreeRTOS的心跳。

    涉及到的内容其实就是开启time0定时器中断,并设置为1ms中断一次。注释掉的部分为原来的代码。

    2.2 portUSR.c

    1. void vTickISR( void )
    2. {
    3. // /* Save the context of the interrupted task. */
    4. portSAVE_CONTEXT();
    5. // /* Increment the RTOS tick count, then look for the highest priority
    6. // task that is ready to run. */
    7. __asm volatile
    8. (
    9. " bl xTaskIncrementTick \t\n" \
    10. " cmp r0, #0 \t\n" \
    11. " beq SkipContextSwitch \t\n" \
    12. " bl vTaskSwitchContext \t\n" \
    13. "SkipContextSwitch: \t\n"
    14. );
    15. /* Ready for the next interrupt. */
    16. // T0_IR = portTIMER_MATCH_ISR_BIT;
    17. // VICVectAddr = portCLEAR_VIC_INTERRUPT;
    18. SRCPND = (1<<10);
    19. INTPND = (1<<10);
    20. // /* Restore the context of the new task. */
    21. portRESTORE_CONTEXT();
    22. }

    这个文件也只需要改一个函数:vTickISR( void )为定时器中断处理函数

    主要功能为检测是否需要切换任务,如果需要切换则切换任务,在这里我们要清除中断,以便继续心跳。

    也就是这两行代码:

    1. SRCPND = (1<<10);
    2. 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中断都跳到定时器中断处理函数中,所以还要简单修改一下,分辨一下中断源是什么

    1. do_irq:
    2. stmdb sp!, {r0-r12}
    3. ldr r0,=0X4A000014/*INTOFFSET*/
    4. ldr r1,[r0]
    5. cmp r1,#10
    6. beq tick
    7. sub lr, lr, #4
    8. stmdb sp!, {lr}
    9. /* 处理irq异常 */
    10. bl handle_irq_c
    11. /* 恢复现场 */
    12. ldmia sp!, {r0-r12, pc}^ /* ^会把spsr_irq的值恢复到cpsr里 */
    13. tick:
    14. ldmia sp!, {r0-r12}
    15. b vTickISR

    INTOFFSET如果为10则代表了timer0中断。

    4. Makefile

    1. objs = boot.o main.o tasks.o timers.o list.o queue.o event_groups.o sdram.o uart.o interrupt.o
    2. objs += ./portable/MemMang/heap_4.o
    3. objs += ./portable/ARM920T/port.o
    4. objs += ./portable/ARM920T/portISR.o
    5. CC = arm-linux-gcc
    6. CFLAGS = -I /usr/local/arm/4.3.2/arm-none-linux-gnueabi/libc/usr/include/
    7. CFLAGS += -I ./portable/ARM920T
    8. CFLAGS += -I ./include
    9. CFLAGS += -I .
    10. all: $(objs)
    11. 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
    12. arm-linux-objcopy -O binary -S s3c2440.elf s3c2440.bin
    13. clean:
    14. rm *.bin *.o *.elf *.dis
    15. distclean:
    16. rm $(dep_files)
    17. %.o : %.c
    18. $(CC) -march=armv4t -c $(CFLAGS) -o $@ $<
    19. %.o : %.S
    20. $(CC) -c -o $@ $<

    Makefile也很简单,无非就是把所有文件连起来。

    5. 创建任务与测试

    创建两个简单的任务用于测试,一个打印T2 run,一个打印T1 run和一个变量,成功则交替执行。

    1. void vTask1( void *pvParameters )
    2. {
    3. const char *pcTaskName = "T1 run\r\n";
    4. char i='A';
    5. /* 任务函数的主体一般都是无限循环 */
    6. for( ;; )
    7. {
    8. i++;
    9. /* 打印任务1的信息 */
    10. puts( pcTaskName );
    11. //GPFDAT=~GPFDAT;
    12. put_char(i);
    13. puts("\r\n");
    14. delay(500000);
    15. }
    16. }
    17. void vTask2( void *pvParameters )
    18. {
    19. const char *pcTaskName = "T2 run\r\n";
    20. /* 任务函数的主体一般都是无限循环 */
    21. for( ;; )
    22. {
    23. /* 打印任务1的信息 */
    24. puts( pcTaskName );
    25. //GPFDAT=~GPFDAT;
    26. delay(500000);
    27. }
    28. }

    测试结果: 

     完整代码已上传,发文时还未过审,有需要至我主页查看自取。

  • 相关阅读:
    Qt软件发布(版本信息,Release版程序,代码打包,制作安装包)
    一文巩固Spring MVC的Bean加载机制
    【云原生之k8s】k8s资源限制以及探针检查
    CSS 3之背景属性
    【scikit-learn基础】--『监督学习』之 决策树分类
    基于Python flask 的豆瓣电影评分可视化,豆瓣电影评分预测系统
    Windows 10 没有【休眠】选项的配置操作
    1023 Have Fun with Numbers
    力扣刷题篇之数与位2
    【Linux基础】Linux的基本指令使用(超详细解析,小白必看系列)
  • 原文地址:https://blog.csdn.net/freestep96/article/details/126671445