AVR Embedded Tutorial - SPI Shiftregister

From Free Pascal wiki

Deutsch (de) English (en)

Controlling a 74HC595 shift register via SPI

While other shift registers can also be controlled, the SPI compatible 74HC595 has a big advantage over a normal shift register in that it is about 10 times faster (ATmega328). This is noticeable with a larger multiplex.

The following functions are described in other tutorials:

Atmega328 / Arduino Uno / Nano example

Constants

Pins which are required at PORTB for SPI.

 const
   SPI_SlaveSelect = 2;
   SPI_MOSI        = 3;
   SPI_Clock       = 5;

Write to a pin

Writing a PORTB pin is used for SlaveSelect and for the software version.

  procedure WritePortB(Pin : byte; Value : boolean);
   begin
     if Value then 
       begin
         PORTB := PORTB or (1 shl pin);
       end 
     else 
       begin
         PORTB := PORTB and not (1 shl pin);
       end;
   end;

Write character string to the SPI port

Writing the string

  procedure WriteDataHardSPI(p : PByte; len : byte);
  var
    i : byte;
  begin
    WritePortB(SPI_SlaveSelect, False);

    for i := len - 1 downto 0 do 
      begin
        SPDR := p[i];

        while(SPSR and (1 shl SPIF)) = 0 do 
          begin
          end;
      end;
    
    WritePortB(SPI_SlaveSelect, True);
  end;

Main program

The SPI register is set to the highest possible speed.

 var
   z : uint16;
 begin
   // All pins set to output
   DDRB := (1 shl SPI_MOSI) or (1 shl SPI_Clock) or (1 shl SPI_SlaveSelect);

   // Set the SPI register
   SPCR := (1 shl SPE) or (1 shl MSTR) or (%00 shl SPR);
   SPSR := (1 shl SPI2X);  

   repeat
     // Increment the counter, return to 0 if overflow occurs
     Inc(z);

     // SPI port described
     WriteDataHardSPI(@z, 2);  // write 2 bytes.
   until 1 = 2;
 end.

ATTiny2313 example

The ATTiny2313 has no direct SPI interface, but there is the universal USI interface, which is used here for SPI . This is a little more complicated when writing than with the direct SPI interface of the Atmega.

Note: The ATTiny2313 also has MOSI and MISO connections, but these are only used for the programmer. There are DO and DI for this, but they work exactly in the reverse order.

Declarations

Pins which are required at PORTB for SPI.

A bitpacked record is used for this, so you can access the pins directly.

 type
   TSPIGPIO = bitpacked record
     p0, p1, p2, p3, SlaveSelect, DataInt, DataOut, Clock : boolean;
   end;

 var
   SPI_PORT : TSPIGPIO absolute PORTB;
   SPI_DDR  : TSPIGPIO absolute DDRB;

Write a character string to the SPI port

Writing the string

  procedure SPIWriteData(p : PByte; len : byte);
   var
     i : byte;
   begin
     SPI_PORT.SlaveSelect := False;

     for i : = len - 1 downto 0 do
       begin
         USIDR := p[i];
         USISR := 1 shl USIOIF;

         repeat
           USICR := (%01 shl USIWM) or (%10 shl USICS) or (1 shl USICLK) or (1 shl USITC);
         until (USISR and (1 shl USIOIF)) <> 0;
       end;

     SPI_PORT.SlaveSelect := True;
   end;

Main program - Hardware version

This example shows how to write 2 cascaded shift registers.

An integer value is simply output.

You can also see that no configuration of USI is necessary at the beginning, except that you have to set the ports to output.

  var
   z : uint16 = 0;
 begin
   SPI_DDR.DataOut     := True;
   SPI_DDR.Clock       := True;
   SPI_DDR.SlaveSelect := True;

   repeat
     Inc(z);
     SPIWriteData(@z, 2);  // output integer
   until 1 = 2;
 end.

Main program - Software version

I can show that you can also do software emulation very well here.

However, this is about a factor of 10 slower than the hardware version of the Atmega328, you can use any ports for this.

The software version has the advantage that it runs on every AVR and can also be used if you need multiple SPI interfaces.

  procedure WriteDataSoftSPI(p : PByte; len : byte);
  var
    i , j : byte;
  begin
    WritePortB(SPI_SlaveSelect, False);

    for j := 0 to len - 1 do 
      begin
        for i := 7 downto 0 do 
          begin
            if(p[j] and (1 shl i)) <> 0 then
              begin
                WritePortB(SPI_MOSI, True);
              end 
            else 
              begin
                WritePortB(SPI_MOSI, False);
              end;

            WritePortB(SPI_Clock, True);
            WritePortB(SPI_Clock, False);
          end;
        
        WritePortB(SPI_SlaveSelect, True);
      end;
  end;

See also