[xhOS]04OS点亮LED--可能是彻底理解操作系统最容易的一次

我们已经可以用裸机点亮LED了,接下来我们要使用OS点亮LED了。

现在的问题是我们还没有OS,哈哈哈哈哈哈,为了学习,还是从0写一个XHOS吧。

我们是为了介绍OS而介绍OS,正常流程应该是先说说裸机和OS的优缺点(详情点击这里),然后再引入OS。

现在假设大家已经知道了一些必要的知识,来看看我们的OS(当然是非常简单的)。

.
├── Makefile    // 编译脚本
├── start.s     // 启动文件
├── user_main.c // 用户代码,main函数在此
├── xhos.c      // OS的实现
├── XHOS.h      // OS头文件,要使用xhos要包含该头文件
└── xhos_stm32.ld  // 链接脚本

这里面的文件比较多,牵扯到的内容很丰富,先列举出来,然后慢慢解释。

 

先看user_main.c

// user_main.c 
/*
 * XHOS 一个为了学习而生的OS
 *  官方网站:findxiaohei.xyz
 */


// XHOS创建闪烁LED task

#include "XHOS.h"

/* 方便C语言操作寄存器 */
#define RCC_BASE 0x40021000
#define RCC_CR (*(volatile unsigned long *)(RCC_BASE))
#define RCC_APB2ENR (*(volatile unsigned long *)(RCC_BASE+0x18))

#define GPIOC_BASE 0x40011000
#define GPIOC_CRH   (*(volatile unsigned long *)(GPIOC_BASE+0x04))
#define GPIOC_ODR   (*(volatile unsigned long *)(GPIOC_BASE+0x0c))


void Delay(uint32_t nCount)
{
	for(; nCount != 0; nCount--);
}


/* 定义task */
void task_func(void)
{
	int i;

	RCC_APB2ENR = 1<<4;  //portC 时钟使能

	GPIOC_CRH = 3<<20;  // pin13 输出模式
	GPIOC_ODR = 0;
	while(1){
		GPIOC_ODR  |= 1<<13;   //pin13 高电平
		Delay(100000);
		GPIOC_ODR &= ~(1<<13);  //pin13 低电平
		Delay(100000);
	}
}

int main()
{
	// 创建task
	osTaskCreate(task_func);

	// 启动OS调度
	osStartScheduler();

	// 不会到达这里
	while (1);

	return 0;
}

这个是用户的入口文件,在[xhOS]03裸机点亮LED 中main函数直接闪烁LED,而有OS的情况下,main函数通过OS提供的接口osTaskCreate创建了一个闪烁LED的任务(task_func).

任务是一个永不退出的死循环。

然后通过osStartScheduler启动OS的调度,本节中就是启动闪烁LED的task。

 

start.s

start.s负责从上电到执行main函数的准备工作

// start.s
/*
 * XHOS 一个为了学习而生的OS
 *  官方网站:findxiaohei.xyz
 */

/* start.S */
.syntax unified /* 统一汇编语法 UAL*/
.cpu cortex-m3
.fpu softvfp
.thumb /* thumb 指令集 */

/* 全局符号 */
.global Vectors

/*****************启动代码**********************/
/* 指定当前段名 */
.section    .text.Reset_Handler
        .weak   Reset_Handler
        .type   Reset_Handler, %function

/* 入口点 */
Reset_Handler:
/* 将数据从 flash copy 到 SRAM */
  movs  r1, #0
  b     LoopCopyDataInit

CopyDataInit:
        ldr     r3, =_sidata
        ldr     r3, [r3, r1]
        str     r3, [r0, r1]
        adds    r1, r1, #4

LoopCopyDataInit:
        ldr     r0, =_sdata
        ldr     r3, =_edata
        adds    r2, r0, r1
        cmp     r2, r3
        bcc     CopyDataInit
        ldr     r2, =_sbss
        b       LoopFillZerobss
/* 用0初始化BSS段 */
FillZerobss:
        movs    r3, #0
        str     r3, [r2], #4

LoopFillZerobss:
        ldr     r3, = _ebss
        cmp     r2, r3
        bcc     FillZerobss
/* 调用用户实现的main函数 (这就是为什么用户程序以main开始了。当然也可以是别的)*/
        bl      main
        bx      lr
.size   Reset_Handler, .-Reset_Handler



/* 所有数据段的开始,在链接脚本中定义,从这里开始拷贝数据到SRAM */
.word   _sidata
/* .data section的开始地址. 链接脚本中定义 */
.word   _sdata
/* .data section的结束地址. 链接脚本中定义 */
.word   _edata
/* .bss section的开始地址. 链接脚本中定义 */
.word   _sbss
/* .bss section的结束地址. 链接脚本中定义 */
.word   _ebss
/* 栈顶. 链接脚本中定义 */
.word   _estack




/*****************异常向量表**********************/

/* 指定向量表所在的段名 */
.section        .isr_vector,"a",%progbits
.type   Vectors, %object
.size   Vectors, .-Vectors


Vectors:
    .word _estack /*sram 最后*/
    .word Reset_Handler
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word 0
    .word SVC_Handler
    .word 0
    .word 0
    .word 0 //PendSV_Handler
    .word 0 //SysTick_Handler


这里比裸机点亮LED多了很多内容。

 

XHOS.h

定义了OS的API和基本的数据结构。

// XHOS.h 
/*
 * XHOS 一个为了学习而生的OS
 *  官方网站:findxiaohei.xyz
 */

#ifndef XHOS_H
#define XHOS_H

/*
 * XHOS的头文件,使用XHOS必须包含该文件
 */

#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>

typedef uint32_t address_t;
typedef long Base_t;
typedef unsigned long UBase_t;

// 定义函数指针,用于任务创建。
typedef void (*TaskFunction_t)( void );

// 任务控制块,就是传说中的TCB
typedef struct TaskControlBlock
{
       volatile address_t    *TopOfStack;
}TCB_t;


#define NULL 0
///

// 创建task
void osTaskCreate(TaskFunction_t task_func);

// 启动OS调度
void osStartScheduler();


#ifdef __cplusplus
}
#endif

#endif

 

xhos.c

xhOS的主要实现部分,包括task的创建,和如何切换到task。

// xhos.c 
/*
 * XHOS 一个为了学习而生的OS
 *  官方网站:findxiaohei.xyz
 */


#include "XHOS.h"

/*
 * XHOS 的实现 v0.1, 最小OS
 */

#define devCSB_VTOR                                     ( * ( ( volatile uint32_t * ) 0xE000ED08 ) )

#define devINITIAL_XPSR					( 0x01000000UL )
#define devSTART_ADDRESS_MASK				( ( address_t ) 0xfffffffeUL )

TCB_t * g_Current;
extern void test_err();
TCB_t task1tcb;
Base_t task_stack[129];

address_t *devInitialiseStack( address_t *TopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
	/* 制造现场,好像该任务是被切换除去,用同样的方法可以切入 */
	TopOfStack--;
	*TopOfStack = devINITIAL_XPSR;       /* xPSR */
	TopOfStack--;
	*TopOfStack = ( ( address_t ) pxCode ) & devSTART_ADDRESS_MASK;    /* PC */
	TopOfStack--;
	*TopOfStack = ( address_t ) 0;        /* LR */
	TopOfStack -= 5;      /* R12, R3, R2 and R1. */
	*TopOfStack = ( address_t ) pvParameters;   /* R0 */
	TopOfStack -= 8;      /* R11, R10, R9, R8, R7, R6, R5 and R4. */

	return TopOfStack;
}

void SVC_Handler( void )
{
	__asm volatile (
			"       ldr r3, CurrentTCB          \n" /* 获取task的TCB */
			"       ldr r1, [r3]                \n"
			"       ldr r0, [r1]                \n" /* TCB 中第一个就是栈顶 */
			"       ldmia r0!, {r4-r11}         \n" /* 将栈中保存的寄存器值恢复到寄存器 */
			"       msr psp, r0                 \n" /* PSP指向用户task的栈顶 */
			"       isb                         \n"
			"       mov r0, #0                  \n"
			"       msr     basepri, r0         \n"
			"       orr r14, #0xd               \n" /* 用于使CPU返回用户模式 */
			"       bx r14                      \n"
			"                                   \n"
			"       .align 4                    \n"
			"CurrentTCB: .word g_Current          \n"
		       );
}
/*-----------------------------------------------------------*/

static void StartFirstTask( void )
{
	// 重定位中断向量
	devCSB_VTOR = 0x08000000;
	__asm volatile(
			" ldr r0, =0xE000ED08      \n" /* 从NVIC 的VTOR中获取中断向量表,向量表的第一个就是栈顶 */
			" ldr r0, [r0]             \n"
			" ldr r0, [r0]             \n" 
			" msr msp, r0              \n" /* 将msp设置为栈顶 */
			" cpsie i                  \n" /* 打开中断 */
			" cpsie f                  \n"
			" dsb                      \n"
			" isb                      \n" 
			" svc 0                    \n" /* 使用SVC启动第一个task */
			" nop                      \n"
		      );
}


// 创建task
void osTaskCreate(TaskFunction_t task_func){
	g_Current = &task1tcb;
	g_Current->TopOfStack = devInitialiseStack(task_stack+128, task_func, NULL);

}

// 启动OS调度
void osStartScheduler(){

	/* Start the first task. */
	StartFirstTask();

	/* Should not get here! */
	return ;
}

 

xhos_stm32.ld

各部分是如何链接成一个可执行的文件。

/* xhos_stm32.ld */
/*
 * GCC linker script for STM32 (ARM Cortex-M).
 */


/* 定义memory,根据实际设备调整起始地址和大小 */
MEMORY
{
    flash (rx) : ORIGIN = 0x08000000, LENGTH = 64K
    ram  (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

/*
 * The stack starts at the end of RAM and grows downwards. Full-descending
 * stack; decrement first, then store.
 */
/* 根据memory计算栈顶,因为ARM Cortex-M是满减栈,所以是ram最高地址 */
_estack = ORIGIN(ram) + LENGTH(ram);

/* 输出段 */
SECTIONS
{
    /* 最开始是异常向量,其中有栈顶和入口点等 */
    .isr_vector :
    {
        __isr_vector_start__ = .;
        KEEP(*(.isr_vector)) /* 使用'KEEP'是为了防止链接器发现没人直接使用本段而不链接 */
        ASSERT(. != __isr_vector_start__, "The .isr_vector section is empty");
    } >flash


    /* Text section (code and read-only data) */
    .text :
    {
	/* 4字节对齐地址 */
        . = ALIGN(4);
        _stext = .; /* 当前地址赋值给 _xtext 变量*/
        *(.text*)   /* code ,所有 输入段为".text*"的段都被链接到这里  */
        *(.rodata*) /* read only data */
        . = ALIGN(4);
        _etext = .;
    } >flash



    /* 已经初始化的 data section. 启动代码将本段从FLASH 拷贝到 RAM */
    _sidata = .;
    .data : AT(_sidata) /* 链接到FLASH 的地址 _sidata,但需要拷贝的RAM中运行 */
    {
        . = ALIGN(4);
        _sdata = .; /* 这里就是RAM中的偏移了,从0x20000000开始 */
        *(.data*)
        . = ALIGN(4);
        _edata = .;
    } >ram /* 最终运行在ram中 */


    /* 未初始化的 data section. 启动代码统一置0 */
    .bss :
    {
        . = ALIGN(4);
        _sbss = .;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = .;
    } >ram

}

 

Makefile

# file :Makefile
All:
	arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c start.s -o start.o
	arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c user_main.c  -o main.o
	arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c xhos.c -o xhos.o
	arm-none-eabi-ld -Txhos_stm32.ld start.o main.o xhos.o -o osLED
	arm-none-eabi-objcopy -O binary -S osLED osLED.bin
	arm-none-eabi-objdump -D -m arm  osLED > osLED.dis
clean:
	-rm *.o osLED*
 
flash: clean All
	sudo stm32flash -w osLED.bin -v -g 0 /dev/ttyUSB0


 

把这6个文件创建好,执行make命令,就能编译一个可以使用task闪烁LED的最小OS了。

各个文件已经有了很多注释,但是对于新手来说,还是太复杂了,我后续将详细介绍每一个文件。

 

访问findxiaohei.xyz获取更多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值