The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#########################################################################################
# Package        HiPi::Interface::MCP4DAC
# Description  : Control MCP4xxx Digital 2 Analog ICs
# Copyright    : Copyright (c) 2013-2017 Mark Dootson
# License      : This is free software; you can redistribute it and/or modify it under
#                the same terms as the Perl 5 programming language system itself.
#########################################################################################

package HiPi::Interface::MCP4DAC;

#########################################################################################

use strict;
use warnings;
use parent qw( HiPi::Interface );
use Carp;
use HiPi qw( :spi :mcp4dac );
use HiPi::Device::SPI;

our $VERSION ='0.66';

__PACKAGE__->create_accessors( qw( bitsperword minvar ic devicename
                                   dualchannel canbuffer buffer gain
                                   writemask shiftvalue shiftbits ) );

sub new {
    my( $class, %userparams ) = @_;
    
    my %params = (
        devicename   => '/dev/spidev0.0',
        speed        => SPI_SPEED_MHZ_1,
        bitsperword  => 8,
        delay        => 0,
        device       => undef,
        ic           => MCP4902,
        buffer       => 0,
        gain         => 0,
        shiftvalue   => 0,
    );
    
    foreach my $key (sort keys(%userparams)) {
        $params{$key} = $userparams{$key};
    }
        
    {
        my $ic = $params{ic};
        
        if( $ic & MCP_DAC_RESOLUTION_12 ) {
            $params{minvar} = 0;
            $params{shiftbits} = 0;
            $params{writemask} = 0b1111111111111111;
        } elsif( $ic & MCP_DAC_RESOLUTION_10 ) {
            $params{minvar} = 4;
            $params{shiftbits} = 2;
            $params{writemask} = 0b1111111111111100;
        } else {
            $params{minvar} = 16;
            $params{shiftbits} = 4;
            $params{writemask} = 0b1111111111110000;
        }
        
        if( $ic & MCP_DAC_CAN_BUFFER ) {
            $params{canbuffer} = 1;
        } else {
            $params{canbuffer} = 0;
        }
        
        if( $ic & MCP_DAC_DUAL_CHANNEL ) {
            $params{dualchannel} = 1;
        } else {
            $params{dualchannel} = 0;
        }
        
    }
    
    unless( defined($params{device}) ) {
        my $dev = HiPi::Device::SPI->new(
            speed        => $params{speed},
            bitsperword  => $params{bitsperword},
            delay        => $params{delay},
            devicename   => $params{devicename},
        );
        
        $params{device} = $dev;
    }
    
    my $self = $class->SUPER::new(%params);
    return $self;
}


sub write {
    my($self, $value, $channelb) = @_;
    $channelb ||= 0;
    $channelb = 0 if !$self->dualchannel;
        
    my $output = ( $channelb ) ? MCP_DAC_CHANNEL_B : MCP_DAC_CHANNEL_A;
    $output += MCP_DAC_BUFFER if($self->canbuffer && $self->buffer);
    $output += ( $self->gain ) ? MCP_DAC_GAIN : MCP_DAC_NO_GAIN;
    $output += MCP_DAC_LIVE;
    
    # allow user to specify values 1-255 for 8 bit device etc
    
    if( $self->shiftvalue ) {
        $value <<= $self->shiftbits;
    }
    
    # mask the $value. If user specifies shiftvalue == true
    # and gives a value over 255 for an 8 bit device
    # confusing things will happen. We only want
    # 12 bits. If user gets it wrong then at least
    # all that happens is they get wrong voltage -
    # instead of potentially writing to wrong channel
    # or shutting the channel down if we shift a high value
    
    $value &= 0b111111111111;
    
    $value = $self->minvar if( $value > 0 && $value < $self->minvar );
    $value = 0 if $value < 0;
        
    $output += $value;
    $output &= $self->writemask;
    $self->device->transfer( $self->_fmt_val( $output ) );
}

sub _fmt_val {
    my($self, $val) = @_;
    pack('n', $val);
}

sub shutdown {
    my($self, $channelb) = @_;
    $channelb ||= 0;
    $channelb = 0 if !$self->dualchannel;
    my $output = ( $channelb ) ? MCP_DAC_CHANNEL_B : MCP_DAC_CHANNEL_A;
    $output += MCP_DAC_SHUTDOWN;
    $self->device->transfer( $self->_fmt_val( $output ) );
}

1;

__END__