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
/v
x.xx
/h/i2c.h
- Nguồn C18:
installation_path
/v
x.xx
/src/pmc_common/i2c/
- Tiêu đề XC8:
installation_path
/v
x.xx
/include/plib/i2c.h
- Nguồn XC8:
installation_path
/v
x.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 OpenI2C
chứ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_mode
là:
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()
và 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
GOTO
hướ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:
- Master viết, byte cuối cùng là địa chỉ
- Master viết, byte cuối cùng là dữ liệu
- Thầy đọc, byte cuối cùng là địa chỉ
- Thầy đọc, byte cuối cùng là dữ liệu
- 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 SSPSTAT
ghi. 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 SSPSTAT
ghi (đầ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ụ.