转发: Digital QR Scanner (QR Code Recorder & QR Code Classify & Chip Counter all in one )
项目介绍
我开发了一款可以扫描条形码和二维码的手持设备。 我称之为“数字 QR 扫描仪”。 扫描条码后,可以直接存入TF,也可以将本次扫描的所有条码按照货物的“型号/批次/日期代码/数量”进行分类。 排序结果可以通过WiFi模块连接云打印机打印出来。 并且可以进行芯片计数。
我使用Raspberry Pi的RP2040作为MCU和WiFi模块WizFi360连接网络打印机; 作为手持设备,我设计了一个内置可充电锂电池的按键开关电路; TF卡用于存储扫描条码的结果; 透明OLED屏(SSD1309)用于状态显示; 一个可以Uart输出的二维码扫描模块,3个触摸按钮和一个操纵杆......
新器件、新应用电路使用较多,硬件调试时间较长。
我使用PCB作为材料来设计这个设备。 4层PCB三明治结构包含了所有模块和器件,不仅增强了结构强度,而且还充当芯片计数的隧道。
基于这个透明的OLED屏幕,我这次使用了新的动画菜单,每个功能都有相应的动画效果。 如下:
基于这个透明的OLED屏幕,我这次使用了新的动画菜单,每个功能都有相应的动画效果。 如下:
typedef enum
{
DO_Menu = 0,
DO_QR_Scan,
DO_QR_Classify,
DO_Chip_Counter,
DO_Setting,
DO_QR_Classify_Print
}STATE_;
STATE_ CurrentState;
要显示“DO_Menu”中的主菜单,请单击触摸按钮的左按钮,Touch_Key_Num 将增加。 单击触摸按钮的右键,Touch_Key_Num 会减小。 然后根据Touch_Key_Num的值显示相应的菜单和图标。
case DO_Menu:
{
display.clearDisplay();
if(Touch_Key_Num%4 == 0)
{
display.drawBitmap(8, 8, frames_QR[frame], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(65, 20);
display.print("QR Code");
display.setCursor(65, 30);
display.print("Scanner");
display.display();
frame = (frame + 1) % FRAME_COUNT_QR;
delay(FRAME_DELAY);
Touch_handling();
delay(FRAME_DELAY);
if(Touch_Key_Enter)
{
CurrentState = DO_QR_Scan;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(18, 1);
display.print("QR Code Scanner");
display.drawBitmap(4, 8, frames_tick[0], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.fillRect(0, 55, 128, 8, SSD1306_WHITE);
Touch_Key_Enter = false;
}
}
else if(Touch_Key_Num%4 == 1)
{
display.drawBitmap(8, 8, frames_Classify[frame], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(65, 20);
display.print("QR Code");
display.setCursor(65, 30);
display.print("Classify");
display.display();
frame = (frame + 1) % FRAME_COUNT_Classify;
delay(FRAME_DELAY);
Touch_handling();
delay(FRAME_DELAY);
if(Touch_Key_Enter)
{
CurrentState = DO_QR_Classify;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(16, 1);
display.print("QR Code Classify");
display.drawBitmap(4, 8, frames_tick[0], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.drawRect(56, 12, 68, 11, SSD1306_WHITE);
display.drawRect(56, 22, 68, 11, SSD1306_WHITE);
display.drawRect(56, 32, 68, 11, SSD1306_WHITE);
display.drawRect(56, 42, 68, 11, SSD1306_WHITE);
display.setTextColor(SSD1306_WHITE);
display.setCursor(58, 14);
display.print("MODEL");
display.setCursor(58, 24);
display.print("Lot");
display.setCursor(58, 34);
display.print("D/C");
display.setCursor(58, 44);
display.print("Qty");
display.drawRoundRect(92, 14, 22,7,7,SSD1306_WHITE);
display.drawRoundRect(92, 24, 22,7,7,SSD1306_WHITE);
display.drawRoundRect(92, 34, 22,7,7,SSD1306_WHITE);
display.drawRoundRect(92, 44, 22,7,7,SSD1306_WHITE);
display.fillRect(0, 55, 128, 8, SSD1306_WHITE);
Touch_Key_Enter = false;
}
}
else if(Touch_Key_Num%4 == 2)
{
display.clearDisplay();
display.drawBitmap(8, 8, frames_Counter[frame], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(65, 20);
display.print("Chip");
display.setCursor(65, 30);
display.print("Counter");
display.display();
frame = (frame + 1) % FRAME_COUNT_Counter;
delay(FRAME_DELAY);
if(Touch_Key_Enter)
{
CurrentState = DO_Chip_Counter;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(32, 1);
display.print("Chip Counter");
display.drawBitmap(8, 8, frames_CountX[0], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.display();
Touch_Key_Enter = false;
}
}
else if(Touch_Key_Num%4 == 3)
{
display.clearDisplay();
display.drawBitmap(8, 8, frames_setting[frame], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(65, 25);
display.print("Setting");
display.display();
frame = (frame + 1) % FRAME_COUNT_setting;
delay(FRAME_DELAY);
if(Touch_Key_Enter)
{
CurrentState = DO_Setting;
Touch_Key_Enter = false;
}
}
}
break;
我将根据3个主要功能来介绍“数字二维码扫描仪”的开发过程。
三个主要功能是:
1.二维码扫描并记录;
2、二维码分类打印;
3、芯片计数;
在菜单上选择“二维码扫描仪”,然后单击触摸按钮的中间按钮,进入“二维码扫描仪模式”。
在二维码扫描模式下,单击中间按钮扫描二维码。 如果成功,左边的方框会被选中,然后右边的数字会加一,屏幕底部会显示本次扫描的结果。
屏幕右侧的两个数字显示扫描计数,上一位为当前计数,下一位为总计数。 右键可以清除当前计数,长按可以清除总计数。
如果扫描失败,屏幕左侧的框中将出现一条错误消息,您需要重新扫描。 如下:
成功识别二维码后,将保存在TF卡中的“recorder.txt”文件中,如下图所示;
二维码存储格式如下:
二维码扫描模式代码如下:
case DO_QR_Scan:
{
if(Touch_Key_Enter == true)
{
display.drawBitmap(4, 8, frames_tick[1], FRAME_WIDTH, FRAME_HEIGHT, 0);
display.drawBitmap(4, 8, frames_tick[0], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.fillRect(0, 55, 128, 8, SSD1306_WHITE);
digitalWrite(PIN_QR_TRIG, LOW);
Scan_En = true;
Touch_Key_Enter = false;
}
if(Scan_En)
{
if( millis()- Time_Touch_Key > 6000)
{
digitalWrite(PIN_QR_TRIG, HIGH);
display.writeLine(17, 21, 37, 41,SSD1306_WHITE);
display.writeLine(17, 41, 37, 21,SSD1306_WHITE);
}
if( millis()- Time_Touch_Key > 10000)
{
display.writeLine(17, 21, 37, 41,SSD1306_BLACK);
display.writeLine(17, 41, 37, 21,SSD1306_BLACK);
Scan_En = false;
}
}
while (QR.available())
{
char a = QR.read();
Serial.print(a);
data_String += a;
digitalWrite(PIN_QR_TRIG, HIGH);
delay(10);
}
if(data_String != "")
{
display.drawBitmap(4, 8, frames_tick[0], FRAME_WIDTH, FRAME_HEIGHT, 0);
display.drawBitmap(4, 8, frames_tick[1], FRAME_WIDTH, FRAME_HEIGHT, 1);
display.setTextColor(SSD1306_BLACK);
display.setTextSize(2);
display.setCursor(65, 15);
display.println(QR_num_Now);
display.setCursor(65, 35);
display.println(QR_num);
QR_num ++;
QR_num_Now ++;
display.setTextColor(SSD1306_BLACK);
display.setTextSize(1);
display.setCursor(1, 56);
display.print(data_String);
myFile = SD.open(file_name, FILE_WRITE);
if (myFile) {
myFile.println(data_String);
myFile.close();
data_String = "";
}
Scan_En = false;
}
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(65, 15);
display.println(QR_num_Now);
display.setCursor(65, 35);
display.println(QR_num);
display.display();
}
break;
在“二维码分类模式”下,逐一扫描每个二维码,按照“型号--批次--日期代码--数量”的顺序扫描二维码。 扫描所有二维码后,即可将所有商品按类别进行汇总。 并输出存储或打印。
如上图所示,屏幕右侧有一个扫描顺序指示符。 当当前需要的二维码被正确扫描后,该类别对应的框将会被填充。
这是我们出库时箱子表面的二维码标签。 我通过判断“P/N”、“LOT”、“D/C”、“QTY”等字符来判断是否是本次扫描需要的二维码。 代码,
代码如下所示:
if(DO_QR_Classify_Step == 0)
{
if(data_String.substring(0, data_String.indexOf(":", 0)) == "P/N")
{
Product_Box[Product_Box_cnt].MODEL = data_String.substring(data_String.indexOf(":", 0)+1, data_String.length()-1);
display.fillRoundRect(95, 16, 16,3,3,SSD1306_WHITE);
DO_QR_Classify_Step ++;
}
}else if(DO_QR_Classify_Step == 1)
{
if(data_String.substring(0, data_String.indexOf(":", 0)) == "LOT")
{
Product_Box[Product_Box_cnt].LOT = data_String.substring(data_String.indexOf(":", 0)+1, data_String.length()-1);
display.fillRoundRect(95, 26, 16,3,3,SSD1306_WHITE);
DO_QR_Classify_Step ++;
}
}else if(DO_QR_Classify_Step == 2)
{
Product_Box[Product_Box_cnt].DC = data_String.substring(data_String.indexOf(":", 0)+1, data_String.length()-1);
if(data_String.substring(0, data_String.indexOf(":", 0)) == "D/C")
{
display.fillRoundRect(95, 36, 16,3,3,SSD1306_WHITE);
DO_QR_Classify_Step ++;
}
}else if(DO_QR_Classify_Step == 3)
{
if(data_String.substring(0, data_String.indexOf(":", 0)) == "QTY")
{
Product_Box[Product_Box_cnt].Qty = data_String.substring(data_String.indexOf(":", 0)+1, data_String.length()-1).toInt();
display.fillRoundRect(95, 46, 16,3,3,SSD1306_WHITE);
myFile = SD.open(file_name, FILE_WRITE);
if (myFile) {
myFile.print("BOX No:");
myFile.println(Product_Box_cnt);
myFile.print("MODEL:");
myFile.println(Product_Box[Product_Box_cnt].MODEL);
myFile.print("LOT:");
myFile.println(Product_Box[Product_Box_cnt].LOT);
myFile.print("D/C:");
myFile.println(Product_Box[Product_Box_cnt].DC);
myFile.print("QTY:");
myFile.println(Product_Box[Product_Box_cnt].Qty);
myFile.println("");
myFile.close();
}
DO_QR_Classify_Step = 0;
Product_Box_cnt ++;
display.fillRoundRect(92, 14, 22,7,7,SSD1306_BLACK);
display.fillRoundRect(92, 24, 22,7,7,SSD1306_BLACK);
display.fillRoundRect(92, 34, 22,7,7,SSD1306_BLACK);
display.fillRoundRect(92, 44, 22,7,7,SSD1306_BLACK);
display.drawRoundRect(92, 14, 22,7,7,SSD1306_WHITE);
display.drawRoundRect(92, 24, 22,7,7,SSD1306_WHITE);
display.drawRoundRect(92, 34, 22,7,7,SSD1306_WHITE);
display.drawRoundRect(92, 44, 22,7,7,SSD1306_WHITE);
}
}
每个扫描成功的二维码都会显示在屏幕底部; 并判断是否是所要求的内容。 如果出现错误,就会卡在本次扫描中。 如果您想退出此扫描,请单击左侧按钮退出此扫描并重试。 扫描,当扫描成功“型号--LOT--DateCode--Quantity”四个类别的二维码后,将存储在TF卡中。
#define Product_Box_max 64
struct _Product_Box{
String MODEL;
String LOT;
String DC;
uint32_t Qty;
} ;
_Product_Box Product_Box[Product_Box_max];
比如下图中的四个标签,
扫描后存储在TF卡中的格式为:
如果长按左键,会进入二维码分类模式,仍然按照“型号--LOT--DateCode”分类,同一类别的商品数量会累加在一起。 仍然使用图中的例子,长按左键后,TF卡会总结输出如下:
为了节省电池电量,WiFi 模块在正常情况下处于关闭状态。 如果在设置中设置了打印选项,数字二维码扫描仪将打开 WiFi 模块并通过网络连接到云打印机。 此扫描的摘要结果也将被打印。
打印部分代码如下:
case DO_QR_Classify_Print:
{
switch(Wifi_State){
case Power_On_wifi:
{
Serial.println("POWER_ON1");
digitalWrite(PIN_Wifi_RST, HIGH);
delay(1000);
WiFi.init(&Serial2);
Serial2.flush();
Serial2.begin(2000000);
// attempt to connect to WiFi network
while ( status != WL_CONNECTED) {
// Connect to WPA/WPA2 network
status = WiFi.begin(ssid, pass);
}
// print your WiFi shield's IP address
ip = WiFi.localIP();
Wifi_State = Link_To_Network;
}
break;
case Link_To_Network:
{
if (client1.connect(Cloud_Printer,80)){
delay(3000);
client1.print(String("TEXT")+String("QR CODE Classify"));
Wifi_State = QR_Code_print;
}
}
break;
case QR_Code_print:
{
}
break;
}
}
break;
芯片计数功能主要由3组红外发射管和接收管处理,其中2组用于计数,另外一组红外发射管和接收管用于判断当前位置是否有芯片 磁带的。
芯片编织层的侧面有一些小孔,每个芯片对应三个孔。 所以通过数孔的数量来计算芯片的数量。
为了准确识别数量,我使用两层PCB设计了一个隧道。
我用了两个中断函数,分别对应两个红外接收管的IO。
pinMode(PIN_IR_1, INPUT);
attachInterrupt(digitalPinToInterrupt(PIN_IR_1), IR_COUNTER, CHANGE);
pinMode(PIN_IR_2, INPUT);
attachInterrupt(digitalPinToInterrupt(PIN_IR_2), IR_COUNTER, CHANGE);
如果正常的话,两个IO的编码应该是00-11-01-10的状态。这意味着有一个小孔穿过两个红外管,计数+1。
void IR_COUNTER()
{
IR_current = ((IR_current << 2) | (digitalRead(PIN_IR_1) << 1) | (digitalRead(PIN_IR_2)));
Serial.println((digitalRead(PIN_IR_1) << 1) | (digitalRead(PIN_IR_2)), HEX);
if ((IR_current == 0b00101101) || (IR_current == 0b11101101)|| (IR_current == 0b00100001) || (IR_current == 0b00101001)|| (IR_current == 0b10001000))
{
Hole_num ++ ;
// Serial.print("NUM:");
// Serial.println(Hole_num);
}
}
整个芯片计数部分的代码如下:
case DO_Chip_Counter:
{
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(32, 1);
display.print("Chip Counter");
if(Hole_num > Hole_num_Now)
{
display.drawBitmap(8, 8, frames_CountX[frame], FRAME_WIDTH, FRAME_HEIGHT, 1);
frame = (frame + 1) % FRAME_COUNT_CountX;
if(frame == FRAME_COUNT_CountX-1)
{
Hole_num_Now = Hole_num;
}
}
else
{
display.drawBitmap(8, 8, frames_CountX[0], FRAME_WIDTH, FRAME_HEIGHT, 1);
}
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(65, 25);
display.println(Hole_num/3);
display.display();
delay(FRAME_DELAY);
}
break;
对于云打印机部分,我使用了上次开发 Any 打印机项目时的代码。 其中代码只需要创建Socket并发送以TEXT为前缀的数据,发送的数据会直接打印出来。
嵌入式网页和跨平台输入程序正在开发中。
其中,跨平台输入功能进展顺利。 通过电脑端的Python程序,接收数字二维码扫描仪的扫描结果,并自动填充EXCEL上的相应位置,我认为是一个比较实用的功能。
待续。
文件