Zabawkowa kasa fiskalna

Ta zabawka udaje prawdziwą kasę fiskalną – drukuje paragony, liczy ceny i pozwala wybierać produkty.

Zobacz film z działania:

Schemat wewnętrzny:

schematic

Kod źródłowy dla Arduino: https://pastebin.com/vuE97mH7

/*
 * Zabawkowa kasa fiskalna
 * 2019-01-31 Aleksander Kawęczyński e.vt0.pl
 * Arduino pro mini, klawiatura membranowa 4x4, LCD 16x2, drukarka DP-Q58A
 * 
 * Układ na wejściu zasilania z szeregowo podłączonym mosfetem-p, załączanie: microswitch chwilowy, podtrzymanie włączenia przez pin A3.
 * 
 * 13 - power control: high - on, low - off; connected to gate of transistor, pobór prądu w stanie wyłączonym: 0,1uA
 * 2 - printer software serial
 * 3 .. 10 - 4x4 keyboard matrix
 * 11 - LCD RS (4)
 * 12 - LCD E (6)
 * A0 - LCD D4 (11)
 * A1 - LCD D5 (12)
 * A2 - LCD D6 (13)
 * A3 - LCD D7 (14)
 * A6 - Vbat przez dzielnik 100K/100K
 */
#include 
#include 
#include 
#include 

#define POWER_CTL 13
#define VBATMIN 6000   //minimum battery voltage in mV
#define VERSION "1.1"

SendOnlySoftwareSerial drukarka(2);  // Tx pin
LiquidCrystal lcd(11, 12, A0, A1, A2, A3);

unsigned long lastAction=0;
const unsigned long idle=(5UL*60UL*1000UL); //2 minutes
uint16_t keyState=0, lastKeyState=0;
char buff[80];
uint16_t suma_cen = 0, powerCounter = 0, printCounter = 0;
const uint8_t baza = 5;
uint8_t cashier=0;

const char  s0[] PROGMEM = "BU\x02KA";
const char  s1[] PROGMEM = "SER";
const char  s2[] PROGMEM = "MLEKO";
const char  s3[] PROGMEM = "P\x02\ATKI";
const char  s4[] PROGMEM = "ZESZYT";
const char  s5[] PROGMEM = "LODY";
const char  s6[] PROGMEM = "PIZZA";
const char  s7[] PROGMEM = "WARZYWA";
const char  s8[] PROGMEM = "SOK";
const char  s9[] PROGMEM = "S\x02\ODYCZE";
const char  s10[] PROGMEM = "KAKAO";
const char  s11[] PROGMEM = "CZYTANKA";

PGM_P const produkty[] PROGMEM = {s0,s1,s2,s3,s4,s5,s6,s7,s8,s9,s10,s11};

const char  t0[] PROGMEM = " Sklep \"Julka\"";
const char  t1[] PROGMEM = " Sklep \"Franek\"";
const char  t2[] PROGMEM = " Sklep \"Maja\"";
const char  t3[] PROGMEM = "Z A P R A S Z A";
const char  t4[] PROGMEM = " DO ZOBACZENIA!";


PGM_P const powitania[] PROGMEM = {t0,t1,t2,t3,t4};

const uint16_t ceny[] PROGMEM = {
  1,//0
  6,//1
  3,//2
  4,//3
  2,//4
  2,//5
  10,//6
  7,//7
  5,//8
  8,//9
  9,//10
  20//11
};

byte el[8] = {
  0b01000,
  0b01001,
  0b01010,
  0b01100,
  0b01000,
  0b11000,
  0b01111,
  0b00000
};

void setup() {
  // pin configuration
  pinMode(POWER_CTL, OUTPUT);
  digitalWrite(POWER_CTL, HIGH);

  //licznik uruchomien
  
  powerCounter = EEPROM.read(0);
  powerCounter += (EEPROM.read(1) << 8);
  printCounter = EEPROM.read(2);
  printCounter += (EEPROM.read(3) << 8);
  
  
  if( (printCounter == 255) && (analogRead(A6)<5)){
  //po przeprogramowaniu, trzeba wyczyścić zmienne  
    powerCounter =0;
    printCounter = 0;
    EEPROM.write(2, ++printCounter);
  }
  
  ++powerCounter;
  EEPROM.write(0, powerCounter);
  
  lcd.begin(16, 2);
  lcd.createChar(2, el);
  lcd.clear();
  cashier = (powerCounter % 3);
  strcpy_P(buff, (PGM_P)pgm_read_word(&(powitania[cashier])));
  lcd.print(buff);
  lcd.setCursor(0, 1);
  strcpy_P(buff, (PGM_P)pgm_read_word(&(powitania[3])));
  lcd.print(buff);
  drukarka.begin(9600);
  //wait until power button is not pressed
  
  delay(100);
  drukarka.print("\x1B\x40\x1B\x61\x49");
  lastAction = millis();
}

void loop() {
  //Keyboard module:
  uint16_t keys = keyDown();
  uint8_t wybranyProdukt = 0;
  //logic module
  if(keys) lastAction = millis();

  //battery voltage check
  
  
  switch (keys){
    case 0:
      delay(100);
      if((analogRead(A6)*10) < VBATMIN){
      lcd.clear();
      lcd.print("Bateria");
      lcd.setCursor(0, 1); 
      lcd.print("Roz\x02adowana!");
      delay(2000);
      wylacz();
      }
    break;
    case (1<<0)://D -- zakonczenie paragonu
      if(suma_cen == 0){
        lcd.clear();
        sprintf(buff, "Vb=%d mV; v" VERSION,analogRead(A6)*10 );
        lcd.print(buff);
        lcd.setCursor(0, 1);
        sprintf(buff, "PWc=%d PrC=%d",powerCounter, printCounter);
        lcd.print(buff);
        delay(2000);
        lcd_powitanie();
      }
      else{
        printFooter();
      }

      
    break;
    case (1<<1)://#
      wybranyProdukt = 12;
    break;
    case (1<<2)://0
      wybranyProdukt = 10;
    break;
    case (1<<3)://*
      wybranyProdukt = 11;
    break;
    case (1<<4)://C
      //change cashier
        if(suma_cen == 0){
          cashier = (++cashier) % 3;
          lcd_powitanie();
        }
    break;
    case (1<<5)://9
      wybranyProdukt = 9;
    break;
    case (1<<6)://8
      wybranyProdukt = 8;
    break;
    case (1<<7)://7
      wybranyProdukt = 7;
    break;
    case (1<<8)://B
      if(suma_cen == 0){
        //wydrukuj cos innego
        drukarka.print("\x1B\x61\x48"); //alignment left
        
        drukarka.print("       ,~~.\r\
      (  6 )-_,\r\
 ('\___ )=='-'    KACZKA\r\
  \\ .   ) )\r\
   \\ `-' /    \r\
~'`~'`~'`~'`~\r\r\r");

        drukarka.print("\x1B\x61\x49"); //alignment middle
      }
      
    break;
    case (1<<9)://6
      wybranyProdukt = 6;
    break;
    case (1<<10)://5
      wybranyProdukt = 5;
    break;
    case (1<<11)://4
      wybranyProdukt = 4;
    break;
    case (1<<12)://A
      if(suma_cen > 0)
        printFooter(); //zakoncz paragon przed wylaczeniem
        
      wylacz();
        
    break;
    case (1<<13)://3
      wybranyProdukt = 3;
    break;
    case (1<<14)://2
      wybranyProdukt = 2;
    break;
    case (1<<15)://1
      wybranyProdukt = 1;
    break;
  }

  if(wybranyProdukt){
    int isFirst=0;
    if(suma_cen == 0) isFirst=1;//pierwszy produkt na tym paragonie,
      
    
     strcpy_P(buff, (PGM_P)pgm_read_word(&(produkty[wybranyProdukt-1])));
    uint8_t a = strlen(buff);
    uint16_t cena = pgm_read_word(&(ceny[wybranyProdukt-1]));
    suma_cen += cena;
    lcd.clear();
    lcd.print(buff);
    itoa(cena, buff, 10);
    lcd.setCursor(15-2-strlen(buff), 0);
    lcd.print(buff);
    lcd.print(" Z\x02");
    
    lcd.setCursor(0, 1);
    sprintf(buff, "SUMA: %7d Z\x02",suma_cen);
    lcd.print(buff);
    ;
    
    if(isFirst) {
      printHeader();
      isFirst=0;
      }
    //drukowanie pozycji na paragonie
    strcpy_P(buff, (PGM_P)pgm_read_word(&(produkty[wybranyProdukt-1])));
    //zamiana \x02 na \x9D
    for (char* p = buff; p = strchr(p, 0x02); ++p) {
      *p = 0x9D;
    }
    drukarka.print(buff);
    int spacje = 32-7-a;
    for(uint8_t k=0;k>3) << 0);
  pinMode(7, INPUT);
  //col B
  pinMode(8, OUTPUT);
  delay(1);
  reads += (((PIND & 0x78)>>3) << 4);
  pinMode(8, INPUT);
  //col C
  pinMode(9, OUTPUT);
  delay(1);
  reads += (((PIND & 0x78)>>3) << 8);
  pinMode(9, INPUT);
  //col D
  pinMode(10, OUTPUT);
  delay(1);
  reads += (((PIND & 0x78)>>3) << 12);
  pinMode(10, INPUT);


  return ~reads;
}

void lcd_powitanie()
{
  lcd.clear();
  strcpy_P(buff, (PGM_P)pgm_read_word(&(powitania[cashier])));
  lcd.print(buff);
  lcd.setCursor(0, 1);
  strcpy_P(buff, (PGM_P)pgm_read_word(&(powitania[3])));
  lcd.print(buff);  
}


void lcd16Bit(uint16_t number)
{
  for (uint8_t i=0;i<16;i++)
  {
    if (((number >> (15-i)) & 0x01))
    {
      lcd.print("1");
    } 
    else
    {
      lcd.print("0");
    }
    
  }
}


///drukuje nagłówek paragonu
void printHeader(){
  EEPROM.write(2, ++printCounter);
  strcpy_P(buff, (PGM_P)pgm_read_word(&(powitania[cashier])));
  drukarka.print("\x1B\x61\x49"); //alignment middle
  drukarka.print("\x1B\x45\x01"); //bold print
  drukarka.print("\x1B\x0E\x02"); //times 2 wider pattern
  drukarka.print(buff);
  drukarka.print("\x1B\x45\x00"); //bold print off
  drukarka.print("\x1B\x14\x02"); //cancel times wider pattern
  delay(30);
  sprintf(buff, "DZIEN DOBRY! \r\nPARAGON NR %d\r\n\x1D\x6B\x03",printCounter);
  drukarka.print(buff);
  sprintf(buff,"%08d", printCounter);
  drukarka.write((uint8_t)0);
  sprintf(buff, "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\r",printCounter);
  drukarka.print(buff);
  //delay(2000);
}

void printFooter(){
  lcd.clear();
  lcd.print("Drukowanie ...");
  lcd.setCursor(0, 1);
  sprintf(buff, "SUMA: %7d Z\x02",suma_cen);
  lcd.print(buff);
  sprintf(buff, "\x1B\x61\x50+ \r\x1B\x61\x49================================\r\n");
  drukarka.print(buff);
  delay(100);
  sprintf(buff, "RAZEM:%23d Z\x9D\r\rDO WIDZENIA!\r\r\r\r",suma_cen);
  drukarka.print(buff);
  delay(100);
  suma_cen = 0;

  lcd_powitanie();
  
  }

uint16_t keyDown(){
  uint16_t ret;
  lastKeyState = keyState;
  keyState = getKeyState();
  ret = ((keyState^lastKeyState)&keyState);
  return ret;  
  
}

void powerFunction(){
   //Power function:
  unsigned long czas = millis();
  if((lastAction+idle) < czas )
    wylacz();
  
  //actions:
  //lastAction = millis();
  
}

void wylacz(){
  lcd.clear();
  strcpy_P(buff, (PGM_P)pgm_read_word(&(powitania[(powerCounter % 3)])));
  lcd.print(buff);
  lcd.setCursor(0, 1);
  strcpy_P(buff, (PGM_P)pgm_read_word(&(powitania[4])));
  lcd.print(buff);
  delay(2000);
  lcd.clear();
  digitalWrite(POWER_CTL, LOW);
  for(;;){};
}

Więcej zdjęć:

IMG_20190201_125455
IMG_20190208_165744
IMG_20190208_165823
IMG_20190208_165829

Szablon do wycięcia obudowy ze sklejki (uwaga: wymiary dostosowane do sklejki o grubości 6,3mm!) obudowa_cnc.pdf

Dokumentacja techniczna drukarki: Embedded printer DP-Q58A Technical Manual.pdf

W kodzie Arduino użyłem biblioteki SendOnlySoftwareSerial.zip: SendOnlySoftwareSerial.zip