Device-Chip-SDCard

 view release on metacpan or  search on metacpan

lib/Device/Chip/SDCard.pm  view on Meta::CPAN

=head2 initialise

   await $card->initialise;

Checks that an SD card is present, switches it into SPI mode and waits for its
initialisation process to complete.

=cut

async method initialise ()
{
   # Initialise first by switching the card into SPI mode
   await $self->protocol->write( "\xFF" x 10 );

   my $resp = await $self->send_command( CMD_GO_IDLE_STATE );
   $resp == 1 or die "Expected 01 response; got $resp";

   foreach my $attempt ( 1 .. 200 ) {
      # TODO: Consider using SEND_IF_COND and doing SDHC initialisation
      $resp = await $self->send_command( CMD_SEND_OP_COND );
      last unless $resp & RESP_IDLE;
   }

   $resp & RESP_IDLE and die "Timed out waiting for card to leave IDLE mode";

   $resp = await $self->send_command( CMD_SET_BLOCKLEN, 512 );
   $resp == 0 or die "Expected 00 response; got $resp";

   return;
}

=head2 size

   $n_bytes = await $card->size;

Returns the size of the media card in bytes.

=cut

async method size ()
{
   my $csd = await $self->read_csd;

   return $csd->{bytes};
}

method _spi_txn ( $code )
{
   $self->protocol->assert_ss->then(
      $code
   )->followed_by( sub {
      my ( $f ) = @_;
      $self->protocol->release_ss->then( sub { $f } );
   });
}

=head2 read_csd

   $data = await $card->read_csd;

Returns a C<HASH> reference containing decoded fields from the SD card's CSD
("card-specific data") register.

This hash will contain the following fields:

   TAAC
   NSAC
   TRAN_SPEED
   CCC
   READ_BL_LEN
   READ_BL_LEN_PARTIAL
   WRITE_BLK_MISALIGN
   READ_BLK_MISALIGN
   DSR_IMP
   C_SIZE
   VDD_R_CURR_MIN
   VDD_R_CURR_MAX
   VDD_W_CURR_MIN
   VDD_W_CURR_MAX
   C_SIZE_MULT
   ERASE_BLK_EN
   SECTOR_SIZE
   WP_GRP_SIZE
   WP_GRP_ENABLE
   R2W_FACTOR
   WRITE_BL_LEN
   WRITE_BL_PARTIAL
   FILE_FORMAT_GRP
   COPY
   PERM_WRITE_PROTECT
   TEMP_WRITE_PROTECT
   FILE_FORMAT

The hash will also contain the following calculated fields, derived from the
decoded fields above for convenience of calling code.

   blocks          # number of blocks implied by C_SIZE / C_SIZE_MULT
   bytes           # number of bytes of storage, implied by blocks and READ_BL_LEN

=cut

# This code is most annoying to write as it involves lots of bitwise unpacking
# at non-byte boundaries. It's easier (though inefficient) to perform this on
# an array of 128 1-bit values
sub _bits_to_uint ( @vals )
{
   my $n = 0;
   ( $n <<= 1 ) |= $_ for reverse @vals;
   return $n;
}

my %_DECSCALE = (
   1 => 1.0, 2 => 1.2, 3 => 1.3, 4 => 1.5, 5 => 2.0, 6 => 2.5,
   7 => 3.0, 8 => 3.5, 9 => 4.0, 0xA => 4.5, 0xB => 5.0,
   0xC => 5.5, 0xD => 6.0, 0xE => 7.0, 0xF => 8.0
);

sub _convert_decimal ( $unit, $val )
{
   my $mult = $unit % 3;
   $unit -= $mult;
   $unit /= 3;

   $val = $_DECSCALE{$val} * ( 10 ** $mult );

   return $val . substr( "num kMG", $unit + 3, 1 );
}

my %_CURRMIN = (
   0 => 0.5, 1 => 1, 2 => 5, 3 => 10,
   4 => 25, 5 => 35, 6 => 60, 7 => 100,
);
my %_CURRMAX = (
   0 => 1, 1 => 5, 2 => 10, 3 => 25,
   4 => 35, 5 => 45, 6 => 80, 7 => 200,
);

sub _unpack_csd_v0 ( $bytes )
{
   my @bits = reverse split //, unpack "B128", $bytes;

   my %csd = (
      TAAC                => _convert_decimal( _bits_to_uint( @bits[112 .. 114] ) - 9, _bits_to_uint( @bits[115 .. 118] ) ) . "s",
      NSAC                => 100*_bits_to_uint( @bits[104 .. 111] ) . "ck",
      TRAN_SPEED          => _convert_decimal( _bits_to_uint( @bits[ 96 ..  98] ) + 5, _bits_to_uint( @bits[ 99 .. 102] ) ) . "bit/s",
      CCC                 => [ grep { $bits[84+$_] } 0 .. 11 ],
      READ_BL_LEN         => 2**_bits_to_uint( @bits[ 80 ..  83] ),
      READ_BL_LEN_PARTIAL => $bits[79],
      WRITE_BLK_MISALIGN  => $bits[78],
      READ_BLK_MISALIGN   => $bits[77],
      DSR_IMP             => $bits[76],
      C_SIZE              => _bits_to_uint( @bits[ 62 ..  73] ),
      VDD_R_CURR_MIN      => $_CURRMIN{ _bits_to_uint( @bits[ 59 ..  61] ) } . "mA",
      VDD_R_CURR_MAX      => $_CURRMAX{ _bits_to_uint( @bits[ 56 ..  58] ) } . "mA",
      VDD_W_CURR_MIN      => $_CURRMIN{ _bits_to_uint( @bits[ 53 ..  55] ) } . "mA",
      VDD_W_CURR_MAX      => $_CURRMAX{ _bits_to_uint( @bits[ 50 ..  52] ) } . "mA",
      C_SIZE_MULT         => _bits_to_uint( @bits[ 47 ..  49] ),
      ERASE_BLK_EN        => $bits[46],
      SECTOR_SIZE         => 1+_bits_to_uint( @bits[ 39 ..  45] ),
      WP_GRP_SIZE         => 1+_bits_to_uint( @bits[ 32 ..  38] ),
      WP_GRP_ENABLE       => $bits[31],
      R2W_FACTOR          => 2**_bits_to_uint( @bits[ 26 ..  28] ),
      WRITE_BL_LEN        => 2**_bits_to_uint( @bits[ 22 ..  25] ),
      WRITE_BL_PARTIAL    => $bits[21],
      FILE_FORMAT_GRP     => $bits[15],
      COPY                => $bits[14],
      PERM_WRITE_PROTECT  => $bits[13],
      TEMP_WRITE_PROTECT  => $bits[12],
      FILE_FORMAT         => _bits_to_uint( @bits[ 10 ..  11] ),
      # Final bits are the CRC, which we ignore
   );

   $csd{blocks} = ( 1 + $csd{C_SIZE} ) * ( 2 ** ( $csd{C_SIZE_MULT} + 2 ) );
   $csd{bytes}  = $csd{blocks} * $csd{READ_BL_LEN};

   return \%csd;
}

async method read_csd ()
{
   my $protocol = $self->protocol;

   my $csd = await $self->_spi_txn( async sub {
      await $protocol->write_no_ss(
         pack "C N C a*", 0x40 | CMD_SEND_CSD, 0, 0xFF, "\xFF"
      );

      my $buf = await $protocol->readwrite_no_ss( "\xFF" x 8 );

      $buf =~ s/^\xFF*//;
      $buf =~ s/^\0// or
         return Future->fail( sprintf "Expected response 00; got %02X to SEND_CSD", ord $buf );

      return await $self->_recv_data_block( $buf, 16 );
   });

   # Top two bits give the structure version
   my $ver = vec( $csd, 0, 2 );
   if( $ver == 0 ) {
      return _unpack_csd_v0( $csd );
   }
   elsif( $ver == 1 ) {
      return _unpack_csd_v1( $csd );
   }
   else {
      die "Bad CSD structure version $ver";
   }
}

=head2 read_ocr

   $fields = await $card->read_ocr;

Returns a C<HASH> reference containing decoded fields from the card's OCR
("operating conditions register").

This hash will contain the following fields:

   BUSY
   CCS
   UHS_II
   1V8_ACCEPTED
   3V5, 3V4, 3V3, ..., 2V7

=cut

bitfield OCR =>
   BUSY   => boolfield( 31 ),
   CCS    => boolfield( 30 ),
   UHS_II => boolfield( 29 ),
   '1V8_ACCEPTED' => boolfield( 24 ),
   '3V5'          => boolfield( 23 ),
   '3V4'          => boolfield( 22 ),
   '3V3'          => boolfield( 21 ),
   '3V2'          => boolfield( 20 ),
   '3V1'          => boolfield( 19 ),
   '3V0'          => boolfield( 18 ),
   '2V9'          => boolfield( 17 ),
   '2V8'          => boolfield( 16 ),
   '2V7'          => boolfield( 15 );

async method read_ocr ()
{
   my ( $resp, $ocr ) = await $self->send_command( CMD_READ_OCR, undef, 4 );

   return { unpack_OCR( unpack "N", $ocr ) };
}

=head2 read_block

   $bytes = await $card->read_block( $lba );

Returns a 512-byte bytestring containing data read from the given sector of
the card.

=cut

async method read_block ( $lba )
{
   my $byteaddr = $lba * 512;

   my $protocol = $self->protocol;

   my $buf;

   return await $self->_spi_txn( async sub {
      await $protocol->write_no_ss(
         pack "C N C a*", 0x40 | CMD_READ_SINGLE_BLOCK, $byteaddr, 0xFF, "\xFF"
      );

      my $buf = await $protocol->readwrite_no_ss( "\xFF" x 8 );

      $buf =~ s/^\xFF*//;
      $buf =~ s/^\0// or



( run in 0.535 second using v1.01-cache-2.11-cpan-c966e8aa7e8 )