Dernière mise à jour le 15/01/2021

Présentation


Vous avez envie d’écrire des données via la liaison I2C sur un PIC 18F4550 (ou autres) en utilisant la cam OpenMV ? c’est ce que nous allons voir dans cette article, ou il y a aura la programmation de la cam OpenMV en Python et un autre “bout” de code en MikroC pour le PIC.

Dans un premier temps, il faut “caler”, ou dirais-je rendre la communication I2C compatible entre la cam et le PIC pour arriver en sorte à ce que la cam puisse écrire des données sur le PIC qui une fois reçues, ce dernier les déposeras tour à tour dans un tableau (array) (tableau de 8×1) qu’on nommera Buffer[8][1].


Une bonne configuration entre la cam OpenMV et le PIC18F4550 est avant tout l’adressage. Oui sans adresse côté PIC il sera impossible de communiquer avec ce dernier. Pour plus de détails sur comment le configurer en mode esclave, vous pouvez vous rendre dans cette rubrique PIC18F4550 (esclave) – OpenMV (maître) – mode I2C.


Pour faire “simple”, si vous configurez votre cam OpenMV avec pour adresse cAdrr = 0x02, il faudra configurer le PIC avec cAdrr = 0x04 (ajout de 1bit à droite donc décalage vers la gauche pour le PIC18F4550 !! – 0x02 << 1 donne 0x04).
ou
si vous configurez votre cam OpenMV avec cette fois-ci pour adresse cAdrr = 0x02 >> 1 (suppression de 1 bit à droite cette fois-ci réalisé au niveau de la cam, décalage à droite !, ce qui donne 0x01), alors côté PIC 18F4550 laisser cAdrr = 0x02).

C’est cette histoire de décalage qui va permettre de rendre compatible la cam OpenMV avec le PIC (on fera plusieurs exemples un peu plus bas pour voir ensemble le principe c’est beaucoup plus parlant).

Mode Ecriture – Cam OpenMV vers PIC18F4550


Ce que je vous propose c’est de regarder dans un 1er temps le code côté cam en Python, et nous ferons une explication sur le principe de fonctionnement.

MAÎTRE – Programmation Python – Cam OpenMV STM32F765VI ARM

# Maître – OpenMV I2C – Ecriture – Python – Electronique71.com

from pyb import I2C
import utime

i2c = I2C(2) # creation du bus I2C
i2c.init(I2C.MASTER, baudrate=100000) # initialisation du maître sur 100Khz

# 0x02 >> 1 : +1 bit à droite (décalage vers la gauche de l'adresse donne 0x04 pour PIC)
cAdrr = 0x02 >> 1
# si cAdrr = 0x02 côté cam OpenMV alors mettre cAdrr = 0x04 côté PIC18F4550 pour compatibilité !

utime.sleep_ms(500)# On attend un peu la fin de l'initialisation du bus I2C.

while (True):

    # Ecriture
    i2c.send(0x00, addr = cAdrr)  # bit 0

    utime.sleep_ms(500) # -> Temp Min : utime.sleep_us(50)
    i2c.send(0x01, addr = cAdrr)  # bit 1

    utime.sleep_ms(500) # -> Temp Min : utime.sleep_us(50)
    i2c.send(0x02, addr = cAdrr)  # bit 2

    utime.sleep_ms(500) # -> Temp Min : utime.sleep_us(50)
    i2c.send(0x03, addr = cAdrr)  # bit 3

    utime.sleep_ms(500) # -> Temp Min : utime.sleep_us(50)
    i2c.send(0x04, addr = cAdrr)  # bit 4

    utime.sleep_ms(500) # -> Temp Min : utime.sleep_us(50)
    i2c.send(0x05, addr = cAdrr)  # bit 5

    utime.sleep_ms(500) # -> Temp Min : utime.sleep_us(50)
    i2c.send(0x06, addr = cAdrr)  # bit 6

    utime.sleep_ms(500) # -> Temp Min : utime.sleep_us(50)
    i2c.send(0x07, addr = cAdrr)  # bit 7

    print("Fin d'écriture !!")

    break

Fonctionnement
Ce code en plusieurs lignes permet uniquement d’envoyer des données sur le bus I2C, et c’est la fonction “send” qui va réaliser tout le travail.

afin de voir ce qu’il se passe côté PIC, j’ai ajouter entre chaque envoi une temporisation de 500ms ce qui permettra de voir le changement des états des leds sur la platine EasyPICV7 (rien ne vous empêche de faire ce test sur une platine d’expérimentation sans soudure).

Dans cette fonction “send” (envoyer) il faut stipuler les données à transmettres, ici, je ne fais simplement l’envoi de chiffre de 0 à 7 en hexadécimal. Biensûr il est possible d’envoyer des caractères comme des lettres en prenant soin d’ajouter ces caractères entre guillemet comme ceci : send(‘A’, addr = cAdrr) – ou send(‘J’, addr = cAdrr) – ou voir même des symboles comme send(‘#’, addr = cAdrr)…etc…etc…


Ce code permet uniquement d’écrire sur le PIC18F4550 rien de plus c’est déjà pas mal pour un début.


Penchons nous côté programmation du PIC18F4550 en utilisant le langage C (MikroC), et regardons les réglages de horloge ou je procède ainsi.


ESCLAVE – Programmation MikroC – PIC18F4550

// Esclave – PIC18F4550 – Ecriture – MikroC – Electronique71.com

const cAdrr = 0x02; // cAdrr = 0x02 correspond à l'adresse de l'esclave
// Si cAdrr = 0x04 côté PIC alors mettre cAddr = 0x02 côté cam OpemMV pour compatibilité !

unsigned char iCount_Ecriture;
unsigned char Data;
unsigned char VideBuffer;

// Tableau 2 dimensions pour envoyer donner vars cam OpenMV
unsigned char Buffer[8][1] = {
                                {0x00},  // bit 0
                                {0x00},  // bit 1
                                {0x00},  // bit 2
                                {0x00},  // bit 3
                                {0x00},  // bit 4
                                {0x00},  // bit 5
                                {0x00},  // bit 6
                                {0x00},  // bit 7
                              };

// Tableau 1 dimension pour envoyer donner vars cam OpenMV
//unsigned char Buffer[8] = {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07};

void interrupt() // On contrôle chaque interruptions
{
  if (PIR1.SSPIF == 1)
  {
    // SSPSTAT.R_W  -> bit0 = 1: Lecture  ou  bit0 = 0: Ecriture
    if (SSPSTAT.R_W == 0)
    {
      // Ecriture -> Maître (OpenMV) vers Esclave (18F4550)
      Data = iCount_Ecriture >> 2;
      Buffer[0][Data] = SSPBUF; // On ajoute les données dans un tableau
      LATD = Buffer[0][Data]; // On affiche les données sur le PORTD
      ++iCount_Ecriture;
    }
  }

  SSPCON1.CKP = 1; // On relache l'horloge pour lancer la suite
  PIR1.SSPIF = 0;  // On remet à zéro le drapeau d'interruption et on attent la prochaine !
}


void init()
{

  ADCON1 = 0xFF;      // On configure toutes les broches en numérique (0 ou 1)

  ADCON0.ADON = 0;   // On désactive le module de convertisseur analogique numérique

  CMCON = 0x07;      // On désactive le mode comparateur

  ADCON1.VCFG0 = 0;  // Bien penser à desactiver les tensions de références !
  ADCON1.VCFG1 = 0;

  TRISA = 0xFF;      // Config Entrée PORTA -> (RA0 à RA7)
  PORTA = 0x00;
  LATA = 0x00;

  TRISB = 0x3F;      // Config les broches du PORTB - RB0 à RB5 comme entrée (SDA/SCL)
  PORTB = 0x00;
  LATB = 0x00;

  TRISD = 0x00;      // Config PORTD en sortie pour la lecture
  PORTD = 0x00;
  LATD = 0x00;

  TRISE = 0x00;      // Config MCLR
  PORTE = 0x00;
  LATE = 0x00;

  INTCON.GIE = 1;    // On active les interruptions globales 
  INTCON.PEIE = 1;   // On active les interruptions périphériques 
 
  PIE1.SSPIE = 1;    // On active le MSSP ( Module port série synch principal I2C ou SPI) 
  PIR1.SSPIF = 0;    // On met le drapeau d'interruption à zéro pour commencer 
 
  SSPCON1.SSPEN = 1; // On active les broches RB0 et RB1 en mode SDA et SCL
 
  // On active le mode 7 bits (0 à 7 = 8 bits) avec interruptions sur bit Start et Bit Stop 
  SSPCON1.SSPM0 = 0;
  SSPCON1.SSPM1 = 1;
  SSPCON1.SSPM2 = 1;
  SSPCON1.SSPM3 = 1;

  SSPADD = cAdrr;    // Configuration de l'adresse de l'esclave du PIC 18F4550

  SSPSTAT.SMP = 1;   //  vitesse de communication 100 Khz (standard)

  SSPCON2.SEN = 1;   // On active l'étirement de l'horloge

  SSPCON2.SEN = 1;   // On active l'étirement de l'horloge

  iCount_Ecriture = 0;

  Data = 0;

  VideBuffer = SSPBUF;  // On vide le Buffer correctement !

  Delay_ms(100);       // On attend un peu la fin de l'initialisation
}

void main()
{
  init(); // Initialisation du PIC18F4550

  while (1)
  {
    if ((P_bit == 1) && (Data >= 8))
    {
      iCount_Ecriture = 0;  // On remet à "0" à 8
      Data = 0;             // et on receommence le cycle !
    }

    // Lecture du tableau et affichage des données sur le PORTD
    if (PORTA.B0 == 1){ LATD = Buffer[0][0]; }

    if (PORTA.B1 == 1){ LATD = Buffer[0][1]; }

    if (PORTA.B2 == 1){ LATD = Buffer[0][2]; }

    if (PORTA.B3 == 1){ LATD = Buffer[0][3]; }

    if (PORTB.B2 == 1){ LATD = Buffer[0][4]; }

    if (PORTB.B3 == 1){ LATD = Buffer[0][5]; }

    if (PORTB.B4 == 1){ LATD = Buffer[0][6]; }

    if (PORTB.B5 == 1){ LATD = Buffer[0][7]; }

  }
}

Fonctionnement
Tout réside sur la réception côté PIC18F4550 ou nous devons utiliser les interruptions.

1er envoi = Adresse:
Lorsque la cam OpenMV va envoyer des données, le 1er octet correspondra TOUJOURS à l’adresse, ici l’adresse pour communiquer avec le PIC est cAdrr = 0x02.
Dans cette adresse le bit de poids faible (bit0) sera à l’état “0” car nous faisons uniquement une écriture.
Lorsque l’adresse est reconnue par le PIC18F4550, une interruption sera déclenchée à condition bien sûr d’avoir activé soigneusement les bits INTCON.GIE = 1 et INTCON.PEIE = 1 dans le registre INTCON du PIC. Le registre SSPSR quant à lui ne va recevoir uniquement un octet complet à chaque réception des données qui émane de la cam OpenMV.

Si l’adresse est reconnue pas le PIC, le registre SSPSR va transférer cette octet vers le buffer interne du PIC qui se nomme SSPBUF, et c’est à ce moment précis lorsque le SSPBUF est “rempli” (1 octet = 8bits) une interruption survient et met à l’état logique haut “1” le bit SSPIF du registre PIR1.

Nous nous retrouvons maintenant au niveau de la ligne SSPSTAT.R_W. Ce bit R_W (R=read/W=Write), permet de lire le bit de poids faible (bit0) afin de voir si celui-ci est à l’état logique bas “0” qui correspond à une écriture ce qui est notre cas , ou à l’état logique haut “1”, dans ce cas nous somme en écriture. Il est important de signaler que ce bit change uniquement d’état sur l’envoi du 1er octet ! (adresse ou le bit R_W contrôle l’état du bit de poids faible bit0).

nous passons à la ligne précédente ou nous allons faire un décalage de 2 vers la droite ! (on verras un peu plus loin pourquoi mais attention ce sera compliqué niveau explication !).

Pour le reste, nous récupérons les données de la cam OpenMV afin de les placer tour à tour dans un tableau nommé Buffer.
Une fois l’écriture terminée, il suffit de présser les boutons RA0 ou RA1 ou … (voir schéma ci-dessous) pour afficher les valeurs envoyées depuis la cam OpenMV tout simplement.


2ème envoi:
Pour le 2ème envoi, idem encore une fois on fait une lecture de l’adresse et on recommence comme évoqué précédement, c’est bien la ou réside la particularité de la fonction “send” ou je serais dans l’obligation de faire un décalage de 2 vers la droite…

Pourquoi ??? Data = iCount_Ecriture >> 2;
Hum ?!! vous êtes sur de vouloir attaquer ce sujet ? ok très bien.
Chaque fois que nous envoyons via la cam OpenMV un caractère sur la liaison I2C en utilisant la fonction “send”, cela compte 4 tours ?!, j’ai réalisé plusieurs fois des tests et la pour le coup je n’ai pas compris et je ne comprend toujours pas pourquoi….


Ce qui est sûr c’est que niveau affichage voila ce qu’il en résulte si nous faisons des envoies au coup par coup:
Sans décalage:
PORTC affiche : 100b       = 4
PORTC affiche : 1000b     = 8
PORTC affiche : 1100b     = 12
PORTC affiche : 10000b   = 16
PORTC affiche : 10100b   = 20
PORTC affiche : 11000b   = 24
PORTC affiche : 11100b   = 28
PORTC affiche : 100000b = 32
PORTC affiche : 100100b = 36
PORTC affiche : 101000b = 40
PORTC affiche : 101100b = 44
PORTC affiche : 110000b = 48
PORTC affiche : 110100b = 52

Avec décalage:
PORTC affiche : 100b = 4 avec décalage 100b >> 2  = 1 = 1
PORTC affiche : 1000b = 8 avec décalage 1000b >> 2 = 10 = 2
PORTC affiche : 1100b = 12 avec décalage 1100b >> 2 = 11 = 3
PORTC affiche : 10000b = 16 avec décalage 10000b >> 2 = 100 = 4
PORTC affiche : 10100b = 20 avec décalage 10100b >> 2 = 101 = 5
PORTC affiche : 11000b = 24 avec décalage 11000b >> 2  = 110 = 6
PORTC affiche : 11100b = 28 avec décalage 11100b >>  2 = 111 = 7
PORTC affiche : 100000b = 32 avec décalage 100000b >> 2  = 1000 = 8
PORTC affiche : 100100b = 36 avec décalage 100100b >> 2  = 1001 = 9
PORTC affiche : 101000b = 40 avec décalage 101000b >> 2  = 1010 = 10
PORTC affiche : 101100b = 44 avec décalage 101100b >> 2  = 1011 = 11
PORTC affiche : 110000b = 48 avec décalage 110000b >> 2  = 1100 = 12
PORTC affiche : 110100b = 52 avec décalage 110100b >> 2  = 1101 = 13
PORTC affiche : 111000b = 56 avec décalage 111000b >> 2  = 1110 = 14
PORTC affiche : 111100b = 60 avec décalage 111100b >> 2  = 1111 = 15


Je vous rassure, je me suis gratté la tête un bon moment pour arriver à trouver une astuce afin de contourner ce problème ce que je peux vous dire, c’est que la fonction “send” côté cam OpenMV va envoyer l’adresse + les données … mettons ça de côté, le principal c’est d’arriver à un code fonctionnel comme celui-ci cadencé niveau du PIC à l’aide d’un quartz de 8Mhz uniquement.

Schéma


Changement d’adresse



Comme évoqué dans la présentation, J’ai réalisé différents tests (8 au total) avec d’autres adresses à savoir de bien respecter que le bit0 soit à un état logique bas “0”. Pour ne pas compliquer, je fais un décalage à gauche uniquement côté PIC et non côté cam OpenMV (voir ci-dessous les différentes adresses)

Test Adresse OpenMV (cAdrr) Adresse 18F4550 (cAdrr) Compatibilité
1 0x04 (100b) 0x08 (1000b) OK
2 0x10 (10000b) 0x20 (100000b) OK
3 0x14 (10100b) 0x28 (101000b) OK
4 0xF0 (11110000b) 0xE0 (11100000b) OK
5 0xAA (10101010b) 0x54 (01010100b) OK
6 0x80 (10000000b) 0x00 (00000000b) NOK
7 0x40 (01000000b) 0x80 (10000000b) OK
8 0x6C (01101100b) 0xD8 (11011000b) OK

Comme vous pouvez le constater, il suffit de rajouter un “0” tout à droite, ce qui décale l’octet à gauche et donc supprime le dernier chiffre tout à gauche.

Test sur platine EasyPIC V7


Oui !! L’envoi des données via la cam OpenMV sont récupérés par les interruptions du PIC18F4550 afin que celle-ci soient affichées sur le PORTD. Ainsi, les leds s’allumes par comptage binaire de 0 à 7.


Le câblage est réalisés sur le PORTC broche RC3(SCL) vers RB1(SCL du PIC18F4550) et RC4(SDA) vers RB0(SDA du PIC18F4550). En effet ce pont entre le PORTC permet de ramener les résistances de 4k7 sur le PORTB où sont reliés les broches SDA et SCL du PIC18F4550 situé sur la platine EasyPicV7. Ne pas oublier de placer les switchs (SW4) RC3 et RC4 en postion “ON” afin de mettre en circulation les résistances PULL-UP qui sont la pour tirer vers le haut (on appel ceci des résistances de tirage d’ou l’expression PULL-UP tirage vers le haut).

Aucunes photos désolé! mais vous avez le code et le schéma c’est le principal ;-).

Historiques


15/01/2021
-1er mise à disposition