ARM裸机的知识点总结---------16、shell简易实战(cmd形式)

Author: 想文艺一点的程序员
自动化专业 工科男
再坚持一点,再自律一点
CSDN@想文艺一点的程序员
来自朱有鹏嵌入式的学习笔记

一、前期梳理

本次实验主要是以 cmd 命令行操作,来进行交互。
所以有涉及 字符串处理串口的接收与发送,这些基本的操作。

1、字符串的知识补缺。

首先在Java,C#,这些高级语言,字符串也被看成是一个类型,可以通过 string 来定义一个字符串变量 S1。

 string s1 = "python"int a = 0
  • 但是在 C 语言当中,我们要通过 数组,或者指针来进行管理字符串。

  • 在c语言当中,字符串的名字 就相当于是一个 数组名。 就相当于一个指针,相当于一个 地址

1、 相当于数组名: 这一用到数组名的解引用功能。
注:
在 printf 当中,打印字符串的时候, %s,这时候后面要传入一个地址。


#include <stdio.h>

int main(char argc , char **argv)
{
    
    
	
	char s1[] = "hello";
	char *p = "hello";
	

	// %s, 对应后面的指针(本质是一个地址), 同理 s1(数组名),“hello”, 也是一个指针(地址)
	printf("%s\n", p); 			  // hello
	printf("%s\n", "hello");	  // hello
	printf("%s\n", s1);			 // hello
	printf("\n");
	
	// 数组名的解引用功能。
	printf("%c\n", *(p+1));			// e
	printf("%c\n", s1[1]);			// e
	printf("%c\n", "hello"[1]);		// e
	printf("\n");
	
	// 可以看出 指针p里面存放的就是 "hello" 的地址,我们并没有去定义一个 字符串 这样的变量, 但是当我们使用它的时候,它会自动占用内存
	printf("%p\n", p);					// 0x8048630
	printf("%p\n", s1);					// 0xbf88ee66
	printf("%p\n", "hello"); 			// 0x8048630
	printf("\n");
	
	// 字符串等于 指针
	printf("%c\n", *(p));			 // h
	printf("%c\n", *("hello"));		 // h 
	printf("%c\n", *(p+1));  		// e
	printf("%c\n", *("hello"+1));   // e
	printf("\n");
	
	return 0;
	
}

执行结果:
在这里插入图片描述

二、shell 小项目梳理 (在 arm裸机 16部分 shell_cmd_led)

在这里插入图片描述

1、start.s

在这里插入图片描述

2、makefile

在这里插入图片描述

3、shell.h (避免重复宏定义)

#ifndef __SHELL_H__
#define __SHELL_H__




// 宏定义
#define MAX_LINE_LENGTH		256			// 命令行长度,命令不能超过这个长度

// 宏定义一些标准命令的第一部分
#define led					"led"
#define lcd					"lcd"
#define pwm					"pwm"
#define CMD_NUM				3			// 当前系统定义的命令数 

// 宏定义一个命令相关信息
#define MAX_CMD_PART		5			// 一个命令最多包含几部分			
#define MAX_LEN_PART		20			// 命令的分部分最大长度



// 全局变量声明
extern char g_cmdset[CMD_NUM][MAX_LINE_LENGTH];



// 硬件相关函数声明
void puts(const char *p);
char *gets(char *p);
void uart_init(void);

// 命令解析/执行相关
void init_cmd_set(void);
void cmd_parser(char *str);
void cmd_exec(void);

// 字符串函数相关
void memset(char *p, int val, int length);
void strcpy(char *dst, const char *src);
int strcmp(const char *cs, const char *ct);
void cmdsplit(char cmd[][MAX_LEN_PART], const char *str);

#endif

4、string.c (自己写的字符串处理函数)

#include "shell.h"

// C标准库中也有个memset函数,但是我们这里用的是自己写的,没用标准库
// char*p : 传入一个指针(地址)--------- str(字符串名称/字符串数组)
// val : 要初始化的值		   --------- 0  初始化为0
// length: 要清除的长度	   --------- sizeof(str)  将整个字符串数组清零
void memset(char *p, int val, int length)
{
    
    
	int i;
	
	for (i=0; i<length; i++)
	{
    
    
		p[i] = val;
	}
}

// 字符串比较, 相同

int strcmp(const char *cs, const char *ct)
{
    
    
	unsigned char c1, c2;

	while (1) {
    
    
		c1 = *cs++;
		c2 = *ct++;
		if (c1 != c2)
			return c1 < c2 ? -1 : 1;
		if (!c1)
			break;
	}
	return 0;
}


// 复制字符串,到指定的地址

void strcpy(char *dst, const char *src)
{
    
    
	while (*src != '\0')
	{
    
    
		*dst++ = *src++;
	}
}

// 将用户输入的字符串命令str 按照空格 分隔成多个字符串,依次放入cmd二维数组中
// 举例: led on (led on 之间有一个空格),我们要将 led 和 on 分开放到2个数组里面
// 也就说:char cmd[0][]: 存放 led   ;char cmd[0][]: 存放 on

void cmdsplit(char cmd[][MAX_LEN_PART], const char *str)
{
    
    
	int m = 0, n = 0;	// m表示二位数组第一维,n表示第二维
	while (*str != '\0')
	{
    
    
		if (*str != ' ')
		{
    
    
			cmd[m][n] = *str;
			n++;
		}
		else
		{
    
    
			cmd[m][n] = '\0';
			n = 0;
			m++;
		}
		str++;
	}
	cmd[m][n] = '\0';
}

5、uart

#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);
}

5、stdio.c(其实就是对 uart 进行改造为标准输入输出)

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;
}

6、cmd.c

// 命令解析和命令执行相关的函数
#include "shell.h"


char g_cmdset[CMD_NUM][MAX_LINE_LENGTH];		// 命令集,存主命令
char cmd[MAX_CMD_PART][MAX_LEN_PART];			// 当前解析出来的命令
int cmd_index = -1;								// 存储解析到的命令是第几个主命令

// 理解这三个参数:
// 举例: 例如命令: led on 1, led on 2
// char g_cmdset[CMD_NUM][MAX_LINE_LENGTH]; 这个存放主命令: 例如:pwm lcd led 等等区别他们是什么硬件---------以后用来做对比
// char cmd[MAX_CMD_PART][MAX_LEN_PART]; 存放解析出来的命令 ,
// 举例: led on (led on 之间有一个空格),我们要将 led 和 on 分开放到2个数组里面
// 也就说:char cmd[0][]: 存放 led   ;char cmd[0][]: 存放 on



// 初始化命令列表
void init_cmd_set(void)
{
    
    
	memset((char *)g_cmdset, 0, sizeof(g_cmdset));		// 先全部清零
	strcpy(g_cmdset[0], led);      // 存放主命令
	strcpy(g_cmdset[1], lcd);
	strcpy(g_cmdset[2], pwm);
	
	memset((char *)cmd, 0, sizeof(cmd));	
}

// 解析命令
void cmd_parser(char *str)
{
    
    
	int i;
	
	// 第一步,先将用户输入的次命令字符串分割放入cmd中
	cmdsplit(cmd, str);
	
	// 第二步,将cmd中的次命令第一个字符串和cmdset对比
	cmd_index = -1;
	for (i=0; i<CMD_NUM; i++)
	{
    
    
		// cmd[0]就是次命令中的第一个字符串,也就是主命令
		if (!strcmp(cmd[0], g_cmdset[i]))
		{
    
    
			// 相等,找到了这个命令,就去执行这个命令所对应的动作。
			//puts("您输入的命令是:");
			//puts(str);
			//puts("\n");
			cmd_index = i;
			
			break;
		}
	}			
/*		
		if (i >= CMD_NUM)
		{
			// 找遍了命令集都没找到这个命令
			cmd_index = -1;	
		}
*/	
}

// 命令没找到处理方法
void do_cmd_notfound(void)
{
    
    
	puts(cmd[0]);
	puts("不是一个内部合法命令,请重新输入\n");
	puts("\n");
}

// led命名的处理方法
void do_cmd_led(void)
{
    
    
	puts("led cmd");
}



// 执行命令
void cmd_exec(void)
{
    
    
	switch (cmd_index)
	{
    
    
		case 0:
			do_cmd_led();			break;
		case 1:
		case 2:
		default:
			do_cmd_notfound();		break;
	}
}

7、main.c

#include "shell.h"


static void shell_init(void)
{
    
    
	// shell init
	init_cmd_set();
	uart_init();
	puts("x210 simple shell:\n");		// shell logo
}

int main(void)
{
    
    
	char buf[MAX_LINE_LENGTH] = {
    
    0};		// 用来暂存用户输入的命令
	
	shell_init();

	while(1)
	{
    
    
		// 第1步:命令获取
		puts("aston#");
		memset(buf, 0, sizeof(buf));		// buf弄干净好存储这次用户输入
		gets(buf);							// 读取用户输入放入buf中
		//puts("您输入的是:");
		//puts(buf);
		//puts("\n");
		// 第2步:命令解析
		cmd_parser(buf);
		// 第3步:命令执行
		cmd_exec();
	}
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/vincent3678/article/details/108193246