ARM裸机-14.shell原理和问答机制引入

16.1.shell是什么鬼
16.1.1、壳与封装
(1)shell就是壳的意思,在计算机中经常提到shell是用户操作接口的意思。
(2)因为计算机程序本身很复杂,里面的实现和外面的调用必须分开。接口本身就是对内部复杂的实现的一种封装,外部只需要通过接口就可以很容易的实现效果,但是却不用理会内部实现的复杂性和原理。

16.1.2、程序或操作系统的用户接口
(1)操作系统运行起来后都会给用户提供一个操作界面,这个操作界面就叫shell。用户可以通过shell来调用操作系统内部的复杂实现。
(2)shell编程就是在shell层次上进行编程。譬如linux中的脚本编程、windows中的批处理。

16.1.3、两种shell:GUI和cmdline
(1)GUI(图形用户界面),特点是操作简单、易学易用,适合使用电脑来工作的人。
(2)cmdline(命令行界面),譬如linux的终端和windows的cmd,特点是不易用易学,优点是可以进行方便的shell编程,适合做开发的人。
(3)展望:将来的shell应该是声音图像等接口的。

16.1.4、shell的运行原理:由消息接收、解析、执行构成的死循环
(1)我们主要分析命令行shell的运行原理。
(2)命令行shell其实就是一个死循环。这个死循环包含3个模块,这3个模块是串联的,分别是命令接收、命令解析、命令执行。
(3)命令行有一个标准命令集,用户在操作的时候必须知道自己想要的操作用通过哪个命令来实现,不能随便输入命令。如果用户输入了一个不是标准命令的命令(不能识别的命令),提示用户这不是一个合法命令,然后重新回到命令行让用户输入下一个命令。
(4)用户输入命令的界面是一个命令行,命令行的意思就是用户输入的命令是以行为单位的,更好理解的说用户输入的命令在用户按下回车键之后就算是结束了,shell可以开始接收了。

16.1.5、shell举例:uboot、linux终端、Windows图形界面等
(1)常见的shell,uboot就是一个裸机程序构成的shell(本课程要完成的shell也是裸机的),clinux中断和windows的cmd是操作系统下的命令行shell。windows图形界面、ubuntu图形界面、android的图形界面这些都是图形界面的shell。突然想到另一个类型的shell,网页类型的shell,典型代表就是路由器。


16.2.shell实战1-从零写最简单shell
16.2.1、使用printf和scanf做输入回显
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <string.h>
 
 
#define MAX_LINE_LENGTH        256         // 命令行长度,命令不能超过这个长度
 
 
int  main( void )
{
     char  str[MAX_LINE_LENGTH];         // 用来存放用户输入的命令内容
     
     while  (1)
     {
         // 打印命令行提示符,注意不能加换行
         printf ( "aston#" );
         // 清除str数组以存放新的字符串
         memset (str, 0,  sizeof (str));
         // shell第一步:获取用户输入的命令
         scanf ( "%s" , str);
         // shell第二步:解析用户输入命令
         
         // shell第三步:处理用户输入命令
         printf ( "%s\n" , str);
     }
 
     return  0;
}

16.2.2、定义简单命令集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <stdio.h>
#include <string.h>
 
 
#define MAX_LINE_LENGTH        256         // 命令行长度,命令不能超过这个长度
 
 
 
// 宏定义一些标准命令
#define led                    "led"
#define lcd                    "lcd"
#define pwm                    "pwm"
#define CMD_NUM                3           // 当前系统定义的命令数 
 
char  g_cmdset[CMD_NUM][MAX_LINE_LENGTH];
 
 
// 初始化命令列表
static  void  init_cmd_set( void )
{
     memset (g_cmdset, 0,  sizeof (g_cmdset));        // 先全部清零
     strcpy (g_cmdset[0], led);
     strcpy (g_cmdset[1], lcd);
     strcpy (g_cmdset[2], pwm);
}
 
int  main( void )
{
     int  i = 0;
     char  str[MAX_LINE_LENGTH];         // 用来存放用户输入的命令内容
     
     init_cmd_set();
     
     while  (1)
     {
         // 打印命令行提示符,注意不能加换行
         printf ( "poi_carefree#" );
         // 清除str数组以存放新的字符串
         memset (str, 0,  sizeof (str));
         // shell第一步:获取用户输入的命令
         scanf ( "%s" , str);
         // shell第二步:解析用户输入命令
         for  (i=0; i<CMD_NUM; i++)
         {
             if  (! strcmp (str, g_cmdset[i]))
             {
                 // 相等,找到了这个命令,就去执行这个命令所对应的动作。
                 printf ( "您输入的命令是:%s,是合法的\n" , str);
                 break ;
             }  
         }
         if  (i >= CMD_NUM)
         {
             // 找遍了命令集都没找到这个命令
             printf ( "%s不是一个内部合法命令,请重新输入\n" , str);
         }
         
         // 第三步的处理已经移到前面分开处理了,所以这里不需要了
         // shell第三步:处理用户输入命令
     }
 
     return  0;
}
16.3.shell实战2-将简易shell移植到开发板中
16.3.1、工程选定、文件复制、Makefile书写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
CC      = arm-linux-gcc
LD         = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR      = arm-linux-ar
 
INCDIR  := $(shell pwd)
# C预处理器的flag,flag就是编译器可选的选项
CPPFLAGS    := -nostdlib -nostdinc -I$(INCDIR)/include
# C编译器的flag
CFLAGS      := -Wall -O2 -fno-builtin
 
#导出这些变量到全局,其实就是给子文件夹下面的Makefile使用
export CC LD OBJCOPY OBJDUMP AR CPPFLAGS CFLAGS
 
 
objs := start.o led.o  clock .o uart.o main.o stdio.o 
 
uart.bin: $(objs)
     $(LD) -Tlink.lds -o uart.elf $^
     $(OBJCOPY) -O binary uart.elf uart.bin
     $(OBJDUMP) -D uart.elf > uart_elf.dis
     gcc mkv210_image.c -o mkx210
     ./mkx210 uart.bin 210.bin
 
lib/libc.a:
     cd lib;    make;   cd ..
     
%.o : %.S
     $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
 
%.o : %.c
     $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c
 
clean:
     rm *.o *.elf *.bin *.dis mkx210 -f
16.3.2、Main函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void  puts ( const  char  *p);
char  * gets ( char  *p);
void  uart_init( void );
 
// C标准库中也有个memset函数,但是我们这里用的是自己写的,没用标准库
void  memset ( char  *p,  int  val,  int  length)
{
     int  i;
     
     for  (i=0; i<length; i++)
     {
         p[i] = val;
     }
}
 
 
 
int  main( void )
{
     char  buf[100] = {0};     // 用来暂存用户输入的命令
     
     uart_init();
     
     puts ( "x210 simple shell:\n" );
     while (1)
     {
         
         puts ( "aston#" );
         memset (buf, 0,  sizeof (buf));      // buf弄干净好存储这次用户输入
         gets (buf);                          // 读取用户输入放入buf中
         puts ( "您输入的是:" );
         puts (buf);
         puts ( "\n" );
     }
     
     return  0;
}

16.3.3、printf和scanf函数(本质是putc和getc函数)的移植
(1)puts和putchar函数比较简单,注意的地方就是windows和linux中的回车键定义的不同。
所以在putchar函数中如果用户要输出'\n'时,实际输出"\r\n"
(2)gets和getchar是从Windows中的SecureCRT终端输入字符串到裸机程序中。这里面至少有2个问题:用户输入回显问题、用户按回车键问题、用户按BackSpace退格键问题
(3)自己实现回显
(4)用户按回车键问题,在getchar中解决。方法是碰到'\r'时直接返回'\n'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
void  uart_putc( char  c);
char  uart_getc( void );
 
// 从stdio输出一个字符c
void  putchar ( char  c)
{
     // 碰到用户输出'\n'时,实际输出"\r\n"
     // windows中按下回车键等效于"\r\n",在linux中按下回车键等效于'\n'
     if  (c ==  '\n' )
         uart_putc( '\r' );
/* 
     if (c == '\b')
     {
         uart_putc('\b');
         uart_putc(' ');
     }
*/ 
     uart_putc(c);
}
 
// 从stdio输出一个字符串p
void  puts ( const  char  *p)
{
     while  (*p !=  '\0' )
     {
         putchar (*p);
         p++;
     }
}
 
// 从stdio输入一个字符
char  getchar ( void )
{
     char  c;
     c = uart_getc();
     if  (c ==  '\r' )
     {
         return  '\n' ;
     }
     
     return  c;
}
 
// 从stdio输入一个字符串
// 返回值指向传进来的数组的首地址的,目的是实现函数的级联调用
char  * gets ( char  *p)
{
     char  *p1 = p;
     char  ch;
     
     // 用户的一次输入是以'\n'为结束标志的
     while  ((ch =  getchar ()) !=  '\n' )
     {
         // 回显
         if  (ch !=  '\b' )
         {
             // 用户输入的不是退格键
             putchar (ch);                 // 回显
             *p++ = ch;                     // 存储ch,等效于 *p = ch; p++;
         }
         else
         {
             // 用户输入的是退格键
             // \b只会让secureCRT终端输出指针向后退一格,但是那个要删掉的字符还在
             // 删掉的方法就是下面3行
             if  (p > p1)
             {
                 putchar ( '\b' );
                 putchar ( ' ' );
                 putchar ( '\b' );               // 3行处理退格键回显
                 p--;                         // 退一格,指针指向了要删除的那个格子
                 *p =  '\0' ;                     // 填充'\0'以替换要删除的那个字符
             }  
         }
         
     }
     // 遇到'\n'行结束,添加'\0'作为字符串结尾。
     *p =  '\0' ;
     putchar ( '\n' );
     
     return  p1;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#define GPA0CON        0xE0200000
#define UCON0         0xE2900004
#define ULCON0        0xE2900000
#define UMCON0        0xE290000C
#define UFCON0        0xE2900008
#define UBRDIV0   0xE2900028
#define UDIVSLOT0  0xE290002C
#define UTRSTAT0   0xE2900010
#define UTXH0      0xE2900020 
#define URXH0      0xE2900024 
 
#define rGPA0CON   (*(volatile unsigned int *)GPA0CON)
#define rUCON0     (*(volatile unsigned int *)UCON0)
#define rULCON0        (*(volatile unsigned int *)ULCON0)
#define rUMCON0        (*(volatile unsigned int *)UMCON0)
#define rUFCON0        (*(volatile unsigned int *)UFCON0)
#define rUBRDIV0   (*(volatile unsigned int *)UBRDIV0)
#define rUDIVSLOT0 (*(volatile unsigned int *)UDIVSLOT0)
#define rUTRSTAT0      (*(volatile unsigned int *)UTRSTAT0)
#define rUTXH0     (*(volatile unsigned int *)UTXH0)
#define rURXH0     (*(volatile unsigned int *)URXH0)
 
// 串口初始化程序
void  uart_init( void )
{
     // 初始化Tx Rx对应的GPIO引脚
     rGPA0CON &= ~(0xff<<0);         // 把寄存器的bit0~7全部清零
     rGPA0CON |= 0x00000022;           // 0b0010, Rx Tx
     
     // 几个关键寄存器的设置
     rULCON0 = 0x3;
     rUCON0 = 0x5;
     rUMCON0 = 0;
     rUFCON0 = 0;
     
     // 波特率设置   DIV_VAL = (PCLK / (bps x 16))-1
     // PCLK_PSYS用66MHz算        余数0.8
     //rUBRDIV0 = 34; 
     //rUDIVSLOT0 = 0xdfdd;
     
     // PCLK_PSYS用66.7MHz算      余数0.18
     // DIV_VAL = (66700000/(115200*16)-1) = 35.18
     rUBRDIV0 = 35;
     // (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18
     // (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3
     rUDIVSLOT0 = 0x0888;      // 3个1,查官方推荐表得到这个数字
}
 
 
// 串口发送程序,发送一个字节
void  uart_putc( char  c)
{                    
     // 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去
     // 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须
     // 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)
     // 如果缓冲区非空则位为0,此时应该循环,直到位为1
     while  (!(rUTRSTAT0 & (1<<1)));
     rUTXH0 = c;
}
 
// 串口接收程序,轮询方式,接收一个字节
char  uart_getc( void )
{
     while  (!(rUTRSTAT0 & (1<<0)));
     return  (rURXH0 & 0xff);
}


猜你喜欢

转载自blog.csdn.net/poi_carefree/article/details/80686478