Arduino nano or Serial.println() corrupts data

Dear forum members,
for years I've been reading this forum for ideas and troubleshooting, and so far I found answer for every problem I had. But now, I face a bug that is way over my knowledge or google-fu. As a new member, I cannot attach files, so my full code will not be visible, but I narrowed down the problem to a printing function.

if((digitalRead(btn_print) == LOW) && (millis() - btn_print_ms > btn_cd) && (receipt_available == true)){   //print receipt
    delay(33);
    if(digitalRead(btn_print) == LOW){
      nx_on(0);
      bv_on(0);
      if(digitalRead(btn_print_LED) == LOW){
        digitalWrite(btn_print_LED, HIGH);
        }
      (*prnt_cnt)++;                    //increase before printing, so there won't be 0000 ticket
      Serial.println(*prnt_cnt);    //test
      write4bytes(uint8_t(0x00), mem_a_print_counter, *prnt_cnt);
      delay(100);
      if(error_flag == false){            //memory operation successful
        uint32_t temp_m = 0;
        sprintf(line0,"   Imprimare");      //Printing
        sprintf(line1,"Asteptati!");        //Please wait
        upd_lcd();
        //print it!
        clear_buf();
        prnt_char_set(0b111);                   //set to print double h,w with underline
        Serial.println("Name");
        prnt_char_set(0);                       //set to print small chars
        Serial.println("Address line 1");          
        Serial.println("Address line 2");  
        Serial.println("Address line 3");           
        Serial.println("Postal code"); 
        prnt_char_set(0b01);                    //set to double h
        sprintf(print_line, "Price pcs. = %u,00 RON", token_price);
        Serial.println(print_line);
        prnt_char_set(0b11);
        sprintf(print_line, "%u pcs. X %u,00 RON", last_tokens, token_price);
        Serial.println(print_line);
        temp_m = last_tokens;
        temp_m = temp_m*token_price;
        sprintf(print_line, "Total %lu,00 RON", temp_m);
        Serial.println(print_line);
        prnt_char_set(0);
        //sprintf(print_line, "%u", *prnt_cnt);
        Serial.println(*prnt_cnt);
        sprintf(print_line,"2%03X-%02X-%02X %02X:%02X", time_arr[6], time_arr[5], time_arr[4], time_arr[2], time_arr[1]);
        Serial.println(print_line);
        prnt_lf(0x03);
        endcut_partial();
        delay(2500);
        prnt_char_set(0);           //just to be sure
        if(digitalRead(btn_print_LED) == HIGH){
          digitalWrite(btn_print_LED, LOW);
          }
        receipt_available = false;
        if(bank != 0){
          bank_tmp = 0;               //so it will display credit
          }else{
            stby_forced = 1;
            return_from_service = true;
            }
        }
      btn_print_ms = millis();
      }    
    }

As you can see, i constantly rewrite the contents of a char array (print_line), and print it over serial on a heat printer. The problem is, when printing the *prnt_cnt memory contents, it becomes garbage. The pointer and the variable both declared as uint32_t global variable. Note that it was my last try, to change from handling global variable to operate trough pointers, but it made no difference at all. Which is strange. As you can see, there are other long type numbers I print with the same method, but they are all fine. On top of that, a 24C32 eeprom stores the prnt_cnt value, and it stores the right value. So the problem in short:
-execution reaches this function
-prnt_cnt variable gets incremented by 1
-the program prints the needed data
-right before printing the prnt_cnt variable, it stores the correct value
-printing the said variable
-it takes a seemingly random value, but always 1330782256
This program part ends, and the eeprom holds the correct value, but the variable has this garbage in it. If now I reset the arduino manually, it reads back the correct value from rom, it can also be checked trough menu, everything seems fine. But on the very first print, it gets incremented, written into eeprom, then somehow gets the same garbage value again. If I don't reset the arduino after one printing, this garbage value gets written into eeprom.
Has anyone any idea, what am I overlooking here?
Hardware configuration: Arduino nano, 2pcs. 16x2 LCD with I2C backpacks, a few buttons, hopper, thermal printer (Serial-RS232).
P.s: the prnt_cnt variable only pops up in 2 other places in my code, once it can be erased from eeprom trough menu, once it can be displayed trough menu. Nowhere else in my code is it modified by any means.
Edit: removed the sensitive informations

Why not post it here rather than attaching it ?

Hello @UKHeliBob, and thanks for chiming in. In the meantime, I got promoted to level 1, so now I can attach the full code. Pasting it here is not an option, as it has ~1000 lines, and the code tags have somewhat 14.000 character limit.
G-ton_v100.ino (41,1 kB)
So anyway, here is the full code.

It would still be better to post it here

/*
User requests:
-memory stores the cashless and cash income, served tokens, everything must be erasable
-2 LCD displays
-custom receipt printing

Error codes:
1 - 
2 - 
3 - 
4 - 
5 - hopper empty
6 - hopper dispense timer overflow
7 - write 4 bytes function error (can't write ROM)
8 - read 4 bytes function error (can't read ROM)
9 - RTC read error
10 - RTC write error

Dependency: Liquidcrystal I2C library by Frank de Brabander
2022.03.05
*/

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <string.h>
//constants
#define pulse_multiplier_bv 1         //pulse multipler for bill validator
#define pulse_multiplier_nx 1         //same as above, for nx
#define led_timer 500                 //leds on-off time
#define pulse_timeout 150
#define pulse_gap 20
#define btn_cd 3000                   //button is not debounced, it only has cooldown
#define keepalive_rate 1000           //change every sec.
#define hopper_empty_chk_rate 500     //check on hopper empty signal so often
#define token_allowed_delay 6000
#define menu_timeout 15000
#define receipt_timeout 30000
//inputs
#define btn_1 A2
#define btn_2 A1
#define btn_menu 13
#define btn_print A0
#define bv_pulse_pin 2
#define bv_busy_pin 4
#define nx_pulse_pin 10
#define hopper_empty_pin 6
#define hopper_opto_pin 8
//outputs
#define btn_print_LED 11                  //also serves as error led
#define hopper_motor_pin 12
#define bv_inh_pin 3
#define nx_inh_pin 9
//LCD and eeprom constants
#define LCD_main_addr 0x27                 //default address for PCF 8574T
#define LCD_secondary_addr 0x26            //A0 soldered short
#define eeprom_addr 0x57
#define mem_a_price 0x20
#define mem_a_token_sum 0x30
#define mem_a_sum_nx 0x40
#define mem_a_sum_bv 0x50
#define mem_a_print_counter 0x60
#define rtc_addr 0x68

const uint8_t LCD_COLS = 16;
const uint8_t LCD_ROWS = 2;
const uint16_t upd_time_rate = 10000;   //check clock every 10s
const uint16_t menu_idle = 10000;

LiquidCrystal_I2C lcd(LCD_main_addr, LCD_COLS, LCD_ROWS); // create main lcd object
LiquidCrystal_I2C lcd2(LCD_secondary_addr, LCD_COLS, LCD_ROWS); // create secondary lcd object

const int BAUDRATE = 9600;        //for serial communication

char line0[17]; //global LCD variables
char line1[17];
char print_line[20];

uint8_t input_pins[] = {bv_pulse_pin, bv_busy_pin, nx_pulse_pin, hopper_empty_pin, hopper_opto_pin};
uint8_t output_pins[] = {btn_print_LED, hopper_motor_pin, bv_inh_pin, nx_inh_pin};
uint8_t btn_pins[] = {btn_1, btn_2, btn_menu, btn_print};

uint8_t count_nx, count_bv, valid_pulses, token_price, token_price_prev, menu_lvl, menu_lvl_prev, view_page, view_page_prev;
uint8_t error_code = 0;                       //0 is normal operation
uint8_t token_counter = 0;
uint8_t last_tokens = 0;
uint8_t last_buy_nx = 0;
uint8_t last_buy_bv = 0;
uint8_t hopper_empty_chk_counter = 0;

uint32_t bank = 0;
uint32_t bank_tmp = 0;
uint32_t cash_sum_nx = 0;
uint32_t cash_sum_bv = 0;
uint32_t token_sum = 0;
uint32_t print_counter = 0;
uint32_t* const prnt_cnt = &print_counter;        //the pointer is itself constant, but the value it points to isn't
uint32_t menu_start_ms, lcd_upd_ms, btn_print_ms, btn_menu_ms, nx_ms, nx_timeout_ms, bv_ms, bv_timeout_ms, bv_busy_ms, led_ms, keepalive_ms, hopper_empty_chk_ms, receipt_ms, read_tmp;

byte time_arr[7];
byte time_arr_tmp[7];

bool error_flag = false;              //error flag
bool stby_forced = 1;
bool lcds_reset = 0;
bool bv_busy = 0;
bool hopper_busy = 0;
bool menu_active = 0;
bool receipt_available = 0;
bool firstboot = 1;
bool return_from_service = 0;
bool conf_flag, runonce, prev_counter_state, ledstate, show_cash, keepalive_state, busy_mssg, menu_refresh, menu_forced_exit;

void setup(){
  delay(2000);
  Serial.begin(BAUDRATE);
  Wire.begin();
  lcd.init(); lcd2.init();
  lcd.backlight(); lcd2.backlight();
/*
  lcd.createChar(0, aa);        //char á
  //WARNING! Addressing 0x00 will cause error.
  //CGRAM rolls over, so it can be called as 0x08
  lcd.createChar(1, ee);        //char é
  lcd.createChar(2, ii);        //char í
  lcd.createChar(3, oo);        //char ó
  lcd.createChar(4, uu);        //char ú
  lcd.createChar(5, oe);        //char ö
  lcd.createChar(6, ue);        //char ü
  lcd.createChar(7, ooe);       //char ő
  */
  sprintf(line0,"  G-Ton v1.00");
  sprintf(line1," G-Trade Inpex");
  lcd.blink(); lcd2.blink(); 
  upd_lcd();
  delay(2500);

  for (uint8_t i = 0; i < (sizeof(input_pins)/sizeof(input_pins[0])); i++){   //all input pins have pullups   
                    pinMode(input_pins[i], INPUT);
                }
  for (uint8_t i = 0; i < (sizeof(btn_pins)/sizeof(btn_pins[0])); i++){   //pull-up btn pins   
                    pinMode(btn_pins[i], INPUT_PULLUP);
                }
  for (uint8_t i = 0; i < (sizeof(output_pins)/sizeof(output_pins[0])); i++){     
                    pinMode(output_pins[i], OUTPUT);
                }
  for (uint8_t i = 0; i < (sizeof(output_pins)/sizeof(output_pins[0])); i++){     
                    digitalWrite(output_pins[i], LOW);
                }
//read in rom contents
  read_tmp = read4bytes(uint8_t(0x00), mem_a_price);
  token_price = uint8_t(read_tmp & 0xFF);
  token_sum = read4bytes(uint8_t(0x00), mem_a_token_sum);  
  cash_sum_nx = read4bytes(uint8_t(0x00), mem_a_sum_nx);
  cash_sum_bv = read4bytes(uint8_t(0x00), mem_a_sum_bv);
  *prnt_cnt = read4bytes(uint8_t(0x00), mem_a_print_counter);
 
  prnt_soft_reset();
  delay(3000);
  codepage_1250();              //set thermal printer codepage
  delay(10);
  prnt_set_center();            //print everything in the middle
  delay(10);
  
  lcd.noBlink();
  lcd2.noBlink();
  //updatetime();   stby_forced will take care of it

  if(token_price == 0 || token_price == 255){
    token_price = 5;
    }// test write
  Serial.println("--------------");
  Serial.println(token_price);
  Serial.println(token_sum);
  Serial.println(cash_sum_nx);
  Serial.println(cash_sum_bv);
  Serial.println(*prnt_cnt);
}

void loop(){
//error handling first
  while(error_flag == true){   //check if error was solved, till then blinky code. Errors above 5 can be only reset with power cycle
    if(runonce == false){    //runonce should be reset before leaving this while loop
      nx_on(0);
      bv_on(0);
      sprintf(line0,"Defectiune: %u", error_code);    //Error
      if(error_code == 5){
        sprintf(line1,"Jetoane terminat");    //Hopper empty
        }else{
          sprintf(line1," ");
          }
        if(bank != 0){
          sprintf(line1,"%lu,00 RON, %u Tn", bank, token_counter);
          }
        upd_lcd();             
        runonce = true;
      }
    if(error_code == 5){            //hopper empty
      if(millis() - hopper_empty_chk_ms >= hopper_empty_chk_rate){
        if(digitalRead(hopper_empty_pin) == HIGH && hopper_empty_chk_counter < 6){
          hopper_empty_chk_counter++;
        }
        if(digitalRead(hopper_empty_pin) == LOW && hopper_empty_chk_counter > 0){
          hopper_empty_chk_counter--;
        }
        if(hopper_empty_chk_counter == 0){
          runonce = false;
          error_code = 0;
          error_flag = 0;
          nx_on(1);
          bv_on(1);
          stby_forced = 1;
          }
        hopper_empty_chk_ms = millis();
        }
      }
    if(error_code > 5){                   //serious errors
      while(1){             
        err_blinky(error_code);
        }
      }
  delay(10);
  }
//reset error display drive
  if(runonce == true){
    runonce = false;
    }
//reset LCD every hour
  if((menu_active == false) && (bank == 0) && (lcds_reset == true) && (hopper_busy == false) && (error_flag == false)){
    nx_on(0);
    bv_on(0);
    lcd.noDisplay();
    lcd2.noDisplay();
    delay(500);
    lcd.init(); lcd2.init();
    lcd.backlight(); lcd2.backlight();
    delay(500);
    upd_lcd();
    nx_on(1);
    bv_on(1);
    lcds_reset = false;
    }
//check on hopper contents
  if((millis() - hopper_empty_chk_ms >= hopper_empty_chk_rate) && (menu_active == false) && (hopper_busy == 0) && (count_nx == 0) && (count_bv == 0) && (digitalRead(nx_inh_pin) == HIGH) && (digitalRead(bv_busy_pin) == HIGH) && (bank == 0)){
    if(digitalRead(hopper_empty_pin) == HIGH && hopper_empty_chk_counter < 6){
      hopper_empty_chk_counter++;
    }
    if(digitalRead(hopper_empty_pin) == LOW && hopper_empty_chk_counter > 0){
      hopper_empty_chk_counter--;
    }
    if(hopper_empty_chk_counter == 6){
      error_code = 5;
      error_flag = 1;
      }
    hopper_empty_chk_ms = millis();
    }
//stby display update
  if((count_nx == 0) && (count_bv == 0) && (bank == 0) && (menu_active == false) && (hopper_busy == 0) && (((millis() - lcd_upd_ms) >= upd_time_rate) || stby_forced == 1)){
    if(stby_forced == 1){
      sprintf(line1, "  In asteptare");    //Standby     
      stby_forced = 0;
      }
    updatetime();
    lcd_upd_ms = millis();
    }
//keepalive
  if((count_nx == 0) && (count_bv == 0) && (menu_active == false) && (hopper_busy == 0) && (digitalRead(nx_inh_pin) == HIGH) && (digitalRead(bv_busy_pin) == HIGH) && (bank == 0) && ((millis() - keepalive_ms) >= keepalive_rate)){
    lcd.setCursor(13,0); lcd2.setCursor(13,0);    
    if(keepalive_state == true){
      lcd.print(" "); lcd2.print(" ");
      }else{
        lcd.print(":"); lcd2.print(":");
        }
    keepalive_state = !keepalive_state;
    keepalive_ms = millis();
    }
//count incoming pulses from nx and update bank
  if((digitalRead(nx_pulse_pin) == LOW) && (count_nx == 0) && (menu_active == false)){
    bv_on(0);
    hopper_busy = 1;
    if(digitalRead(btn_print_LED) == HIGH){
      digitalWrite(btn_print_LED, LOW);
      }
    valid_pulses = 0;
    count_nx = 1;
    nx_ms = millis();                 //falling edge detected
    sprintf(line0,"    Numarare");    //Counting
    sprintf(line1,"Asteptati!..");    //Please wait..      
    upd_lcd();
    }
  if(count_nx == 1){
    if((valid_pulses == 0) && ((millis() - nx_ms) >= pulse_gap)){
      if(digitalRead(nx_pulse_pin) == LOW){
        count_nx = 2;
        valid_pulses = 1;
        prev_counter_state = 0;
        nx_timeout_ms = nx_ms = millis();
        }else{
          count_nx = 0;
          valid_pulses = 0;
          bv_on(1);
          nx_on(1);
          hopper_busy = 0;
          }
      }
    }
  if((count_nx == 2) && ((millis() - nx_ms) >= pulse_gap)){
    if(digitalRead(nx_pulse_pin) == HIGH && prev_counter_state == 0){
      nx_timeout_ms = millis();
      prev_counter_state = 1;
      }
    if(digitalRead(nx_pulse_pin) == LOW && prev_counter_state == 1){
      nx_timeout_ms = millis();
      prev_counter_state = 0;
      valid_pulses++;
      }
    if(millis() - nx_timeout_ms > pulse_timeout){
      count_nx = 3;
    }    
    nx_ms = millis();
    }
  if(count_nx == 3){
    bank += valid_pulses*pulse_multiplier_nx;                     //used for lcd display
    cash_sum_nx += valid_pulses*pulse_multiplier_nx;              //used for eprom write
    last_buy_nx += valid_pulses*pulse_multiplier_nx;              //used for printing
    write4bytes(uint8_t(0x00), mem_a_sum_nx, cash_sum_nx);
    count_nx = 0;
    valid_pulses = 0;
    nx_on(0);
    }
//count incoming pulses from bv and update bank
  if((digitalRead(bv_pulse_pin) == LOW) && (count_bv == 0) && (menu_active == false)){
    nx_on(0);
    hopper_busy = 1;
    if(digitalRead(btn_print_LED) == HIGH){
      digitalWrite(btn_print_LED, LOW);
      }
    valid_pulses = 0;
    count_bv = 1;
    bv_ms = millis();                 //falling edge detected
    sprintf(line0,"    Numarare");    //Counting
    sprintf(line1,"Asteptati!..");    //Please wait..
    upd_lcd();    
    }
  if(count_bv == 1){
    if((valid_pulses == 0) && ((millis() - bv_ms) >= pulse_gap)){
      if(digitalRead(bv_pulse_pin) == LOW){
        count_bv = 2;
        valid_pulses = 1;
        prev_counter_state = 0;
        bv_timeout_ms = bv_ms = millis();
        }else{
          hopper_busy = 0;
          count_bv = 0;
          valid_pulses = 0;
          bv_on(1);
          nx_on(1);
          }
      }
    }
  if((count_bv == 2) && ((millis() - bv_ms) >= pulse_gap)){
    if(digitalRead(bv_pulse_pin) == HIGH && prev_counter_state == 0){
      bv_timeout_ms = millis();
      prev_counter_state = 1;
      }
    if(digitalRead(bv_pulse_pin) == LOW && prev_counter_state == 1){
      bv_timeout_ms = millis();
      prev_counter_state = 0;
      valid_pulses++;
      }
    if(millis() - bv_timeout_ms > pulse_timeout){
      count_bv = 3;
    }    
    bv_ms = millis();
    }
  if(count_bv == 3){
    bank += valid_pulses*pulse_multiplier_bv;                   //used for lcd display
    cash_sum_bv += valid_pulses*pulse_multiplier_bv;            //used for eprom
    last_buy_nx += valid_pulses*pulse_multiplier_bv;            //used for printer
    write4bytes(uint8_t(0x00), mem_a_sum_bv, cash_sum_bv);
    count_bv = 0;
    valid_pulses = 0;
    bv_on(0);
    }
//bank value changed
  if(bank_tmp != bank && menu_active == false){
    if(bank < token_price && bank != 0){      //cash was sent in, but less than the token price
      sprintf(line0,"%lu,00 RON", bank);
      sprintf(line1,"Pret jeton: %u,00", token_price); //Token price is:
      upd_lcd();
      bank_tmp = bank;
      hopper_busy = 0;
      busy_mssg = 0;
    }
    if(bank >= token_price){
      hopper_busy = 1;
      token_counter += bank / token_price;
      token_sum += token_counter;
      last_tokens = token_counter;
      if(bank % token_price != 0){
        bank = bank % token_price;
        bank_tmp = 0;
        }else{
          bank = 0;
          }
      sprintf(line0,"   Eliberare");    //Dispensing
      sprintf(line1,"Asteptati!...");   //Please wait...     
      upd_lcd();
      delay(100);
      write4bytes(uint8_t(0x00), mem_a_token_sum, token_sum);
      drive_hopper();
      if(error_flag == false){            //returned with no error
        sprintf(line0,"   Multumim!");    //Thank you
        sprintf(line1,"  La revedere");   //Goodbye
        upd_lcd();
        delay(1500);
        receipt_available = 1;
        receipt_ms = millis();
        hopper_busy = 0;
        stby_forced = 1;
        return_from_service = true;
        }      
      }
    if(bank == 0){
      bank_tmp = bank;
      }
    }
//set-reset inhibits
  if(digitalRead(bv_busy_pin) == LOW && error_flag == false && menu_active == false){    
    if(digitalRead(nx_inh_pin) == HIGH){
      nx_on(0);
      if(digitalRead(btn_print_LED) == HIGH){
        digitalWrite(btn_print_LED, LOW);
        }
    }
    if(busy_mssg == 0){
      sprintf(line0,"   Procesare");    //Validator busy
      sprintf(line1,"Asteptati!.");     //Please wait.     
      upd_lcd();
      busy_mssg = 1;
      return_from_service = true;
    }
    }
  if(digitalRead(bv_busy_pin) == HIGH && error_flag == false && hopper_busy == 0 && menu_active == false){  //turn on nx if left in inhibit
    if(digitalRead(nx_inh_pin) == LOW){
      nx_on(1);
      }
    if(digitalRead(bv_inh_pin) == HIGH){
      bv_on(1);
      } 
    if(busy_mssg == 1){
      busy_mssg = 0;
      bank_tmp = 0;         //this forces the display to show remaining cash
      if(bank == 0){
        stby_forced = 1;
        }
      }
    }
//print functions
  if(millis() - receipt_ms > receipt_timeout && receipt_available == true){
    if(digitalRead(btn_print_LED) == HIGH){
        digitalWrite(btn_print_LED, LOW);
        }
    receipt_available = false;
    }
//print button pushed
  if((digitalRead(btn_print) == LOW) && (millis() - btn_print_ms > btn_cd) && (receipt_available == true)){   //print receipt
    delay(33);
    if(digitalRead(btn_print) == LOW){
      nx_on(0);
      bv_on(0);
      if(digitalRead(btn_print_LED) == LOW){
        digitalWrite(btn_print_LED, HIGH);
        }
      (*prnt_cnt)++;                    //increase before printing, so there won't be 0000 ticket
      Serial.println(*prnt_cnt);    //test
      write4bytes(uint8_t(0x00), mem_a_print_counter, *prnt_cnt);
      delay(100);
      if(error_flag == false){            //memory operation successful
        uint32_t temp_m = 0;
        sprintf(line0,"   Imprimare");      //Printing
        sprintf(line1,"Asteptati!");        //Please wait
        upd_lcd();
        //print it!
        clear_buf();
        prnt_char_set(0b111);                   //set to print double h,w with underline
        Serial.println("SC. Stebali Trans srl.");
        prnt_char_set(0);                       //set to print small chars
        Serial.println("pl. Salonta");          
        Serial.println("str. Menumorut nr. 2");  
        Serial.println("jud. Bihor");           
        Serial.println("CIF       RO10094495"); 
        prnt_char_set(0b01);                    //set to double h
        sprintf(print_line, "1 bucata jeton = %u,00 RON", token_price);
        Serial.println(print_line);
        prnt_char_set(0b11);
        sprintf(print_line, "%u buc X %u,00 RON", last_tokens, token_price);
        Serial.println(print_line);
        temp_m = last_tokens;
        temp_m = temp_m*token_price;
        sprintf(print_line, "Total %lu,00 RON", temp_m);
        Serial.println(print_line);
        prnt_char_set(0);
        //sprintf(print_line, "%u", *prnt_cnt);
        Serial.println(*prnt_cnt);
        sprintf(print_line,"2%03X-%02X-%02X %02X:%02X", time_arr[6], time_arr[5], time_arr[4], time_arr[2], time_arr[1]);
        Serial.println(print_line);
        prnt_lf(0x03);
        endcut_partial();
        delay(2500);
        prnt_char_set(0);           //just to be sure
        if(digitalRead(btn_print_LED) == HIGH){
          digitalWrite(btn_print_LED, LOW);
          }
        receipt_available = false;
        if(bank != 0){
          bank_tmp = 0;               //so it will display credit
          }else{
            stby_forced = 1;
            return_from_service = true;
            }
        }
      btn_print_ms = millis();
      }    
    }

//menu button pushed
  if((digitalRead(btn_menu) == LOW) && (millis() - btn_menu_ms > btn_cd) && hopper_busy == 0 && menu_active == false){   //set menu_active
    delay(53);
    if(digitalRead(btn_menu) == LOW){      
      nx_on(0);
      bv_on(0);
      delay(500);
      if(digitalRead(btn_menu) == LOW){
        if(digitalRead(btn_print_LED) == HIGH){
          digitalWrite(btn_print_LED, LOW);
          }
        menu_lvl = 1;
        menu_lvl_prev = 0;
        menu_active = true;      
        btn_menu_ms = menu_start_ms = millis();
        sprintf(line0,"    -Meniu-");
        sprintf(line1," ");     
        upd_lcd();
        delay(1000);
        menu_refresh = false;
        menu_forced_exit = false;
        }else{
          nx_on(1);
          bv_on(1);
          }
      }   
    }
//menu
  if(menu_active == true){
    if(menu_lvl != menu_lvl_prev){
      if(menu_lvl == 1){
        sprintf(line1,"Setari ora/data");   //Set date-time
        upd_lcd();
        if(menu_refresh == true){
          delay(500);
          menu_refresh = false;
          }
        }
      if(menu_lvl == 2){
        sprintf(line1,"Sters total");    //Delete all
        upd_lcd();
        if(menu_refresh == true){
          delay(500);
          menu_refresh = false;
          }
        }
      if(menu_lvl == 3){
        sprintf(line1,"Sters jetoane");   //Delete tokens
        upd_lcd();
        if(menu_refresh == true){
          delay(500);
          menu_refresh = false;
          }
        }
      if(menu_lvl == 4){
        sprintf(line1,"Sters plati Card");    //Delete cashless
        upd_lcd();
        if(menu_refresh == true){
          delay(500);
          menu_refresh = false;
          }
        }
      if(menu_lvl == 5){
        sprintf(line1,"Sters plati Cash");    //Delete cash
        upd_lcd();
        if(menu_refresh == true){
          delay(500);
          menu_refresh = false;
          }
        }
      if(menu_lvl == 6){
        sprintf(line1,"Configurare pret");    //Set price
        upd_lcd();
        if(menu_refresh == true){
          delay(500);
          menu_refresh = false;
          }
        }
      if(menu_lvl == 7){
        sprintf(line1,"Consultare date");   //View data
        upd_lcd();
        if(menu_refresh == true){
          delay(500);
          menu_refresh = false;
          }
        }      
      menu_lvl_prev = menu_lvl;
      }
    if(digitalRead(btn_1) == LOW && menu_lvl < 7){
      menu_lvl++;
      menu_start_ms = millis();
      menu_refresh = true;
      }
    if(digitalRead(btn_2) == LOW && menu_lvl > 1){
      menu_lvl--;
      menu_start_ms = millis();
      menu_refresh = true;
      }
    if(digitalRead(btn_menu) == LOW){
      delay(32);
      if(menu_btn_validator() == true){    //longpress accepted, enter menu according
          if(menu_lvl == 1){
            //set date and time
            settime();
            ok_mssg(1);
            menu_forced_exit = true;
            }
          if(menu_lvl == 2){
            //delete all
            token_sum = 0;
            cash_sum_nx = 0;
            cash_sum_bv = 0;
            *prnt_cnt = 0;
            write4bytes(uint8_t(0x00), mem_a_token_sum, token_sum);
            if(error_flag == false){
              write4bytes(uint8_t(0x00), mem_a_sum_nx, cash_sum_nx);
              if(error_flag == false){
                write4bytes(uint8_t(0x00), mem_a_sum_bv, cash_sum_bv);
                if(error_flag == false){
                  write4bytes(uint8_t(0x00), mem_a_print_counter, *prnt_cnt);
                  }
                }
              }            
            if(error_flag == false){                  //all 3 memory operations were successful
              ok_mssg(2);              
              }
            }
          if(menu_lvl == 3){
            //delete tokens
            token_sum = 0;
            write4bytes(uint8_t(0x00), mem_a_token_sum, token_sum);
            if(error_flag == false){                  //all memory operations were successful
              ok_mssg(2);              
              }
            }
          if(menu_lvl == 4){
            //delete cashless
            cash_sum_nx = 0;
            write4bytes(uint8_t(0x00), mem_a_sum_nx, cash_sum_nx);
            if(error_flag == false){                  //all memory operations were successful
              ok_mssg(2);              
              }
            }
          if(menu_lvl == 5){
            //delete cash
            cash_sum_bv = 0;
            write4bytes(uint8_t(0x00), mem_a_sum_bv, cash_sum_bv);
            if(error_flag == false){                  //all memory operations were successful
              ok_mssg(2);              
              }
            }
          if(menu_lvl == 6){
            //set price
            conf_flag = true;
            token_price_prev = token_price;
            sprintf(line0,"Pret jeton:");   //Token price
            sprintf(line1,"%u,00 RON", token_price);
            upd_lcd();
            while(conf_flag == true){
              if(digitalRead(btn_1) == LOW && token_price < 250){
                token_price++;
                menu_start_ms = millis();
                }
              if(digitalRead(btn_2) == LOW && token_price > 1){
                token_price--;
                menu_start_ms = millis();
                }
              if(token_price_prev != token_price){
                sprintf(line1,"%u,00 RON", token_price);
                upd_lcd();
                token_price_prev = token_price;
                delay(300);
                }
              if(digitalRead(btn_menu) == LOW){
                delay(32);
                if(menu_btn_validator() == true){   //longpress OK
                  //save value to ROM and exit
                  write4bytes(uint8_t(0x00), mem_a_price, token_price);   //function input is uint32_t, price is uint8_t! works now
                  ok_mssg(1);
                  conf_flag = false;
                  menu_forced_exit = true;
                  }
                menu_start_ms = millis();
                }
              if(millis() - menu_start_ms > menu_timeout){
                conf_flag = false;
                }
              }
            }
          if(menu_lvl == 7){
            //view data
            conf_flag = true;
            view_page_prev = view_page = 1;
            sprintf(line0,"Pret jeton:");     //Token price
            sprintf(line1,"%u,00 RON", token_price);
            upd_lcd();
            while(conf_flag == true){
              if(digitalRead(btn_1) == LOW && view_page < 5){
                view_page++;
                menu_start_ms = millis();
                }
              if(digitalRead(btn_2) == LOW && view_page > 1){
                view_page--;
                menu_start_ms = millis();
                }
              if(view_page_prev != view_page){
                if(view_page == 1){
                  sprintf(line0,"Pret jeton:");     //Token price
                  sprintf(line1,"%u,00 RON", token_price);
                  }else if(view_page == 2){
                    sprintf(line0,"Jetoaneeliberate");    //Tokens out:
                    sprintf(line1,"%lu buc", token_sum);
                    }else if(view_page == 3){
                      sprintf(line0,"Plata cu CARD:");    //Cashless in:
                      sprintf(line1,"%lu,00 RON", cash_sum_nx);
                      }else if(view_page == 4){
                        sprintf(line0,"Plata CASH");      //Cash in:
                        sprintf(line1,"%lu,00 RON", cash_sum_bv);
                        }else if(view_page == 5){
                          sprintf(line0,"nr. bon");      //print nr.
                          sprintf(line1,"%lu", *prnt_cnt);
                          }
                upd_lcd();
                view_page_prev = view_page;
                delay(300);
                }
              if(digitalRead(btn_menu) == LOW){
                delay(32);
                if(menu_btn_validator() == true){   //longpress OK
                  ok_mssg(0);
                  conf_flag = false;
                  menu_forced_exit = true;
                  }
                menu_start_ms = millis();
                }
              if(millis() - menu_start_ms > menu_timeout){
                conf_flag = false;
                }
              }
            }
          }      
      menu_start_ms = millis();
      }
    if((millis() - menu_start_ms > menu_timeout) || (menu_forced_exit == true)){        //shut down menu
      menu_active = false;
      if(bank != 0){
        bank_tmp = 0;               //so it will display credit
        }else{
          stby_forced = 1;
          }
      }
    }
//led showoff
  if((receipt_available == true) && ((millis() - led_ms) >= led_timer) && (hopper_busy == 0) && (menu_active == false)){
    if(ledstate == true){
      digitalWrite(btn_print_LED, HIGH);      
      }else{
        digitalWrite(btn_print_LED, LOW);
        }
    ledstate = !ledstate;
    led_ms = millis();
    }
}     //end of main loop
//delete OK message
void ok_mssg(uint8_t type){
  if(type == 1){
    sprintf(line0,"   Salvare OK");    //Save OK
    }else if(type == 2){
      sprintf(line0,"  Stergere OK");   //Delete OK
      }else if(type == 0){
        sprintf(line0,"     Iesire");      //Exiting
        }
  sprintf(line1," "); 
  upd_lcd();
  menu_forced_exit = true;
  delay(2000);
  }
//menu button longpress check
bool menu_btn_validator(){
  bool retval = false;
  if(digitalRead(btn_menu) == LOW){
    lcd.setCursor(13,0);
    lcd.print(">");
    lcd2.setCursor(13,0);
    lcd2.print(">");
    delay(1000);
    if(digitalRead(btn_menu) == LOW){
      lcd.print(">");
      lcd2.print(">");
      delay(1000);
      if(digitalRead(btn_menu) == LOW){
        lcd.print(">");
        lcd2.print(">");
        delay(300);
        retval = true;
        }
      }
    if(retval == false){            //transient, false button press
      lcd.setCursor(13,0);
      lcd.print("   ");
      lcd2.setCursor(13,0);
      lcd2.print("   ");
      }
  }
  return retval;
}
//update both LCD lines with line0 and line1 char arrays
void upd_lcd(){         //write line0 and line1 on LCD
  lcd.clear(); 
  lcd2.clear();         //clear is also home positioning
  lcd.print(line0); 
  lcd.setCursor(0,1);   //must move to second row
  lcd.print(line1);
  lcd2.print(line0);
  lcd2.setCursor(0,1);
  lcd2.print(line1);                                    
  }
//update time
void updatetime(){
  uint8_t diff = 0;
  uint8_t j;  
  raw_time();       //fills time_arr with actual data  
  for(j = 1; j < (sizeof(time_arr)/sizeof(time_arr[0])); j++){    //time_arr[0] is seconds, so j starts from 1
    if(time_arr[j] != time_arr_tmp[j]){
      diff++;
      }
    }
  if((time_arr[2] != time_arr_tmp[2]) && (firstboot == false)){      //1 hour passed, time to reset the LCDs
    lcds_reset = true;
    }
  if(diff != 0 || stby_forced == 1 || menu_forced_exit == true || (return_from_service == true && busy_mssg == false)){
    for(j = 0; j < (sizeof(time_arr)/sizeof(time_arr[0])); j++){
      time_arr_tmp[j] = time_arr[j];
      }    
    menu_forced_exit = false;
    return_from_service = false;
    sprintf(line0,"2%03X-%02X-%02X %02X:%02X", time_arr[6], time_arr[5], time_arr[4], time_arr[2], time_arr[1]);
    upd_lcd();      
      }
  if(firstboot == true){
    firstboot = false;
    }
  }
//fill up time array with data
void raw_time(){
  int idx = 0;
  uint8_t cnt = 0;
  byte temp = 0;
  bool rdy = 0;      
  
  while(cnt < 5 && rdy == 0){
    Wire.beginTransmission(rtc_addr); 
    Wire.write(0x00);                         // Set device to start read reg 0
    if(Wire.endTransmission() == 0){             
      Wire.requestFrom(rtc_addr, 7, 1);       // request 7 bytes from the DS3231 and release the I2C bus
      while(Wire.available() > 0){            // read the bytes into an array
        temp = Wire.read();                   // read each byte from the register
        time_arr[idx] = temp;                 // store each single byte in the array
        idx++;
    }
    rdy = 1;
    error_code = 0;                       //in case there is a previous error read
    error_flag = false;
    }else{
    if(cnt == 4){ //last run
      for(uint8_t i = 0; i < (sizeof(time_arr)/sizeof(time_arr[0])); i++){
        time_arr[i] = 0;      
        }
      }    
    error_code = 9;                      // RTC read error
    error_flag = true;
    cnt++;
    delay(10);
    }
  }      
}
//drive hopper
void drive_hopper(){
  uint32_t motor_start_ms = millis();
  uint8_t dispense_lvl = 0;
  uint8_t sent_tokens = 0;
  
  while(token_counter > 0 && ((millis() - motor_start_ms) < token_allowed_delay)){   //must be enough to dispense 1 token, otherwise error
    if(dispense_lvl == 0){
      digitalWrite(hopper_motor_pin, HIGH);
      dispense_lvl = 1;
      }
    if(dispense_lvl == 1){
      if(digitalRead(hopper_opto_pin) == LOW){      //falling edge detected
        delay(10);
        if(digitalRead(hopper_opto_pin) == LOW){    //still low
          digitalWrite(hopper_motor_pin, LOW);
          dispense_lvl = 2;
          }
        }
      }
    if(dispense_lvl == 2){                          //wait for rising edge
      if(digitalRead(hopper_opto_pin) == HIGH){     //rising edge detected
        delay(20);
        if(digitalRead(hopper_opto_pin) == HIGH){   //coin left the opto gate
          sent_tokens++;
          lcd.setCursor(13,0);
          lcd.print(sent_tokens);
          lcd2.setCursor(13,0);
          lcd2.print(sent_tokens);
          token_counter--;
          dispense_lvl = 0;
          motor_start_ms = millis();
          }
        }
      }
    }
  if(digitalRead(hopper_motor_pin) == HIGH){
    digitalWrite(hopper_motor_pin, LOW);
  }
  if(token_counter != 0){                         //timer overflow, hopper error
    error_code = 6;
    error_flag = true;
    }
  delay(800);         //only to make last sent_token display readable
  }
//bill validator inhibit
void bv_on(bool state){
  if (state == true){
    digitalWrite(bv_inh_pin, LOW);
    }else{
      digitalWrite(bv_inh_pin, HIGH);
      }
  }
//nx inhibit
void nx_on(bool state){
  if (state == true){
    digitalWrite(nx_inh_pin, HIGH);
    }else{
      digitalWrite(nx_inh_pin, LOW);
      }
  }
//check if rom is busy
uint8_t chk_rom_busy(void){
  uint8_t retVal = 4;
  Wire.beginTransmission(eeprom_addr);
  retVal = Wire.endTransmission();                //returns 0 if OK, >=1 otherwise
  return retVal;                                  //return busy status, 0 is not busy
}
//write rom
void write4bytes(uint8_t addr_h, uint8_t addr_l, uint32_t datacore){    //returns void, only sets global error flags on fault
  uint8_t retries = 0;
  uint8_t data_out[4];
  while(chk_rom_busy() != 0 && retries < 5){
    delay(10);
    retries++;
    }
  if(retries == 5){
    error_flag = true;
    error_code = 7;
    }else{
      data_out[3] = uint8_t((datacore & 0xFF));
      data_out[2] = uint8_t(((datacore >> 8) & 0xFF));
      data_out[1] = uint8_t(((datacore >> 16) & 0xFF));
      data_out[0] = uint8_t(((datacore >> 24) & 0xFF));
      Wire.beginTransmission(eeprom_addr);
      Wire.write(addr_h);                 //First Word Address
      Wire.write(addr_l);                 //Second Word Address
      for(uint8_t k = 0; k < 4; k++){     //MSB first!
        Wire.write(data_out[k]);
        }                          
      if(Wire.endTransmission() != 0){    // End I2C transmission, 0 is OK, other number is error
        error_flag = true;
        error_code = 7;
        }
      }      
  }
//read rom
uint32_t read4bytes(uint8_t addr_h, uint8_t addr_l){
  uint32_t retv = 0;
  uint32_t temp;
  uint8_t retries = 0;
  uint8_t received_bytes;
  while(chk_rom_busy() != 0 && retries < 5){
    delay(10);
    retries++;
    }
  if(retries == 5){
    retv = 0;
    error_flag = true;
    error_code = 8;
    }else{
      Wire.beginTransmission(eeprom_addr);
      Wire.write(addr_h);            // Send memory address bits [15:8]
      Wire.write(addr_l);            // Send memory address bits [7:0]
      Wire.endTransmission(0);     // Send restart condition
      Wire.requestFrom(eeprom_addr, 4);        
      received_bytes = Wire.available();
      if(received_bytes == 4){
        uint8_t k = 3;
        for(uint8_t j = 0; j < 4; j++){
          temp = Wire.read();
          temp = (temp << (8*k));
          retv += temp;
          k--;
          }
        }else{
          retv = 0;
          error_flag = true;
          error_code = 8;
          }
      }      
  return retv;
  }
//printer functions
void clear_buf(){                     //clear line buffer
  byte tosend[] = {0x13, 0x43};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void codepage_1250(){                     //set code page to display special chars
  byte tosend[] = {0x1B, 0x74, 0x03};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void prnt_set_center(){                     //set printer to print center aligned
  byte tosend[] = {0x1B, 0x61, 0x01};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void prnt_char_set(uint8_t mode){    //set height, width, underline. 000 is all off, 001 is double height, 010 is double width, 100 is underlined
  uint8_t bin_out = 0;
  if(mode == 0){                //normal small characters
    bin_out = 0;
    }else if(mode == 0b1){      //double height
      bin_out = 0b10000;
      }else if(mode == 0b10){      //double width
        bin_out = 0b100000;
        }else if(mode == 0b100){      //underline
          bin_out = 0b10000000;
          }else if(mode == 0b11){      //double height and width
            bin_out = 0b110000;
            }else if(mode == 0b101){      //double height with underline
              bin_out = 0b10010000;
              }else if(mode == 0b110){      //double width with underline
                bin_out = 0b10100000;
                }else if(mode == 0b111){      //double height and width with underline
                  bin_out = 0b10110000;
                  }
  byte tosend[] = {0x1B, 0x21, bin_out};  // 0b00110000
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void prnt_lf(byte val){                  //print and line feed given lines
  byte tosend[] = {0x1B, 0x64, val};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void prnt_lfcr(){                  //lf cr
  byte tosend[] = {0x0A, 0x0D};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void prnt_soft_reset(){                  //Soft reset
  byte tosend[] = {0x11};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void endcut_full(){                                        //full paper cut
  byte tosend[] = {0x1B, 0x69};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }

void endcut_partial(){                                        //partial paper cut
  byte tosend[] = {0x1B, 0x6D};
  Serial.write(tosend, sizeof(tosend)/sizeof(tosend[0]));
  }


void err_blinky(int rate){
  for (int i = 0; i < rate; i++){
    digitalWrite(btn_print_LED, 1);
    delay((rate*500)/((rate*2)-1));
    digitalWrite(btn_print_LED, 0);
    delay((rate*500)/((rate*2)-1));
    }
    delay(1700);  
  }
//set date-time
void settime(){    
  int curs = 0;
  int vc = 0;
  int q;
  int validpos[5] = {3,6,9,12,15};
  int ps_in_t_arr[5] = {6,5,4,2,1};
  int min_vals[5] = {0,1,1,0,0};
  int max_vals[5] = {99,12,31,23,59};
  int t_arr_dec[5];
  bool set_ok = false;
  bool val_chgd;
   
  for(q = 0; q < 5; q++){
    t_arr_dec[q] = bcdToDec(time_arr[ps_in_t_arr[q]]);
    }     
  sprintf(line0,"2%03d-%02d-%02d %02d:%02d", t_arr_dec[0], t_arr_dec[1], t_arr_dec[2], t_arr_dec[3], t_arr_dec[4]);
  upd_lcd();
  lcd.setCursor(validpos[curs], 0);
  lcd.blink();
  lcd2.setCursor(validpos[curs], 0);
  lcd2.blink(); 
  delay(1000);     
  while(set_ok == false){                              
    if(digitalRead(btn_1) == LOW || digitalRead(btn_2) == LOW || digitalRead(btn_menu) == LOW){    //crude debounce
      if(vc >= 2000){
        vc = 200;
        }
      vc++;
      if(curs == 4 && vc >= 300){
        set_ok = true;                                  
       }
      delay(5);
          if(vc == 10 && t_arr_dec[curs] <= max_vals[curs] && t_arr_dec[curs] >= min_vals[curs]){
            if(digitalRead(btn_1) == LOW){
              if(t_arr_dec[curs] < max_vals[curs]){
                t_arr_dec[curs]++;
                val_chgd = 1;
                }
              }else if(digitalRead(btn_2) == LOW){
                if(t_arr_dec[curs] > min_vals[curs]){
                  t_arr_dec[curs]--;
                  val_chgd = 1;
                  }                                          
                }else if(digitalRead(btn_menu) == LOW){
                  if(curs < 4){ 
                    curs++;
                    val_chgd = 1;
                    }
                  }
            }
      }else{
        vc = 0;
        }
    if(val_chgd == 1){
      val_chgd = 0;                                
      sprintf(line0,"2%03d-%02d-%02d %02d:%02d", t_arr_dec[0], t_arr_dec[1], t_arr_dec[2], t_arr_dec[3], t_arr_dec[4]);
      upd_lcd();
      lcd.setCursor(validpos[curs],0);
      lcd2.setCursor(validpos[curs],0);                                                               
      }
    }
  //write values back to RTC
  Wire.beginTransmission(rtc_addr);
  Wire.write(0x01);
  Wire.write(decToBcd(t_arr_dec[4]));
  Wire.write(decToBcd(t_arr_dec[3]));
  Wire.write(time_arr[3]);                //day won't change - may be wrong?
  Wire.write(decToBcd(t_arr_dec[2]));
  Wire.write(decToBcd(t_arr_dec[1]));
  Wire.write(decToBcd(t_arr_dec[0]));
  if(Wire.endTransmission() != 0){
    error_code = 10;
    error_flag = true;                      
    }      
  lcd.noBlink();
  lcd2.noBlink();
    }
//conversions
byte bcdToDec(byte val){
  return((val/16*10) + (val%16));
}

byte decToBcd(byte val){
  return((val/10*16) + (val%10));
}

I know for sure you have a memory corruption issue. The following line tries to stuff more than 20 characters into print_line:

sprintf(print_line, "1 bucata jeton = %u,00 RON", token_price);

This can cause all kinds of weirdness. Fix that first.

Are you sure that when you use sprintf() that you are never putting more than 20 characters in the print_line buffer ?

Have you tried using snprintf() instead ?

@ToddL1962 , @UKHeliBob guys, I cant' believe how fast you are with remote diagnosis. I gave the print_line char array 40 slots to play with (instead of 20) and it immediately printed the right number! Thank you very much, I thought it would be a much more nuance problem.
UKHeliBob: I never heard of snprintf(), but will look into it.

snprintf() is the "safe" version of sprintf(). In my line of work any function that allows you to write past the end of a buffer is considered "unsafe".

It was not remote once we could see the whole of your sketch and could see how bit your print buffer was. Two mouse clicks and it was in an editor for examination

Looks like I was living on the edge the whole time! :slight_smile:
I used sprintf() for many years, mainly to write to 16x2 LCDs, and there you have 16 chars/line plus endline character, so 17 was always enough. As for safeness, I must adapt to this snprintf(), because I don't want another problem like this.

I've always worked on safety critical systems where an error can cause loss of equipment or life. All of my code is statically scanned for memory leaks, unsafe functions, and other nasty stuff.

Just being curious, do you scan your code for memory leaks by hand, or is there some method for that?

Well, for small programs a visual inspection would suffice. At work our code exceeds 1 million lines so we typically use Fortify Static Code Analyzer.

There are a lot of "static analysis" tools that claim to spot bugs like these. They're generally pretty expensive. I would expect that there are some OSSW tools that would detect the easy cases of "sprintf more than 20 characters to a 20 character buffer" Hmm... Given source code:

#include <stdio.h>

int main() {
  char buffer[10];
  sprintf(buffer, "more than 10 characters");
}

cpplint says simply "never use sprintf()" (grr. That's style, not an actual check!)
cppcheck comes up with a more useful:

foo.c:5:11: error: Buffer is accessed out of bounds: buffer [bufferAccessOutOfBounds]
  sprintf(buffer, "more than 10 characters");
          ^

Those are both OSSW. I don't know if there are tools that are Arduino-aware...
See also List of tools for static code analysis - Wikipedia

Thank you for the links, I will look into them.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.