Continue to Site

# 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.

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..

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.

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... 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]);
}
}

{
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);
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..
5.2 KB · Views: 2,538
36.3 KB · Views: 2,145
30.3 KB · Views: 2,276
33.1 KB · Views: 2,265
• I2C.h
4.6 KB · Views: 745
Author
Ian Rogers
Views
8,790
First release
Last update
Rating
2 ratings