目 录
SylixOS 最小系统包含中断、Tick、串口三部分。其中中断为最小系统的任务处理提供基础,允许系统进行任务调度;Tick 是整个最小系统的最小计时单位,为系统的任务处理时间提供参考依据;串口是最小系统中板卡与 PC 端进行通信的基础,用于板卡信息的收发。三个部分的协同作用保证了 SylixOS 最小系统的正常运行。
移植T3bsp最小系统可参考博客:http://www.databusworld.cn/10115.html。博客中的内容可支持达到操作系统通过uboot的串口驱动来打印出调试信息。除此之外,文档偏向与UART驱动框架的讲述。
图 1-1 内核启动流程
这个就是startup.S中做的工作,主要就是一些架构相关的初始化工作。
主核启动时在BSP中会调用API_KernelPrimaryStart 执行内核所有组件的初始化工作。首先会调用_KernelPrimaryLowLevelInit 接口来初始化系统一些基础组件,比如调度器、消息队列、内核堆管理器等等:
通过调用bspIntInit 接口来初始化中断控制器,这个接口需要在bspLib.c中实现。
调用_KernelHighLevelInit 来初始化系统高级组件,比如信号系统、中断延迟队列、总线系统。
这是在bspInit.c中的usrStartup 函数中做的。
BSP中的大部分驱动初始化、设备创建等等工作都是在halBootThread 这个线程中做的。这个线程同样是在usrStartup 函数中创建,但是要注意的是这时仅仅是创建了这个线程,它并没有被调度运行,在内核所有工作都初始化完成后会调度这个线程进行工作。
7、BSP系统TICK初始化
通过调用bspLib.c中的bspTickInit 接口来初始化系统TICK,其实就是初始化一个硬件定时器并让其以系统TICK频率开始工作。
8、启动内核,调度任务运行
到这里为止,内核的初始化工作哦基本已经完成了,这时就需要选择一个优先级最高的线程来运行了,这是通过调用_KernelPrimaryCoreStartup 来实现的。
9、运行初始化启动任务
在这个初始化线程halBootThread 中会去初始化驱动、Posix组件、动态加载器、SylixOS Shell等等一些列的功能,这些初始化的顺序使用默认的即可,一般不需要太大的修改。这里只介绍几个调用到的比较重要的接口。
10、创建t_main任务运行
这个线程主要就是创建一个SylixOS Shell以让用户可以和SylixOS进行交互。
使用时用户设置一个初始值,启动后定时器进行递减,如果减到0则可以产生一个中断。如果用户使用自动重载功能,硬件自动重新从初始值再次开始递,直到再次减到0产生中断,依次往复,如图 2-1所示。
图 2-1 定时器流程图
进行 T3 定时器的驱动实现。驱动相关函数接口如下:
VOID timerStart (INT32 iNum, UINT32 uiHZ) { UINT32 uiCount; UINT32 uiIntvOffset; UINT32 uiCtrlOffset; if (0 == iNum) { uiIntvOffset = TIMER0_INTV; uiCtrlOffset = TIMER0_CTRL; } else if (1 == iNum) { uiIntvOffset = TIMER1_INTV; uiCtrlOffset = TIMER1_CTRL; } else { return ; } uiCount = TIMER_FREQ; uiCount /= uiHZ; writel(uiCount, TIMER_BASE + uiIntvOffset); writel(BIT(1) | BIT(2) | BIT(4), TIMER_BASE + uiCtrlOffset); while ((readl(TIMER_BASE + uiCtrlOffset) >> 1) & 0x01); writel(readl(TIMER_BASE + uiCtrlOffset) | BIT(0), TIMER_BASE + uiCtrlOffset); writel(readl(TIMER_BASE + TIMER_IRQ_EN) | BIT(iNum), TIMER_BASE + TIMER_IRQ_EN); } VOID timerIntClear (INT32 iNum) { if ((0 != iNum) && (1 != iNum)) return ; writel(readl(TIMER_BASE + TIMER_IRQ_STA) | BIT(iNum), TIMER_BASE + TIMER_IRQ_STA); } BOOL timerIsIntPending (INT32 iNum) { if ((0 != iNum) && (1 != iNum)) return FALSE; return (readl(TIMER_BASE + TIMER_IRQ_STA) & BIT(iNum)) ? TRUE : FALSE; } |
timerStart:使能定时器,需要将定时器输出的频率作为参数传入。
timerIntClear:清除定时器模块中断状态。
timerIsIntPending:检测定时器模块是否产生了中断。
串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工业接口。
UART 作为串口的一种,其工作原理也是将数据一位一位的进行传输,发送和接收各用一条线,因此通过 UART 接口与外界相连最少只需要三条线:TXD(发送)、RXD(接收)和 GND(地 线)。图 3-1 就是 UART 的通信格式:
图中各位的含义如下:
空闲位:数据线在空闲状态的时候为逻辑“1”状态,也就是高电平,表示没有数据线空闲,没有数据传输。
起始位:当要传输数据的时候先传输一个逻辑“0”,也就是将数据线拉低,表示开始数据传输。
数据位:数据位就是实际要传输的数据,数据位数可选择 5~8 位,我们一般都是按照字节传输数据的,一个字节 8 位,因此数据位通常是 8 位的。低位在前,先传输,高位最后传输。
奇偶校验位:这是对数据中“1”的位数进行奇偶校验用的,可以不使用奇偶校验功能。
停止位:数据传输完成标志位,停止位的位数可以选择 1 位、1.5 位或 2 位高电平,一般都选择 1 位停止位。
波特率:波特率就是 UART 数据传输的速率,也就是每秒传输的数据位数,一般选择 9600、19200、115200 等。
定时器的接口函数实现主要参考 T3数据手册对相应寄存器的设置,通过对这些寄存器的信息读写操作实现定时器驱动,图 3-2所示。
Register Name | Offset | Description |
UART_RBR | 0x0000 | 接收缓存区寄存器 |
UART_THR | 0x0000 | 传送保持寄存器 |
UART_DLL | 0x0000 | 除数锁存寄存器 |
UART_DLH | 0x0004 | 除数锁存寄存器 |
UART_IER | 0x0004 | 中断使能寄存器 |
UART_IIR | 0x0008 | 中断标识寄存器 |
UART_FCR | 0x0008 | FIFO控制寄存器 |
UART_LCR | 0x000C | 线路控制寄存器 |
UART_MCR | 0x0010 | Modem控制寄存器 |
UART_LSR | 0x0014 | 线路状态寄存器 |
UART_MSR | 0x0018 | Modem状态寄存器 |
UART_SCH | 0x001C | Scratch注册 |
UART_USR | 0x007C | 状态寄存器 |
UART_TFL | 0x0080 | 传输FIFO级别 |
UART_RFL | 0x0084 | 收到FIFO级别 |
UART_HALT | 0x00A1 | 停止TX寄存器 |
图 3-2定时器相关寄存器
程序所需要配置的寄存器也只有RBR、THR、DLL、DLH、IER、FCR、LCR、LSR、USR寄存器。
RBR、THR寄存器:(USR寄存器bit0位为1:busy,0:空闲)只有UART在空闲状态下设置了LCR的bit7位DLAB为0才可以访问此寄存器,接受和发送数据。
DLL、DLH寄存器:(USR寄存器bit0位为1:busy,0:空闲)只有UART在空闲状态下设置了LCR的bit7位DLAB为1才可以访问此寄存器,设置波特率。
IER:bit0来启用接收数据可用中断,bit1来启用传输保持寄存器空中断。
FCR:bit0使能FIFO每当该位改变,FIFO的XMIT和RCVR控制寄存器都会重置,bit1 RCVR重置自我清理,bit2 XMIF重置自我清理。
LCR:配置数据位,停止位,奇偶校验,除数锁存寄存器。
LSR:bit0 DR数据准备好,bit5 THRE传输保持寄存器为空。
USR:bit0 uart busy,传输和发送FIFO是否为满,是否为空。
TTY设备是一个字符设备,对TTY设备的操作流程与普通文件操作流程相似,用SIO_CHAN创建一个TTY设备。
安装TTY设备驱动程序 iosDrvInstall(_ttyOpen, (FUNCPTR)LW_NULL, _ttyOpen, _ttyClose, _TyRead, _TyWrite, _ttyIoctl); |
UART 的正常运行主要依靠五个基本功能函数实现:Ioctl 串口控制函数、TxStartup 启动发送函数、CallbackInstall 回调注册函数、PollInput 轮询接收函数、PollOutput 轮询发送函数。这五个函数会在 base 中进行调用以确保 UART 的正常运行。
_TyWrite中:SemBPend,rngBufPut,_TyTxStartup,SemBPost。
_TyRead中:SemBPend,rngBufGet,SemBPost。
_ttyloctl中:sioloctl,_Tyloctl。
_ttyStartup中:sioTxStartup。
SIO_DRV_FUNCS按照要求实现5个函数
struct sio_drv_funcs { /* driver functions */
INT (*ioctl) ( SIO_CHAN *pSioChan, INT cmd, PVOID arg );
INT (*txStartup) ( SIO_CHAN *pSioChan );
INT (*callbackInstall) ( SIO_CHAN *pSioChan, INT callbackType, VX_SIO_CALLBACK callback, PVOID callbackArg );
INT (*pollInput) ( SIO_CHAN *pSioChan, PCHAR inChar );
INT (*pollOutput) ( SIO_CHAN *pSioChan, CHAR outChar ); }; |
CallbackInstall:回调实现接口。
Poll接口:轮询收发接口实现。
sio16c550isr:收发中断接口。
TxStartup:启动发送接口。
进行最小系统移植起初会进行bspBoardDebugMsg打印调试信息,此时操作系统通过uboot的串口驱动来打印出调试信息,使用的是直接向THR寄存器写入数据。
在进行uart驱动框架的搭建,操作系统是通过TTY设备来访问UART驱动的,集可进行open,read,write,close,ioctl。开发者在编写UART驱动只需要将sio_drv_funcs结构体的函数实现就可以被tty设备调用。
app向串口发送数据大致流程为:app将buffer传给tty设备的buffer,tty设备的buffer再向寄存器写入。
串口向app发送数据大致流程为:串口将数据传给tty设备的buffer,tty设备的buffer再传给app。
T3 最小系统中的 UART 驱动调用主要体现在 bsp 多任务状态下的初始化启动任务中创建了一个 SylixOS 可识别的 tty 字符终端设备。主要过程为在 ”bsp/bspInit.c” 中 halDevInit函数初始化目标系统静态设备组件时创建一个UART 通道,然后调用内核接口函数添加相应 tty 设备,具体设置如下:
SIO_CHAN *psio; /* 内核可识别通道 */
#if UART0_EN > 0 psio = sioChanCreate(0); /* 创建串口0 通道*/ if (psio) { ttyDevCreate("/dev/ttyS0", psio, 256, 256); /* 增加 tty 设备 */ } #endif |
注:
为避免 T3 最小系统的板卡驱动初始化的更改影响 bsp 的整体运行,将板级设备初始化单独封装成 bspBoardDevInit 函数便于驱动的删改;
为便于不同 UART 的管理,本例中将不同 UART 的使能进行了宏定义;
由于在目标板卡上未将 UART1 和 UART6对应引脚引出,所以本例中未将 UART1 和 UART6 进行初始化。
全志 T3 板卡最小系统搭建完毕后应能正常启动 SylixOS 终端,在屏幕上打印 SylixOS 标志及 bsp 基本信息,且能正常使用 tshell 风格命令,同时每次调用tshell 命令后相对应的 UART 中断及 Tick 中断都应当增加,如图 4-1所示。至此全志 T3 板卡 SylixOS 最小系统搭建完毕,中断、Tick 和串口驱动运行正常。
图 4-1SylixOS 最小系统运行
注:
图示为避免 Uboot 串口初始化影响串口驱动正常运行与否判断,已将系统标准输入输出文件设置为 “/dev/ttyS7”,即 UART7。此时 UART7 能正常显示信息并产生中断,证明串口驱动编写无误,需将系统标准输入输出文件改为默认 “/dev/ttyS0”。
《SylixOS设备驱动程序开发》
全志 T3 Linux 源码
Base 16c550源码
博客:http://www.databusworld.cn/10202.html