转自 https://edu.csdn.net/lecturer/505 朱老师物联网大讲堂
《16.shell原理和问答机制引入》

第一部分、章节目录
1.16.1.shell是什么鬼
1.16.2.shell实战1-从零写最简单shell
1.16.3.shell实战2-将简易shell移植到开发板中
1.16.4.shell实战3-定义标准命令集及解析
1.16.5.shell实战4-添加第一个命令
1.16.6.shell实战5-添加其他命令1
1.16.7.shell实战6-添加其他命令2
1.16.8.shell实战7-实现开机倒计时自动执行命令1
1.16.9.shell实战7-实现开机倒计时自动执行命令2
1.16.10.shell实战8-初步实现环境变量
1.16.11.shell实战8-初步实现环境变量2

第二部分、章节介绍
1.16.1.shell是什么鬼
本节为概念篇,主要讲解了什么是shell,shell的工作方式,常见的shell有哪些,shell的实现机制等,目的是让大家对shell有直观的认识,后面才好去写shell。
1.16.2.shell实战1-从零写最简单shell
本节是shell实战第一节,我们将从零开始编程自己写一个最简单的shell,并且调试运行。通过这个简单到不能再简单的shell让大家进一步理解shell的原理和实现方法。
1.16.3.shell实战2-将简易shell移植到开发板中
本节是shell实战第二节,将上节编写的shell程序移植到开发板中。我们在之前的裸机程序中随便选一个工程为基础进行移植,这个过程请大家体会嵌入式软件的移植。
1.16.4.shell实战3-定义标准命令集及解析
本节是shell实战第三节,本节我们将建立标准的命令集,并且写出命令集解析函数的框架结构,这个框架必须是一个具有可扩展性的结构以方便后续添加新的命令。

1.16.5.shell实战4-添加第一个命令
本节是shell实战第四节,我们将在上节构建的标准命令集基础上实现第一个命令-控制板载LED点亮和熄灭。通过这个命令的添加打通标准命令集下的shell运行。
1.16.6.shell实战5-添加其他命令1
本节是shell实战第五节,我们将继续向标准命令集的shell添加更多的命令,本节实现的是pwm蜂鸣器的控制和ADC采集的控制。
1.16.7.shell实战6-添加其他命令2
本节是shell实战第六节,我们将继续向标准命令集的shell添加更多的命令,本节实现的是LCD的控制和各种图像的显示。
1.16.8.shell实战7-实现开机倒计时自动执行命令1
本节是shell实战第七节,本节我们要添加的功能是开机倒计时自启动,这个功能是模仿PC机BIOS和uboot的自动启动功能的。
1.16.9.shell实战7-实现开机倒计时自动执行命令2
本节是shell实战第七节,本节我们要添加的功能是开机倒计时自启动,这个功能是模仿PC机BIOS和uboot的自动启动功能的。
1.16.10.shell实战8-初步实现环境变量1
本节是shell实战第八节,本节我们将为shell程序添加环境变量。这里的环境变量将被宏定义在程序代码中并且运行时存在于内存中工作,目的是使大家初步接受并且理解环境变量的概念和意义。
1.16.11.shell实战8-初步实现环境变量2
本节继续来实现环境变量。首先解决了代码大小超限导致的被截取的问题,然后实现了env_get和env_set方法。

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

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

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

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

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

1.16.2.shell实战1-从零写最简单shell
1.16.2.1、使用printf和scanf做输入回显
1.16.2.2、定义简单命令集

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

1.16.3.shell实战2-将简易shell移植到开发板中
1.16.3.1、工程选定、文件复制、Makefile书写

1.16.3.2、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.16.3.3、编译下载和调试验证

#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("aston#");// 清除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;
}

1.16.4.shell实战3-定义标准命令集及解析
1.16.4.1、添加cmd_parser和cmd_exec
1.16.4.2、实现简单的cmd_parser
1.16.4.3、实现对应的cmd_exec

/********man.c********/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;
}
/***************uatr.c****************/
#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.18rUBRDIV0 = 35;// (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3rUDIVSLOT0 = 0x0888;      // 3个1,查官方推荐表得到这个数字
}// 串口发送程序,发送一个字节
void uart_putc(char c)
{                   // 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去// 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须// 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)// 如果缓冲区非空则位为0,此时应该循环,直到位为1while (!(rUTRSTAT0 & (1<<1)));rUTXH0 = c;
}// 串口接收程序,轮询方式,接收一个字节
char uart_getc(void)
{while (!(rUTRSTAT0 & (1<<0)));return (rURXH0 & 0xff);
}
stdio.c
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.16.5.shell实战4-添加第一个命令
1.16.5.1、命令任务分析
(1)第一个命令:led,实现的功能是控制板载LED的亮和灭
(2)命令定义:led on 表示点亮led,led off 熄灭led
(3)扩展:led 1 on 表示点亮第一个led。(课堂不实现,留给大家做作业)
1.16.5.2、命令解析
(1)命令解析其实就是把一个类似 led on这种命令解析成led和on两个字符串,放在一个字符串数组中。

man.c
#include "shell.h"static void shell_init(void)
{// shell initinit_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;
}
cmd.c
// 命令解析和命令执行相关的函数
#include "shell.h"char g_cmdset[CMD_NUM][MAX_LINE_LENGTH];// 初始化命令列表
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);
}// 解析命令
void cmd_parser(char *str)
{int i;for (i=0; i<CMD_NUM; i++){if (!strcmp(str, g_cmdset[i])){// 相等,找到了这个命令,就去执行这个命令所对应的动作。puts("您输入的命令是:");puts(str);puts("\n");break;}   }if (i >= CMD_NUM){// 找遍了命令集都没找到这个命令puts(str);puts("不是一个内部合法命令,请重新输入\n");puts("\n");}
}// 执行命令
void cmd_exec(void)
{}
string.c
//#include "shell.h"// C标准库中也有个memset函数,但是我们这里用的是自己写的,没用标准库
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++;}
}
stdio.c
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;
}
uart.c
#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.18rUBRDIV0 = 35;// (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3rUDIVSLOT0 = 0x0888;      // 3个1,查官方推荐表得到这个数字
}// 串口发送程序,发送一个字节
void uart_putc(char c)
{                   // 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去// 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须// 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)// 如果缓冲区非空则位为0,此时应该循环,直到位为1while (!(rUTRSTAT0 & (1<<1)));rUTXH0 = c;
}// 串口接收程序,轮询方式,接收一个字节
char uart_getc(void)
{while (!(rUTRSTAT0 & (1<<0)));return (rURXH0 & 0xff);
}

1.16.6.shell实战5-添加其他命令1
1.16.6.1、第一个硬件LED的控制
1.16.6.2、扩展其他硬件命令

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;                                // 存储解析到的命令是第几个主命令// 初始化命令列表
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;}
}

1.16.7.shell实战6-添加其他命令2

main.c
#include "shell.h"static void hardware_init(void)
{led_init();                // led初始化timer2_pwm_init();     // 蜂鸣器初始化
}static void shell_init(void)
{// shell initinit_cmd_set();uart_init();hardware_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;
}
// 命令解析和命令执行相关的函数
#include "shell.h"char g_cmdset[CMD_NUM][MAX_LINE_LENGTH];        // 命令集,存主命令
char cmd[MAX_CMD_PART][MAX_LEN_PART];           // 当前解析出来的命令
int cmd_index = -1;                                // 存储解析到的命令是第几个主命令/****************************具体硬件操作命令处理函数*************************/// 命令没找到处理方法
void do_cmd_notfound(void)
{puts(cmd[0]);puts("不是一个内部合法命令,请重新输入\n");puts("\n");
}// led命令的处理方法
void do_cmd_led(void)
{int flag = -1;//puts("led cmd");// 真正的led命令的操作实现// 目前支持的命令有led on | led off // cmd[0]里面是led,cmd[1]里面是on|offif (!strcmp(cmd[1], "on")){// led onled_on();flag = 1;}if (!strcmp(cmd[1], "off")){// led offled_off();flag = 1;}// ..... 还可以继续扩展if (-1 == flag){// 如果一个都没匹配,则打印使用方法puts("command error, try: led on | led off");puts("\n");}
}// 蜂鸣器命令处理方法
void do_cmd_buzzer(void)
{int flag = -1;//puts("led cmd");// 真正的buzzer命令的操作实现// 目前支持的命令有buzzer on | buzzer off // cmd[0]里面是buzzer,cmd[1]里面是on|offif (!strcmp(cmd[1], "on")){// buzzer onbuzzer_on();flag = 1;}if (!strcmp(cmd[1], "off")){// buzzer offbuzzer_off();flag = 1;}// ..... 还可以继续扩展if (-1 == flag){// 如果一个都没匹配,则打印使用方法puts("command error, try: buzzer on | buzzer off");puts("\n");}
}// lcd命令处理方法// ADC命令处理方法/*********************************shell 命令解析执行框架***********************/// 初始化命令列表
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 cmd_exec(void)
{switch (cmd_index){case 0:     // leddo_cmd_led();         break;case 1:       // lcdcase 2:       // buzzerdo_cmd_buzzer();       break;default:do_cmd_notfound();        break;}
}
clock.c
// 时钟控制器基地址
#define ELFIN_CLOCK_POWER_BASE      0xE0100000  // 时钟相关的寄存器相对时钟控制器基地址的偏移值
#define APLL_LOCK_OFFSET        0x00
#define MPLL_LOCK_OFFSET        0x08#define APLL_CON0_OFFSET        0x100
#define APLL_CON1_OFFSET        0x104
#define MPLL_CON_OFFSET         0x108#define CLK_SRC0_OFFSET            0x200
#define CLK_SRC1_OFFSET         0x204
#define CLK_SRC2_OFFSET         0x208
#define CLK_SRC3_OFFSET         0x20c
#define CLK_SRC4_OFFSET         0x210
#define CLK_SRC5_OFFSET         0x214
#define CLK_SRC6_OFFSET         0x218
#define CLK_SRC_MASK0_OFFSET    0x280
#define CLK_SRC_MASK1_OFFSET    0x284#define CLK_DIV0_OFFSET            0x300
#define CLK_DIV1_OFFSET         0x304
#define CLK_DIV2_OFFSET         0x308
#define CLK_DIV3_OFFSET         0x30c
#define CLK_DIV4_OFFSET         0x310
#define CLK_DIV5_OFFSET         0x314
#define CLK_DIV6_OFFSET         0x318
#define CLK_DIV7_OFFSET         0x31c#define CLK_DIV0_MASK          0x7fffffff// 这些M、P、S的配置值都是查数据手册中典型时钟配置值的推荐配置得来的。
// 这些配置值是三星推荐的,因此工作最稳定。如果是自己随便瞎拼凑出来的那就要
// 经过严格测试,才能保证一定对。
#define APLL_MDIV               0x7d        // 125
#define APLL_PDIV               0x3
#define APLL_SDIV               0x1#define MPLL_MDIV                0x29b       // 667
#define MPLL_PDIV               0xc
#define MPLL_SDIV               0x1#define set_pll(mdiv, pdiv, sdiv)    (1<<31 | mdiv<<16 | pdiv<<8 | sdiv)
#define APLL_VAL            set_pll(APLL_MDIV,APLL_PDIV,APLL_SDIV)
#define MPLL_VAL            set_pll(MPLL_MDIV,MPLL_PDIV,MPLL_SDIV)#define REG_CLK_SRC0  (ELFIN_CLOCK_POWER_BASE + CLK_SRC0_OFFSET)
#define REG_APLL_LOCK   (ELFIN_CLOCK_POWER_BASE + APLL_LOCK_OFFSET)
#define REG_MPLL_LOCK   (ELFIN_CLOCK_POWER_BASE + MPLL_LOCK_OFFSET)
#define REG_CLK_DIV0    (ELFIN_CLOCK_POWER_BASE + CLK_DIV0_OFFSET)
#define REG_APLL_CON0   (ELFIN_CLOCK_POWER_BASE + APLL_CON0_OFFSET)
#define REG_MPLL_CON    (ELFIN_CLOCK_POWER_BASE + MPLL_CON_OFFSET)#define rREG_CLK_SRC0    (*(volatile unsigned int *)REG_CLK_SRC0)
#define rREG_APLL_LOCK  (*(volatile unsigned int *)REG_APLL_LOCK)
#define rREG_MPLL_LOCK  (*(volatile unsigned int *)REG_MPLL_LOCK)
#define rREG_CLK_DIV0   (*(volatile unsigned int *)REG_CLK_DIV0)
#define rREG_APLL_CON0  (*(volatile unsigned int *)REG_APLL_CON0)
#define rREG_MPLL_CON   (*(volatile unsigned int *)REG_MPLL_CON)void clock_init(void)
{// 1 设置各种时钟开关,暂时不使用PLLrREG_CLK_SRC0 = 0x0;// 2 设置锁定时间,使用默认值即可// 设置PLL后,时钟从Fin提升到目标频率时,需要一定的时间,即锁定时间rREG_APLL_LOCK = 0x0000ffff;rREG_MPLL_LOCK = 0x0000ffff;// 3 设置分频// 清bit[0~31]rREG_CLK_DIV0 = 0x14131440;// 4 设置PLL// FOUT = MDIV*FIN/(PDIV*2^(SDIV-1))=0x7d*24/(0x3*2^(1-1))=1000 MHzrREG_APLL_CON0 = APLL_VAL;// FOUT = MDIV*FIN/(PDIV*2^SDIV)=0x29b*24/(0xc*2^1)= 667 MHzrREG_MPLL_CON = MPLL_VAL;// 5 设置各种时钟开关,使用PLLrREG_CLK_SRC0 = 0x10001111;
}
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;                                // 存储解析到的命令是第几个主命令/****************************具体硬件操作命令处理函数*************************/// 命令没找到处理方法
void do_cmd_notfound(void)
{puts(cmd[0]);puts("不是一个内部合法命令,请重新输入\n");puts("\n");
}// led命令的处理方法
void do_cmd_led(void)
{int flag = -1;//puts("led cmd");// 真正的led命令的操作实现// 目前支持的命令有led on | led off // cmd[0]里面是led,cmd[1]里面是on|offif (!strcmp(cmd[1], "on")){// led onled_on();flag = 1;}if (!strcmp(cmd[1], "off")){// led offled_off();flag = 1;}// ..... 还可以继续扩展if (-1 == flag){// 如果一个都没匹配,则打印使用方法puts("command error, try: led on | led off");puts("\n");}
}// 蜂鸣器命令处理方法
void do_cmd_buzzer(void)
{int flag = -1;//puts("led cmd");// 真正的buzzer命令的操作实现// 目前支持的命令有buzzer on | buzzer off // cmd[0]里面是buzzer,cmd[1]里面是on|offif (!strcmp(cmd[1], "on")){// buzzer onbuzzer_on();flag = 1;}if (!strcmp(cmd[1], "off")){// buzzer offbuzzer_off();flag = 1;}// ..... 还可以继续扩展if (-1 == flag){// 如果一个都没匹配,则打印使用方法puts("command error, try: buzzer on | buzzer off");puts("\n");}
}// lcd命令处理方法// ADC命令处理方法/*********************************shell 命令解析执行框架***********************/// 初始化命令列表
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 cmd_exec(void)
{switch (cmd_index){case 0:     // leddo_cmd_led();         break;case 1:       // lcdcase 2:       // buzzerdo_cmd_buzzer();       break;default:do_cmd_notfound();        break;}
}
led.c
#define GPJ0CON     0xE0200240
#define GPJ0DAT     0xE0200244#define rGPJ0CON  *((volatile unsigned int *)GPJ0CON)
#define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)void delay(void);// 该函数要实现led闪烁效果
void led_blink(void)
{// led初始化,也就是把GPJ0CON中设置为输出模式//volatile unsigned int *p = (unsigned int *)GPJ0CON;//volatile unsigned int *p1 = (unsigned int *)GPJ0DAT;rGPJ0CON = 0x11111111;while (1){// led亮rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));// 延时delay();// led灭rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));// 延时delay();}
}void delay(void)
{volatile unsigned int i = 900000;     // volatile 让编译器不要优化,这样才能真正的减while (i--);                            // 才能消耗时间,实现delay
}// led初始化
void led_init()
{rGPJ0CON = 0x11111111;rGPJ0DAT = ((0<<3) | (1<<4) | (0<<5));
}   void led_on(void)
{rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
}void led_off(void)
{rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
}
pwm.c#define     GPD0CON     (0xE02000A0)
#define     TCFG0       (0xE2500000)
#define     TCFG1       (0xE2500004)
#define     CON         (0xE2500008)
#define     TCNTB2      (0xE2500024)
#define     TCMPB2      (0xE2500028)#define     rGPD0CON    (*(volatile unsigned int *)GPD0CON)
#define     rTCFG0      (*(volatile unsigned int *)TCFG0)
#define     rTCFG1      (*(volatile unsigned int *)TCFG1)
#define     rCON        (*(volatile unsigned int *)CON)
#define     rTCNTB2     (*(volatile unsigned int *)TCNTB2)
#define     rTCMPB2     (*(volatile unsigned int *)TCMPB2)// 初始化PWM timer2,使其输出PWM波形:频率是2KHz、duty为50%
void timer2_pwm_init(void)
{// 设置GPD0_2引脚,将其配置为XpwmTOUT_2rGPD0CON &= ~(0xf<<8);rGPD0CON |= (2<<8);// 设置PWM定时器的一干寄存器,使其工作rTCFG0 &= ~(0xff<<8);rTCFG0 |= (65<<8);          // prescaler1 = 65, 预分频后频率为1MHzrTCFG1 &= ~(0x0f<<8);rTCFG1 |= (1<<8);            // MUX2设置为1/2,分频后时钟周期为500KHz// 时钟设置好,我们的时钟频率是500KHz,对应的时钟周期是2us。也就是说每隔2us// 计一次数。如果要定的时间是x,则TCNTB中应该写入x/2usrCON |= (1<<15);     // 使能auto-reload,反复定时才能发出PWM波形//rTCNTB2 = 250;          // 0.5ms/2us = 500us/2us = 250//rTCMPB2 = 125;           // duty = 50%rTCNTB2 = 50;            rTCMPB2 = 25;  // 第一次需要手工将TCNTB中的值刷新到TCNT中去,以后就可以auto-reload了rCON |= (1<<13);        // 打开自动刷新功能rCON &= ~(1<<13);     // 关闭自动刷新功能rCON |= (1<<12);      // 开timer2定时器。要先把其他都设置好才能开定时器
}void buzzer_on(void)
{rGPD0CON &= ~(0xf<<8);rGPD0CON |= (2<<8);
}// 关蜂鸣器的思路:TCON中设置关,GPIO设置成其他模式
void buzzer_off(void)
{rGPD0CON &= ~(0xf<<8);rGPD0CON |= (0<<8);
}// 现象:引脚直接配置成输出模式,然后输出高电平,buzzer叫了。输出低电平就关了
// 优点就是SOC不用有pwm功能,只要能输出高低电平就能控制蜂鸣器
// 缺点是蜂鸣器的鸣叫的频率无法改动
// 如果可以,那么开关蜂鸣器就变成了GPIO输出高低电平了。
start.c
/** 文件名: start.S* 作者: 朱老师* 描述: 演示串口通信*/#define WTCON       0xE2700000#define SVC_STACK 0xd0037d80.global _start                    // 把_start链接属性改为外部,这样其他文件就可以看见_start了
_start:// 第0步:开发板置锁ldr r0, =0xE010E81Cldr r1, [r0]ldr r2, =0x301orr r1, r1, r2str r1, [r0]// 第1步:关看门狗(向WTCON的bit5写入0即可)ldr r0, =WTCONldr r1, =0x0str r1, [r0]// 第2步:初始化时钟bl clock_init// 第3步:设置SVC栈ldr sp, =SVC_STACK// 第4步:开/关icachemrc p15,0,r0,c1,c0,0;            // 读出cp15的c1到r0中//bic r0, r0, #(1<<12)            // bit12 置0  关icacheorr r0, r0, #(1<<12)          // bit12 置1  开icachemcr p15,0,r0,c1,c0,0;bl main// 从这里之后就可以开始调用C程序了//bl led_blink                   // led_blink是C语言实现的一个函数// 汇编最后的这个死循环不能丢b .
stdio.c
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;
}
string.c
#include "shell.h"// C标准库中也有个memset函数,但是我们这里用的是自己写的,没用标准库
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二维数组中
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';
}
uart.c
#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.18rUBRDIV0 = 35;// (rUDIVSLOT中的1的个数)/16=上一步计算的余数=0.18// (rUDIVSLOT中的1的个数 = 16*0.18= 2.88 = 3rUDIVSLOT0 = 0x0888;      // 3个1,查官方推荐表得到这个数字
}// 串口发送程序,发送一个字节
void uart_putc(char c)
{                   // 串口发送一个字符,其实就是把一个字节丢到发送缓冲区中去// 因为串口控制器发送1个字节的速度远远低于CPU的速度,所以CPU发送1个字节前必须// 确认串口控制器当前缓冲区是空的(意思就是串口已经发完了上一个字节)// 如果缓冲区非空则位为0,此时应该循环,直到位为1while (!(rUTRSTAT0 & (1<<1)));rUTXH0 = c;
}// 串口接收程序,轮询方式,接收一个字节
char uart_getc(void)
{while (!(rUTRSTAT0 & (1<<0)));return (rURXH0 & 0xff);
}
link.lds
SECTIONS
{. = 0xd0020010;.text : {start.o* (.text)}.data : {* (.data)}bss_start = .; .bss : {* (.bss)}bss_end  = .;
}
makefile
CC      = arm-linux-gcc
LD      = arm-linux-ld
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
AR      = arm-linux-arINCDIR   := $(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 CFLAGSobjs := start.o led.o clock.o uart.o main.o stdio.o cmd.o string.o pwm.ouart.bin: $(objs)$(LD) -Tlink.lds -o uart.elf $^$(OBJCOPY) -O binary uart.elf uart.bin$(OBJDUMP) -D uart.elf > uart_elf.disgcc mkv210_image.c -o mkx210./mkx210 uart.bin 210.binlib/libc.a:cd lib;   make;   cd ..%.o : %.S$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -c%.o : %.c$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $< -cclean:rm *.o *.elf *.bin *.dis mkx210 -f

1.16.8.shell实战7-实现开机倒计时自动执行命令1
(1)计时功能。要用计时的部件(定时器)来完成。
(2)主程序
注意:C语言中声明全局变量时不能加初始化,如果加了编译器就会把这个声明当作定义

1.16.9.shell实战7-实现开机倒计时自动执行命令2

1.16.10.shell实战8-初步实现环境变量1
1.16.10.1、环境变量就好象程序的全局变量一样,整个程序中唯一。可以影响程序的执行,环境变量可以支持一些命令来查询环境变量、设置环境变量、保存环境变量(必须借助flash才能完成。本节只在内存中实现环境变量所以无法保存)。

#include "string.h"
#include "shell.h"env_t envset[MAX_ENV_NUM];          // 系统最多支持10个环境变量void env_init(void)
{memset((char *)envset, 0, sizeof(envset));// 第一个环境变量strcpy(envset[0].env_name, "bootdelay");strcpy(envset[0].env_val, "5");envset[0].is_used = 1;// 第二个环境变量strcpy(envset[1].env_name, "bootcmd");strcpy(envset[1].env_val, "ttttt");envset[1].is_used = 1;// 更多的环境变量
}// 成功找到env则返回0,失败则返回1.
int env_get(const char *pEnv, char *val)
{int i;for (i=0; i<sizeof(envset)/sizeof(envset[0]); i++){if (!envset[i].is_used){continue;}if (!strcmp(envset[i].env_name, pEnv)){// 找到了环境变量strcpy(val, envset[i].env_val);return 0;}}return 1;
}void env_set(const char *pEnv, const char *val)
{int i;// 先找目前有没有这个环境变量,如果有就直接改值for (i=0; i<sizeof(envset)/sizeof(envset[0]); i++){if (!envset[i].is_used){continue;}if (!strcmp(envset[i].env_name, pEnv)){// 找到了环境变量strcpy(envset[i].env_val, val);return;}}// 没找到这个环境变量,则新建即可for (i=0; i<sizeof(envset)/sizeof(envset[0]); i++){if (envset[i].is_used){continue;}// 找到了一个空位,然后在此处插入即可strcpy(envset[i].env_name, pEnv);strcpy(envset[i].env_val, val);envset[i].is_used = 1;return;}// 找遍了环境变量的数组,还是没有空位,说明已经存满了printf("env array is full.\n");
/*if (i >= sizeof(envset)/sizeof(envset[0]){// 找遍了环境变量的数组,还是没有空位,说明已经存满了printf("env array is full.\n");}
*/
}

1.16.11.shell实战8-初步实现环境变量2

《16.shell原理和问答机制引入》相关推荐

  1. ComeFuture英伽学院——2020年 全国大学生英语竞赛【C类初赛真题解析】(持续更新)

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  2. ComeFuture英伽学院——2019年 全国大学生英语竞赛【C类初赛真题解析】大小作文——详细解析

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  3. 信息学奥赛真题解析(玩具谜题)

    玩具谜题(2016年信息学奥赛提高组真题) 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业.有一天, 这些玩具小人把小南的眼镜藏了起来.小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的 ...

  4. 信息学奥赛之初赛 第1轮 讲解(01-08课)

    信息学奥赛之初赛讲解 01 计算机概述 系统基本结构 信息学奥赛之初赛讲解 01 计算机概述 系统基本结构_哔哩哔哩_bilibili 信息学奥赛之初赛讲解 02 软件系统 计算机语言 进制转换 信息 ...

  5. 信息学奥赛一本通习题答案(五)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  6. 信息学奥赛一本通习题答案(三)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  7. 信息学奥赛一本通 提高篇 第六部分 数学基础 相关的真题

    第1章   快速幂 1875:[13NOIP提高组]转圈游戏 信息学奥赛一本通(C++版)在线评测系统 第2 章  素数 第 3 章  约数 第 4 章  同余问题 第 5 章  矩阵乘法 第 6 章 ...

  8. 信息学奥赛一本通题目代码(非题库)

    为了完善自己学c++,很多人都去读相关文献,就比如<信息学奥赛一本通>,可又对题目无从下手,从今天开始,我将把书上的题目一 一的解析下来,可以做参考,如果有错,可以告诉我,将在下次解析里重 ...

  9. 信息学奥赛一本通(C++版) 刷题 记录

    总目录详见:https://blog.csdn.net/mrcrack/article/details/86501716 信息学奥赛一本通(C++版) 刷题 记录 http://ybt.ssoier. ...

  10. 最近公共祖先三种算法详解 + 模板题 建议新手收藏 例题: 信息学奥赛一本通 祖孙询问 距离

    首先什么是最近公共祖先?? 如图:红色节点的祖先为红色的1, 2, 3. 绿色节点的祖先为绿色的1, 2, 3, 4. 他们的最近公共祖先即他们最先相交的地方,如在上图中黄色的点就是他们的最近公共祖先 ...

最新文章

  1. python的redis数据库连接与使用
  2. python 源码安装教程_python安装步骤
  3. You C.A.N.大赛 解锁7大行业智能硬件创新密码
  4. Spring Boot Starters介绍
  5. Nginx upstream 配置
  6. ASP.NET Core 认证与授权[5]:初识授权
  7. python 文件写入多个参数_pandas 把数据写入txt文件每行固定写入一定数量的值方法...
  8. numpy 数组访问方式_索引、切片、迭代
  9. java笔试题型_JAVA笔试必备题
  10. EXCEL破冰 - 如何为透视表组织数据
  11. 圣诞节即将到来让你的Mac屏幕下雪只需一招轻松解决
  12. Brute Force(暴力算法)
  13. java创建内部面板类_Java图形与文本(18)
  14. python学习笔记--Django入门二 Django 的模板系统
  15. 中国行政区划 地级 js
  16. 浅议一种低成本的48V蓄电池组电压电流采集模块
  17. 4选1数据选择器程序及testbench文件,给出仿真波形,分析
  18. mysql analyze_技术分享 | MySQL EXPLAIN ANALYZE
  19. 机器学习速成课程_关于获得一流客户推荐的速成课程
  20. wap端使用a标签跳转不刷新问题

热门文章

  1. 物联网工程规划与设计
  2. Firewalld 日志开启.
  3. 华为运营商级路由器配置示例 | EVdPdNd L3VdPdNdv6 over SRv6 BE ECMP
  4. Linux系统下收集ADU/IML/survey报告方法整理(需要系统中有psp包)
  5. spring aop参数传递
  6. 【计算机网络】FQDN 是什么
  7. Real-Time Rendering——5.5.1 Blending Order 混合顺序
  8. Vue中播放音频和语音合成
  9. 产品小白如何在Axure中从无到有创建原型
  10. 浏览器兼容模式中文乱码