Bắt đầu với I2C trên PIC18


8

Đối với một dự án tôi muốn ba PIC (hai nô lệ PIC18F4620, một PIC18F46K22 chính) để giao tiếp qua xe buýt I2C. Sau đó, nhiều nô lệ có thể được thêm vào (như EEPROM, SRAM, ...). Tôi đang viết mã cho các PIC này bằng C bằng trình biên dịch C18. Tôi đã tìm kiếm rất nhiều trên internet, nhưng không thể tìm thấy các thư viện để xử lý ngoại vi SSP (M). Tôi đã đọc biểu dữ liệu của cả hai PIC trên thiết bị ngoại vi SSP (M) ở chế độ I2C nhưng không thể tìm ra cách giao diện xe buýt.

Vì vậy, tôi cần thư viện chủ nô lệ.

Bạn đề xuất món gì? Bạn có một thư viện như vậy ở đâu đó không? Được tích hợp sẵn trong trình biên dịch và nếu có, ở đâu? Có một hướng dẫn tốt ở đâu đó trên mạng?


2
Tôi đã có vấn đề tương tự vài tháng trước. Bạn có thể đọc về họ ở đây . Dưới đây là các thư viện cho C18 hoạt động với I ^ 2C, nhưng thiếu một điều lớn: Bạn cần đặt tốc độ xe buýt bằng tay bằng cách viết vào đăng ký thích hợp và điều đó không được đề cập ở bất cứ đâu trong tài liệu của thư viện.
AndrejaKo

Cảm ơn, điều đó rất hữu ích! Tuy nhiên, nó chỉ làm phần chủ chứ không phải phần nô lệ.

Vâng, lúc đó tôi không cần phải làm việc với nô lệ, nên không có ví dụ về nô lệ. Lấy làm tiếc.
AndrejaKo

2
Không, điều đó tốt, nó hữu ích cho phần chính! :-)

vui lòng tắt analog trên các cổng ANSELC = 0;

Câu trả lời:


22

Microchip đã viết ghi chú ứng dụng về điều này:

  • AN734 về việc thực hiện nô lệ I2C
  • AN735 về việc triển khai một chủ I2C
  • Ngoài ra còn có AN736 lý thuyết hơn về việc thiết lập giao thức mạng để giám sát môi trường, nhưng nó không cần thiết cho dự án này.

Các ghi chú ứng dụng đang hoạt động với ASM nhưng có thể chuyển sang C dễ dàng.

Trình biên dịch C18 và XC8 miễn phí của Microchip có chức năng I2C. Bạn có thể đọc thêm về chúng trong tài liệu thư viện Trình biên dịch , phần 2.4. Dưới đây là một số thông tin bắt đầu nhanh:

Đang cài đặt

Bạn đã có trình biên dịch C18 hoặc XC8 của Microchip. Cả hai đều có chức năng I2C tích hợp. Để sử dụng chúng, bạn cần bao gồm i2c.h:

#include i2c.h

Nếu bạn muốn xem mã nguồn, bạn có thể tìm thấy nó ở đây:

  • Tiêu đề C18: installation_path/vx.xx/h/i2c.h
  • Nguồn C18: installation_path/vx.xx/src/pmc_common/i2c/
  • Tiêu đề XC8: installation_path/vx.xx/include/plib/i2c.h
  • Nguồn XC8: installation_path/vx.xx/sources/pic18/plib/i2c/

Trong tài liệu, bạn có thể tìm thấy tập tin nào trong /i2c/thư mục có chức năng.

Mở kết nối

Nếu bạn quen thuộc với các mô-đun MSSP của Microchip, trước tiên bạn sẽ biết chúng phải được khởi tạo. Bạn có thể mở kết nối I2C trên cổng MSSP bằng OpenI2Cchức năng. Đây là cách nó được định nghĩa:

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

Với sync_mode, bạn có thể chọn xem thiết bị là chủ hay nô lệ và nếu là nô lệ, liệu nó nên sử dụng địa chỉ 10 bit hay 7 bit. Hầu hết thời gian, 7 bit được sử dụng, đặc biệt là trong các ứng dụng nhỏ. Các tùy chọn cho sync_modelà:

  • SLAVE_7 - Chế độ nô lệ, địa chỉ 7 bit
  • SLAVE_10 - Chế độ nô lệ, địa chỉ 10 bit
  • MASTER - Chế độ chính

Với slew, bạn có thể chọn nếu thiết bị nên sử dụng tốc độ xoay. Thông tin thêm về những gì ở đây: Tốc độ xoay cho I2C là gì?

Hai mô-đun MSSP

Có một cái gì đó đặc biệt về các thiết bị có hai mô-đun MSSP, như PIC18F46K22 . Chúng có hai bộ hàm, một cho mô-đun 1 và một cho mô-đun 2. Ví dụ, thay vì OpenI2C(), chúng có OpenI2C1()openI2C2().

Được rồi, vì vậy bạn đã thiết lập tất cả và mở kết nối. Bây giờ hãy làm một số ví dụ:

Ví dụ

Thầy viết ví dụ

Nếu bạn quen thuộc với giao thức I2C, bạn sẽ biết một chuỗi ghi chủ điển hình trông như thế này:

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

Đầu tiên, chúng tôi gửi một điều kiện BẮT ĐẦU. Hãy xem xét điều này nhận điện thoại. Sau đó, địa chỉ với một bit Ghi - quay số. Tại thời điểm này, nô lệ với địa chỉ được gửi biết rằng anh ta sẽ được gọi. Anh ấy gửi một lời cảm ơn ("Xin chào"). Bây giờ, thiết bị chính có thể gửi dữ liệu - anh ta bắt đầu nói chuyện. Anh ta gửi bất kỳ số lượng byte. Sau mỗi byte, Slave sẽ ACK dữ liệu nhận được ("vâng, tôi nghe thấy bạn"). Khi thiết bị chính đã nói xong, anh ta cúp máy với điều kiện STOP.

Trong C, trình tự viết chính sẽ trông như thế này đối với chủ:

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

Thầy đọc ví dụ

Trình tự đọc chính hơi khác với trình tự ghi:

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

Một lần nữa, chủ nhân bắt đầu cuộc gọi và quay số. Tuy nhiên, bây giờ anh ấy muốn có được thông tin. Đầu tiên nô lệ trả lời cuộc gọi, sau đó bắt đầu nói chuyện (gửi dữ liệu). Bậc thầy thừa nhận từng byte cho đến khi anh ta có đủ thông tin. Sau đó, anh ta gửi một Not-ACK và cúp máy với điều kiện STOP.

Trong C, nó sẽ trông như thế này cho phần chính:

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

Mã nô lệ

Đối với nô lệ, tốt nhất là sử dụng Định tuyến dịch vụ ngắt hoặc ISR. Bạn có thể thiết lập vi điều khiển của mình để nhận một ngắt khi địa chỉ của bạn được gọi. Bằng cách đó bạn không phải kiểm tra xe buýt liên tục.

Đầu tiên, hãy thiết lập những điều cơ bản cho các ngắt. Bạn sẽ phải kích hoạt các ngắt và thêm ISR. Điều quan trọng là PIC18 có hai mức ngắt: cao và thấp. Chúng tôi sẽ đặt I2C làm ngắt ưu tiên cao, vì việc trả lời cuộc gọi I2C là rất quan trọng. Những gì chúng ta sẽ làm là như sau:

  • Viết ISR SSP, khi ngắt là ngắt SSP (chứ không phải ngắt khác)
  • Viết ISR ưu tiên cao chung, khi ngắt được ưu tiên cao. Hàm này phải kiểm tra loại ngắt nào được kích hoạt và gọi ISR ​​phụ đúng (ví dụ: SSP ISR)
  • Thêm một GOTOhướng dẫn vào ISR chung trên vectơ ngắt ưu tiên cao. Chúng ta không thể đặt ISR chung trực tiếp lên vector vì nó quá lớn trong nhiều trường hợp.

Đây là một ví dụ mã:

// 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
}

Điều tiếp theo cần làm là kích hoạt ngắt ưu tiên cao khi chip khởi tạo. Điều này có thể được thực hiện bằng một số thao tác đăng ký đơn giản:

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

Bây giờ, chúng tôi đã làm việc gián đoạn. Nếu bạn đang thực hiện điều này, tôi sẽ kiểm tra nó ngay bây giờ. Viết một cơ bản SSPISR()để bắt đầu nhấp nháy đèn LED khi xảy ra gián đoạn SSP.

Được rồi, vì vậy bạn có ngắt của bạn làm việc. Bây giờ hãy viết một số mã thực sự cho SSPISR()hàm. Nhưng trước tiên một số lý thuyết. Chúng tôi phân biệt năm loại ngắt I2C khác nhau:

  1. Master viết, byte cuối cùng là địa chỉ
  2. Master viết, byte cuối cùng là dữ liệu
  3. Thầy đọc, byte cuối cùng là địa chỉ
  4. Thầy đọc, byte cuối cùng là dữ liệu
  5. NACK: kết thúc truyền

Bạn có thể kiểm tra trạng thái của mình bằng cách kiểm tra các bit trong thanh SSPSTATghi. Thanh ghi này như sau trong chế độ I2C (các bit không sử dụng hoặc không liên quan được bỏ qua):

  • Bit 5: D / KHÔNG A: Dữ liệu / Không phải địa chỉ: được đặt nếu byte cuối cùng là dữ liệu, bị xóa nếu byte cuối cùng là một địa chỉ
  • Bit 4: P: Dừng bit: đặt nếu điều kiện STOP xảy ra lần cuối (không có hoạt động nào)
  • Bit 3: S: Bit bắt đầu: được đặt nếu điều kiện START xảy ra lần cuối (có một hoạt động đang hoạt động)
  • Bit 2: R / KHÔNG W: Đọc / Không ghi: đặt nếu thao tác là Master Đọc, bị xóa nếu thao tác là Master Write
  • Bit 0: BF: Bộ đệm đầy đủ: đặt nếu có dữ liệu trong thanh ghi SSPBUFF, bị xóa nếu không

Với dữ liệu này, thật dễ dàng để xem cách xem mô-đun I2C ở trạng thái nào:

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

Trong phần mềm, tốt nhất nên sử dụng trạng thái 5 làm mặc định, được giả sử khi các yêu cầu cho các trạng thái khác không được đáp ứng. Theo cách đó, bạn không trả lời khi bạn không biết chuyện gì đang xảy ra, vì nô lệ không phản hồi NACK.

Dù sao, hãy xem mã:

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

Bạn có thể xem cách bạn có thể kiểm tra thanh SSPSTATghi (đầu tiên là AND 0x2dđể chúng ta chỉ có các bit hữu ích) bằng cách sử dụng bitmasks để xem loại ngắt nào chúng ta có.

Đó là công việc của bạn để tìm ra những gì bạn phải gửi hoặc làm khi bạn phản ứng với một ngắt: nó phụ thuộc vào ứng dụng của bạn.

Người giới thiệu

Một lần nữa, tôi muốn đề cập đến các ghi chú ứng dụng Microchip đã viết về I2C:

  • AN734 về việc thực hiện nô lệ I2C
  • AN735 về việc triển khai một chủ I2C
  • AN736 về việc thiết lập giao thức mạng để giám sát môi trường

Có tài liệu cho các thư viện trình biên dịch: Tài liệu thư viện trình biên dịch

Khi tự thiết lập một cái gì đó, hãy kiểm tra biểu dữ liệu của chip của bạn trên phần (M) SSP để liên lạc I2C. Tôi đã sử dụng PIC18F46K22 cho phần chính và PIC18F4620 cho phần phụ.


3

Đầu tiên, tôi khuyên bạn nên thay đổi trình biên dịch XC8 đơn giản vì đây là phiên bản mới nhất. Có sẵn các thư viện ngoại vi, nhưng tôi chưa bao giờ sử dụng chúng nhiều. Kiểm tra trên trang web của Microchips để biết chi tiết và các tài liệu.

Được rồi, tôi có một số thói quen cơ bản rất cũ ở đây cho I2C eeprom comms tôi đã sử dụng từ lâu với PIC16F và trình biên dịch tầm trung Microhip cũ (có thể là Hi-Tech), nhưng tôi nghĩ chúng có thể hoạt động tốt với PIC18, vì tôi nghĩ thiết bị ngoại vi là như nhau. Dù sao thì bạn cũng sẽ tìm ra rất nhanh nếu tất cả đều khác biệt.
Chúng là một phần của một tệp lớn hơn được sử dụng với dự án bộ ghi nhiệt độ, vì vậy tôi đã nhanh chóng tách ra tất cả các chức năng không liên quan khác và được lưu dưới dạng các tệp bên dưới, vì vậy có thể tôi đã tạo ra một chút hỗn độn của nó, nhưng hy vọng bạn sẽ có thể có được một số ý tưởng về những gì cần thiết (thậm chí nó có thể chỉ hoạt động, bạn không bao giờ biết ;-))

Bạn sẽ cần đảm bảo tệp tiêu đề chính là chính xác và kiểm tra / thay đổi các chân để đảm bảo chúng là các chân ngoại vi I2C chính xác và tên đăng ký nếu chúng là từ trình biên dịch Hi-Tech, đã làm mọi thứ hơi khác liên quan đến quy ước đặt tên đăng ký.

Tệp 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
}

Tệp tiêu đề 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 );

Cảm ơn, đó là phần chính và có lẽ giống như PIC18. Cũng cảm ơn về ghi chú của trình biên dịch :-) Hỏi xung quanh một chút đã cho tôi một số ghi chú ứng dụng, vì vậy tôi sẽ tự thêm chúng dưới dạng câu trả lời.

Bạn nên xem xét thêm một phần về thiết lập trình tạo tốc độ baud. Mã thường trông giống như SSP1ADD = ((_XTAL_FREQ/100000)/4)-1;1KHz, v.v.
Jesse Craig

1

Trình biên dịch XC8 và XC16 bao gồm các thư viện cho I2C.

Vấn đề tôi gặp phải là tài liệu không tốt lắm! Nếu bạn sử dụng các ví dụ từ tài liệu Microchip, bạn sẽ không gặp may. Ngay cả hỗ trợ Microchip cũng không thể giúp bạn. Tôi đã ở đó một mình.

Cách đây một thời gian, tôi đã làm việc với bộ vi điều khiển dòng PIC24EP512GP và thư viện không hoạt động với tôi như tài liệu của Microchip.


Ah, thật là xấu hổ! Vậy bạn đã làm gì?

Cải thiện không may của riêng tôi.
Chetan Bhargava

1
Chúng có hữu ích cho người khác không? Tôi muốn nhìn thấy chúng!
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.