/******************************************************************************
  HardwareRNG1.c   Makes RNG serial bytes from 50Hz mains
                   Copyright RomanBlack, 15 Aug 2012
                   (see schematic at www.RomanBlack.com/HardRNG/HardRNG.htm)
  Compiler; MikroC for PIC
  
  PIC 16F628A, 20MHz HSOSC, xtal or resonator both OK.
  PIC Pins;
    RA0 = input, comparator1, detecting 100Hz mains (rectified full wave)
    RA2 = PIC internal Vref for comparators, connects to 47uF tant cap
    RA3 connected to RB3 = comp1 output connected to CCP1 capture input
    RB2 = output, USART TX, serial data out to PC
    RB7 = output, LED indicates "data good" flashes once per second

  PIC Config setup;
    BODEN_OFF, BOREN_OFF, PWRTE_ON, WDT_OFF, LVP_OFF, MCLRE_OFF, HS_OSC
******************************************************************************/
#define MAINSFREQ     50    // set this number to match your mains; 50Hz or 60Hz

#define MAINSPERIOD (2500000 / MAINSFREQ)   // don't change these 3 defines
#define MAINSPERIODMIN ((MAINSPERIOD * 96) / 100)     // -4%
#define MAINSPERIODMAX ((MAINSPERIOD * 104) / 100)    // +4%

// global vars
unsigned int  capture_new     absolute 0x20;    // 16bit period CCP1 capture
unsigned char capture_new_lo  absolute 0x20;    // overloaded to allow each byte access
unsigned char capture_new_hi  absolute 0x21;

unsigned int capture_last;    // also for capture measuring
unsigned int period16;        // "
unsigned int timeout;         // for detecting debounce timed out

unsigned char deb;            // debounce
unsigned char mainsgood;      // count of how many good mains cycles
unsigned char flashcount;     // sequencing LED flash once per second
unsigned char tbyte;          // temp byte for bit processing
unsigned char serbyte;        // holds serial byte to transmit
unsigned char serbitcount;    // counts bits until serial byte full

unsigned char shiftA[6];      // two shift registers used in RNG
unsigned char shiftB[6];


//=============================================================================
//   TIMER_DELAY_20uS
//=============================================================================
void timer_delay_20uS(void)
{
  //-------------------------------------------------------
  // This uses TMR0 to make a delay of exactly 20uS, and the
  // delay is cyclic, so it syncs to 20uS timer periods and
  // negates dead time from other loops etc.
  // Since TMR0 runs at 5MHz, a 20uS delay is 100 TMR0 counts.
  //-------------------------------------------------------
  while(!INTCON.T0IF) continue;   // wait here for TMR0 overflow
  INTCON.T0IF = 0;                // clear overflow flag
  TMR0 -= (100-3);                // set TMR0 to overflow in 100 counts time
                                  // (note! there is a 3 count latency)
  timeout++;                      // used to detect mains fail timed-out
}
//-----------------------------------------------------------------------------


//=============================================================================
//   STOP_OUTPUT
//=============================================================================
void stop_output(void)
{
  //-------------------------------------------------------
  // this shuts down the RNG process and resets everything ready
  // so when mains has tested good again it can restart later.
  //-------------------------------------------------------
  mainsgood = 0;      // start re-testing mains from scratch
  serbitcount = 0;    // clear all serial bit data, to start again
  PORTB.F7 = 0;       // LED off,
  flashcount = 0;     // and LED flash sequence cleared too
  timeout = 0;        // reset timeout ready to restart
}
//-----------------------------------------------------------------------------


//=============================================================================
//   MAIN
//=============================================================================
void main(void)
{
  //-------------------------------------------------------
  // Setup PIC 16F628A
  CMCON =  0b00011110;    // comps ON, internal Vref, RA0 in, RA3 out, comp1 inverted
  VRCON =  0b11100101;    // CVref ON, low range, 1.04v, Vref output on RA2

  PORTA =  0b00000000;    //
  TRISA =  0b00010101;    // RA0 comp1 in, RA3 comp1 out, RA2 Vref

  PORTB =  0b00000000;    //
  TRISB =  0b00001000;    // RB3 is CCP1 in, RB2 is USART TX out, RB7 LED out

  OPTION_REG = 0b10001000;    // PORTB pullups disabled, TMR0 at 1:1 prescaler

  // setup TMR1 and CCP1 to capture the mains pulse
  T1CON = 0b00000001;     // TMR1 ON, 1:1 prescale (5MHz)
  CCP1CON = 0b00000101;   // capture, every rising edge

  //-------------------------------------------------------
  // prepare all vars etc ready start RNG later
  stop_output();

  // setup USART at baudrate
  Usart_Init(9600);

  //-------------------------------------------------------
  // loop and output RNG data from USART
  while(1)
  {
    //---------------------------------------------
    // NOTE! each loop should be one mains half-cycle
    //---------------------------------------------
    // debounce, first wait for mains to be high for at least 4000uS (4mS)
    timeout = 0;
    for(deb=0; deb<200; deb++)     // 200 loops of 20uS = 4000uS
    {
      timer_delay_20uS();
      if(!PORTB.F3) deb = 0;
      if(timeout >= 500) stop_output();   // timeout error after 10mS
    }

    //---------------------------------------------
    // debounce, wait for mains to be low for at least 200uS
    timeout = 0;
    for(deb=0; deb<10; deb++)     // 20 loops of 20uS = 200uS
    {
      timer_delay_20uS();
      if(PORTB.F3) deb = 0;
      if(timeout >= 500) stop_output();   // timeout error after 10mS
    }

    //---------------------------------------------
    // now mains is synced, wait for capture of mains pulse on first / edge
    timeout = 0;
    PIR1.CCP1IF = 0;
    while(!PIR1.CCP1IF)   // wait for CCP1 capture!
    {
      timer_delay_20uS();
      if(timeout >= 100) stop_output();    // timeout error after 2mS
    }

    //---------------------------------------------
    // gets here after a capture! so test if period is within
    // the right range, if it is we assume mains freq is good.

    capture_new_lo = CCPR1L;    // get the new 16bit capture value
    capture_new_hi = CCPR1H;
    period16 = (capture_new - capture_last);  // calculate 16bit period between captures
    capture_last = capture_new;         // save value to get period next time

    // now the period is in period16, so test it for valid range
    if(period16 < MAINSPERIODMIN || period16 > MAINSPERIODMAX)  // if NOT valid
    {
      stop_output();
    }
    else      // else mains seems to be good!
    {
      mainsgood++;                          // count good mains cycles
      if(mainsgood > 200) mainsgood = 200;  // and limit max count
    }

    //---------------------------------------------
    // If mains is reliable, ie has been good for at least 100 cycles,
    // then start filling the shift registers.
    if(mainsgood >= 100)
    {
      // shift the bit1 into shift register A
      shiftA[5] <<= 1;
      shiftA[5].F0 = shiftA[4].F7;
      shiftA[4] <<= 1;
      shiftA[4].F0 = shiftA[3].F7;
      shiftA[3] <<= 1;
      shiftA[3].F0 = shiftA[2].F7;
      shiftA[2] <<= 1;
      shiftA[2].F0 = shiftA[1].F7;
      shiftA[1] <<= 1;
      shiftA[1].F0 = shiftA[0].F7;
      shiftA[0] <<= 1;
      shiftA[0].F0 = capture_new_lo.F1;

      // shift the bit2 into shift register B
      shiftB[3] <<= 1;
      shiftB[3].F0 = shiftB[2].F7;
      shiftB[2] <<= 1;
      shiftB[2].F0 = shiftB[1].F7;
      shiftB[1] <<= 1;
      shiftB[1].F0 = shiftB[0].F7;
      shiftB[0] <<= 1;
      shiftB[0].F0 = capture_new_lo.F2;
    }

    //---------------------------------------------
    // if mains is reliable and shift registers full, then process
    // RNG data and send it out serial port, and operate "data good" LED.
    if(mainsgood > (100+47))
    {
      // XOR the 3 bits; shiftA bit47, shiftB bit29, and new bit0
      tbyte.F0 = capture_new_lo.F0;     // get new bit0 into temp byte bit0
      if(shiftA[5].F7) tbyte ^= 0x01;   // XOR shiftA bit 47
      if(shiftB[3].F5) tbyte ^= 0x01;   // XOR shiftB bit 29

      // add to final shift register to output each 8bits as a byte
      serbyte <<= 1;            // left shift 1 bit
      serbyte.F0 = tbyte.F0;    // and add in new RNG bit
      serbitcount++;
      if(serbitcount >= 8)
      {
        serbitcount = 0;
        Usart_Write(serbyte);   // output the RNG byte to serial
      }

      // flash the "data good" LED, exactly once per second at 50% duty cycle
      flashcount++;
      if(flashcount >= (MAINSFREQ*2))  flashcount = 0;
      if(flashcount < MAINSFREQ) PORTB.F7 = 1;     // LED on
      else                       PORTB.F7 = 0;     // or off
    }
    //---------------------------------------------
  }
}
//-----------------------------------------------------------------------------


