|
好吧,通过这几天的努力终于可以写一点关于裸机的东西了,raw-os的包还是蛮大的不太适合我这种零基础的学习,纯粹为了学习这里使用ucosii移植的原始包来学习,其实raw-os移植也有参考这份代码的吧。后面有机会再去学习一下raw-os咯,搞懂了移植才不会有问题,当然了我还是希望后面有机会去移植FreeRTOS,毕竟这款我已经使用好久了,相对熟悉些。在分析uCOSII移植的过程中比对过raw-os感觉和freertos很类似,比如说这个函数raw_start_first_task,熟悉的人会知道freertos也有类似的porting接口vPortStartFirstTask。好了不多说了开始我的分析吧,还是有点东西的建议大家好好看看,也算是初学者教程吧。
首先我们简单看下启动的过程:
1)start.S文件中程序段定义
- ;-------------------------------------------------------------------------------
- ; 程序起始段定义RESET
- ;-------------------------------------------------------------------------------
- PRESERVE8 ;字节对齐
- CODE32 ;ARM代码
- AREA RESET,CODE,READONLY ;reset名,代码段,只读
- ENTRY ;程序入口
- b HANDLE_ResetInit
-
复制代码
2)分散加载描述文件
如果有Scatter file则链接器不会生成类似|Image$$RW_RAM1$$ZI$$XXXX|的符号,这些可以在MDK中设置
这里可以使用它的设置,这样可以使用如下的方式导入符号,具体的可以参考start.S中的InitRORWZI初始化。
- IMPORT |Image$RW_RAM1$ZI$Base|
- IMPORT |Image$RW_RAM1$ZI$Limit|
复制代码
好了这里重点说一下分散加载文件S3C2416.srt,在MDK如下地方添加
文件内容如下
- LR_ROM1 0x30800000 0x2800000 { ; load region size_region
- RW_ROM1 0x30800000 0x2800000 { ; load address = execution address
- *.o (RESET, +First)
- .ANY (+RO +RW)
-
- }
- RW_RAM1 0x33000000 0x1000000 {
- .ANY(+ZI)
- }
-
- }
复制代码注意这里的RESET符号,当然了起始地址是0x30800000,所以我们需要把bin文件加载到这个位置然后go这个地址就可以run了。
你可以修改这个,不过还有一些别的需要注意的。
3)HANDLER_ResetInit函数
- ;-------------------------------------------------------------------------------
- ; 复位初始化
- ;-------------------------------------------------------------------------------
- HANDLE_ResetInit
- mrc p15,0,r0,c1,c0,0
- ; disable MMU
- ldr r1,=~(R1_M :OR R1_I :OR R1_C :OR R1_W)
- and r0,r0,r1
- mcr p15,0,r0,c1,c0,0
- mov r0, #0
- ; invalidate I,D caches on v4
- mcr p15, 0, r0, c7, c7
- ; drain write buffer on v4
- mcr p15, 0, r0, c7, c10, 4
- ; invalidate I,D TLBs on v4
- mcr p15, 0, r0, c8, c7
- ; svc mode
- mov r1, #0xd3
- msr cpsr_cxsf,r1
- ldr sp,=0x33fe4000
-
- ; set to high vector address
- ; read c1 to r5
- MRC p15,0,r5,c1,c0,0
-
- ; set bit 13 of c1
- orr r5, r5, #0x2000
-
- ; write r5 to c1
- mcr p15, 0, r5, c1, c0, 0
- ;---------------------------
- ; disable the watchdog timer.
- ;---------------------------
-
- ldr r0, =WTCON
- mov r1, #0
- str r1, [r0]
- ;---------------------------
- ; Interrupt configuration.
- ;---------------------------
-
- ; mask all first-level interrupts.
- ldr r0, =INTMSK
- ldr r1, =0xffffffff
- str r1, [r0]
- ; mask all second-level interrupts.
- ldr r0, =INTSUBMSK
- ldr r1, =0xffffffff
- str r1, [r0]
-
- <b><font color="#ff0000"> bl make_mmu_table</font></b>
- ; MMU page table
- <b><font color="#ff0000"> ldr r1,=0x30100000</font></b>
- mcr p15, 0, r1, c2, c0, 0
- ; MMU_SetDomain
- ldr r0,=0x55555555
- mcr p15,0,r0,c3,c0,0
-
- ; MMU_SetProcessId
- ldr r0,=0
- mcr p15,0,r0,c13,c0,0
-
- ; MMU_Enable
- ldr r1, =(R1_M :OR R1_I :OR R1_C :OR R1_W :OR R1_V:OR R1_iA:OR R1_nF)
- mrc p15,0,r0,c1,c0,0
- orr r0,r0,r1
- mcr p15,0,r0,c1,c0,0
- nop
- nop
- <font color="#ff0000"><b> ldr pc,=Virtual_Start</b></font>
- nop
- nop
复制代码注意我上面标红的三个地方,下面我们一个个分析。
4)make_mmu_table
- /**
- * @brief make_mmu_table
- * @note mmu表初始化
- * @param none
- * @retval none
- */
-
- void make_mmu_table(void)
- {
- int i;
-
- unsigned int *addr = (unsigned int *)MMU_PAGE_TABLE_START;
-
- /* 虚地址0x00000000-0x30000000映射到0x00000000-0x30000000 */
- for ( i=(MMU_IO_AREA_START>>20); i<(MMU_MEM_AREA_START_3>>20); i++ ) {
-
- addr[i] = SECTION_ENTRY(i,1,1,0,0);
- }
-
-
- /*
- 1)WRITE THROUGH
- CPU向CACHE写入数据时,同时向MEMORY也写一份,使CACHE和MEMORY的数据保持一致.
- 优点是简单,缺点是每次都要访问MEMORY速度比较慢.
- 2)POST WRITE
- CPU更新CACHE数据时,把更新的数据写入到一个更新缓冲器,在合适的时候才对MEMORY进行更新.
- 这样可以提高CACHE访问速度,但是在数据连续被更新两次以上的时候,缓冲区将不够使用,被迫同时更新MEMORY.
- 3)WRITE BACK
- CPU更新CACHE时,只是把更新的CACHE区标记一下,并不同步更新MEMORY.
- 只是在CACHE区要被新进入的数据取代时才更新MEMORY,考虑到很多时候CACHE存入的是中间结果,没有必要同步更新MEMORY.
- 优点是CPU执行的效率提高,缺点是实现起来技术比较复杂.
- */
-
- /* 映射WRITE BACK CACHE从0x30000000-0x33800000 */
- for ( i=(MMU_MEM_AREA_START_3>>20); i<(LCD_DMA_BUFFER_PA_BASE >> 20); i++ ) {
-
- addr[i] = SECTION_ENTRY_CACHE(i,0,1,1,1,1);
- }
- /* 映射WRITE THROUGH CACHE从0x33800000-0x33b00000 */
- for ( i=(LCD_DMA_BUFFER_PA_BASE>>20); i<(LCD_DMA_BUFFER_PA_END >> 20); i++ ) {
- addr[i] = SECTION_ENTRY_CACHE(i,0,1,1,1,0);
- }
- /* 地址0x33b00000-0x34000000未被作为CACHE使用,可用于硬件DMA */
- for ( i=(LCD_DMA_BUFFER_PA_END>>20); i<(MMU_MEM_AREA_START_3_4 >> 20); i++ ) {
-
- addr[i] = SECTION_ENTRY_CACHE(i,0,1,1,0,0);
- }
-
- /* 虚地址0x34000000-0x60000000映射到0x34000000-0x60000000 */
- for ( i=(MMU_MEM_AREA_START_3_4>>20); i<(MMU_RESERVED1_START>>20); i++ ) {
-
- addr[i] = SECTION_ENTRY(i,1,1,0,0);
- }
-
-
- /* 虚地址0x60000000-0xc0000000禁止访问 */
- for ( i=(MMU_RESERVED1_START>>20); i<(MMU_MEM_AREA_START>>20); i++ ) {
-
- addr[i] = 0x00000000;
- }
-
-
- /* 系统内存被映射到0xc0000000-0xc4000000 */
- for ( i=(MMU_MEM_AREA_START>>20); i<(MMU_RESERVED2_START>>20); i++ ) {
-
- addr[i] = SECTION_ENTRY((i-((MMU_MEM_AREA_START>>20)-(MMU_MEM_AREA_START_3>>20))),1,1,0,0);
- }
-
-
- /* 虚地址0xc4000000-0xffff0000禁止访问,虚地址0xffff0000-0xffffffff映射到0x30000000-0x300fffff */
- for ( i=(MMU_RESERVED2_START>>20); i<0x1000; i++ ) {
- #if ENABLE_EXCEPTION_MMU
- if ( i >= (MMU_EXCEPTION_START >> 20) ) {
- addr[i] = SECTION_ENTRY( (i-((MMU_EXCEPTION_START>>20)-(MMU_PHYSICAL_EXCEPTION_START>>20))), 1, 1, 0, 0);
- } else {
- /* 禁止访问 */
- addr[i] = 0x00000000;
- }
- #else
- if ( i == (MMU_EXCEPTION_START >> 20) ) {
- addr[i] = SECTION_ENTRY( (i-((MMU_EXCEPTION_START>>20)-(MMU_PHYSICAL_EXCEPTION_START>>20))), 1, 1, 0, 0);
- break;
- } else {
- /* 禁止访问 */
- addr[i] = 0x00000000;
- }
- #endif
- }
-
- }
复制代码这个我就不文字分析了,看我上面的描述,这个地方可以参考三个文字,如下
/*
NOTE:
http://blog.sina.com.cn/s/blog_9447904001015h38.html
http://www.it165.net/embed/html/201408/2633.html
DDI0198E_arm926ejs_r0p5_trm.pdf
*/
注意红色的这个,这是最好的最有根据的,ARM官方926技术手册。
这里在啰嗦一下,我看玩raw-os下面的描述真的不太理解,看了很多资料才明白的,可以参考上面的网址。
其实需要理解以下两个函数
- #define SECTION_ENTRY(base, ap, d, c, b) \
- ((base << 20)|(ap<<10)|(d<<5)|(1<<4)|(c<<3)|(b<<2)|(1<<1))
- #define SECTION_ENTRY_CACHE(base, tex, ap, d, c, b) \
- ((base << 20)|(tex << 12)|(ap<<10)|(d<<5)|(1<<4)|(c<<3)|(b<<2)|(1<<1))
复制代码注意可以对比uboot中的汇编代码来理解,主要依据是 DDI0198E_arm926ejs_r0p5_trm.pdf
我简单的说说吧:
- 1、针对SECTION_ENTRY,ap是控制访问权限,读写啊用户啊这些,后面三个一般d=1 ,c=0 ,b=0,细节参考那个文档吧。
- 2、针对SECTION_ENTRY_CACHE,ap是控制访问权限
- 1)后面d=1,c=1,b=1表示映射WRITE BACK CACHE
- 2)后面d=1,c=1,b=0表示映射WRITE THROUGH CACHE
- 3)后面d=1,c=0,b=0表示未被作为CACHE使用
复制代码
好了最终是这样的如下描述
- /*
- 虚地址0x00000000-0x30000000映射到0x00000000-0x30000000
- 虚地址0x34000000-0x60000000映射到0x34000000-0x60000000
- 虚地址0xc0000000-0xc4000000映射到0x30000000-0x34000000
- 虚地址0x60000000-0xc0000000禁止访问
- 虚地址0xc4000000-0xffff0000禁止访问
- 虚地址0xffff0000-0xffffffff映射到0x30000000-0x30100000
-
- 0x30000000-0x30100000---异常向量表使用
- 0x30100000-0x30200000---MMU的PAGE_TABLE使用
- 0x33800000-0x33b00000---LCD的DMA缓冲使用
- 0x33b00000-0x34000000---CACHE没有打开供给外设DMA使用
- 0x30200000-0x30800000---可用于堆栈
- */
复制代码说道堆栈,我想说还需要自行实现malloc,可以在前面的分散加载文件S3C2416.srt中添加heap相关的,需要进一步熟悉srt的语法。后面我会实现分享给大家,当然raw-os作者的做法也不错,值得学习。
注意并不是0x00000000-0x60000000的虚地址和硬件地址是一一映射的中间有个断层,也就是DRAM的区间0x30000000-0x34000000
好了看完MMU映射后我们继续看,后面的第二个红色,指定这个table在0x30100000这个地址。
继续看下去到虚拟启动。
5)Virtual_Start
- ;-------------------------------------------------------------------------------
- ; 虚拟启动
- ;-------------------------------------------------------------------------------
- Virtual_Start
- nop
- nop
- nop
- nop
- ;FIQ mode
- mov r1, #0x11
- msr cpsr_cxsf,r1
- ldr sp,=0x30200000
-
- ;IRQ mode
- mov r1, #0x12
- msr cpsr_cxsf,r1
- ldr sp,=0x301fc000
- ;SYS mode
- mov r1, #0x1f
- msr cpsr_cxsf,r1
- ldr sp,=0x301f8000
-
- ;ABT mode
- mov r1, #0x17
- msr cpsr_cxsf,r1
- ldr sp,=0x301f4000
- ;UDF mode
- mov r1, #0x1b
- msr cpsr_cxsf,r1
- ldr sp,=0x301f0000
-
- ;SVC mode
- mov r1, #0xd3
- msr cpsr_cxsf,r1
- ldr sp,=0x301ec000
- BL InitRORWZI
-
- <b><font color="#ff0000"> LDR PC,=rtos_main</font></b>
- LOOP
- b LOOP
复制代码注意看这里跳转到c程序的main,注意我测试的结果是不能使用标准的main作为名称,会有报错的,可以使用大写Main来替代。
6)exception_vector
- ;-------------------------------------------------------------------------------
- ; 异常向量表
- ;-------------------------------------------------------------------------------
- exception_vector
- LDR PC,=reset_exception_process ;复位异常
- LDR PC,=undef_exception_process_asm ;未定义指令异常
- LDR PC,=swi_exception_process ;软中断异常
- LDR PC,=pabort_exception_process ;取指中止异常
- LDR PC,=dabort_exception_process ;数据中止异常
- LDR PC,. ;保留
- <b><font color="#ff0000"> LDR PC,=OS_CPU_IRQ_ISR ;保留</font></b>
- LDR PC,=fiq_exception_process ;FIQ中断异常
复制代码注意看红色这句需要指向我RTOS的中断函数。
- ;*******************************************************************************
- ; OS_CPU_IRQ_ISR
- ;*******************************************************************************
- OS_CPU_IRQ_ISR
- STMFD SP!, {R1-R3}
- MOV R1, SP
- ADD SP, SP, #12
- SUB R2, LR, #4
- MRS R3, SPSR
- MSR CPSR_cxsf, #SVCMODE:OR:NOINT
- ;//take care! this sp is not the one before!svc mode's SP
- STMFD SP!, {R2}
- STMFD SP!, {R4-R12, LR}
- LDMFD R1!, {R4-R6}
- STMFD SP!, {R4-R6}
- STMFD SP!, {R0}
- STMFD SP!, {R3}
- LDR R4, =OSTCBCur
- LDR R5, [R4]
- LDR SP, [R5]
- BL OSIntEnter
-
- MSR CPSR_c, #IRQMODE:OR:NOINT
- <b><font color="#ff0000"> BL irq_process
- </font></b>
- MSR CPSR_c, #SVCMODE:OR:NOINT
- BL OSIntExit
- LDMFD SP!, {R4}
- MSR SPSR_cxsf, R4
- LDMFD SP!, {R0-R12, LR, PC}^
-
-
复制代码
这里红色的地方会跳转到当前的中断位置,算是用户中断管理吧,uCOSII不太熟悉,不知道是否有OS级别中断和非OS级别中断,FreeRTOS是有一个宏定义的数值来控制的,体会起来挺复杂的。
7)rtos_main
- /**
- * @brief rtos_main
- * @note óû§Ö÷3ìDò
- * @param none
- * @retval none
- */
- int rtos_main(void)
- {
- inerrupt_vector_init();
- memcpy( (void *)0xffff0000, (void *)(exception_vector), 128 );
- MMU_EnableDCache();
- MMU_EnableICache();
-
- OSInit();
- InitTask();
- OSStart();
-
- while(1);
- }
复制代码 终于到了我们的main了,注意需要把start.S中的exception_vector拷贝到我们指定的位置0xffff0000-0xffffffff(注意这个虚地址对应我们的DRAM起始地址处0x30000000-0x30100000)。
8)定时器初始化,用于系统节拍
- /**
- * @brief TIMER0_Init
- * @note TIMER0初始化
- * @param none
- * @retval none
- */
- void TIMER0_Init(void)
- {
- /*
- * TIMER INPUT_CLK = PCLK/{Precaler+1}/{Divider_Value}
- *
- * TIMER INPUT_CLK = PCLK/(15+1)/4 = PCLK/64
- *
- * TCNTB0_REG = INPUT_CLK/OS_TICKS_PER_SEC = get_PCLK()/(64*OS_TICKS_PER_SEC)
- */
-
-
- /* 使用TIMER0前,清除相关配置 */
- TCON_REG &= ~0x0F;
- TCFG0_REG &= ~0xFF;
- TCFG1_REG &= ~0x0F;
- TIMER0_ISR_Init();
- register_irq(10, TIMER0_ISR_Service);
-
- /* TIMER0_Prescaler=15 */
- TCFG0_REG |= 15;
-
- /* TIMER0_Divider_Value=1/4 */
- TCFG1_REG |= 0x01;
- TCNTB0_REG = get_PCLK()/(64*OS_TICKS_PER_SEC);
- /* 更新TCNTB0&TCMPB0 */
- TCON_REG |= 0x02;
-
- /* 手动更新位必须在下次写前清除 */
- TCON_REG &= ~0x02;
-
- /* 自动重装,启动定时器 */
- TCON_REG |= 0x09;
-
- }
复制代码 这里我想说一下这个,关于TCNTB0寄存器的设置,应该是按照我的方式设置,也许我是错的,希望大家指出,当然了raw-os的作者也不是这样的和原版本的uCOSII一样。
最后大家简单看下我的platform.h头文件吧,不是别的,希望大家的态度积极认真一些,对自己真的很有帮助,虽然我的代码很多参考别人或者来自他人,但是对于这些代码要有根有据,自己能说服自己也是好的,当然我也喜欢用自己的代码格式来排版他们,看着舒服,愿意一点点的维护他们,他们很美好。
- /**
- *******************************************************************************
- * @file platform.h
- * @author camel.shoko
- * @version v1.1
- * @date 2014/08/20
- * @brief 平台相关定义
- * @copyright camel.shoko@gmail.com
- *******************************************************************************
- */
- #ifndef PLATFORM_H_
- #define PLATFORM_H_
- /* Includes ------------------------------------------------------------------*/
- #include "stdlib.h"
- #include "2416_reg.h"
- #include "os_cfg.h"
- #include "ucos_ii.h"
- /* Defines -------------------------------------------------------------------*/
- /* 小端模式 */
- #define LITTELE_ENDIAN 1
- /* 软件版本定义1.10 */
- #define VER_MAJOR 1
- #define VER_MINOR 1
- #define VER_PATCH 0
- #define S3C2416_ARM926_PORT 1
- #define USE_STDARG_PRINTF 1
- #define ENALBE_IAR_LIB_PRINT 0
- /* Macros --------------------------------------------------------------------*/
- /* 调度器和协程 */
- #ifdef CRTHREAD_SCHED
- #define MAX_CRTHREAD 10
- #endif
- /* 硬件平台PORTING */
- #if S3C2416_ARM926_PORT
- #define UCOSII_RTOS_PORTING 1
- #endif
- /* 指定调试方法 */
- #if USE_IAR_LIB_PRINTF == 1
- #define ENALBE_IAR_LIB_PRINT 1
- #define ENABLE_STDARG_PRINTF 0
- #elif USE_STDARG_PRINTF == 1
- #define ENALBE_IAR_LIB_PRINT 0
- #define ENABLE_STDARG_PRINTF 1
- #else
- #error "You must define the printf method!"
- #endif
- #if ENABLE_STDARG_PRINTF
- #include "printf_stdarg.h"
- #endif
- #define _STR(x) #x
- #define STR(x) _STR(x)
- #define VERSION STR(VER_MAJOR)"."STR(VER_MINOR)"."STR(VER_PATCH)
- #ifdef __cplusplus
- #define DECLARE_EXTERN_C_BEGIN extern "C" {
- #define DECLARE_EXTERN_C_END }
- #else
- #define DECLARE_EXTERN_C_BEGIN
- #define DECLARE_EXTERN_C_END
- #endif
- #ifndef NULL
- #ifdef __cplusplus
- #define NULL ( 0 )
- #else
- #define NULL ( (void *)0 )
- #endif
- #endif
- #if defined(DEBUG) || defined(_DEBUG)
- #define DEBUG(x) my_printf(x"\r\n")
- #define DEBUGN(x) my_printf(x)
- #define DEBUG0(x) my_printf(x"\r\n")
- #define DEBUG1(x, y) my_printf(x"\r\n", y)
- #define DEBUG2(x, y, z) my_printf(x"\r\n", y, z)
- #define DEBUG3(x, y, z, a) my_printf(x"\r\n", y, z, a)
- #define DEBUG4(x, y, z, a, b) my_printf(x"\r\n", y, z, a, b)
- #define DEBUG_HEX(pData, nLen) \
- do \
- { \
- int i = 0, len = nLen; \
- uint8_t *pByte = (uint8_t *)pData; \
- for (; i<len; i++) \
- { \
- my_printf("%02X ", pByte[i]); \
- } \
- my_printf("\r\n"); \
- } \
- while (0);
- #else
- #define DEBUG(x)
- #define DEBUG0(x)
- #define DEBUGN(x)
- #define DEBUG1(x, y)
- #define DEBUG2(x, y, z)
- #define DEBUG3(x, y, z, a)
- #define DEBUG4(x, y, z, a, b)
- #define DEBUG_HEX(pData, nLen)
- #endif
-
- #if LITTELE_ENDIAN
- #define SWAP16(A) ((((WORD)(A) & 0xFF00) >> 8) | (((WORD)(A) & 0x00FF) << 8) )
-
- #define SWAP32(A) \
- ((((DWORD)(A) & 0xff000000) >> 24) | \
- (((DWORD)(A) & 0x00ff0000) >> 8) | \
- (((DWORD)(A) & 0x0000ff00) << 8) | \
- (((DWORD)(A) & 0x000000ff) << 24))
- #else
- #define SWAP16(A) ( A )
- #define SWAP32(A) ( A )
- #endif
-
-
- <font color="#0000ff">#if UCOSII_RTOS_PORTING
- #define msleep(ms) OSTimeDly((1000*ms)/OS_TICKS_PER_SEC)
- #endif</font>
- /* Global Function Prototype -------------------------------------------------*/
- extern void MMU_EnableDCache(void);
- extern void MMU_EnableICache(void);
- extern void exception_vector(void);
- #endif /*PLATFORM_H_*/
- /****************** (C) COPYRIGHT 2013 Camle.shoko ***********END OF FILE******/
复制代码
哈哈,吐槽一下,我在移植uCOSII的时候,测试中使用 OSTimeDly我以为是ms为单位的呢,结果是时间不准,我还以为是系统tick有问题呢,太久没用ucos都忘了是多少个系统tick时间的含义。用惯了freertos了,他vTaskDelay就是实际的多久时间,这里我也添加了个宏,便于使用,可参考上面蓝色部分宏定义。
测试程序:
- /**
- * @brief Task1
- * @note 任务1
- * @param none
- * @retval none
- */
- static void Task1(void *parg)
- {
- (void) parg;
- while (1) {
- DEBUG("# DELAY-TASK");
- OSTimeDly(100);
- }
- }
- /**
- * @brief Task2
- * @note 任务2
- * @param none
- * @retval none
- */
- static void Task2(void *parg)
- {
- (void) parg;
-
- /* 设置GPB1引脚为输出 */
- GPBCON_REG = 0x00000004;
-
- while (1) {
- DEBUG("# BLINK-TASK");
-
- /* 设置GPB1输出低电平 */
- GPBDAT_REG = 0x00000000;
- OSTimeDly(100);
-
- /* 设置GPB1输出高电平 */
- GPBDAT_REG = 0x00000002;
- OSTimeDly(100);
- }
- }
- /**
- * @brief InitTask
- * @note 初始化任务
- * @param none
- * @retval none
- */
- void InitTask(void)
- {
- //初始化代码段
- TIMER0_Init();
- UART0_Init();
-
- //任务创建
- OSTaskCreate(Task1, (void *)1, (void *)&Task1Stk[1023], 4);
- OSTaskCreate(Task2, (void *)2, (void *)&Task2Stk[1023], 8);
- }
复制代码
后面会完善malloc的实现,添加其他组件网络部分和文件系统为主,期待和大家分享。
|
赞赏
-
1
查看全部赞赏
-
|