Начиная с I2C на PIC18

8

Для проекта мне бы хотелось, чтобы три PIC (два ведомых PIC18F4620, один главный PIC18F46K22) обменивались данными по шине I2C. Позже может быть добавлено больше рабов (например, EEPROM, SRAM, ...). Я пишу код для этих PIC в C, используя компилятор C18. Я много смотрел в интернете, но не смог найти библиотек для обработки периферийных устройств (M) SSP. Я прочитал таблицу данных обоих PIC на периферийном устройстве (M) SSP в режиме I2C, но не смог выяснить, как взаимодействовать с шиной.

Поэтому мне нужны основные и подчиненные библиотеки.

Что вы порекомендуете? У вас где-нибудь есть такая библиотека? Он встроен в компилятор и если да, то где? Где-нибудь в сети есть хороший учебник?


источник
2
У меня были подобные проблемы несколько месяцев назад. Вы можете прочитать о них здесь . Вот библиотеки для C18, которые работают с I ^ 2C, но здесь не хватает одной важной вещи: вам нужно вручную установить скорость шины, записав в соответствующий регистр, и это нигде не упоминается в документации библиотеки.
AndrejaKo
Спасибо, это было полезно! Это только сделало главную часть, а не подчиненную часть.
Да, мне тогда не нужно было работать с рабом, так что никаких примеров раба. Сожалею.
AndrejaKo
2
Нет, это нормально, это было полезно для мастер-части! :-)
также отключите аналог на портах ANSELC = 0;

Ответы:

22

Микрочип написал примечания к приложению по этому поводу:

  • AN734 по внедрению ведомого I2C
  • AN735 по реализации мастера I2C
  • Существует также более теоретический AN736 по настройке сетевого протокола для мониторинга окружающей среды, но он не нужен для этого проекта.

Замечания по применению работают с ASM, но их легко перенести на C.

Бесплатные компиляторы Microchip C18 и XC8 имеют функции I2C. Подробнее о них вы можете прочитать в документации к библиотекам компилятора , раздел 2.4. Вот небольшая информация о начале:

Настройка

У вас уже есть компилятор Microchip C18 или XC8. Они оба имеют встроенные функции I2C. Чтобы использовать их, вам необходимо включить i2c.h:

#include i2c.h

Если вы хотите взглянуть на исходный код, вы можете найти его здесь:

  • Заголовок C18: installation_path/vx.xx/h/i2c.h
  • C18 источник: installation_path/vx.xx/src/pmc_common/i2c/
  • Заголовок XC8: installation_path/vx.xx/include/plib/i2c.h
  • Источник XC8: installation_path/vx.xx/sources/pic18/plib/i2c/

В документации вы можете найти, в каком файле в /i2c/папке находится функция.

Открытие соединения

Если вы знакомы с MSSP-модулями Microchip, вы будете знать, что их сначала нужно инициализировать. Вы можете открыть соединение I2C через порт MSSP, используя OpenI2Cфункцию. Вот как это определяется:

void OpenI2C (unsigned char sync_mode, unsigned char slew);

С помощью sync_modeвы можете выбрать, является ли устройство ведущим или ведомым, и, если это ведомое устройство, должно ли оно использовать 10-битный или 7-битный адрес. Большую часть времени используется 7-бит, особенно в небольших приложениях. Варианты для sync_mode:

  • SLAVE_7 - ведомый режим, 7-битный адрес
  • SLAVE_10 - ведомый режим, 10-битный адрес
  • MASTER - Мастер режим

С помощью slewвы можете выбрать, будет ли устройство использовать скорость нарастания. Подробнее о том, что здесь: Какова скорость нарастания для I2C?

Два модуля MSSP

В устройствах с двумя модулями MSSP есть что-то особенное, например, PIC18F46K22 . У них есть два набора функций, один для модуля 1 и один для модуля 2. Например, вместо того OpenI2C(), они имеют OpenI2C1()иopenI2C2() .

Итак, вы все настроили и открыли соединение. Теперь давайте сделаем несколько примеров:

Примеры

Мастер написать пример

Если вы знакомы с протоколом I2C, вы будете знать, что типичная основная последовательность записи выглядит следующим образом:

Master : START | ADDR+W |     | DATA |     | DATA |     | ... | DATA |     | STOP
Slave  :       |        | ACK |      | ACK |      | ACK | ... |      | ACK |

Сначала мы отправляем условие START. Подумайте об этом, поднимая трубку. Затем адрес с битом записи - набор номера. На этом этапе раб с отправленным адресом знает, что его вызывают. Он отправляет подтверждение («Привет»). Теперь ведущее устройство может отправлять данные - он начинает говорить. Он отправляет любое количество байтов. После каждого байта ведомый должен подтверждать полученные данные («да, я вас слышу»). Когда ведущее устройство завершило разговор, он вешает трубку в состоянии STOP.

В C последовательность записи мастера будет выглядеть следующим образом:

IdleI2C();                         // Wait until the bus is idle
StartI2C();                        // Send START condition
IdleI2C();                         // Wait for the end of the START condition
WriteI2C( slave_address & 0xfe );  // Send address with R/W cleared for write
IdleI2C();                         // Wait for ACK
WriteI2C( data[0] );               // Write first byte of data
IdleI2C();                         // Wait for ACK
// ...
WriteI2C( data[n] );               // Write nth byte of data
IdleI2C();                         // Wait for ACK
StopI2C();                         // Hang up, send STOP condition

Мастер прочитал пример

Последовательность основного чтения немного отличается от последовательности записи:

Master : START | ADDR+R |     |      | ACK |      | ACK | ... |      | NACK | STOP
Slave  :       |        | ACK | DATA |     | DATA |     | ... | DATA |      |

Опять же, мастер инициирует вызов и набирает номер. Однако сейчас он хочет получить информацию. Сначала подчиненный отвечает на вызов, затем начинает говорить (отправляя данные). Мастер подтверждает каждый байт, пока у него не будет достаточно информации. Затем он отправляет Not-ACK и кладет трубку с условием STOP.

В Си это будет выглядеть так для основной части:

IdleI2C();                         // Wait until the bus is idle
StartI2C();                        // Send START condition
IdleI2C();                         // Wait for the end of the START condition
WriteI2C( slave_address | 0x01 );  // Send address with R/W set for read
IdleI2C();                         // Wait for ACK
data[0] = ReadI2C();               // Read first byte of data
AckI2C();                          // Send ACK
// ...
data[n] = ReadI2C();               // Read nth byte of data
NotAckI2C();                       // Send NACK
StopI2C();                         // Hang up, send STOP condition

Ведомый код

Для ведомого лучше всего использовать программу обработки прерываний или ISR. Вы можете настроить свой микроконтроллер на получение прерывания при вызове вашего адреса. Таким образом, вам не нужно постоянно проверять автобус.

Во-первых, давайте настроим основы для прерываний. Вам нужно будет включить прерывания и добавить ISR. Важно, чтобы PIC18 имели два уровня прерываний: высокий и низкий. Мы собираемся установить I2C как прерывание с высоким приоритетом, потому что очень важно отвечать на вызов I2C. Что мы собираемся сделать, это следующее:

  • Напишите ISR SSP для случая, когда прерывание является прерыванием SSP (а не другим прерыванием)
  • Напишите общий ISR с высоким приоритетом, для которого прерывание имеет высокий приоритет. Эта функция должна проверять, какой тип прерывания сработал, и вызывать правильный подчиненный ISR (например, ISP SSP).
  • Добавьте GOTOинструкцию к общему ISR для вектора прерывания с высоким приоритетом. Мы не можем поместить общий ISR непосредственно в вектор, потому что во многих случаях он слишком велик.

Вот пример кода:

// Function prototypes for the high priority ISRs
void highPriorityISR(void);

// Function prototype for the SSP ISR
void SSPISR(void);

// This is the code for at the high priority vector
#pragma code high_vector=0x08
void interrupt_at_high_vector(void) { _asm GOTO highPriorityISR _endasm }
#pragma code

// The actual high priority ISR
#pragma interrupt highPriorityISR
void highPriorityISR() {
    if (PIR1bits.SSPIF) {        // Check for SSP interrupt
        SSPISR();            // It is an SSP interrupt, call the SSP ISR
        PIR1bits.SSPIF = 0;  // Clear the interrupt flag
    }
    return;
}

// This is the actual SSP ISR
void SSPISR(void) {
    // We'll add code later on
}

Следующее, что нужно сделать, - включить высокоприоритетное прерывание при инициализации чипа. Это можно сделать несколькими простыми манипуляциями с регистром:

RCONbits.IPEN = 1;          // Enable interrupt priorities
INTCON &= 0x3f;             // Globally enable interrupts
PIE1bits.SSPIE = 1;         // Enable SSP interrupt
IPR1bits.SSPIP = 1;         // Set SSP interrupt priority to high

Теперь у нас работают прерывания. Если вы реализуете это, я бы проверил это сейчас. Напишите основную информацию, SSPISR()чтобы начать мигать светодиодом, когда происходит прерывание SSP.

Ладно, значит, ваши прерывания работают. Теперь давайте напишем реальный код для SSPISR()функции. Но сначала немного теории. Мы выделяем пять различных типов прерываний I2C:

  1. Мастер пишет, последний байт был адресом
  2. Мастер пишет, последний байт был данными
  3. Мастер читает, последний байт был адресом
  4. Мастер читает, последний байт был данными
  5. NACK: конец передачи

Вы можете проверить, в каком состоянии вы находитесь, проверив биты в SSPSTATрегистре. Этот регистр выглядит следующим образом в режиме I2C (неиспользуемые или нерелевантные биты опускаются):

  • Бит 5: D / NOT A: Адрес / Не адрес: устанавливается, если последний байт был данными, очищается, если последний байт был адресом
  • Бит 4: P: стоповый бит: устанавливается, если условие STOP возникло последним (нет активной операции)
  • Бит 3: S: стартовый бит: устанавливается, если условие START возникло последним (активная операция)
  • Бит 2: R / NOT W: чтение / не запись: устанавливается, если операция является мастер-чтением, сбрасывается, если операция является мастер-записью
  • Бит 0: BF: Buffer Full: устанавливается, если в регистре SSPBUFF есть данные, очищается, если нет

С помощью этих данных легко увидеть, в каком состоянии находится модуль I2C:

State | Operation | Last byte | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 0
------+-----------+-----------+-------+-------+-------+-------+-------
1     | M write   | address   |   0   |   0   |   1   |   0   |   1
2     | M write   | data      |   1   |   0   |   1   |   0   |   1
3     | M read    | address   |   0   |   0   |   1   |   1   |   0
4     | M read    | data      |   1   |   0   |   1   |   1   |   0
5     | none      | -         |   ?   |   ?   |   ?   |   ?   |   ?

В программном обеспечении лучше использовать состояние 5 по умолчанию, которое предполагается, когда не соблюдаются требования для других состояний. Таким образом, вы не отвечаете, когда не знаете, что происходит, потому что раб не отвечает на NACK.

В любом случае, давайте посмотрим на код:

void SSPISR(void) {
    unsigned char temp, data;

    temp = SSPSTAT & 0x2d;
    if ((temp ^ 0x09) == 0x00) {            // 1: write operation, last byte was address
        data = ReadI2C();
        // Do something with data, or just return
    } else if ((temp ^ 0x29) == 0x00) {     // 2: write operation, last byte was data
        data = ReadI2C();
        // Do something with data, or just return
    } else if ((temp ^ 0x0c) == 0x00) {     // 3: read operation, last byte was address
        // Do something, then write something to I2C
        WriteI2C(0x00);
    } else if ((temp ^ 0x2c) == 0x00) {     // 4: read operation, last byte was data
        // Do something, then write something to I2C
        WriteI2C(0x00);
    } else {                                // 5: slave logic reset by NACK from master
        // Don't do anything, clear a buffer, reset, whatever
    }
}

Вы можете увидеть, как вы можете проверить SSPSTATрегистр (сначала с AND, 0x2dчтобы у нас были только полезные биты), используя битовые маски, чтобы увидеть, какой у нас тип прерывания.

Ваша задача выяснить, что вы должны отправить или сделать, когда отвечаете на прерывание: это зависит от вашего приложения.

Ссылки

Опять же, я хотел бы упомянуть замечания по применению, которые Микрочип написал о I2C:

  • AN734 по внедрению ведомого I2C
  • AN735 по реализации мастера I2C
  • AN736 по настройке сетевого протокола для мониторинга окружающей среды

Есть документация для библиотек компилятора: Документация для библиотек компилятора

При настройке чего-либо самостоятельно проверьте таблицу данных вашего чипа в разделе (M) SSP для связи I2C. Я использовал PIC18F46K22 для основной части и PIC18F4620 для подчиненной части.

Сообщество
источник
3

Во-первых, я бы рекомендовал перейти на компилятор XC8 просто потому, что он самый последний. Доступны периферийные библиотеки, но я никогда ими не пользовался. Проверьте на сайте Microchips подробности и документацию.

Хорошо, у меня есть несколько очень старых базовых подпрограмм для IEP-коммуникаций EEPROM, которые я использовал давным-давно с PIC16F и старым компилятором среднего уровня Microhip (возможно, Hi-Tech), но я думаю, что они могут работать нормально с PIC18, так как я думаю, что периферийное устройство то же самое. Вы все равно очень быстро узнаете, все ли по-другому.
Они были частью большого файла, который использовался в проекте регистратора температуры, поэтому я быстро удалил все другие несвязанные функции и сохранил их в виде файлов ниже, так что, возможно, я немного напутал, но, надеюсь, вы сможет получить представление о том, что нужно (это может даже просто работать, вы никогда не знаете ;-))

Вам нужно убедиться, что основной файл заголовка правильный, и проверить / изменить контакты, чтобы убедиться, что они являются правильными периферийными выводами I2C, и имена регистров, если они от компилятора Hi-Tech, который сделал вещи немного по-другому в отношении соглашения о присвоении имен.

Файл I2C.c:

#include "I2C.h"
#include "delay.h"
#include <pic.h>

#define _XTAL_FREQ 20000000


void write_ext_eeprom(unsigned int address, unsigned char data)
 {
    unsigned char a0 = ((address & 0x8000) >> 14);  
    unsigned char msb = (address >> 8);
    unsigned char lsb = (address & 0x00FF);


   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_write(data);
   i2c_stop();
   DelayMs(11);
}

/******************************************************************************************/

unsigned char read_ext_eeprom(unsigned int address)
{
   unsigned char a0 = ((address & 0x8000) >> 14);  
   unsigned char data;
   unsigned char msb = (address >> 8);
   unsigned char lsb = (address & 0x00FF);

   i2c_start();
   i2c_write(0xa0 | a0);
   i2c_write(msb);
   i2c_write(lsb);
   i2c_repStart();
   i2c_write(0xa1 | a0);
   data=i2c_read(0);
   i2c_stop();
   return(data);
}

void i2c_init()
{
 TRISC3=1;           // set SCL and SDA pins as inputs
 TRISC4=1;

 SSPCON = 0x38;      // set I2C master mode
 SSPCON2 = 0x00;

 //SSPADD = 9;          // 500kHz bus with 20MHz xtal 
 SSPADD = 49;           // 100kHz bus with 20Mhz xtal

 CKE=0;     // use I2C levels      worked also with '0'
 SMP=1;     // disable slew rate control  worked also with '0'

 PSPIF=0;      // clear SSPIF interrupt flag
 BCLIF=0;      // clear bus collision flag
}

/******************************************************************************************/

void i2c_waitForIdle()
{
 while (( SSPCON2 & 0x1F ) | RW ) {}; // wait for idle and not writing
}

/******************************************************************************************/

void i2c_start()
{
 i2c_waitForIdle();
 SEN=1;
}

/******************************************************************************************/

void i2c_repStart()
{
 i2c_waitForIdle();
 RSEN=1;
}

/******************************************************************************************/

void i2c_stop()
{
 i2c_waitForIdle();
 PEN=1;
}

/******************************************************************************************/

int i2c_read( unsigned char ack )
{
 unsigned char i2cReadData;

 i2c_waitForIdle();

 RCEN=1;

 i2c_waitForIdle();

 i2cReadData = SSPBUF;

 i2c_waitForIdle();

 if ( ack )
  {
  ACKDT=0;
  }
 else
  {
  ACKDT=1;
  }
  ACKEN=1;               // send acknowledge sequence

 return( i2cReadData );
}

/******************************************************************************************/

unsigned char i2c_write( unsigned char i2cWriteData )
{
 i2c_waitForIdle();
 SSPBUF = i2cWriteData;
//if(ACKSTAT)
{
//while(ACKSTAT);
}
 return ( ! ACKSTAT  ); // function returns '1' if transmission is acknowledged
}

Заголовочный файл I2C.h:

extern void i2c_init();
extern void i2c_waitForIdle();
extern void i2c_start();
extern void i2c_repStart();
extern void i2c_stop();
extern int i2c_read( unsigned char ack );
extern unsigned char i2c_write( unsigned char i2cWriteData );
Оли Глейзер
источник
Спасибо, это основная часть, и, вероятно, такая же, как PIC18. Также спасибо за примечание компилятора :-) Немного расспросив, дала мне некоторые замечания по применению, поэтому я добавлю их как ответ сам.
Вам следует рассмотреть возможность добавления раздела по настройке генератора скорости передачи. Код обычно выглядит SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;для 1 кГц и т. Д.
Джесси Крейг
1

Компиляторы XC8 и XC16 включают библиотеки для I2C.

Проблема, с которой я столкнулся, заключается в том, что документация не очень хорошая! Если вы используете примеры из документации по Microchip, вам не повезло. Даже поддержка Microchip не может вам помочь. Я был там сам.

Некоторое время назад я работал с микроконтроллером серии PIC24EP512GP, и библиотека не работала для меня, как описано в Microchip.

Четан Бхаргава
источник
Ах, это позор! Итак, что ты сделал?
Импровизированный мой, к сожалению.
Четан Бхаргава
1
Они полезны и для других? Я хотел бы увидеть их!