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