8.1 helloworld例子程序
在Keil开发环境下创建helloworld工程。
8.1.1 Keil开发环境设置
根据上一章中的内存分配方案设置Keil开发环境。“Memory Model”和“Code Rom Size”都选择Large模式。把“BL51 Locate”选项中的“Code Range”设置为0x8000-0xFFFF,“Xdata Range”设置为0x0000-0x3FFF。“C51”选项中的“Interrupt vectors at address”设置为0x8000。这样设置后,生成的代码起始地址是0x8000,中断入口也从0x8000开始。设置输出的程序是Hex格式。
一般地,我们都认为C程序是从main函数开始执行的,实际上,在main函数之前,Keil开发环境会自动添加一段代码,这段代码中将主要完成栈指针SP的设置和全局变量的初始化工作。不同程序的变量占用片内RAM的大小是不确定的,所以栈指针要根据程序实际情况设置。程序存储在程序存储空间,而全局变量存储在数据存储空间,那怎么实现main函数执行时,全局变量已经有值了呢?只能在main函数之前写段汇编代码实现全局变量的初始化。我们可以把系统里自带的STARTUP.A51(安装目录Keil\C51\LIB下)和INIT.A51(安装目录Keil\C51\LIB下)汇编文件添加到自己的项目中,通过修改这两个文件可以改变main函数执行之前要做的事情。
设计51DOS时,执行LCALL指令后,返回地址自动压入栈,SP栈指针则指向栈顶。如前面所说,main函数之前会重设SP指针的指向和初始化片内部分RAM,如果不保护返回地址,那么return指令就无法返回操作系统了。所以,我们先把返回地址存储到DPTR寄存器,重设SP指针后,再把DPTR内容压入栈,然后再继续后面的全局变量初始化和main函数,这样处理后,main函数最后的return指令就能返回操作系统了。对STARTUP.A51的修改见下面代码中的斜体部分,INIT.A51文件则不用修改。
IDATALEN EQU 80H;片内RAM初始化为0的长度
XDATASTART EQU 0
XDATALEN EQU 0;外扩RAM初始化为0的长度,可以不初始化
PDATASTART EQU 0H
PDATALEN EQU 0H
IBPSTACK EQU 0
IBPSTACKTOP EQU 0xFF +1;没选择这种方式
XBPSTACK EQU 1
XBPSTACKTOP EQU 0x3FFF +1;选择的XDATA方式,所以要设置
PBPSTACK EQU 0
PBPSTACKTOP EQU 0xFF +1;没选择这种方式
PPAGEENABLE EQU 0
PPAGE EQU 0
PPAGE_SFR DATA 0A0H
ACC DATA 0E0H
B DATA 0F0H
SP DATA 81H
DPL DATA 82H
DPH DATA 83H
NAME ?C_STARTUP
?C_C51STARTUP SEGMENT CODE
?STACK SEGMENT IDATA
RSEG ?STACK
DS 1
EXTRN CODE (?C_START)
PUBLIC ?C_STARTUP
CSEG AT 0x8000;代码的起始地址
?C_STARTUP: LJMP STARTUP1;从这里开始执行
RSEG ?C_C51STARTUP
STARTUP1:
POP DPH;先把返回地址保存到DPTR寄存器
POP DPL
IF IDATALEN <> 0;片内RAM清零
MOV R0,#IDATALEN - 1
CLR A
IDATALOOP: MOV @R0,A
DJNZ R0,IDATALOOP
ENDIF
IF XDATALEN <> 0;XDATA无需清零
MOV DPTR,#XDATASTART
MOV R7,#LOW (XDATALEN)
IF (LOW (XDATALEN)) <> 0
MOV R6,#(HIGH (XDATALEN)) +1
ELSE
MOV R6,#HIGH (XDATALEN)
ENDIF
CLR A
XDATALOOP: MOVX @DPTR,A
INC DPTR
DJNZ R7,XDATALOOP
DJNZ R6,XDATALOOP
ENDIF
IF PPAGEENABLE <> 0;没用到
MOV PPAGE_SFR,#PPAGE
ENDIF
IF PDATALEN <> 0;没用到
MOV R0,#LOW (PDATASTART)
MOV R7,#LOW (PDATALEN)
CLR A
PDATALOOP: MOVX @R0,A
INC R0
DJNZ R7,PDATALOOP
ENDIF
IF IBPSTACK <> 0;没用到
EXTRN DATA (?C_IBP)
MOV ?C_IBP,#LOW IBPSTACKTOP
ENDIF
IF XBPSTACK <> 0;选择这种方式,C语言局部变量栈设置
EXTRN DATA (?C_XBP)
MOV ?C_XBP,#HIGH XBPSTACKTOP
MOV ?C_XBP+1,#LOW XBPSTACKTOP
ENDIF
IF PBPSTACK <> 0;没用到
EXTRN DATA (?C_PBP)
MOV ?C_PBP,#LOW PBPSTACKTOP
ENDIF
;重设栈指针SP的指向,跟C程序中片内RAM分配有关
MOV SP,#?STACK-1
;设置完SP后,把返回地址从DPTR压入SP指向的栈
;这样,当main函数最后return时,会自动把这个返回
;地址写入PC寄存器,也就可以返回到操作系统了
PUSH DPL
PUSH DPH
;跳转到INIT.A51中去初始化全局变量,然后转到main
LJMP ?C_START
END
8.1.2 HelloWorld源代码
环境设置好了,下一步就是写自己的C程序,写C程序的方法和第5章所讲的内容是一致的,不用考虑操作系统。添加main.c文件, helloworld的源代码如下:
#include <reg51.h>
#include <stdio.h>
// 向串口输出一个字符
void PutChar(char c)
{
SBUF = c;
while (TI == 0);
TI = 0;
}
void Init_USART()
{
SCON = 0x50;
TMOD = 0x20;
PCON = 0x00;
TH1 = TL1 = 0xFD;
TI = RI = 0;
TR1 = 1;
}
// 主程序
void main()
{
Init_USART();
printf("hello world!\r\n");
}
编译源代码,可以生成helloworld.hex程序。
8.1.3 SD卡镜像文件读写
前面设计的8051计算机和51DOS系统能执行SD上的程序,所以需要把helloworld.hex写入SD卡镜像文件。我们提供两个windows下的小工具,分别是hex2bin.exe和sd_img.exe。因为8051计算机只能执行二进制程序,hex2bin.exe工具可以把hex格式文件转换成二进制文件。sd_img.exe工具可以用来创建镜像文件、从镜像文件里读出文件、把文件写入镜像等功能,这个小工具也是使用上一章设计的文件系统读写镜像的,源代码文件sd_img.c在本章“PC上的工具”目录下,读者可自行阅读。
sd_img.exe用法如下:
Ø sd_img.exe –h,列出支持的功能;
Ø sd_img.exe img_file –w local_file,把本地的文件local_file写入镜像,如果没有镜像,则自动创建;
Ø sd_img.exe img_file –r sd_file,把SD卡镜像中的文件读出到本地,文件名不变;
Ø sd_img.exe img_file –d sd_file,把sd卡镜像中的文件删除;
Ø sd_img.exe img_file –l,列出sd卡镜像中的文件;
Ø sd_img.exe img_file –u,统计sd卡空间占用情况。
首先,把hex2bin.exe和sd_img.exe复制到windows命令行目录下。然后,把helloworld.hex也复制到windows命令行目录下,命令行输入“hex2bin.exe hellowolrd.hex”生成hellowolrd.bin二进制文件,再输入“sd_img.exe test.mmc –w hellowolrd.bin”,可以创建test.mmc镜像,并把helloworld.bin文件写入镜像。操作示例如图8-1所示。
图8-1 hex2bin.exe和sd_img.exe工具使用示意图
最后,打开第6章提供的“8051计算机”项目,设置Proteus仿真中SD卡的镜像文件为test.mmc,执行仿真,输入helloworld.bin就能看到执行结果了,运行结果如图8-2所示。
图8-2 hellowolrd执行结果
如果把“8051计算机”做成实物,可以使用linux下dd等工具把镜像文件按字节写入真实的SD卡,也可以从真实SD卡读出镜像。
简单梳理一下,我们设计的计算机系统分成3个部分,分别是8051计算机硬件、51DOS操作系统和应用程序,应用程序以文件的方式存储在SD卡(好比硬盘)里,通过51DOS提供的命令行界面执行应用程序。应用程序的开发与没有操作系统时一样,只是需要设置一下开发环境,生成的hex程序转化成二进制文件并写入SD卡,把SD卡插入8051计算机,通过命令行界面运行各种应用程序。
后面再写其它应用程序时,可以使用helloworld例子的环境设置,读者只需要修改自己的C代码就行了。
8.2 文件操作类程序
下面我们设计并实现一组文件操作类命令程序,有了这些命令,用户就可以使用命令创建、删除、复制、查看、列出文件,就像DOS或Linux命令一样。
上一章中我们实现一组文件系统的操作函数接口,并封装成了文件库FILE.LIB,读者只需要把file.h头文件加到自己C代码里,把FILE.LIB加到自己的项目里就可以使用文件操作函数了。
8.2.1 touch命令
“touch 文件名”用来创建一个空文件。文件操作函数里的fcreate()函数可以用来创建空文件,用户可以使用helloworld例子的环境和代码,加入FILE.LIB库和file.h头文件,创建空文件的代码如下:
void main()
{
int ret;
Init_USART();
//创建空文件,操作系统传过来的第一个参数存储在外扩RAM的0x7F80
ret = fcreate(&XBYTE[0x7F80]);
if(ret == -1)
printf("create file %s error!\r\n", &XBYTE[0x7F80]);
else printf("create file %s ok!\r\n", &XBYTE[0x7F80]);
}
touch命令程序创建空文件效果如图8-3所示。
图8-3 touch命令执行示例
8.2.2 ls命令
ls命令用来列出SD卡上的文件信息,显示文件名和文件大小。
void main()
{
int i, ret;
char name[32];
Init_USART();
for(i=0; i<50; i++)
{
memset(name, 0, 32);
//循环读出每一个位置上的文件信息,
ret = read_fileinfo(i, name);
if(ret != -1)
printf("%s %d\r\n", name, ret);
}
}
ls命令的执行结果如图8-4所示。
图8-4 ls命令执行示例
8.2.3 df命令
df命令用来统计SD卡存储空间使用情况,注意区分文件大小和文件占用存储空间的大小。
void main()
{
int i, ret;
long fsize, usesize;
char name[32];
Init_USART();
fsize = 0;
usesize = 0;
for(i=0; i<50; i++)
{
ret = read_fileinfo(i, name);
if(ret != -1)
{
fsize = fsize + ret; //文件总大小
//文件占用的存储空间大小是512的倍数
usesize = usesize + (ret / 512 + 1) * 512;
}
}
printf("1048576 bytes total, %ld bytes used\r\nfiles size: %ld bytes\r\n", usesize, fsize);
}
df命令的执行结果如图8-5所示。
图8-5 df命令执行示例
8.2.4 rm命令
rm命令用来删除文件,使用fremove函数就能实现,代码如下:
void main()
{
int ret;
Init_USART();
//要删除的文件名存储在外扩RAM的0x7F80处,也就是第一个参数
ret = fremove(&XBYTE[0x7F80]);
if(ret == -1)
printf("%s not found!\r\n", &XBYTE[0x7F80]);
else printf("rm %s ok!\r\n", &XBYTE[0x7F80]);
}
rm命令执行情况如图8-6所示,执行rm后删除了test.txt文件。
图8-6 rm命令执行示例
8.2.5 cp命令
cp命令用来复制文件。如果源文件是空文件,则直接创建目标空文件;如果源文件是非空文件,则循环读取源文件内容并写入目标文件。代码如下:
void main()
{
int filesize, i, ret;
char buffer[512];
Init_USART();
//操作系统传过来的第一个参数是源文件
filesize = fsize(&XBYTE[0x7F80]);
if(filesize == -1)//源文件不存在
printf("can not find %s\r\n", &XBYTE[0x7F80]);
else if(filesize == 0)//源文件是空文件,目标文件也创建为空文件
{
ret = fcreate(&XBYTE[0x7F90]);//第二个参数是目标文件名
if(ret == -1)
printf("create %s failed\r\n", &XBYTE[0x7F90]);
}
else//源文件是非空文件
{
i = 0;
ret = fcreate(&XBYTE[0x7F90]);//先创建目标空文件
if(ret == -1)
{
printf("create %s failed\r\n", &XBYTE[0x7F90]);
return;
}
while(1)
{ //读取源文件内容并写入目标文件
ret = fread(&XBYTE[0x7F80], buffer, i*512, 512);
if(ret < 512)//已经读取完毕,完成最后内容写入
{
fwrite(&XBYTE[0x7F90], buffer, ret);
printf("cp ok\r\n");
break;
}
else fwrite(&XBYTE[0x7F90], buffer, 512);//读取512字节并写入
printf(".");//文件比较大时,显示进度条
i++;
}
}
}
执行“cp ls list”命令,把ls程序复制一份为list程序,执行list,可以看到list文件和ls文件大小一样,并且list的功能也和ls一样,cp命令执行情况如图8-7所示。
图8-7 cp命令执行示例
8.2.6 cat命令
cat命令用来查看文本文件的内容,循环读取,每次读取512字节并显示,直到文件结束,代码如下:
void main()
{
int filesize, i, ret;
char buffer[513];
Init_USART();
//操作系统传过来的第一个参数是要显示的文件名
filesize = fsize(&XBYTE[0x7F80]);
if(filesize == -1)
printf("can not find %s\r\n", &XBYTE[0x7F80]);
else if(filesize == 0)//空文件,无内容
printf("%s is an empty file\r\n", &XBYTE[0x7F80]);
else
{
i = 0;
while(1)
{
memset(buffer, 0, 513);//数组最后一个元素存储字符串结尾0
//每次读取512字节并显示,如果返回值小于512字节,则结束
ret = fread(&XBYTE[0x7F80], buffer, i*512, 512);
printf("%s", buffer);
if(ret < 512)//已经读取完毕
break;
i++;
}
}
}
通过ls命令可以看到test.txt文件有68字节,使用“cat test.txt”可以看到文件内容是一段basic代码,执行示例如图8-8所示。
图8-8 cat命令执行示例
8.3 行文本编辑器
前面的小命令基本可以完成SD卡上文件的管理,但怎样实现对文本文件内容进行编辑呢?比如插入文本、追加文本、删除文本等操作,我们设计一个行文本编辑程序,可以实现基于“行”的文本插入、删除等操作。
8.3.1 子命令设计
我们设计的文本编辑器实现的基本功能:插入文本、删除文本、追加文本、列出内容、保存到SD卡、统计行数、退出等功能。设计最大支持8192字节的文件编辑,一次插入和追加的文件内容不超过512字节,输入的一行文本不超过128字节。根据设计分配内存,分配一个8192字节的buffer数组作为文件内容缓存,分配一个512字节的temp_buffer数组作为临时缓存,再分配一个128字节的temp数组作为一行数据的缓存。
首先,把文件内容从SD卡读取到文件缓存buffer;然后,接收用户的输入命令;最后,解析命令,并根据命令完成相应的文本编辑功能,编辑完成后返回重新接收命令。
文本编辑器支持的子命令设计如下:
l l n1,n2 列出n1行到n2行之间的内容;
l d n1,n2 删除n1行到n2行之间的内容;
l a 追加内容到结尾,“回车”+“.”+“回车”完成追加并返回命令行;
l i n 第n行之前插入内容,“回车”+“.”+“回车”完成插入并返回命令行;
l w 保存文件内容缓存至SD卡;
l n 统计文件行数;
l q 退出文件编辑程序,回到操作系统;
l h 显示支持的功能。
8.3.2 子命令实现方法分析
1. 插入文本
接收用户输入数据至temp数组,当遇到回车时,则认为完成一行的输入,把temp中的一行数据输入temp_buffer,再接收新行并输入temp_buffer,直到遇到插入结束标志(“回车”+“.”+“回车”),把temp_buffer缓存中数据插入到文件缓存buffer的指定位置,重新回到命令解析模式,继续处理其它子命令。
怎样把临时缓存temp_buffer中的内容插入到文件缓存buffer中呢?
假设要把临时缓存temp_buffer中的内容插入到第6行,temp_buffer中总共有128字节,可以先把第6行行首到文件结尾的内容整体往后移动128字节,然后把temp_buffer中的128字节内容插入到空余出来地方。
2. 追加文本
接收用户输入的每一行数据至临时缓存temp_buffer,直到遇到追加结束标志(“回车”+“.”+“回车”),把temp_buffer缓存中的数据复制到文件缓存buffer的结尾即可。
3. 列出文本
列出n1到n2行之间的内容,并且在行首显示行号。把文件缓存中的内容从头到尾循环一遍,只要当前行号位于n1和n2之间,就显示当前字符出来,当前行号仍然统计回车符。
4. 删除文本
假设用户要删除n1至n2行之间的内容,可以先找到n1行的行首作为删除的开始位置start,再找到n2行的行尾作为删除的结束位置end,只需要删除start至end之间的内容即可。
怎样删除文件缓存buffer中start和end之间的数据呢?
我们可以把end后面直到文件结尾的内容依次往start位置搬移,就是把end后面的内容整体往前平移,覆盖要删除的内容。
5. 保存文本
保存文本就是把文件缓存buffer中的数据写入SD卡文件。如果文件不存在,则先创建文件后写入;如果文件为空,则直接写入;如果文件存在且不为空,则先删除原文件,再创建文件,最后写入内容。文件的操作函数上一章实现好了。
6. 统计行数
计算换行符“\n”的个数。
7. 退出
接收到q子命令时,设置退出标志为1,主函数里检测标志,如果标志为1,则返回到操作系统。
8.3.3 源代码分析
#include <reg51.h>
#include <stdio.h>
#include <string.h>
#include <absacc.h>
#include "file.h"
char buffer[8192];//文件内容缓存,暂时支持最大8192字节的文件
char cmd_line[100];//子命令存储缓存
char temp_buffer[512];//临时缓存,存放每次插入或追加的内容
char quit;//退出标志
// 向串口输出一个字符
void PutChar(char c)
{
SBUF = c;
while (TI == 0);
TI = 0;
}
void Init_USART()
{
SCON = 0x50;
TMOD = 0x20;
PCON = 0x00;
TH1 = TL1 = 0xFD;
TI = RI = 0;
TR1 = 1;
}
//保存编辑好的文件至SD卡
void save_file()
{
int file_len, ret;
file_len = fsize(&XBYTE[0x7F80]);//文件名存在0x7F80处
if(file_len == -1)//如果文件不存在,则创建新文件并写入
{
ret = fcreate(&XBYTE[0x7F80]);
if(ret == -1)
{
printf("save file error!\r\n");
return;
}
fwrite(&XBYTE[0x7F80], buffer, strlen(buffer));
}else if(file_len == 0) //如果文件大小是0,则直接写入
{
fwrite(&XBYTE[0x7F80], buffer, strlen(buffer));
}else if(file_len > 0) //如果是非空文件,先删除、再创建、再写入
{
fremove(&XBYTE[0x7F80]);
fcreate(&XBYTE[0x7F80]);
fwrite(&XBYTE[0x7F80], buffer, strlen(buffer));
}
}
//插入小工具
void insert(char *dest, char *src, int pos)
{
int length, i, j;
length = strlen(src);
for(i = strlen(dest) - 1; i >= pos; i--)//整体往后平移
dest[i + length] = dest[i];
for(j = pos;j < pos + length; j++)//插入空出来的位置
dest[j] = src[j - pos];
}
//列出n1行到n2行之间的内容
void list_file_contents()
{
char string1[5];
char string2[5];
char *p = NULL;
int n1, n2;
int i;
int line = 1;
//子命令是“l n1,n2”,但sscanf只认得空格分割,把逗号替换成空格
p = strchr(cmd_line, ',');
*p = ' ';
sscanf(cmd_line, "l %s %s", string1, string2);
sscanf(string1, "%d", &n1);//取出n1值
sscanf(string2, "%d", &n2);//取出n2值
if(n1 == 1)
printf("%d ", line);
//把文件缓存内容循环一遍,当前行号在n1、n2范围内,显示字符
for(i=0; i<8192; i++)
{
if(buffer[i] == 0) //到文件结尾了,结束
{
printf("\r\n");
break;
}
if(line >= n1 && line <= n2)//当前行号位于n1和n2之间时,显示
printf("%c", buffer[i]);
if(buffer[i] == '\n')//计算当前行号
{
line++;
if(line >= n1 && line <= n2)
printf("%d ", line);//显示行号
if(line > n2)//行号大于n2,结束显示
break;
}
}
}
//删除n1行到n2行之间的内容
void delete_file_contents()
{
char *p = NULL;
int n1, n2;
int i;
int line = 1;
int start = 0;
int end = 0;
p = strchr(cmd_line, ',');
*p = ' ';
sscanf(cmd_line, "d %d %d", &n1, &n2);//取出n1、n2
if(n1 == 1)
start = 0;
for(i=0; i<8192; i++)
{
if(buffer[i] == '\n')
{
line++;
if(line == n1)
start = i + 1;//找出n1行的行首作为删除的起始位置
if(line == n2 + 1)//n2的行尾时,行号已经变成了n2+1
end = i;//找出n2的行尾作为删除的终止位置
}
}
for(i=0; i<8192 - end -1; i++)
{
if(buffer[start + i] == 0)//到了文件末尾
break;
//end后面内容整体往前平移,覆盖要删除的内容,包括最后一个0
buffer[start + i] = buffer[end + 1 + i];
}
}
//追加文本内容至文件结尾
void append_txt()
{
int i;
int line_len;
char temp[128];
memset(temp_buffer, 0 ,512);
while(1)
{
memset(temp, 0, 128);
gets(temp, 128);//读取输入的一行数据,遇到回车往下执行
printf("\r"); //gets只回显换行,不回显回车,\r\n回车换行
for(i=0; i<512; i++)
{ //把接收到的一行数据添加到临时缓存的末尾
if(temp_buffer[i] == 0)
{
strcpy(&temp_buffer[i], temp);//复制的内容不包含回车换行
line_len = strlen(temp);
//行尾统一以回车换行(\r\n)作为结束标志,自行添加
temp_buffer[line_len + i] = '\r';
temp_buffer[line_len + i + 1] = '\n';
break;
}
}
//回车换行+'.'+回车换行是追加结束标志,追加结束需要把临时缓存
//内容添加到文件缓存末尾,然后回到命令界面
if((temp_buffer[line_len + i - 1] == '.') && (temp_buffer[line_len + i -2] == '\n'))
{//把“.”和回车换行也输入了temp_buffer中,需要再去掉
temp_buffer[line_len + i + 1] = 0;//去掉多余的“.”
temp_buffer[line_len + i] = 0;//去掉多余的回车
temp_buffer[line_len + i - 1] = 0;//去掉多余的换行
for(i=0; i<8192; i++)
{//把临时缓存的内容添加到文件缓存末尾
if(buffer[i] == 0)
{
strcpy(&buffer[i], temp_buffer);
break;
}
}
break;
}
}
}
//插入文本内容
void insert_txt()
{
char temp[128];
int i;
int n;
int num = 1;
int line_len;
memset(temp_buffer, 0 ,512);
sscanf(cmd_line, "i %d", &n);
while(1)
{
memset(temp, 0, 128);
gets(temp, 128);//输入一行数据
printf("\r");
for(i=0; i<512; i++)
{
if(temp_buffer[i] == 0)
{
strcpy(&temp_buffer[i], temp);
line_len = strlen(temp);
temp_buffer[line_len + i] = '\r';
temp_buffer[line_len + i + 1] = '\n'; //行尾以回车换行结束
break;
}
}
//遇到插入结束标志
if((temp_buffer[line_len + i - 1] == '.') && (temp_buffer[line_len + i -2] == '\n'))
{ //清除点和回车换行
temp_buffer[line_len + i + 1] = 0;
temp_buffer[line_len + i] = 0;
temp_buffer[line_len + i - 1] = 0;
for(i=0; i<8192; i++)//找出插入行的行首位置
{
if(buffer[i] == '\n')
num++;
if(num == n)
break;
}
//把临时缓存内容插入文件缓存,若插入第一行,实际就是插入
//开始位置,其它行,插入位置就是i+1
insert(buffer, temp_buffer, n == 1 ? 0 : i + 1);
break;
}
}
}
//统计行数
void count_lines()
{
int i;
int num = 0;
for(i=0; i<8192; i++)
if(buffer[i] == '\n')//就是统计换行符个数
num++;
printf("%d\r\n", num + 1);
}
//解析子命令
void parse_cmd_line()
{
printf("->");
gets(cmd_line, 100);
printf("\r");
if(cmd_line[0] == 'h')//帮助子命令
{
printf("l n1,n2---List n1,n2 contents\r\n");
printf("d n1,n2---Delete n1,n2 contents\r\n");
printf("a---append txt to the last line, enter+'.'+ enter back to cmd_line state\r\n");
printf("i n---insert txt to the line n, enter + '.'+ enter back to cmd_line state\r\n");
printf("w---write to file\r\n");
printf("n---how many lines\r\n");
printf("q---Quit\r\n");
printf("h---Show this help\r\n");
}else if(cmd_line[0] == 'l')//列出子命令
{
list_file_contents();
}else if(cmd_line[0] == 'd')//删除子命令
{
delete_file_contents();
}else if(cmd_line[0] == 'a')//追加子命令
{
append_txt();
}else if(cmd_line[0] == 'i')//插入子命令
{
insert_txt();
}else if(cmd_line[0] == 'n')//统计行数子命令
{
count_lines();
}else if(cmd_line[0] == 'w')//保存子命令
{
save_file();
}else if(cmd_line[0] == 'q')//返回操作系统子命令
{
quit = 1;
}else printf("cmd_line error!\r\n");//命令输入有误
memset(cmd_line, 0, 100);
}
// 主程序
void main()
{
int ret;
Init_USART();
quit = 0;
memset(buffer, 0, 8192);
ret = fsize(&XBYTE[0x7F80]);
if(ret > 0)//只有文件存在且有内容才需要读取到文件缓存
{
ret = fread(&XBYTE[0x7F80], buffer, 0, ret);
if(ret == -1)
{
printf("read %s error!\r\n", &XBYTE[0x7F80]);
return;
}
}
while(1)
{
parse_cmd_line();//循环解析命令并处理命令
if(quit == 1)
return; /返回到操作系统
}
}
8.3.4 文本编辑器使用演示
文本编辑程序的使用范例如图8-9所示。分别操作了打开test.txt文件,统计行数,列出1到7行内容,删除3到6行内容,再统计行数,再显示1到3行内容,在第2行处插入两行文本,统计行数,列出文件缓存内容,保存至SD卡文件,退回到操作系统
图8-9 ed命令执行示例
到这里为止,我们把计算机硬件的组成、硬件的工作原理、简单磁盘操作系统工作原理和应用程序开发都展现给了大家。打个比方,计算机硬件是一切的物质基础,好比人的身体;操作系统向下管理硬件,向上提供服务,好比人的思想;应用程序实现各种各样的具体功能,好比人的具体工作。我们首先要有健康的身体,然后要有强大的思想(做人、做事、宽心的哲学),最后还要有具体的事业,计算机系统也是如此,三者相辅相成,互为促进,缺一不可。前面还说过“结构、指令、程序”的模型,“结构”与这里说的硬件一致,“程序”与这里说的应用程序一致,“指令”与这里说的操作系统一致,操作系统可以认为是计算机指令集的更高级别的抽象。