本帖最后由 vany5921 于 2019-9-28 14:04 编辑
第一步:使用UIFLOW编制主控台(m5stack)的UI并且编写主控台侧程序 UIFLOW是M5出品的图形化编程工具,目前可以连接m5stack core以及m5stickc两种产品。玩家可以去官网自行了解一下。UIFLOW集成了M5的各类周边,提供了积木式的编程体验,同时也提供生成的micropyhon代码,除了开发以外,也是入门学习mpy的非常好的工具。 (1)首先设计UI,模拟超市的UI比较简单,一共分为标题栏、内容栏以及控制按钮等三部分。 标题栏:显示M5 Sim SuperMarket 内容栏:商品名称、重量、单价以及总价等四项内容 控制栏:苹果按钮、核桃按钮以及打印按钮 因为是模拟小超市,所以利用m5stack basic 的三只按钮,提供苹果以及核桃两种商品选择,玩家后续也可以通过M5的face套件或者刚刚出品的卡片键盘套件扩展你的商品选择。 整个UI的设计是拖动+所见即所得式的,你可以在设计中不断“RUN”来观察在实际主控屏幕上的状态,这就是uiflow以及mpy的优势所在,如果是arduino,你需要不停地编译-上传,对于esp32来说,这个设计迭代周期会令人厌烦,但是在uiflow+mpy就让这项工作变得非常容易。 (2)主控台编程 除了UI以外,主控台编程主要有几个部分: 一是电子秤部分的编程,这部分要实现主控与电子秤之间的连接和调试,接线在一楼已经示意了,注意VCC我选用3.3V。软件部分主要依赖了一个hx711库,详细的调用方式,玩家可以参考我的那篇电子秤帖子。这里依然将校准系数保存在一个txt文件里,存储在主控里。由于uiflow可以很方便在主控里保留多个功能程序,所以我把电子秤的校准程序也上传到主控里,玩家可以根据温度变化(比如季节变化对形变传感器是有影响的)用100g/200g砝码进行校准,校准结果就存放在txt文件里,方便调用。 二是uart部分的编程,我们选择uart2作为数据外送的端口。因为需要传送商品、单价、重量、总价等四组数据,所以我们使用json格式来进行传递。 三是按钮陷阱的编程,这部分比较简单。对三只按钮的触发进行响应处理,其中苹果和核桃,将更新品名和单价,而打印按钮,就把组装好的json串通过uart2送出去。 以下是主控台的代码,注释得还算清楚,您可以结合一楼进行理解。我们使用的工具是vscode+m5stack的插件,调试非常便捷,在下载程序时,用到了upyloder这个很棒的小工具。 注意:使用vscode+m5stack的插件调试时,core侧必须保持在usb电缆连接模式下。
[mw_shl_code=python,true]# date 2019-09-25
# UIFLOW设计ui,实现电子秤
# 0914 ----- 解决了uart发送单价+称重数据+总价+品名
# 0924 ----- 与arduino 第一次联调
# 0925 ----- 与arduino pro mini 5V/16M联调,价格大致参考市价,苹果15(核桃31元)/kg整理
from m5stack import *
from m5ui import *
from utime import sleep_us
from uiflow import *
from hx711 import HX711
global val_cort #校正系数
global val,v_up,v_tp
v_up=0
v_tp=0
global s_json,v_comd #发送到uart的json字符串
s_json=''
v_comd=''
#uart 初始化,使用uart2向arduin pro mini传递需要打印的数据
uart = None
uart = machine.UART(2, tx=17, rx=16)
uart.init(9600, bits=8, parity=None, stop=1)
class Scales(HX711):
def __init__(self, d_out, pd_sck):
super(Scales, self).__init__(d_out, pd_sck)
self.offset = 0
def reset(self):
self.power_off()
self.power_on()
def tare(self):
self.offset = self.read()
def raw_value(self):
return self.read() - self.offset
def stable_value(self, reads=10, delay_us=500):
values = []
for _ in range(reads):
values.append(self.raw_value())
sleep_us(delay_us)
return self._stabilizer(values)
@staticmethod
def _stabilizer(values, deviation=10):
weights = []
for prev in values:
weights.append(sum([1 for current in values if abs(prev - current) / (prev / 100) <= deviation]))
return sorted(zip(values, weights), key=lambda x: x[1]).pop()[0]
#创建一个实例
scales = Scales(d_out=5, pd_sck=2)
#读出预存的校正系数
with open('cort.txt', 'r') as myfile:
val_cort=float(myfile.read().replace('\n', '')) #读出预存的校正系数
myfile.close()
scales.tare() #初始化时进行一次去皮
#------------------UI部分
setScreenColor(0x222222)
M5title = M5Title(title="M5 Sim Supermarket", x=3 , fgcolor=0xFFFFFF, bgcolor=0x0000FF)
label0 = M5TextBox(16, 49, "Commodity", lcd.FONT_Ubuntu,0xFFFFFF, rotate=0)
label1 = M5TextBox(16, 90, "Unit Price", lcd.FONT_Ubuntu,0xFFFFFF, rotate=0)
label2 = M5TextBox(16, 127, "Weight", lcd.FONT_Ubuntu,0xFFFFFF, rotate=0)
label3 = M5TextBox(17, 164, "Total Price", lcd.FONT_Ubuntu,0xFFFFFF, rotate=0)
rectangle0 = M5Rect(34, 203, 60, 20, 0xf80d0d, 0xfcfbfb)
rectangle1 = M5Rect(127, 203, 60, 20, 0x55e10c, 0xf8f4f4)
rectangle2 = M5Rect(222, 203, 60, 20, 0x528be5, 0xFFFFFF)
label4 = M5TextBox(42, 205, "Apple", lcd.FONT_Default,0xFFFFFF, rotate=0)
label5 = M5TextBox(134, 207, "Walnut", lcd.FONT_Default,0xfaf9fa, rotate=0)
label6 = M5TextBox(237, 207, "Print", lcd.FONT_Default,0xFFFFFF, rotate=0)
text_comd = M5TextBox(135, 47, "Text", lcd.FONT_Default,0xFFFFFF, rotate=0)
text_up = M5TextBox(134, 93, "Text", lcd.FONT_Default,0xFFFFFF, rotate=0)
text_weig = M5TextBox(134, 130, "Text", lcd.FONT_Default,0xFFFFFF, rotate=0)
text_tp = M5TextBox(134, 164, "Text_tp", lcd.FONT_Default,0xFFFFFF, rotate=0)
while True:
val = val_cort*scales.stable_value() #带有折算补偿系数的计算,如-0.00051235
val1=("%.2f" % val) #将称重数据格式化,小数点后保留2位
text_weig.setText(str(val1)) #显示更新称重数据
v_tp=val*v_up/1000 #因称重为克,折算千克的价格
v_tp=("%.2f" % v_tp)
text_tp.setText(str(v_tp)) #更新总价数据
sleep_us(200000)
if btnA.wasPressed():
# global params
v_comd='Apple'
text_comd.setText(v_comd)
text_up.setText('15.0')
v_up=15
pass
if btnB.wasPressed():
# global params
v_comd='Walnut'
text_comd.setText(v_comd)
text_up.setText('31.0')
v_up=31
pass
if btnC.wasPressed():
s_json="{\"up\":\""+str(v_up)+"\",\"tp\":\""+str(v_tp)+"\",\"commodity\":\""+v_comd+"\",\"weigh\":\""+str(val1)+"\"}"
uart.write(s_json+"\r\n")
pass
[/mw_shl_code]
第二步:热敏打印机控制器(arduino UNO)与热敏打印机的调试 (1)热敏打印机控制器我们选择arduino UNO来做原型调试,一是因为adafruit的热敏打印机库就是在arduino上的,我们没必要造轮子了。二是我们能在TB找到的打印机(包括价格能在接收范围内的),实际上也是基于这个库的,所以使用arduino做原型调试是首选。 (2)热敏打印机选择 在adafruit官网看到这个打印机,对照TB上面的图片,初步确定了这是一款701模组的嵌入式热敏打印机,购买时注意选择TTL电平。电压输入是5~9V,店家介绍最好在8V,供电电流要求1.5A~2A。实测5A/2A的供电方式,对于字符打印是足够的,但是在打印图片过程中,效果不好,因此建议玩家如果有条件用稳压电源吧。 (3)控制器代码部分 控制器的代码主要有两部分内容: 一是需要定义两个软串口,一个用于接收来自m5stack的json格式的商品信息。第二个是用于向热敏打印机发出打印指令。两个软串口之间需要切换侦听,这一点务必注意。我在一楼也用专门的标注提醒了。否则不能正常工作。 二是需要解析来自m5stack的json格式数据,我们用到了大名鼎鼎的arduinojson库,目前版本是6.12,详细内容请玩家自行去arduinojson.org学习了解。因为这次仅仅需要解析四个数据,所以我们用了一层json的格式就可以了。arduinojson.org提供了一个json助手,可以很方便地对你需要设计的json串以及相关代码,内存分配等进行辅助性工作。 以下是arduino uno部分的代码。
[mw_shl_code=arduino,true]//-------date:2019-09-24
//-------模拟超市打印机控制端程序
//-------主要功能:1、解析来自(M5STACK)softserial的数据
// 2、测试热敏打印机端的输出
// 3、与M5STACK basic对接
/*
m5stack 发送来数据格式:up---单价;tp---总价,commodity---品名,weigh--重量
{
"up": "7",
"tp": "840",
"commodity": "apple",
"weigh": "120"
}
*/
//------------ArduinoJson部分
#include <ArduinoJson.h>
#include <SoftwareSerial.h>
SoftwareSerial swSer1(10,11); //UNO-10-RX;UNO-11-TX
//------------Thermal printer部分
#include "Adafruit_Thermal.h"
SoftwareSerial mySerial(5, 6); // UNO-5-RX;UNO-6-TX
Adafruit_Thermal printer(&mySerial); // Pass addr to printer constructor
void setup() {
// Initialize serial port
Serial.begin(9600);
swSer1.begin(9600);
mySerial.begin(9600);
printer.begin(); // Init printer
}
void loop() {
//DynamicJsonDocument doc(152);
//监听来自M5STACK的软串口
swSer1.listen();
while (!swSer1.available())
delay(100);
// Deserialize the JSON document
const size_t capacity = JSON_OBJECT_SIZE(4) + 50;//定义来自arduinojson.org的助手生成
DynamicJsonDocument doc(capacity);
DeserializationError error = deserializeJson(doc, swSer1); //不用软串口读取数据
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.c_str());
return;
}
//仅供调试
String s_commodity=doc["commodity"];
String s_weigh=doc["weigh"];
String s_up=doc["up"];
String s_tp=doc["tp"];
Serial.print(s_commodity);
Serial.print("-----");
Serial.print(s_weigh);
Serial.print("-----");
Serial.print(s_up);
Serial.print("-----");
Serial.println(s_tp);
ToPrint(s_commodity,s_weigh,s_up,s_tp); //调用打印模块
}
void ToPrint( String t_comm , String t_weig , String t_up , String t_tp){
//监听2号软串口
mySerial.listen();
printer.begin(); // Init printer
// caption
printer.inverseOn();
printer.println(F(" M5 Sim SuperMarket "));
printer.inverseOff();
//print
printer.println("--------------------------");
printer.boldOn();
printer.print(F("Commodity:"));
printer.boldOff();
printer.println(t_comm);
printer.boldOn();
printer.print(F("Weigh(g):"));
printer.boldOff();
printer.println(t_weig);
printer.boldOn();
printer.print(F("Unit Price:"));
printer.boldOff();
printer.println( t_up);
printer.boldOn();
printer.print(F("Total Price:"));
printer.boldOff();
printer.println(t_tp);
printer.println("--------------------------");
printer.feed(2);
printer.setDefault(); // Restore printer to defaults
}[/mw_shl_code]
第三步:第一次联调:PC-----热敏打印机控制器(arduino UNO)及热敏打印机 联调一共分为两步,这是第一步,用PC的串口助手,模拟来自m5stack的商品数据,然后和arduino uno以及热敏打印机进行测试。本次联调实际上也是两步。 一是你需要将arduino uno与热敏打印机首先调试成功,这一步实际上占用了我很多时间。注意提醒:1、你需要根据你到货的打印机标注的通信波特率,对adafriut家提供的打印机库里面的波特率进行修改,比如我买到的是9600,而默认是115200,需要自行调整一下。2、热敏打印机到手后,你可以做一个自检,自检页会显示驱动版本以及波特率。
这个教程写得非常详细清晰,感谢伟大的adafriut.com 二是PC的串口助手,用一个测试的json串,9600波特率,通过一个usb-ttl转换器,连接到arduino uno上,接线如一楼。在UNO软串口收到来自PC的测试串口,进行解析,然后对需要打印的变量进行赋值。我们写了一个ToPrint()函数,将解析后的四个商品信息,打印到热敏打印机上。也就是形成了超市小票。这张小票同样有两个部分,一是标题区,我们用反相字体的方式进行设计,形成了超市的标题,二是内容区,品名等用粗体字,数据部分用正常字体。两个区域之间我们用“--------”进行隔离。在打印结束后,我们考虑了两步进纸。小票就生成了。然后恢复打印机设置为默认。
第四步:第二次联调:主控台(M5STACK)与打印机控制器(arduino UNO)及热敏打印机 有了前面的积累,这一步联调就比较简单了,注意:ESP32的电平是3.3V的,而我这次选择的arduino uno是可调电平的版本,需要注意将UNO的电平也调整至3.3V。 此刻,你就会看到,如视频中的展示:一旦print按钮按下,uno就收到json数据,然后你可以听到打印机欢快的声音了,因为是热敏打印机,你听不到针头以及喷头的声音,听到的就是进纸的声音。看到了小票打出,数据与主控台的数据一致,这步联调就结束了。 需要说明的是:电子秤输出的是克(g),所以我们专门查询了苹果和核桃的市价,大致选择了一个平均价,这个价格是元/1000g,所以这个转换直接在程序设计里,就写入了。小票上,你看到的是重量为g,但是单价和总价都是元。体现模拟小超市尽可能接近现实吧。
第五步:将打印机控制器移植到arduino promini 缩小作品的体积 UNO的体积比较大,作为原型设计很好,但是作为模拟小超市的组成部分,显得体积较大和接线凌乱,所以我们选择了一款袖珍的arduino 控制器,同样是基于328的promini,原本我选择了一片3.3v/8m的版本,结果手头正好有一片焊了排针的5v/16m版,就拿来用了,注意:一是两者电平不一样,好在promini只是读取来自esp32的数据,所以我们省略掉了promini的TX-----esp32这根线。也就是说,来自m5stack(esp32)的3.3v是可以被promini识别的,而且没有过压的担心,反之的那根线我们省略掉了。如果需要两者交互通信,就必须考虑电平转换问题,这一点必须注意。 我们将接好线的promini放在一个透明的圆柱型小盒子里,看着很面熟吧,就是m5staickV的盒子,两头钻孔后,正好作为一个promini的容器。 好啦,到此为止,所有的调试工作结束了,如果和孩子一起玩,还可以设计一个收款台,把热敏打印机嵌入到台面上(所谓嵌入,指的就是这个意思)。也可以和孩子一起做一些涂鸦和装饰。 后续我们可能还会跟踪提升这个小玩具。 在使用m5的过程中,我的体会是,能不造轮子尽量别造,能够利用网络上丰富的库和工具来实现你的创意,是一件很有意思的事。不是不鼓励造轮子和创造,而是一定要评估你自己的水平,像我这样的水平一般的就多用成品来组合,否则会有挫败感从而降低你创造的兴趣。 以上就是制作过程的分享,把我能想到的坑也都做了提醒,如果在制作过程中,大家还有问题,欢迎一起交流。谢谢大家。国庆将至,硕果累累,预祝大家节日快乐,善待自己、陪伴家人。 沧海合十。
|