#!/usr/bin/perl
use v5.26;
use warnings;
use Tickit;
use Tickit::Widgets qw( VBox HBox Button Static GridBox CheckButton Choice );
use Device::BusPirate;
use Getopt::Long;
# All the ->add code is nicer if you can chain calls
{
# TODO: This should be in Tickit itself
my $old_add = Tickit::ContainerWidget->can( 'add' );
no strict 'refs'; no warnings 'redefine';
*Tickit::ContainerWidget::add = sub {
my $self = shift;
$self->$old_add( @_ );
return $self;
};
}
Tickit::Style->load_style( <<'EOF' );
HBox { spacing: 1; }
CheckButton:active { fg: "green"; check-fg: "green"; }
EOF
GetOptions(
'p|pirate=s' => \my $PIRATE,
'b|baud=i' => \my $BAUD,
'n|no-chip' => \my $NO_CHIP,
'f|fuses=s' => \my $FUSEVALUES,
) or exit 1;
my $avr;
unless( $NO_CHIP ) {
my $pirate = Device::BusPirate->new(
serial => $PIRATE,
baud => $BAUD,
);
END { $pirate and $pirate->stop; }
$avr = $pirate->mount_chip( "AVR_HVSP" )->get;
$avr->start->get;
END { $avr and $avr->stop->get; }
# Powerdown after initial read
$avr->all_power(0)->get;
}
my $partname = $avr ? $avr->partname : "ATtiny841";
my $fuseinfo = $avr ? $avr->fuseinfo : Device::BusPirate::Chip::AVR_HVSP::FuseInfo->for_part( "ATtiny841" );
my $has_efuse = grep { $_->offset > 1 } $fuseinfo->fuses;
my $tickit = Tickit->new(
root => Tickit::Widget::VBox->new( spacing => 1 )
->add( Tickit::Widget::HBox->new( spacing => 2 )
->add( Tickit::Widget::Static->new(
text => "Device: " . $partname
), expand => 1 )
->add( my $fuselabel = Tickit::Widget::Static->new(
text => "Fuses: "
), expand => 1 )
)
->add( Tickit::Widget::HBox->new( spacing => 2 )
->add( Tickit::Widget::Button->new(
label => "Default",
on_click => \&default_fuses,
), expand => 1 )
->add( Tickit::Widget::Button->new(
label => "Read",
on_click => \&read_fuses,
), expand => 1 )
->add( Tickit::Widget::Button->new(
label => "Write",
on_click => \&write_fuses,
), expand => 1 )
)
->add( my $fusegrid = Tickit::Widget::GridBox->new(
col_spacing => 2,
), expand => 1 )
);
my %fuses; # $name => [ $value, $on_read ]
sub def_fuse
{
my ( $name ) = @_;
my $row = $fusegrid->rowcount;
$fusegrid->add( $row, 0,
Tickit::Widget::Static->new(
text => $name,
)
);
return $row;
}
sub def_boolfuse
{
my ( $name, $caption ) = @_;
my $row = def_fuse( $name );
$fusegrid->add( $row, 1,
my $check = Tickit::Widget::CheckButton->new(
label => $caption,
on_toggle => sub {
$fuses{$name}[0] = !$_[1];
render_fuse_label();
},
)
);
$fuses{$name}[1] = sub { $_[0] ? $check->deactivate : $check->activate };
}
sub def_intfuse
{
my ( $name, $caption, $values ) = @_;
my $row = def_fuse( $name );
$fusegrid->add( $row, 1, Tickit::Widget::Static->new( text => $caption ) );
$fusegrid->add( $row+1, 1,
my $choice = Tickit::Widget::Choice->new
);
foreach my $val ( @$values ) {
$choice->push_choice( $val->value => $val->caption );
}
$choice->set_on_changed( sub {
my ( undef, $value ) = @_;
$fuses{$name}[0] = $value;
render_fuse_label();
});
$fuses{$name}[1] = sub {
eval { $choice->choose_by_value( $_[0] ) };
};
}
foreach my $fuse ( $fuseinfo->fuses ) {
if( my $values = $fuse->values ) {
def_intfuse $fuse->name, $fuse->caption, $values;
}
else {
def_boolfuse $fuse->name, $fuse->caption;
}
}
our $LOADING;
sub render_fuse_label
{
return if $LOADING;
my $fusebytes = $fuseinfo->pack( map { $_ => $fuses{$_}[0] } keys %fuses );
$fuselabel->set_text( sprintf "Fuses: %v02x", $fusebytes );
}
sub default_fuses
{
# These from the ATtiny24/44/84 data sheet
my %fusevals = (
SELFPRGEN => 1,
RSTDISBL => 1,
DWEN => 1,
SPIEN => 0,
WDTON => 1,
EESAVE => 1,
BODLEVEL => 0x07,
CKDIV8 => 0,
CKOUT => 1,
SUT => 0x02,
CKSEL => 0x02,
);
$fuses{$_}[0] = $fusevals{$_} for keys %fusevals;
foreach my $name ( keys %fusevals ) {
$fuses{$name}[1]->( $fusevals{$name} ) if $fuses{$name}[1];
}
}
sub read_fuses
{
my $lfuse;
my $hfuse;
my $efuse;
if( $avr ) {
$avr->all_power(1)->get;
$lfuse = $avr->read_lfuse->get,
$hfuse = $avr->read_hfuse->get,
$efuse = ( $has_efuse ? $avr->read_efuse->get : "" ),
$avr->all_power(0)->get;
}
else {
( $lfuse, $hfuse, $efuse ) = map { chr hex } split m/:/, $FUSEVALUES;
}
my %fusevals = $fuseinfo->unpack( join "", $lfuse, $hfuse, $efuse );
$fuselabel->set_text( sprintf "Fuses: %v02x", $lfuse.$hfuse.$efuse );
$fuses{$_}[0] = $fusevals{$_} for keys %fusevals;
local $LOADING = 1;
foreach my $name ( keys %fusevals ) {
$fuses{$name}[1]->( $fusevals{$name} ) if $fuses{$name}[1];
}
}
sub write_fuses
{
$avr->all_power(1)->get;
my $fusebytes = $fuseinfo->pack( map { $_ => $fuses{$_}[0] } keys %fuses );
$avr->write_lfuse( substr $fusebytes, 0, 1 )->get;
$avr->write_hfuse( substr $fusebytes, 1, 1 )->get;
$avr->write_efuse( substr $fusebytes, 2, 1 )->get if $has_efuse;
$avr->all_power(0)->get;
}
$tickit->run;