lớp I2C -- giao thức nối tiếp hai dây

I2C là giao thức hai dây dùng để giao tiếp giữa các thiết bị. Ở cấp độ vật lý, nó gồm 2 dây: SCL và SDA, lần lượt là đường xung nhịp và đường dữ liệu.

Các đối tượng I2C được tạo ra gắn với một bus cụ thể. Chúng có thể được khởi tạo khi tạo hoặc khởi tạo sau.

In đối tượng I2C sẽ cho bạn biết thông tin về cấu hình của nó.

Cả hai triển khai I2C phần cứng và phần mềm đều tồn tại thông qua các lớp I2CSoftI2C. I2C phần cứng sử dụng hỗ trợ phần cứng cơ bản của hệ thống để thực hiện các thao tác đọc/ghi và thường hiệu quả và nhanh nhưng có thể có hạn chế về các chân (pin) có thể sử dụng. I2C phần mềm được triển khai bằng cách bit-banging và có thể sử dụng trên bất kỳ chân (pin) nào nhưng không hiệu quả bằng. Các lớp này có cùng các phương thức và khác nhau chủ yếu ở cách chúng được xây dựng.

Ghi chú

Bus I2C yêu cầu mạch kéo lên (pull-up) trên cả SDA và SCL để hoạt động. Thông thường đây là các điện trở trong khoảng 1 - 10 kOhm, kết nối từ mỗi SDA/SCL đến Vcc. Nếu không có các điện trở này, hành vi là không xác định và có thể bao gồm từ treo máy, reset watchdog bất ngờ cho đến giá trị sai. Thường mạch kéo lên này đã được tích hợp sẵn trong bo mạch MCU hoặc bo sensor, nhưng không có quy tắc cứng nhắc về điều đó. Vì vậy hãy kiểm tra khi gặp sự cố. Xem thêm hướng dẫn học tập tuyệt vời này của Adafruit về cách đấu dây I2C.

Ví dụ sử dụng:

from machine import I2C

i2c = I2C(freq=400000)          # create I2C peripheral at frequency of 400kHz
                                # depending on the port, extra parameters may be required
                                # to select the peripheral and/or pins to use

i2c.scan()                      # scan for peripherals, returning a list of 7-bit addresses

i2c.writeto(42, b'123')         # write 3 bytes to peripheral with 7-bit address 42
i2c.readfrom(42, 4)             # read 4 bytes from peripheral with 7-bit address 42

i2c.readfrom_mem(42, 8, 3)      # read 3 bytes from memory of peripheral 42,
                                #   starting at memory-address 8 in the peripheral
i2c.writeto_mem(42, 2, b'\x10') # write 1 byte to memory of peripheral 42
                                #   starting at address 2 in the peripheral

Các hàm khởi tạo

class machine.I2C(id: int, *, scl: Pin | None = None, sda: Pin | None = None, freq: int = 400000, timeout: int = 50000)

Xây dựng và trả về một đối tượng I2C mới sử dụng các tham số sau:

  • id xác định một ngoại vi I2C cụ thể. Các giá trị cho phép phụ thuộc vào cổng/bo mạch cụ thể

  • scl phải là một đối tượng chân (pin) chỉ định chân sử dụng cho SCL.

  • sda phải là một đối tượng chân (pin) chỉ định chân sử dụng cho SDA.

  • freq phải là một số nguyên thiết lập tần số tối đa cho SCL.

  • timeout là thời gian tối đa tính bằng micro giây cho phép các giao dịch I2C. Tham số này không được phép trên một số cổng.

Lưu ý rằng một số cổng/bo mạch sẽ có giá trị mặc định của sclsda có thể thay đổi trong hàm khởi tạo này. Các cổng/bo mạch khác sẽ có giá trị cố định của sclsda không thể thay đổi.

Các phương thức chung

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

Khởi tạo bus I2C với các đối số đã cho:

  • scl là đối tượng chân (pin) cho đường SCL

  • sda là đối tượng chân (pin) cho đường SDA

  • freq là tốc độ xung nhịp SCL

Trong trường hợp I2C phần cứng, tần số xung nhịp thực tế có thể thấp hơn tần số yêu cầu. Điều này phụ thuộc vào phần cứng của nền tảng. Tốc độ thực tế có thể xác định bằng cách in đối tượng I2C.

scan() List[int]

Quét tất cả địa chỉ I2C từ 0x08 đến 0x77 (bao gồm cả hai đầu) và trả về danh sách các địa chỉ đáp ứng. Một thiết bị đáp ứng nếu nó kéo đường SDA xuống thấp sau khi địa chỉ của nó (bao gồm bit ghi) được gửi trên bus.

Các thao tác I2C nguyên thủy

Các phương thức sau đây triển khai các thao tác bus điều khiển I2C nguyên thủy và có thể kết hợp để tạo bất kỳ giao dịch I2C nào. Chúng được cung cấp nếu bạn cần kiểm soát nhiều hơn đối với bus, nếu không thì có thể sử dụng các phương thức tiêu chuẩn (xem bên dưới).

Các phương thức này chỉ khả dụng trên lớp SoftI2C.

start() None

Tạo điều kiện START trên bus (SDA chuyển sang mức thấp trong khi SCL ở mức cao).

stop() None

Tạo điều kiện STOP trên bus (SDA chuyển sang mức cao trong khi SCL ở mức cao).

readinto(buf: bytearray, nack: bool = True, /) None

Đọc các byte từ bus và lưu chúng vào buf. Số byte đọc là độ dài của buf. Một ACK sẽ được gửi trên bus sau khi nhận tất cả các byte trừ byte cuối cùng. Sau khi nhận byte cuối cùng, nếu nack là true thì một NACK sẽ được gửi, ngược lại một ACK sẽ được gửi (và trong trường hợp này thiết bị ngoại vi giả định rằng sẽ có thêm byte được đọc trong lần gọi sau).

write(buf: bytes) int

Ghi các byte từ buf lên bus. Kiểm tra rằng ACK được nhận sau mỗi byte và dừng truyền các byte còn lại nếu nhận được NACK. Hàm trả về số lượng ACK đã nhận được.

Các thao tác bus tiêu chuẩn

Các phương thức sau đây triển khai các thao tác đọc và ghi điều khiển I2C tiêu chuẩn nhắm vào một thiết bị ngoại vi cụ thể.

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

Đọc nbytes từ thiết bị ngoại vi được chỉ định bởi addr. Nếu stop là true thì điều kiện STOP được tạo ra ở cuối quá trình truyền. Trả về đối tượng bytes với dữ liệu đã đọc.

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

Đọc vào buf từ thiết bị ngoại vi được chỉ định bởi addr. Số byte đọc sẽ là độ dài của buf. Nếu stop là true thì điều kiện STOP được tạo ra ở cuối quá trình truyền.

Phương thức trả về None.

writeto(addr: int, buf: bytes, stop: bool = True, /) int

Ghi các byte từ buf đến thiết bị ngoại vi được chỉ định bởi addr. Nếu nhận được NACK sau khi ghi một byte từ buf thì các byte còn lại sẽ không được gửi. Nếu stop là true thì điều kiện STOP được tạo ra ở cuối quá trình truyền, ngay cả khi nhận được NACK. Hàm trả về số lượng ACK đã nhận được.

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

Ghi các byte trong vector đến thiết bị ngoại vi được chỉ định bởi addr. vector phải là một tuple hoặc danh sách các đối tượng có giao thức bộ đệm. addr được gửi một lần và sau đó các byte từ mỗi đối tượng trong vector được ghi ra tuần tự. Các đối tượng trong vector có thể có độ dài bằng không, trong trường hợp đó chúng không đóng góp vào đầu ra.

Nếu nhận được NACK sau khi ghi một byte từ một trong các đối tượng trong vector thì các byte còn lại và bất kỳ đối tượng còn lại nào đều không được gửi. Nếu stop là true thì điều kiện STOP được tạo ra ở cuối quá trình truyền, ngay cả khi nhận được NACK. Hàm trả về số lượng ACK đã nhận được.

Các thao tác bộ nhớ

Một số thiết bị I2C hoạt động như thiết bị bộ nhớ (hoặc tập hợp các thanh ghi) có thể đọc và ghi. Trong trường hợp này có hai địa chỉ liên quan đến giao dịch I2C: địa chỉ ngoại vi và địa chỉ bộ nhớ. Các phương thức sau đây là các hàm tiện ích để giao tiếp với các thiết bị như vậy.

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

Đọc nbytes từ thiết bị ngoại vi được chỉ định bởi addr bắt đầu từ địa chỉ bộ nhớ được chỉ định bởi memaddr. Đối số addrsize chỉ định kích thước địa chỉ theo bit. Trả về đối tượng bytes với dữ liệu đã đọc.

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

Đọc vào buf từ thiết bị ngoại vi được chỉ định bởi addr bắt đầu từ địa chỉ bộ nhớ được chỉ định bởi memaddr. Số byte đọc là độ dài của buf. Đối số addrsize chỉ định kích thước địa chỉ theo bit.

Phương thức trả về None.

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

Ghi buf đến thiết bị ngoại vi được chỉ định bởi addr bắt đầu từ địa chỉ bộ nhớ được chỉ định bởi memaddr. Đối số addrsize chỉ định kích thước địa chỉ theo bit.

Phương thức trả về None.

lớp SoftI2C -- bus I2C mô phỏng bằng phần mềm

Lớp SoftI2C triển khai I2C bằng cách bit-banging các chân GPIO tùy ý. Nó cung cấp cùng giao diện phương thức như I2C cộng thêm các thao tác bus nguyên thủy cấp thấp (start(), stop(), readinto(), write()) cho các lập trình viên cần tạo các giao dịch không chuẩn. Sử dụng khi các chân (pin) bạn cần không được kết nối với khối I2C phần cứng, khi bạn cần nhiều bus hơn phần cứng cung cấp, hoặc để giao tiếp với các thiết bị yêu cầu các trình tự đặc biệt (xung nhịp phụ, lặp lại START sau khi ghi, v.v.).

Các hàm khởi tạo

class machine.SoftI2C(scl: Pin, sda: Pin, *, freq: int = 400000, timeout: int = 50000)

Xây dựng bus I2C phần mềm được điều khiển bởi scl / sda.

freq là tốc độ xung nhịp SCL mục tiêu tính bằng Hz (tốc độ thực tế thường thấp hơn do chi phí vòng lặp bit-bang).

timeout là thời gian tối đa tính bằng micro giây để chờ clock stretching (SCL bị kéo xuống thấp bởi thiết bị khác trên bus); khi hết thời gian sẽ xuất hiện ngoại lệ OSError(ETIMEDOUT).

Các phương thức chung

init(scl: Pin, sda: Pin, *, freq: int = 400000) None

Khởi tạo lại bus I2C phần mềm với các chân và tần số đã cho. Tương đương với việc xây dựng một SoftI2C mới trên cùng đối tượng.

scan() List[int]

Quét tất cả địa chỉ I2C từ 0x08 đến 0x77 (bao gồm cả hai đầu) và trả về danh sách các địa chỉ đã đáp ứng.

Các thao tác I2C nguyên thủy

Các phương thức sau đây triển khai các thao tác bus điều khiển I2C nguyên thủy và có thể kết hợp để tạo bất kỳ giao dịch I2C nào. Chúng chỉ dành cho SoftI2C -- lớp I2C phần cứng không cung cấp chúng.

start() None

Tạo điều kiện START trên bus (SDA chuyển sang mức thấp trong khi SCL ở mức cao).

stop() None

Tạo điều kiện STOP trên bus (SDA chuyển sang mức cao trong khi SCL ở mức cao).

readinto(buf: bytearray, nack: bool = True, /) None

Đọc các byte từ bus vào buf. Đọc len(buf) byte; ACK được gửi sau mỗi byte trừ byte cuối cùng. Sau byte cuối cùng, nack=True (mặc định) gửi NACK để kết thúc truyền; nack=False gửi ACK để thiết bị vẫn được chọn cho readinto() tiếp theo.

write(buf: bytes) int

Ghi buf lên bus, kiểm tra ACK sau mỗi byte. Truyền dừng ở NACK đầu tiên. Trả về số lượng ACK đã nhận được.

Các thao tác bus tiêu chuẩn

Các phương thức sau đây triển khai các thao tác đọc và ghi điều khiển I2C tiêu chuẩn nhắm vào một thiết bị ngoại vi cụ thể.

readfrom(addr: int, nbytes: int, stop: bool = True, /) bytes

Đọc nbytes từ thiết bị tại địa chỉ 7-bit addr. Nếu stop là true thì điều kiện STOP được tạo ra ở cuối quá trình truyền.

readfrom_into(addr: int, buf: bytearray, stop: bool = True, /) None

Đọc len(buf) byte từ thiết bị tại addr vào buf. Nếu stop là true thì điều kiện STOP được tạo ra ở cuối quá trình truyền.

writeto(addr: int, buf: bytes, stop: bool = True, /) int

Ghi buf đến thiết bị tại addr. Truyền dừng ở NACK đầu tiên. Nếu stop là true, điều kiện STOP luôn được tạo ra ở cuối quá trình truyền (ngay cả khi NACK sớm). Trả về số lượng ACK đã nhận được.

writevto(addr: int, vector: tuple | list, stop: bool = True, /) int

Ghi phần nối tiếp của các bộ đệm trong vector đến thiết bị tại addr như một giao dịch đơn. Các bộ đệm rỗng được bỏ qua. Hoạt động giống writeto() về ngữ nghĩa stop và giá trị trả về.

Các thao tác bộ nhớ

Một số thiết bị I2C hoạt động như thiết bị bộ nhớ (hoặc tập hợp các thanh ghi) có thể đọc và ghi. Trong trường hợp này có hai địa chỉ liên quan đến giao dịch I2C: địa chỉ ngoại vi và địa chỉ bộ nhớ. Các phương thức sau đây là các hàm trợ giúp tiện ích để giao tiếp với các thiết bị như vậy.

readfrom_mem(addr: int, memaddr: int, nbytes: int, *, addrsize: int = 8) bytes

Đọc nbytes từ thiết bị tại addr bắt đầu từ thanh ghi memaddr. addrsize là chiều rộng địa chỉ thanh ghi theo bit (thường là 8 hoặc 16).

readfrom_mem_into(addr: int, memaddr: int, buf: bytearray, *, addrsize: int = 8) None

Đọc vào buf từ thiết bị tại addr bắt đầu từ thanh ghi memaddr.

writeto_mem(addr: int, memaddr: int, buf: bytes, *, addrsize: int = 8) None

Ghi buf đến thiết bị tại addr bắt đầu từ thanh ghi memaddr.