How to Make an Arduino POV (Persistence of Vision) Display

This a POV (Persistence of vision) Display based on an Arduino Pro Mini clone.

The POV is fully interrupt driven making use of the Timer 1 input capture interrupt and the Timer 1 Output compare match interrupt for stable operation.

 

Arduino POV Wiring Diagram - Techydiy

Arduino POV Wiring Diagram – Techydiy

Parts

When you click on links to various merchants on this site and make a purchase, this can result in this site earning a commission. Affiliate programs and affiliations include, but are not limited to, the eBay Partner Network.

Ebay affiliate links:

(16Mhz pro mini for the best performance)
FT232RL usb serial

150mah lipo battery

Neodymium magnets 1/12″x1/8″ 2mmx3mm
0.01uf or 10nf ceramic capacitor
10k resistor 1/8w
Ribbon cable – 9 way
Fidget spinner
M8x20mm bolts
M8x25mm bolt
M8 nuts
M8 washers
M8 dome nut

Test Leds sketch

// Pov display - test leds
// Turn on each led in turn
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/

void setup(){
  DDRD = 0b11111111;    // Set all pins in PORTD to outputs
  PORTD = 0;            // Turn off the leds
}

void loop() {
    PORTD = 0;       // Turn off the leds
    delay(100);
    byte dispval = 1;
    for (int i=0; i <= 7; i++){   //  Turn on each led in turn
      PORTD=dispval;
      delay(100);
      dispval=dispval<<1;
    }    
}

Test hall effect sensor sketch

// Pov display - test hall effect sensor
// Leds reflect the output of the hall effect sensor
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/

const int hallEffectPin = 8;   
int hallEffectState = 0; 

void setup(){
  DDRD = 0b11111111;    // All pins in PORTD are outputs
  PORTD = 0;            // Turn off the leds

  // initialize the hall effect pin as an input:
  pinMode(hallEffectPin, INPUT);

}

void loop() {

 hallEffectState = digitalRead(hallEffectPin);
 
 if (hallEffectState == HIGH) {
    // turn LED on:
          PORTD=255;
  } else {
    // turn LED off:
    PORTD=0;
  } 
}

Sketch 1 – Basic display driver

// POV project
// Basic POV Sketch - Prints static text
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
// 
// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D
 
const byte inputCapturePin = 8;  //  Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0;         // The count width of a column 
 
volatile byte timer1CountOverflows = 0;     // Timer 1 counter overflows
volatile unsigned int povDisplayColumn = 0;         // The next pov display column
 
// Data to be displayed on the pov display
// initialised with static text
// Look at the pattern of 1s sideways
//
volatile byte povDisplayData[64] ={
  B00000001,     // T
  B00011111,
  B00000001,
  B00000000,
  B00011111,     // E
  B00010101,
  B00010101,
  B00000000,
  B00011111,     // C
  B00010001,
  B00010001,
  B00000000,
  B00011111,     // H
  B00000100,
  B00011111,
  B00000000,
  B00010111,     // y
  B00010100,
  B00011111,
  B00000000,
  B00011111,     // D
  B00010001,
  B00001110,
  B00000000,
  B00011111,     // I
  B00000000,
  B00010111,     // Y
  B00010100,
  B00011111
  };
 
                                   
void setup()
{
  noInterrupts();         // Turn off interrupts
 
  DDRD = 0b11111111;      // Set all PORTD pins to outputs
  PORTD = 0;              // Turn the leds off
 
  pinMode(inputCapturePin, INPUT_PULLUP);   //   Hall effect sensor connected to this pin
 
// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//
 
  TCCR1A = 0;    // Normal wave generation mode
  TCCR1B = 0;
 
  TCNT1 = 0;          // Reset timer 1 counter
  OCR1A = 0xFFFE;     // Initialise timer 1 output compare match
  // Timer counter contol register
    //  ICNC1 - Input capture noise canceller
    //  ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
    //
    // TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10);     //   /1024 prescale
    // TCCR1B = bit(ICNC1) | bit(CS12) ;                //   /256 prescale
    // TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10);     //   /64 prescale
    //TCCR1B = bit(ICNC1) | bit(CS11);                  //   /8 prescale
     TCCR1B = bit(ICNC1) | bit(CS10);                 //     /1 prescale
 
  // Timer interrupt mask register
    // Input capture interrupt enable
    // Output compare A match interrupt enable
    // Timer overflow interrupt enable
 
  TIMSK1 = bit(ICIE1) | bit(OCIE1A)  |  bit(TOIE1);     
       
  interrupts();       // enable interrupts
   
}
 
 
void loop() {         // Empty main program loop
}
 
 
// Timer 1 - Input capture interrupt routine - digital input 8
ISR(TIMER1_CAPT_vect) {
 
  povDisplayColumnWidthCount = ( ICR1 + timer1CountOverflows * 65535 ) / 64;    // Calculate the time count for a column 
  TCNT1 = 0;                                    // Reset timer 1 counter
  povDisplayColumn=0;                           // Reset display to the first column
  OCR1A  = povDisplayColumnWidthCount-1;        // Set timer 1 OCR1A interrupt count duration        
  timer1CountOverflows = 0;                     // Reset timer1 overflows
     
}
 
// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
  timer1CountOverflows++;      // Increment the timer1 overflows counter
}
 
 
ISR(TIMER1_COMPA_vect){ // Timer 1 Output compare A match interrupt routine
  OCR1A  = TCNT1 + povDisplayColumnWidthCount-1;       // Update timer1 with the next time interval
 
  if (povDisplayColumn < 64){
    PORTD = povDisplayData[povDisplayColumn];             // Output data to the leds
  } else { 
    PORTD = 0;                                            // Turn off the leds  
  }
 
  povDisplayColumn++;                        // increment the display column
}

 

Sketch 2 – Print any text to the POV display with a frame buffer

Please copy the font.h file here: font.h (1261 downloads )  into the same directory as the sketch.

The sketch also makes use of a frame buffer, which allows you to modify the display data and then display it with   povUpdateDisplay();    when you are ready.

// POV project
// Prints any text on the display
// This also has a frame buffer, so that the screen can be updated
// and then the results displayed when you are ready.
// 
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D
 
#include "font.h";         // defines array with font data
 
const byte ledpin = 13;     //  On board led output pin
const byte inputCapturePin = 8;  //  Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0;         // The count width of a column 
 
volatile byte timer1CountOverflows = 0;     // Timer 1 ounter overflows
volatile unsigned int povDisplayColumn = 0;         // The current pov display column
volatile byte povFrameBuffer[64];   // POV display working frame buffer
volatile boolean showpov = false;  // Set to update the POV frame buffer
volatile boolean updateFrameBuffer = false; // Timer 1 uses this to tell itself to update the frame buffer
 
// Data to be displayed on the pov display
volatile byte povDisplayData[64];
 
byte printseg=0;   // Next column to print on
 
void setup()
{
  noInterrupts();
 
  DDRD = 0b11111111;      // Set all PORTD pins to outputs
  PORTD = 255;            // Turn the leds off
 
  pinMode(ledpin, OUTPUT);  // Onboard LED output
  pinMode(inputCapturePin, INPUT_PULLUP);   //   Hall effect sensor connected to this pin
 
 
// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//
 
  TCCR1A = 0;    // Normal wave generation mode
  TCCR1B = 0;
 
  TCNT1 = 0;          // Reset timer 1 counter
  OCR1A = 0xFFFE;     // Initialise timer 1 output compare match
  // Timer counter contol register
    //  ICNC1 - Input capture noise canceller
    //  ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
    //
    // TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10);     //   /1024 prescale
    // TCCR1B = bit(ICNC1) | bit(CS12) ;                //   /256 prescale
    // TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10);     //   /64 prescale
    //TCCR1B = bit(ICNC1) | bit(CS11);                  //   /8 prescale
     TCCR1B = bit(ICNC1) | bit(CS10);                 //     /1 prescale
 
  // Timer interrupt mask register
    // Input capture interrupt enable
    // Output compare A match interrupt enable
    // Timer overflow interrupt enable
 
  TIMSK1 = bit(ICIE1) | bit(OCIE1A)  |  bit(TOIE1);     
       
  interrupts();       // enable interrupts
}
 
void loop() {         // Empty main program loop
 
  printseg=0;                       // Reset print column to the start
  povPrint("Techydiy");             // print text
  povUpdateDisplay();               // Show the display data on screen
 
  delay(500);
  povClear();                       // Clear the display data
  printseg=0;                       // Reset print column to the start
  povPrint("Projects");             // print text
  povUpdateDisplay();               // Show the display data on screen
 
  delay(500);
  povClear();                       // Clear the display data
}
 
 
// ---------------------------------
//
//
// Signal to update the frame buffer and then wait for it to be completed
void povUpdateDisplay(){;
  showpov = true;                       // Show the display data on screen
    do{ } while (showpov);                // Wait for the display to be updated
}
 
void displayCharOnPov(char letter) {
  unsigned int y = 0;
  for (int i=0; i < 6; i++){
    y=Font8x5[letter][i];
    povDisplayData[printseg]= y;
    printseg++;                 // update the next print segment pointer
  }
}
 
void povPrint(String povtxt){
  for (int z=0; z< povtxt.length(); z++){
    displayCharOnPov( povtxt.charAt(z));
  }
}
 
// Clear pov Display
void povClear(){
for (int zb=0; zb < 64; zb++){
    povDisplayData[zb]=0;
  }
}
 
//------------------------------------------------------------
//
// Timer 1 - Input capture interrupt routine - digital input 8
//
 
ISR(TIMER1_CAPT_vect) {
 
  povDisplayColumnWidthCount = ( ICR1 + timer1CountOverflows * 65535 ) / 64;    // Calculate the time count for a column 
  TCNT1 = 0;                                    // Reset timer 1 counter
  povDisplayColumn=0;                           // Reset display to the first column
  OCR1A  = povDisplayColumnWidthCount-1;        // Set timer 1 OCR1A interrupt count duration        
  timer1CountOverflows = 0;                     // Reset timer1 overflows
   
  if (showpov){
    updateFrameBuffer = true;   // Tell timer 1 OCR1A interrupt to update the frame buffer
  }
     
}
 
// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
  timer1CountOverflows++;      // Increment the timer1 overflows counter
  digitalWrite(ledpin, !digitalRead(ledpin));     // Toggle onboard LED
}
 
 
ISR(TIMER1_COMPA_vect){ // Timer 1 Output compare A match interrupt routine
  OCR1A  = TCNT1 + povDisplayColumnWidthCount-1;       // Update timer1 with the next time interval
 
  if (updateFrameBuffer){                               // Update the framebuffer only once
    povFrameBuffer[povDisplayColumn]=povDisplayData[povDisplayColumn];    // Copy display data into the framebuffer
    if (povDisplayColumn >= 63){                          
      updateFrameBuffer = false;                        // framebuffer has been updated
      showpov = false;
    }
  }
  if (povDisplayColumn < 64){
    PORTD = povFrameBuffer[povDisplayColumn];             // Output data to the leds from the frame buffer
  } else { 
    //PORTD = 0;          // If the spinning stops then turn off the leds  
  }
 
  povDisplayColumn++;                        // increment the display column
}

Sketch 3 – Display the RPM (revolutions per minute) on the POV display

Please copy the font.h file here: font.h (1261 downloads )  into the same directory as the sketch.

// POV project
// Prints RPM (revolutions per minute) on the display
 
// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/
 
// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D
 
#include "font.h";         // defines array with font data.
 
const byte inputCapturePin = 8;  //  Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0;         // The count width of a column 
volatile unsigned long int revCount;                          // Count length of the last revolution
 
volatile byte timer1CountOverflows = 0;     // Timer 1 ounter overflows
volatile unsigned int povDisplayColumn = 0;         // The current pov display column
volatile byte povFrameBuffer[64];   // POV display working frame buffer
volatile boolean showpov = false;  // Set to update the POV frame buffer
volatile boolean updateFrameBuffer = false; // Timer 1 uses this to tell itself to update the frame buffer
 
// Data to be displayed on the pov display
volatile byte povDisplayData[64];
 
byte printseg=0;   // Next column to print on
 
void setup()
{
  noInterrupts();
 
  DDRD = 0b11111111;      // Set all PORTD pins to outputs
  PORTD = 255;            // Turn the leds off
 
  pinMode(inputCapturePin, INPUT_PULLUP);   //   Hall effect sensor connected to this pin
 
// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//
 
  TCCR1A = 0;    // Normal wave generation mode
  TCCR1B = 0;
 
  TCNT1 = 0;          // Reset timer 1 counter
  OCR1A = 0xFFFE;     // Initialise timer 1 output compare match
  // Timer counter contol register
    //  ICNC1 - Input capture noise canceller
    //  ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
    //
    // TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10);     //   /1024 prescale
    // TCCR1B = bit(ICNC1) | bit(CS12) ;                //   /256 prescale
    // TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10);     //   /64 prescale
    //TCCR1B = bit(ICNC1) | bit(CS11);                  //   /8 prescale
     TCCR1B = bit(ICNC1) | bit(CS10);                 //     /1 prescale
 
  // Timer interrupt mask register
    // Input capture interrupt enable
    // Output compare A match interrupt enable
    // Timer overflow interrupt enable
 
  TIMSK1 = bit(ICIE1) | bit(OCIE1A)  |  bit(TOIE1);     
       
  interrupts();       // enable interrupts
}
 
void loop() {         // Empty main program loop
  int rpm = 0;
  int oldrpm = 0;
  printseg=0;                       // Reset print column to the start
  povPrint("Techydiy");             // print text
  povUpdateDisplay();               // Show the display data on screen
 
  delay(1000);
  povClear();                       // Clear the display data
 
  while(true){
                            // Calculate the rpm of the last revolution
    rpm = 480000000 / revCount;  // rpm = 60 / ( timer count x timer period )
                            // timer count = revcnt
                            // timer period = 125 x 10-9 
                            // 8Mhz clock - no prescaler
 
   if (rpm != oldrpm){                  // If the RPM value has changed print it
      printseg=0;                       // Reset print column to the start
      povPrint(String(rpm)+" RPM ");    // Print rpm value to the pov display                        
      povUpdateDisplay();               // Show the display data on screen
      oldrpm = rpm;
   }
  }
}
 
// ---------------------------------
//
//
// Signal to update the frame buffer and then wait for it to be completed
void povUpdateDisplay(){;
  showpov = true;                       // Show the display data on screen
    do{ } while (showpov);                // Wait for the display to be updated
}
 
void displayCharOnPov(char letter) {
  unsigned int y = 0;
  for (int i=0; i < 6; i++){
    y=Font8x5[letter][i];
    povDisplayData[printseg]= y;
    printseg++;                 // update the next print segment pointer
  }
}
 
void povPrint(String povtxt){
  for (int z=0; z< povtxt.length(); z++){
    displayCharOnPov( povtxt.charAt(z));
  }
}
 
// Clear pov Display
void povClear(){
for (int zb=0; zb < 64; zb++){
    povDisplayData[zb]=0;
  }
}
 
//------------------------------------------------------------
//
// Timer 1 - Input capture interrupt routine - digital input 8
//
 
ISR(TIMER1_CAPT_vect) {
  revCount = ICR1 + timer1CountOverflows * 65535;   // Count for the last revolution of the disc
  povDisplayColumnWidthCount = revCount / 64;    // Calculate the time count for a column 
  TCNT1 = 0;                                    // Reset timer 1 counter
  povDisplayColumn=0;                           // Reset display to the first column
  OCR1A  = povDisplayColumnWidthCount-1;        // Set timer 1 OCR1A interrupt count duration        
  timer1CountOverflows = 0;                     // Reset timer1 overflows
   
  if (showpov){
    updateFrameBuffer = true;   // Tell timer 1 OCR1A interrupt to update the frame buffer
  }
     
}
 
// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
  timer1CountOverflows++;      // Increment the timer1 overflows counter
}
 
 
ISR(TIMER1_COMPA_vect){ // Timer 1 Output compare A match interrupt routine
  OCR1A  = TCNT1 + povDisplayColumnWidthCount-1;       // Update timer1 with the next time interval
 
  if (updateFrameBuffer){                               // Update the framebuffer only once
    povFrameBuffer[povDisplayColumn]=povDisplayData[povDisplayColumn];    // Copy display data into the framebuffer
    if (povDisplayColumn >= 63){                          
      updateFrameBuffer = false;                        // framebuffer has been updated
      showpov = false;
    }
  }
  if (povDisplayColumn < 64){
    PORTD = povFrameBuffer[povDisplayColumn];             // Output data to the leds from the frame buffer
  } else { 
    PORTD = 0;                                            // If the spinning stops then turn off the leds  
  }
 
  povDisplayColumn++;                        // increment the display column
}

Sketch 4 – POV Animation Effect –  Rotate Rows

This sketch uses multiple screens or frame buffers to create an animation effect. The frame buffers are initialised with display data which is rotated in each consecutive buffer.

The main program loop switches between the frame buffers to create the effect.

// POV project
//
// Animation effect - rotate rows
// This uses multiple screens or frame buffers to create an animation effect

// techydiy.org
// https://creativecommons.org/licenses/by-nc-sa/4.0/

// Hall effect sensor is connected to ICP1 digital pin 8
// LEDS connected to port D

const byte ledpin = 13;     //  On board led output pin
const byte inputCapturePin = 8;  //  Hall effect sensor is connected to this pin
volatile unsigned int povDisplayColumnWidthCount = 0;         // The measured count width of a column 

volatile byte timer1CountOverflows = 0;     // Timer 1 ounter overflows
volatile unsigned int povDisplayColumn = 0;         // The current pov display column

volatile byte povCurrentDisplay =0 ; // The display currently being sent to screen
volatile byte povRequestDisplay =0 ; // The display to be sent to screen next

// Data to be displayed on the pov display
// initialised with static text
volatile byte povDisplayData[8][64] ={
  B00000001,     // T
  B00011111,
  B00000001,
  B00000000,
  B00011111,     // E
  B00010101,
  B00010101,
  B00000000,
  B00011111,     // C
  B00010001,
  B00010001,
  B00000000,
  B00011111,     // H
  B00000100,
  B00011111,
  B00000000,
  B00010111,     // y
  B00010100,
  B00011111,
  B00000000,
  B00011111,     // D
  B00010001,
  B00001110,
  B00000000,
  B00011111,     // I
  B00000000,
  B00010111,     // Y
  B00010100,
  B00011111,
  0,
  0,
  0,
  B11111000,  // Y
  B00101000,
  B11101000,
  B00000000,
  B11111000,  // I
  B00000000,
  B01110000,  // D
  B10001000,
  B11111000,
  B00000000,
  B11111000,  // Y
  B00101000,
  B11101000,
  B00000000,
  B11111000,  // H
  B00100000,
  B11111000,
  B00000000,
  B10001000,  // C
  B10001000,
  B11111000,
  B00000000,
  B10101000,  // E
  B10101000,
  B11111000,
  B00000000,
  B10000000,
  B11111000,  // T
  B10000000,
  0,
  0,
  0
  };

void setup()
{
  noInterrupts();

  DDRD = 0b11111111;      // Set all PORTD pins to outputs
  PORTD = 255;            // Turn the leds off

  pinMode(ledpin, OUTPUT);  // Onboard LED output
  pinMode(inputCapturePin, INPUT_PULLUP);   //   Hall effect sensor connected to this pin


// ----------------------------------------------------
// TIMER 1 CONFIGURATION
//

  TCCR1A = 0;    // Normal wave generation mode
  TCCR1B = 0;

  TCNT1 = 0;          // Reset timer 1 counter
  OCR1A = 0xFFFE;     // Initialise timer 1 output compare match
  
  // Timer counter contol register
    //  ICNC1 - Input capture noise canceller
    //  ICES1 - Input capture edge select. 0 negative edge, 1 positive edge.
    //
    // TCCR1B = bit(ICNC1) | bit(CS12) | bit(CS10);     //   /1024 prescale
    // TCCR1B = bit(ICNC1) | bit(CS12) ;                //   /256 prescale
    // TCCR1B = bit(ICNC1) | bit(CS11) | bit(CS10);     //   /64 prescale
    //TCCR1B = bit(ICNC1) | bit(CS11);                  //   /8 prescale
     TCCR1B = bit(ICNC1) | bit(CS10);                 //     /1 prescale

  
  // Timer interrupt mask register
    // Input capture interrupt enable
    // Output compare A match interrupt enable
    // Timer overflow interrupt enable

  TIMSK1 = bit(ICIE1) | bit(OCIE1A)  |  bit(TOIE1);     
       
  interrupts();       // enable interrupts



  // Load rotated copies of the data in the first display into the other displays
  //
  for (byte rotn=0; rotn <= 63; rotn++){        // columns 0 to 63
    for (byte rown=1; rown <= 7; rown++){        //  rows 1 to 7
       povDisplayData[rown][rotn] = rotl( povDisplayData[rown-1][rotn] );
    }
  }
   
}

void loop() {        
 povRequestDisplay = 0;
 delay(500);
  for (byte rown=0; rown <= 7; rown++){         //  Displays 0 to 7
    povRequestDisplay = rown;                   // Change displays
    
    do{} while(povRequestDisplay != povCurrentDisplay);       // Wait for screen to update
    delay(100);      
  }
   
}

unsigned char rotl(unsigned char c)
{
    return (c << 1) | (c >> 7);
}

//------------------------------------------------------------
//
// Timer 1 - Input capture interrupt routine - digital input 8
ISR(TIMER1_CAPT_vect) {
 
  povDisplayColumnWidthCount = ( ICR1 + timer1CountOverflows * 65535 ) / 64;    // Calculate the time count for a column 
  TCNT1 = 0;                                    // Reset timer 1 counter
  povDisplayColumn=0;                           // Reset display to the first column
  OCR1A  = povDisplayColumnWidthCount-1;        // Set timer 1 OCR1A interrupt count duration        
  timer1CountOverflows = 0;                     // Reset timer1 overflows
   
   if (povRequestDisplay != povCurrentDisplay){      // Change the display
     povCurrentDisplay = povRequestDisplay;
  }
     
}

// Timer 1 overflow interrupt routine
ISR(TIMER1_OVF_vect) {
  timer1CountOverflows++;      // Increment the timer1 overflows counter
 }

// Timer 1 Output compare A match interrupt routine
ISR(TIMER1_COMPA_vect){ 
  OCR1A  = TCNT1 + povDisplayColumnWidthCount-1;       // Update timer1 with the next time interval

  if (povDisplayColumn < 64){
        PORTD = povDisplayData[povCurrentDisplay][povDisplayColumn];    // Output data to the leds from the frame buffer 
  } else { 
    PORTD = 0;          // If the spinning stops then turn off the leds  
  }

  povDisplayColumn++;                        // increment the display column
}

 

 

One thought on “How to Make an Arduino POV (Persistence of Vision) Display

  1. Laswin Smith

    In the sketch 2, where to display ” povUpdateDisplay” in frame buffer section, please help

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.