1)实验平台:正点原子Linux开发板

2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南
关注官方微信号公众号,获取更多资料:正点原子

第十二章官方SDK移植试验

在上一章中,我们参考ST官方给STM32编写的stm32f10x.h来自行编写I.MX6U的寄存器定义文件。自己编写这些寄存器定义不仅费时费力,没有任何意义,而且很容易写错,幸好NXP官方为I.MX6ULL编写了SDK包,在SDK包里面NXP已经编写好了寄存器定义文件,所以我们可以直接移植SDK包里面的文件来用。虽然NXP是为I.MX6ULL编写的SDK包,但是I.MX6UL也是可以使用的!本章我们就来讲解如何移植SDK包里面重要的文件,方便我们的开发。

12.1 I.MX6ULL官方SDK包简介

NXP针对I.MX6ULL编写了一个SDK包,这个SDK包就类似于STM32的STD库或者HAL库,这个SDK包提供了Windows和Linux两种版本,分别针对主机系统是Windows和Linux。因为我们是在Windows下使用Source Insight来编写代码的,因此我们使用的是Windows版本的。Windows版本SDK里面的例程提供了IAR版本,肯定有人会问既然NXP提供了IAR版本的SDK,那我们为什么不用IAR来完成裸机试验,偏偏要用复杂的GCC?因为我们要从简单的裸机开始掌握Linux下的GCC开发方法,包括Ubuntu操作系统的使用、Makefile的编写、shell等等。如果为了偷懒而使用IAR开发裸机的话,那么后续学习Uboot移植、Linux移植和Linux驱动开发就会很难上手,因为开发环境都不熟悉!再者,不是所有的半导体厂商都会为Cortex-A架构的芯片编写裸机SDK包,我使用过那么多的Cotex-A系列芯片,也就发现了NXP给I.MX6ULL编写了裸机SDK包。而且去NXP官网看一下,会发现只有I.MX6ULL这一款Cotex-A内核的芯片有裸机SDK包,NXP的其它Cotex-A芯片都没有。说明在NXP的定位里面,I.MX6ULL就是一个Cotex-A内核的高端单片机,定位类似ST的STM32H7。说这么多的目的就是想告诉大家,使用Cortex-A内核芯片的时候不要想着有类似STM32库一样的东西,I.MX6ULL是一个特例,基本所有的Cortex-A内核的芯片都不会提供裸机SDK包。因此在使用STM32的时候那些用起来很顺手的库文件,在Cotex-A芯片下基本都需要我们自行编写,比如.s启动文件、寄存器定义等等。

因为本教程是教大家Linux驱动开发入门的,本教程需要尽可能的降低入门难度,这也是为什么本教程会选择I.MX6U芯片的一个重要的原因,因为其提供了I.MX6ULL的裸机SDK包,大家上手会很容易。I.MX6ULL的SDK包在NXP官网下载,下载界面如图12.1.1所示:

图12.1.1 I.MX6ULL SDK包下载界面

我们下载图12.1.1中的WIN版本SDK,也就是“SDK2.2_iMX6ULL_WIN”,我们已经下载好放到光盘中,路径为:开发板光盘-> 7、I.MX6U参考资料->3、I.MX6ULL SDK包->SDK_2.2_MCIM6ULL_RFP_Win.exe。双击SDK_2.2_MCIM6ULL_RFP_Win.exe安装SDK包,安装的时候需要设置好安装位置,安装完成以后的SDK包如图12.1.2所示:

图12.1.2 SDK包

我们本教程不是讲解SDK包如何开发的,我们只是需要SDK包里面的几个文件,所以就不去详细的讲解这个SDK包了,感兴趣的可以看一下,所有的例程都在boards这个文件夹里面。我们重点是需要SDK包里面与寄存器定义相关的文件,一共需要如下三个文件:

fsl_common.h:位置为SDK_2.2_MCIM6ULLdevicesMCIMX6Y2driversfsl_common.h。

fsl_iomuxc.h: 位置为SDK_2.2_MCIM6ULLdevicesMCIMX6Y2driversfsl_iomuxc.h。

MCIMX6Y2.h:位置为SDK_2.2_MCIM6ULLdevicesMCIMX6Y2MCIMX6YH2.h。

整个SDK包我们就需要上面这三个文件,把这三个文件准备好,我们后面移植要用。

12.2硬件原理图分析

本章使用到的硬件资源和第八章一样,就是一个LED0。

12.3试验程序编写

本实验对应的例程路径为:开发板光盘-> 1、裸机例程->4_ledc_sdk。

12.3.1 SDK文件移植

使用VSCode新建工程,将fsl_common.h、fsl_iomuxc.h和MCIMX6Y2.h这三个文件拷贝到工程中,这三个文件直接编译的话肯定会出错的!需要对其做删减,因为这三个文件里面的代码都比较大,所以就不详细列出这三个文件删减以后的内容了。大家可以参考我们提供的裸机例程来修改这三个文件,很简单的。修改完成以后的工程目录如图12.3.1.1所示:

图12.3.1.1工程目录

12.3.2 创建cc.h文件

新建一个名为cc.h的头文件,cc.h里面存放一些SDK库文件需要使用到的数据类型,在cc.h里面输入如下代码:

示例代码12.3.2.1 cc.h文件代码

1 #ifndef __CC_H

2 #define __CC_H

3/***************************************************************

4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.

5文件名 : cc.h

6作者 : 左忠凯

7版本 : V1.0

8描述 : 有关变量类型的定义,NXP官方SDK的一些移植文件会用到。

9其他 : 无

10日志 : 初版V1.0 2019/1/3 左忠凯创建

11 ***************************************************************/

12

13/*

14 * 自定义一些数据类型供库文件使用

15 */

16 #define __I volatile

17 #define __O volatile

18 #define __IO volatile

19

20 #define ON 1

21 #define OFF 0

22

23typedefsigned char int8_t;

24typedefsigned short int int16_t;

25typedefsigned int int32_t;

26typedefunsigned char uint8_t;

27typedefunsigned short int uint16_t;

28typedefunsigned int uint32_t;

29typedefunsigned long long uint64_t;

30typedefsigned char s8;

31typedefsigned short int s16;

32typedefsigned int s32;

33typedefsigned long long int s64;

34typedefunsigned char u8;

35typedefunsigned short int u16;

36typedefunsigned int u32;

37typedefunsigned long long int u64;

38

39 #endif

在cc.h文件中我们定义了很多的数据类型,因为有些第三方库会用到这些变量类型。

12.3.3编写实验代码

新建start.S和main.c这两个文件,start.S文件的内容和上一章一样,直接复制过来就可以,创建完成以后工程目录如图12.3.3.1所示:

图12.3.3.1 工程目录文件

在main.c中输入如下所示代码:

示例代码12.3.3.1 main.c文件代码

/**************************************************************

Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.

文件名 : mian.c

作者 : 左忠凯

版本 : V1.0

描述 : I.MX6U开发板裸机实验4 使用NXP提供的I.MX6ULL官方IAR SDK包开发

其他 : 前面其他所有实验中,寄存器定义都是我们自己手写的,但是I.MX6U

的寄存器有很多,全部自己写太费时间,而且没意义。NXP官方提供了

针对I.MX6ULL的SDK开发包,是基于IAR环境的,这个SDK包里面已经提

供了I.MX6ULL所有相关寄存器定义,虽然是针对I.MX6ULL编写的,但是同样

适用于I.MX6UL。本节我们就将相关的寄存器定义文件移植到Linux环境下,

要移植的文件有:

fsl_common.h

fsl_iomuxc.h

MCIMX6Y2.h

自定义文件 cc.h

日志 : 初版V1.0 2019/1/3 左忠凯创建

**************************************************************/

1 #include "fsl_common.h"

2 #include "fsl_iomuxc.h"

3 #include "MCIMX6Y2.h"

4

5/*

6 * @description : 使能I.MX6U所有外设时钟

7 * @param : 无

8 * @return : 无

9 */

10void clk_enable(void)

11{

12 CCM->CCGR0 =0XFFFFFFFF;

13 CCM->CCGR1 =0XFFFFFFFF;

14

15 CCM->CCGR2 =0XFFFFFFFF;

16 CCM->CCGR3 =0XFFFFFFFF;

17 CCM->CCGR4 =0XFFFFFFFF;

18 CCM->CCGR5 =0XFFFFFFFF;

19 CCM->CCGR6 =0XFFFFFFFF;

20

21}

22

23/*

24 * @description : 初始化LED对应的GPIO

25 * @param : 无

26 * @return : 无

27 */

28void led_init(void)

29{

30/* 1、初始化IO复用 */

31 IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0);

32

33/* 2、、配置GPIO1_IO03的IO属性

34 *bit 16:0 HYS关闭

35 *bit [15:14]: 00 默认下拉

36 *bit [13]: 0 kepper功能

37 *bit [12]: 1 pull/keeper使能

38 *bit [11]: 0 关闭开路输出

39 *bit [7:6]: 10 速度100Mhz

40 *bit [5:3]: 110 R0/6驱动能力

41 *bit [0]: 0 低转换率

42 */

43 IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0X10B0);

44

45/* 3、初始化GPIO,设置GPIO1_IO03设置为输出 */

46 GPIO1->GDIR |=(1<<3);

47

48/* 4、设置GPIO1_IO03输出低电平,打开LED0 */

49 GPIO1->DR &=~(1<<3);

50}

51

52/*

53 * @description : 打开LED灯

54 * @param : 无

55 * @return : 无

56 */

57void led_on(void)

58{

59/* 将GPIO1_DR的bit3清零 */

60 GPIO1->DR &=~(1<<3);

61}

62

63/*

64 * @description : 关闭LED灯

65 * @param : 无

66 * @return : 无

67 */

68void led_off(void)

69{

70/* 将GPIO1_DR的bit3置1 */

71 GPIO1->DR |=(1<<3);

72}

73

74/*

75 * @description : 短时间延时函数

76 * @param - n : 要延时循环次数(空操作循环次数,模式延时)

77 * @return : 无

78 */

79void delay_short(volatileunsignedint n)

80{

81while(n--){}

82}

83

84/*

85 * @description : 延时函数,在396Mhz的主频下

86 * 延时时间大约为1ms

87 * @param - n : 要延时的ms数

88 * @return : 无

89 */

90void delay(volatileunsignedint n)

91{

92while(n--)

93{

94 delay_short(0x7ff);

95}

96}

97

98/*

99 * @description : mian函数

100 * @param : 无

101 * @return : 无

102 */

103int main(void)

104{

105 clk_enable(); /* 使能所有的时钟 */

106 led_init(); /* 初始化led */

107

108while(1) /* 死循环 */

109{

110 led_off(); /* 关闭LED */

111 delay(500); /* 延时500ms */

112

113 led_on(); /* 打开LED */

114 delay(500); /* 延时500ms */

115}

116

117return0;

118}

和上一章一样,main.c有7个函数,这7个函数的含义都一样,只是本例程我们使用的是移植好的NXP官方SDK里面的寄存器定义。main.c文件的这7个函数的内容都很简单,前面都讲过很多次了,我们重点来看一下led_init函数中的第31行和第43行,这两行的内容如下:

IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);

IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0X10B0);

这里使用了两个函数IOMUXC_SetPinMux和IOMUXC_SetPinConfig,其中函数IOMUXC_SetPinMux是用来设置IO复用功能的,最终肯定设置的是寄存器“IOMUXC_SW_MUX_CTL_PAD_XX”。函数IOMUXC_SetPinConfig设置的是IO的上下拉、速度等的,也就是寄存器“IOMUXC_SW_PAD_CTL_PAD_XX”,所以上面两个函数其实就是上一章中的:

IOMUX_SW_MUX->GPIO1_IO03 = 0X5;

IOMUX_SW_PAD->GPIO1_IO03 = 0X10B0;

函数IOMUXC_SetPinMux在文件fsl_iomuxc.h中定义,函数源码如下:

static inline void IOMUXC_SetPinMux(uint32_t muxRegister,

uint32_t muxMode,

uint32_t inputRegister,

uint32_t inputDaisy,

uint32_t configRegister,

uint32_t inputOnfield)

{

*((volatile uint32_t *)muxRegister) =

IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) |

IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);

if (inputRegister)

{

*((volatile uint32_t *)inputRegister) =

IOMUXC_SELECT_INPUT_DAISY(inputDaisy);

}

}

函数IOMUXC_SetPinMux有6个参数,这6个参数的函数如下:

muxRegister:IO的复用寄存器地址,比如GPIO1_IO03的IO复用寄存器SW_MUX_CTL_PAD_GPIO1_IO03的地址为0X020E0068。

muxMode: IO复用值,也就是ALT0~ALT8,对应数字0~8,比如要将GPIO1_IO03设置为GPIO功能的话此参数就要设置为5。

inputRegister:外设输入IO选择寄存器地址,有些IO在设置为其他的复用功能以后还需要设置IO输入寄存器,比如GPIO1_IO03要复用为UART1_RX的话还需要设置寄存器UART1_RX_DATA_SELECT_INPUT,此寄存器地址为0X020E0624。

inputDaisy:寄存器inputRegister的值,比如GPIO1_IO03要作为UART1_RX引脚的话此参数就是1。

configRegister:未使用,函数IOMUXC_SetPinConfig会使用这个寄存器。

inputOnfield:IO软件输入使能,以GPIO1_IO03为例就是寄存器SW_MUX_CTL_PAD_GPIO1_IO03的SION位(bit4)。如果需要使能GPIO1_IO03的软件输入功能的话此参数应该为1,否则的话就为0。

IOMUXC_SetPinMux的函数体很简单,就是根据参数对寄存器muxRegister和inputRegister进行赋值。在“示例代码12.3.3.1”中的31行使用此函数将GPIO1_IO03的复用功能设置为GPIO,如下:

IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);

第一次看到上面代码的时候肯定会奇怪,为何只有两个参数?不是应该6个参数的吗?不要着急,先看一个IOMUXC_GPIO1_IO03_GPIO1_IO03是个什么玩意。这是个宏,在文件fsl_iomuxc.h中有定义,NXP的SDK库将一个IO的所有复用功能都定义了一个宏,比如GPIO1_IO03就有如下9个宏定义:

IOMUXC_GPIO1_IO03_I2C1_SDA

IOMUXC_GPIO1_IO03_GPT1_COMPARE3

IOMUXC_GPIO1_IO03_USB_OTG2_OC

IOMUXC_GPIO1_IO03_USDHC1_CD_B

IOMUXC_GPIO1_IO03_GPIO1_IO03

IOMUXC_GPIO1_IO03_CCM_DI0_EXT_CLK

IOMUXC_GPIO1_IO03_SRC_TESTER_ACK

IOMUXC_GPIO1_IO03_UART1_RX

IOMUXC_GPIO1_IO03_UART1_TX

上面9个宏定义分别对应着GPIO1_IO03的九种复用功能,比如复用为GPIO的宏定义就是:

#define IOMUXC_GPIO1_IO03_GPIO1_IO03 0x020E0068U, 0x5U, 0x00000000U,

0x0U, 0x020E02F4U

将这个宏带入到“示例代码12.3.3.1”的31行以后就是:

IOMUXC_SetPinMux (0x020E0068U, 0x5U, 0x00000000U, 0x0U, 0x020E02F4U, 0);

这样就与函数IOMUXC_SetPinMux的6个参数对应起来了,如果我们要将GPIO1_IO03复用为I2C1_SDA的话就可以使用如下代码:

IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_I2C1_SDA, 0);

函数IOMUXC_SetPinMux就讲解到这里,接下来看一下函数IOMUXC_SetPinConfig,此函数同样在文件fsl_iomuxc.h中有定义,函数源码如下:

static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,

uint32_t muxMode,

uint32_t inputRegister,

uint32_t inputDaisy,

uint32_t configRegister,

uint32_t configValue)

{

if (configRegister)

{

*((volatile uint32_t *)configRegister) = configValue;

}

}

函数IOMUXC_SetPinConfig有6个参数,其中前五个参数和函数IOMUXC_SetPinMux一样,但是此函数只使用了参数configRegister和configValue,cofigRegister参数是IO配置寄存器地址,比如GPIO1_IO03的IO配置寄存器为IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03,其地址为0X020E02F4,参数configValue就是要写入到寄存器configRegister的值。同理,“示例代码12.3.3.1”的43行展开以后就是:

IOMUXC_SetPinConfig(0x020E0068U, 0x5U, 0x00000000U, 0x0U, 0x020E02F4U, 0X10B0);

根据函数IOMUXC_SetPinConfig的源码可以知道,上面函数就是将寄存器0x020E02F4的值设置为0X10B0。函数IOMUXC_SetPinMux和IOMUXC_SetPinConfig就讲解到这里,我们以后就可以使用这两个函数来方便的配置IO的复用功能和IO配置。

main.c就讲到这里,基本和上一章一样,只是我们使用了NXP官方写好的寄存器定义,另外中断讲解了函数IOMUXC_SetPinMux和IOMUXC_SetPinConfig。

12.4编译下载验证

12.4.1编写Makefile和链接脚本

新建Makefile文件,Makefile文件内容如下:

示例代码12.4.1.1 Makefile文件代码

1 CROSS_COMPILE ?= arm-linux-gnueabihf-

2 NAME ?= ledc

3

4 CC :=$(CROSS_COMPILE)gcc

5 LD :=$(CROSS_COMPILE)ld

6 OBJCOPY :=$(CROSS_COMPILE)objcopy

7 OBJDUMP :=$(CROSS_COMPILE)objdump

8

9 OBJS:= start.o main.o

10

11$(NAME).bin:$(OBJS)

12 $(LD) -Timx6ul.lds -o $(NAME).elf $^

13 $(OBJCOPY) -O binary -S $(NAME).elf $@

14 $(OBJDUMP) -D -m arm $(NAME).elf >$(NAME).dis

15

16 %.o:%.s

17 $(CC) -Wall -nostdlib -c -O2 -o $@ $<

18

19 %.o:%.S

20 $(CC) -Wall -nostdlib -c -O2 -o $@ $<

21

22 %.o:%.c

23 $(CC) -Wall -nostdlib -c -O2 -o $@ $<

24

25 clean:

26 rm -rf *.o $(NAME).bin $(NAME).elf $(NAME).dis

本章实验的Makefile文件是在第十一章中的Makefile上修改的,只是使用到了变量。链接脚本imx6ul.lds的内容和上一章一样,可以直接使用上一章的链接脚本文件。

12.4.2编译下载

使用Make命令编译代码,编译成功以后使用软件imxdownload将编译完成的ledc.bin文件下载到SD卡中,命令如下:

chmod 777 imxdownload //给予imxdownload可执行权限,一次即可

./imxdownload ledc.bin /dev/sdd //烧写到SD卡中

烧写成功以后将SD卡插到开发板的SD卡槽中,然后复位开发板,如果代码运行正常的话LED0就会以500ms的时间间隔亮灭,实验现象和上一章一样。

韦东山 IMX6ULL和正点原子_「正点原子Linux连载」第十二章官方SDK移植试验相关推荐

  1. stm32 文件系统dma大小_「正点原子NANO STM32F103开发板资料连载」第二十二章 DMA 实验...

    1)实验平台:[正点原子] NANO STM32F103 开发板 2)摘自<正点原子STM32 F1 开发指南(NANO 板-HAL 库版)>关注官方微信号公众号,获取更多资料:正点原子 ...

  2. linux键盘设置的文件在哪个文件夹,「正点原子Linux连载」第十五章按键输入试验...

    原标题:「正点原子Linux连载」第十五章按键输入试验 第十五章按键输入试验 前面几章试验都是讲解如何使用I.MX6U的GPIO输出控制功能,I.MX6U的IO不仅能作为输出,而且也可以作为输入.I. ...

  3. 【正点原子Linux连载】第二十二章 AP3216C 摘自【正点原子】I.MX6U嵌入式Qt开发指南V1.0.2

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 3)全套实验源码+手册+视频下载地址: ...

  4. 判断按键值_「正点原子NANO STM32开发板资料连载」第十六章电容触摸按键实验...

    1)实验平台:ALIENTEK NANO STM32F411 V1开发板2)摘自<正点原子STM32F4 开发指南(HAL 库版>关注官方微信号公众号,获取更多资料:正点原子 第十六章电容 ...

  5. 【正点原子FPGA连载】第十二章 呼吸灯实验 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

  6. 【正点原子FPGA连载】第十二章呼吸灯实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  7. 【正点原子FPGA连载】 第二十二章 HDMI方块移动实验 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

  8. 韦东山 IMX6ULL和正点原子_「正点原子Linux连载」第四十四章设备树下的LED驱动实验...

    1)实验平台:正点原子Linux开发板 2)摘自<正点原子I.MX6U嵌入式Linux驱动开发指南> 关注官方微信号公众号,获取更多资料:正点原子 上一章我们详细的讲解了设备树语法以及在驱 ...

  9. 简述sd卡2.0协议_【正点原子FPGA连载】第十二章SD卡读写TXT文本实验-领航者 ZYNQ 之嵌入式开发指南...

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

最新文章

  1. phaser.min.js_如何使用Phaser 3,Express和Socket.IO构建多人纸牌游戏
  2. LeetCode 解题报告索引
  3. Python如何实现单例模式?其他23中设计模式python如何实现?
  4. Android studio | Android studio下APP目录工程结构详解
  5. TCPview 介绍
  6. 时间控制插件调用接口
  7. linux的常用操作——共享库
  8. mysql差异备份数据库get shell_shell进行完整和增量备份mysql数据库
  9. 【nodejs原理源码赏析(4)】深度剖析cluster模块源码与node.js多进程(上)
  10. 有哪些不怎么火,实际上却很厉害的软件
  11. nginx发布静态目录备忘
  12. 16. Use the same form in correspondng uses of new and delete
  13. Atitit. Gui控件and面板----程序快速启动区--最佳实践Launchy ObjectDock-o0g
  14. 轩逸android 苹果 蓝牙,【图】Iphone与轩逸车载蓝牙连接的小技巧
  15. qq语音聊天 java_用Java语言实现QQ部分聊天功能
  16. 值得一看的纪录片——《河西走廊》墙裂推荐
  17. ERLANG recon使用示例
  18. 浪潮和思科联合 华为、新华三怎么看?
  19. python实现AI抠图
  20. SAP结帐操作详细操作指南

热门文章

  1. 汾阳市教师招聘中职计算机真题,2019年山西省吕梁市汾阳市中小学语文教师招聘/编制考试历年真题试卷及答案解析...
  2. 震惊!我竟然在1080Ti上加载了一个35亿参数的模型(ZeRO, Zero Redundancy Optimizer)
  3. JNPF快速开发框架的八大功能介绍
  4. 解决海康威视摄像头无法通过路由器远程访问的问题
  5. Gitlab回滚到上次提交
  6. windows远程桌面互传文件
  7. SCOI2012 Blinker的仰慕者 BZOJ 2757
  8. date命令显示格式化的年月日时分秒
  9. 22年的梦想《仙剑奇侠传》
  10. QA的职责和角色定位