DIY掌上POS机,或许是最小的收银POS机了!
Posted 铁熊玩创客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DIY掌上POS机,或许是最小的收银POS机了!相关的知识,希望对你有一定的参考价值。
本文作者:默 & 铁熊
在这个共享经济时代,万物皆可共享,这其中包括了共享单车、共享充电宝、共享雨伞等服务。所有这些共享经济产品的背后,都用到了扫码支付相关的技术。
作为个人 DIY 玩家,有没有办法在自己的作品上增加扫码支付相关的功能呢?比如通过扫码支付控制设备实现不同的功能呢。想法虽然美好,但现实却是残酷的,因为大多数支付接口并不针对个人开放,个人想要实现扫码支付效果,一般只能通过第三方服务实现。本教程中,我将介绍一种第三方服务:奇迹码支付(http://pay.qj61.cn/login),它是一个独立开发者个人即时到账收款平台,可以通过监测手机通知从而验证支付状态,验证支付成功之后,我们就可以 DIY 各种扫码付费项目啦。
本教程中,我给大家制作了一个掌上 POS 机为大家演示扫码收款功能,效果如下:
Arduino实现全网最迷你收米工具
在这个项目中,我们实现了类似 POS 机的效果,不仅可以设置收款金额,而且还可以选择收款方式,比如支付宝、微信、QQ等,用户扫码后就可以实现支付。
预期目标及功能
- 触摸键盘功能;
- 支付图标显示;
- 支付方式选择;
- 二维码生成;
- 网络状态反馈;
- 触摸震动反馈;
所用硬件
-
M5Core2 模块
M5Core2 具有如下特点: -
基于 ESP32 开发,支持 WiFi,蓝牙;
-
16M 闪存,8M PSRAM;
-
内置扬声器,电源指示灯,震动马达,RTC,I2S 功放,电容式触摸屏,电源键,复位键;
-
TF 卡插槽(支持最大 16GB);
-
内置锂电池,配备电源管理芯片;
-
独立小板内置 6 轴 IMU,PDM 麦克风;
-
M-Bus 总线插座。
程序设计
下面开始详细讲解程序设计过程。
开发环境
我们使用 Arduino IDE 来编写本项目的程序,上传程序时开发板选择 M5Stack-Core2,编程过程中需要用到的软件及库,将会打包作为附件给大家下载,详见文末下载说明。
程序思路
为了实现项目的所有功能,我们先根据预期的目标绘制思维导图,再根据思维导图逐步实现自制 POS 结算终端机的功能。
下面我们将具体讨论自制结算终端的各个子功能是如何实现的。
触摸按键测试程序
我们想要使用触摸屏实现金额的输入以及支付方式的选择,离不开设计触摸按键。M5Core2 为我们提供了成熟的解决方案,我们能够轻易地绘制一个按键,并且设置指定的区域,触摸按键的使用示例如下:
#include <M5Core2.h>
ButtonColors on_clrs = {YELLOW, WHITE, WHITE};
ButtonColors off_clrs = {BLACK, WHITE, WHITE};
Button tl(0, 0, 0, 0, false , "Button", off_clrs, on_clrs, MC_DATUM);
void setup() {
M5.begin();
M5.Buttons.addHandler(eventDisplay, E_ALL - E_MOVE);
doButtons();
}
void loop() {
M5.update();
}
void doButtons() {
uint8_t but_w = 100;
uint8_t but_h = 60;
tl.set(110, 90, but_w, but_h); // 设置按键的显示坐标以及长和宽
M5.Buttons.draw();
}
void eventDisplay(Event& e) {
Serial.printf("%-12s finger%d %-18s (%3d, %3d) --> (%3d, %3d) ",
e.typeName(), e.finger, e.objName(), e.from.x, e.from.y,
e.to.x, e.to.y);
Serial.printf("( dir %d deg, dist %d, %d ms )\\n", e.direction(),
e.distance(), e.duration);
}
其中:
-
ButtonColors on_clrs = {YELLOW, WHITE, WHITE}
定义了按键按下时的颜色以及按键框的颜色; -
ButtonColors off_clrs = {BLACK, WHITE, WHITE}
定义了按键释放时的颜色以及按键框的颜色; -
Button tl(0, 0, 0, 0, false , "Button", off_clrs, on_clrs, MC_DATUM)
定义了按键的显示文本以及文本显示方式; -
doButtons()
函数绘制了按钮; -
eventDisplay(Event& e)
函数用来侦测屏幕触摸事件。
如果你想要实现更多个性化设置,请参考 M5Core2.h
库文件进行设置。
触摸按键效果测试
上传上面的测试程序,打开串口监视器,点击 M5Core2 屏幕上的触摸按键可看到下图所示内容:
可以看到:
-
当点击程序定义的触摸按键时,串口会返回按键的标签字符串 Button 以及按下的持续时间以及坐标区域;
-
当点击未被程序设置的区域时,返回的字符串是 background;
-
当点击触摸屏上的另外三个默认触摸按键时,返回 BtnA,BtnB 或 BtnC。
这里我们重点关注串口打印的 e.typeName()
(触发类型)和 e.objName()
(触发按键名),后面我们将重点利用这两个返回值,可以根据返回值区分我们按下的每一个按键。触发类型我们关注 E_RELEASE
这个返回值,该字符串代表了按键被释放,可以用来检测按键是否点击结束。
主界面 UI 设计
知道了如何利用程序定义一个触摸按钮之后,接下来我们来设计该主界面的 UI。根据前面所学的按钮绘制以及定义方法,绘制出所有数字输入按键、支付按键、清除按键以及确认按键,结合圆角矩形绘制函数 M5.Lcd.drawRoundRect()
绘制出主界面,主界面设计如下:
图标显示
显示图标有两种方式,第一种方式是单色图标,第二种方式是彩色图标。单色图标可以直接使用 M5.Lcd.drawXBitmap()
函数绘制,彩色图标则可以使用 M5.Lcd.drawBitmap()
函数进行绘制。本项目中,单色图标有 WiFi 图标以及货币的符号 ¥。
对于单色图标,可以通过取模软件 Image2Lcd 取模。在 Image2Lcd 软件中,选择需要取模的图片,根据自己的屏幕类型,调整取模方式、取模大小、亮度,最后导出取模数据。设置如下:
对于货币符号 ¥ 我们使用 Mixly 软件中的取模工具获取字模,取模设置如下:
对于彩色图片,可以使用 ImageConverter 软件获取彩色图片取模数据,其界面如下,选择需要取模的图片并调整其大小,最后导出为 C 语言取模数据即可:
按键功能以及 UI 设计
现在根据前面的按键 UI 设计示例、以及图像显示函数设计出按键的处理程序,我们先定义一个输入字符串变量 Input_data
代表输入的字符串,当我们按下数字按键以及小数点时对输入的字符进行连接,按下删除按键 DEL 删除 Input_data 的最后一个字符,按下对应的支付方式时显示对应的图标,具体程序如下:
#include <M5Core2.h>
extern const unsigned short success_icon[0x125C0];
extern const unsigned short failure_icon[0xE100];
extern const unsigned char currency[0x78];
extern const unsigned short QQ_icon[0x3A2];
extern const unsigned short WX_icon[0x384];
extern const unsigned short ZFB_icon[0x384];
extern const unsigned char wifi[0x78];
String Input_data;
ButtonColors on_clrs = {YELLOW, WHITE, WHITE};
ButtonColors off_clrs = {BLACK, WHITE, WHITE};
Button tl(0, 0, 0, 0, false , "7", off_clrs, on_clrs, MC_DATUM);
Button t2(0, 0, 0, 0, false, "8", off_clrs, on_clrs, MC_DATUM);
Button t3(0, 0, 0, 0, false, "9", off_clrs, on_clrs, MC_DATUM);
Button t4(0, 0, 0, 0, false, "QQ", off_clrs, on_clrs, MC_DATUM);
Button t5(0, 0, 0, 0, false , "4", off_clrs, on_clrs, MC_DATUM);
Button t6(0, 0, 0, 0, false, "5", off_clrs, on_clrs, MC_DATUM);
Button t7(0, 0, 0, 0, false, "6", off_clrs, on_clrs, MC_DATUM);
Button t8(0, 0, 0, 0, false, "WX", off_clrs, on_clrs, MC_DATUM);
Button t9(0, 0, 0, 0, false , "1", off_clrs, on_clrs, MC_DATUM);
Button t10(0, 0, 0, 0, false, "2", off_clrs, on_clrs, MC_DATUM);
Button t11(0, 0, 0, 0, false, "3", off_clrs, on_clrs, MC_DATUM);
Button t12(0, 0, 0, 0, false, "ZFB", off_clrs, on_clrs, MC_DATUM);
Button t13(0, 0, 0, 0, false , "0", off_clrs, on_clrs, MC_DATUM);
Button t14(0, 0, 0, 0, false, ".", off_clrs, on_clrs, MC_DATUM);
Button t15(0, 0, 0, 0, false, "DEL", off_clrs, on_clrs, MC_DATUM);
Button t16(0, 0, 0, 0, false, "CON", off_clrs, on_clrs, MC_DATUM);
void setup() {
M5.begin();
M5.Lcd.fillScreen(BLACK); // 设置背景颜色
M5.Buttons.addHandler(eventDisplay, E_ALL - E_MOVE); // 注册按键动作检测
M5.Lcd.drawRoundRect(0, 0, 315, 43, 5, WHITE); // 绘制显示文本框
M5.Lcd.drawXBitmap(10, 6, currency, 30, 30, WHITE); // 显示货币图标
M5.Lcd.setTextColor(WHITE, BLACK); // 设置显示文本颜色
M5.Lcd.setTextSize(2); // 设置显示字体大小
M5.Lcd.drawBitmap(262, 6, 30, 30, WX_icon); // 显示默认微信图标
doButtons(); // 设置按钮属性及绘制按钮
}
void loop() {
M5.update();
}
void doButtons() {
uint8_t but_w = 75;
uint8_t but_h = 43;
tl.set(0, 48, but_w, but_h);
t2.set(80, 48, but_w, but_h);
t3.set(160, 48, but_w, but_h);
t4.set(240, 48, but_w, but_h);
t5.set(0, 96, but_w, but_h);
t6.set(80, 96, but_w, but_h);
t7.set(160, 96, but_w, but_h);
t8.set(240, 96, but_w, but_h);
t9.set(0, 144, but_w, but_h);
t10.set(80, 144, but_w, but_h);
t11.set(160, 144, but_w, but_h);
t12.set(240, 144, but_w, but_h);
t13.set(0, 192, but_w, but_h);
t14.set(80, 192, but_w, but_h);
t15.set(160, 192, but_w, but_h);
t16.set(240, 192, but_w, but_h);
M5.Buttons.draw();
}
void eventDisplay(Event& e) {
Serial.printf("%-12s finger%d %-18s (%3d, %3d) --> (%3d, %3d) ",
e.typeName(), e.finger, e.objName(), e.from.x, e.from.y,
e.to.x, e.to.y);
Serial.printf("( dir %d deg, dist %d, %d ms )\\n", e.direction(),
e.distance(), e.duration);
if (String(e.objName()).equals(String("BtnA"))) {
// 执行BtnA事件
delay(50);
} else {
if (String(e.objName()).equals(String("BtnB"))) {
// 执行BtnB事件
delay(50);
} else {
if (String(e.objName()).equals(String("BtnC"))) {
// 执行BtnC事件
delay(50);
} else {
if (String(e.typeName()).equals(String("E_RELEASE"))) { // 检测是否释放屏幕
if (!String(e.objName()).equals(String("background"))) { // 检测是否为已注册按键区域
if (String(e.objName()).equals(String("QQ"))) { // 检测是否为按下QQ按键
Serial.println("QQ");
M5.Lcd.drawBitmap(262, 6, 30, 31, QQ_icon); // 显示QQ图标
delay(50);
} else {
if (String(e.objName()).equals(String("WX"))) { // 检测是否为按下WX按键
Serial.println("WX");
M5.Lcd.drawBitmap(262, 6, 30, 30, WX_icon); // 显示微信图标
delay(50);
} else {
if (String(e.objName()).equals(String("ZFB"))) { // 检测是否为按下ZFB按键
Serial.println("ZFB");
M5.Lcd.drawBitmap(262, 6, 30, 30, ZFB_icon); // 显示支付宝图标
delay(50);
} else {
if (String(e.objName()).equals(String("CON"))) { // 检测是否为按下CON确认按键
Serial.println("CON");
if (!Input_data.equals(String("")) && Input_data.toFloat() > 0) { // 当输入不为空且数字大于0生成订单
Serial.println("Amount:" + Input_data);
// 生成订单
}
delay(50);
} else {
if (String(e.objName()).equals(String("DEL"))) { // 检测是否为按下DEL删除按键,按下删除末尾字符
Input_data = String(Input_data).substring(0, (String(Input_data).length() - 1)); // 删除输入字符串末尾字符
M5.Lcd.fillRoundRect(40, 0, 180, 43, 5, BLACK); // 清空显示字符串
M5.Lcd.drawRoundRect(0, 0, 315, 43, 5, WHITE); // 重新绘制文本显示框
M5.Lcd.setCursor(40, 33); // 设置文本显示坐标
M5.Lcd.printf(Input_data.c_str()); // 显示输入文本
delay(50);
} else {
if (String(Input_data).length() <= 7) { // 检测到数字按键及小数点
Input_data = String(Input_data) + String(e.objName()); 通过OSS,POS机数据也可以上机器学习挖掘数据价值