Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
Resource icon

Basic 8051 Tutorial 6 2015-04-17

We start with the SPI bus... The main idea behind serial buses is to save on micro-controller pins... SPI requires three pins or more if there are multiple devices.. The serial transfer on an SPI bus is circular.. The inputs and outputs of each buffer are connected and the shift is synchronized.
upload_2015-4-17_18-5-20.png


As data is shifted out of the SDO pin of a micro, the data is shifted into the SDI pin of the device...The data in the device is simultaneously shifted back into the micro. When large data transfers are made after the first (dummy) data is read, the next byte will be called data, this means you can swap chunks of data back and forth relativity quickly... Some SPI buses can run at 10Mbit per second...

Multiple devices are addressed using a Chip Select (CS) pin.... There are certain devices that are software addressable making the need of the CS pin obsolete, but not many! For the examples I use hardware addressing which usually means supplying a CS pin per device.

The protocol for talking to SPI devices is fairly simple.. 8 clocks to shift the buffers round whilst holding the CS pin low...

There are 4 modes of operation and most devices will work with at least two, the modes are determined by the clock signal...

Normally Low with Rising Clock Mode 0,0 / Mode 0
Normally Low with Falling Clock Mode 0,1 / Mode 1
Normally High with Rising Clock Mode 1,0 / Mode 2
Normally High with Falling Clock Mode 1,1 / Mode 3

Each device has a datasheet ( somewhere on the net ) that will describe the operation... Micro's with hardware SPI modules have a register where this can be set. However! We are going to implement a software bit banged one so we'll have to take it into consideration.

The devices I'm going to use are the ADC and the DAC. For the DAC I use the MCP4921 and the MCP3001 for the ADC..

The MCP4921 is a 12 bit DAC.... So our output has 4096 resolutions, that means for a 0v ~ 5v span we will see changes a little as 1.2mV... To output a voltage we have to load the DAC via the SPI pins... There is 12bits for the DAC itself and 4 bits for control.

Here is the register inside the DAC as displayed in the relevant datasheet..

upload_2015-4-17_18-7-26.png


From this we can see we need 2 x 8 bit data transfers..

Bit 15 is only used on the dual MCP4922.
Bit 14 is for the internal buffer, this is used when updating the DAC causes loading issues.
Bit 13 is the gain control, it set it uses the ref pin as its span if unset it uses the ref pin and doubles( if it can ) the output.... If the DAC runs on 5v this will be the cap.. It cannot be used as a voltage doubler.. Most people use a precision 1.2v or 2.5v reference... This means they can double the output to 2.4v and 5v respectively..
Bit 12 is the shutdown control.

When we write to the DAC we need to add 0x3000 to the DAC value so the chip will be enabled and we can use a 5v reference.

The MCP3001 has a slightly different protocol than the DAC... This being a 10bit ADC there are only 11 bits to shift out.. 1 NULL bit and then the 10 ADC result bits.. There are no control bits..


If we read 16 clocks the first two clocks receive nothing... The 3rd clock receives the NULL bit and the following 10 clocks receive the data.. The last few clocks can be discarded... As we will be using a software SPI implementation for both chips then the full 16 bits will be being used. This way if an upgrade to hardware SPI is required, little software changes will be needed..

Ok! We need a circuit.
upload_2015-4-17_18-8-15.png


I haven't included( as usual ) the connections to the bootloader.. This is an addon to the basic circuit shown in the first tutorial... Also as we are dabbling with ADC's and DAC's we need to ensure decoupling caps are on all chips... 0.1uf is the usual cap to use... These must be placed as close to the power pins as possible...

I'll show the ASM code first so we can see it in action... I'm going to read a pot connected to the MCP3001 and watch a voltmeter connected to the MCP4921...

Code 17
Code:
$MOD51

ResultL    equ   31H     ; Low byte
ResultH    equ   30H     ; High byte

   org    0
   ljmp   Start     ; jump over vectors

   org    30H
Start:   call   SPIInit     ; Ready for SPI
   
Main:
   call    GetVal     ; Read ADC
   call   SendVal     ; Convert to 12 bit
   sjmp    Main     ; And send to DAC

; The MCP3001 is a 10 bit ADC but the split over the two
; registers is weird
; x,x,x,null,D9,D8,D7,D6,D5,D4,D3,D2,D1,D0,D1,D2,D3

GetVal:
   clr   P1.4     ; CS enable
   mov   A,#0
   call   doSPI     ; Clock in ADC
   mov   ResultH,A   ; Get ADC high
   mov   A,#0
   call   doSPI     ; Clock in ADC
   mov   ResultL,A   ; Get ADC low
   setb   P1.4     ; CS disable
   mov   R0,#2     ; Shift out 2 bits

; Next bit removes all rubish and makes a 12 bit result..
   
shift:   clr   C     ; clear carry before shift
   mov   A,ResultH   ; shift High into Acc
   rrc   A     ; rotate with carry
   mov   ResultH,A   ; store back
   mov   A,ResultL   ; low byte into Acc
   rrc   A     ; rotate with carry
   mov   ResultL,A   ; store back
   djnz   R0,shift   ; All 4 bits
   mov   A,ResultH   ; Get High byte
   anl   A,#0fh     ; Trim to 12 bits
   mov   ResultH,A   ; Store back
   ret

; Send 12 bit result to the MCP4921..

SendVal:
   clr   P1.3     ; CS enable
   mov   A,ResultH   ; get High byte
   add   A,#30h     ; set control bits
   call   doSPI     ; send out
   mov   A,ResultL   ; get low byte
   call   doSPI     ; send out
   setb   P1.3     ; CS dissable
   ret

; Ready the pins for SPI transfers

SPIInit:
   mov   P1,#0ffH   ; Port 1 as input
   clr   P1.6     ; Clock low idle
   ret

; Do the SPI transfer

doSPI:   clr   C     ; Clear carry
   mov   R0,#8     ; 8 clocks
LpIn:   clr   P1.5
   jnb   Acc.7,BBit   ; bit 7? high / low
   setb   P1.5     ; high
BBit:   call   Clock     ; low and clock
   jnb   P1.7, None   ; Whats come in???
   setb   C     ; High
   jmp   ABit
None:   clr   C     ; or Low
ABit:   rlc   A     ; next bit
   djnz    R0,LpIn     ;
   ret

; Clock the SCL line
   
Clock:
   setb   P1.6     ; Clock
   nop       ; Wait
   clr   P1.6     ; Scl pin
   ret

   end
It works as expected...

Okay! The C version...

Code18
C:
/* Main.c file generated by New Project wizard
 *
 * Created:  Thu Apr 16 2015
 * Processor: AT89C51RB2
 * Compiler:  SDCC for 8051
 */

#include <mcs51reg.h>
#define   SDO P1_5
#define   SDI P1_7
#define   SCL P1_6
#define   CSA P1_4
#define   CSD P1_3

void SPIinit(void)
  {
  P1 = 0xBF;      // Clock idle low   
  }
   
unsigned char doSPI(unsigned char CH)
  {
  char Idx;
   
  for (Idx = 0; Idx<8;Idx++)
  {
  SDO = 0;
  if(CH & 0x80) SDO = 1;
  SCL = 1;
  SCL = 0;
  CH <<= 1;
  CH &= 0xFE;
  if(SDI == 1) CH++;
  }
  return CH;   
  }
   
int ReadADC(void)
  {
  int tmp = 0;
  CSA = 0;
  tmp = (int)doSPI(0);
  tmp <<= 8;
  tmp += (int)doSPI(0);
  CSA = 1;
  tmp>>= 2;
  return tmp & 0xFFF;
  }

void WriteDAC(int x)
  {
  x += 0x3000;
  CSD = 0;
  doSPI(x >> 8);
  doSPI(x & 0xFF);
  CSD = 1;
  }
   
void main(void)
 {
  // Write your code here
  while (1)
  {
  WriteDAC(ReadADC());   
  }   
 }
So you can see SPI isn't rocket science DATA is shifted out by the most significant bit whilst DAT is shifted in on the least significant bit.. This really speeds up the operation.

The other major contender for inter device communication is I2C..

Inter IC Communication. Shortened to.. I squared C ( I2C )..

I2C is pretty much the same as SPI but has only two wire hookup, hence why some call it two wire communication.. The master controls up to 128 devices.. Each device has an internal address... This allows us to leave out the Chip Select pins....

If an I2C bus has several similar devices they must have external code switches...

An 24LC256 eeprom has an internal address of 0x50... But! As the address has to include a R/W bit we need to shift the address into a byte so the address becomes 0xA0 for writing and 0xA1 for reading..

For our example, I'll use a 24LC256 eeprom coupled with DS1307 RTC... We will have to bring back the LCD routines so we can see stuff on the screen ( otherwise nothing will look like its happening ).

First thing as before.. A schematic.. We need to know how to connect it up.. I normally hook stuff up to port 1, but I have the LCD on there.... I have use Port 0 pins 6 and 7..

As we only have one 24lc256 the three address lines are grounded...
upload_2015-4-17_18-16-18.png


I have included a file or two... This is helpful when making large programs.. Take all your code and bundle it in a file.... Call it MyLibrary.C or something else that fits the bill... Name all your function definitions and constants and place it in a file called MyLibrary.H or similar... Now when you include any functions from the library, then only these will be included in the build..

But before the version I'll start with ASM as usual..

Code19
Code:
sec   equ   30h
min   equ   31h
hour   equ   32h
day   equ   33h
date   equ   34h
mon   equ   35h
year   equ   36h

   org   0
   sjmp   start
   org   30h

start:   acall   i2cinit
   acall   LcdInit
   acall   settime
   mov   R3,#1
   mov   R2,#0
   mov   R1,#0AAh
   acall   writeEEprom
   acall   delay
   mov   R1,#0
   acall   readEEprom

main:
   acall   gettime
   acall   printRTC
   sjmp   main

t_data:   db   0h,23h,13h,0h,17h,4h,15h

printRTC:
   acall   LcdGotoline1
   mov   R0,#hour
   mov   A,@R0
   dec   R0
   acall   printhex
   mov   A,#3Ah
   call    lcdData
   mov   A,@R0
   dec   R0
   acall   printhex
   mov   A,#3Ah
   call    lcdData
   mov   A,@R0
   acall   printhex
   acall   LcdGotoline2
   mov   R0,#date
   mov   A,@R0
   inc   R0
   acall   printhex
   mov   A,#2fh
   call    lcdData
   mov   A,@R0
   inc   R0
   acall   printhex
   mov   A,#2fh
   call    lcdData
   mov   A,@R0
   acall   printhex
   mov   A,#20h
   call    lcdData
   ret
   
printhex:
   push   Acc
   swap   A
   anl   A,#0fh
   add   A,#30h
   call    lcdData
   pop   Acc
   anl   A,#0fh
   add   A,#30h
   call    lcdData
   ret

gettime:
   mov   R0,#7
   mov   R2,#0
   mov   R1,#sec
idx2:   acall   readRTC
   mov   A,R3
   mov   @R1,A
   inc   R2
   inc   R1
   djnz   R0,idx2
   ret

settime:
   mov   R0,#7
   mov   R2,#0
   mov   dptr,#t_data
idx1:   mov   A,#0h
   movc   A,@a+dptr
   mov   R3,A
   acall   writeRTC
   inc   R2
   inc   dptr
   djnz   R0,idx1
   ret
   
$include   (I2C.inc)
   end

The include file (I2C,inc) has all the low level code... I will stick it in the appendix and include it on the forum.


Code20

C:
/* Main.c file generated by New Project wizard
 *
 * Created:  Fri Apr 17 2015
 * Processor: AT89C51RB2
 * Compiler:  SDCC for 8051
 */

#include <mcs51reg.h>
#include <stdio.h>
#include "MyLibrary.H"

unsigned char output[17];
unsigned char time[] = {0x0,0x3,0x17,0x0,0x17,0x4,0x15};

void WriteClock(char *array)  // Not used
  {
  char x;
  for(x=0;x<7;x++)
  {
  WriteRTC(x,array[x]);
  }  
  }  

void ReadClock(char *array)
  {
  char x;
  for(x=0;x<7;x++)
  array[x] = ReadRTC(x);  // No need to BCD-BIN
  }  

void main(void)
 {
  I2Cinit();
  lcd_init();
  WriteClock(&time[0]);
  while (1)
  {
  lcd_command(0x80);
  ReadClock(&time[0]);
  sprintf(output,"%02x:%02x:%02x",time[2],time[1],time[0]);
  lcd_print(output);
  lcd_command(0xC0);
  sprintf(output,"%02x/%02x/%02x",time[4],time[5],time[6]);
  lcd_print(output);
  }   
 }

And there in lies software SPI and I2C routines
I have included the two files... I2C.inc for the ASM code ( rename from I2C.h)
and the MyLibrary.H for the C code ( My compiler wont allow multiple C files so I have done it as a header!!! Same difference)..

I'm on with the Graphical display now.... I'll try and get it posted soon..
  • upload_2015-4-17_18-5-20.png
    upload_2015-4-17_18-5-20.png
    5.2 KB · Views: 3,227
  • upload_2015-4-17_18-7-26.png
    upload_2015-4-17_18-7-26.png
    36.3 KB · Views: 2,807
  • upload_2015-4-17_18-8-15.png
    upload_2015-4-17_18-8-15.png
    30.3 KB · Views: 2,920
  • upload_2015-4-17_18-16-18.png
    upload_2015-4-17_18-16-18.png
    33.1 KB · Views: 2,824
  • I2C.h
    4.6 KB · Views: 1,043

Latest reviews

Thank you!
This tutorial is a great reference. I'll surely get back on these articles once I get my board. Thanks for sharing. Hope to see more of your posts...thanks

Latest threads

New Articles From Microcontroller Tips

Back
Top