以一款簡單、易學(xué)的嵌入式開發(fā)平臺ARM Mini2440(CPU是三星ARM 9系列的ARM S3C2440)為例,通過具體代碼實現(xiàn),介紹如何從裸板入手設(shè)計簡單的輪詢系統(tǒng)、前后臺系統(tǒng),以及如何一步一步在ARM Mini2440上編寫RTOS內(nèi)核,到如何讓RTOS內(nèi)核支持多核嵌入式處理器。
aCoral是2009年創(chuàng)建的開源的、支持多核的RTOS。
aCoral
目前aCoral包括五大模塊
- 內(nèi)核:由電子科技大學(xué)實時計算實驗室編寫。
- 文件系統(tǒng)
- 輕型TCP/IP
- GUI:來自開源嵌入式Linux圖形系統(tǒng)LGUI。
- 簡單應(yīng)用。
aCoral支持多任務(wù)模式,其最小配置時,生成的代碼為7KB左右,而配置文件系統(tǒng),輕型TCP/IP,GUI后生成的代碼僅有300KB左右。
輪詢系統(tǒng)概述
輪詢系統(tǒng)也稱為簡單循環(huán)控制系統(tǒng),是一種最簡單的嵌入式實時軟件體系結(jié)構(gòu)模型。
在單個微處理器情況下,系統(tǒng)功能由多個函數(shù)(子程序)完成,每個函數(shù)負責(zé)該系統(tǒng)的一部分功能。這些函數(shù)被循環(huán)調(diào)用執(zhí)行,它們按照一個指向順序構(gòu)成一個單向的有序環(huán)(輪循環(huán)),依次占用CPU。
每個函數(shù)訪問完成之后,才將CPU交給下一個函數(shù)使用。對于某個函數(shù)而言,當(dāng)它提出執(zhí)行請求時,必須等到它被CPU接管后才能執(zhí)行。
程序框架
initialize();
while(TRUE)
{
if(condition 1)
{
F1();
}
if(condition 2)
{
F2();
}
if(condition 3)
{
F3();
}
}
在系統(tǒng)工作以前,首先進行系統(tǒng)初始化,然后系統(tǒng)進入無限循環(huán)狀態(tài);主程序依次對輪循環(huán)中的函數(shù)進行判斷,若該函數(shù)要求占用CPU,則讓其執(zhí)行,否則跳過該函數(shù)去執(zhí)行提出請求的下一個函數(shù),這種判定某個函數(shù)是否滿足執(zhí)行條件的過程,稱為輪詢。
調(diào)度
根據(jù)程序結(jié)構(gòu)特點,基本輪詢系統(tǒng)具有以下工作特點:系統(tǒng)完成一個輪詢的時間取決于輪循環(huán)中需要執(zhí)行的函數(shù)的個數(shù)。循環(huán)的次序是靜態(tài)固定的,在運行時不能進行動態(tài)調(diào)整。
這種特點決定了輪詢系統(tǒng)在諸如多路采樣系統(tǒng)、實時監(jiān)控系統(tǒng)等嵌入式應(yīng)用中可以得到廣泛使用,但是所有函數(shù)必須順序執(zhí)行,不區(qū)分函數(shù)的重要程度,系統(tǒng)也無法根據(jù)應(yīng)用的實際需要靈活地調(diào)整對函數(shù)的使用粒度。
- 優(yōu)先權(quán)調(diào)度
克服以上缺陷的最簡單辦法就是允許優(yōu)先級高的函數(shù)被多次重復(fù)調(diào)度,即在輪詢環(huán)中增加重要函數(shù)的訪問CPU的次數(shù)。這樣,每一次輪詢中,相對重要的函數(shù)獲得CPU的概率就更大。
上述機制還可以通過一個指針表的形式加以改進,使得函數(shù)的優(yōu)先級可以在允許時動態(tài)改變。
F_1()
{
if(condition 1)
{
F1();
}
}
F_2()
{
if(condition 2)
{
F2();
}
}
#define N_ACTION 3
int action_ptr;
main()
{
other_initialization();
action_ptr=0;
while(TRUE)
{
if(++action_ptr==N_ACTION) action_ptr=0;
}
}
子輪詢
當(dāng)某些函數(shù)的執(zhí)行時間相對較長時,可以將其分解成若干子函數(shù),這些子函數(shù)也構(gòu)成一個輪詢,稱為子輪詢。
例如,一個函數(shù)需要打印消息到一個慢速輸出設(shè)備中,可以將其分解成兩部分:第一部分是打印消息處理(F2_1),第二部分是慢速設(shè)備忙時的等待處理(F2_2)。
典型系統(tǒng)
許多工業(yè)現(xiàn)場網(wǎng)絡(luò)中,由于需要控制的設(shè)備較多,相互距離又較遠,且現(xiàn)場有較強的工業(yè)干擾,因此采用體積小、抗干擾能力強的單片機作為上位機,與現(xiàn)場控制器一起組成分布式數(shù)據(jù)采集與控制系統(tǒng),是一種較好的選擇。
如圖所示,在一個多機通信系統(tǒng)中,只有一臺單機(8051)作為主機,各臺從機不能相互通信,必須通過主機轉(zhuǎn)發(fā)來交換信息。單片機通過RS-485總線通信,主機通過點名方式向各從機發(fā)送命令,實現(xiàn)對系統(tǒng)的控制。同時,對從機不斷地輪詢,監(jiān)視從機的狀態(tài),接收從機的請求或信息。
搭建開發(fā)環(huán)境
- ARM9 Mini2440開發(fā)板
- 仿真器J-LINK
開發(fā)人員的程序是在PC上編寫的,PC是Intel的處理器,Windows的操作系統(tǒng)。
單片機可能是TI的8051、430等。
Intel的處理器和單片機采用的是不同指令集。
交叉開發(fā)Cross Developing是嵌入式軟件系統(tǒng)開發(fā)的特殊方法。
開發(fā)系統(tǒng)建立在軟硬件資源均比較豐富的PC或工作站上,一般稱為宿主機或Host,嵌入式軟件的編輯、編譯、鏈接等過程都是在宿主機上完成。
嵌入式軟件的最終運行平臺卻是和宿主機有很大差別的嵌入式設(shè)備,一般稱為目標(biāo)機或Target,這里的目標(biāo)機就是ARM Mini2440。
宿主機與目標(biāo)機通過串口、并口、網(wǎng)口或其它通信端口相連,嵌入式軟件的調(diào)試和測試是由宿主機和目標(biāo)機之間協(xié)作完成。
宿主機與目標(biāo)機的差別主要在于:
- 硬件的差別:最主要是兩者的處理器不同,因此兩者支持的指令集、地址空間都不同。其它的差別,如內(nèi)存容量,外圍設(shè)備等。
- 軟件環(huán)境的差異:在宿主機上都有通用操作系統(tǒng)等系統(tǒng)軟件提供軟件開發(fā)支持,而目標(biāo)機除了調(diào)試代理外幾乎沒有其他用于嵌入式軟件開發(fā)的軟件資源?,F(xiàn)有的嵌入式操作系統(tǒng)僅可作為嵌入式軟件運行時的支撐環(huán)境。
裸板輪詢系統(tǒng)的交叉開發(fā)環(huán)境如圖所示。
其中,ADS(ARM Development Suite)是針對ARM處理器的集成開發(fā)環(huán)境,包含了代碼編輯器、編譯器、連接器、調(diào)試器。
圖中的宿主機與目標(biāo)機的連接通過J-LINK、串口連接。
J-LINK主要是為了燒寫和調(diào)試被調(diào)試程序(將要開發(fā)的輪詢系統(tǒng)),而串口主要是為了回顯調(diào)試信息。
J-LINK要正常工作,需要在PC上安裝J-flashARM。
這樣,就可以在PC上編寫代碼,然后用ADS將其編譯、鏈接成具有ARM指令集的可執(zhí)行程序,即被調(diào)試程序;再通過J-LINK將被調(diào)試程序燒寫在Mini2440的NORFlash上(NORFlash的起始地址是0x00000000,開發(fā)板上電后PC將指向這里,從該地址存放的指令開始運行);最后,重新啟動Mini2440,被調(diào)試程序便可在目標(biāo)機上運行,并通過串口回顯運行信息。
將PC、Mini2440和J-LINK連接好后,就可以在ADS下創(chuàng)建工程了,具體步驟如下:
- 新建一個ADS工程,并為其命名為MINE,然后新建file文件my_2440_init.s(.s表示該文件由匯編語言編寫,因為此時開發(fā)板尚未初始化,只支持匯編語言),在創(chuàng)建好后,就將該文件添加到剛建立的MINE工程中,并在debug、release和debugrel三個選項上打√,工程路徑和命名都不能有中文。
- 設(shè)置“my_2440_init.s”文件編譯鏈接后生成的可執(zhí)行文件的格式。將ADS菜單“Edit” - > DebugRel Settings -> Linker -> ARM formELF中的“output format”輸出形式設(shè)定為“Plain binary”文件類型,后綴為“.bin”。
- 在my_2440_init.s文件中輸入將要編寫的輪詢系統(tǒng)匯編代碼。如果代碼編寫完成,并且編譯成功,可以在MINE工程文件的MINE_data -> DebugRel文件夾中發(fā)現(xiàn)一個生成了的MINE.bin的可執(zhí)行文件。
- 生成bin文件后,就可通過J-LINK將該文件“MINE.bin”燒寫到Mini2440的開發(fā)板中。此時,開發(fā)板設(shè)置為“NORFlash”啟動,接上J-LINK后,插在底板的JTAG插座上,J-LINK另一頭接PC的USB接口。
- 開發(fā)板上電。
- 打開之前在PC上安裝的J-flashARM工具,開始->所有程序->SEGGER->JLINK ARM V4.08 ->JLINK ARM,該界面能將開發(fā)板上從0x00000000開始的內(nèi)存數(shù)據(jù)顯示出來。
- 燒寫完成后斷電,再取下J-LINK.
- 再重新上電,如果MINE.bin程序正確無誤,Mini2440上將會有所顯示,例如LED燈被點亮,當(dāng)然Mini2440的顯示結(jié)果與開發(fā)人員的程序有關(guān)。
啟動Mini2440
交叉開發(fā)環(huán)境搭建好后,便可動手編碼了,編碼的首要工作是:用ARM匯編語言啟動處理器S3C2440A。
為什么需要啟動
無論一個計算機系統(tǒng)由多少硬件設(shè)備組合而成,該系統(tǒng)能夠運行的基礎(chǔ)至少需要一個CPU與運行指令與數(shù)據(jù)的載體,該載體被稱為主存。
當(dāng)系統(tǒng)上電后,CPU掛在主存的存儲器上開始命令的執(zhí)行,一般的CPU通常是從0x0處開始取指執(zhí)行,Mini2440的S3C2440A芯片也是如此。
當(dāng)開發(fā)板上電后,CPU的PC寄存器的值通過硬件機制被初始化為0x0。
一切指令與程序都只能在主存上運行。而主存價格比較昂貴,并且開發(fā)人員的程序通常比較大,因此開發(fā)人員的程序通常存儲在外存中,而外存無法作為應(yīng)用程序運行的載體。
當(dāng)系統(tǒng)上電后,要將應(yīng)用程序從其它存儲設(shè)備復(fù)制到主存。
啟動代碼的作用可以隨著需求的增加而進行擴充,上述描述只是確保系統(tǒng)能夠基本運行啟動代碼的功能。其實,啟動代碼還可以包括實際開發(fā)板上各級硬件和接口的驅(qū)動程序,BootLoader(如FriendlyARM BIOS2.0或者Supervivi)就是這樣一類啟動代碼。
啟動流程
啟動代碼是與硬件設(shè)備密切相關(guān)的。
這一節(jié)將以Mini2440的S3C2440A處理器為例來講述啟動代碼的流程。
如圖所示,這里的啟動代碼只是一個能使CPU正常工作的一個最小系統(tǒng)。
Mini2440開發(fā)板有兩種啟動模式,一種是從NOR Flash啟動,另一種是從NAND Flash啟動,本節(jié)從NAND Flash啟動為例介紹。
當(dāng)Mini2440從NAND Flash啟動時,因為NAND Flash無法作為程序運行的載體,所以S3C2440A芯片通過硬件機制將NAND Flash的開頭4KB的內(nèi)容復(fù)制到了S3C2440A芯片內(nèi)部的4KB大小的SRAM中,并且S3C2440A芯片會自動將這4KB大小的SRAM映射為自身內(nèi)存的BANK0,將這4KB大小的內(nèi)容映射到從0x00000000開始的地址上,然后處理器從0x00000000地址開始執(zhí)行。
創(chuàng)建異常向量表
當(dāng)程序在S3C2440A芯片上運行發(fā)生異常時,程序指針PC會自動跳轉(zhuǎn)到主存最開始的地址(0x00000000),這里就是異常向量表的起始地址,然后會通過專門的硬件機制定位到相應(yīng)的異常向量。
ARM處理器內(nèi)核一共定義了七種異常:復(fù)位異常、未定義指令異常、軟中斷異常、預(yù)取指終止異常、數(shù)據(jù)終止異常、IRQ中斷異常、IRQ快速中斷異常。
- 復(fù)位異常。當(dāng)開發(fā)板復(fù)位時,S3C2440A的ARM920T核將當(dāng)前正在運行程序的CPSR與PC存入管理模式下的SPSR與LR中。然后,強制將CPSR的M[4:0]位寫為10011(管理模式),并將CPSR的I位與F位置1(屏蔽IRQ與FIQ),將CPSR的T位清0(進入ARM指令模式)。之后強制將PC的值設(shè)為0x0,讓CPU從0x0開始取指執(zhí)行命令,這時CPU運行在ARM狀態(tài)。
- 未定義指令異常,當(dāng)ARM920T遇到一個無法處理的指令時,未定義指令異常發(fā)生。當(dāng)前運行程序的下一條指令的地址將會被保存到相應(yīng)模式下的LR,CPSR被保存到相應(yīng)的SPSR中,然后CPSR的M[4:0]被設(shè)置為相應(yīng)的模式值,并且PC被強制賦值為0x4(ARM 9是32位的指令集,每條指令占用4B的空間)。
- 軟中斷異常。當(dāng)軟中斷指令SWI被執(zhí)行時,軟中斷異常發(fā)生。當(dāng)軟中斷異常發(fā)生時,PC值取值為0x8。軟中斷是用戶模式切換到特權(quán)模式的唯一途徑,軟中斷會將程序帶到管理模式下,這樣程序就可以對更多的寄存器,特別是CPSR有了修改的權(quán)利。軟中斷通常用來實現(xiàn)特權(quán)模式下的系統(tǒng)調(diào)用功能。
- 預(yù)取指終止異常。當(dāng)在一條指令的預(yù)取指片段執(zhí)行失?。ㄍǔ閮?nèi)存讀取錯誤時),預(yù)取指終止異常發(fā)生,PC取值為0xC。預(yù)取指終止異常只有當(dāng)該指令進入了流水線時(也就是該指令被預(yù)取指時)發(fā)生,但是如果引發(fā)該異常的指令沒有被執(zhí)行,程序不會終止去處理異常。
- 數(shù)據(jù)終止異常。當(dāng)在讀出數(shù)據(jù)時發(fā)生內(nèi)存錯誤時,數(shù)據(jù)終止異常發(fā)生。PC取值為0x10。
- IRQ中斷異常。當(dāng)CPU接受到外部設(shè)備發(fā)出的中斷請求時,IRQ中斷異常發(fā)生。PC取值為0x18。IRQ中斷異常會在FIQ快速中斷異常中被屏蔽。
- FIQ快速中斷異常。FIQ快速中斷異常是為數(shù)據(jù)傳輸與處理提供的快速中斷通道。當(dāng)FIQ發(fā)生時,PC取值為0x1C,F(xiàn)IQ快速中斷異常將進入FIQ快速中斷模式,在此模式下,ARM提供了更多的專用寄存器,為中斷處理節(jié)省了寄存器保護入棧的時間。
七種運行模式中,有兩個模式對應(yīng)于中斷:中斷模式,快中斷模式。
快中斷的優(yōu)先級比一般中斷高。
當(dāng)發(fā)生中斷和快中斷時,程序計數(shù)器(PC)將會跳到指定的地址開始執(zhí)行,為執(zhí)行相應(yīng)的中斷服務(wù)提供了可能。
下面代碼是建立異常向量表的過程,其中第一個指令通常都是存放在主存的零地址。異常向量表存放的全是匯編跳轉(zhuǎn)指令,這些指令從主存的零地址(0x0)開始連續(xù)存儲在內(nèi)存中(每條指令長度為4B)。
B Reset ;
b Undef ;
b SWI ;
b PreAbort ;
b DataAbort ;
b. ;
b IRQ ;中斷模式的入口地址,當(dāng)產(chǎn)生中斷后,PC會自動通過硬件機制跳轉(zhuǎn)到該地址,然后執(zhí)行該地址開始的代碼
b FIQ ;快速中斷模式的入口地址,當(dāng)產(chǎn)生中斷后,PC會自動通過硬件機制跳轉(zhuǎn)到該地址,然后執(zhí)行該地址開始的代碼
代碼中的標(biāo)識符Reset…等都代表了一個地址,該地址就是跳轉(zhuǎn)指令需要跳轉(zhuǎn)的地址,指向各異常服務(wù)程序的起始地址。這些標(biāo)識符的定義和使用在不同的匯編器下是不同的。
當(dāng)發(fā)生對應(yīng)的異常時,PC將通過硬件機制跳轉(zhuǎn)到相應(yīng)異常向量對應(yīng)的地址開始執(zhí)行,因為是由硬件機制實現(xiàn)的,所有跳轉(zhuǎn)的地址都是在CPU芯片生產(chǎn)時就確定且無法更改,并且這些跳轉(zhuǎn)指令都是單條指令連續(xù)在一起的,所以無法在原地實現(xiàn)中斷服務(wù)程序,這也是異常向量表中的代碼全是跳轉(zhuǎn)指令的原因。
當(dāng)產(chǎn)生異常時,通過硬件跳轉(zhuǎn)到一個確定的地址,再通過跳轉(zhuǎn)指令跳轉(zhuǎn)到一個異常處理程序的起始地址。
初始化基本硬件
不同嵌入式處理器集成的硬件設(shè)備不一樣。
在Mini2440開發(fā)板上,如果設(shè)置從NAND Flash啟動,則最開始的啟動代碼不能超過4KB,所以在這個階段的硬件初始化最好只對CPU和主存進行初始化,使CPU能運行,將更為完整的硬件初始化代碼從外存復(fù)制到主存中執(zhí)行。
在硬件初始化階段,是不希望有中斷的打擾。
硬件初始化流程
- 關(guān)閉看門狗。看門狗WDT(Watch Dog Timer)是S3C2440A提供的一個計數(shù)器??撮T狗是一種防止程序跑飛的監(jiān)測機制,它需要在一個設(shè)定的時間內(nèi)向其發(fā)送一個喂狗的脈沖信號,如果超出了設(shè)定值,看門狗就會發(fā)出一個復(fù)位信號,讓程序復(fù)位。如果不關(guān)閉看門狗,啟動程序在一定的時間內(nèi)就需要去“喂狗”,增加了代碼量,還浪費了系統(tǒng)的寶貴資源。因為在啟動代碼中不會涉及大量運算和長時間循環(huán)。
WTCON EQU 0x53000000
LDR R0,=WTCON
MOV R1,0X00000000
STR R1,[R0]
- 屏蔽中斷
在啟動代碼的開始階段,屏蔽中斷是顯而易見的,因為CPU等硬件初始化是一切服務(wù)的基礎(chǔ),在沒有完成初始化前,中斷是沒有意義的。其實,在復(fù)位異常發(fā)生后,ARM920T已經(jīng)通過硬件機制將CPSR的I為與F為置為1,屏蔽了中斷。為了代碼更具有通用性、嚴(yán)整性與邏輯性,通過設(shè)置中斷控制器相關(guān)寄存器來屏蔽中斷。
INTMSK EQU 0x4A000008
INTSUBMSK EQU 0x4A00001C
ldr r0,=INTMSK
ldr r1,=0xffffffff
str r1,[r0]
ldr r0,=INTSUBMSK
ldr r1,=0x7fff
str r1,[r0]
在啟動代碼屏蔽中斷后,若啟動完了所加載的應(yīng)用程序,需重新開啟中斷。
- 設(shè)置時鐘與PLL
S3C2440A芯片為用戶提供了多個CPU頻率與多個相應(yīng)的AHB(Advanced High-performance Bus)總線頻率和APB(Advanced Peripheral Bus)總線頻率。
在啟動代碼中,選擇所需的頻率來配置CPU是必須的,不同的芯片時鐘設(shè)置方法也不一樣。
在S3C2440A芯片中,所有時鐘的時鐘源均來自于一個12MHz的外部晶體振蕩器,CPU的高頻率是通過鎖相環(huán)PLL(Phase Locked Loop)電路模塊將12MHz頻率提升后得到的,AHB與APB的時鐘頻率是通過設(shè)置與CPU時鐘的比值得到的。
有兩個PLL:MPLL和UPLL。
MPLL是用于CPU及其他外圍期間的,UPLL是用于USB的,MPLL產(chǎn)生三種頻率:FCLK、HCLK、PCLK,其用途分別為:
(1)FCLK為CPU提供時鐘信號,S3C2440最大支持400MHz的主頻。
(2)HCLK為AHB總線提供時鐘信號,主要用于高速外設(shè),如內(nèi)存控制器、中斷控制器、LCD控制器、DMA等。
(3)PCLK為APB總線提供時鐘信號,主要用于低速外設(shè),如看門狗、UART控制器等。
UPLL專門用于驅(qū)動USB host/Device,并且驅(qū)動的頻率必須為48MHz。
必須先設(shè)定UPLL,才能設(shè)定MPLL,而且中間需要若干空指令(NOP)的間隔。
設(shè)定設(shè)置時鐘與PLL時,需要用到的寄存器主要有鎖定時間計數(shù)寄存器LOCKTIME、MPLL配置寄存器MPLLCON、UPLL配置寄存器UPLLCON、時鐘分頻控制寄存器CLKDIVN。
- LOCKTIME寄存器。LOCKTIME寄存器使用與設(shè)置的原因是與PLL的硬件特征有關(guān)的。當(dāng)PLL啟動后需要一定的時間才能穩(wěn)定工作。所以在向CPU與外部USB總線提供時鐘頻率之前,需要鎖定一段時間等待PLL能正常工作。根據(jù)S3C2440A的數(shù)據(jù)手冊,MPLL與UPLL所需的等待時間應(yīng)該大于300us,將其設(shè)置為默認值0xFFFF即可。
- MPLLCON與UPLLCON寄存器。這兩個寄存器用來設(shè)置CPU頻率與USB頻率相對外部晶體振蕩器頻率的倍數(shù)參數(shù)的,即設(shè)置相應(yīng)頻率大小的。兩個PLL在MPLLCON與UPLLCON寄存器值沒有重新寫入之前是不會工作的。
- CLKDIV寄存器。設(shè)置CPU、AHB和APB頻率的比值,當(dāng)AHB與CPU的頻率比不為1:1時,CPU的總線模式應(yīng)該從快速總線模式切換到異步總線模式。如果不切換,CPU將以AHB的頻率運行。
初始化BANK
S3C2440A芯片配置了8個BANK,每一個BANK大小最大為128MB。
BANK6的起始地址為0x30000000,Mini2440開發(fā)板的內(nèi)存掛載在BANK6,所以Mini2440的內(nèi)存起始地址為0x30000000,這也是CPU啟動后需要將應(yīng)用程序加載到的地方。
在應(yīng)用程序被加載到內(nèi)存并運行前,必須對BANK進行初始化,確定8個BANK的內(nèi)存分布,完成內(nèi)存刷新頻率等設(shè)置。文章來源:http://www.zghlxwxcb.cn/news/detail-530962.html
初始化堆棧
ARM有七種不同的運行模式,而各模式都共同享有公用的通用寄存器,所以在模式切換后,有必要將前一種模式的通用寄存器上的數(shù)據(jù)保存以便模式切換后能正常運行。
這時,不同模式下的堆棧就發(fā)揮了保護現(xiàn)場的作用。
ARM在不同模式下都有專用的堆棧指針,所以每個模式的堆棧初始化只需要將堆棧指針賦值為預(yù)先確定好的一個固定的、與各模式相對應(yīng)的地址。
在Mini2440開發(fā)板復(fù)位和上電時,ARM920T處于管理模式(Supervisor Mode),又因為在進行各模式的堆棧初始化時,需要分別進入各個工作模式分別初始化,所以將管理模式的系統(tǒng)堆棧初始化放在最后。文章來源地址http://www.zghlxwxcb.cn/news/detail-530962.html
到了這里,關(guān)于嵌入式實時操作系統(tǒng)的設(shè)計與開發(fā)(一)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!