[SOLVED] 20x4 LCD code partially not working

Hi,

I didn't write this code. It works as expected except for that part showing the battery icon. Currently it doesn't show anything. If I substitute with and ASCII character it prints it correctly.
I have voltage on the respective analog pin and it's reading it correctly.
Any help would be appreciated.

#include <LiquidCrystal.h>
#include <string.h>
#include <stdio.h>

// TIMER ASSIGNMENT
// 8BIT  TIMER0 - PWM output at 7.8 kHz to pin 6 (OCOA)
// 16BIT TIMER1 - input time measurement (1 reading = 8 full periods of 3150 Hz test tone)
// 8BIT  TIMER2 - 3150 Hz test tone (50% duty cycle pulses to R-C-diode limiter) to pin 11 (OC2A)

// ARDUINO NANO PIN ASSIGNMENT
// TOP SIDE ===========================================================
// 12 PB4     *** LCD DB4
// 11 PB3     *** OC2A = output 3150Hz to analogue output
// 10 PB2     *** LCD DB5
// 09 PB1     ***  
// 08 PB0     *** ICP1 = audio pulse input
// 07 PD7     *** 
// 06 PD6     *** OC0A = output 8-bit PWM to meter (nano: pin9)
// 05 PD5     *** LCD RS 
// 04 PD4     *** LCD EN
// 03 PD3     *** LCD DB7
// 02 PD2     *** LCD DB6
// 01 PD1 TX  *** 
// 00 PD0 RX  *** 
// BOTTOM SIDE ========================================================
// 13 PB5     *** 
// A0 PC0     *** debug pin
// A1 PC1     *** 
// A2 PC2     *** 
// A3 PC3     *** 
// A4 PC4     *** Switch3-x3
// A5 PC5     *** Switch2-x10
// A6 PC4     *** Battery sense
// A7 PC5     *** Switch1-DIN-RMS

// Timing constants. The sampling period equals eight input audio periods, thus the sampling rate is merely 393.75 Hz.
// Measuring each input period (around 0.3 ms or 5K clock cycles) is technically possible, but the resolution will be to coarse to be useful

const long     clock_frequency=16000000L;   
const uint8_t  pulses_per_pack=8;
const float    fr_dividend=128000000.0; /* clock_frequency*pulses_per_pack adjusted for actual clock speed*/
const float    std_signal_frequency_float=3150.0;
const uint16_t std_signal_frequency_int=3150;
const uint16_t std_pack_clocks=40635; /* pulses_per_pack*clock_frequency/std_signal_frequency) */ 
const float    std_sampling_frequency=393.75;
const float    std_pack_period=0.0025397; /* seconds per pack */
const uint16_t toofast_pack_clocks=27090; /* -1/2 std speed */
const uint16_t tooslow_pack_clocks=60952; /* +1/2 std speed */
const uint16_t nosignal_indicator_delay=200;
const uint16_t wrongspeed_indicator_delay=200;

// IIR DSP filter settings
// Alphas are variable, so they can be adjusted if input frequency (and thus the sampling rate) differs considerably from the standard.
// Narrowband channel filters trimmed in a spreadsheet for a center maximum at 4 Hz per DIN 45507

const float    instant_deviation_limit_pos=0.0999;
const float    instant_deviation_limit_neg=-0.0999;
const float    dispersion_limit=0.0499; 
const float    overflow_value=0.099;
      
const float    LPF_input_freq=200.0; /* Hz */  
const float    LPF_input_alpha=0.7; 
const float    LPF_input_beta=0.3; /* =1-? */      
const float    LPF_avg1_freq=0.2;  /* Hz */    
const float    LPF_avg1_alpha=0.0032;
const float    LPF_avg1_beta=0.9968; /* =1-? */      
const float    LPF_avg2_freq=0.2; /* Hz */     
const float    LPF_avg2_alpha=0.0032;
const float    LPF_avg2_beta=0.9968; /* =1-? */    
const float    HPF_wide_freq=0.2; /* Hz */     
const float    HPF_wide_beta=0.9968; /* =1-? */
const float    HPF_narrow_freq=1; /* Hz */
const float    HPF_narrow_beta=0.9842; /* =1-? */
const float    LPF_narrow_freq=10; /* Hz */
const float    LPF_narrow_alpha=0.15;
const float    LPF_narrow_beta=0.85; /* =1-? */     

const float    adjust_input=1.0;     /* placeholder */
const float    adjust_wide=1.0;      /* placeholder */ 
const float    adjust_narrow=1.0;    /* 1.0606 from spreadsheet */

const float    RMS_alpha=0.005;
const float    RMS_beta=0.995;
const float    decay_output_tau=0.5;  /* tau,seconds */
const float    decay_output_alpha=0.001; 
const float    decay_output_beta=0.999; /* =1-? */

// Reading input stream of pulses - IIR DSP filter variables
volatile float raw_input_frequency;
float          LPF_input_filter;
float          LPF_avg1_filter;
float          average_frequency; /* output of LPF_avg2 */
float          instant_deviation;
float          HPF_wide_previous_in;
float          HPF_wide_filter;
float          HPF_narrow_previous_in;
float          HPF_narrow_filter;
float          LPF_narrow_filter;
float          wide_dispersion;   /* unsigned abs value */
float          narrow_dispersion; /* unsigned abs value */
volatile float narrow_RMS_sumofsquares;

// buffering frequency, wideband and narrowband dispersion so that frequent updates won't interfere with slow LCD regen
volatile float buf_FR;
volatile float buf_WB;
volatile float buf_NB; 
volatile float buf_NB_RMS; 

// Processing input stream of pulses - flags
volatile uint8_t  loop_lockout; /* prohibits LCD redraw until a complete new capture/calculation is done */
uint8_t           edge_count; /* count rising fronts in a pack */
uint8_t           timer_overflow; /* no-input-signal flag */
uint16_t          timer_overflow_countdown; /* delayed copy of no-signal flag for blinker */
uint8_t           timer_wrongspeed; /* signal present but wrong speed flag */
uint16_t          timer_wrongspeed_countdown; /* too-fast-signal flag */
volatile uint16_t buffer_reading; /* state of timer0 at the time of n-th capture interrupt */
uint8_t           dispersion_overflow_flag;


// LCD in 4-bit mode 
const uint8_t  lcd_RS=5;
const uint8_t  lcd_EN=4;
const uint8_t  lcd_DB4=3;
const uint8_t  lcd_DB5=2;
const uint8_t  lcd_DB6=9;
const uint8_t  lcd_DB7=7;
LiquidCrystal  lcd(lcd_RS, lcd_EN, lcd_DB4, lcd_DB5, lcd_DB6, lcd_DB7); 
uint8_t        show_RMS; /* false: show DIN 45507 */
const uint8_t  lcd_chars=20;

// Display scale bands for 20x4 alphanumeric LCD (line1)
const size_t   x10_pin=A5;
const size_t   x3_pin=A4;
const size_t   RMS_pin=A3;
const size_t   debug_pin=A0;
const uint8_t  meter_bands=4;
const float    meter_band_limit[meter_bands]={ 0.00100, 0.00300, 0.0100, 0.0300 };
const char     meter_band_texts[meter_bands][lcd_chars+1]={
               "0   .03%  .06%   .1%",
               "0    .1%   .2%   .3%",
               "0    .3%   .6%    1%",
               "0     1%    2%    3%" };
const uint8_t  error_message_position=4;
const char     meter_band_nosignal_message[]  ="  NO SIGNAL";
const char     meter_band_wrongspeed_message[]="  FREQUENCY?";
const char     meter_band_overflow_message[]  ="  OVERFLOW ";

// Display bargraph animation
const char     bargraph_off=0x80;
const char     bargraph_on=0xFF;

// Battery voltage monitor
const uint8_t  battery_adc_pin=A2;
const uint8_t  battery_states=5;
uint8_t        battery_state; /* 0=low ... 4=high */
const char     battery_state_icon[battery_states]={ 0x9F, 0x9E, 0x9D, 0x9C,0x9B };
const uint16_t battery_state_lowest_reading[battery_states]={ 0, 680, 720, 760, 820 };

void process_reading()
{
volatile float old, val;
      dispersion_overflow_flag=0;
 /* extract and denoise instant frequency from buffered timer reading ======================================= */           
      raw_input_frequency=fr_dividend/(float(buffer_reading));   
      old=LPF_input_filter; 
      LPF_input_filter=raw_input_frequency*LPF_input_alpha+old*LPF_input_beta;
 /* two LPFs to extract average frequency =================================================================== */      
      old=LPF_avg1_filter;            
      LPF_avg1_filter=LPF_input_filter*LPF_avg1_alpha+old*LPF_avg1_beta;
      old=average_frequency;                     
      average_frequency=LPF_avg1_filter*LPF_avg2_alpha+old*LPF_avg2_beta;
 /* extract deviation (relative to average frequency ========================================================= */     
      HPF_wide_previous_in=instant_deviation;  /* saving former value for HPF-wide first */
      instant_deviation=(LPF_input_filter/average_frequency)-1;
      if (instant_deviation>instant_deviation_limit_pos) { instant_deviation=instant_deviation_limit_pos; }
        else if (instant_deviation<instant_deviation_limit_neg) { instant_deviation=instant_deviation_limit_neg; }                            
 /* wideband channel HPF ===================================================================================== */            
      old=HPF_wide_filter;            
      HPF_wide_filter=HPF_wide_beta*old+(instant_deviation-HPF_wide_previous_in); /* y[n]=?y[n?1]+x[n]?x[n?1] */
  /* narrowband channel HPF+LPF ============================================================================== */                   
      HPF_narrow_previous_in=old;
      old=HPF_narrow_filter;          
      HPF_narrow_filter=HPF_narrow_beta*old+(HPF_wide_filter-HPF_narrow_previous_in); /* y[n]=?y[n?1]+x[n]?x[n?1] */      
      old=LPF_narrow_filter;            
      LPF_narrow_filter=HPF_narrow_filter*LPF_narrow_alpha+old*LPF_narrow_beta;
 /* wideband channel "precision rectifier" =================================================================== */ 
      val=(abs(HPF_wide_filter))*adjust_wide;                        
      if (val>dispersion_limit) { val=dispersion_limit; dispersion_overflow_flag=1; }  
      else { if (val>=wide_dispersion) { wide_dispersion=val; } else { wide_dispersion=wide_dispersion*decay_output_beta; } }     
 /* narrowband channel "precision rectifier" ================================================================= */  
      val=adjust_narrow*abs(LPF_narrow_filter);
      if (val>dispersion_limit) { val=dispersion_limit; dispersion_overflow_flag=1; } 
      if (val>wide_dispersion) { val=wide_dispersion; } 
      else { if (val>=narrow_dispersion) { narrow_dispersion=val; } else { narrow_dispersion=narrow_dispersion*decay_output_beta; } }                                          
/* narrowband RMS uses val calculated just above! */
      old=narrow_RMS_sumofsquares;
      narrow_RMS_sumofsquares=val*val*RMS_alpha+old*RMS_beta; 
      buf_NB_RMS=sqrt(narrow_RMS_sumofsquares);     
 /* buffering and other housekeeping ========================================================================= */          
      buf_FR=average_frequency;
      buf_WB=wide_dispersion;  
      buf_NB=narrow_dispersion;   
      loop_lockout=0;                                                      
}

ISR(TIMER1_CAPT_vect)
{ 
  digitalWrite(debug_pin,1);
  edge_count++; 
  /* upon the 8-th upward pulse front record the timing */
  if (edge_count>=pulses_per_pack)
  { 
    TCNT1=0; 
    edge_count=0; 
    timer_overflow=0; 
    if (timer_overflow_countdown) timer_overflow_countdown--; 
    buffer_reading=ICR1; 
    if((buffer_reading>toofast_pack_clocks)&&(buffer_reading<tooslow_pack_clocks)) 
    /* normal speed */
    { timer_wrongspeed=0;  if (timer_wrongspeed_countdown) timer_wrongspeed_countdown--;  }
    else 
    /* wrong speed */
    { buffer_reading=toofast_pack_clocks; timer_wrongspeed=1;  timer_wrongspeed_countdown=wrongspeed_indicator_delay; }
    process_reading();  
  }   
  digitalWrite(debug_pin,0);  
}

ISR(TIMER1_OVF_vect)
{ 
  /* overflow = no signal in = display exception */  
  edge_count=0;
  timer_overflow=1;
  timer_overflow_countdown=nosignal_indicator_delay;
  buffer_reading=std_pack_clocks;
  process_reading();
}

void setup() 
{
/* TIMER0 8-bit fast pwm at around 8 kHz */
   noInterrupts();
   TCCR0A=(1<<COM0A1) | (1<<WGM01) | (1<<WGM00);  
   TCCR0B=(1<<CS01) ;
   OCR0A =22;
   pinMode(6, OUTPUT);
/* TIMER2 8-bit 3150 Hz oscillator (actually 3.13 kHz) */   
   TCCR2A=(1<<COM2A0) | (1<<WGM21);  
   TCCR2B=(1<<WGM22) | (1<<CS21) | (1<<CS20);
   OCR2A =79;
   pinMode(11, OUTPUT);
/* TIMER 1 16-bit reading on input pulses */
   TCCR1A=0;
   TCCR1B=(1<<ICNC1) | (1<<ICES1) | (1<<CS10);
   TIMSK1=(1<<ICIE1) | (1<<TOIE1); 
   pinMode(8, INPUT);
/* reset data */ 
   raw_input_frequency=std_signal_frequency_float;
   LPF_input_filter=std_signal_frequency_float;
   LPF_avg1_filter=std_signal_frequency_float;
   average_frequency=std_signal_frequency_float; 
   instant_deviation=0.0;
   HPF_wide_previous_in=0.0;
   HPF_wide_filter=0.0;
   HPF_narrow_previous_in=0.0;
   HPF_narrow_filter=0.0;
   LPF_narrow_filter=0.0;
   wide_dispersion=0.0;   
   narrow_dispersion=0.0; 
   buf_FR=std_signal_frequency_float;
   buf_NB=0.0;
   buf_WB=0.0;  
   buf_NB_RMS=0.0;
/* reset flags */   
   loop_lockout=1;  
   edge_count=0;
   dispersion_overflow_flag=0;
   timer_overflow=1; 
   timer_overflow_countdown=nosignal_indicator_delay;
   timer_wrongspeed=1; 
   timer_wrongspeed_countdown=wrongspeed_indicator_delay;     
   show_RMS=0;
/* housekeeping */
   pinMode(battery_adc_pin, INPUT);
   pinMode(debug_pin, OUTPUT);
   pinMode(RMS_pin, INPUT_PULLUP);
   pinMode(x10_pin, INPUT_PULLUP);
   pinMode(x3_pin, INPUT_PULLUP);
   lcd.begin(20, 4);    
   interrupts();
}

void loop() 
{ 
   char lcd_line[lcd_chars+1];
   show_RMS=digitalRead(RMS_pin);
   while (loop_lockout) delayMicroseconds(1);
/* LINE 1 ============================================================================================ */
   /* get scale setting */
   uint8_t current_meter_band=0;
   if (!digitalRead(x10_pin)) { current_meter_band=current_meter_band+2; }
   if (!digitalRead(x3_pin))  { current_meter_band++; }
   /* fill string buffer */
   strncpy(lcd_line, &(meter_band_texts[current_meter_band][0]), lcd_chars);
   char *error_message=0;
   if (timer_overflow_countdown) { error_message=meter_band_nosignal_message; } 
      else { if (timer_wrongspeed_countdown) { error_message=meter_band_wrongspeed_message; }
           else { if (dispersion_overflow_flag) { error_message=meter_band_overflow_message; } } }
   if (error_message) strncpy(lcd_line+error_message_position, error_message, strlen(error_message));
   lcd_line[lcd_chars]=0;
   lcd.setCursor(0, 0); 
   lcd.print(lcd_line);    
/* LINE 2 ============================================================================================ */
   float block_size=meter_band_limit[current_meter_band]/lcd_chars;
   volatile float val;
   if (show_RMS) { val=buf_NB_RMS; } else { val=buf_WB; }
   uint16_t blocks=uint16_t(val/block_size+.5);
   if (blocks>lcd_chars) blocks=lcd_chars;
   for (byte i=0; i<lcd_chars; i++) lcd_line[i]=bargraph_off;
   for (byte i=0; i<blocks; i++) lcd_line[i]=bargraph_on; 
   lcd_line[lcd_chars]=0;
   lcd.setCursor(0, 1); 
   lcd.print(lcd_line);   
/* LINE 3 ============================================================================================ */
   blocks=uint16_t(buf_NB/block_size+.5);
   if (blocks>lcd_chars) blocks=lcd_chars;
   for (byte i=0; i<lcd_chars; i++) lcd_line[i]=bargraph_off;
   for (byte i=0; i<blocks; i++) lcd_line[i]=bargraph_on; 
   lcd_line[lcd_chars]=0;
   lcd.setCursor(0, 2); 
   lcd.print(lcd_line);       
/* LINE 4 ============================================================================================ */
   uint16_t adc=analogRead(battery_adc_pin);
   for (uint8_t i=0; i<battery_states ;i++) { if (adc>=battery_state_lowest_reading[i]) { battery_state=i; } }
   dtostrf(val*100, 4, 2, &(lcd_line[1]));
   dtostrf(buf_NB*100,      4, 2, &(lcd_line[8]));   
   dtostrf((buf_FR+.25),          4, 2, &(lcd_line[15]));
   if (show_RMS) { lcd_line[0]='R'; } else { lcd_line[0]='W'; };
   lcd_line[ 5]='%';
   lcd_line[ 6]=' ';
   lcd_line[ 7]='N';
   lcd_line[12]='%';
   lcd_line[13]=' ';
   lcd_line[14]='F';
   lcd_line[19]=battery_state_icon[battery_state];
   lcd_line[20]=0;
   lcd.setCursor(0, 3); 
   lcd.print(lcd_line); 
   // Inside the loop() function

/* set wait-for-buffer flag =========================================================================== */    
   loop_lockout=1;  
}

probably this

is not compatible with the your LCD's character set

do you have a link to your LCD?

It's an LCD I got from Aliexpress:

https://www.aliexpress.com/item/1005006336854741.html

Characters on those Ali pics are not shown correctly for the blue version which I have.

Those have basic charset, typical hd44780 driven LCD

They usually have one of these charset

so no battery icon is available as a char entry code

you would need to use custom chars and modify a bit the code of the program

There are online character generator and I would suggest to use this library

Thanks for your suggestion. I tried this LCD Custom Character Generator:
LCD 1602 2004 Custom Character – Lonely Binary
but no matter of the string data it outputs 4 horizontal lines.
If I understand correctly { 0x9F, 0x9E, 0x9D, 0x9C,0x9B } is supposed to draw lines of top of each other corresponding to the battery voltage level?

I did not read it like that

this is the code

My guess is that 0x9F is a predefined character in their LCD's charset that represent an empty battery and 0x9B represents a full battery

when they update the screen they do

so they go get one of those 5 values based on the battery_state variable which goes from 0 to 4

what you would need to do with your code is create 5 battery icons, add them as custom chars from empty (0) to full (4) and then remove the battery_state_icon array and modify the way the screen is filled by using lcd.write(battery_state); where you want to print the custom char

1 Like

I get your idea. I can generate 5 battery icons but it would be too complicated for me to integrate it in the existing code. I was hoping for an easy fix but it looks like this is not the case.

It’s an easy enough fix.

Build the 5 icons, share the code and I’ll see if I can update your code

#include <LiquidCrystal.h>
#include <string.h>
#include <stdio.h>

// TIMER ASSIGNMENT
// 8BIT  TIMER0 - PWM output at 7.8 kHz to pin 6 (OCOA)
// 16BIT TIMER1 - input time measurement (1 reading = 8 full periods of 3150 Hz test tone)
// 8BIT  TIMER2 - 3150 Hz test tone (50% duty cycle pulses to R-C-diode limiter) to pin 11 (OC2A)

// ARDUINO NANO PIN ASSIGNMENT
// TOP SIDE ===========================================================
// 12 PB4     *** LCD DB4
// 11 PB3     *** OC2A = output 3150Hz to analogue output
// 10 PB2     *** LCD DB5
// 09 PB1     ***  
// 08 PB0     *** ICP1 = audio pulse input
// 07 PD7     *** 
// 06 PD6     *** OC0A = output 8-bit PWM to meter (nano: pin9)
// 05 PD5     *** LCD RS 
// 04 PD4     *** LCD EN
// 03 PD3     *** LCD DB7
// 02 PD2     *** LCD DB6
// 01 PD1 TX  *** 
// 00 PD0 RX  *** 
// BOTTOM SIDE ========================================================
// 13 PB5     *** 
// A0 PC0     *** debug pin
// A1 PC1     *** 
// A2 PC2     *** 
// A3 PC3     *** 
// A4 PC4     *** Switch3-x3
// A5 PC5     *** Switch2-x10
// A6 PC4     *** Battery sense
// A7 PC5     *** Switch1-DIN-RMS

// Timing constants. The sampling period equals eight input audio periods, thus the sampling rate is merely 393.75 Hz.
// Measuring each input period (around 0.3 ms or 5K clock cycles) is technically possible, but the resolution will be to coarse to be useful

const long     clock_frequency=16000000L;   
const uint8_t  pulses_per_pack=8;
const float    fr_dividend=128000000.0; /* clock_frequency*pulses_per_pack adjusted for actual clock speed*/
const float    std_signal_frequency_float=3150.0;
const uint16_t std_signal_frequency_int=3150;
const uint16_t std_pack_clocks=40635; /* pulses_per_pack*clock_frequency/std_signal_frequency) */ 
const float    std_sampling_frequency=393.75;
const float    std_pack_period=0.0025397; /* seconds per pack */
const uint16_t toofast_pack_clocks=27090; /* -1/2 std speed */
const uint16_t tooslow_pack_clocks=60952; /* +1/2 std speed */
const uint16_t nosignal_indicator_delay=200;
const uint16_t wrongspeed_indicator_delay=200;

// IIR DSP filter settings
// Alphas are variable, so they can be adjusted if input frequency (and thus the sampling rate) differs considerably from the standard.
// Narrowband channel filters trimmed in a spreadsheet for a center maximum at 4 Hz per DIN 45507

const float    instant_deviation_limit_pos=0.0999;
const float    instant_deviation_limit_neg=-0.0999;
const float    dispersion_limit=0.0499; 
const float    overflow_value=0.099;
      
const float    LPF_input_freq=200.0; /* Hz */  
const float    LPF_input_alpha=0.7; 
const float    LPF_input_beta=0.3; /* =1-? */      
const float    LPF_avg1_freq=0.2;  /* Hz */    
const float    LPF_avg1_alpha=0.0032;
const float    LPF_avg1_beta=0.9968; /* =1-? */      
const float    LPF_avg2_freq=0.2; /* Hz */     
const float    LPF_avg2_alpha=0.0032;
const float    LPF_avg2_beta=0.9968; /* =1-? */    
const float    HPF_wide_freq=0.2; /* Hz */     
const float    HPF_wide_beta=0.9968; /* =1-? */
const float    HPF_narrow_freq=1; /* Hz */
const float    HPF_narrow_beta=0.9842; /* =1-? */
const float    LPF_narrow_freq=10; /* Hz */
const float    LPF_narrow_alpha=0.15;
const float    LPF_narrow_beta=0.85; /* =1-? */     

const float    adjust_input=1.0;     /* placeholder */
const float    adjust_wide=1.0;      /* placeholder */ 
const float    adjust_narrow=1.0;    /* 1.0606 from spreadsheet */

const float    RMS_alpha=0.005;
const float    RMS_beta=0.995;
const float    decay_output_tau=0.5;  /* tau,seconds */
const float    decay_output_alpha=0.001; 
const float    decay_output_beta=0.999; /* =1-? */

// Reading input stream of pulses - IIR DSP filter variables
volatile float raw_input_frequency;
float          LPF_input_filter;
float          LPF_avg1_filter;
float          average_frequency; /* output of LPF_avg2 */
float          instant_deviation;
float          HPF_wide_previous_in;
float          HPF_wide_filter;
float          HPF_narrow_previous_in;
float          HPF_narrow_filter;
float          LPF_narrow_filter;
float          wide_dispersion;   /* unsigned abs value */
float          narrow_dispersion; /* unsigned abs value */
volatile float narrow_RMS_sumofsquares;

// buffering frequency, wideband and narrowband dispersion so that frequent updates won't interfere with slow LCD regen
volatile float buf_FR;
volatile float buf_WB;
volatile float buf_NB; 
volatile float buf_NB_RMS; 

// Processing input stream of pulses - flags
volatile uint8_t  loop_lockout; /* prohibits LCD redraw until a complete new capture/calculation is done */
uint8_t           edge_count; /* count rising fronts in a pack */
uint8_t           timer_overflow; /* no-input-signal flag */
uint16_t          timer_overflow_countdown; /* delayed copy of no-signal flag for blinker */
uint8_t           timer_wrongspeed; /* signal present but wrong speed flag */
uint16_t          timer_wrongspeed_countdown; /* too-fast-signal flag */
volatile uint16_t buffer_reading; /* state of timer0 at the time of n-th capture interrupt */
uint8_t           dispersion_overflow_flag;


// LCD in 4-bit mode 
const uint8_t  lcd_RS=5;
const uint8_t  lcd_EN=4;
const uint8_t  lcd_DB4=3;
const uint8_t  lcd_DB5=2;
const uint8_t  lcd_DB6=9;
const uint8_t  lcd_DB7=7;
LiquidCrystal  lcd(lcd_RS, lcd_EN, lcd_DB4, lcd_DB5, lcd_DB6, lcd_DB7); 
uint8_t        show_RMS; /* false: show DIN 45507 */
const uint8_t  lcd_chars=20;

// Display scale bands for 20x4 alphanumeric LCD (line1)
const size_t   x10_pin=A5;
const size_t   x3_pin=A4;
const size_t   RMS_pin=A3;
const size_t   debug_pin=A0;
const uint8_t  meter_bands=4;
const float    meter_band_limit[meter_bands]={ 0.00100, 0.00300, 0.0100, 0.0300 };
const char     meter_band_texts[meter_bands][lcd_chars+1]={
               "0   .03%  .06%   .1%",
               "0    .1%   .2%   .3%",
               "0    .3%   .6%    1%",
               "0     1%    2%    3%" };
const uint8_t  error_message_position=4;
const char     meter_band_nosignal_message[]  ="  NO SIGNAL";
const char     meter_band_wrongspeed_message[]="  FREQUENCY?";
const char     meter_band_overflow_message[]  ="  OVERFLOW ";

// Display bargraph animation
const char     bargraph_off=0x80;
const char     bargraph_on=0xFF;

// Battery voltage monitor
const uint8_t  battery_adc_pin=A2;
const uint8_t  battery_states=5;
uint8_t        battery_state; /* 0=low ... 4=high */
const uint16_t battery_state_lowest_reading[battery_states]={ 0, 680, 720, 760, 820 };
byte battery_state_icon[8] = {
  0b01110,
  0b11011,
  0b10001,
  0b10001,
  0b10001,
  0b10001,
  0b11111,
};

void process_reading()
{
volatile float old, val;
      dispersion_overflow_flag=0;
 /* extract and denoise instant frequency from buffered timer reading ======================================= */           
      raw_input_frequency=fr_dividend/(float(buffer_reading));   
      old=LPF_input_filter; 
      LPF_input_filter=raw_input_frequency*LPF_input_alpha+old*LPF_input_beta;
 /* two LPFs to extract average frequency =================================================================== */      
      old=LPF_avg1_filter;            
      LPF_avg1_filter=LPF_input_filter*LPF_avg1_alpha+old*LPF_avg1_beta;
      old=average_frequency;                     
      average_frequency=LPF_avg1_filter*LPF_avg2_alpha+old*LPF_avg2_beta;
 /* extract deviation (relative to average frequency ========================================================= */     
      HPF_wide_previous_in=instant_deviation;  /* saving former value for HPF-wide first */
      instant_deviation=(LPF_input_filter/average_frequency)-1;
      if (instant_deviation>instant_deviation_limit_pos) { instant_deviation=instant_deviation_limit_pos; }
        else if (instant_deviation<instant_deviation_limit_neg) { instant_deviation=instant_deviation_limit_neg; }                            
 /* wideband channel HPF ===================================================================================== */            
      old=HPF_wide_filter;            
      HPF_wide_filter=HPF_wide_beta*old+(instant_deviation-HPF_wide_previous_in); /* y[n]=?y[n?1]+x[n]?x[n?1] */
  /* narrowband channel HPF+LPF ============================================================================== */                   
      HPF_narrow_previous_in=old;
      old=HPF_narrow_filter;          
      HPF_narrow_filter=HPF_narrow_beta*old+(HPF_wide_filter-HPF_narrow_previous_in); /* y[n]=?y[n?1]+x[n]?x[n?1] */      
      old=LPF_narrow_filter;            
      LPF_narrow_filter=HPF_narrow_filter*LPF_narrow_alpha+old*LPF_narrow_beta;
 /* wideband channel "precision rectifier" =================================================================== */ 
      val=(abs(HPF_wide_filter))*adjust_wide;                        
      if (val>dispersion_limit) { val=dispersion_limit; dispersion_overflow_flag=1; }  
      else { if (val>=wide_dispersion) { wide_dispersion=val; } else { wide_dispersion=wide_dispersion*decay_output_beta; } }     
 /* narrowband channel "precision rectifier" ================================================================= */  
      val=adjust_narrow*abs(LPF_narrow_filter);
      if (val>dispersion_limit) { val=dispersion_limit; dispersion_overflow_flag=1; } 
      if (val>wide_dispersion) { val=wide_dispersion; } 
      else { if (val>=narrow_dispersion) { narrow_dispersion=val; } else { narrow_dispersion=narrow_dispersion*decay_output_beta; } }                                          
/* narrowband RMS uses val calculated just above! */
      old=narrow_RMS_sumofsquares;
      narrow_RMS_sumofsquares=val*val*RMS_alpha+old*RMS_beta; 
      buf_NB_RMS=sqrt(narrow_RMS_sumofsquares);     
 /* buffering and other housekeeping ========================================================================= */          
      buf_FR=average_frequency;
      buf_WB=wide_dispersion;  
      buf_NB=narrow_dispersion;   
      loop_lockout=0;                                                      
}

ISR(TIMER1_CAPT_vect)
{ 
  digitalWrite(debug_pin,1);
  edge_count++; 
  /* upon the 8-th upward pulse front record the timing */
  if (edge_count>=pulses_per_pack)
  { 
    TCNT1=0; 
    edge_count=0; 
    timer_overflow=0; 
    if (timer_overflow_countdown) timer_overflow_countdown--; 
    buffer_reading=ICR1; 
    if((buffer_reading>toofast_pack_clocks)&&(buffer_reading<tooslow_pack_clocks)) 
    /* normal speed */
    { timer_wrongspeed=0;  if (timer_wrongspeed_countdown) timer_wrongspeed_countdown--;  }
    else 
    /* wrong speed */
    { buffer_reading=toofast_pack_clocks; timer_wrongspeed=1;  timer_wrongspeed_countdown=wrongspeed_indicator_delay; }
    process_reading();  
  }   
  digitalWrite(debug_pin,0);  
}

ISR(TIMER1_OVF_vect)
{ 
  /* overflow = no signal in = display exception */  
  edge_count=0;
  timer_overflow=1;
  timer_overflow_countdown=nosignal_indicator_delay;
  buffer_reading=std_pack_clocks;
  process_reading();
}

void setup() 
{
/* TIMER0 8-bit fast pwm at around 8 kHz */
   noInterrupts();
   TCCR0A=(1<<COM0A1) | (1<<WGM01) | (1<<WGM00);  
   TCCR0B=(1<<CS01) ;
   OCR0A =22;
   pinMode(6, OUTPUT);
/* TIMER2 8-bit 3150 Hz oscillator (actually 3.13 kHz) */   
   TCCR2A=(1<<COM2A0) | (1<<WGM21);  
   TCCR2B=(1<<WGM22) | (1<<CS21) | (1<<CS20);
   OCR2A =79;
   pinMode(11, OUTPUT);
/* TIMER 1 16-bit reading on input pulses */
   TCCR1A=0;
   TCCR1B=(1<<ICNC1) | (1<<ICES1) | (1<<CS10);
   TIMSK1=(1<<ICIE1) | (1<<TOIE1); 
   pinMode(8, INPUT);
/* reset data */ 
   raw_input_frequency=std_signal_frequency_float;
   LPF_input_filter=std_signal_frequency_float;
   LPF_avg1_filter=std_signal_frequency_float;
   average_frequency=std_signal_frequency_float; 
   instant_deviation=0.0;
   HPF_wide_previous_in=0.0;
   HPF_wide_filter=0.0;
   HPF_narrow_previous_in=0.0;
   HPF_narrow_filter=0.0;
   LPF_narrow_filter=0.0;
   wide_dispersion=0.0;   
   narrow_dispersion=0.0; 
   buf_FR=std_signal_frequency_float;
   buf_NB=0.0;
   buf_WB=0.0;  
   buf_NB_RMS=0.0;
/* reset flags */   
   loop_lockout=1;  
   edge_count=0;
   dispersion_overflow_flag=0;
   timer_overflow=1; 
   timer_overflow_countdown=nosignal_indicator_delay;
   timer_wrongspeed=1; 
   timer_wrongspeed_countdown=wrongspeed_indicator_delay;     
   show_RMS=0;
/* housekeeping */
   pinMode(battery_adc_pin, INPUT);
   pinMode(debug_pin, OUTPUT);
   pinMode(RMS_pin, INPUT_PULLUP);
   pinMode(x10_pin, INPUT_PULLUP);
   pinMode(x3_pin, INPUT_PULLUP);
   lcd.begin(20, 4);    
   interrupts();
}

void loop() 
{ 
   char lcd_line[lcd_chars+1];
   show_RMS=digitalRead(RMS_pin);
   while (loop_lockout) delayMicroseconds(1);
/* LINE 1 ============================================================================================ */
   /* get scale setting */
   uint8_t current_meter_band=0;
   if (!digitalRead(x10_pin)) { current_meter_band=current_meter_band+2; }
   if (!digitalRead(x3_pin))  { current_meter_band++; }
   /* fill string buffer */
   strncpy(lcd_line, &(meter_band_texts[current_meter_band][0]), lcd_chars);
   char *error_message=0;
   if (timer_overflow_countdown) { error_message=meter_band_nosignal_message; } 
      else { if (timer_wrongspeed_countdown) { error_message=meter_band_wrongspeed_message; }
           else { if (dispersion_overflow_flag) { error_message=meter_band_overflow_message; } } }
   if (error_message) strncpy(lcd_line+error_message_position, error_message, strlen(error_message));
   lcd_line[lcd_chars]=0;
   lcd.setCursor(0, 0); 
   lcd.print(lcd_line);    
/* LINE 2 ============================================================================================ */
   float block_size=meter_band_limit[current_meter_band]/lcd_chars;
   volatile float val;
   if (show_RMS) { val=buf_NB_RMS; } else { val=buf_WB; }
   uint16_t blocks=uint16_t(val/block_size+.5);
   if (blocks>lcd_chars) blocks=lcd_chars;
   for (byte i=0; i<lcd_chars; i++) lcd_line[i]=bargraph_off;
   for (byte i=0; i<blocks; i++) lcd_line[i]=bargraph_on; 
   lcd_line[lcd_chars]=0;
   lcd.setCursor(0, 1); 
   lcd.print(lcd_line);   
/* LINE 3 ============================================================================================ */
   blocks=uint16_t(buf_NB/block_size+.5);
   if (blocks>lcd_chars) blocks=lcd_chars;
   for (byte i=0; i<lcd_chars; i++) lcd_line[i]=bargraph_off;
   for (byte i=0; i<blocks; i++) lcd_line[i]=bargraph_on; 
   lcd_line[lcd_chars]=0;
   lcd.setCursor(0, 2); 
   lcd.print(lcd_line);       
/* LINE 4 ============================================================================================ */
   uint16_t adc=analogRead(battery_adc_pin);
   for (uint8_t i=0; i<battery_states ;i++) { if (adc>=battery_state_lowest_reading[i]) { battery_state_icon[6-i] = 0b11111; } else { battery_state_icon[6-i] = 0b10001; } }
   dtostrf(val*100, 4, 2, &(lcd_line[1]));
   dtostrf(buf_NB*100,      4, 2, &(lcd_line[8]));   
   dtostrf((buf_FR+.25),          4, 2, &(lcd_line[15]));
   if (show_RMS) { lcd_line[0]='R'; } else { lcd_line[0]='W'; };
   lcd_line[ 5]='%';
   lcd_line[ 6]=' ';
   lcd_line[ 7]='N';
   lcd_line[12]='%';
   lcd_line[13]=' ';
   lcd_line[14]='F';
   lcd.createChar(1, battery_state_icon);
   lcd_line[19]=char(1);
   lcd_line[20]=0;
   lcd.setCursor(0, 3); 
   lcd.print(lcd_line); 
   // Inside the loop() function

/* set wait-for-buffer flag =========================================================================== */    
   loop_lockout=1;  
}

Or just 1!

"too complicated"?

Generate the 5 icons.

Change the battery_state_icon numbers from 0x9F-0x9B to 0x05-0x01.

Make 5 calls to lcd.createChar() to assign the icons.

Done.

Below is the result of 10 minutes work. And that included rounding up the hardware and wiring it.

that's smart not to use custom char 0 :slight_smile:

You can use 0x08 - 0x0F in place of 0x00 - 0x07 to avoid the zero value.

It is for me. I have limited knowledge in coding. Thanks for your suggestions.
I compiled this form a simple example:


#include <LiquidCrystal.h>

LiquidCrystal lcd(5, 4, 3, 2, 9, 7); // RS, E, D4, D5, D6, D7

// lcd Custom Characters
uint8_t Battery0Char[] = {0x00, 0x06, 0x0F, 0x09, 0x09, 0x09, 0x09, 0x0F};
uint8_t Battery1Char[] = {0x00, 0x06, 0x0F, 0x09, 0x09, 0x09, 0x0F, 0x0F};
uint8_t Battery2Char[] = {0x00, 0x06, 0x0F, 0x09, 0x09, 0x0F, 0x0F, 0x0F};
uint8_t Battery3Char[] = {0x00, 0x06, 0x0F, 0x09, 0x0F, 0x0F, 0x0F, 0x0F};
uint8_t Battery4Char[] = {0x00, 0x06, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F};
void setup()
{
  // Initialize The lcd. Parameters: [ Columns, Rows ]
 lcd.begin(16, 2);
  // Send The Custom Characters To lcd's CGRAM
  lcd.createChar(3, Battery0Char);
  lcd.createChar(4, Battery1Char);
  lcd.createChar(5, Battery2Char);
  lcd.createChar(6, Battery3Char);
  lcd.createChar(7, Battery4Char);
  // Clear The lcd Dispaly
  lcd.clear();
  lcd.print("Custom Characters:");
  // Print The Custom Characters
  
  lcd.setCursor(6, 1);
  lcd.write(byte(3));
  lcd.setCursor(8, 1);
  lcd.write(byte(4));
  lcd.setCursor(10, 1);
  lcd.write(byte(5));
  lcd.setCursor(12, 1);
  lcd.write(byte(6));
  lcd.setCursor(14, 1);
  lcd.write(byte(7));
}
void loop()
{
  // DO NOTHING!
}

PaulRB: Does your code just print the icon without reflecting the voltage on the analog pin?

Nope.

for (uint8_t i=0; i<battery_states ;i++) { if (adc>=battery_state_lowest_reading[i]) { battery_state_icon[6-i] = 0b11111; } else { battery_state_icon[6-i] = 0b10001; } }

Did you try it?

@merlynb - Your BatteryXChar values all look the same. I used image2cpp to create these:

// 'bat0', 5x8px
const unsigned char epd_bitmap_bat0 [] PROGMEM = {
	0x98, 0x08, 0x68, 0x68, 0x68, 0x68, 0x08, 0xf8
};
// 'bat1', 5x8px
const unsigned char epd_bitmap_bat1 [] PROGMEM = {
	0x98, 0x08, 0x68, 0x68, 0x68, 0x08, 0x08, 0xf8
};
// 'bat2', 5x8px
const unsigned char epd_bitmap_bat2 [] PROGMEM = {
	0x98, 0x08, 0x68, 0x68, 0x08, 0x08, 0x08, 0xf8
};
// 'bat3', 5x8px
const unsigned char epd_bitmap_bat3 [] PROGMEM = {
	0x98, 0x08, 0x68, 0x08, 0x08, 0x08, 0x08, 0xf8
};
// 'bat4', 5x8px
const unsigned char epd_bitmap_bat4 [] PROGMEM = {
	0x98, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0xf8
};

// Array of all bitmaps for convenience. (Total bytes used to store images in PROGMEM = 160)
const int epd_bitmap_allArray_LEN = 5;
const unsigned char* epd_bitmap_allArray[5] = {
	epd_bitmap_bat0,
	epd_bitmap_bat1,
	epd_bitmap_bat2,
	epd_bitmap_bat3,
	epd_bitmap_bat4
};

(enlarged images all 5px X 8px)

Yes I did. I was connected to the USB cable only and it showed empty battery. When connected to a power supply (7VDC) it shows half full battery but there's a 20k/20k voltage divider going to the analog pin so I guess it has something to do with voltage mapping.

I tried that sketch and batteries look fine.

1 Like

What is A6 connected to (via the voltage divider)? Vin pin? If so, when powered by USB, I'm not sure what voltage you would get at Vin, could be zero, certainly less than 5V, which would give an ADC reading less than 500.

const uint16_t battery_state_lowest_reading[battery_states]={ 0, 680, 720, 760, 820 };

That would give an ADC reading of about 710~720, so one or two more pixels on the battery symbol.

Thanks for your help so far.
Battery sensing pin is A2. A6 is free.
The voltage divider connects to the PSU ( Vin). LCD is powered from the 5V pin.
By the way the battery on that orange LCD is 4 pixel wide and 7 pixel high. I tried to generate that and replace it in code but it doesn't print properly.

post your current code and an image of what you get currently.

obvious, you also have to adopt the print out of the former line

lcd_line[19]=battery_state_icon[battery_state];

to access the correct custom character instead of one character in the old array (or you just adopt the content of each array field with the position of your custom character ...)