科技不总是冷冰冰,智能便携打印机让文字更有温度!——嵌入式功能实现篇
Posted 三明治开发社区
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了科技不总是冷冰冰,智能便携打印机让文字更有温度!——嵌入式功能实现篇相关的知识,希望对你有一定的参考价值。
简介:利用Wi-Fi&BLE云模组让普通打印机实现App端输入文字即可实时&远程打印。
1、功能需求
基本打印流程:
(1)用户通过涂鸦App完成设备配网。
(2)将需要打印的文字或者简单图片输入文本框。
(3)输入完成后APP将文本框中的内容全部转换成bmp单色位图存储到云端,并将图片的url给到App。
(4)分别将第一段url和第二段url下发到设备,设备端将url拼接。
(5)设置打印份数后点击开始打印。
(6)设备收到打印命令后访问url,下载图片到模组后开始打印。
(7)返回打印结果(成功/失败)。
功能名称 | 功能详解 |
---|---|
打印份数 | 设置具体要打印的份数,默认一份 |
生成第一段链接 | APP生成的图片会存储到云端,以url形式给到设备,url长度超过255个字节,由于IoT平台string类型DP最多只能容纳255个字节,因此需要两个DP |
生成第二段链接 | 将得到的两组url拼接成完整的url,设备访问该url将图片下载到模组 |
开始打印 | 设置好打印份数,下发打印命令,设备开始打印 |
正在打印份数 | 根据设置的打印份数,设备会上报当前正在打印的份数在APP显示 |
缺纸报警 | 检测到打印机无纸时无法打印,并上报缺纸状态到APP |
电量显示 | 电量以10%间隔显示在APP |
低电量报警 | 电量在低于10%时APP电量报警 |
打印结果 | |
打印状态 | 打印成功、打印失败(缺纸、电池电量不足、图片拉去失败) |
2、环境搭建
(1)开发环境搭建可以参考Wi-Fi 模组二次开发教程——1. SoC开发环境搭建。如果已经有虚拟机和乌班图的开发环境可直接跳至4.2 下载编译依赖工具
处进行剩余环境搭建。
(2)产品创建可以参考Wi-Fi模组二次开发教程——2. 涂鸦IoT平台介绍。由于IoT平台品类中没有打印机,所以选择找不到品类?。默认自定义方案,填入产品名称和产品型号,通讯协议默认Wi-Fi&蓝牙,创建产品后添加所需的功能点。
功能点定义完成后选择设备面板为自由配置面板,其他步骤均可参考Wi-Fi模组二次开发教程——2. 涂鸦IoT平台介绍完成产品创建。
(3)参考Wi-Fi模组二次开发课程——3. 快速上手来完成代码修改编译、固件上传、获取token、烧录授权和设备配网。
3、功能实现
3.1 打印功能
打印是打印机最重要的功能点,其他所有的功能点都是在此基础上实现的。通过步进电机和热敏头的协同工作来实现字符和图片的打印。
打印头与涂鸦Wi-Fi&BLE双模模组之间通过SPI进行数据通信,具体打印步骤为:
(1)模组每次向热敏头发送48位数据。
(2)热敏头接收到数据后在CLK信号的上升沿将数据传输到移位寄存器。
(3)一行(48位)数据传输完成后将/LAT信号拉低紧接着拉高把数据存储到锁存寄存器中。
(4)将DST信号拉高激活打印元件,接着使步进电机转动两步完成一点行的打印。
(5)在电机转过两步后要及时将DST信号拉低,长时间加热会损坏加热元件甚至冒烟。
步进电机的步长为0.01325mm,一点行的宽度为0.0625mm,因此打印出一点行的数据需要步进电机转两步。DST信号激活频率为步进电机每转两步激活一次。
打印时序图如下:
步进电机时序图如下:
打印功能部示例代码:
/**
* @function:set_motor_phases
* @brief: set motor phases
* @param[in]: phase[4]
* @return: none
*/
STATIC VOID set_motor_phases(CONST UINT8_T phase[4])
{
tuya_gpio_write(PH1, phase[0]);
tuya_gpio_write(PH2, phase[1]);
tuya_gpio_write(PH3, phase[2]);
tuya_gpio_write(PH4, phase[3]);
}
/**
* @function:idle_motor
* @brief: idel status no power consumption
* @param[in]: none
* @return: none
*/
VOID idle_motor(VOID)
{
if (isIdle) {
return;
}
CONST UINT8_T idle_phase[] = {0, 0, 0, 0};
set_motor_phases(idle_phase);
isIdle = TRUE;
}
/**
* @function:set_motor_step
* @brief:
* @param[in]: none
* @return: none
*/
VOID set_motor_step(VOID)
{
isIdle = false;
CONST INT_T totalSteps = ARRAY_SIZE;
currStep = (currStep + totalSteps + 1) % totalSteps;
set_motor_phases(motor_phases[currStep]);
}
/**
* @function:tuya_motor_feedPaper_line
* @brief: Feed paper for `count` lines
* @param[in]: count -> print line num
* @param[in]: direction: FORWARD->forward BACKWARD->backward
* @param[in]: speed: Adjust the motor speed unit:ms
* @return: none
*/
VOID tuya_motor_feedPaper_line(UINT_T count, INT8_T direction, UINT8_T speed)
{
INT_T i;
/* out of paper or cuont num is 0 return */
if (1 == tuya_TmlHead_out_of_paper_alarm() || 0 == count) {
return;
}
CONST UINT_T stepsPerLine = ARRAY_SIZE / 2;
for (i = 0; i < (stepsPerLine * count); i++) {
tuya_motor_set_motor_step(direction);
tuya_hal_system_sleep(speed); // delay 2ms unit:ms
}
}
/**
* @function:begin_print_line
* @brief: send 1bpp data to printhead and begin heating
* @param[in]: data -> The data to be printed
* @return: none
*/
STATIC VOID begin_print_line(UINT8_T* data)
{
if (1 == out_of_paper_alarm()) {
return;
}
bk_spi_master_dma_send(&g_spi_msg);
tuya_hal_system_sleep(5);
tuya_gpio_write(PRINT_DST, FALSE);
tuya_gpio_write(PRINT_LAT, FALSE);
tuya_gpio_write(PRINT_LAT, TRUE);
tuya_gpio_write(PRINT_DST, TRUE);
}
/**
* @function:end_printLine
* @brief:end heating
* @param[in]: none
* @return: none
*/
STATIC VOID end_printLine(VOID)
{
tuya_gpio_write(PRINT_DST, FALSE);
}
/**
* @function: print_1bLine
* @brief: Print one line of data
* @param[in]: data -> Data to be printed
* @return: none
*/
STATIC VOID print_1bLine(UINT8_T* data)
{
if (1 == out_of_paper_alarm()) {
return;
}
begin_print_line(data);
motor_feedPaper_line(1, FORWARD, 2); // The actual number of steps is 2*Param
end_printLine();
}
要合理控制步进电机送纸速度,速度过快容易走不动纸,速度过慢加热头会对热敏纸同一个点长时间加热使热敏纸颜色由黑变灰甚至变白,影响打印效果。
3.2 打印份数
打印份数默认打印一份,在APP上设置参数可调整打印份数。由于步进电机和热敏头不能长时间连续工作,因此打印份数不宜设置过多,否则容易烧坏电机和热敏头。
3.3 生成第一段链接 & 生成第二段链接
由于平台受限,url长度超过255个字节,string类型DP最多只能容纳255个字节,因此需要两个DP。url在代码中本质为字符串。APP将两端url下发到设备后再将得到的两组url拼接成完整的url,设备访问该url将图片下载到模组。
case PRINT_NUM_DPID:
bmp_info.paper_num = root->value.dp_value;
break;
case CREATE_LINK1_DPID:
memset (bmp_info.first_url, 0, 255);
memcpy(bmp_info.first_url, root->value.dp_str, strlen(root->value.dp_str));
break;
case CREATE_LINK2_DPID:
memset (bmp_info.second_url, 0, 255);
memcpy(bmp_info.second_url, root->value.dp_str, strlen(root->value.dp_str));
break;
3.4 开始打印 & 正在打印的份数
设备收到打印命令后访问url下载图片到模组后开始打印。由于模组RAM有限,剩余空间只有40k左右,而需要打印的图片很可能超过了40k,因此采用一边拉取部分图片一边打印的方式完成打印。打印份数bmp_info.paper_num
控制for循环的次数,bmp_info.print_num
即为当前正在打印的份数,将print_num
实时上报到APP即可显示当前正在打印的份数。
开始打印部分代码:
CHAR_T *image_url = (CHAR_T *)malloc(512*sizeof(CHAR_T));
strcpy(image_url, "https://storage-proxy.tuyacn.com:7779/dst=");
tuya_hal_semaphore_wait(pv_handle);
strcat(image_url, bmp_info.first_url);
strcat(image_url, bmp_info.second_url);
for (bmp_info.print_num = 0; bmp_info.print_num < bmp_info.paper_num; bmp_info.print_num++) {
iot_httpc_download_file(image_url, PULL_SIZE, iot_download_image_cb, NULL, bmp_info.total_len, file_hmac);
tuya_bmp_print_num_update(++bmp_info.print_num);
}
3.5 缺纸报警
打印机内部采用一个反射性光电通断侦测传感器,当缺纸时,光电侦测发出的光无法被反射,输出高电平。当纸张正常,光电侦测发出的光被反射,由接收管接收,输出低电平。将输出的电平值实时上报到APP。当缺纸时,不能启动加热头和电机。
缺纸检测部分代码:
/**
* @function: out_of_paper_alarm
* @brief: detection printer have paper
* @param: none
* @return: ALARM-->out of paper NORMAL-->have a paper
* @others: none
*/
UCHAR_T out_of_paper_alarm(VOID)
{
if (NO_PAPER == tuya_gpio_read(PAPER_SENSOR)) {
return PAPER_ALARM;
} else {
return PAPER_NORMAL;
}
}
dp_arr[2].dpid = OUT_OF_PAPER_DPID;
dp_arr[2].type = PROP_BOOL;
dp_arr[2].time_stamp = 0;
dp_arr[2].value.dp_bool = out_of_paper_alarm();
3.6 电量显示 & 低电量报警
打印机采用7.4v可充电锂电池供电,采用ADC采集电池端的电压,显示10%、20%、…、90%、100%电量值,当电池电压等于10%时上报APP,并且红灯亮起,提醒用户充电。大于10%时绿灯常亮。该任务放在单独的线程中每3s采集一次电池电压,并上报电量值。
ADC_ELECTRICITY
处分压后电压为2.1v。
部分示例代码:
/**
* @function:tuya_BatMon_BatStatus
* @brief: Battery level display
* @param[in]: none
* @return: none
*/
VOID tuya_BatMon_BatStatus(VOID)
{
UINT16_T battery_val = 0;
BOOL_T bat_alarm = 0;
while (1) {
battery_val = tuya_BatMon_BatVal_get(2400, 4096, 4, 400);
if (battery_val <= precent_10) {
bat_alarm = 1;
tuya_BatMon_ch443k_toggle(BAT_LED_PIN, FALSE);
tuya_gpio_write(BAT_LED_PIN, FALSE);
} else {
bat_alarm = 0;
tuya_BatMon_ch443k_toggle(BAT_LED_PIN, TRUE);
tuya_gpio_write(BAT_LED_PIN, TRUE);
}
if (battery_val >= precent_100) {
vlotage_percent = _100p;
} else if (battery_val < precent_100 && battery_val >= precent_90) {
vlotage_percent = _90p;
} else if (battery_val < precent_90 && battery_val >= precent_80) {
vlotage_percent = _80p;
} else if (battery_val < precent_80 && battery_val >= precent_70) {
vlotage_percent = _70p;
} else if (battery_val < precent_70 && battery_val >= precent_60) {
vlotage_percent = _60p;
} else if (battery_val < precent_60 && battery_val >= precent_50) {
vlotage_percent = _50p;
} else if (battery_val < precent_50 && battery_val >= precent_40) {
vlotage_percent = _40p;
} else if (battery_val < precent_40 && battery_val >= precent_30) {
vlotage_percent = _30p;
} else if (battery_val < precent_30 && battery_val >= precent_20) {
vlotage_percent = _20p;
} else {
vlotage_percent = _10p;
}
/* Voltage detection frequency */
tuya_hal_system_sleep(CKECK_TIME);
tuya_update_Bat_Val_dp(bat_alarm);
}
}
e if (battery_val < precent_50 && battery_val >= precent_40) {
vlotage_percent = _40p;
} else if (battery_val < precent_40 && battery_val >= precent_30) {
vlotage_percent = _30p;
} else if (battery_val < precent_30 && battery_val >= precent_20) {
vlotage_percent = _20p;
} else {
vlotage_percent = _10p;
}
/* Voltage detection frequency */
tuya_hal_system_sleep(CKECK_TIME);
tuya_update_Bat_Val_dp(bat_alarm);
}
}
以上是关于科技不总是冷冰冰,智能便携打印机让文字更有温度!——嵌入式功能实现篇的主要内容,如果未能解决你的问题,请参考以下文章