#!/usr/bin/env perl

use strict;
use warnings;
$|++;


use Device::SerialPort;
use Digest::CRC qw/crc16/;
use Time::HiRes qw/usleep/;


my %g_dev = (
	dev => '/dev/ttyACM0',
	speed => '9600',
);


use constant {
	SLIP_END => 0300,
	SLIP_ESC => 0333,
	SLIP_ESC_END => 0334,
	SLIP_ESC_ESC => 0335,
};


sub slip_send {
	my @data = map { 
		$_ == SLIP_END ? (SLIP_ESC, SLIP_ESC_END) : 
			($_ == SLIP_ESC ? (SLIP_ESC, SLIP_ESC_ESC) : $_) } @_ ;

	my $crc = crc16( pack "C*", (0,0,@_));
	return pack "CSC*", (SLIP_END, $crc, @data, SLIP_END); 
}


sub slip_recv {
	my $read_cb = shift || return;
	my @data = ();
	my $mode = 0;

	while (1) {
		my $c = 0x00;
		next unless &$read_cb(\$c);

		if ($c == SLIP_END) {
			last if (scalar @data);
			next;
		}

		if ($c == SLIP_ESC) {
			$mode = 1;
			next;
		}

		if ($mode) {
			$c = ($c == SLIP_ESC_END ? SLIP_END : 
					($c == SLIP_ESC_ESC ? SLIP_ESC : $c));
			$mode = 0;
		}

		push @data, $c;
	}
	
	pack "C*", @data;
}

# get the input values
die "host_slip.pl <prescaler> <ocr> <max_st>\n"
	unless (scalar @ARGV == 3);

my $prescaler = shift || 0;
my $ocr = shift || 0;
my $max_st = shift || 0;

# initialize the port
my $port = new Device::SerialPort($g_dev{dev}); 
$port->baudrate($g_dev{speed}); 
$port->parity("none"); 
$port->databits(8); 
$port->stopbits(1); 
$port->dtr_active(1);
$port->write_settings();
$port->purge_all;


# getchar wrapper for slip receive
sub serial_getc {
	my $cout = shift;
	my ($cin, $cdata) = $port->read(1);
	$$cout = unpack "C", $cdata;
	return $cin;
}


# wait for the reset
sleep 4;

# send the timer_data
$port->write(slip_send($prescaler, $ocr, $max_st));
until ($port->write_drain) {}

# wait for ACK or NACK
my $data = slip_recv(\&serial_getc);

# unpack the serialized data to variables
my ($crc, $status) = unpack("SC", $data);

# calculate the checksum of the response for verification
my $crc_calcd = crc16(pack("SC", (0,$status)));

# display the results
printf("CRC/CRC_CALCD/STATUS: 0x%04x/0x%04x/%s[%02x]\n", 
		$crc, 
		$crc_calcd,
		($status == 0x10 ? "ACK" : "NACK"),
		$status);