概述
本项目是我自己使用的
来源于用串口传文件数据到电脑进行分析的时候,需要转换成可以打开的文件
而常用的串口助手大都没有直接保存为二进制文件的功能
也完全可以当作c语言初学者的练习项目(很简单)
需求
用串口传来的文件是如下格式(ASCII字符)
需要将其转化为二进制数据,使其可以打开
如下(上图是记事本打开的文本文件,下图是winhex打开的二进制文件)
另外需要运行效率相对较高(有时候会有Gb级文件解码的需要)
附加:便于使用,目前设计的是拖动要转化的文件到程序上,可以自动将其转化并输出文件(带后缀名)到指定目录
方案
核心程序选用C语言设计,本人对C相对熟悉,C语言的执行效率较高,可编译成exe文件
外围输入输出使用bat文件(批处理),获取拖入的文件名,及更改输出的文件名和位置
程序
C
控制台传参
当我们把主函数(main)定义成特殊形式时,我们可以在控制台(cmd)调用exe时向函数传递参数
如下图所示,则可以传入3个参数 第一个参数是1 第二个参数是2 (参数间用空格分开,为char *型)
我们把主函数定义为如下形式时即可实现这个功能
int main(int argc, char *argv[])
{
}
argc:是参数数量,上例子中为4(包括一开始的exe文件路径)
argv:是参数的内容
上例中 argv[0]是text.exe的全路径名(一般用不到)
argv[1]是"a1"
argv[2]是"a2"
argv[3]是"a3"
在本项目中,我设计传入的参数有3个,
第一个是被解析文件的全路径(绝对路径)(C:\home\1.txt)
第二个是输出文件名(为了区分,在程序中让这个文件名加上了后缀)(如 outputfile)(支持目录前缀 相对目录)
第三个是文件的后缀名(.jpeg)
输出文件名检测
这个部分是为了应对文件名出现重复的情况
例子:假设要输出的文件名是out ,但是输出目录下有了这个文件,则我需要让他输出out1
如果这个也有了则让他输出out2依次类推
设计是大于某个数(1000000)时则认为出错,也就是out1000000
思路:设计一个缓冲区(char *),将文件名拼接起来(sprintf)输出到缓冲区,之后检测文件是否存在(access)
如果存在则后缀加1,不存在直接创建即可
sprintf是字符串格式化输出的函数,百度连接,和printf用法差不多,只是将输出内容放到了字符串(Add_output)中
access是检测权限的一个函数,百度连接,返回值为0时文件不存在
malloc和free是c的动态分配函数,百度连接
Add_output = (char *)malloc(sizeof(char) *(strlen(Add_output_befor) + strlen(Add_output_after) + 15));
这句的作用是重新分配缓冲区的大小,让其值变成可被sprintf覆写的状态,malloc的用法请自行百度
注意使用后需要free
char *Add_output;
char *Add_output_befor = argv[2];
char *Add_output_after = argv[3];
for (int i = 0;; i++)
{
Add_output = (char *)malloc(sizeof(char) *(strlen(Add_output_befor) + strlen(Add_output_after) + 15));
sprintf(Add_output, "%s%d%s", Add_output_befor, i, Add_output_after);
// puts(Add_output);
if (access(Add_output, 0) != 0)
break;
else
free(Add_output);
if (i > 1000000)
{
printf("ERROR:output file name error\r\n");
}
}
//puts(Add_output);
文件的打开与基本读写
使用fopen打开文件,fgetc读取单个字符,feof判断文件末尾,fputc输出内容到文件,fclose关闭文件
这几个函数很常用,不懂的请自行百度
思路:我们到这里已经将输出文件的位置和名字确定好了(上一步),
现在需要判断输入的文件是否可以打开,如果不打开则输出错误信息并退出
之后在打开输出文件,开始为读写做准备
输入文件使用只读模式打开,输出文件使用二进制写模式打开
char *Add_input = argv[1];
FILE *input_fp, *output_fp;
input_fp = fopen(Add_input, "r");
if (input_fp == NULL)
{
printf("ERROR: failed to open the read file!\r\n");
fclose(input_fp);
fclose(output_fp);
free(Add_output);
system("pause");
return 0;
}
output_fp = fopen(Add_output, "wb");
while (!feof(input_fp)) // 读文件
{
data_zj = fgetc(input_fp);
/*省略的内容*/
fputc(data_all, output_fp);
}
格式转化(核心)
这是这个项目的核心,就是将ascii的字符解码成二进制的过程
我们先分析一下字符的样子
如下图,"B5 10 00 "由0-9和A-F来表示16进制的字符,以1Byte为单位,Byte直接用空格隔开
目标则是转化为 0xB5,0x10,0x00
思路:
搞一个循环,知道读取到文件尾退出
首先读取一个字符,如果这个字符是空格则跳过这次循环,如果不是空格则再次判断是否到了文件尾(避免出现不合法的字符的情况),文件尾则退出循环
将这个字符转化为数,如Ascii 的’0’到’9’就减去’0’,'A’到’F’就减去’A’加10(0xA代表十进制的10).之后在读取一个字符,并对其转化
将两个字符按顺序拼接起来,输出到文件即可
while (!feof(input_fp)) // 读文件
{
data_zj = fgetc(input_fp);
if (data_zj == ' ')
continue;
if (feof(input_fp))
break;
data_first = Ascll2uchar(data_zj);
data_last = Ascll2uchar(fgetc(input_fp));
data_all = (data_first << 4) | data_last;
// printf("%x ", data_all);
fputc(data_all, output_fp);
}
时间计算
使用clock函数,在程序开头和结尾分别计算一次时间,相减即可
int begintime, endtime;
begintime = clock();
/*中间程序*/
endtime = clock();
printf("finish\r\nspend %d ms\r\n", endtime - begintime);
完整程序
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#define uint8_t unsigned char
uint8_t Ascll2uchar(uint8_t input)
{
if (input >= 'a' && input <= 'z')
return input - 'a' + 10;
if (input >= 'A' && input <= 'Z')
return input - 'A' + 10;
if (input >= '0' && input <= '9')
return input - '0';
}
int main(int argc, char *argv[])
{
int begintime, endtime;
uint8_t data_all, data_first, data_last;
uint8_t data_zj;
FILE *input_fp, *output_fp;
char *Add_output;
char *Add_input = argv[1];
char *Add_output_befor = argv[2];
char *Add_output_after = argv[3];
// char *Add_input = "C:\\Users\\HZ12138\\Desktop\\1.txt";
// char *Add_output_befor = "output";
// char *Add_output_after = ".jpeg";
begintime = clock();
for (int i = 0;; i++)
{
Add_output = (char *)malloc(sizeof(char) *
(strlen(Add_output_befor) + strlen(Add_output_after) + 15));
sprintf(Add_output, "%s%d%s", Add_output_befor, i, Add_output_after);
// puts(Add_output);
if (access(Add_output, 0) != 0)
break;
else
free(Add_output);
if (i > 1000000)
{
printf("ERROR:output file name error\r\n");
}
}
puts(Add_output);
input_fp = fopen(Add_input, "r");
if (input_fp == NULL)
{
printf("ERROR: failed to open the read file!\r\n");
fclose(input_fp);
fclose(output_fp);
free(Add_output);
system("pause");
return 0;
}
output_fp = fopen(Add_output, "wb");
while (!feof(input_fp)) // 读文件
{
data_zj = fgetc(input_fp);
if (data_zj == ' ')
continue;
if (feof(input_fp))
break;
data_first = Ascll2uchar(data_zj);
data_last = Ascll2uchar(fgetc(input_fp));
data_all = (data_first << 4) | data_last;
// printf("%x ", data_all);
fputc(data_all, output_fp);
}
fclose(input_fp);
fclose(output_fp);
free(Add_output);
endtime = clock();
printf("finish\r\nspend %d ms\r\n", endtime - begintime);
return 0;
}
批处理文件
使用批处理文件是为了方便的更改输出文件位置和文件名(记事本就能打开更改)
进入当前目录,打开exe文件
其中这个%1指的是获取拖拽到当前批处理上文件的绝对目录
@echo off
path = .\
Ascii2Bin.exe %1 output .jpeg
pause
使用教程
如下图,上面的是可执行文件(c语言编译出来的),中间的是批处理文件,下面的是要转化的文件
我们将要转化的文件拖入批处理文件中,即可在当前目录生成解码后的文件,并输出信息