热敏打印机字库下载工具

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;

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可能感兴趣的项目设计:应用于 POS 机、收银机等80mm 高速微型打印机(原理图、上位机、程序源码) 附件内容截图: 字库改组: 为了便于处理,我们对字模进行如下改组: 把 16 X 16 汉字,分成左边从上到下16个字节,和右边16个字节,目的是可以把一个汉字当成两个 16X8 字母或数字处理。 改组后,字模数据从 RAM 读出送往打印机时,无论是汉字还是字母或数字,第n行点的 "字节数据" 就是: n, n+16, n+16*2, n+16*3......一行取48个字节, 8*48=384 个点。 把 24 X 24 汉字,分成左边从上到下 24行 X 12列, 和右边 24 X 12 数据,一行 12bit 数据存放在半字 16bit 中的低12bit, 左右半边都有 24个"半字",或 48 个字节,一个24X24汉字变成96个字节了。如此处理字模时,把一个汉字当成两个 24 X 12 字母或数字来处理。 不管汉字字母或数字,第n行点的 ”半字数据“ 就是 n,n+24,n+24*2,n+24*3......取32个"半字",刚好够 12bit*32=384 个点,即一行可打 32个字母,或16个汉字。 字模写入 SPI FLASH 时,完成以上变换。打印时,NUC123 从 SPI FLASH 读出的字模,认为是上述变换后的格式。 高速 SPI 接口传输数据 打印时,字模数据要从 W25Q16 中通过 SPI 接口读取,再通过 SPI 接口移入打印头。 一行 16个 24 X 24 汉字有 16*96 = 1536 个字节。字符行与行之间一般有一到三个空行,走纸最快时 90mm/秒,694us 走一行,在这个时间内要读取所有 1536个字节,放入 RAM 打印缓冲区。代码中,把 SPI 配置成 28.8Mbps 从FLASH 中读取字模,为便于用示波器查看波形,每次收发 16bit 中间,插入一个空闲 CLK,收发完 1536 Byte 要经过 1536/2 = 768 次 17bit 时钟,时间为 768*17/28.8 = 453us。关键是 SPI 收发 16bit 中间不能再有额外间隙,否则 28.8Mbps 失去意义,所以代码中,使能 SPI 的 FiFo,同时配置 PDMA 来完成 SPI 收发。 点数据移入打印头,SPI 速率配置为 4.8Mbps,384个点。数据传输时间约为 384/4.8 = 80us,走纸最快 90毫米/秒 时 694us走一行,数据早传完了。走纸速度再快一倍也来得及。 USB 打印机模式 USB 打印模式,上位机传输过来的,是图形点阵数据与命令混在一起,无需从 SPI FLASH 中读字模,只要把点数据从USB数据流里分离出来,移入打印头即可。代码中定义了一个 4096 字节的环形缓冲区。 USB 数据中断: 分析数据流,见函数 PTR_Data_Receive(), 遇图形点阵数据流,写入环形缓冲区。遇命令立即执行,或为执行做好准备。若有打印命令或走纸命令,就把 “走纸行数” 通过全局变量 StepIncrease 传给主循环代码。 主循环里,1>若传过来的走纸行数 StepIncrease 不为0,就启动走纸。2>若已在走纸,就检查环形缓冲区是否有待打印数据——比较写入指针与读出指针的值可得知,若有待打印数据,就会在纸每走过一行时,把数据送入打印头并加热打印。 走纸,加热和数据传输,三者同时处理的时序 需要走纸时,只要设定速度值全局变量 pStepM_TimLimit,然后调用走纸启动函数 PaperRollStart() 就开始走纸了,何时走下一行,何时停止,都在 Timer1 中断代码里处理。中断外面无需再处理走纸的事。 需要说明的是:有一个走纸剩余行数全局变量 RemainderStep, 在此变量非0时,可以随时加大这个值,加长走纸行数。 再来看何时把数据串入打印头,何时加热,下图用鼠标点击可放大。 打印开始时,先启动走纸,前面空一两行没事,一行只有 0.0625mm。 把第一行点数据,串入打印头后,就等待走纸到第三行——由剩余步数全局变量 RemainderStep 的值可得知。只要一走到第三行,立即输出点数据到加热头,并启动加热定时。然后就去处理下一行点数据。 每次在走到新的一行时(实际是两行), 都立即启动已准备好的、这一行数据的加热,接着代码去准备下一行点数据。 何时加热停止呢? 不用担心,由 Timer2 中断代码处理了。 打印开始时,必须知道要走几行, 两行一个点,一行 16 X 16 字符,要走 32行,一般还要加上开头和结尾的空行。 连续打印时,只要剩余行数 RemainderStep 不为0——走纸未停,可以随时增加这个值,加长走纸行数,继续打

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值