Digital SM Part III: I2C PIC18F4550 Slave and Raspberry Pi master
In part I and part II I described the communication between two PIC18F4550s. Now that I've got that working, I'm gonna replace the master by a Raspberry Pi model B. The I2C of the Pi uses 3.3V but the PIC18F4550 uses 5V. So I need a level shifter for each of the two I2C lines (data and clock). I use 2 x 2N7000 MOSFETS and 4 x 10k resistors.
I2C level shifter
I2C level shifter
Raspberry Pi connected to PIC18F4550
Raspberry Pi connected to PIC18F4550

Now that the Raspberry Pi and the PIC18F4550 have been connected, we need to configure the Pi to support I2C. A good post about how to do this can be found at abelectronics Although there were some step missing (at least there was for me: replacing the kernel that is used). All steps are summerized here:
  • install the i2c tools: sudo apt-get install i2c-tools

  • Open the blacklist file with your favorite editor, mine is vim: sudo vim /etc/modprobe.d/raspi-blacklist.conf
  • Add # for spi:#blacklist spi-bcm2708
  • Add # for i2c:#blacklist i2c-bcm2708
  • Open the modules file with your favorite editor, mine is vim: sudo vim /etc/modules
  • add i2c-dev to the end of the modules file: i2c-dev
  • add user 'pi' to the 'i2c' group:sudo adduser pi i2c
  • update your Raspberry pi:sudo apt-get update
  • upgrade your Raspberry Pi:sudo apt-get upgrade
  • upgrade your Raspberry Pi distribution:sudo apt-get dist-upgrade

And the extra steps I needed to do:
  • back-up original kernal: sudo cp /boot/kernel.img /boot/kernel.img.org
  • use newly downloaded kernel:sudo cp /boot/vmlinuz-[version]-rpi /boot/kernel.img
  • reboot the RPi:sudo reboot

N.B.: the RPi I use is running Raspbian. Different distro's might need different steps.

I slightly changed the slave (PIC18F4550) program :
  • The address is now 0x42
  • discovery is enabled
  • address masking is enabled

SSPCON1 = 0; SSPSTAT = 0; // slew rate is disabled SSPSTATbits.SMP = 1; // enable SM SSPSTATbits.CKE = 1; // Release clock SSPCON1bits.CKP = 1; // Enable interrupt when a general call address (0000h) is received in the SSPSR // Masking of SPADD5...2 enabled // Masking of SPADD 1 enabled // Clock stretching is disabled SSPCON2 = 0b10111110;// mask

When I run i2cdetect on the Rpi (and choose Y for probing). I get the following response

pi@raspbian ~ $ i2cdetect -r 1
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-1 using read byte commands.
I will probe address range 0x03-0x77.
Continue? [Y/n] Y
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f
30: 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
pi@raspbian ~ $

Hmm. This isn't what I expected. The '0x42' isn't there. It's time to read the PIC18F4550 documentation. It turns out that the actual address is stored in bits 7...1 and that bit 0 is for read / write mode. So a value of 0x42 = 0b01000010 means that the address actually = 0b00100001 = 0x21.When all mask bits are '1', then the address becomes:0b001xxxxx where the 'x' stands for don't care. So the PIC responds to the address range 0b00100000 - 0b00111111 = 0x20 - 0x3F. This matches the output of i2cdetect. When I put 0x42 in SSPADD and use no masking, I expect to see only 0x21 as address.
pi@raspbian ~ $ i2cdetect -r 1

WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-1 using read byte commands.
I will probe address range 0x03-0x77.
Continue? [Y/n] Y
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- 21 -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

So let's write the software to 'talk' to the PIC18F4550. It's a simple program:
  • Open the i2c channel
  • Write a command to the slave (0xF6).
  • Read 7 bytes of data.
  • Calculate the checksum of the first 5 bytes [B0...B4]
  • Compare the calculated checksum with the last 2 bytes [B5*256+B6]
  • Print the received data.
  • Close the i2c channel
  • Exit the program

#include <errno.h> #include <printf.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <linux/i2c-dev.h> #include <sys/ioctl.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> static int printf_arginfo_B(const struct printf_info *info, size_t n, int *argtypes) { /* "%B" always takes one argument, an int */ if (n > 0) { argtypes[0] = PA_CHAR; } return 1; } /* printf_arginfo_B */ static int printf_output_B(FILE *stream, const struct printf_info *info, const void *const *args) { unsigned char* data; int len; data = *(unsigned char **)(args[0]); uint a = (uint) data; len = fprintf(stream, "%03d = 0b%d%d%d%d%d%d%d%d", \ a,\ (a & 0x80 ? 1: 0), \ (a & 0x40 ? 1: 0), \ (a & 0x20 ? 1: 0), \ (a & 0x10 ? 1: 0), \ (a & 0x8 ? 1: 0) ,\ (a & 0x4 ? 1: 0) ,\ (a & 0x2 ? 1: 0) ,\ (a & 0x1 ? 1: 0) ); return len; } /* printf_output_B */ int main(void) { int file; char filename[40]; const char *buffer; int addr = 0x21; int result = 0; sprintf(filename,"/dev/i2c-1"); if ((file = open(filename,O_RDWR)) < 0) { printf("Failed to open the bus."); /* ERROR HANDLING; you can check errno to see what went wrong */ exit(1); } if (ioctl(file,I2C_SLAVE, addr) < 0) { printf("Failed to acquire bus access and/or talk to slave.\n"); /* ERROR HANDLING; you can check errno to see what went wrong */ exit(1); } char buf[10] = {0}; float data; char channel; buf[0] = 0xF6; // command for slave: collect data if (register_printf_function ('B', printf_output_B, printf_arginfo_B)) { printf("Failed to register \%B\n"); } if (write(file,buf,1) != 1) { /* ERROR HANDLING: i2c transaction failed */ printf("Failed to write to the i2c bus.\n"); buffer = strerror(errno); printf(buffer); printf("\n\n"); } int calculatedChecksum = 0; int checksum = 0; for(int i = 0; i<5; i++) { // Using I2C Read if (read(file,buf,1) != 1) { /* ERROR HANDLING: i2c transaction failed */ printf("Failed to read from the i2c bus.\n"); buffer = strerror(errno); printf(buffer); printf("\n\n"); } else { //data = (float)((buf[0] & 0b00001111)<<8)+buf[1]; //data = data/4096*5; //channel = ((buf[0] & 0b00110000)>>4); //printf("Channel %02d Data: %04f\n",channel,data); printf("%B\n", buf[0]); calculatedChecksum += buf[0]; } } if (read(file,buf,1) == 1) { checksum = buf[0]*256; } if (read(file,buf,1) == 1) { checksum += buf[0]; } printf("%d == %d ? %s\n", calculatedChecksum , checksum, calculatedChecksum == checksum ? "ja" : "nee"); return 0; }


pi@raspbian ~/src/my_i2c $ ./fsaysI2C
195 = 0b11000011
153 = 0b10011001
015 = 0b00001111
240 = 0b11110000
231 = 0b11100111
834 == 834 ? ja
pi@raspbian ~/src/my_i2c $

Every time the program is executed, the slave returns the same data. The next steps will be: change the hard-coded part and send the real values of PIC18F4550 ports A-E and do something with it on the RPi-side. But that will be in a next blog...

Back to List

All form fields are required.
A confirmation mail for the comments will be send to you.