实验过程
新建 src/include/prt_shell.h 头文件:
#ifndef _HWLITEOS_SHELL_H
#define _HWLITEOS_SHELL_H
#include "prt_typedef.h"
#define SHELL_SHOW_MAX_LEN 272
#define PATH_MAX 1024
typedef struct {
U32 consoleID;
U32 shellTaskHandle;
U32 shellEntryHandle;
void *cmdKeyLink;
void *cmdHistoryKeyLink;
void *cmdMaskKeyLink;
U32 shellBufOffset;
U32 shellBufReadOffset;
U32 shellKeyType;
char shellBuf[SHELL_SHOW_MAX_LEN];
char shellWorkingDirectory[PATH_MAX];
} ShellCB;
#endif /* _HWLITEOS_SHELL_H */
该结构体包含多个成员,负责跟踪 shell 的各种状态信息,包括控制台 ID、任务句柄、命令历史记录等。
consoleID
:用于标识控制台。
shellTaskHandle
:保存 shell 任务的句柄。
shellEntryHandle
:保存 shell 条目的句柄。
cmdKeyLink
:指向命令键链的指针,可能是链表或数组的某种数据结构。
cmdHistoryKeyLink
:指向命令历史键链的指针。
cmdMaskKeyLink
:指向命令屏蔽键链的指针。
shellBufOffset
:shell 缓冲区的偏移量,表示当前写入位置。
shellBufReadOffset
:shell 缓冲区的读取偏移量。
shellKeyType
:保存键类型,可能用于命令解析。
shellBuf
:用于存储 shell 输入的字符缓冲区。
shellWorkingDirectory
:存储 shell 的当前工作目录。
接收输入
QEMU的virt机器默认没有键盘作为输入设备,但当我们执行QEMU使用 -nographic 参数(disable graphical output and redirect serial I/Os to console)时QEMU会将串口重定向到控制台,因此我们可以使用UART作为输入设备。
在 src/bsp/print.c 中的 PRT_UartInit 添加初始化代码,使其支持接收数据中断。 同时定义了用于串口接收的信号量 sem_uart_rx。
#include "prt_sem.h"
#include "prt_shell.h"
#define UARTCR_UARTEN (1 << 0)
#define UARTCR_TXE (1 << 8)
#define UARTCR_RXE (1 << 9)
#define UARTICR_ALL (1 << 0)
#define UARTIMSC_RXIM (1 << 4)
#define UARTIBRD_IBRD_MASK 0xFFFF
#define UARTFBRD_FBRD_MASK 0x3F
#define UARTLCR_H_WLEN_MASK (3 << 5)
#define UARTLCR_H_PEN (1 << 1)
#define UARTLCR_H_STP1 (0 << 3)
SemHandle sem_uart_rx;
extern void OsGicIntSetConfig(uint32_t interrupt, uint32_t config);
extern void OsGicIntSetPriority(uint32_t interrupt, uint32_t priority);
extern void OsGicEnableInt(U32 intId);
extern void OsGicClearInt(uint32_t interrupt);
extern U32 PRT_Printf(const char *format, ...);
U32 PRT_UartInit(void)
{
U32 result = 0;
U32 reg_base = UART_0_REG_BASE;
UART_REG_WRITE(0, (unsigned long)(reg_base + 0x30));// 禁用pl011
UART_REG_WRITE(0x7ff, (unsigned long)(reg_base + 0x44));// 清空中断状态
UART_REG_WRITE(UARTIMSC_RXIM, (unsigned long)(reg_base + 0x38));// 设定中断mask,需要使能的中断
UART_REG_WRITE(13, (unsigned long)(reg_base + 0x24));
UART_REG_WRITE(1, (unsigned long)(reg_base + 0x28));
// https://developer.arm.com/documentation/ddi0183/g/programmers-model/register-descriptions/line-control-register--uartlcr-h?lang=en
result = UART_REG_READ((unsigned long)(reg_base + DW_UART_LCR_HR));
result = result | UARTLCR_H_WLEN_MASK | UARTLCR_H_PEN | UARTLCR_H_STP1 | DW_FIFO_ENABLE;
UART_REG_WRITE(result, (unsigned long)(reg_base + DW_UART_LCR_HR)); // 8N1 FIFO enable
UART_REG_WRITE(UARTCR_UARTEN | UARTCR_RXE | UARTCR_TXE, (unsigned long)(reg_base + 0x30));// 启用pl011
// 启用UART 接收中断
OsGicIntSetConfig(33, 0); //可省略
OsGicIntSetPriority(33, 0);
OsGicClearInt(33); //可省略
OsGicEnableInt(33);
// 创建uart数据接收信号量
U32 ret;
ret = PRT_SemCreate(0, &sem_uart_rx);
if (ret != OS_OK) {
PRT_Printf("failed to create uart_rx sem\n");
return 1;
}
return OS_OK;
}
这个函数的主要功能是初始化UART,包括禁用UART、清空中断状态、设置波特率、配置行控制寄存器、启用UART、配置GIC中断控制器和创建UART数据接收信号量。通过这些步骤,确保UART可以正常工作并处理接收中断。
- 禁用pl011 UART:
UART_REG_WRITE(0, (unsigned long)(reg_base + 0x30));
- 禁用UART,确保在配置过程中不会有数据传输。
- 清空中断状态:
UART_REG_WRITE(0x7ff, (unsigned long)(reg_base + 0x44));
- 清除所有中断状态,确保没有残留的中断。
- 设定中断mask:
UART_REG_WRITE(UARTIMSC_RXIM, (unsigned long)(reg_base + 0x38));
- 使能接收中断。
- 设置波特率:
UART_REG_WRITE(13, (unsigned long)(reg_base + 0x24));
UART_REG_WRITE(1, (unsigned long)(reg_base + 0x28));
- 设置波特率的整数和小数部分。
- 配置行控制寄存器:
result = UART_REG_READ((unsigned long)(reg_base + DW_UART_LCR_HR));
result = result | UARTLCR_H_WLEN_MASK | UARTLCR_H_PEN | UARTLCR_H_STP1 | DW_FIFO_ENABLE;
UART_REG_WRITE(result, (unsigned long)(reg_base + DW_UART_LCR_HR));
- 配置UART为8位数据,无奇偶校验,1个停止位,启用FIFO。
- 启用pl011 UART:
UART_REG_WRITE(UARTCR_UARTEN | UARTCR_RXE | UARTCR_TXE, (unsigned long)(reg_base + 0x30));
- 启用UART的发送和接收功能。
- 配置GIC中断控制器:
OsGicIntSetConfig(33, 0);
OsGicIntSetPriority(33, 0);
OsGicClearInt(33);
OsGicEnableInt(33);
- 配置和启用UART接收中断。
- 创建UART数据接收信号量:
ret = PRT_SemCreate(0, &sem_uart_rx);
- 创建一个初始值为0的信号量,用于UART数据接收。
- 返回结果:
return OS_OK;
- 初始化成功返回
OS_OK
,否则返回错误码。
在 src/bsp/print.c 中实现 OsUartRxHandle() 处理接收中断:
extern ShellCB g_shellCB;
void OsUartRxHandle(void)
{
U32 flag = 0;
U32 result = 0;
U32 reg_base = UART_0_REG_BASE;
flag = UART_REG_READ((unsigned long)(reg_base + 0x18));
while((flag & (1<<4)) == 0)
{
result = UART_REG_READ((unsigned long)(reg_base + 0x0));
// PRT_Printf("%c", result);
// 将收到的字符存到g_shellCB的缓冲区
g_shellCB.shellBuf[g_shellCB.shellBufOffset] = (char) result;
g_shellCB.shellBufOffset++;
if (g_shellCB.shellBufOffset == SHELL_SHOW_MAX_LEN)
g_shellCB.shellBufOffset = 0;
PRT_SemPost(sem_uart_rx);
flag = UART_REG_READ((unsigned long)(reg_base + 0x18));
}
return;
}
这个函数用于处理UART接收中断,将接收到的数据存储到缓冲区中,并通知相关任务数据已经到达。
flag
和 result
是用于存储从 UART 读取的数据。
reg_base
是 UART 的基地址,在初始化时设置。
flag
读取 UART 的接收状态寄存器。reg_base + 0x18
通常对应 UART 的接收状态寄存器(FR)。
(flag & (1 << 4)) == 0
检查 UART 的接收 FIFO 是否为空。如果第 4 位是 0,表示 FIFO 中有数据。
result
读取 UART 的数据寄存器(DR),通常位于 reg_base + 0x0
处。此寄存器包含接收到的字符。
g_shellCB.shellBuf
是接收数据的缓冲区。
g_shellCB.shellBufOffset
是当前缓冲区的位置,指向下一个可以存放数据的位置。
g_shellCB.shellBufOffset++
增加缓冲区的偏移量。
如果缓冲区到达 SHELL_SHOW_MAX_LEN
(缓冲区的最大长度),则将偏移量重置为 0,实现循环缓冲区。
PRT_SemPost(sem_uart_rx)
向 sem_uart_rx
信号量发送信号,通知相关任务有新数据到达。相关任务通常会在这个信号量上等待数据。
在 src/bsp/prt_exc.c 中OsHwiHandleActive() 链接中断和处理函数OsUartRxHandle():
extern void OsTickDispatcher(void);
extern void OsUartRxHandle(void);
OS_SEC_ALW_INLINE INLINE void OsHwiHandleActive(U32 irqNum)
{
switch(irqNum){
case 30:
OsTickDispatcher();
// PRT_Printf(".");
break;
case 33:
OsUartRxHandle();
default:
break;
}
}
irqNum
为 30 时,调用 OsTickDispatcher
处理时钟中断。
irqNum
为 33 时,调用 OsUartRxHandle
处理 UART 接收中断。
在 src/kernel/task/prt_task.c 中加入函数:
extern U32 PRT_Printf(const char *format, ...);
OS_SEC_TEXT void OsDisplayTasksInfo(void)
{
struct TagTskCb *taskCb = NULL;
U32 cnt = 0;
PRT_Printf("\nPID\t\tPriority\tStack Size\n");
// 遍历g_runQueue队列,查找优先级最高的任务
LIST_FOR_EACH(taskCb, &g_runQueue, struct TagTskCb, pendList) {
cnt++;
PRT_Printf("%d\t\t%d\t\t%d\n", taskCb->taskPid, taskCb->priority, taskCb->stackSize);
}
PRT_Printf("Total %d tasks", cnt);
}
OsDisplayTasksInfo
函数用于显示系统中所有正在运行的任务信息,包括任务 PID、优先级和栈大小。它遍历任务运行队列 g_runQueue
,输出每个任务的相关信息并统计任务总数。
在 src/kernel/tick/prt_tick.c 中加入函数:
extern U32 PRT_Printf(const char *format, ...);
OS_SEC_TEXT void OsDisplayCurTick(void)
{
PRT_Printf("\nCurrent Tick: %d", PRT_TickGetCount());
}
显示系统当前的滴答计数。
shell 处理
新建 src/shell/shmsg.c 文件:
#include "prt_typedef.h"
#include "prt_shell.h"
#include "os_attr_armv8_external.h"
#include "prt_task.h"
#include "prt_sem.h"
extern SemHandle sem_uart_rx;
extern U32 PRT_Printf(const char *format, ...);
extern void OsDisplayTasksInfo(void);
extern void OsDisplayCurTick(void);
OS_SEC_TEXT void ShellTask(uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4)
{
U32 ret;
char ch;
char cmd[SHELL_SHOW_MAX_LEN];
U32 idx;
ShellCB *shellCB = (ShellCB *)param1;
while (1) {
PRT_Printf("\nminiEuler # ");
idx = 0;
for(int i = 0; i < SHELL_SHOW_MAX_LEN; i++)
{
cmd[i] = 0;
}
while (1){
PRT_SemPend(sem_uart_rx, OS_WAIT_FOREVER);
// 读取shellCB缓冲区的字符
ch = shellCB->shellBuf[shellCB->shellBufReadOffset];
cmd[idx] = ch;
idx++;
shellCB->shellBufReadOffset++;
if(shellCB->shellBufReadOffset == SHELL_SHOW_MAX_LEN)
shellCB->shellBufReadOffset = 0;
PRT_Printf("%c", ch); //回显
if (ch == '\r'){
// PRT_Printf("\n");
if(cmd[0]=='t' && cmd[1]=='o' && cmd[2]=='p'){
OsDisplayTasksInfo();
} else if(cmd[0]=='t' && cmd[1]=='i' && cmd[2]=='c' && cmd[3]=='k'){
OsDisplayCurTick();
}
break;
}
}
}
}
OS_SEC_TEXT U32 ShellTaskInit(ShellCB *shellCB)
{
U32 ret = 0;
struct TskInitParam param = {0};
// task 1
// param.stackAddr = 0;
param.taskEntry = (TskEntryFunc)ShellTask;
param.taskPrio = 9;
// param.name = "Test1Task";
param.stackSize = 0x1000; //固定4096,参见prt_task_init.c的OsMemAllocAlign
param.args[0] = (uintptr_t)shellCB;
TskHandle tskHandle1;
ret = PRT_TaskCreate(&tskHandle1, ¶m);
if (ret) {
return ret;
}
ret = PRT_TaskResume(tskHandle1);
if (ret) {
return ret;
}
}
ShellTask
函数实现了一个简单的命令行接口,通过UART接收用户输入的命令并执行相应的操作。这个函数目前能够处理两个命令:top
和 tick
。
在主循环中,函数首先打印提示符 miniEuler #
,然后初始化命令缓冲区 cmd
和索引 idx
,准备接收用户输入。
内部循环中,函数调用 PRT_SemPend
等待 sem_uart_rx
信号量,表示有新的数据到达。
从 shellCB
缓冲区读取字符,并存储到命令缓冲区 cmd
中。同时,回显用户输入的字符。
当检测到回车键('\r'
)时,表示用户输入结束。函数检查命令缓冲区中的内容,并根据命令执行相应的操作。
top
命令- 功能:显示当前系统中所有任务的信息。
- 实现:当用户输入
top
并按下回车键时,函数调用OsDisplayTasksInfo
函数来显示任务信息。
tick
命令- 功能:显示当前系统时钟的滴答数。
- 实现:当用户输入
tick
并按下回车键时,函数调用OsDisplayCurTick
函数来显示当前的系统时钟滴答数。
作业
实现一条有用的 shell 指令。
首先在main
函数中引用各种外部函数,启动Shell程序
extern U32 PRT_Printf(const char *format, ...);
extern void PRT_UartInit(void);
extern U32 OsActivate(void);
extern U32 OsTskInit(void);
extern U32 OsSemInit(void);
extern U32 OsHwiInit(void);
extern void ShellTask(uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4);
extern U32 ShellTaskInit(ShellCB *shellCB);
extern void CoreTimerInit(void);
extern ShellCB g_shellCB;
static SemHandle sem_sync;
static SemHandle sem_uart_rx;
.
.
.
OsHwiInit();
OsTskInit();
OsSemInit();
CoreTimerInit();
PRT_UartInit();
.
.
.
// 初始化 Shell 任务
ret = ShellTaskInit(&g_shellCB);
if (ret != OS_OK) {
PRT_Printf("failed to initialize shell task\n");
return ret;
实现结果如下:
这里实现了三条额外的指令:
第一条是clear指令,用于清零Tick值,使Tick从0开始计数:
else if(cmd[0]=='c' && cmd[1]=='l' && cmd[2]=='e' && cmd[3]=='a' && cmd[4]=='r')
{
OsClearTick();
}
OS_SEC_TEXT void OsClearTick(void)
{
g_uniTicks = 0;
PRT_Printf("Tick count cleared.\n");
PRT_Printf("Current Tick: 0");
}
第二条是Shell程序中常见的help指令,可以打印出所有的指令以及指令用途:
else if(cmd[0]=='h' && cmd[1]=='e' && cmd[2]=='l' && cmd[3]=='p'){
PRT_Printf("\ntop - Display tasks information");
PRT_Printf("\ntick - Display current tick count");
PRT_Printf("\nclear - Clear the tick");
PRT_Printf("\nhelp - Display help message");
PRT_Printf("\nquit - Exit the process");
}
第三条是quit,退出指令:
else if(cmd[0]=='q' && cmd[1]=='u' && cmd[2]=='i' && cmd[3]=='t')
{
PRT_Printf("\nThank you for using it! Looking forward to next use!") ;
flag=0;
}
最后的main程序为:
OS_SEC_TEXT void ShellTask(uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4)
{
U32 ret;
char ch;
char cmd[SHELL_SHOW_MAX_LEN];
U32 idx;
ShellCB *shellCB = (ShellCB *)param1;
int flag=1;
while (flag) {
PRT_Printf("\ngongming's miniEuler # ");
idx = 0;
for(int i = 0; i < SHELL_SHOW_MAX_LEN; i++)
{
cmd[i] = 0;
}
while (1){
PRT_SemPend(sem_uart_rx, OS_WAIT_FOREVER);
// 读取shellCB缓冲区的字符
ch = shellCB->shellBuf[shellCB->shellBufReadOffset];
cmd[idx] = ch;
idx++;
shellCB->shellBufReadOffset++;
if(shellCB->shellBufReadOffset == SHELL_SHOW_MAX_LEN)
shellCB->shellBufReadOffset = 0;
PRT_Printf("%c", ch); //回显
if (ch == '\r'){
// PRT_Printf("\n");
if(cmd[0]=='t' && cmd[1]=='o' && cmd[2]=='p'){
OsDisplayTasksInfo();
} else if(cmd[0]=='t' && cmd[1]=='i' && cmd[2]=='c' && cmd[3]=='k'){
OsDisplayCurTick();
}
else if(cmd[0]=='c' && cmd[1]=='l' && cmd[2]=='e' && cmd[3]=='a' && cmd[4]=='r'){
OsClearTick();
}
else if(cmd[0]=='h' && cmd[1]=='e' && cmd[2]=='l' && cmd[3]=='p'){
PRT_Printf("\ntop - Display tasks information");
PRT_Printf("\ntick - Display current tick count");
PRT_Printf("\nclear - Clear the tick");
PRT_Printf("\nhelp - Display help message");
PRT_Printf("\nquit - Exit the process");
}
else if(cmd[0]=='q' && cmd[1]=='u' && cmd[2]=='i' && cmd[3]=='t')
{
PRT_Printf("\nThank you for using it! Looking forward to next use!") ;
flag=0;
}
break;
}
}
}
}
标签:shellCB,UART,void,cmd,PRT,HNU,2022,Printf,lab9
From: https://blog.csdn.net/2301_76658831/article/details/143439407