MegaSquirt Tuning Tools

I've created a distributable package of perl modules containing useful tools for dealing with megasquirt input and output files. While working with my own and others projects these tools were formed organically in response to the lack of simple commandline interfaces for offline fine tuning fuel maps. The main tasks covered in these modules are parsing and analyzing fuel maps and datalogs. There are also some handy tools for converting matrixes to different resolutions. I've use these tools with great success so I am posting them here for others to use and modify.

I realize that the intersection of perl programmers and MegaSquirt users is very small but I figured I would throw this out there for anyone interested.

There are some explicit instructions below on how to install and use this code. Feel free to email me if you have any questions or requests.

Download megasquirt-perl-1.01.tar.gz
Download megasquirt-perl-1.01.zip


Install instructions:

Prerequisites:
Class::Exception
Math::Interpolate
Math::Derivative

You can use the CPAN system to install these modules.

perl -MCPAN -eshell

cpan> install Class::Exception
cpan> install Math::Interpolate
cpan> install Math::Derivative

Download the megasquirt-perl tarball and unpack it:

tar -xvzf megasquirt-perl-1.01.tar.gz

cd into the resulting megasquirt directory and build the modules

cd megasquirt
perl Makefile.PL
make && make install

The megasquirt-perl modules will be installed in your perl tree

Usage:

This package includes a module for manipulating VE tables (MegaSquirt::VE) and another for manipulating datalogs (MegaSquirt::DataLog). The datalog object will create a series of events containing all attributes of that event. The naming convention for those events is the same used by the logging software and any event value can be called using a method by the same name, i.e. coolant temperature value can be accessed by calling $event->CLT()

The example scripts below should get you started.

Matrix Converter

The script below slurps in a .vex file and converts it to a larger or smaller matrix of arbritrary size. The conversion routines take into account the curved distribution of points in the matrix and maintains that distribution during the conversion through the use of mathematical splines.


use strict;

use MegaSquirt::VE;
use MegaSquirt::DataLog;
use Data::Dumper;

# set the desired size of the new matrix
# dimensions are limited by your machine's memory
my $new_rpm = 12;
my $new_kpa = 12;
                                                                               
my $vex_file = $ARGV[0];
die 'need vex file' unless $vex_file;
                                                                             
my $ve;
eval {
    $ve = MegaSquirt::VE->new_from_file({ filename => $vex_file });
};
die $@ if $@;

my $original = $ve->dump_matrix;
print $original;
my $cnv = $ve->convert_map({ rpm_size => $new_rpm,
                             kpa_size => $new_kpa });

my $converted = $ve->dump_matrix;
print $cnv->out;

Tuning script

The AFR corrections are based on a wideband voltage crossover point of 2.35. Modify the voltage_to_af() and af_to_voltage() functions in Megasquirt::Event to suit your own sensor's conversion factor


use strict;

use MegaSquirt::VE;
use MegaSquirt::DataLog;
use Data::Dumper;

our $PRINT = 0;

# accept inputs
my $log_file = $ARGV[0];
die 'need log file' unless $log_file;
my $vex_file = $ARGV[1];
die 'need vex file' unless $vex_file;

# parse files into memory
my $log;
eval {
    # create datalog object from file
    $log = MegaSquirt::DataLog->new_from_file({ filename => $log_file });
};
die $@ if $@;
my $ve;
eval {
    # create VE table object from file
    $ve = MegaSquirt::VE->new_from_file({ filename => $vex_file });
};
die $@ if $@;
 
# print original matrix
my $original = $ve->dump_matrix;

my $lowest_kpa = 100;
my $highest_kpa = 0;
my $lowest_rpm = 100;
my $highest_rpm = 0;

my $corrected;

# The log object is really a collection of logging events
# iterate using the ->next() method
while (my $event = $log->next) {

    # ignore warmup events
    next if $event->gwarm > 100;

    my $calc = $ve->calculate_ve({ rpm => $event->rpm,
      kpa => $event->kpa, });
    my ($rpm_bin, $kpa_bin, $distance_factor) = $ve->nearest_bin($event->rpm,$event->kpa);

    my $afr = $event->{afr};
    my $rpm = $event->rpm;
    my $kpa = $event->kpa;
    my $gve = $event->gve;
    my $nve = $event->nve;
    my $afr_target = afr_target($rpm,$kpa);
    $nve = $event->correct_ve($event->gve,$event->afr,$afr_target);# unless $nve;
    my $change = $nve - $event->gve;
    push(@{$corrected->{$rpm_bin}->{$kpa_bin}->{nve_data}}, $nve) if $nve;
    my $sign = $change > 0 ? '+' : '';

    $lowest_kpa = $event->kpa if $event->kpa < $lowest_kpa;
    $highest_kpa =  $event->kpa if $event->kpa > $highest_kpa;
    $lowest_rpm = $event->rpm if $event->rpm < $lowest_rpm;
    $highest_rpm = $event->rpm if $event->rpm > $highest_rpm;

    print qq(
    RPM: $rpm_bin ($rpm), 
    KPA: $kpa_bin ($kpa) 
    AFR: $afr  
    CALC: $calc 
    VE: $gve 
    NEW_VE: $nve ($sign$change)\n) if (int($change) > 10 
    || int($change) < -10) if $PRINT;
}

# do a statistical analysis of the data for every 
# bin seen in the logs
process_corrected($ve, $corrected);

print "LOW KPA: $lowest_kpa\n" if $PRINT;
print "HI  KPA: $highest_kpa\n" if $PRINT;;
print "LOW RPM: $lowest_rpm\n" if $PRINT;;
print "HI  RPM: $highest_rpm\n" if $PRINT;;

# output VE file
print $ve->out;
    
sub process_corrected {
    my $ve = shift;
    my $corrected = shift;

    foreach my $rpm (sort(keys %$corrected)) {
    foreach my $kpa (sort(keys %{$corrected->{$rpm}})) {
        my $i = 0;
	    my $sum;
	        foreach my $val (sort(@{$corrected->{$rpm}->{$kpa}->{nve_data}})) {
		$i++;
		$sum += $val;
		    }
		        my $avg = round($sum/$i);
			    my $rprint = 100 * $rpm;

			        print "\n\nrpm: $rprint, kpa: $kpa == AVG: $avg\n\n";
				    $ve->bin($rpm,$kpa,0,$avg);
				    }
    }
    print "ORIGINAL\n$original\n\n-----------------------------\n";
    print "NEW\n" . $ve->dump_matrix . "\n";
}

sub afr_target {
    # used to set different target afrs
    # for given vacuum conditions
    # alter for your own application 
    # (my car likes a richer-than stoich 14.2:1 mixture for cruising and idle)
    my $rpm = shift;
    my $kpa = shift;
    return 12.9 if $kpa >= 50;
    return 13.9 if $kpa >= 36;
    return 13.4 if $rpm > 45;
    return 14.2; 
}


sub round {
    my $val = shift;
    $val =~ /(.*)\.(\d)/;
    return $1 unless $2;

    return $1 + 1 if $2 >= 5;
    return $1;
}




Download megasquirt-perl-1.01.tar.gz