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