物聯網實時內核 vnRTOS 免費開源 旗點雲作品

本內核開源免費,歡迎大家下載使用學習,目前內核基礎工作模塊工作正常,有bug可以反饋給我。

內核源碼下載鏈接:https://gitee.com/qidiyun/QDos 

此例程是基於 STM32F407ZG 芯片的,STM32F103 的也差不多,自己移植,或者我有空了再放上來。

 

自制國產實時內核——vnRTOS 所有文檔:

第 1 節 內核介紹

第 2 節 代碼結構

第 3 節 讓內核跑起來

第 4 節 線程的同步與互斥——資源

第 5 節 線程的異步通知

 

問:當前市場上有ucOS、freeRTOS、RT-thread 等內核、還有開源的linux等,爲什麼我們還要自己在開發一套內核呢?

ucOS 是商業收費的,freeRTOS 是一個免費的開源的內核,非常好用。RT-thread也是一款國產的實時內核,非常好用,強烈推薦大家使用 RT-thread。至於爲什麼還要自己開發一套實時內核呢?

一個是源於技術的追求。vnRTOS 是我大學的畢業設計,當時就覺得寫內核是一件很 cool 的事情。接下來會有幾篇文章詳細講解如何自己寫內核,有興趣的同學可以關注一波。

另外一個是技術的儲備。我們寫這個 vnRTOS 不是爲了去取代 ucOS或者 FreeRTOS、RT-thread等。相反,我們覺得其它實時內核做得非常好,很值得我們去學習。而我們開發 vnRTOS 是爲了將來有一天,當我們被別人卡脖子的時候(參考 某爲  和 某米國的故事)。當我們沒有內核可用時,我們有自己的技術,我們有自己的儲備。

簡而言之。。。這個 vnRTOS 就是個備胎。。。(不過備胎雖備胎,但是目前我們用這個 vnRTOS做了個物聯網項目,目前還算穩定,比較適合一些微小型場合)
 

一、前言

嵌入式系統是用來控制或者監視機器、裝置、工廠等大規模設備的系統。大多數嵌入式系統都是由單個程序實現整個控制邏輯,但也有些嵌入式系統還包含操作系統

當前比較流行的嵌入式操作系統有:WinCE嵌入式LinuxVxwork、ucos II 等。但他們都有着各自的缺陷。WinCE嵌入式Linux內核較爲龐大,不適應一些低端的、資源較少的場合,而Vxwork、ucos II雖然都具有微內核這個特點,但版權費較高。

本文試圖自己構建一個簡單微小內核,以便在一些低端的、對系統資源要求嚴格、且成本不高的場合中使用。

本文設計的 vn Kernel採用的是基於優先級的時間片的任務調度思想。而vn 即 John von Neumann。

John von Neumann是一位偉大的數學家。他設計的“馮·諾依曼架構”是計算機架構的一個經典。正是有了這個架構,纔有了今天的計算機,也纔會有了現今嵌入式系統中的核心——微控制器。

本人在此表示深深的敬意。

 

 

二、內核框架:

2.1內核定義:

內核是操作系統最基本的部分。它是爲衆多應用程序提供對計算機硬件的安全訪問的一部分軟件,這種訪問是有限的,並且內核決定一個程序在什麼時候對某部分硬件操作多長時間。內核的分類可分爲單內核和雙內核以及微內核

顯然,一個微小的內核更適合嵌入式領域。因爲在嵌入式系統中,ROM、RAM等資源都特別寶貴,尤其是在一些低端的領域,ROM都只有一百多K,RAM不到一百K,無法運行Win CE、嵌入式Linux這類操作系統。

然而,採用單個程序控制的思路,則在一些對實時性要求特別高的場合下行不通,因而,一個簡單有效的微小內核對系統的性能、成本有着重要的影響。

 

2.2任務調度:

2.1優先級調度法

Vn Kernel採用任務的思想,支持任意多個任務,這取決於系統的RAM等資源。同時,每個任務都有自身的優先級,總共有 64 個優先級可選。其中、最高優先級和最低兩個優先級是內核專有的,用戶可用到的優先級有 61 個。優先級數值越大,優先級越低。

實時內核的一個標準就是當前執行的任務是否可被搶佔。本文設計的Vn Kernel屬於可搶佔式內核,高優先級的任務會立即搶佔當前任務,獲得CPU的執行權。 

 

2.2 時間片調度法:

時間片調度法是一個經典的任務調度策略,許多操作系統都採用該調度法,例如windows、linux等。

Vn kernel可選的優先級有64個,不同優先級的任務採用優先級高搶佔低優先級的調度方式,同時,允許有多個任務有着相同的優先級。

相同優先級的任務將會在內核中以雙向鏈表的形式存在,並採用時間片調度方式。時間片長度可設置,考慮到系統頻繁地切換任務會帶來更多的資源消耗,故而時間片長度一般爲10ms。

 

2.3 任務塊鏈表:

內核會爲每個任務創建一個任務塊以便描述該任務。在內核中,所有準備就緒的任務塊會以數組鏈表的形式存在。所謂數組鏈表,即在內核中,有一個指針數組,該數組的元素指向任務鏈表。所有任務將根據自身的優先級,添加到對應的鏈表中。其結構大致如下:

圖中第一行爲數組元素,A~G爲任務編號。內核會根據任務的優先,將任務添加到相應的鏈表中,並且,會根據數組的下標,使數組的元素指向相應的任務鏈表頭。

如此,內核便可以根據該指針數組訪問到所有準備就緒的任務。例如上圖有許多任務都準備就緒。內核會找到最高優先級的任務,即 2 所指向的任務 A。由於與任務A相同優先級的任務還有 B 和 C。所以內核將會採用時間片調度的方法,輪流執行這三個任務。

假設這個時候,系統創建了任務 X 該任務的優先級爲 1 。那麼此時,任務X將搶佔當前任務,獲得CPU執行權。此時內核只會執行任務X,除非有更高的優先級任務或者任務X放棄CPU執行權。

 

三、引導代碼:

bootloader:

考慮到實際產品的升級問題,本文設計了一套基於STM32的bootloader。該bootloader支持啓動內核、系統升級、燒寫flash、參數設置、命令行等功能。對於STM32而言,復位後系統一般都會從0x8000000 處啓動。

故而、bootloader存放在0x8000000處,預留大小爲 10 K。參數存放地址爲0x8003000 ~ 0x8003800 ,大小爲 2 K。內核存在0x8003800處。

關於BootLoader的源碼見這篇文章:BootLoader 源碼鏈接

Bootlader實際演示結果如下圖:

四、內核源碼分析:

4.1任務塊結構體:

/***************************************************************
定義一個task_tcb結構體,用於記錄任務的相關信息
***************************************************************/
typedef struct task_tcb *PT_task_tcb;

//#pragma pack(1)

typedef struct task_tcb{
	INT32U 			*task_sp;			//任務堆棧指針
	/* 
	 *以下兩個元素看似必須,實則不需要。
	 *那麼系統是怎麼根據TCB找到執行函數 和 參數指針的呢?
	 *實際上,當我們爲TCB初始化任務棧的時候,就已經將這兩個參數
	 *傳遞進入了。想想看, 任務棧是什麼?所有寄存器,也就是
	 *有 R0 (ARM架構函數的第一個參數存放在R0) PC
	 *現在知道爲什麼TCB沒有指明任務的執行函數,卻能找到該函數了吧。
	 */
	//void (*task_fun)(void *pd);			//任務的執行函數
	//void 			*pdata;					//任務處理函數的參數指針	
	INT32U			task_id;				//任務的ID,由系統統一分配
	
#if TASK_IF_NAME
	INT8U			task_name[TASK_NAME_LEN];	//任務的名字,由用戶指定
#endif
	
	INT8U			task_prio;				//任務優先級
	INT8U			task_state;				//任務狀態
	INT32U			task_runtime;			//任務的時間片長度
	INT32U			task_delaytime;			//任務如果需要等待,那麼等待的時間長度
	
	/* 以下這個元素,用於就緒表中優先級相同的情況下 */
	PT_task_tcb		task_rdy_next;		
	PT_task_tcb		task_rdy_prev;
//	PT_task_tcb		task_waitlist;
	
	/* 以下兩個僅用於構成雙向鏈表,實際中作用不大 */
	PT_task_tcb		task_prev;			//指向上一個
	PT_task_tcb		task_next;			//指向下一個

	struct list_head list;				//鏈表,用於任務等待

	/* 每個任務都可以獲取資源,下面是任務已經申請到的資源
		當任務被刪除時,要釋放資源
	*/
	struct os_resource	*task_resource;

#if STACK_ADD
	INT32U *pdata;
#else
	//INT32U *pdata;
#endif
//	
	
}T_task_tcb;

其中最重要的是任務堆棧指針。系統調用task_create 這個函數來創建一個任務,同時爲該任務分配一塊內存,用以存放任務塊、此外,還將額外多分配一塊內存,用以任務的堆棧。

 

4.2任務搶佔:

當有一個更高優先級的任務發生時,內核將會觸發一次軟件中斷。Cortex-M3架構中,提供一個可懸起中斷——pendSV_handler。內核的實際任務切換工作是在該中斷完成的。

內核首先將當前所有寄存器壓棧。並找到最高優先級的任務的任務棧,並將裏面的數據出棧。

對於Cortex-M3架構,其經典的任務棧操作彙編代碼如下:

	
;********************************************************************
;																	*
;					第一次任務調度									*
;																	*
;********************************************************************
;	狀態分析:
;									*
;		當系統第一次調度任務之前,很顯然,此時系統還不算存於多任務系統
;	可以看成裸機狀態,那麼此時系統是在運行那個任務呢?
;		顯然,沒有任務,可以理解爲 bootloader 階段。顯然,當我們進入到
;	多任務階段後,是不想系統再回到 之前的階段。
;		而且,最重要的是,第一次任務切換時,觸發 pendSV 中斷時,系統會
;	自動將 xPSR,PC,LR,R12,R0~R3 壓棧。
;		此時,系統進入中斷之前不是存於多任務系統狀態,那麼就不需要再
;	將 R8 ~ R11 入棧。
;		之後,系統存於多任務系統狀態,那麼就需要對 R8 ~ R11 入棧
;
;**********************************************************************
;
;*********************************************************************
;	特權級-用戶級 分析:
;
;		此外,當系統剛復位時,系統是處於 線程特權模式 請參考 
;	Cortex-M3 權威指南.pdf  25頁
;		此時,系統缺省值的 是 MSP ,主程序堆棧。但是,當我們運行任務
;	時,希望系統使用的是 PSP ,線程堆棧。
;	當系統進入異常時,處於 特權級handler 模式,使用的一定是 MSP
;		如何從 告訴系統要使用 psp 呢?
;	方法一:
;		在中斷退出時,修改LR
;			 ORR     LR, LR, #0x04  
;	方法二:
;		請參考 Cortex-M3 權威指南.pdf  40頁
;
;*********************************************************************
;	實現步驟
;		1.	設置 pendSV 中斷的優先級
;		2.	設置 PSP 爲 0 ,告訴系統,這是第一次調用
;		3.	觸發中斷
;********************************************************************
__cpu_start_shced
;設置 pendSV 中斷優先級
	LDR		R0,		=NVIC_SYSPRI14		;取中斷優先級寄存器地址
	LDR		R1,		=NVIC_PENDSV_PRI	;去中斷優先級 0xff
	STRB	R1,		[R0]				;將 R1 寫入到 [r0] 中
										;需要注意的是 strb 是寫入一個字節
										;也就是寫入 0xf 8位,因爲中斷優先級寄存器
										;都是 8 位的。
										;請參考 Cortex-M3 權威指南.pdf  404 頁
										
;設置 psp 爲 0 。是否記得前面說過, msp psp 的區別?
;那麼現在的問題是:當 stm32 執行到這裏的時候,stm32 處於何種狀態?
;顯然,前面說過復位後是	特權級線程模式 ,那麼可見的是 msp 。那是否意味着我們不能使用
; psp 呢? 不是,所謂可見是對於 push pop 操作而言的,那麼復位後 psp 的值時多少呢?
;我不知道,沒去查,但由於一般的程序,復位後都沒有去更改stm的特權級,也沒去修改 sp 是哪個。
;所以就沒有用過 psp 
;但是現在,我們希望系統第一次任務調度後,使用的是 psp 。而該 psp 指向當前任務的棧。爲何?
;因爲前面說過,當系統執行異常時,使用的一定是 msp 。這樣就能把 系統棧 跟 任務棧很好的分開了。

;設置 psp 爲0 ,告訴系統,這是第一次調度,之後再任務切換函數裏頭,會去修改 psp 使其指向任務棧
	MOVS	R0,		#0
	MSR		PSP,	R0					;MSR 是特殊指令,用於操作特殊寄存器的
	
;系統剛啓動時,是不允許任務調度的,	if_task_run 爲 0
	LDR		R0,		=if_task_run		;將 if_task_run 的地址寫入到 R0 中,這是一個僞指令
	MOVS	R1,		#1					;
	STRB	R1,		[R0]				;將 R1 的值寫入到地址爲 R0 的內存中

;觸發一次 pendSV 中斷,只要往中斷控制寄存器中的第 28 位寫入 1 ,即可觸發一次軟件中斷
	LDR		R0,		=NVIC_INT_CTRL		;把 NVIC_INT_CTRT 展開,可以得到
										; ldr r0, =0xE000ED04
										;如果等於號後面是一個數值,則表示 r0 = 0xE000ED04
										;如果等於號後面是一個變量名,或標號,則 取其地址
	LDR		R1,		=NVIC_PENDSVSET
	STR		R1,		[R0]

;當執行到指令時,系統已經觸發了 pendSV 中斷了,那麼系統應該跳到中斷處理函數哪裏了。
;理論上是如此,不過我們得保證中斷時允許的啊,可能前面禁了中斷後忘記打開了。
;開中斷 關中斷和開中斷可以分別由指令CPSID i和CPSIE i實現,
	CPSIE	I

;----------------------------------------------------------------------------------------------------
	LDR		R0, =task_shced_user
	BLX		R0

;接下來是一個死循環,防止系統跑飛。不過系統一般是不會到這裏的。
__cpu_err
	B		__cpu_err
	

	
	
;************************************************************************
;																		*
;					任務切換函數	__cpu_shced							*
;																		*
;************************************************************************
;		前面已經說過了,只需簡單地觸發一次 pendSV 中斷即可,真正的任務
;	切換在 中斷處理函數 中完成
;********************************************************************
__cpu_shced
	LDR		R0,	=NVIC_INT_CTRL
	LDR		R1,	=NVIC_PENDSVSET
	STR		R1,	[R0]
	BX		LR


;************************************************************************
;																		*
;					中斷退出調度函數	__cpu_int_shced					*
;																		*
;************************************************************************
;		當一箇中斷退出時, os_int_exit 要調用這個函數,確認是否需要從新調度
__cpu_int_shced
	LDR     R0, =NVIC_INT_CTRL
    LDR     R1, =NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR	

	
	
;************************************************************************
;																		*
;					pensSV 中斷處理函數	__cpu_pendSV_handler			*
;																		*
;************************************************************************
;	真正的任務切換函數 
;	由於 CM3 在中斷時會有一般的寄存器自動保存到任務堆棧裏頭、所以
;	OS_CPU_PendSVHandler	只需要保存 R4-R11 並調節堆棧指針即可 
__cpu_pendSV_handler
	CPSID   I                   ;任務切換需要關中斷

	MRS		R0,		PSP			;讀取 psp 的值
;如果 psp 爲 0 說明是第一次任務調度,則跳過下面的步驟
	CBZ		R0,		__cpu_pendSV_handler_nosave
	
	;if enable the FPU
    SUBS    R0, R0, #0X40
	VSTM    R0, {S16-S31}
	
;如果不是 0 ,那麼保存 R4 ~ R11 到任務棧
;爲什麼要減去 0x20呢? 0x20 是32,也就是 8 個寄存器(一個寄存器4個字節)因爲還要入棧
;數數看, R4 ~ R11 是不是 8 個寄存器
	SUBS	R0,		R0,	#0X20	;後綴 S 是要求更新 APSR 中的相關標誌
	STM		R0,		{R4-R11}	;將 {R4-R11} 壓入到 地址爲 R0 的內存中,注意不是壓棧操作
								;所以要先把 R0 - 0x32 ,之後低地址是 r4 高地址是 r11
								;那麼 R0 是多少呢? 顯然,前面已經令其爲 psp 了
								;第一次任務調度時,是不會執行這段的,但是當任務開始
								;調度後,psp 不再是0 ,而是當前任務的 任務棧
;修改任務的 TCB 的棧指針,請注意,TCB 結構體得第一個元素就是該任務棧的指針
;task_tcb_cur->task_ps = r0	(r0 是 psp 偏移後的值)
	LDR		R1,		=task_tcb_cur	;當前任務 tcb 的地址
	LDR		R1,		[R1]			;從地址中讀出值
									;讀出來的值時什麼呢? 就是 tcb 的第一個元素
									;這是一個指針,任務棧指針
	STR		R0,		[R1]			;將 R0 寫入到 地址爲 R1 的內存中
									;這一段比較難理解,我們可以轉換成 C 語言來看
									;首先, task_tcb_cur 是一個指針,指向當前 任務 TCB 的內存地址
									;上面 3 句等價與下面 3 句
									; 1.	r1 = &tcb
									; 2.	r1 = *(&tcb) = tcb
									; 3.	*(r1) = r0
									;將 2 代入到 3 式中
									; 4.	**(&tcb) = r0
									;也就是 *tcb = r0
									;前面已經所過了, tcb 是執行當前任務塊得指針 假設當前任務塊 是 TCB
									;那麼:代入到 4 中	
									; 5.	*(&TCB) = r0		也就是:
									;		TCB = r0		(這樣寫不恰當,應該是 TCB 的第一個元素)
									;也就是 tcb->sp = r0 = psp
	
__cpu_pendSV_handler_nosave
;調用用戶的函數,不過一般都置空
	PUSH	{R14}
	LDR		R0, =task_shced_user
	BLX		R0
	POP		{R14}
;修改 task_tcb_cur
;	task_tcb_cur = task_tcb_high
	LDR		R0, 	=task_tcb_cur	;	r0 = &task_tcb_cur
	LDR		R1, 	=task_tcb_high	;	r1 = &task_tcb_high
	LDR		R2,		[R1]			;	r2 = *(&task_tcb_high)
	STR		R2,		[R0]			;	*(&task_tcb_cur) = *(&task_tcb_high)
									;	約掉 * 和 & 得到
									;	task_tcb_cur = task_tcb_high
;接下來要出之前壓入的 r4 ~ r11
;當對於第一次調度而言,之前根本就沒有壓入過 r4 ~ r11 啊
;請參考 os_cpu.c 中的 task_init_ptop 任務棧初始化函數
;在任務第一次創建時,就已經初始化棧了,r4 ~ r11 被手工放入,所以要先出手工放入的值
	LDR		R0,		[R2]			;	R0 = *(*(&task_tcb_high))	
									;	這句相當於 r0 = task_tcb_high 指向的 TCB 的第一個元素
									;	也就是 r0 = task_tcb_high->task_sp
	LDM		R0,		{R4-R11}		;	從地址爲 R0 的內存中讀出內容
	ADDS	R0,		R0,	#0X20		;	知道爲什麼要加 20 不?棧是從高往低增長的
									;	我們出了 r4-r11 這 0x20 個字節後,要從新調整棧指針
	;if enable FPU
	VLDM    R0, {S16-S31}
	ADDS    R0, R0, #0X40
	
	MSR		PSP,	R0				;	psp = r0
	ORR		LR,		LR,	#0X04
;打開中斷
	CPSIE	I
	BX		LR	
	
	END

4.3:雙向鏈表的操作

內核會將相同優先級的任務放到同一個鏈表中。其雙向鏈表的操作函數如下:

void add_tcb_list(struct task_tcb *head, struct task_tcb *ptcb)
{
	head->task_rdy_prev->task_rdy_next 	= ptcb;
	ptcb->task_rdy_next 				= head;
	ptcb->task_rdy_prev 				= head->task_rdy_prev;
	head->task_rdy_prev 				= ptcb;	
}

void del_tcb_list(struct task_tcb *ptcb)
{
	ptcb->task_rdy_next->task_rdy_prev = ptcb->task_rdy_prev;
	ptcb->task_rdy_prev->task_rdy_next = ptcb->task_rdy_next;
}

4.4:時間片調度算法

Cortex-M3內核提供一個系統時基定時器——Tick定時器。可以作爲10ms定時功能。當發生中斷時,內核會找到當前正在運行的任務,將其時間片長度減並判斷其值,如果爲0,在當前任務的鏈表中中找到下一個任務塊,並執行任務切換,其代碼如下:

if(task_tcb_cur->task_state == TASK_STATE_RUNING)
	{
		task_tcb_cur->task_runtime --;		//時間片長度--
		if(task_tcb_cur->task_runtime == 0)
		{
			task_tcb_cur->task_runtime = TASK_RUNTIME;
			//task_tab[task_tcb_cur->task_prio] = task_tcb_cur->task_rdy_next;
			flag = 1;
		}
	}

 

4.5:任務休眠

任務除了上面說的就緒態和運行態,任務有時候還需要休眠,讓出CPU執行權,內核中的休眠函數的源碼如下:

/*******************************************************************************
	任務休眠
*******************************************************************************/
void task_sleep(INT32U ms)
{
	INT32U cpu_sr;
	sys_interrupt_disable();

	tcb_tab_del(task_tcb_cur,task_tcb_cur->task_prio);		//刪除
	task_tcb_cur->task_delaytime = ms;						//休眠時間
	list_add((struct list_head *)&task_tcb_cur->list,&sleep_list);				//添加到休眠鏈表
	
	task_shced();											//任務調度

	/* 在這裏更改狀態 */
	task_tcb_cur->task_state = TASK_STATE_SLEEP;			//更改狀態
	
	sys_interrupt_enable();
}

休眠的任務將會從就緒鏈表中刪除,並加入到一個休眠鏈表中——sleep_list 。定時器週期產生中斷,並對休眠鏈表中的所有任務進行休眠時間查詢。如果該任務的休眠時間到了,則將任務從休眠鏈表中刪除,並加入到就緒鏈表中,再做一次任務調度。

/* 休眠鏈表 */
	list_for_each_entry_safe_reverse(pos,n,&sleep_list,list,struct task_tcb)
	{
		pos->task_delaytime --;
		if(pos->task_delaytime == 0)
		{
			pos->task_state = TASK_STATE_READY;
			list_del((struct list_head *)&pos->list);	  				//從休眠鏈表中刪除
			task_tab_add(pos,pos->task_prio);		//加入到就緒鏈表中
			flag = 1;
		}
	}
	if(flag == 1)
	{	
		__task_shced_timer();
		//不相等時纔要做切換.
		if(task_tcb_high != task_tcb_cur)
		{
			__cpu_int_shced();
		}
	}

五、Cortex-M3處理器:

5.1簡介:

任何內核,都是要在具體的CPU上運行纔有意義。本文設計的 vn kernel 是基於STM32F103ZET6這款芯片。

而該芯片採用的ARM公司的Cortex-M3內核架構。

Cortex-M3處理器採用ARMv7-M架構,它包括所有的16位Thumb指令集和基本的32位Thumb-2指令集架構,Cortex-M3處理器不能執行ARM指令集。

 

5.2工作模式:

Cortex-M3處理器支持2種工作模式:線程模式和處理模式。當處理器復位時,處理器處於 “特權級線程模式”,而發生異常時,處理將會進入“特權級handle”模式,異常返回時回到“特權級線程模式”。

然而,不管是“handle”還是“線程”模式,只要處理器處於“特權級”,那麼處理器將使用的程序主堆棧——MSP。

除此之外,處理器還支持“用戶線程級模式”,在該模式下,處理器將使用線程堆棧——PSP。顯然,我們希望任務時處於“線程級模式”,內核是處於“特權級模式”。

六、實驗結果:

本文編寫了一個簡單的測代碼。基於 STM32F407 內核工程文件如下:

 

Main函數如下:

 

一開始時處理器相關的一些初始化工作,之後調用 core_init() ,對內核進行初始化。Debug_1() 則是創建兩個任務 led1 led2 。分別控制兩個LED燈閃爍。 core_start() 則是開始啓動內核。

兩個任務的代碼大致相同,如下所示:

int main(void)
{ 
	find_stack_direction();
	SystemInit();
	LED_Init();		    //初始化LED端口
	/* 重定義向量表 */
	NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0000);
	core_init();
	
	debug_1();
	
	core_start();
/*-------------------------------------------------------------------*/
//	led1_task((void *)0);
	while(1)
	{
		u8 t;
		t++;
	}
}

debug1函數內如下:

void debug_1(void)
{
	led1_id = task_create(led1_task, (void *)0, 50, 24, "led1");
	led2_id = task_create(led2_task, (void *)0, 50, 25, "led2");	
	res_id1 = task_create(debug_resource1, (void *)0, 50, 26, "debug1");
	res_id2 = task_create(debug_resource2, (void *)0, 50, 27, "debug2");
//	res_id3 = task_create(debug_resource3, (void *)0, 5, 24, "debug3");
}

void led1_task(void *p)
{
	volatile INT32U i;

	LED_Init();

	for(;;)
	{	
		//task_change_prio(TASK_SELF, 25);
		for(i = 0; i < 2; i++)
		{
			res_id5 = task_create(debug_resource1, (void *)0, 50, 26, "debug1");
			res_id6 = task_create(debug_resource2, (void *)0, 50, 27, "debug2");
			GPIO_WriteBit(GPIOE, GPIO_Pin_3, Bit_SET);
			task_sleep(100);	//delay(1000); //task_sleep(100);
			GPIO_WriteBit(GPIOE, GPIO_Pin_3, Bit_RESET);
			task_sleep(200);	//task_sleep(100);
			task_delete(res_id5);
			task_delete(res_id6);
		}
	}
}

void led2_task(void *p)
{
	int i;
	LED_Init();
	
	for(;;)
	{
		//task_change_prio(TASK_SELF, 20);
		for(i = 0; i < 2; i++)
		{
			res_id3 = task_create(debug_resource1, (void *)0, 50, 26, "debug1");
			res_id4 = task_create(debug_resource2, (void *)0, 50, 27, "debug2");
			GPIO_WriteBit(GPIOE, GPIO_Pin_4, Bit_SET);
			task_sleep(300);	//task_sleep(100);	//
			GPIO_WriteBit(GPIOE, GPIO_Pin_4, Bit_RESET);
			task_sleep(200);	//delay(1000); //
			task_delete(res_id3);
			task_delete(res_id4);
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章