热敏打印机是一个电子机械单元,一般用于打印小票。从编程的角度看,它主要有两个电控单元,步进电机和热打印头。
步进电机:是用来拖动打印纸走动的,一般我们用步进电机的H桥的驱动即可。 热打印头:热打印头上是很多并排成一条线的加热点,当通电时,该加热点发热,从而使热敏纸显示出颜色。电子驱动部如下图所示:
操作流程是: 通过数据口写入数据 -> 锁存数据 -> STROBE控制为高 ->延迟一段时间 -> STROBE控制为低。 这里的延迟时间就是加热时间。
热敏打印机驱动的要点:
- 打印要流畅,速度快,不卡顿,噪音小;
- 打出的字颜色要均匀,字体要匀称;
- 要有保护措施:缺纸检测,不会出现长时间对一个点的加热,对供电要求低。
难点
一. 防止加热电流过大 由于一行的加热点通常有三四百个,为了打印速度快,每个点的电流都较大,如果这一行的点都要打印加热,会导致电流会很大,所以策略是一行的点要分批次加热打印,函数如下:
/*
brief: 函数功能:data_buff为要打印的数据,从中可以顺序取出打印的数据,并统计出数据中累计bit ‘1’的个数,即是加热的点数,当点数累计到一定数量后,即停止取出数据,这些数据则为本次可以加热打印的数据。
return: 打印数据的个数
pdot_num 打印数据的总点数;
*/
int Printer_GetData(uint8_t *data_buff, int buff_len, uint32_t *pdot_num) {
int len;
int dot_num;
//0 ~ 255, 每个数据中 ‘1’ 的个数,如 0x03 中有2个‘1’
const unsigned char bit_num_table[256] ={
0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8,
};
len = 0;
dot_num = 0;
while(dot_num < PRT_HEAT_MAX_POINT){
dot_num = bit_num_table[data_buff[len]];
len ;
if(len >= buff_len){
break;
}
}
*pdot_num = dot_num;
return len;
}
二. 马达的速度控制 为了加快打印速度,我们都希望马达以最高的速度运转,但是由于惯性原因,如果一上来就是最大的速度,这会导致步进马达失步,从而影响打印效果,所以控制马达需要有一个加速的过程。
//马达加速表
//由于惯性原因,马达一上来,不可能就达到最大速度,需要一个加速过程
//加速表由打印机厂商提供
const uint32_t add_speed_table[] = {
4300, 2600, 2500, 2420, 2270, 2140, 2030, 1940,
1860, 1780, 1720, 1660, 1610, 1560, 1510, 1470,
1439, 1402, 1372, 1342, 1313, 1287, 1261, 1238,
1213, 1194, 1174, 1155, 1136, 1119, 1102, 1086,
1071, 1056, 1042, 1029, 1012, 1003, 991 , 979,
968, 957, 947, 936, 927, 917, 908
};
//para: speed 当前马达速度,
//return: 返回下一步马达的速度
uint32_t Printer_MotorSpeed(uint32_t speed) {
uint32_t next_speed;
uint32_t max_speed;
int i;
next_speed = add_speed_table[0];
//最小速度
if((speed == 0) || (speed > add_speed_table[0])){
next_speed = add_speed_table[0];
goto Label;
}
//最大速度
max_speed = add_speed_table[sizeof(add_speed_table) / 4 - 1];
if(speed <= max_speed) {
next_speed = max_speed;
goto Label;
}
//表中查下一个速度
for(i = 0; i < sizeof(add_speed_table) / 4 - 1; i ) {
if((speed <= add_speed_table[i]) && (speed > add_speed_table[i 1])) {
next_speed = add_speed_table[i 1];
}
}
Label:
return next_speed;
}
整个程序控制流程:
typedef struct _Printer_Work_t {
uint8_t work; //打印机是否开始工作, 系统上电
uint32_t step; //步进电机, 电机停止后,从0开始
uint32_t speed; //电机当前速度
} Printer_Work_t;
Printer_Work_t prt_work;
//printer data buffer, 参数我的文章circle buffer的实现
CBuff_t prt_buff;
//初始化
int Printer_Init(void) {
static uint8_t buff[24000];
CBuff_Init(&prt_buff, buff, sizeof(buff));
//硬件初始化
Printer_HwInit();
Printer_PowerOn(0);
Printer_Strobe(0);
Printer_MotorStop();
return 0;
}
int Printer_Task(void *arg) {
int ret;
uint8_t data_buff[PRT_LINE_BYTES_MAX];
static uint32_t s_time = 0;
int len;
uint32_t heat_time;
if(prt_work.work == 1) {
//一段时间没开始工作,断电, 停止工作
//由于需要打印的数据,是连续下发下来的
if(IS_TIME_OUT(s_time, 1200)) {
//停止
CBuff_Clean(&prt_buff);
prt_work.work = 0;
Printer_PowerOn(0);
Printer_Strobe(0);
Printer_MotorStop();
prt_work.step = 0; //马达位置
prt_work.speed = 0; //马达速度为 0
}
}
//是否有数据
if(Printer_HasData() < 0){
return 0;
}
//收到数据,开始打印
if(prt_work.work == 0){
Printer_PowerOn(1); //开电源
Timer_DelayMs(50); //保证电源稳定下来
prt_work.work = 1;
}
//检测纸张
if(Printer_PaperDetect(0) == 0) {
CBuff_Clean(&prt_buff);
return -1;
}
s_time = Timer_Ticks();
//加热时间
heat_time = Printer_HeatTime(0, 0);
while(Printer_HasData() > 0){
uint32_t point;
uint32_t dot_num;
uint32_t motor_start_time;
uint32_t heat_start_time;
uint8_t *pdata;
//读出一行数据
ret = CBuff_Read(&prt_buff, data_buff, PRT_LINE_BYTES_MAX);
if(ret != PRT_LINE_BYTES_MAX){
break;
}
//打印一行
point = 0;
Printer_Strobe(1);
while(point < PRT_LINE_BYTES_MAX){
//取数据
len = Printer_GetData(&data_buff[point], PRT_LINE_BYTES_MAX - point, &dot_num);
if(len <= 0){
break;
}
//写数据
ret = Printer_WriteDot(point, &data_buff[point], len);
if(ret < 0){
break;
}
point = len;
if(dot_num > 0) {
//有点,加热
Timer_DelayUs(heat_time);
}
}
//马达 4步一行点阵?
prt_work.speed = Printer_MotorSpeed(prt_work.speed, heat_time);
prt_work.step = Printer_MotorStep(prt_work.step);
Timer_DelayUs(prt_work.speed);
prt_work.speed = Printer_MotorSpeed(prt_work.speed, heat_time);
prt_work.step = Printer_MotorStep(prt_work.step);
Timer_DelayUs(prt_work.speed);
Printer_Strobe(0);
prt_work.speed = Printer_MotorSpeed(prt_work.speed, heat_time);
prt_work.step = Printer_MotorStep(prt_work.step);
Timer_DelayUs(prt_work.speed);
prt_work.speed = Printer_MotorSpeed(prt_work.speed, heat_time);
prt_work.step = Printer_MotorStep(prt_work.step);
Timer_DelayUs(prt_work.speed);
}
//Printer_PowerOn(0);
//prt_work.work = 0; //防止电源来回并关
Printer_Strobe(0); //停止加热
//Printer_MotorStop(); //马达停止
return 0;
}
以上的代码经过测试,可以基本满足要求。但由于马达步进时间与加热时间不是固定的,而我们的策略是边热,边打印,如何实现加热与马达步进之间的同步,是最大的问题,以上的代码虽然写成固定的,但在实际中的测试结果还能达到我们的要求。