1.引言
1.1编写目的
为校验打印机汉字字库在出厂时的完整性,防止用户在使用过程中打印汉字时出错。采用PC机和设备相结合的方法对字库进行校验。
1.2背景
打印机在生产时由烧写器进行字库烧写,在生产时,可能因为各种原因造成了字库数据部分丢失,在出厂测试时不能发现,因此增加了校验字库功能,在出厂时对字库进行校验,如果校验成功,则可以正常出厂,如果校验失败,则本工具重新对打印机进行下载字库操作,下载完后自动校验.
2. 系统的结构
打印机字库系列,一PP414_V1.8系列 和PP414_V5.0系列,上位机工具需兼容所有的下位机软件版本,包括两种字库和有无校验功能的软件.
字库文件说明
PP414_V1.8字库一般按72bit拆分,PP414_V5.0字库一般按64byte拆分,由于下位机的处理方式有差异,下载PP414_V1.8字库后校验是按照烧写器下载的字库文件参与校验计算,而不是PP414_V1.8字库本身,PP414_V5.0字库下载校验均用同一个文件.
2.1 PC软件界面设计
-
- 采用标准的Windows界面风格,避免简单的错误,减少操作时间,下载过程明了;
- 本工具仅一个可执行文件,无需安装,无需配置,本工具采用打包资源文件方式将下载过程中用到的三个字库文件捆绑,操作时仅需选择字库版本,不必另行选择字库文件;
- 点击“设置”,设置串口参数,包括:端口、波特率、奇偶位、数据位、停止位等。默认设置为:端口为com1,波特率为19200,无奇偶位,数据位为8位,停止位为1位;
- 点击“检查版本”,工具通过串口发出检查版本的命令,并且将接收到的版本信息显示在下载状态信息栏里.供下载操作时参考;
- 点击“检验”,工具通过串口发送CRC检验命令与下位机进行握手,自动匹配校验码后判断校验是否正确,如果校验错误,工具自动下发下载字库文件操作。校验失败(即上下位机校验握手失败或超时)或校验正确时,等待操作者执行其它命令。
- 点击“下载字库”,工具将下载所选的字库文件。
- 点击“选择字库”,选择汉字字库,包括校验时使用的字库版本。
- 点击“关闭”按钮,则关闭软件
- “校验统计”是统计生产时校验的情况供操作者参考。点击“统计清零”,将成功、失败、总计等台数清零
- “信息显示窗口”显示当前操作的信息。字库下载或校验情况,串口打开关闭等操作记录。
- “状态栏”窗口,显示当前操作过程状态,字库下载指针,下载进度显示。
- 每次操作结束或中断后,无论本工具是否具有焦点,均主动显示操作结束提示,方便下载过程中的操作。
- 允许本工具重复打开,方便生产时多个串口同时操作。互不干扰。
2.2通信协议
通信协议有CRC字库校验协议和在线字库下载协议。字库校验协议用于校验存储汉字字库的Flash数据的正确性,在进行字库检验时,PC和打印机要先进行握手,握手成功后才进行字库校验。在线字库下载协议用于手动下载字库和字库检验到Flash数据不正确时自动下载字库。
2.2.1CRC字库校验协议
PC机和打印机约定采用同一个方法对字库进行CRC校验,得出校验码。比较两者计算出的校验码是否相同,如果一样,说明字库是完整,没有出现坏块等问题。如果校验码不一样,则PC机发送字库下载命令,在线下载字库。PC机和打印机的通信采用串口,通信波特率为19200,数据位8位,无校验位,停止位1位。具体的通信步骤如下:
-
-
- PC和打印机握手协议
-
1)、PC发送“0x1b+0x41+0x54”,每100ms发送一次。
2)、打印机接收到命令后返回“K”
3)、如果握手不成功,则不进行字库检验
-
-
- PC机发送“0x43”,告诉打印机准备校验,
- PC机发送校验的字节长度,如“0x9307C0“,此值必须是64的倍数,如果最后一个包不够64byte,将其他的用0xFF填充参与校验.
- 打印机将CRC检验码发送给PC机。
- PC机通过比较校验码,如果校验码一致,则显示OK,如果检验码不一致,则显示错误,并发送在线下载字库命令
-
2.2.2在线字库下载协议
-
-
- PC发送在线字库下载命令“0x1b+0x44+0x4c”。每100ms发送一次。
- 打印机接收到在命令后对Flash进行擦除,擦除成功后发送返回擦除成功标志“E”,然后再发送数据包大小“cPackSize”(0<cPaceSize<255),即约定每个数据包的大小
- PC发送“O”告诉打印机上位机准备好
- 打印机发送“G”告诉PC下位机准备好
- PC发送数据包,数据包格式化:0x44+0x41+cPaceSize个字库字节数据。
- 打印机接收cPaceSize个数据后,存储数据到Flash,待处理完后,发送“N”。
- PC接收到“N”后,返回上一步继续发送下一个数据包,直接检测到数据完全发完后,退出字库下载状态,并自动转入校验操作。
-
2.2.3 CRC校验方法
采用的方法是将上一字节CRC码的低12 位左移4 位后,再加上上一字节余式CRC 右移4 位(也既取高4 位)和本字节之和后所求得的CRC 码,如果我们把4 位二进制序列数的CRC 全部计算出来,放在一个表里,采用查表法。下面是按半字节求CRC 码的C 语言程序。*ptr 指向发送缓冲区的首字节,len 是要发送的总字节数,CRC 余式表是按0x11021 多项式求出的。
下位机C代码:
unsigned cal_crc(unsigned char *ptr, unsigned char len) {
unsigned int crc;
unsigned char da;
unsigned int crc_ta[16]={ /* CRC 余式表 */
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
}
crc=0;
while(len--!=0) {
da=((uchar)(crc/256))/16; /* 暂存CRC 的高四位 */
crc<<=4; /* CRC 右移4 位,相当于取CRC 的低12 位)*/
crc^=crc_ta[da^(*ptr/16)]; /* CRC 的高4 位和本字节的前半字节相加后查表计算 CRC,然后加上上一次CRC 的余数 */
da=((uchar)(crc/256))/16; /* 暂存CRC 的高4 位 */
crc<<=4; /* CRC 右移4 位, 相当于CRC 的低12 位) */
crc^=crc_ta[da^(*ptr&0x0f)]; /* CRC 的高4 位和本字节的后半字节相加后查表计算
CRC,然后再加上上一次CRC 的余数 */
ptr++;
}
return(crc);
}
上位机Delphi代码
const CRCTab: array[0..15] of word = ($0000, $1021, $2042, $3063, $4084, $50A5, $60C6, $70E7,
$8108, $9129, $A14A, $B16B, $C18C, $D1AD, $E1CE, $F1EF); //CRCÓàʽ±í
var
tmpBuf: byte;
L: integer;
CalCRC: word;
CRCTemp, CRCData: byte;
bytecount: integer;
begin
CalCRC := $00;
try
filestream.Position := 0;
L := ceil(filestream.Size / 64) * 64;
while L > 0 do
begin
dec(L);
bytecount := filestream.Read(tmpBuf, 1);
if bytecount = 0 then
tmpBuf := $FF;
CRCData := tmpBuf; //
CRCTemp := CalCRC shr 12; //暂存CRC 的高四位
CalCRC := CalCRC shl 4; // CRC 右移4 位,相当于取CRC 的低12 位
CalCRC := CalCRC xor CRCTab[CRCTemp xor (CRCData shr 4)];// CRC 的高4 位和本字节的前半字节相加后查表计算CRC,然后加上上一次CRC 的余数
CRCTemp := CalCRC shr 12; //暂存CRC 的高4 位
CalCRC := CalCRC shl 4; // CRC 右移4 位, 相当于CRC 的低12 位)
CalCRC := CalCRC xor CRCTab[CRCTemp xor (CRCData and $0F)];// CRC 的高4 位和本字节的后半字节相加后查表计算CRC,然后再加上上一次CRC 的余数
end;
Result := inttohex(CalCRC, 4);
except
Result := 'FFFF';
end;
end;
2.3 软件处理过程的状态图
根据四个操作入口,进入数据交互状态中,分别是:下载字库操作,校验字库操作,读取版本操作,中断操作(如关闭串口)。
各个状态的处理说明:
状态1:下载握手:
来源有两个,一个是下载字库操作进入了状态1,另外一个是校验状态5的情况下校验失败自动转入。
具体处理:程序打开定时器,通过定时器每隔1000ms发送一个下载握手命令即:0x1b+0x44+0x4c,发送一次对超时计数器累加1
-
-
- 当超时计数器达到一定数量时,则退时状态1。提示握手超时。
- 收到下位机Flash擦除成功标志“E“,转入状态2。
-
转入的状态:一个是状态2,一个是收到中断操作转入状态0
状态2:Flash准备好:
来源于状态1(下载握手)时收到了擦除成功标志“E”而转入的状态
具体处理:根据收到下位机约定的数据包大小,对选定的字库资源分割。并发送“O”告诉打印机,上位机就绪,并转入状态3
状态3:下载状态
来源于状态2
具体处理:等待接收下位机下载准备就绪的信号“G”按约定的数据长度发送数据,数据格式0x44+0x41+cPaceSize个数据,数据不满cPacesSize情况补0xFF。
状态4:校验握手
来源有两个,一个是完成下载字库操作自动进入了状态4,另外一个由校验字库操作进入。
具体处理:程序打开定时器,通过定时器每隔1000ms发送一个校验握手命令即:0x1b+0x41+0x54,发送一次对超时计数器累加1
1、当超时计数器达到一定数量时,则退时状态4,转入状态0。提示校验握手超时。
2、收到下位机握手成功标志“K“,PC机发送0x43,再发送待校验的文件长度。转入状态5(校验状态)
状态5:校验状态
来源于校验握手成功
具体处理:PC机计算出校验数据,并等待下位机送出校验数据,校验数据为两个字节,PC机收到下位机送出的校验数据后,匹配校验数据,如果正确,则提示校验正确并转入状态0,如果错误,则自动转入状态1(下载握手),重新下载字库.
2.4资源文件的处理
资源文件创建:创建一个文件文件HZFfile.rc
把需要加入资源的字库文件放到HZFfile.rc同一目录下
编写rc文件的内容为:
HZK24S exefile HZK24S
HZK24Sold exefile HZK24Sold
HZK24SoldCRC exefile HZK24SoldCRC
在开始->运行里,执行cmd进入DOS命令,进入rc文件所在的目录,执行brcc32.exe HZFfile.rc命令,命令成功执行后即生成一个HZKfile.RES资源文件。
资源文件捆绑:
在Delphi环境下,在主窗体{$R *.DFM}下面加上{$R HZKfile.RES}
编译Delphi工程后,这三个字库就被包含在字库下载工具的可执行文件中了。
资源文件调用:
声明Tstream类,对Tstream类赋值:
FStream := TResourcestream.Create(HINSTANCE, 'HZK24Sold', 'exefile');
接下来就Fstream就是按字节存储的字库文件流
3.主要模块设计说明
cmd_status: integer; //命令状态变量 1 准备下载 2 准备好
硬件返回指令,接收并处理
procedure TFrmMain.ApdComPort1Trigger(CP: TObject; Msg, TriggerHandle,
Data: Word);
procedure cmddealbyte(Buffer: byte);
var
k: integer;
sBuf: array[0..254] of Byte;
begin
case cmd_status of
0: ; //无状态
1: begin
if Buffer = $45 then
begin
Timer1.Enabled := false; //握手成功
memoinfo.Lines.Add('下载开始' + datetimetostr(now));
StsBar.Panels[1].Text := '正在下载...';
cmd_status := 2; //转入状态Flash准备好
end;
end;
2: begin //拆分文件
pkgsize := Buffer;
//初始化BUF
Fstream.Position := 0;
totalcount := ceil(Fstream.Size / pkgsize) + 1; //多发一个空包
//发送命令O,转入状态发送数据
Progressbar1.Max := totalcount;
Progressbar1.Step := 1;
cmd_status := 3;下一状态
fileindex := 0; //初始发送位置标志
sBuf[0] := 79; //D
ApdComPort1.PutChar(chr(sBuf[0]));发送指令
end;
3: begin
case Buffer of
$47: begin
inc(fileindex);
sbuf[0] := $44;
sbuf[1] := $41;
Publicbuflen := FStream.Read(PublicBuf, pkgsize); //读取pkgsize个字节
ApdComPort1.PutChar(chr(sBuf[0]));
ApdComPort1.PutChar(chr(sBuf[1]));
for k := 0 to Publicbuflen - 1 do
ApdComPort1.PutChar(chr(PublicBuf[k]));
Progressbar1.Position := 0;
StsBar.Panels[2].Text := inttostr(fileindex) + '/' + inttostr(totalcount);
end;
$4E: begin
if fileindex <= totalcount - 1 then
begin
inc(fileindex);
sbuf[0] := $44;
sbuf[1] := $41;
Publicbuflen := FStream.Read(PublicBuf, pkgsize); //读取pkgsize个字节
ApdComPort1.PutChar(chr(sBuf[0]));
ApdComPort1.PutChar(chr(sBuf[1]));
for k := 0 to Publicbuflen - 1 do
ApdComPort1.PutChar(chr(PublicBuf[k]));
for k := Publicbuflen to pkgsize - 1 do
ApdComPort1.PutChar(chr($FF)); //不足一个单位补FF
Progressbar1.StepIt;
StsBar.Panels[2].Text := inttostr(fileindex) + '/' + inttostr(totalcount);
end
else
begin
ApdComPort1.PutChar(chr($46)); //结束符下载
memoinfo.Lines.Add('下载结束' + datetimetostr(now));
StsBar.Panels[1].Text := '下载结束';
cmd_status := 0; //无状态
Progressbar1.StepIt;
CRCcheck; //检查校验
end;
end;
$41: begin
// memoinfo.Lines.Add('错误发生,地址:'+inttostr(fileindex));
{ sbuf[0] := $44;
sbuf[1] := $41;
StsBar.Panels[2].Text := inttostr(fileindex) + '/' + inttostr(totalcount);
ApdComPort1.PutChar(chr(sBuf[0]));
ApdComPort1.PutChar(chr(sBuf[1]));
for k := 0 to Publicbuflen - 1 do
ApdComPort1.PutChar(chr(PublicBuf[k]));
for k := Publicbuflen to pkgsize - 1 do
ApdComPort1.PutChar(chr($FF)); //不足一个单位补FF }
end;
end;
sleep(1);
end;
4: begin //校验握手成功,发送校验命令和校验文件长度
if Buffer = $4B then
begin
cmd_status := 6;
CRCstr := '';
Timer3.Enabled := false;
Timer2.Enabled := true;
memoinfo.Lines.Add('校验开始');
StsBar.Panels[1].Text := '正在校验...';
ApdComPort1.PutChar(chr($43)); //校验命令
sBuf[0] := (sizeint and $FF0000) shr 16;
sBuf[1] := (sizeint and $00FF00) shr 8;
sBuf[2] := (sizeint and $0000FF);
ApdComPort1.PutChar(chr(sBuf[0])); //校验文件的长度
ApdComPort1.PutChar(chr(sBuf[1]));
ApdComPort1.PutChar(chr(sBuf[2]));
end;
end;
5: begin //打印机版本号
if (Buffer <> $0D) and (Buffer <> $0A) then
verstr := verstr + chr(Buffer);
// memoinfo.Text := memoinfo.Text + chr(Buffer);
// memoinfo.Text := memoinfo.Text + inttohex(Buffer,2);
if Buffer = $0A then
begin
memoinfo.Lines.Add(verstr);
cmd_status := 0;
end;
// sendmessage(memoinfo.Handle,WM_VSCROLL,SB_BOTTOM,0);
end;
6: begin
//取得校验字符
CRCstr := CRCstr + inttohex(Buffer, 2);
if length(CRCstr) >= 4 then //
begin
StsBar.Panels[1].Text := '校验结束';
if CRCcalstr = CRCstr then
begin
memoinfo.Lines.Add('校验正确');
inc(Downsucc);
Lblsuccess.Caption := inttostr(Downsucc);
Lbltotal.Caption := inttostr(Downsucc + Downfail);
setforegroundwindow(application.Handle); //使程序获得焦点
BtnCRC.Enabled := true;
BtnCRC.SetFocus;
BtnDownload.Enabled := true;
Progressbar1.Position := 0;
cmd_status := 0;
Timer2.Enabled := false;
end
else
begin
//memoinfo.Lines.Add('校验失败:' + CRCcalstr + ' , ' + CRCstr);
memoinfo.Lines.Add('校验错误');
cmd_status := 0;
inc(Downfail);
Lblfail.Caption := inttostr(Downfail);
Lbltotal.Caption := inttostr(Downsucc + Downfail);
Timer1.Enabled := true; //校验失败重新下载
Timer2.Enabled := false;
end;
end;
end;
end;
end;
var
I: Word;
C: byte;
begin
try
case Msg of
APW_TRIGGERDATA: //响应数据匹配触发器
{got 'login', send response}
;
APW_TRIGGERAVAIL:
begin
for i := 0 to Data - 1 do
begin
C := ord(apdComPort1.GetChar);
cmddealbyte(C);
end;
end;
APW_TRIGGERTIMER: // 响应时间触发器
{timed out waiting for login prompt, handle error}
;
end;
except
end;
end;