#!/usr/bin/perl

# Generates new delay tables for use in NewSoftSerial.  New values are
# generated by calculating a point on the line that contains the 8MHz
# and 16MHz points.

# Copyright (C) 2010 John C. Luciani Jr. 

# This program may be distributed or modified under the terms of
# version 0.2 of the No-Fee Software License published by
# John C. Luciani Jr.

# A copy of the license is at the end of this file.

use strict;
use warnings;
use Tie::IxHash;
use Carp;

use Data::Dumper;

# (x0,y0) 8MHz  point
# (x1,y1) 16MHz point
# (x2,y2) calculated point 

use constant X0 => 8000000; # Hz (point 0)
use constant X1 =>16000000; # Hz (point 1)
use constant X2 =>12000000; # Hz (new frequency)
#
# All delay values from the existing code block are read into the
# Timing hash. An indexed hash is used so that the code block can be
# recreated with the new values at the end.
#
# The hash is populated with the xmit_start_adjustment,
# and the delay values for rxcenter, rxintra, rxstop and tx.
#
# $Timing{CPU_FREQ}{adj} = ADJ_VALUE
# $Timing{CPU_FREQ}{delays}{BAUD} = { rxcenter => RXCENTER_VALUE,
#                                     rxintra  => RXINTRA_VALUE,
#                                     rxstop   => RXSTOP_VALUE,
#                                     tx       => TX_VALUE }
#
my @Fields = qw(rxcenter rxintra rxstop tx); # delay fields
my %Timing; # timing table
tie %Timing, "Tie::IxHash";
#
# Read the lines in between the __DATA__ and __END__ markers
# and populate %Timing
#
my $F_CPU; # Frequency contained in the #if F_CPU conditional
while (<DATA>) {
    s/^\s*//;            # Remove leading spaces
    s/\s*$//;            # Revove trailing spaces
    next unless length;  # Skip empty lines
    last if /^__END__$/; # Skip lines after the end marker
    if (s/\\\s*$//) {    # Remove the continuation backslash and 
	$_ .= <DATA>;    # append the next line to $_ then 
	redo unless eof; # restart the loop block after the conditional
    }
    $F_CPU = $1, next if /F_CPU\D+(\d+)/;
    next unless defined $F_CPU; 
    # 
    # if the conditional block ends then the CPU frequency
    # is undefined
    #
    $F_CPU = undef, next if /#else/;
    $F_CPU = undef, next if /#elif/;
    $F_CPU = undef, next if /#endif/;
    #
    # Save the data values
    #
    if (/XMIT_START_ADJUSTMENT\D+(\d+)/) {
	$Timing{$F_CPU}{adj} = $1;
    } elsif (/(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d+)\D+/) {
	my ($baud, @v) = ($1, $2, $3, $4, $5);
	$Timing{$F_CPU}{delays}{$baud} = { map { $_ => shift(@v) } @Fields };
    } 
}
#
# foreach baud found generate a set of timing delays.
# timing delay.
#
my @bauds = reverse sort { $a <=> $b } keys % { $Timing{&X0}{delays} };
foreach my $baud (@bauds) {
    $Timing{&X2}{delays}{$baud} = { map { $_ => &y2($baud,$_) } @Fields };
}
#
# generate the transmit start adjustment
#
my ($y1, $y0) = map { $Timing{$_}{adj} } (X1, X0);
$Timing{&X2}{adj} = &_y2(X0, $y0, X1, $y1, X2);
#
# Output the new table. New entries are at the bottom of the table
#
my @F_CPU = keys %Timing;
my $IF = "#if ";
while (@F_CPU) {
    my $f_cpu = shift @F_CPU;
    printf("%s F_CPU == %i\n\n", $IF, $f_cpu);
    printf("static const DELAY_TABLE PROGMEM table[] = \n");
    printf("{\n");
    printf("  //  baud    rxcenter   rxintra    rxstop        tx\n");
    my @bauds = reverse sort { $a <=> $b } keys % { $Timing{$f_cpu}{delays} };
    foreach my $baud (@bauds) {
	my $timing = $Timing{$f_cpu}{delays}{$baud};
	printf("  { %6i,     %6i,   %6i,   %6i,   %6i, },\n",
	       $baud, map { $timing->{$_} } @Fields);
    }
    printf("};\n\n");
    printf("const int XMIT_START_ADJUSTMENT = %i;\n\n", $Timing{$f_cpu}{adj});
    $IF = "#elif"; 
}
printf("#else\n\n");
printf("#error This version of NewSoftSerial does not support your clock clock frequency\n\n");
printf("#endif\n");
#
# calculates a new y value given (x0, y0), (x1, y1) and a 
# new x value (x2)
#
sub _y2 {
    my ($x0, $y0, $x1, $y1, $x2) = @_;
    my $y2 = ($x2 - $x0) * ($y1 - $y0) / ($x1 - $x0) + $y0;
    return(sprintf("%i", $y2 + 0.5));
}
#
# calculates a new y value given the baud rate and field
# X0, X1, X2 are global constants
#
sub y2 {
    my ($baud, $field) = @_;
    my ($y1, $y0) = map { $Timing{$_}{delays}{$baud}{$field} } (X1, X0);
    return(&_y2(X0, $y0, X1, $y1, X2));
}


### Place the delay_table block from the original NewSoftSerial.cpp
### between the __DATA__ and __END__ markers.

__DATA__

#if F_CPU == 16000000

static const DELAY_TABLE PROGMEM table[] = 
{
  //  baud    rxcenter   rxintra    rxstop    tx
  { 115200,   1,         17,        17,       12,    },
  { 57600,    10,        37,        37,       33,    },
  { 38400,    25,        57,        57,       54,    },
  { 31250,    31,        70,        70,       68,    },
  { 28800,    34,        77,        77,       74,    },
  { 19200,    54,        117,       117,      114,   },
  { 14400,    74,        156,       156,      153,   },
  { 9600,     114,       236,       236,      233,   },
  { 4800,     233,       474,       474,      471,   },
  { 2400,     471,       950,       950,      947,   },
  { 1200,     947,       1902,      1902,     1899,  },
  { 300,      3804,      7617,      7617,     7614,  },
};

const int XMIT_START_ADJUSTMENT = 5;

#elif F_CPU == 8000000

static const DELAY_TABLE table[] PROGMEM = 
{
  //  baud    rxcenter    rxintra    rxstop  tx
  { 115200,   1,          5,         5,      3,      },
  { 57600,    1,          15,        15,     13,     },
  { 38400,    2,          25,        26,     23,     },
  { 31250,    7,          32,        33,     29,     },
  { 28800,    11,         35,        35,     32,     },
  { 19200,    20,         55,        55,     52,     },
  { 14400,    30,         75,        75,     72,     },
  { 9600,     50,         114,       114,    112,    },
  { 4800,     110,        233,       233,    230,    },
  { 2400,     229,        472,       472,    469,    },
  { 1200,     467,        948,       948,    945,    },
  { 300,      1895,       3805,      3805,   3802,   },
};

const int XMIT_START_ADJUSTMENT = 4;

#elif F_CPU == 20000000

// 20MHz support courtesy of the good people at macegr.com.
// Thanks, Garrett!

static const DELAY_TABLE PROGMEM table[] =
{
  //  baud    rxcenter    rxintra    rxstop  tx
  { 115200,   3,          21,        21,     18,     },
  { 57600,    20,         43,        43,     41,     },
  { 38400,    37,         73,        73,     70,     },
  { 31250,    45,         89,        89,     88,     },
  { 28800,    46,         98,        98,     95,     },
  { 19200,    71,         148,       148,    145,    },
  { 14400,    96,         197,       197,    194,    },
  { 9600,     146,        297,       297,    294,    },
  { 4800,     296,        595,       595,    592,    },
  { 2400,     592,        1189,      1189,   1186,   },
  { 1200,     1187,       2379,      2379,   2376,   },
  { 300,      4759,       9523,      9523,   9520,   },
};

const int XMIT_START_ADJUSTMENT = 4;

#endif


__END__

# Style (adapted from the Perl Cookbook, First Edition, Recipe 12.4)

# 1. Names of functions and local variables are all lowercase.
# 2. The program's persistent variables (either file lexicals
#    or package globals) are capitalized.
# 3. Identifiers with multiple words have each of these
#    separated by an underscore for readability.
# 4. Constants are all uppercase.
# 5. If the arrow operator (->) is followed by either a
#    method name or a variable containing a method name then
#    there is a space before and after the operator.


##### No-Fee Software License Version 0.2

#### Intent

### The intent of this license is to allow for distribution of this
### software without fee. Usage of this software other than
### distribution, is unrestricted.

#### License

### Permission is granted to make and distribute verbatim copies of
### this software provided that (1) no fee is charged and (2) the
### original copyright notice and this license document are preserved
### on all copies.

### Permission is granted to make and distribute modified versions of
### this software under the conditions for verbatim copying provided
### that the entire resulting derived work is distributed under terms
### of a license identical to this one.


### This software is provided by the author "AS IS" and any express or
### implied warranties, including, but not limited to, the implied
### warranties of merchantability and fitness for a particular purpose
### are disclaimed. In no event shall the author be liable for any
### direct, indirect, incidental, special, exemplary, or consequential
### damages (including, but not limited to, procurement of substitute
### goods or services; loss of use, data, or profits; or business
### interruption) however caused and on any theory of liability,
### whether in contract, strict liability, or tort (including
### negligence or otherwise) arising in any way out of the use of this
### software, even if advised of the possibility of such damage.

