Continuing the STM8 Expedition

STM8S105 Discovery

Software SPI – MAX6675

Software SPI is basically bit-banging ordinary GPIOs to emulate SPI signals. Software SPI is rarely needed unless hardware SPI peripheral is unavailable or hardware SPI pins are used up for some other tasks. Though it is slow and require extra coding and therefore extra overhead compared to hardware SPI, it is sometimes very helpful in debugging SPI-based hardware as it offers full control over all SPI signals.

To demo software SPI here, I have used MAX6675 Cold-Junction-Compensated K-Thermocouple-to-Digital Converter. It is interfaced with a typical K-type thermocouple and sends out the temperature sensed by the thermocouple.

Hardware Connection

sw_spi

Code Example

MAX6675.h

#include "STM8S.h"


#define SW_SPI_Port     GPIOC                 

#define CS_pin          GPIO_PIN_1
#define SCK_pin         GPIO_PIN_2
#define SO_pin          GPIO_PIN_3

#define CS_OUT_HIGH()   GPIO_WriteHigh(SW_SPI_Port, CS_pin)
#define CS_OUT_LOW()    GPIO_WriteLow(SW_SPI_Port, CS_pin)
#define SCK_OUT_HIGH()  GPIO_WriteHigh(SW_SPI_Port, SCK_pin)
#define SCK_OUT_LOW()   GPIO_WriteLow(SW_SPI_Port, SCK_pin)

#define SO_IN()         GPIO_ReadInputPin(SW_SPI_Port, SO_pin)

#define T_min           0
#define T_max           1024

#define count_max       4096

#define no_of_pulses    16

#define deg_C           0
#define deg_F           1
#define tmp_K           2

#define open_contact    0x04
#define close_contact   0x00

#define scalar_deg_C    0.25
#define scalar_deg_F_1  1.8
#define scalar_deg_F_2  32.0
#define scalar_tmp_K    273.0

#define no_of_samples   16


void MAX6675_init(void);
unsigned char MAX6657_get_ADC(unsigned int *ADC_data);
float MAX6675_get_T(unsigned int ADC_value, unsigned char T_unit);

 

 

MAX6675.c

#include "MAX6675.h"


void MAX6675_init()
{
   GPIO_DeInit(SW_SPI_Port);

   GPIO_Init(SW_SPI_Port,
              ((GPIO_Pin_TypeDef)(SCK_pin | CS_pin)),
              GPIO_MODE_OUT_PP_HIGH_FAST);

   GPIO_Init(SW_SPI_Port, SO_pin, GPIO_MODE_IN_FL_NO_IT);

   CS_OUT_HIGH();
   SCK_OUT_HIGH();
}


unsigned char MAX6657_get_ADC(unsigned int *ADC_data)
{
   unsigned char samples = no_of_samples;
   unsigned char clk_pulses = 0;
   unsigned int temp_data = 0;
   unsigned long avg_value = 0;


   while(samples > 0)
   {
         clk_pulses = no_of_pulses;
         temp_data = 0;

         CS_OUT_LOW();

         while(clk_pulses > 0)
         {   
            temp_data <<= 1;

            if(SO_IN())
            {
                temp_data |= 1;
            }

            SCK_OUT_HIGH();
            SCK_OUT_LOW();

            clk_pulses--;
         };  

         CS_OUT_HIGH();
         temp_data &= 0x7FFF;

         avg_value += temp_data;

         samples--;
         delay_ms(10);
   };

   temp_data = (avg_value >> 4);

   if((temp_data & 0x04) == close_contact)
   {
      *ADC_data = (temp_data >> 3);
      return close_contact;
   }
   else
   {
      *ADC_data = (count_max + 1);
      return open_contact;
   }
}


float MAX6675_get_T(unsigned int ADC_value, unsigned char T_unit)
{
   float tmp = 0.0;

   tmp = (((float)ADC_value) * scalar_deg_C);

   switch(T_unit)
   {
      case deg_F:
      {
         tmp *= scalar_deg_F_1;
         tmp += scalar_deg_F_2;
         break;
      }
      case tmp_K:
      {
        tmp += scalar_tmp_K;
        break;
      }
      default:
      {
        break;
      }
   }

   return tmp;
}

 

 

main.c

#include "STM8S.h"
#include "MAX6675.h"
#include "lcd.h"


unsigned char bl_state;
unsigned char data_value;

const unsigned char symbol[0x08] =
{
   0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00
};


void clock_setup(void);
void lcd_symbol(void);
void print_C(unsigned char x_pos, unsigned char y_pos, signed int value);
void print_I(unsigned char x_pos, unsigned char y_pos, signed long value);
void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points);
void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points);


void main(void)
{            
       unsigned char state = close_contact;
       unsigned int ti = 0x0000;
       float tf = 0.0;

       clock_setup();

       MAX6675_init();

       LCD_init(); 
       LCD_clear_home();
       lcd_symbol();

       LCD_goto(0, 0);
       LCD_putstr("STM8 SW-SPI Test");

       while(TRUE)
       {
                state = MAX6657_get_ADC(&ti);

                switch(state)
                {
                       case open_contact:
                       {
                             LCD_goto(0, 1);
                             LCD_putstr("     Error!     ");
                             delay_ms(200);
                             LCD_goto(0, 1);
                             LCD_putstr("                ");
                             break;
                       }
                       case close_contact:
                       {
                             tf = MAX6675_get_T(ti, deg_C); 

                             LCD_goto(0, 1);
                             LCD_putstr("T/              ");

                             LCD_goto(2, 1);
                             LCD_send(0, DAT);
                             LCD_goto(3, 1);
                             LCD_putstr("C:");

                             print_F(9, 1, tf, 2);
                             delay_ms(100);

                             break;
                       }
                }
       };
}


void clock_setup(void)
{
       CLK_DeInit();

       CLK_HSECmd(DISABLE);
       CLK_LSICmd(DISABLE);
       CLK_HSICmd(ENABLE);
       while(CLK_GetFlagStatus(CLK_FLAG_HSIRDY) == FALSE);

       CLK_ClockSwitchCmd(ENABLE);
       CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);
                                                                            CLK_SYSCLKConfig(CLK_PRESCALER_CPUDIV1);

       CLK_ClockSwitchConfig(CLK_SWITCHMODE_AUTO,
                             CLK_SOURCE_HSI,
                             DISABLE,
                             CLK_CURRENTCLOCKSTATE_ENABLE);

       CLK_PeripheralClockConfig(CLK_PERIPHERAL_I2C, ENABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_SPI, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_ADC, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_AWU, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_UART1, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER1, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, DISABLE);
       CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER4, DISABLE);
}


void lcd_symbol(void
{
   unsigned char s = 0; 

   LCD_send(0x40, CMD);

   for(s = 0; s < 8; s++)
   {
        LCD_send(symbol[s], DAT);
   }

   LCD_send(0x80, CMD);
}


void print_C(unsigned char x_pos, unsigned char y_pos, signed int value)
{
     unsigned char ch[5] = {0x20, 0x20, 0x20, 0x20, '\0'};

     if(value < 0x00)
     {
        ch[0] = 0x2D;
        value = -value;
     }
     else
     {
        ch[0] = 0x20;
     }

     if((value > 99) && (value <= 999))
     {
         ch[1] = ((value / 100) + 0x30);
         ch[2] = (((value % 100) / 10) + 0x30);
         ch[3] = ((value % 10) + 0x30);
     }
     else if((value > 9) && (value <= 99))
     {
         ch[1] = (((value % 100) / 10) + 0x30);
         ch[2] = ((value % 10) + 0x30);
         ch[3] = 0x20;
     }
     else if((value >= 0) && (value <= 9))
     {
         ch[1] = ((value % 10) + 0x30);
         ch[2] = 0x20;
         ch[3] = 0x20;
     }

     LCD_goto(x_pos, y_pos);
     LCD_putstr(ch);
}


void print_I(unsigned char x_pos, unsigned char y_pos, signed long value)
{
    unsigned char ch[7] = {0x20, 0x20, 0x20, 0x20, 0x20, 0x20, '\0'};

    if(value < 0)
    {
        ch[0] = 0x2D;
        value = -value;
    }
    else
    {
        ch[0] = 0x20;
    }

    if(value > 9999)
    {
        ch[1] = ((value / 10000) + 0x30);
        ch[2] = (((value % 10000)/ 1000) + 0x30);
        ch[3] = (((value % 1000) / 100) + 0x30);
        ch[4] = (((value % 100) / 10) + 0x30);
        ch[5] = ((value % 10) + 0x30);
    }

    else if((value > 999) && (value <= 9999))
    {
        ch[1] = (((value % 10000)/ 1000) + 0x30);
        ch[2] = (((value % 1000) / 100) + 0x30);
        ch[3] = (((value % 100) / 10) + 0x30);
        ch[4] = ((value % 10) + 0x30);
        ch[5] = 0x20;
    }
    else if((value > 99) && (value <= 999))
    {
        ch[1] = (((value % 1000) / 100) + 0x30);
        ch[2] = (((value % 100) / 10) + 0x30);
        ch[3] = ((value % 10) + 0x30);
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else if((value > 9) && (value <= 99))
    {
        ch[1] = (((value % 100) / 10) + 0x30);
        ch[2] = ((value % 10) + 0x30);
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }
    else
    {
        ch[1] = ((value % 10) + 0x30);
        ch[2] = 0x20;
        ch[3] = 0x20;
        ch[4] = 0x20;
        ch[5] = 0x20;
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_D(unsigned char x_pos, unsigned char y_pos, signed int value, unsigned char points)
{
    char ch[5] = {0x2E, 0x20, 0x20, '\0'};

    ch[1] = ((value / 100) + 0x30);

    if(points > 1)
    {
        ch[2] = (((value / 10) % 10) + 0x30);

              if(points > 1)
              {
                     ch[3] = ((value % 10) + 0x30);
              }
    }

    LCD_goto(x_pos, y_pos);
    LCD_putstr(ch);
}


void print_F(unsigned char x_pos, unsigned char y_pos, float value, unsigned char points)
{
    signed long tmp = 0x0000;

    tmp = value;
    print_I(x_pos, y_pos, tmp);
    tmp = ((value - tmp) * 1000);

    if(tmp < 0)
    {
       tmp = -tmp;
    }

    if(value < 0)
    {
        value = -value;
              LCD_goto(x_pos, y_pos);
        LCD_putchar(0x2D);
    }
    else
    {
        LCD_goto(x_pos, y_pos);
        LCD_putchar(0x20);
    }

    if((value >= 10000) && (value < 100000))
    {
        print_D((x_pos + 6), y_pos, tmp, points);
    }
    else if((value >= 1000) && (value < 10000))
    {
        print_D((x_pos + 5), y_pos, tmp, points);
    }
    else if((value >= 100) && (value < 1000))
    {
        print_D((x_pos + 4), y_pos, tmp, points);
    }
    else if((value >= 10) && (value < 100))
    {
        print_D((x_pos + 3), y_pos, tmp, points);
    }
    else if(value < 10)
    {
        print_D((x_pos + 2), y_pos, tmp, points);
    }
}

 

Explanation

A decent implementation of software SPI first requires I/Os and their purposes to be defined at first:

#define SW_SPI_Port     GPIOC                 

#define CS_pin          GPIO_PIN_1
#define SCK_pin         GPIO_PIN_2
#define SO_pin          GPIO_PIN_3

#define CS_OUT_HIGH()   GPIO_WriteHigh(SW_SPI_Port, CS_pin)
#define CS_OUT_LOW()    GPIO_WriteLow(SW_SPI_Port, CS_pin)
#define SCK_OUT_HIGH()  GPIO_WriteHigh(SW_SPI_Port, SCK_pin)
#define SCK_OUT_LOW()   GPIO_WriteLow(SW_SPI_Port, SCK_pin)

#define SO_IN()         GPIO_ReadInputPin(SW_SPI_Port, SO_pin)

Trust me defining stuffs this way saves both time and helps in debugging.

SW_SPI_Timing

We must code signal patterns as per timing diagram in the device’s datasheet (shown above). The datasheet of MAX6675 states that sensed data should be read on falling edges of the serial clock with chip select pin held low. This is what we have to code:

CS_OUT_LOW();

while(clk_pulses > 0)
{   
    temp_data <<= 1;

     if(SO_IN())
     {
         temp_data |= 1;
      }

      SCK_OUT_HIGH();
      SCK_OUT_LOW();

      clk_pulses--;
};  

CS_OUT_HIGH();

Note that Chip Select (CS) is held low to begin communication. Serial Clock (SCK) is held high first and the set low to emulate a falling edge of the serial clock. In total sixteen such transitions are needed to extract all data from MAX6675 via serial input pin.

The rest of the code is data processing and conversion, and then finally data display. I’m sure you’ll understand.

Demo

 

SW-SPI (2) SW-SPI (1)

Continue Reading ...

Related Posts

12 comments

  • Hi,
    Thats great work
    Do you have steps for to work ST7735 TFT IN 8 BIT 8080 mode

  • I don’t even understand how I ended up right here, however
    I believed this publish used to be good. I don’t know who you are however definitely
    you’re going to a well-known blogger should you aren’t already.
    Cheers!

  • Sankalp Rai Gambhir

    Thanks Shawon, your blogs are indeed very helpful. Keep Growing.

  • why are you sending the received data back through TX pin?

  • Hi SHAWON SHAHRYIAR

    I am wondering how to get a Max31855 to talk to a STM8s via SPI.

    JP

    • What’s to wonder about it? It is a simple SPI communication and SPI for STM8 is no different from the SPI of other MCUs…. The following lines are taken from the device’s datasheet and the write up there states how to communicate with it:

      “Drive CS low to output the first bit on the SO pin. A complete serial-interface read of the cold-junction compensated thermocouple temperature requires 14 clock cycles. Thirty-two clock cycles are required to read both the thermocouple and reference junction temperatures (Table 2 and Table 3.) The first bit, D31, is the thermocouple temperature sign bit, and is presented to the SO pin within tDV of the falling edge of CS. Bits D[30:18] contain the converted temperature in the order of MSB to LSB, and are presented to the SO pin within tD0 of the falling edge of SCK. Bit D16 is normally low and goes high when the thermocouple input is open or shorted to GND or VCC. The reference junction temperature data begins with D15. CS can be taken high at any point while clocking out con-version data. If T+ and T- are unconnected, the thermocouple temperature sign bit (D31) is 0, and the remainder of the thermocouple temperature value (D[30:18]) is 1.”

Leave a Reply

Your email address will not be published. Required fields are marked *