目录
基于嘉立创开源项目进行开发:点击直达
功能
1.能够模拟作业帮旗下喵喵机的通信协议,通过手机app喵喵机(V6.2.80)进行蓝牙连接打印。
2.能够通过图片取模打印图片。
3.打印机进行打印时,LED灯快闪提示用户。打印完LED灯1s闪一次,提示用户打印机进入待机状态。
4.能够进行缺纸检测,缺纸时会进行电机微动提示用户。
5.能够进行电量检测并将结果发送给app端,能进行打印头温度检测,过热时自动停止加热并闪灯+电机微动。
6.能够进行按键出纸,按下按键打印机能够自动出纸。
开发环境
VScode + PlatFromIO插件 + ESP32 Dev Module + Arduino 框架
简单说一下开发界面
注意:由于该打印机上电后,针头会一直加热,为防止针头烧坏,先在工程中加入以下代码初始化针头停止加热,最后在setup中调用。
//6个打印头
#define PIN_STB1 14
#define PIN_STB2 27
#define PIN_STB3 26
#define PIN_STB4 25
#define PIN_STB5 33
#define PIN_STB6 32
//打印头电源升压控制引脚
#define PIN_VHEN 17
void Pin_Stb_Low()
{
pinMode(PIN_STB1, OUTPUT);
pinMode(PIN_STB2, OUTPUT);
pinMode(PIN_STB3, OUTPUT);
pinMode(PIN_STB4, OUTPUT);
pinMode(PIN_STB5, OUTPUT);
pinMode(PIN_STB6, OUTPUT);
digitalWrite(PIN_STB1, LOW);
digitalWrite(PIN_STB2, LOW);
digitalWrite(PIN_STB3, LOW);
digitalWrite(PIN_STB4, LOW);
digitalWrite(PIN_STB5, LOW);
digitalWrite(PIN_STB6, LOW);
}
void Init_Print()
{
Pin_Stb_Low();
// 初始化打印电源控制引脚、并关闭电源
pinMode(PIN_VHEN, OUTPUT);
digitalWrite(PIN_VHEN, LOW);
}
一、按键控制LED灯
目标:通过按键控制LED灯四种状态的切换。
状态1:亮
状态2:灭
状态3:慢闪
状态4:快闪
1.按键、LED初始化
原理图:
电路分析:LED灯为引脚18控制,引脚18输出低电平时亮。按键由引脚5控制,检测到引脚5为低电平时,此时按键按下。
LED和按键的初始化步骤:
// 按键和 LED 的引脚编号
#define LED 18
#define BUTTON 5
#define LED_ON() digitalWrite(LED, LOW)
#define LED_OFF() digitalWrite(LED, HIGH)
// 初始化 LED 和按键
void Led_Config(void)
{
// 配置 IO 口模式
pinMode(BUTTON, INPUT);
pinMode(LED, OUTPUT);
// 关灯
LED_OFF();
}
1.定义引脚18为LED,引脚5为BUTTON
2.定义LED_ON()为digitalWrite(LED, LOW),代表此时灯亮,定义LED_OFF()为digitalWrite(LED, HIGH),代表此时灯灭,方便后续操作。
3.配置引脚模式,引脚5连接按键(BUTTON),为了能够检测该引脚的高低电平,将该引脚配置为输入模式。引脚18连接LED灯,为了能够输出高电平控制LED灯的亮灭,将该引脚配置为输出模式。
4.为了保证代码的严谨性,配置完LED后随即将其关掉。
注意:单独写文件时,记得包含#include "Arduino.h"头文件。
2.按键控制LED灯状态
先写一个LED灯的闪烁
//闪烁
void LED_quick(int a)
{
LED_ON();
vTaskDelay(a);
LED_OFF();
vTaskDelay(a);
}
该函数通过改变a的值来改变LED灯的闪烁状态(快闪、慢闪)。
使用vTaskDelay主要是为了方便后续使用FreeRTOS整合该项目。
四种状态Switch转换:
/*
状态0:灯灭
状态1:灯亮
状态2:待机慢烁
状态3:打印快闪
*/
void Led_Status(int a)
{
switch (a)
{
case 0:
LED_OFF();
break;
case 1:
LED_ON();
break;
case 2:
LED_quick(1000);
break;
case 3:
LED_quick(50);
break;
}
}
最后加上一个按键控制,逻辑整合一下就可以实现了。
#include <Arduino.h>
#include "LED.h"
int a;
void setup()
{
Init_Print();
Led_Config();
int a=0;
}
void loop()
{
if(digitalRead(BUTTON)==0){
delay(10);
if(digitalRead(BUTTON)==0){
a++;
a%=4;
}
}
Led_Status(a);
}
二、步进电机控制
步进电机规格参数:
驱动方式:
驱动时序:
原理图:
通过步进电机的说明手册,我们可以得知该步进电机的驱动方式。
其中,比较关键的就是该步进电机的驱动时序,只要按照驱动时序来控制引脚输出高地电平,我们就能驱动该电机。
该电机由TC1508S芯片作为ESP32和 步进电机之间的桥梁进行控制。
引脚输出对应如下:
ESP32 | TC1508S | 步进电机 |
---|---|---|
IO23 | IN1 | M_A+(A) |
IO22 | IN2 | M_A-(Ā) |
IO21 | IN3 | M_B+(B) |
IO19 | IN4 | M_B-(B̄) |
1.电机初始化
由上可知,初始化电机时需配置IN1-IN4四个引脚为输出模式,然后将四个引脚配置为低电平。
// 定义步进电机引脚
#define PIN_MOTOR_AP 23 //M_A+ IN1
#define PIN_MOTOR_AM 22 //M_A- IN2
#define PIN_MOTOR_BP 21 //M_B+ IN3
#define PIN_MOTOR_BM 19 //M_B- IN4
void init_motor()
{
pinMode(PIN_MOTOR_AP, OUTPUT);
pinMode(PIN_MOTOR_AM, OUTPUT);
pinMode(PIN_MOTOR_BP, OUTPUT);
pinMode(PIN_MOTOR_BM, OUTPUT);
digitalWrite(PIN_MOTOR_AP, 0);
digitalWrite(PIN_MOTOR_AM, 0);
digitalWrite(PIN_MOTOR_BP, 0);
digitalWrite(PIN_MOTOR_BM, 0);
}
2.电机驱动
只需按照时序图控制四个引脚的高低电平输出即可:
//运行8步
void motor_run8(){
digitalWrite(PIN_MOTOR_AM, 1);
digitalWrite(PIN_MOTOR_BM, 0);
digitalWrite(PIN_MOTOR_AP, 0);
digitalWrite(PIN_MOTOR_BP, 1);
delay(2);
digitalWrite(PIN_MOTOR_AM, 0);
digitalWrite(PIN_MOTOR_BM, 0);
digitalWrite(PIN_MOTOR_AP, 0);
digitalWrite(PIN_MOTOR_BP, 1);
delay(2);
digitalWrite(PIN_MOTOR_AM, 0);
digitalWrite(PIN_MOTOR_BM, 0);
digitalWrite(PIN_MOTOR_AP, 1);
digitalWrite(PIN_MOTOR_BP, 1);
delay(2);
digitalWrite(PIN_MOTOR_AM, 0);
digitalWrite(PIN_MOTOR_BM, 0);
digitalWrite(PIN_MOTOR_AP, 1);
digitalWrite(PIN_MOTOR_BP, 0);
delay(2);
digitalWrite(PIN_MOTOR_AM, 0);
digitalWrite(PIN_MOTOR_BM, 1);
digitalWrite(PIN_MOTOR_AP, 1);
digitalWrite(PIN_MOTOR_BP, 0);
delay(2);
digitalWrite(PIN_MOTOR_AM, 0);
digitalWrite(PIN_MOTOR_BM, 1);
digitalWrite(PIN_MOTOR_AP, 0);
digitalWrite(PIN_MOTOR_BP, 0);
delay(2);
digitalWrite(PIN_MOTOR_AM, 1);
digitalWrite(PIN_MOTOR_BM, 1);
digitalWrite(PIN_MOTOR_AP, 0);
digitalWrite(PIN_MOTOR_BP, 0);
delay(2);
digitalWrite(PIN_MOTOR_AM, 1);
digitalWrite(PIN_MOTOR_BM, 0);
digitalWrite(PIN_MOTOR_AP, 0);
digitalWrite(PIN_MOTOR_BP, 0);
delay(2);
}
以上是该电机转动一个周期(8步)的示例。
每步中间的延时决定了每个步进的时间间隔,从而影响电机的步进频率。
delay(2) 表示每个步骤之间的间隔是 2 毫秒。这相当于 500 Hz 的频率(因为 1 / 0.002 = 500)。通过调整这个延时,你可以控制电机的转速。
学明白了以上代码和原理我们就可以优化代码并写点花的。
将电机的时序放在数组里
//步进电机节拍
uint8_t motor_table[8][4] = { //JX 系列
{1, 0, 0, 0},
{1, 0, 1, 0},
{0, 0, 1, 0},
{0, 1, 1, 0},
{0, 1, 0, 0},
{0, 1, 0, 1},
{0, 0, 0, 1},
{1, 0, 0, 1}
};
走一个周期
void motor_run()
{
digitalWrite(PIN_MOTOR_AP, motor_table[motor_pos][0]);
digitalWrite(PIN_MOTOR_AM, motor_table[motor_pos][1]);
digitalWrite(PIN_MOTOR_BP, motor_table[motor_pos][2]);
digitalWrite(PIN_MOTOR_BM, motor_table[motor_pos][3]);
motor_pos++;
if (motor_pos >= 8)
{
motor_pos = 0;
}
}
按指定步数走
//指定步数运行
void motor_run_step(uint32_t steps)
{
while (steps)
{
digitalWrite(PIN_MOTOR_AP, motor_table[motor_pos][0]);
digitalWrite(PIN_MOTOR_AM, motor_table[motor_pos][1]);
digitalWrite(PIN_MOTOR_BP, motor_table[motor_pos][2]);
digitalWrite(PIN_MOTOR_BM, motor_table[motor_pos][3]);
motor_pos++;
if (motor_pos >= 8)
{
motor_pos = 0;
}
delay(2);
steps--;
}
}
3.按键走纸
按键走纸实际上就是按下按键,步进电机开始转动,如下所示:
//按键走纸
void ButtonRun(){
if(digitalRead(BUTTON)==0){
delay(10);
if(digitalRead(BUTTON)==0){
motor_run_step(4);
}
}
}
三、图片取模打印图片
打印头原理
时序:
1.打印头初始化
由上图可知,该打印模块是由6个加热针头控制的。
每个针头控制8位,相当于每行传输48个数据,共384位。
数据的获取是靠SPI传输的,PIN_SCK(CLK )作为SPI 时钟线,PIN_SDA(DI)作为数据传输。因为是打印且仅有一个打印模块所以主机输入和片选线均无。
打印流程:
1.VH电源置为高电平。
2.SPI传输需要打印的数据。
3.LAT从低拉高1us锁存数据。
4.STB1~6开始按照数据逐个开启加热,加热时间越长,打印颜色越深。
5.加热完成后,就相当于打印了 一行,此时控制电机移动4步(该硬件设置的是移动4步相当于打印行高)。
6. 之后就是重复上面的打印步骤,重新开始下一行的SPI数据传输,锁存器锁存打印,直到所有数据打印完毕。
初始化spi
#include <myspi.h>
#include <spi.h>
#define PIN_SCK 15
#define PIN_SDA 13
SPIClass printerSPI = SPIClass(HSPI)
SPISettings print_SPI_Set = SPISettings(1000000, SPI_MSBFIRST, SPI_MODE0);
void init_spi()
{
myspi.begin(PIN_SCK, -1, PIN_SDA, -1);
myspi.setFrequency(2000000);
}
1.选总线,此处配置的为HSPI总线。
在 ESP32 上,HSPI 和 VSPI 是两个硬件 SPI 总线接口。每个总线接口都有不同的默认引脚:
HSPI(High-Speed SPI):通常使用 GPIO 14 (SCK), GPIO 12 (MISO), GPIO 13 (MOSI), 和 GPIO 15 (SS)。
VSPI(Very-Speed SPI):通常使用 GPIO 18 (SCK), GPIO 19 (MISO), GPIO 23 (MOSI), 和 GPIO 5 (SS)。
2.配SPI
创建一个 SPISettings 对象 print_SPI_Set,用于设置 SPI 配置:
1000000:SPI 的时钟频率设置为 1 MHz(1,000,000 Hz)。
SPI_MSBFIRST:指定数据传输时的字节顺序为最高位优先(MSB First)。
SPI_MODE0:指定 SPI 模式为 0(CPOL = 0, CPHA = 0),表示时钟极性和相位都为 0。
2.初始化参数
mySPI.begin() 用于初始化 SPI 总线。 SPI 时钟引脚 (SCK)、SPI 从输入引脚 (MISO)、SPI 主输出引脚 (MOSI)、和 SPI 片选引脚 (SS)。
PIN_SCK 和 PIN_SDA 是 SCK 和 MOSI 指定的引脚。-1 表示使用默认引脚,不指定 MISO 和 SS 引脚的情况。
setFrequency() 用于设置 SPI 总线的频率。这里设置为 2 MHz。
频率决定了数据传输的速度。2MHz 意味着 SPI 总线每秒可以传输 2,000,000 个时钟周期的数据。
最终初始化函数:
void init_printer()
{
// 初始化电机IO
init_motor();
// 初始化数据引脚、通道引脚
pinMode(PIN_LAT, OUTPUT);
pinMode(PIN_SCK, OUTPUT);
pinMode(PIN_SDA, OUTPUT);
// 加热通道全部关闭
Init_Print();
// 初始化打印电源控制引脚、并关闭电源
pinMode(PIN_VHEN, OUTPUT);
digitalWrite(PIN_VHEN, LOW);
// 初始化SPI
init_spi();
}
2.图片取模
该蓝牙打印机只能支持黑白两种颜色,所以我们需要先准备一个单色的图片,可以借助电脑上的取模软件PCtoLCD2002实现。
先找到想要打印的图片,用电脑画图软件打开,属性修改如下所示。
最后另存为BMP文件,保存好之后,通过取模软件打开,需要先选择取模软件的模式为图形模式,然后打开自己的文件。
文件正常打开之后还需要在配置中,对取模方式进行是的那个修改,点击齿轮图标进入设置模式,然后按照下图红框所示内容进行修改,修改完成之后点击保存。
最后将取模后的数组单独保存在文件中,总共是9600个数组。
3.图片打印
主要函数
void Print_Img()
{
uint8_t print_data[48];
uint32_t print_len = 48;
for (uint8_t i = 0; i < 200; i++)
{
set_img_Data(print_data, i + 1);
// 发数据
Send_Run(print_data, print_len);//
motor_run_step(4);
}
printf("图片打印完成!");
}
逻辑:每行打印48个数据,所以每次传输48个数据,总共9600个数据,所以循环200次。每次打印完一行后,步进电机跑4步,相当于一行。循环往复最终就能将照片打印出来了。
其中包含的其他函数:
#define PRINT_TIME 2000 //打印加热时间
#define PRINT_END_TIME 10 //冷却时间
#define LAT_TIME 1 //数据锁存时间
void start_send_data(uint8_t *data, uint32_t len)
{
uint32_t offset = 0;
uint8_t *ptr = data;
while (1)
{
if (len > offset)
{
// 发送一行数据 48byte*8=384bit
send_one_line_data(ptr);
// 每次偏移48
offset += 48;
ptr += 48;
}
//下面就是根据上面传输的48位数据进行加热针头,一个一个加热然后冷却
run_stb(0);
run_stb(1);
run_stb(2);
run_stb(3);
run_stb(4);
run_stb(5);
break;
}
}
static void set_img_Data(uint8_t *print_data, uint8_t a)//分割图片取模代码,48一组
{
for (uint32_t index = 0; index < 48; ++index)
{
print_data[index] = img_data[index + 48 * a];
}
}
static void send_one_line_data(uint8_t *data)//发送第一行代码
{
spiCommand(data, 48);
digitalWrite(PIN_LAT, LOW);//这个地方是数据锁存拉低锁存,拉高清除
delayMicroseconds(LAT_TIME);
digitalWrite(PIN_LAT, HIGH);
}
void spiCommand(uint8_t *data_buffer, uint8_t data_len)//SPI发送数据
{
myspi.beginTransaction(print_SPI_Set);
myspi.transfer(data_buffer, data_len);
myspi.endTransaction();
}
void run_stb(uint8_t stb_num)//单个引脚加热,冷却
{
switch (stb_num)
{
case 0:
digitalWrite(PIN_STB1, 1);
delayMicroseconds(5500);//这个地方单独设置5500是因为我的针头1被我玩坏了,所以需要加热时间长一点,正常的话填PRINT_TIME 加热2000us即可
digitalWrite(PIN_STB1, 0);
delayMicroseconds(PRINT_END_TIME);
break;
case 1:
digitalWrite(PIN_STB2, 1);
delayMicroseconds(PRINT_TIME);
digitalWrite(PIN_STB2, 0);
delayMicroseconds(PRINT_END_TIME);
break;
case 2:
digitalWrite(PIN_STB3, 1);
delayMicroseconds(PRINT_TIME);
digitalWrite(PIN_STB3, 0);
delayMicroseconds(PRINT_END_TIME);
break;
case 3:
digitalWrite(PIN_STB4, 1);
delayMicroseconds(PRINT_TIME);
digitalWrite(PIN_STB4, 0);
delayMicroseconds(PRINT_END_TIME);
break;
case 4:
digitalWrite(PIN_STB5, 1);
delayMicroseconds(PRINT_TIME);
digitalWrite(PIN_STB5, 0);
delayMicroseconds(PRINT_END_TIME);
break;
case 5:
digitalWrite(PIN_STB6, 1);
delayMicroseconds(PRINT_TIME);
digitalWrite(PIN_STB6, 0);
delayMicroseconds(PRINT_END_TIME);
break;
}
}