SPI Library problem

General discussion on mikroBasic PRO for PIC.
Post Reply
Author
Message
russellt
Posts: 7
Joined: 24 Feb 2014 19:09

SPI Library problem

#1 Post by russellt » 24 Feb 2014 22:28

As part of a step by step approach to my project I am attempting to get a pair of PIC16F873As to talk to each other over their hardware SPI interface. Pin 14 (SCK) of the master is connected to pin 14 of the slave. Pin 15 (SDI) of the master is connected to pin 16 (SDO) of the slave and pin 16 (SDO) of the master is connected to pin 15 (SDI) of the slave. The slave select pin has a pull up resistor and an optional ground connection.

The master has an LED to show me the code is running and the slave has an LCD to show me what is being received.

The problems I am having are:

that I need to re-initialise the SPI interface between every byte received and

that although the slave select pin works OK I can’t make it work with slave select disabled.

My code is shown below – both send and receive are here and the mode variable selects whether the send or receive code is executed.

I have used the LCD to check the SSPSTAT and SSPCON1 registers and these are not changed by the SPI1_Init_Advanced statement.

I expect there is something obvious that I have overlooked but I’ve spent ages looking at the datasheet and I’m struggling.

I’d be grateful for any advice.

Russell

Code: Select all

program MyProject

' Declarations section
' Lcd module connections
dim
  LCD_RS as sbit at RB4_bit
  LCD_EN as sbit at RB5_bit
  LCD_D7 as sbit at RB3_bit
  LCD_D6 as sbit at RB2_bit
  LCD_D5 as sbit at RB1_bit
  LCD_D4 as sbit at RB0_bit
  CSN as sbit at RA5_bit
  CE as sbit at RA4_bit
dim
  LCD_RS_Direction as sbit at TRISB4_bit
  LCD_EN_Direction as sbit at TRISB5_bit
  LCD_D7_Direction as sbit at TRISB3_bit
  LCD_D6_Direction as sbit at TRISB2_bit
  LCD_D5_Direction as sbit at TRISB1_bit
  LCD_D4_Direction as sbit at TRISB0_bit
' End Lcd module connections

dim
i as integer
n as integer
txt as string[3]
buffer as byte
take, dummy as byte
mode as byte
reg as byte

main:
mode = 1
'mode = 0                                           'remove the apostrophe to make the program receive
adcon1 =7
cmcon = 7
trisa.5 = 1
porta.5 = 0
SPI1_Init()
'*****************************************************************************************
Lcd_Init()
Lcd_Cmd(_LCD_CURSOR_OFF)
trisa.0 = 0                                         'sets a.0 as output
i = 1
n = 1
'*****************************************************************************************
'SEND ROUTINE
  while mode = 1
  SPI1_Write(buffer)                                'Send a byte to the SPI
'*****************************************************************************************
  porta.0 = i
  delay_ms(200)
  buffer = buffer + 1
     
      if i = 1 then
      i = 0
      else
      i = 1
      end if

      if buffer = 255 then
      buffer = 0
      end if
'*****************************************************************************************
  WEND

'   RECEIVE ROUTINE
'SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH)
'SPI1_Init_Advanced(_SPI_SLAVE_SS_ENABLE, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH)
  WHILE mode = 0

  SPI1_Init_Advanced(_SPI_SLAVE_SS_ENABLE, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH)
  take = SPI1_Read(dummy)          'Does this pause until it gets data?
  ByteToStr(take, txt)
'*****************************************************************************************
  LCD_OUT(1,1,txt)
  LCD_OUT(2,n,".")
  'delay_ms(10)       

    if n = 16 then
    LCD_OUT(2,1,"                ")
    n=1
    else
    n= n+1
    end if
'*****************************************************************************************
  WEND

end.

russellt
Posts: 7
Joined: 24 Feb 2014 19:09

Re: SPI Library problem

#2 Post by russellt » 20 Mar 2014 22:03

This must be more difficult than I thought.

Or is it too trivial to need a reply?

Russell

User avatar
marina.petrovic
Posts: 2986
Joined: 18 Apr 2013 08:11

Re: SPI Library problem

#3 Post by marina.petrovic » 25 Mar 2014 09:51

Hi,

Please, can you explain me a little bit more where the problem occurs?
You said, "although the slave select pin works OK I can’t make it work with slave select disabled"?

Did you manage to conclude in which part of your code problem occurs - sending or receiving?

Did you take a look at simple example from compiler SPI library?
mikroBasic PRO for PIC Libraries -> Hardware Libraries -> SPI Library

In the initial part of the code, we used:

Code: Select all

Chip_Select = 1                       ' Deselect CS
Chip_Select_Direction = 0          ' Set CS# pin as Output
SPI1_Init()                              ' Initialize SPI module
and then:

Code: Select all

Chip_Select = 0                        ' Select CS
...
SPI1_Write()
...
Chip_Select = 1                        ' Deselect CS
Best regards,
Marina

russellt
Posts: 7
Joined: 24 Feb 2014 19:09

Re: SPI Library problem

#4 Post by russellt » 04 Apr 2014 14:17

Hi Marina

Thank you for your reply.

I haven't had a chance to look at your suggestions yet.

The problem with slave select was that if I set the receive module with slave select enabled it would work fine and I could turn it on and off by toggling the slave select pin.

As I understand it with slave select disabled it doesn't need a slave select connection and I should be able to disconnect that pin. With slave select disabled it doesn't receive anything.

Russell

dangerous
Posts: 748
Joined: 08 Mar 2005 16:06
Location: Nottinghamshire, UK

Re: SPI Library problem

#5 Post by dangerous » 07 Apr 2014 16:01

Hi, SPI SS is optional in slave mod. Bit SMP has to be cleared for slave mode in SSPSTAT reg

To get the received data you have to read the salve's SSPBUFF which then clears it for the next byte.

I did this for an I2C slave with a PIC16F88 some years ago and that worked fine. it was written for MB6 but the idea is still the same.

Code: Select all

program i2c_spi_port
'modded NOV 15th 06 to account for board layout change on port input.
'i2c Slave routines  These are modified routines from idea posted by daburdz.
'Uses SSP module in this example on 16F88 @ 4MHz with I2C on portb.
'Only sends 1 byte back after read request, used for portb status report.
'Input only used to switch bits 0 & 1 of porta in this case.
'For devices with MSSP, must set SSPCON2, and TRISC if portC is used.
'I2C on portC on some chips. See data sheets
'Portb is converted to send back data according to Case statements.
'Master test software is I2C_Master_Port.
'********************************************************************************
Const MyADD = 0xD6                     'set I2C device address
Const Delay_Time = 1000                'port check delay
Dim j As Byte                          'just dummy for buffer read
Dim rxbuffer As Byte[2]                'Allows 2 bytes max for this device
Dim rxbufferindex As Byte              'can expand data bytes as needed
'********************************************************************************
Dim inport,savp1,savp2,outdata,tx_data as  Byte 'processing bits for this app.
'********************************************************************************
sub procedure Initialise
ANSEL = 00                           'All ports set to digital. Chip specific.
'Check in datasheet. Analog switch is in ADCON1 reg in 16F87x
TRISB = 0xff                         'Set PORTB as input(shares I2C Bus pins on 16F88)
TRISA = %000000                      'porta as output
SSPADD =  MyADD                      'Get our address (7bit). Lsb is read/write flag
SSPCON = 0x36                        ' Set to I2C slave with 7-bit address
rxbufferindex = 0                    'set buffer index pointer to 0
PIE1.SSPIF = 1                       'enable SSP interrupts
INTCON = %11000000                   'enable INTCON.GIE
TRISB = (TRISB)OR %111011101         'Explicitly set all non I2C pins as input
end sub
'********************************************************************************
sub procedure Interrupt                 'I2C slave interrupt handler
If PIR1.SSPIF = 1 Then                       'I2C Interrupt
   PIR1.SSPIF = 0                             'reset SSP interrupt flag
'if Lsb = 1 then we have to send data in tx_data back to master
'------------------------transmit data to master-------------------
  If SSPSTAT.R_W = 1 Then                    ' Read request from master
    SSPBUF = tx_data                         ' Get data to send
    SSPCON.CKP = 1                           ' Release SCL line
    Goto i2cexit                             ' That's it
  End if
'------------------------
  If SSPSTAT.BF = 0 Then                    'all done,
    Goto i2cexit                            'Nothing in buffer so exit
  End if
'if Lsb = 0 then we have to accept data from master and save in Rxbuffer
'-------------------------recieve data from master----------------
  If SSPSTAT.D_A = 1 Then                    ' Data [not address]
    rxbuffer[rxbufferindex] = SSPBUF         ' Put data into array
    rxbufferindex = inc(rxbufferindex)       ' +
    Goto i2cexit
  End if
'--------------------------
  If SSPSTAT.D_A = 0 Then                     ' Our address
    rxbufferindex = 0
  End if
End if
i2cexit:
j = SSPBUF                                'read  buffer to clear flag  [address]
end sub
'*************************************************************************************
main:
Initialise    'calls subroutine to set up ports etc
'main routine runs when no interrupt occurs from I2C bus
while true
      inport = portb   'get port & save
      savp1 = (inport)OR(%00010010) 'mask SCL & SDA to high
      delay_mS(Delay_Time)                'wait
      inport = portb
      savp2 = (inport)OR(%00010010) 'do it again
      if savp2 = savp1 then
         Select Case savp2
                Case 0xfe
                          tx_data = 1
                Case 0xfb
                          tx_data = 2
                Case 0xf7
                          tx_data = 3
                Case 0xdF
                          tx_data = 4
                case 0xbF
                          tx_data = 5
                Case 0x7f
                          tx_data = 6
          end select
      else tx_data = 0x00
      end if
'read input data and use
     if rxbufferindex <>0 then  'if data received rxbufferindex is 1 or more
          outdata = rxbuffer[0]       'get  1 byte of data only
          porta = outdata AND 0x03    'use porta 0 & 1 as outputs
          rxbufferindex = 0           'clear buffer for next receive
     end if
    wend
End.
Study the SPI / I2C section (MSSP) of the datasheet for more information.

Basically you have to monitor SSPIF for all data latched in then read SSPBUF to clear the buffer for the next data byte to be shifted in. The interrupt enable does not have to be set but you can run it interrupted mode if you need to

Try searching for SPI Slave mode on our forum.

You could use I2C and save some pins, or even serial ports as 5V RS232 type signals. Uart tx & rx is fairly simple to follow.

russellt
Posts: 7
Joined: 24 Feb 2014 19:09

Re: SPI Library problem

#6 Post by russellt » 04 Jul 2014 17:16

Hi

I've had another chance to have a look at this after a long delay.

Thanks for posting your code Dangerous it may be useful.

I've tested a lot of different library initialisation commands and I have found some unexpected results. I am using a 16F873a

If I use the command.

SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_HIGH_2_LOW)

It sets the clock polarity bit (SSPCON1.CKP) to 1 (idle high) as expected and the SSPSTAT.CKE bit to 1 (active to idle). In this example active is low, idle is high, so active to idle is low to high - which is not what I asked it to do.

If SSPSTAT.CKE (which controls which end of the pulse transmit occurs) is set then there doesn't appear to be any way to set it to zero. The SPI1_Init_Advanced command should do it but it doesn't.

To confuse matters even further if I use two commands

SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_HIGH_2_LOW)
SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_LOW_2_HIGH)

The second command does not alter the result of the first. It appears that if SSPSTAT.CKE (which controls which end of the pulse transmit occurs) is set then there doesn't appear to be any way to set it to zero using the library functions. The SPI1_Init_Advanced command should do it but it doesn't.

Have I missed something - or is this a bug - or even two bugs.

Russell

russellt
Posts: 7
Joined: 24 Feb 2014 19:09

Re: SPI Library problem

#7 Post by russellt » 07 Jul 2014 18:11

I've got further with this and I now have a 16F873a sending data to another one.

The library behaves in some unexpected ways.

As well as the two problems I've already identified above I've discovered that if you use the command

SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, ..etc) it sets SCK as an output. Slave mode needs it to be an input.

Here's my code:

Code: Select all

program MyProject

' Declarations section
' Lcd module connections
dim
  LCD_RS as sbit at RB4_bit
  LCD_EN as sbit at RB5_bit
  LCD_D7 as sbit at RB3_bit
  LCD_D6 as sbit at RB2_bit
  LCD_D5 as sbit at RB1_bit
  LCD_D4 as sbit at RB0_bit
  CSN as sbit at RA5_bit
  CE as sbit at RA4_bit
dim
  LCD_RS_Direction as sbit at TRISB4_bit
  LCD_EN_Direction as sbit at TRISB5_bit
  LCD_D7_Direction as sbit at TRISB3_bit
  LCD_D6_Direction as sbit at TRISB2_bit
  LCD_D5_Direction as sbit at TRISB1_bit
  LCD_D4_Direction as sbit at TRISB0_bit
' End Lcd module connections

dim
i,n as integer
txt as string[3]
buffer, take, dummy, mode, reg as byte

main:
mode = 1                                            'mode = 1 is transmit (master)
'mode = 0                                            'mode = 0 is receive (slave)
adcon1 =7                                           'sets all analog pins to digital
cmcon = 7                                           'sets comparators to off

buffer = 123
'*****************************************************************************************

i = 1                                               'initialise count variables
n = 1
'*****************************************************************************************
'SEND ROUTINE
  while mode = 1                                    'master transmit
  
  trisa.0 = 0                                       'sets a.0 as output to flash an LED
  SSPSTAT.CKE = 0                                   'clear the CKE bit as the SPI library wont
  SPI1_Init_Advanced(_SPI_MASTER_OSC_DIV4, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_LOW_2_HIGH)
  
    DO

     SSPBUF = buffer                                'Send a byte to the SPI
     
     IF SSPSTAT.BF  THEN                            'Read the received data to prevent overfow errors
     dummy = SSPBUF
     END IF
     
     porta.0 = i                                    'turn the led on or off
     delay_ms(200)                                  'pause
     buffer = buffer + 1                            'change the number to be sent

        if i = 1 then                               'change the number for setting the LED
        i = 0
        else
        i = 1
        end if

        if buffer = 255 then                        'stop overflow by restting to zero
        buffer = 0
        end if
     LOOP until 0 = 1
   WEND
   
'*****************************************************************************************
  WHILE mode = 0        'slave (receive)
  
  LCD_Init                                            'Initialise the LCD
  Lcd_Cmd(_LCD_CURSOR_OFF)                              'Turn the cursor off
  Lcd_Cmd(_LCD_CLEAR)                                   'clear the lcd

  SSPSTAT.CKE = 0                                       'The library seems incapable of seting CKE to zero so do it here.
  SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_LOW_2_HIGH)
  TRISC.3 = 1                                           'Library SS disable function turns the clock input off so turn it back on

    DO

    IF SSPSTAT.BF then                          'Check for received data - buffer full
    take = SSPBUF
    'SPI1_Read(dummy)                           
    ByteToStr(take, txt)
    LCD_OUT(1,1,txt)
    END IF  

    LOOP until 0 = 1
    
  WEND
'*****************************************************************************************

end.
Russell

User avatar
marina.petrovic
Posts: 2986
Joined: 18 Apr 2013 08:11

Re: SPI Library problem

#8 Post by marina.petrovic » 10 Jul 2014 12:05

Hi,

Thank you very much for pointing on this behavior in compiler, I will pass it to our software developers
and they will certainly investigate this behavior little bit further and try to find were the problem occurs.

I am very sorry for the inconvenience.

Best regards,
Marina

dangerous
Posts: 748
Joined: 08 Mar 2005 16:06
Location: Nottinghamshire, UK

Re: SPI Library problem

#9 Post by dangerous » 29 Jul 2014 16:33

Hi Am I confused here? The slave unit has to respond to the master's command. In slave mode there is no clock generation and it cannot initiate transmission. The SPI_init routines do not apply to slave functions. The clock has to be an input as does MOSI and SS whilst MISO is an output only when it transmits. It is Hi-Z otherwise. This is to allow other chips to be paralleled up with each other.

When the master transmits, the slave has to wait: a) to be selected, b) for the correct clock state, c) to accept incoming data and to transfer whatever data it has to send back and finally d) to be unselected. Optionally it may have a ready status pin that can suspend the master sending data until the slave is in a position to accept it.

It is all done on a bit by bit basis. First the chip is selected, as a kind of framing pulse and stays selected until the current session is finished. That could be one data byte or many if an eeprom is being read.

If you have the master set to clock High to Low, data is sent on the falling edge to the slave. As new data is clocked in to the slave, existing data is clocked out after half a clock delay. So the master has to sample the received data after it has transmitted data to the slave or the data will be corrupt. The new data to be returned is then written to the output buffer in the slave ready for the next received byte. Finally the master deselects the slave when it has finished its whole data transfer.

It is explained here: https://www.diolan.com/dln_doc/spi-transfer-modes.html and again http://www.eetindia.co.in/STATIC/PDF/20 ... S=DOWNLOAD

You will have to write the slave routines yourself as the PIC library is master only. The slave just becomes a dumb device. It can only respond when called.

The first byte could be an address or command, as long as the slave knows what you are telling it, similar to I2C. Almost invariably the first byte has to be followed by dummy reads to get valid data back as the buffer is read out as the new data is received, and so cannot be a response to what you just sent. If you send a command "tell me your status" say 0x88, the data returned on that command will be whatever is in the buffer, if you then send a dummy say 0x00, the response should be correct as the data should have been updated by the slave, by monitoring the tx buffer status for empty.

russellt
Posts: 7
Joined: 24 Feb 2014 19:09

Re: SPI Library problem

#10 Post by russellt » 29 Jul 2014 21:42

The library is not master only. It has a command to set a PIC to be a slave. If you set the PIC to be a slave without using slave select - as for example a single master and single slave then the slave setting sets the clock (SCK) as an output. As you say the slave has no clock so SCK has to be an input. If you are using a dumb device (eg an nrf24l01) then the library only deals with the master.

As far as clock timing goes the PIC datasheet looks at idle state and idle to active. My testing (16F873a) didn't give me the expected result for that using the library commands which refer to idle state and high to low or low to high

I'd be interested to hear any comments from the developers.

Russell

dangerous
Posts: 748
Joined: 08 Mar 2005 16:06
Location: Nottinghamshire, UK

Re: SPI Library problem

#11 Post by dangerous » 30 Jul 2014 11:26

Apologies Russell

The help files are a bit vague on the Slave action.

Having not used slave mode, I thought it was enabling the SS pin on the master.-Duhhhhh!

I have just run this simple setup:

Code: Select all

 ADCON1 = 0x07
  SPI1_Init_Advanced(_SPI_SLAVE_SS_ENABLE, _SPI_DATA_SAMPLE_END, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH)
  While true
  
  Wend 
Running in the debugger sets SSPSTAT,7 to 1.

This seems to happen in the translation of the first line of the setup:

Code: Select all

;SPI_SLAVE.mbas,7 ::                 SPI1_Init_Advanced(_SPI_SLAVE_SS_ENABLE, _SPI_DATA_SAMPLE_END, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH)
0x0036        0x3004              MOVLW      4
0x0037        0x00A0              MOVWF      FARG_SPI1_init_advanced_master
0x0038        0x3080              MOVLW      128
0x0039        0x00A1              MOVWF      FARG_SPI1_init_advanced_data_sample
0x003A        0x01A2              CLRF       FARG_SPI1_init_advanced_clock_idle
0x003B        0x3001              MOVLW      1
0x003C        0x00A3              MOVWF      FARG_SPI1_init_advanced_transmit_edge
As the program runs this happens

Code: Select all

0x0019        0x0821              MOVF       FARG_SPI1_init_advanced_data_sample, 0
0x001A        0x1683              BSF        STATUS, 5
0x001B        0x0494              IORWF      SSPSTAT, 1
;__Lib_SPI_c345.mpas,59 ::                 
Advanced data sample is set to 128 in line 0038 of the listing and when it is imported to this routine upsets SSPSTAT.
This appears to be embedded in the library as it automatically enters the value as soon as the program runs.

This has to be 0 for slave mode according to microchip. Does this mean that _data_sample_end is not valid in slave mode as this bit appears to control that? Does it matter as data and clock are sent by the master?

Running the simulator (debugger) gives SSPCON = 0x24 and SSPSTAT 0XC0 where it should be 0x40. Trisa,5 should be 1 for SS enabled but is 0.

Changing to

Code: Select all

 ADCON1 = 0x07
  SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_END, _SPI_CLK_IDLE_LOW, _SPI_LOW_2_HIGH)
  While true
  
  Wend
changes TRISA,5 to 1 (SS as input) but still leaves SSPSATA,7 as 1 since FARG_SPI1_init_advanced_data_sample is still set to 128

This is weird.

Running your code:

Code: Select all

SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_HIGH_2_LOW)
SPI1_Init_Advanced(_SPI_SLAVE_SS_DIS, _SPI_DATA_SAMPLE_MIDDLE, _SPI_CLK_IDLE_HIGH, _SPI_LOW_2_HIGH)
does not change CKE as you say. It leaves SSPSTAT at 0x40 and SSPCON at 0x35. These are set by the first line. The second line has no effect, but does if you comment out the first line. It then leaves SSPSTAT at 0x00, SSPCON does not change, which is correct

Somewhere there is an issue in the setup translation I believe.

Does this make sense to anyone at MikroE?

russellt
Posts: 7
Joined: 24 Feb 2014 19:09

Re: SPI Library problem

#12 Post by russellt » 01 Aug 2014 21:48

Hi Dangerous

Thanks for your comments.

It's reassuring that I'm not the only one who finds this library behaves strangely.

Russell

dangerous
Posts: 748
Joined: 08 Mar 2005 16:06
Location: Nottinghamshire, UK

Re: SPI Library problem

#13 Post by dangerous » 04 Aug 2014 12:55

Hi,

I just ran the simulator for the simple sequence on a 18F45K22 and the same thing happens. Bit SMP of SSP1STAT remains set, although now TRISA,5 remains clear at all times with _SPI_SLAVE_SS_DIS or _SPI_SLAVE_SS_ENABLE.

Strange!

Post Reply

Return to “mikroBasic PRO for PIC General”