热敏打印机是一个电子机械单元,一般用于打印小票。从编程的角度看,它主要有两个电控单元,步进电机和热打印头。
步进电机:是用来拖动打印纸走动的,一般我们用步进电机的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;}
以上的代码经过测试,可以基本满足要求。但由于马达步进时间与加热时间不是固定的,而我们的策略是边热,边打印,如何实现加热与马达步进之间的同步,是最大的问题,以上的代码虽然写成固定的,但在实际中的测试结果还能达到我们的要求。