Exploring STC 8051 Microcontrollers – Coding

SPI

Serial Peripheral Interface (SPI) communication is another short-distance synchronous communication method like I2C. Unlike I2C, SPI requires 3 or more connections but the bus speed is significantly higher than that of I2C’s. Except chip selection pin, all SPI devices in a SPI bus can share the same set of communication pins. Typical full-duplex SPI bus requires four basic I/O pins. Their naming suggests their individual purpose.

  • Master-In-Slave-Out (MIS0) connected to Slave-Data-Out (SDO).
  • Master-Out-Slave-In (MOSI) connected to Slave-Data-In (SDI).
  • Serial Clock (SCLK) connected to Slave Clock (SCK).
  • Slave Select (SS) connected to Chip Select (CS).

Since SPI bus is very fast, it is widely used for interfacing displays, flash memories, etc. that needs fast responsiveness.

SPI is can visualized as a shift register that shifts data in and out one-by-one with clock pulse transitions. Like I2C bus, there can be one master device in a SPI bus and virtually unlimited amount of slave devices only separated by slave selection pins. Master sends commands to slave(s) by generating clock signals and selecting one slave device at a time. Selected slave responds to commands sent by the master.

In general, if you wish to know more about SPI bus here are some cool links:

Code

SPI.h

 /*
SS          MOSI        MISO        SCLK    Hex         Option
P1.2        P1.3        P1.4        P1.5    0x00        option 1
P2.2        P2.3        P2.4        P2.5    0x04        option 2
P7.4        P7.5        P7.6        P7.7    0x08        option 3
P3.5        P3.4        P3.3        P3.2    0x0C        option 4
*/                                                                             
 
#define SPI_clear                                                        do{\
                                                                             SPCTL = 0x00; \
                                                                             SPSTAT = 0xC0; \
                                                                             SPDAT = 0x00;}while(0)
 
 
#define SPI_timeout                                                     300                                                                             
                                                                             
 
#define SPI_pin_option(value)                                           do{P_SW1 |= value;}while(0)
 
#define SPI_pins(value)
 
#define SPI_disable                                                     bit_clr(SPCTL, 6)
#define SPI_enable                                                      bit_set(SPCTL, 6)
 
//clk
#define SPI_clk_sysclk_div_4                                            0x00
#define SPI_clk_sysclk_div_8                                            0x01
#define SPI_clk_sysclk_div_16                                           0x02
#define SPI_clk_sysclk_div_32                                           0x03
 
//MS
#define SPI_slave                                                       0x00
#define SPI_master                                                      0x10
 
//data_orientation
#define SPI_MSB_first                                                   0x00
#define SPI_LSB_first                                                   0x20
 
//CPHA
#define SPI_CPHA_leading                                                0x00
#define SPI_CPHA_trailing                                               0x04
 
//CPOL
#define SPI_CLK_CPOL_idle_low                                           0x00
#define SPI_CLK_CPOL_idle_high                                          0x08
 
//ss
#define SPI_SS_not_ignored                                              0x00
#define SPI_SS_ignored                                                  0x80





#define SPI_setup(clk, MS, data_orientation, CPHA, CPOL, ss)            do{ \
                                                                            SPI_clear; \
                                                                            SPCTL |= (clk | MS \
                                                                                    | data_orientation \
                                                                                    | CPHA \
                                                                                    | CPOL \
                                                                                    | ss); \
                                                                          }while(0)
 
unsigned char SPI_transfer(unsigned char write_value)
{
  unsigned char read_value = 0x00;
  unsigned int timeout = SPI_timeout;
 
  SPDAT = write_value;
  while((!check_SPI_flag) && (timeout > 0)) 
  {
    timeout--;
    delay_ms(1);
  };
 
  clear_SPI_flag;
  clear_SPI_write_collision_flag;
 
  read_value = SPDAT;
  
  return read_value;

DS3234.h

 #define read_cmd                     0x00
#define write_cmd                    0x80
 
#define second_reg                   0x00
#define minute_reg                   0x01
#define hour_reg                     0x02
#define day_reg                      0x03
#define date_reg                     0x04
#define month_reg                    0x05
#define year_reg                     0x06
#define alarm1sec_reg                0x07
#define alarm1min_reg                0x08
#define alarm1hr_reg                 0x09
#define alarm1date_reg               0x0A
#define alarm2min_reg                0x0B
#define alarm2hr_reg                 0x0C
#define alarm2date_reg               0x0D
#define control_reg                  0x0E
#define status_reg                   0x0F
#define ageoffset_reg                0x10
#define tempMSB_reg                  0x11
#define tempLSB_reg                  0x12
#define tempdisable_reg              0x13
#define sramaddr_reg                 0x18
#define sramdata_reg                 0x19
 
#define am                             0
#define pm                             1
#define hr24                           0
#define hr12                           1
 
#define DS3234_SS_HIGH                 P12_high
#define DS3234_SS_LOW                  P12_low
 
unsigned char s = 10;
unsigned char min = 10;
unsigned char hr = 10;
unsigned char ampm = pm;
unsigned char dy = 1;
unsigned char dt = 1;
unsigned char mt = 1;
unsigned char yr = 1;
unsigned char hr_format = hr12;
 
unsigned char bcd_to_decimal(unsigned char d);
unsigned char decimal_to_bcd(unsigned char d);
void DS3234_init(void);
unsigned char DS3234_read(unsigned char addr);
void DS3234_write(unsigned char addr, unsigned char value);
float DS3234_get_temp(void);
void DS3234_write_SRAM(unsigned char addr, unsigned char value);
unsigned char DS3234_read_SRAM(unsigned char addr);
void DS3234_get_time(unsigned short hour_format);
void DS3234_get_date(void);
void DS3234_set_time(unsigned char hSet, unsigned char mSet, unsigned char sSet, unsigned char am_pm_state, unsigned char hour_format);
void DS3234_set_date(unsigned char daySet, unsigned char dateSet, unsigned char monthSet, unsigned char yearSet);

DS3234.c

 #include "DS3234.h"
 
unsigned char bcd_to_decimal(unsigned char d)
{                                                                                          
  return ((d & 0x0F) + (((d & 0xF0) >> 4) * 10));
}                                
                                                             
 
unsigned char decimal_to_bcd(unsigned char d)
{
  return (((d / 10) << 4) & 0xF0) | ((d % 10) & 0x0F);
}     
 
void DS3234_init(void)
{
  P12_push_pull_mode;
  DS3234_SS_HIGH;
 
  SPI_pin_option(0x00);
 
  SPI_setup(SPI_clk_sysclk_div_16, \
            SPI_master, \
            SPI_MSB_first, \
            SPI_CPHA_trailing, \
            SPI_CLK_CPOL_idle_low, \
            SPI_SS_ignored);
  
  SPI_enable;
  
  DS3234_write(control_reg, 0x20);
  DS3234_write(status_reg, 0x48);
  DS3234_set_time(hr, min, s, ampm, hr_format);
  DS3234_set_date(dy, dt, mt, yr);
}
 
unsigned char DS3234_read(unsigned char addr)
{
  unsigned char value = 0;
 
  DS3234_SS_LOW;
  SPI_transfer(addr | read_cmd);
  value = SPI_transfer(0x00);
  DS3234_SS_HIGH;
 
  return value;
}
 
void DS3234_write(unsigned char addr, unsigned char value)
{
  unsigned long temp = 0;
 
  DS3234_SS_LOW;
  temp = (addr | write_cmd);
  SPI_transfer(temp);
  SPI_transfer(value);
  DS3234_SS_HIGH;
}
 
float DS3234_get_temp(void)
{
  float t = 0;
  signed char highByte = 0;
  unsigned char lowByte = 0;
 
  highByte = DS3234_read(tempMSB_reg);
  lowByte = DS3234_read(tempLSB_reg);
 
  lowByte >>= 6;
  t = (lowByte & 0x03);
  t *= 0.25;
  t += highByte;
 
  return t;

 
void DS3234_write_SRAM(unsigned char addr, unsigned char value)
{
  DS3234_write(sramaddr_reg, addr);
  DS3234_write(sramdata_reg, value);
}
 
unsigned char DS3234_read_SRAM(unsigned char addr)
{
  unsigned char value = 0;
 
  DS3234_SS_LOW;
  SPI_transfer(sramaddr_reg);
  SPI_transfer(addr);
  value = DS3234_read(sramdata_reg);
  DS3234_SS_HIGH;
 
  return value;
}
 
void DS3234_get_time(unsigned short hour_format)
{
  unsigned char tmp = 0;
 
  s = DS3234_read(second_reg);
  s = bcd_to_decimal(s);
  min = DS3234_read(minute_reg);
  min = bcd_to_decimal(min);
  tmp = DS3234_read(hour_reg);
  
  switch(hour_format)
  {
    case hr12:
    {
       ampm = (tmp & 0x20);
       ampm >>= 5;
       hr = (tmp & 0x1F);
       hr = bcd_to_decimal(hr);
       break;
    }
    
    default:
    {
       hr = (0x3F & tmp);
       hr = bcd_to_decimal(hr);
       break;
    }
  }
}
 
void DS3234_get_date(void)
{
  yr = DS3234_read(year_reg);
  yr = bcd_to_decimal(yr);
  mt = (0x1F & DS3234_read(month_reg));
  mt = bcd_to_decimal(mt);
  dt = (0x3F & DS3234_read(date_reg));
  dt = bcd_to_decimal(dt);
  dy = (0x07 & DS3234_read(day_reg));
  dy = bcd_to_decimal(dy);
}
 
void DS3234_set_time(unsigned char hSet, unsigned char mSet, unsigned char sSet, unsigned char am_pm_state, unsigned char hour_format)
{
  unsigned char tmp = 0;
 
  DS3234_write(second_reg, (decimal_to_bcd(sSet)));
  DS3234_write(minute_reg, (decimal_to_bcd(mSet)));
  
  switch(hour_format)
  {
    case hr12:
    {
       switch(am_pm_state)
       {
          case pm:
          {
               tmp = 0x20;
               break;
          }
          
          default:
          {
               tmp = 0x00;
               break;
          }
       }
       
       DS3234_write(hour_reg, ((tmp | 0x40 | (0x1F & (decimal_to_bcd(hSet))))));
       break;
    }
    
    default:
    {
       DS3234_write(hour_reg, (0xBF & (0x3F & (decimal_to_bcd(hSet)))));
       break;
    }
  }
}
 
void DS3234_set_date(unsigned char daySet, unsigned char dateSet, unsigned char monthSet, unsigned char yearSet)
{
  DS3234_write(day_reg, (decimal_to_bcd(daySet)));
  DS3234_write(date_reg, (decimal_to_bcd(dateSet)));
  DS3234_write(month_reg, (decimal_to_bcd(monthSet)));
  DS3234_write(year_reg, (decimal_to_bcd(yearSet)));
}

main.c

 #include "STC8xxx.h"
#include "BSP.h"
#include "LCD.c"
#include "lcd_print.c"
#include "DS3234.c"
 
#define SET           1
#define INC           2
#define SAVE          3
 
#define keypad_dly    10
#define disp_dly      90
 
void setup(void);
unsigned char get_keypad(void);
void display_value(unsigned char y_pos, unsigned char x_pos, unsigned char value);
void show_temperature();
void display_time(void);
void get_date_time_data(void);
unsigned char change_value(unsigned char y_pos, unsigned char x_pos, unsigned char value, unsigned char max_value, unsigned char min_value);
void setting(void);
void show_day(unsigned char value);
 
void main(void)
{
 
  setup();
  
  
  while(1)
  {
    P55_toggle;
    setting();
    get_date_time_data();
    show_temperature();
    display_time();
    delay_ms(200);
  };
}
 
void setup(void)
{
  CLK_set_sys_clk(IRC_24M, 2, MCLK_SYSCLK_no_output, MCLK_out_P54);
 
  P40_input_mode;
  P41_input_mode;
  P42_input_mode;
  
  P55_open_drain_mode;
  
  DS3234_init();
 
  LCD_init();
  LCD_clear_home();
  load_custom_symbol();
}
 
unsigned char get_keypad()
{
  if(!P40_get_input)
  {
    delay_ms(keypad_dly);
    return SET;
  }
  
  else if(!P41_get_input)
  {
    delay_ms(keypad_dly);
    return INC;
  }
  
  else if(!P42_get_input)
  {
    delay_ms(keypad_dly);
    return SAVE;
  }
  
  else
  {
    return 0;
  }
}
 
void display_value(unsigned char y_pos, unsigned char x_pos, unsigned char value)
{
  unsigned char ch = 0x00;
 
  ch = (value / 10);
  LCD_goto((x_pos - 1), (y_pos - 1));
  LCD_putchar((ch + 0x30));
  ch = (value % 10);
  LCD_goto(x_pos, (y_pos - 1));
  LCD_putchar((ch + 0x30));
}
 
void display_time()
{
  display_value(1, 1, hr);
  LCD_goto(2, 0);
  LCD_putstr(":");
  display_value(1, 4, min);
  LCD_goto(5, 0);
  LCD_putstr(":");
  
  display_value(1, 7, s);
 
  switch(hr_format)
  {
    case hr12:
    {
      switch(ampm)
      {
        case pm:
        {
          LCD_goto(9, 0);
          LCD_putstr("PM");
          break;
        }
        default:
        {
          LCD_goto(9, 0);
          LCD_putstr("AM");
          break;
        }
      }
      break;
    }
    default:
    {
      LCD_goto(9, 0);
      LCD_putstr("  ");
      break;
    }
  }
 
  display_value(2, 1, dt);
  LCD_goto(2, 1);
  LCD_putstr("/");
  display_value(2, 4, mt);
  LCD_goto(5, 1);
  LCD_putstr("/");
  display_value(2, 7, yr);
  show_day(dy);
}
 
void show_temperature()
{
  float t = 0;
  unsigned char temp = 0;
 
  t = DS3234_get_temp();
  
  temp = t;
  display_value(2, 10, temp);
  LCD_goto(11, 1);
  LCD_putstr(".");
  temp = ((t - temp) * 100);
  display_value(2, 13, temp);
  print_symbol(14, 1, 0);
  LCD_goto(15, 1);
  LCD_putstr("C");
}
 
void get_date_time_data()
{
  DS3234_get_date();
  DS3234_get_time(hr_format);
}
 
unsigned char change_value(unsigned char y_pos, unsigned char x_pos, unsigned char value, unsigned char max_value, unsigned char min_value)
{
  while(TRUE)
  {
    if(get_keypad() == INC)
    {
      value++;
    }
    
    if(value > max_value)
    {
      value = min_value;
    }
 
    LCD_goto((x_pos - 1), (y_pos - 1));
    LCD_putstr("  ");    
    delay_ms(disp_dly);
    
    display_value(y_pos, x_pos, value);
    delay_ms(disp_dly);
    
    if(get_keypad() == SAVE)
    {
      while(get_keypad() == SAVE);
      return value;
    }
  }
}
 
void setting()
{
     bit set_cmd;
     unsigned char tmp = 0;
 
     set_cmd = 0;
     if(get_keypad() == SET)
     {
         while(get_keypad() == SET);
         set_cmd = 1;
     }
     
     while(set_cmd)
     {
       while(1)
       {
         if(get_keypad() == INC)
         {
           tmp++;
         }
         if(tmp > 2)
         {
           tmp = 0;
         }
           
           
         LCD_goto(9, 0);
         LCD_putstr(" ");
           
         delay_ms(disp_dly);
         switch(tmp)
         {
            case 1:
            {
              LCD_goto(9, 0);
              LCD_putstr("AM");                    
              ampm = am;
              hr_format = hr12;
              break;
            }
         
            case 2:
            {
              LCD_goto(9, 0);
              LCD_putstr("PM"); 
              ampm = pm;
              hr_format = hr12;
              break;
            }
 
            default:
            {
              LCD_goto(9, 0);
              LCD_putstr("24"); 
              hr_format = hr24;
              break;
            }
          }
 
          delay_ms(disp_dly);
           
          if(get_keypad() == SAVE)
          {
            break;
          }
         }
         s = change_value(1, 7, s, 59, 0);
         min = change_value(1, 4, min, 59, 0);
         
         switch(hr_format)
         {
           case hr12:
           {
             hr = change_value(1, 1, hr, 12, 1);
             break;
           }
           default:
           {
             hr = change_value(1, 1, hr, 23, 0);
             break;
           }
         }
 
         dt = change_value(2, 1, dt, 31, 1);
         mt = change_value(2, 4, mt, 12, 1);
         yr = change_value(2, 7, yr, 99, 0);
 
         tmp = dy;
         while(1)
         {
           if(get_keypad() == INC)
           {
             tmp++;
           }
           if(tmp > 7)
           {
             tmp = 1;
           }
 
          
           LCD_goto(13, 0);
           LCD_putstr("    "); 
           delay_ms(disp_dly);
           show_day(tmp);
           delay_ms(disp_dly);
 
           if(get_keypad() == SAVE)
           {
             dy = tmp;
             break;
           }
         }
         
         DS3234_set_time(hr, min, s, ampm, hr_format);
         DS3234_set_date(dy, dt, mt, yr);
         set_cmd = 0;
     }
}
 
void show_day(unsigned char value)
{
  LCD_goto(13, 0);
  
  switch(value)
  {
    case 1:
    {
      LCD_putstr("SUN"); 
      break;
    }
    case 2:
    {
      LCD_putstr("MON");
      break;
    }
    case 3:
    {
      LCD_putstr("TUE");
      break;
    }
    case 4:
    {
      LCD_putstr("WED");
      break;
    }
    case 5:
    {
      LCD_putstr("THR");
      break;
    }
    case 6:
    {
      LCD_putstr("FRI");
      break;
    }
    case 7:
    {
      LCD_putstr("SAT");
      break;
    }
  }
}

Schematic

Explanation

As with other hardware, the built-in SPI hardware also has many alternative pin options. The SPI header file describes these pin combinations.

 /*
SS          MOSI        MISO        SCLK    Hex         Option
P1.2        P1.3        P1.4        P1.5    0x00        option 1
P2.2        P2.3        P2.4        P2.5    0x04        option 2
P7.4        P7.5        P7.6        P7.7    0x08        option 3
P3.5        P3.4        P3.3        P3.2    0x0C        option 4
*/
 
 
#define SPI_pin_option(value)         do{P_SW1 |= value;}while(0)

To use SPI peripheral, we have to set it up first as shown below:

 SPI_setup(SPI_clk_sysclk_div_16, \
          SPI_master, \
          SPI_MSB_first, \
          SPI_CPHA_trailing, \
          SPI_CLK_CPOL_idle_low, \
          SPI_SS_ignored);
  
SPI_enable;

SPI_setup function sets up SPI bus clock, device role, data sequence, clock phase and polarity alongside built-in hardware slave select pin operation state. After having these set up, the SPI hardware is ready to be enabled.

Since SPI functions like a ring shift-register, only one function is needed for SPI bus data transactions. The following SPI_transfer function is used for this purpose. First data is written and then read. Reading and writing SPDAT register automatically generates SPI bus clock.

 unsigned char SPI_transfer(unsigned char write_value)
{
  unsigned char read_value = 0x00;
  unsigned int timeout = SPI_timeout;
 
  SPDAT = write_value;
  while((!check_SPI_flag) && (timeout > 0)) 
  {
    timeout--;
    delay_ms(1);
  };
 
  clear_SPI_flag;
  clear_SPI_write_collision_flag;
 
  read_value = SPDAT;
  
  return read_value;

SPI peripheral is demoed here using DS3234 Real-Time Clock (RTC). The first three functions are what we basically need to fully make the RTC communicate with our STC micro. The rest of the RTC library functions are extensions of the DS3234 read and write functions. The read and write functions are typically what we see with other SPI devices. I recommend readers to check the timing diagrams of DS3234 from its datasheet and check the code in order to properly realize the SPI working principle in conjunction with the code presented here. Note that since the hardware slave select pin is not used, slave selection is done in code by changing the state of a GPIO pin.

 void DS3234_init(void)
{
  P12_push_pull_mode;
  DS3234_SS_HIGH;
 
  SPI_pin_option(0x00);
 
  SPI_setup(SPI_clk_sysclk_div_16, \
            SPI_master, \
            SPI_MSB_first, \
            SPI_CPHA_trailing, \
            SPI_CLK_CPOL_idle_low, \
            SPI_SS_ignored);
  
  SPI_enable;
  
  DS3234_write(control_reg, 0x20);
  DS3234_write(status_reg, 0x48);
  DS3234_set_time(hr, min, s, ampm, hr_format);
  DS3234_set_date(dy, dt, mt, yr);
}
 
unsigned char DS3234_read(unsigned char addr)
{
  unsigned char value = 0;
 
  DS3234_SS_LOW;
  SPI_transfer(addr | read_cmd);
  value = SPI_transfer(0x00);
  DS3234_SS_HIGH;
 
  return value;
}
 
void DS3234_write(unsigned char addr, unsigned char value)
{
  unsigned long temp = 0;
 
  DS3234_SS_LOW;
  temp = (addr | write_cmd);
  SPI_transfer(temp);
  SPI_transfer(value);
  DS3234_SS_HIGH;
}

Demo

Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

Related Posts

2 comments

  • i always get excited when you release new tutorials ,you are really doing a great job i wish i could write code and develop libraries like you.

  • Well, this is very nice and thorough tutorial indeed, many thanks!
    Unfortunately I doubt there is good any reason to learn the STC platform beyond curiosity.
    The STC 8051, although pretty evolved from the original 8051 ISA, does not offer anything crucial to justify the relatively high price of these micros and development tools along with certain cumbersomeness of this ancient platform.
    They simply can not compete even with the legacy Cortex M0 in any way. I am even not aware about any affordable debugger/emulator for them.
    All in all, I would never recommend anybody to start learning/using any 8051 without some very good reason to do so.

Leave a Reply

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