1.背景
项目需要用到热敏打印机,控制接口为串口(RS232),运行环境为Linux+Qt。在此之前,在MCU平台的实时系统(RT-Thread)上已经实现出正确的打印功能,原则上把代码移植过来,调整下打印位置、字体大小等即可。代码移植后,运行结果是英文、数字打印正常,中文打印则出现乱码。现有的配置情况是,热敏打印机提供的英文、数字字库是ASII格式,中文字库是GB2312格式,平台控制端采用UTF-8编码格式,C++代码编写。因此基本可以确定是编码问题。
2.处理方法1——简单粗暴,将文件格式由UTF-8转为ANSI(windows只有这个格式选项,在简体中文系统下,ANSI代表GB2312)
鉴于之前开发的项目,团队成员之间或者其他电脑查阅源码工程时,都会莫名其妙的乱码,所以从此项目开始,所有源码编码的都采用UTF-8的保存格式(这个可以Qt Creator中设置),打印内容文件也是UTF-8保存。在此之前,记得上一东家同事的文件格式采用ANSI时,无需作编码转换即可正常打印中文。本人也做个尝试,将打印内容的文件改为ANSI格式(windows下用记事本打开->另存为->选择ANSI->保存)。结果很遗憾,打印还是乱码。也许是设置或者某些细节问题,这个我没有深入研究,因为比较感兴趣的是编码转换问题。
结论:该方法本人未能成功实现,后续再深入研究,欢迎大家分享经验。
3.处理方法2——iconv
iconv基于GPL公开源代码,是GNU项目的一部分,在各种Unix-like操作系统下很容易编译和使用,从网络上也能找到大牛介绍或者利用该函数写的编码转换源码【1】。所以,本人首先在Linux下,用C编写程序,调用该函数实现转换,然后再移植到Qt下面用C++方式实现,Linux下经过测试可以正常打印。关键代码如下:
保存为C源文件时,必须以UTF-8的格式。
#include <stdio.h> /* 标准输入、输出定义 */
#include <unistd.h> /* UNIX 标准函数定义 */
#include <fcntl.h> /* 文件控制定义 */
#include <errno.h> /* 错误编号定义 */
#include <termios.h> /* POSIX 终端控制定义 */
#include <string.h> /* 字符串函数定义 */
#include <iconv.h>
#define SERIAL_PORT "/dev/ttySAC3" //ARM串口端口
#define BAUDRATE B9600 //9600bps
#define MAX_SIZE 255
/** \brief 打开串行端口一
* \brief 成功后返回文件描述符,或是失败后返回-1
*/
int code_convert(char *from_charset,char *to_charset,char *inbuf,int inlen,char *outbuf,int outlen)
{
iconv_t cd;
printf("in= %s\n",inbuf);
char **pin = &inbuf;
char **pout = &outbuf;
char *old;
old = outbuf;
cd = iconv_open(to_charset,from_charset);
if(cd==(iconv_t)-1)
{
printf("iconv open failed!\n");
perror("iconv_open");
return -1;
}
memset(outbuf,0,outlen);
if (iconv(cd,pin,(size_t*)&inlen,pout,(size_t*)&outlen) == -1)
{
printf("iconv failed!\n");
perror("iconv");
}
iconv_close(cd);
printf("out= %s\n",&outbuf);
return outbuf - old;
}
int gb2312_utf8(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("gb2312","utf-8",inbuf,inlen,outbuf,outlen);
}
int utf8_gb2312(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("utf-8","gb2312",inbuf,inlen,outbuf,outlen);
}
int gb2312_ucs2(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("gb2312","ucs-2",inbuf,inlen,outbuf,outlen);
}
int ucs2_gb2312(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("ucs-2","gb2312",inbuf,inlen,outbuf,outlen);
}
int open_port()
{
int fd; /* 端口文件描述符 */
fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open /dev/ttyS0 - ");
}
return (fd);
}
void set_port(int fd)
{
struct termios options;
int flag;
tcgetattr(fd, &options);
fcntl(fd, F_SETFL, 0);
cfsetispeed(&options, BAUDRATE);
cfsetospeed(&options, BAUDRATE);
options.c_cflag |= CREAD;
/* Set to 8N1 */
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
/* RAW */
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag &= ~OPOST;
/* For read data from printer */
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 1;
flag = tcsetattr(fd, TCSANOW, &options);
if (flag < 0)
{
perror("set_port: Setting port /dev/ttyS0 error - ");
}
}
void close_port(int fd)
{
close(fd);
}
void print(int fd, char* sent)
{
int len = strlen(sent);
char ToGB2312[100] = {0x00};
utf8_gb2312(sent,strlen(sent),ToGB2312,len);
write(fd, &ToGB2312, strlen(ToGB2312));
}
void print_line(int fd, char* sent)
{
int len = strlen(sent);
char ToGB2312[100] = {0x00};
utf8_gb2312(sent,strlen(sent),ToGB2312,len);
ToGB2312[strlen(ToGB2312)] = 0x0a;
write(fd, &ToGB2312, strlen(ToGB2312));
}
//走纸
void feed(int fd, int num)
{
unsigned char ch_feed = 0x0a;
int i;
for (i=0;i<num;i++)
{
write(fd,&ch_feed,1);
}
}
//热敏打印机条码打印,输入的为数字
void barcode(int fd, char* bar)
{
char ins[3] = {0x1d, 0x6b, 0x45}; //指令和条形码类型
char hex_len[1]; //条形码长度
hex_len[0] = strlen(bar);
char ToGB2312[50] = {0x00};
char all[100] = {0x00};
write(fd,&ins,strlen(ins));
write(fd,&hex_len,1);
utf8_gb2312(bar,strlen(bar),ToGB2312,strlen(bar));
write(fd,&ToGB2312,strlen(ToGB2312));
}
int main(int argc, char *argv[])
{
int fd, i;
unsigned char convert[100] = {0x00};
fd = open_port();
set_port(fd);
print(fd,"我爱中国\x0A");
print_line(fd,"=======================");
print(fd,"Acuity\x0a");
barcode(fd,"1234567");
print_line(fd,"1234567");
feed(fd,5);
close_port(fd);
return 0;
}
printf("iconv open failed!\n");
perror("iconv_open");
return -1;
}
memset(outbuf,0,outlen);
if (iconv(cd,pin,(size_t*)&inlen,pout,(size_t*)&outlen) == -1)
{
printf("iconv failed!\n");
perror("iconv");
}
iconv_close(cd);
printf("out= %s\n",&outbuf);
return outbuf - old;
}
int gb2312_utf8(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("gb2312","utf-8",inbuf,inlen,outbuf,outlen);
}
int utf8_gb2312(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("utf-8","gb2312",inbuf,inlen,outbuf,outlen);
}
int gb2312_ucs2(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("gb2312","ucs-2",inbuf,inlen,outbuf,outlen);
}
int ucs2_gb2312(char *inbuf,int inlen,char *outbuf,int outlen)
{
return code_convert("ucs-2","gb2312",inbuf,inlen,outbuf,outlen);
}
int open_port()
{
int fd; /* 端口文件描述符 */
fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open /dev/ttyS0 - ");
}
return (fd);
}
void set_port(int fd)
{
struct termios options;
int flag;
tcgetattr(fd, &options);
fcntl(fd, F_SETFL, 0);
cfsetispeed(&options, BAUDRATE);
cfsetospeed(&options, BAUDRATE);
options.c_cflag |= CREAD;
/* Set to 8N1 */
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
/* RAW */
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_oflag &= ~OPOST;
/* For read data from printer */
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 1;
flag = tcsetattr(fd, TCSANOW, &options);
if (flag < 0)
{
perror("set_port: Setting port /dev/ttyS0 error - ");
}
}
void close_port(int fd)
{
close(fd);
}
void print(int fd, char* sent)
{
int len = strlen(sent);
char ToGB2312[100] = {0x00};
utf8_gb2312(sent,strlen(sent),ToGB2312,len);
write(fd, &ToGB2312, strlen(ToGB2312));
}
void print_line(int fd, char* sent)
{
int len = strlen(sent);
char ToGB2312[100] = {0x00};
utf8_gb2312(sent,strlen(sent),ToGB2312,len);
ToGB2312[strlen(ToGB2312)] = 0x0a;
write(fd, &ToGB2312, strlen(ToGB2312));
}
//走纸
void feed(int fd, int num)
{
unsigned char ch_feed = 0x0a;
int i;
for (i=0;i<num;i++)
{
write(fd,&ch_feed,1);
}
}
//热敏打印机条码打印,输入的为数字
void barcode(int fd, char* bar)
{
char ins[3] = {0x1d, 0x6b, 0x45}; //指令和条形码类型
char hex_len[1]; //条形码长度
hex_len[0] = strlen(bar);
char ToGB2312[50] = {0x00};
char all[100] = {0x00};
write(fd,&ins,strlen(ins));
write(fd,&hex_len,1);
utf8_gb2312(bar,strlen(bar),ToGB2312,strlen(bar));
write(fd,&ToGB2312,strlen(ToGB2312));
}
int main(int argc, char *argv[])
{
int fd, i;
unsigned char convert[100] = {0x00};
fd = open_port();
set_port(fd);
print(fd,"我爱中国\x0A");
print_line(fd,"=======================");
print(fd,"Acuity\x0a");
barcode(fd,"1234567");
print_line(fd,"1234567");
feed(fd,5);
close_port(fd);
return 0;
}
插曲:在Linux执行该程序时,提示“invalid argument”,后面通过强大的优快云解决【2】。
4.处理方法3
将第二种方法移植,修改为C++描述,并置于Qt下,编译通过。但执行失败,在打开(iconv_open)iconv句柄时提示“invalid argument”,而此时的libc库已经更新,方法2可以成功执行,在Qt下为什么失败???继续换方法!后面想想,Qt应该有相关封装好的编码转换函数,果不其然,真的有,后面就轻松实现了。关键代码如下,打印内容文件依然需要保存UTF-8格式保存,字符参数采用Qt QString格式,或者C++的string都可以,比用C的char方便多了。
inline int UTF82GB2312(const QString &inStr,char *outbuf, int *outlen)
{
QTextCodec *gbk = QTextCodec::codecForName("GB2312");
QTextCodec *utf8 = QTextCodec::codecForName("UTF-8");
char *p;
QByteArray byte_utf = gbk->fromUnicode(inStr);
int byte_utf_size = byte_utf.size();
p = byte_utf.data();
while(byte_utf_size > 0)
{
byte_utf_size --;
*outbuf++ = *p++;
}
*outbuf = 0;
return 0;
}
//UTT-8 to GB2312
int peripherals::utf8_gb2312(char *inbuf, int inlen, char *outbuf, int outlen)
{
return code_convert("utf-8","gb2312",inbuf,inlen,outbuf,outlen); //此方式为调用处理方法2中的函数,但执行失败。
}
//UTT-8 to GB2312
int peripherals::utf8_gb2312(QString &instr, char *outbuf, int *outlen)
{
return UTF82GB2312(instr,outbuf,outlen);
}
//UTT-8 to GB2312
int peripherals::utf8_gb2312(QString &instr, char *outbuf, int *outlen)
{
return UTF82GB2312(instr,outbuf,outlen);
}
参考:
[1] http://blog.youkuaiyun.com/sealyao/article/details/5043138
[2] http://blog.youkuaiyun.com/violet089/article/details/51568667