STM32, giao diện I2C nối tiếp. STM32, giao diện nối tiếp I2C Stm32 tín hiệu i2c ack và nack

  • 18.03.2024

Gần đây tôi ngày càng gặp phải những đánh giá tiêu cực về lốp xe. I2C Tại STM32, họ nói làm việc với cô ấy có nghĩa là nhảy với tambourine, v.v.
Trong tháng qua, tôi đã cố gắng khởi chạy hai vi mạch chạy trên I2C và không khiêu vũ, chỉ cần đọc kỹ bảng dữ liệu.

mô-đun I2C Tại STM32 có các tính năng sau:

  • có thể làm việc ở hai chế độ Fm(chế độ nhanh) và Sm(chế độ tiêu chuẩn), chế độ đầu tiên hoạt động ở tần số lên tới 400KHz, chế độ thứ hai lên tới 100KHz
  • Bộ đệm 1 byte có hỗ trợ DMA
  • hỗ trợ tính toán tổng kiểm tra phần cứng
  • trên cơ sở đó có thể thực hiện được SMBus(Bus quản lý hệ thống) và PMBus(Bus quản lý nguồn)
  • hai vectơ ngắt, được tạo khi truyền thành công và khi xảy ra lỗi
  • bộ lọc tiếng ồn
  • có thể hoạt động ở chế độ Master hoặc Slave
Ở chế độ Chính:
  • tạo ra tín hiệu định thời
  • tạo BẮT ĐẦU và DỪNG
Ở chế độ nô lệ:
  • bạn có thể lập trình các địa chỉ chính và thay thế mà nó sẽ phản hồi
  • xác định DỪNG

Theo mặc định mô-đun ở chế độ Nô lệ, nhưng nó sẽ tự động chuyển sang chế độ Bậc thầy sau khi tạo trạng thái BẮT ĐẦU.
Sự khác biệt cơ bản giữa Master và Slave là Bậc thầy tạo ra tín hiệu đồng hồ và luôn bắt đầu và kết thúc quá trình truyền dữ liệu. Nô lệ như nhau, trả lời địa chỉ của nó và phát sóng và phản hồi tới địa chỉ quảng bá có thể bị vô hiệu hóa. Cũng Nô lệ tạo ra trạng thái ACK, nhưng nó cũng có thể bị vô hiệu hóa.

Việc giải thích chi tiết như vậy là cần thiết vì ở cả hai chế độ, thiết bị có thể hoạt động như cả máy phát và máy thu.

  • Máy phát nô lệ
  • Người nhận nô lệ
  • Máy phát chính
  • Bộ thu chính

Cấu trúc của mô-đun I2C được hiển thị bên dưới.

Thanh ghi điều khiển I2C_CR1:

SWRST(Đặt lại phần mềm) - một đơn vị trong bit này đặt lại giá trị của tất cả các thanh ghi của mô-đun về trạng thái mặc định; nó có thể được sử dụng để đặt lại khi xảy ra lỗi.

BÁO ĐỘNG(Cảnh báo SMBus) - đặt bit này thành 1 cho phép tạo tín hiệu báo động trong chế độ SMBus.

PEC(Kiểm tra lỗi gói) - bit này được điều khiển bằng phần mềm, nhưng nó có thể được đặt lại bằng phần cứng khi PEC, START, STOP hoặc PE=0 được truyền đi. Một bit trong bit này cho phép truyền CRC.

POS(Xác nhận/Vị trí PEC (để nhận dữ liệu)) - trạng thái của bit này xác định vị trí ACK/PEC trong cấu hình hai byte ở chế độ Chính.

ACK(Kích hoạt xác nhận) - một đơn vị trong bit này cho phép gửi ACK/NACK sau khi nhận được một địa chỉ hoặc byte dữ liệu.

DỪNG LẠI(Dừng tạo) - đặt bit này thành 1 sẽ tạo ra tín hiệu DỪNG LẠIở chế độ Chính.

BẮT ĐẦU(Bắt đầu tạo) - đặt bit này thành một sẽ tạo ra trạng thái BẮT ĐẦUở chế độ Chính,

lỗ mũi(Tắt kéo dài đồng hồ (Chế độ phụ)) - nếu quá trình xử lý dữ liệu mất thời gian Nô lệ có thể dừng việc truyền tải chính bằng cách nhấn vào đường dây SCL nối đất, Master sẽ đợi và không gửi bất cứ thứ gì cho đến khi đường dây được giải phóng. Số 0 trong bit này được nhấn SCL xuống đất.

tiếng anh(Bật cuộc gọi chung) - nếu bit này được đặt thành một, mô-đun sẽ phản hồi ACK om đến địa chỉ quảng bá 0x00.

ENPEC(Bật PEC) - đặt bit này thành 1 cho phép đếm phần cứng CRC.

ENARP(Bật ARP) - đặt bit này thành một sẽ bật ARP.

SMBTYPE(Loại SMBus) - nếu bit này được đặt thành 0, mô-đun sẽ hoạt động ở chế độ Phụ, nếu một mô-đun ở chế độ Chính.

SMBUS(Chế độ SMBus) - nếu bit này được đặt thành 0, mô-đun sẽ hoạt động ở chế độ I2C, nếu một SMBus.

THỂ DỤC.(Kích hoạt ngoại vi) - một đơn vị trong bit này kích hoạt mô-đun.

Thanh ghi điều khiển I2C_CR2:

CUỐI CÙNG(lần truyền cuối cùng của DMA) - một trong bit này cho phép DMA tạo tín hiệu kết thúc truyền EOT(Kết thúc chuyển giao).

DMAEN(Bật yêu cầu DMA) - một đơn vị trong bit này cho phép đưa ra yêu cầu DMA khi đặt cờ TxE hoặc RxNE.

ITBUFEN(Bật ngắt bộ đệm) - nếu bit này trống, tất cả các ngắt đều được bật ngoại trừ các ngắt nhận và truyền.

ITEVTEN(Cho phép ngắt sự kiện) - một bit trong bit này cho phép ngắt sự kiện.

ITERREN(Cho phép ngắt lỗi) - một bit trong bit này cho phép ngắt khi xảy ra lỗi.

tần số(Tần số xung nhịp ngoại vi) - tần số xung nhịp của mô-đun phải được ghi vào trường bit này; nó có thể nhận giá trị từ 2 đến 50.

Đăng ký I2C_OAR1:

THÊM(Chế độ địa chỉ) - bit này xác định kích thước của địa chỉ Slave, số 0 tương ứng với kích thước địa chỉ 7 bit, một - 10 bit.

THÊM VÀO(Địa chỉ giao diện) - bit quan trọng nhất của địa chỉ, nếu địa chỉ là 10 bit.

THÊM VÀO(Địa chỉ giao diện) - địa chỉ thiết bị.

THÊM0(Địa chỉ giao diện) - bit có ý nghĩa nhỏ nhất của địa chỉ, nếu địa chỉ là 10 bit..

Đăng ký I2C_OAR2:

THÊM2- một địa chỉ thay thế mà Slave sẽ phản hồi.

CUỐI CÙNG(Bật chế độ địa chỉ kép) - một bit trong bit này cho phép Slave phản hồi với một địa chỉ thay thế ở chế độ 7 bit.

I2C_DR- thanh ghi dữ liệu, để gửi dữ liệu chúng ta ghi vào thanh ghi DR, để tiếp nhận chúng tôi đọc nó.

Thanh ghi trạng thái I2C_SR1:

SMBALERT(Cảnh báo SMBus) - xảy ra trong trường hợp có cảnh báo trên xe buýt SMBus.

HẾT GIỜ(Lỗi hết thời gian chờ hoặc Tlow) - xảy ra nếu dòng SCLép xuống đất. Vì bậc thầy 10ms, cho nô lệ 25mS.

PECERR(Lỗi PEC khi tiếp nhận) - xảy ra khi có lỗi PEC khi nhập học.

OVR(Tràn/Chạy thiếu) - xảy ra khi dữ liệu tràn.

A.F.(Xác nhận lỗi) - được đặt khi nhận được tín hiệu NACK. Để đặt lại, bạn cần viết 0.

ARLO(Mất trọng tài (chế độ chính)) - được đặt khi mất trọng tài. Để đặt lại, bạn cần viết 0.

BERR(Lỗi xe buýt) - lỗi xe buýt. Đặt khi có tín hiệu xảy ra BẮT ĐẦU hoặc DỪNG LẠI không đúng lúc.

TxE(Thanh ghi dữ liệu trống (máy phát)) - được đặt khi thanh ghi DR trống, hay đúng hơn là khi dữ liệu từ nó đã được chuyển sang thanh ghi thay đổi.

RxNE(Thanh ghi dữ liệu không trống (bộ thu)) - được đặt khi nhận một byte dữ liệu không phải là địa chỉ.

DỪNG(Phát hiện dừng (chế độ phụ)) - khi hoạt động ở chế độ nô lệđặt khi phát hiện tín hiệu DỪNG LẠI, nếu có tín hiệu ACK trước đó. Để thiết lập lại bạn phải đọc SR1 và ghi vào CR1.

THÊM10(Đã gửi tiêu đề 10 bit (Chế độ chính)) - được đặt khi gửi byte đầu tiên của địa chỉ 10 bit.

BTF(Đã hoàn tất quá trình truyền byte) - cờ được đặt khi byte được nhận/truyền; nó chỉ hoạt động khi lỗ mũi bằng không.

ĐỊA CHỈ(Địa chỉ đã gửi (chế độ chính)/khớp (chế độ phụ)) - ở chế độ bậc thầyđược đặt sau khi chuyển địa chỉ, ở chế độ nô lệđược đặt khi địa chỉ khớp. Để thiết lập lại, bạn cần đọc thanh ghi SR1 và sau đó là SR2.

S.B.(Bắt đầu bit (Chế độ chính)) - được đặt khi tín hiệu BẮT ĐẦU xảy ra. Để đặt lại cờ bạn phải đọc SR1 và ghi dữ liệu vào sổ đăng ký DR .

Thanh ghi trạng thái I2C_SR2:

PEC(Thanh ghi kiểm tra lỗi gói) - tổng kiểm tra khung được ghi vào trường bit này.

KÉP(Cờ kép (Chế độ nô lệ)) - số 0 trong bit này cho biết địa chỉ mà Slave nhận được tương ứng OAR1, nếu không thì OAR2.

SMBHOST(Tiêu đề máy chủ SMBus (chế độ phụ)) - được đặt khi nhận được tiêu đề Máy chủ SMBus.

lỗi lầm(Địa chỉ mặc định của thiết bị SMBus (Chế độ phụ)) - đặt nếu địa chỉ mặc định được chấp nhận
SMBus-thiết bị.

GỌI TỔNG HỢP(Địa chỉ cuộc gọi chung (Chế độ phụ)) - đặt nếu nhận được địa chỉ quảng bá ở chế độ phụ.

TRA(Bộ phát/bộ thu) - một giá trị trong bit này cho biết mô-đun hoạt động như một bộ phát, nếu không thì là bộ thu.

BẬN(Xe buýt bận) - cờ bận.

MSL(Master/Slave) - một giá trị trong bit này cho biết mô-đun hoạt động ở chế độ Master, nếu không thì là Slave.

Thanh ghi điều khiển tần số I2C_CCR:

F/S(Lựa chọn chế độ chính I2C) - khi bit này được đặt thành 1, mô-đun hoạt động ở chế độ NHANH, nếu không thì TIÊU CHUẨN.

NHIỆM VỤ(Chu kỳ nhiệm vụ chế độ Fm) - bit này thiết lập chu kỳ nhiệm vụ tín hiệu SCL trong chế độ NHANH. Nếu số 0 được đặt tlow/đùi = 2, nếu không thì tlow/đùi = 16/9.

CCR(Thanh ghi điều khiển đồng hồ ở chế độ Fm/Sm (Chế độ chính)) - khi hoạt động ở chế độ Chính, đặt tần số xung nhịp của đường SCL.

Chế độ Sm hoặc SMBus:
Đùi = CCR * TPCLK1
Tlow = CCR * TPCLK1

Chế độ FM:
Nếu NHIỆM VỤ = 0:
Đùi = CCR * TPCLK1
Tlow = 2 * CCR * TPCLK1

Nếu DUTY = 1: (đạt 400 kHz)
Đùi = 9 * CCR * TPCLK1
Tlow = 16 * CCR * TPCLK1

Chúng tôi nhận được cho chế độ S.M. tiếp theo:
CCR * TPCLK1 + CCR * TPCLK1 = 10.000ns
CCR = 10.000/(2* TPCLK1)

Đăng ký I2C_TRISE:

TRISE- xác định thời gian tăng của mặt trước. Tính bằng công thức (Tr max/TPCLK1)+1,
Ở đâu Tr maxS.M. lên tới 1000nS, va cho FM 300nS,
MỘT TPCLK1- kỳ được tính là 1/ F(APB1).

Thanh ghi điều khiển bộ lọc I2C_FLTR:

ANOFF(Bộ lọc nhiễu tương tự TẮT) - số 0 trong bit này sẽ bật bộ lọc tương tự.

DNF(Bộ lọc nhiễu kỹ thuật số) - trường bit để thiết lập bộ lọc kỹ thuật số. Để biết chi tiết, vui lòng tham khảo tài liệu.

Khởi tạo một mô-đun từ một dự án đang hoạt động.
void I2C2_Init(void) ( /* SDL -> PB10 SDA -> PB11 RST -> PE15 */ // bật xung nhịp của các cổng và mô-đun I2C RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN | RCC_AHB1ENR_GPIOEEN; RCC->APB1ENR |= RCC_APB1ENR_I2C2EN; / / chức năng thay thế, đầu ra thoát nước mở, GPIOB->AFR 2 MHz |= (0x04<<2*4); GPIOB->AFR |= (0x04<<3*4); GPIOB->MODER |= GPIO_MODER_MODER10_1; GPIOB->OTYPER |= GPIO_OTYPER_OT_10; GPIOB->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR10; GPIOB->MODER |= GPIO_MODER_MODER11_1; GPIOB->OTYPER |= GPIO_OTYPER_OT_11; GPIOB->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR11; // Đầu ra kéo đẩy PE15 50 MHz GPIOE->MODER |= GPIO_MODER_MODER15_0; GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR15; AU_RST_HIGH // đặt mô-đun ở chế độ I2C I2C2->CR1 &= ~2C_CR1_SMBUS; // chỉ định tần số xung nhịp của mô-đun I2C2->CR2 &= ~I2C_CR2_FREQ; I2C2->CR2 |= 42; // Fclk1=168/4=42MHz // định cấu hình I2C, chế độ tiêu chuẩn, chu kỳ nhiệm vụ 100 KHz 1/2 I2C2->CCR &= ~(I2C_CCR_FS | I2C_CCR_DUTY); //đặt tần số hoạt động của mô-đun SCL bằng công thức 10.000nS/(2* TPCLK1) I2C2->CCR |= 208; //10 000ns/48ns = 208 //Standart_Mode = 1000nS, Fast_Mode = 300nS, 1/42MHz = 24nS I2C2->TRISE = 42; //(1000nS/24nS)+1 //bật mô-đun I2C2->CR1 |= I2C_CR1_PE; ) void I2C_Write(uint8_t reg_addr, uint8_t data) ( //start I2C2->CR1 |= I2C_CR1_START; while(!(I2C2->SR1 & I2C_SR1_SB))(); (void) I2C2->SR1; // vượt qua thiết bị địa chỉ I2C2->DR = I2C_ADDRESS(ADDR,I2C_MODE_WRITE); while(!(I2C2->SR1 & I2C_SR1_ADDR))(); (void) I2C2->SR1 (void) I2C2->SR2; (!(I2C2->SR1 & I2C_SR1_TXE))(); //ghi dữ liệu I2C2->DR = dữ liệu; while(!(I2C2->SR1 & I2C_SR1_BTF))(); ) ( dữ liệu uint8_t; //bắt đầu I2C2->CR1 |= I2C_CR1_START; while(!(I2C2->SR1 & I2C_SR1_SB))(); (void) I2C2->SR1; / /truyền địa chỉ thiết bị I2C2->DR = I2C_ADDRESS (ADR,I2C_MODE_WRITE); while(!(I2C2->SR1 & I2C_SR1_ADDR))(); (void) I2C2->SR1; (void) I2C2->SR2; địa chỉ đăng ký I2C2->DR = reg_addr; I2C2->SR1 & I2C_SR1_TXE))(); I2C2->CR1 |= I2C_CR1_STOP; //khởi động lại!!! I2C2->SR1 & I2C_SR1_SB))(); // truyền địa chỉ thiết bị, nhưng bây giờ để đọc I2C2->DR = I2C_ADDRESS(ADR,I2C_MODE_READ); while(!(I2C2->SR1 & I2C_SR1_ADDR))(); (void) I2C2->SR1; (void) I2C2->SR2; //đọc I2C2->CR1 &= ~I2C_CR1_ACK; while(!(I2C2->SR1 & I2C_SR1_RXNE))(); dữ liệu = I2C2->DR; I2C2->CR1 |= I2C_CR1_STOP; trả về dữ liệu; )

Một số người thích bánh nướng, một số thì không.

Giao diện i2c được phổ biến và sử dụng rộng rãi. Trong stm32f4 có tới ba mô-đun thực hiện giao thức này.
Đương nhiên, với sự hỗ trợ đầy đủ cho toàn bộ vấn đề này.

Nói chung, làm việc với mô-đun cũng giống như trong các bộ điều khiển khác: bạn đưa ra các lệnh cho nó, nó thực thi chúng và báo cáo kết quả:
Tôi> Chúng tôi đã BẮT ĐẦU.
S> Được rồi, tôi đã gửi nó.
Tôi> Tuyệt, gửi địa chỉ ngay. Như thế này: 0xXX.
S> Được rồi, tôi đã gửi nó. Tôi đã được thông báo rằng ACK. Tiếp tục nào.
Tôi> Vẫn còn sống, tốt. Đây là số đăng ký: 0xYY, - đi thôi.
S> Đã gửi, đã nhận ACK.
Tôi> Bây giờ hãy gửi dữ liệu cho anh ấy, đây là byte: 0xZZ.
S> Đã gửi thì anh đồng ý thêm: ACK.
Tôi> Địt anh ta, chưa đâu. Họ đã DỪNG LẠI.
S> Được rồi.

Và mọi thứ đều xấp xỉ theo tinh thần này.

Trong bộ điều khiển này, các chân i2c được phân bổ giữa các cổng như sau:
PB6: I2C1_SCL
PB7: I2C1_SDA

PB8: I2C1_SCL
PB9: I2C1_SDA

PB10: I2C2_SCL
PB11: I2C2_SDA

PA8: I2C3_SCL
PC9: I2C3_SDA
Nói chung, thật thuận tiện khi xem sơ đồ chân của các thiết bị ngoại vi ở trang 59.

Đáng ngạc nhiên là để làm việc với i2c, bạn cần tất cả các thanh ghi của nó, may mắn thay là có rất ít trong số đó:
I2C_CR1- các lệnh tới mô-đun để gửi lệnh/trạng thái và chọn chế độ vận hành;
I2C_CR2- thiết lập DMA và chỉ ra tần số hoạt động của mô-đun (2-42 MHz);
I2C_OAR1- cài đặt địa chỉ thiết bị (cho nô lệ), kích thước địa chỉ (7 hoặc 10 bit);
I2C_OAR2- cài đặt địa chỉ thiết bị (nếu có hai địa chỉ);
I2C_DR- đăng ký dữ liệu;
I2C_SR1- thanh ghi trạng thái mô-đun;
I2C_SR2- thanh ghi trạng thái (phụ, phải được đọc nếu cờ ADDR hoặc STOPF được đặt trong SR1);
I2C_CCR- thiết lập tốc độ giao diện;
I2C_TRISE- thiết lập thời gian của các cạnh.

Tuy nhiên, một nửa trong số họ thuộc loại “viết ra rồi quên đi”.

Bo mạch STM32F4-Discovery đã có thiết bị I2C mà bạn có thể thực hành: CS43L22, DAC âm thanh. Nó được kết nối với chân PB6/PB9. Điều chính là đừng quên áp mức cao cho chân PD4 (~RESET nằm ở đó), nếu không DAC sẽ không phản hồi.

Quy trình thiết lập xấp xỉ như sau:
1 . Cho phép bấm giờ các cổng và chính mô-đun.
Chúng ta cần các chân PB6/PB9, vì vậy chúng ta cần đặt bit 1 (GPIOBEN) trong thanh ghi RCC_AHB1ENR để kích hoạt cổng.
Và đặt bit 21 (I2C1EN) trong thanh ghi RCC_APB1ENR để kích hoạt mô-đun I2C. Đối với mô-đun thứ hai và thứ ba, số bit lần lượt là 22 và 23.
2 . Tiếp theo, các chân được cấu hình: Đầu ra Oped Drain (GPIO->OTYPER), chế độ chức năng thay thế (GPIO->MODER) và số chức năng thay thế (GPIO->AFR).
Nếu muốn, bạn có thể định cấu hình một bộ kéo lên (GPIO->PUPDR), nếu nó không có trên bo mạch (và cần phải kéo lên nguồn điện của cả hai đường dây dưới mọi hình thức). Số của I2C luôn giống nhau: 4. Thật tuyệt khi có một số riêng cho từng loại thiết bị ngoại vi.
3 . Tần số xung nhịp hiện tại của thiết bị ngoại vi Fpclk1 (tính bằng MHz) được chỉ định trong thanh ghi CR2. Theo tôi hiểu, điều này là cần thiết để tính toán thời gian giao thức khác nhau.
Nhân tiện, phải có ít nhất hai cái cho chế độ bình thường và ít nhất bốn cái cho chế độ nhanh. Và nếu bạn cần tốc độ tối đa 400 kHz thì nó cũng phải chia cho 10 (10, 20, 30, 40 MHz).
Tần số xung nhịp tối đa cho phép: 42 MHz.
4 . Tốc độ giao diện được cấu hình trong thanh ghi CCR và chế độ được chọn (bình thường/nhanh).
Ý nghĩa là: Tsck = CCR * 2 * Tpckl1, tức là khoảng thời gian SCK tỷ lệ thuận với CCR (đối với chế độ nhanh, mọi thứ phức tạp hơn một chút, nhưng nó được mô tả bằng RM).
5 . Thời gian cạnh tăng tối đa trong thanh ghi TRISE được điều chỉnh. Đối với chế độ tiêu chuẩn, thời gian này là 1 µs. Số chu kỳ bus phù hợp với thời gian này, cộng thêm một, phải được ghi vào sổ đăng ký:
nếu chu trình Tpclk1 kéo dài 125 ns thì viết (1000 ns / 125 ns) + 1 = 8 + 1 = 9.
6 . Việc tạo tín hiệu ngắt (lỗi, trạng thái và dữ liệu) được bật tùy chọn;
7 . Mô-đun bật: cờ PE trong thanh ghi CR1 được đặt thành 1.

Sau đó, mô-đun hoạt động như bình thường. Bạn chỉ cần thực hiện đúng thứ tự lệnh và kiểm tra kết quả. Ví dụ: một mục đăng ký:
1 . Trước tiên, bạn cần gửi BẮT ĐẦU bằng cách đặt cờ có cùng tên trong thanh ghi CR1. Nếu mọi thứ đều ổn thì sau một thời gian cờ SB sẽ được đặt trong thanh ghi SR1.
Tôi muốn lưu ý một điểm - nếu không có điểm kéo lên trên đường dây (và chúng ở mức 0), thì bạn hoàn toàn không thể chờ đợi lá cờ này.
2 . Nếu nhận được cờ thì chúng tôi sẽ gửi địa chỉ. Đối với địa chỉ 7 bit, chúng ta chỉ cần viết nó vào DR chính xác như trên dòng (7 bit địa chỉ + bit hướng). Đối với 10 bit, một thuật toán phức tạp hơn.
Nếu thiết bị phản hồi địa chỉ bằng ACK thì cờ ADDR sẽ xuất hiện trong thanh ghi SR1. Nếu không, cờ AF (Xác nhận lỗi) sẽ xuất hiện.
Nếu ADDR xuất hiện, bạn cần đọc thanh ghi SR2. Bạn không cần phải nhìn vào bất cứ thứ gì ở đó, chỉ cần đọc tuần tự SR1 và SR2 sẽ đặt lại cờ này. Và trong khi cờ được đặt, SCL được thiết bị chủ giữ ở mức thấp, điều này rất hữu ích nếu bạn cần yêu cầu thiết bị từ xa đợi trước khi gửi dữ liệu.
Nếu mọi thứ đều ổn thì mô-đun sẽ chuyển sang chế độ nhận hoặc truyền dữ liệu, tùy thuộc vào bit ít quan trọng nhất của địa chỉ đã gửi. Để viết nó phải bằng 0, để đọc nó phải bằng một.
nhưng chúng ta đang xem xét bản ghi, vì vậy chúng ta sẽ giả định rằng có số 0 ở đó.
3 . Tiếp theo, chúng tôi gửi địa chỉ đăng ký mà chúng tôi quan tâm. Theo cách tương tự, viết nó ra trong DR. Sau khi truyền, cờ TXE (bộ đệm truyền trống) và cờ BTF (hoàn tất truyền) được đặt.
4 . Tiếp theo là dữ liệu có thể được gửi trong khi thiết bị phản hồi bằng ACK. Nếu phản hồi là NACK, những cờ này sẽ không được đặt.
5 . Sau khi hoàn tất quá trình chuyển (hoặc trong trường hợp xảy ra tình trạng không mong muốn), chúng tôi gửi STOP: cờ cùng tên được đặt trong thanh ghi CR1.

Khi đọc thì mọi thứ đều giống nhau. Chỉ thay đổi sau khi ghi địa chỉ đăng ký.
Thay vì ghi dữ liệu, START được gửi lại (khởi động lại) và địa chỉ được gửi với tập bit có ý nghĩa nhỏ nhất (dấu đọc).
Mô-đun sẽ chờ dữ liệu từ thiết bị. Để khuyến khích nó gửi các byte tiếp theo, bạn cần đặt cờ ACK trong CR1 trước khi nhận (để sau khi nhận mô-đun sẽ gửi cùng ACK này).
Khi chán thì gỡ cờ ra, máy sẽ thấy NACK và im lặng. Sau đó, chúng tôi gửi STOP theo cách thông thường và vui mừng với dữ liệu nhận được.

Đây là điều tương tự ở dạng mã:
// Khởi tạo mô-đun void i2c_Init(void) ( uint32_t Clock = 16000000UL; // Tần số xung nhịp mô-đun (system_stm32f4xx.c không được sử dụng) uint32_t Speed ​​​​= 100000UL; // 100 kHz // Kích hoạt xung nhịp cổng GPIOB RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; // Thiết lập các chân PB6, PB9 // Mở cống! GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_9; // Kéo lên là bên ngoài, vì vậy nó không được định cấu hình ở đây! GPIOB->Thanh ghi PUPDR // Số của hàm GPIOB thay thế ->AFR &= ~(0x0FUL<< (6 * 4)); // 6 очистим GPIOB->AFR |= (0x04UL<< (6 * 4)); // В 6 запишем 4 GPIOB->AFR &= ~(0x0FUL<< ((9 - 8) * 4)); // 9 очистим GPIOB->AFR |= (0x04UL<< ((9 - 8) * 4)); // В 9 запишем 4 // Режим: альтернативная функция GPIOB->MODER &= ~((0x03UL<< (6 * 2)) | (0x03UL << (9 * 2))); // 6, 9 очистим GPIOB->MODER |= ((0x02UL<< (6 * 2)) | (0x02UL << (9 * 2))); // В 6, 9 запишем 2 // Включить тактирование модуля I2C1 RCC->APB1ENR |= RCC_APB1ENR_I2C1EN; // Tại thời điểm này, nên tắt I2C // Đặt lại mọi thứ (SWRST == 1, đặt lại) I2C1->CR1 = I2C_CR1_SWRST; // PE == 0, đây là cái chính I2C1->CR1 = 0; // Chúng tôi giả sử rằng chúng được khởi chạy từ RC (16 MHz) // Không có bộ đếm trước trong hệ thống đồng hồ (tất cả 1) // Một cách thân thiện, cần phải tính toán tất cả những điều này từ // tần số xung nhịp thực tế của mô-đun I2C1->CR2 = Đồng hồ / 1000000UL; // 16 MHz // Điều chỉnh tần số ( // Tclk = (1 / Fperiph); // Thigh = Tclk * CCR; // Tlow = Thigh; // Fi2c = 1 / CCR * 2; // CCR = Fperiph / ( Fi2c * 2); uint16_t Giá trị = (uint16_t)(Đồng hồ / (Tốc độ * 2)); // Giá trị tối thiểu: 4 if(Value< 4) Value = 4; I2C1->CCR = Giá trị; ) // Đặt thời gian tăng giới hạn // Ở chế độ tiêu chuẩn, thời gian này là 1000 ns // Chúng ta chỉ cần thêm một vào tần số được biểu thị bằng MHz (xem RM trang 604). I2C1->TRISE = (Đồng hồ / 1000000UL) + 1; // Kích hoạt mô-đun I2C1->CR1 |= (I2C_CR1_PE); // Bây giờ bạn có thể làm gì đó ) // Gửi một byte bool i2c_SendByte(uint8_t Địa chỉ, uint8_t Register, uint8_t Data) ( if(!i2c_SendStart()) return false; // Địa chỉ chip if(!i2c_SendAddress(Address)) return i2c_SendStop (); // Đăng ký địa chỉ if(!i2c_SendData(Register)) return i2c_SendStop(); // Dữ liệu if(!i2c_SendData(Data)) return i2c_SendStop(); // Dừng! Nhận byte bool i2c_ReceiveByte(uint8_t Địa chỉ, uint8_t Đăng ký , uint8_t * Data) ( if(!i2c_SendStart()) return false; // Địa chỉ chip if(!i2c_SendAddress(Address)) return i2c_SendStop(); // Đăng ký địa chỉ if(! i2c_SendData(Register)) return i2c_SendStop(); // Khởi động lại if(!i2c_SendStart()) return false; // Địa chỉ chip (đọc) if(!i2c_SendAddress(Address | 1)) return i2c_SendStop(); Stop! i2c_SendStop(); return true; (việc này phải được thực hiện bằng cách nào đó) // Gửi một byte đến thiết bị có địa chỉ 0x94, để đăng ký 0x00 với giá trị 0x00. i2c_SendByte(0x94, 0x00, 0x00); // Nhận một byte từ thiết bị có địa chỉ 0x94 từ thanh ghi 0x01 (ID) vào bộ đệm biến i2c_ReceiveByte(0x94, 0x01, &ID); )
Tất nhiên, bạn không thể làm điều này ngoại trừ trong một ví dụ huấn luyện. Thời gian chờ đợi để hoàn thành hành động là quá lâu đối với bộ điều khiển nhanh như vậy.

kselltrum Ngày 20 tháng 2 năm 2017 lúc 01:17

Những bước đầu tiên với trình biên dịch STM32 và mikroC cho kiến ​​trúc ARM - Phần 4 - Kết nối LCD dựa trên I2C, pcf8574 và HD4478

  • Lập trình vi điều khiển

Tôi muốn dành bài viết tiếp theo để làm việc với giao diện i2c phổ biến, giao diện này thường được sử dụng trong các vi mạch khác nhau được kết nối với vi điều khiển.

I2C là một bus hoạt động trên hai kết nối vật lý (ngoài dây chung). Có khá nhiều bài viết về nó trên Internet; có những bài viết hay trên Wikipedia. Ngoài ra, thuật toán vận hành bus được mô tả rất rõ ràng. Tóm lại, bus là bus đồng bộ hai dây. Có thể có tối đa 127 thiết bị trên bus cùng lúc (địa chỉ thiết bị là 7 bit, chúng ta sẽ quay lại vấn đề này sau). Dưới đây là sơ đồ điển hình để kết nối các thiết bị với bus i2c, với MK là thiết bị chính.


Đối với i2c, tất cả các thiết bị (cả chính và phụ) đều sử dụng đầu ra cống mở. Nói một cách đơn giản, họ CHỈ có thể hút lốp xe xuống ĐẤT. Mức bus cao được đảm bảo bằng điện trở kéo lên. Giá trị của các điện trở này thường được chọn trong khoảng từ 4,7 đến 10 kOhm. i2c khá nhạy cảm với các đường dây vật lý kết nối các thiết bị, vì vậy nếu sử dụng kết nối có điện dung lớn (ví dụ: cáp dài mỏng hoặc được che chắn), ảnh hưởng của điện dung này có thể “làm mờ” các cạnh của tín hiệu và gây nhiễu hoạt động bình thường của xe buýt. Điện trở kéo lên càng nhỏ thì điện dung này càng ít ảnh hưởng đến đặc tính của các cạnh tín hiệu, nhưng TẢI LỚN hơn trên các bóng bán dẫn đầu ra trên giao diện i2c. Giá trị của các điện trở này được chọn cho từng cách thực hiện cụ thể, nhưng chúng không được nhỏ hơn 2,2 kOhms, nếu không bạn có thể chỉ cần đốt các bóng bán dẫn đầu ra trong các thiết bị hoạt động với bus.

Bus bao gồm hai dòng: SDA (dòng dữ liệu) và SCL (tín hiệu đồng hồ). Đồng hồ xe buýt Thiết bị chủ, thường là MK của chúng tôi. Khi SCL ở mức cao, thông tin sẽ được đọc từ bus dữ liệu. Trạng thái SDA chỉ có thể được thay đổi khi tín hiệu đồng hồ ở mức thấp.. Khi mức SCL cao, tín hiệu trên SDA sẽ thay đổi khi tạo tín hiệu BẮT ĐẦU (khi SCL ở mức cao, tín hiệu trên SDA thay đổi từ cao xuống thấp) và DỪNG LẠI - khi mức SCL cao, tín hiệu trên SDA thay đổi từ thấp sang cao).

Riêng biệt, cần phải nói rằng trong i2c, địa chỉ được chỉ định là số 7 bit. 8 - bit có ý nghĩa nhỏ nhất cho biết hướng truyền dữ liệu 0 - nghĩa là Slave sẽ truyền dữ liệu, 1 - nhận.. Tóm lại, thuật toán làm việc với i2c như sau:

  • Mức độ cao trên SDA và SCL- xe buýt miễn phí, bạn có thể bắt đầu làm việc
  • Thang máy chính SCL lên 1 và thay đổi trạng thái S.D.A. từ 1 đến 0 - hút nó xuống đất - tín hiệu được hình thành BẮT ĐẦU
  • Master truyền địa chỉ Slave 7-bit với một bit định hướng (dữ liệu trên S.D.A.được trưng bày khi SCL kéo xuống đất và được nô lệ đọc khi nó được thả ra). Nếu nô lệ không có thời gian để “chộp lấy” bit trước đó, nó sẽ thu hút SCL xuống đất, nói rõ với chủ rằng không cần thay đổi trạng thái của bus dữ liệu: “Tôi vẫn đang đọc cái trước”. Sau khi chủ đã nhả lốp ra, anh ta kiểm tra nô lệ có để cô ấy đi không?.
  • Sau khi truyền 8 bit địa chỉ, thiết bị chủ tạo chu kỳ xung nhịp thứ 9 và giải phóng bus dữ liệu. Nếu người nô lệ nghe được địa chỉ của anh ta và chấp nhận nó thì anh ta sẽ nhấn S.D.A. xuống đất. Đây là cách tín hiệu được hình thành HỎI- chấp nhận, mọi thứ đều ổn. Nếu người nô lệ không hiểu gì, hoặc đơn giản là anh ta không có mặt ở đó thì sẽ không có ai ép lốp. chủ sẽ đợi một thời gian chờ và hiểu rằng anh ta không được hiểu.
  • Sau khi truyền địa chỉ, nếu chúng ta đã thiết lập hướng từ chủ đến nô lệ(8 bit địa chỉ bằng 1), sau đó master truyền dữ liệu đến Slave, không quên kiểm tra sự có mặt của HỎI từ Slave, chờ thiết bị Slave xử lý thông tin đến.
  • Khi master nhận dữ liệu từ Slave, master sẽ tự tạo tín hiệu HỎI sau khi nhận được từng byte và nô lệ sẽ kiểm soát sự hiện diện của nó. Bậc thầy có thể không gửi cụ thể HỎI trước khi gửi lệnh DỪNG LẠI, thường nói rõ với nô lệ rằng không cần cung cấp thêm bất kỳ dữ liệu nào.
  • Nếu sau khi gửi dữ liệu từ master (chế độ ghi), cần phải đọc dữ liệu từ Slave, sau đó chủ tạo lại tín hiệu BẮT ĐẦU , gửi địa chỉ nô lệ bằng cờ đọc. (nếu trước lệnh BẮT ĐẦUđã không được chuyển giao DỪNG LẠI sau đó một đội được thành lập KHỞI ĐỘNG LẠI). Điều này được sử dụng để thay đổi hướng giao tiếp chủ-nô lệ. Ví dụ: chúng tôi chuyển địa chỉ thanh ghi cho nô lệ và sau đó đọc dữ liệu từ nó.)
  • Sau khi hoàn thành công việc với nô lệ, chủ sẽ tạo ra tín hiệu DỪNG LẠI- ở mức cao của tín hiệu đồng hồ, nó tạo thành sự chuyển đổi bus dữ liệu từ 0 sang 1.
STM 32 có bộ thu phát bus i2c được triển khai bằng phần cứng. Có thể có 2 hoặc 3 mô-đun như vậy trong MK. Để định cấu hình chúng, các thanh ghi đặc biệt được sử dụng, được mô tả trong tài liệu tham khảo dành cho MK được sử dụng.

Trong MicroC, trước khi sử dụng i2c (cũng như bất kỳ thiết bị ngoại vi nào), nó phải được khởi tạo đúng cách. Để thực hiện việc này, chúng tôi sử dụng chức năng sau (Khởi tạo với tư cách chính):

I2Cn_Init_Advanced(dài không dấu: I2C_ClockSpeed, const Module_Struct *mô-đun);

  • N- ví dụ: số lượng mô-đun được sử dụng I2C1 hoặc I2C2.
  • I2C_Tốc độ đồng hồ- tốc độ bus, 100000 (100 kbs, chế độ tiêu chuẩn) hoặc 400000 (400 kbs, chế độ nhanh). Cái thứ hai nhanh hơn 4 lần, nhưng không phải thiết bị nào cũng hỗ trợ nó
  • *mô-đun- ví dụ: con trỏ tới một mô-đun ngoại vi &_GPIO_MODULE_I2C1_PB67, chúng ta đừng quên ở đây rằng Trợ lý mã (dấu cách ctrl ) giúp đỡ rất nhiều.
Đầu tiên, hãy kiểm tra xem xe buýt có rảnh không, có chức năng nào cho việc này không I2Cn_Is_Idle(); trả về 1 nếu xe buýt trống và 0 nếu có trao đổi trên đó.

I2Cn_Start();
Ở đâu N- số lượng mô-đun i2c được sử dụng của vi điều khiển của chúng tôi. Hàm sẽ trả về 0 nếu có lỗi trên bus và 1 nếu mọi thứ đều ổn.

Để truyền dữ liệu đến Slave chúng ta sử dụng hàm:

I2Cn_Write(địa chỉ nô lệ char không dấu, char *buf không dấu, số đếm dài không dấu, chế độ END_mode dài không dấu);

  • N- số lượng mô-đun được sử dụng
  • địa chỉ nô lệ- Địa chỉ nô lệ 7 bit.
  • *buf- một con trỏ tới dữ liệu của chúng tôi - một mảng byte hoặc byte.
  • đếm- số byte dữ liệu được truyền.
  • chế độ END_- phải làm gì sau khi chuyển dữ liệu sang nô lệ, END_MODE_STOP - truyền tín hiệu DỪNG LẠI, hoặc END_MODE_RESTART gửi lại BẮT ĐẦU, tạo ra tín hiệu KHỞI ĐỘNG LẠI và nói rõ với bộ phận rằng phiên làm việc với anh ấy vẫn chưa kết thúc và dữ liệu sẽ được đọc từ anh ấy.
Để đọc dữ liệu từ Slave, hãy sử dụng hàm:

I2Cn_Read(char Slav_address, char *ptrdata, số lượng dài không dấu, chế độ END_mode dài không dấu);

  • N- số lượng mô-đun được sử dụng
  • địa chỉ nô lệ- Địa chỉ nô lệ 7 bit.
  • *buf- một con trỏ tới một biến hoặc mảng mà chúng ta nhận dữ liệu vào đó, gõ char hoặc short int
  • đếm- số byte dữ liệu nhận được.
  • chế độ END_- phải làm gì sau khi nhận được dữ liệu từ nô lệ - END_MODE_STOP - truyền tín hiệu DỪNG LẠI, hoặc END_MODE_RESTART gửi tín hiệu KHỞI ĐỘNG LẠI.
Hãy thử kết nối thứ gì đó với MK của chúng ta. Để bắt đầu với: vi mạch PCF8574(A) phổ biến, là bộ mở rộng các cổng đầu vào/đầu ra được điều khiển thông qua bus i2c. Con chip này chỉ chứa một thanh ghi bên trong, đó là cổng I/O vật lý của nó. Nghĩa là, nếu bạn chuyển một byte cho cô ấy, nó sẽ ngay lập tức đưa ra kết luận của cô ấy. Nếu bạn đếm một byte từ nó (Truyền BẮT ĐẦUđịa chỉ có cờ đọc, tín hiệu KHỞI LẠI,đọc dữ liệu và cuối cùng tạo ra tín hiệu DỪNG LẠI) thì nó sẽ phản ánh các trạng thái logic trên đầu ra của nó. Hãy kết nối vi mạch của chúng tôi theo bảng dữ liệu:


Địa chỉ của vi mạch được hình thành từ trạng thái của các chân A0, A1, A2. Đối với vi mạch PCF8574địa chỉ sẽ là: 0100A0A1A2. (Ví dụ ta có A0, A1, A2 ở mức cao nên địa chỉ của vi mạch của ta sẽ là 0b0100 111 = 0x27). Vì PCF8574A - 0111A0A1A2, với sơ đồ kết nối của chúng tôi sẽ cung cấp địa chỉ 0b0111 111 = 0x3F. Ví dụ: nếu A2 được nối đất thì địa chỉ của PCF8574A sẽ 0x3B. Tổng cộng, bạn có thể treo đồng thời 16 vi mạch trên một bus i2c, mỗi vi mạch 8 PCF8574A và PCF8574.

Hãy thử chuyển một cái gì đó, khởi tạo bus i2c và chuyển một cái gì đó sang PCF8574 của chúng tôi.

#define PCF8574A_ADDR 0x3F //Địa chỉ của PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Tạo tín hiệu START I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); // Truyền 1 byte dữ liệu và tạo STOP tín hiệu ) char PCF8574A_reg ; // biến mà chúng ta viết trong PCF8574 void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Bắt đầu I2C delay_ms(25); // Đợi một chút PCF8574A_reg.b0 = 0; // bật đèn LED đầu tiên PCF8574A_reg.b1 = 1; // tắt đèn LED thứ hai trong khi (1) ( delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // đảo ngược trạng thái của đèn LED I2C_PCF8574_WriteReg (PCF8574A_reg) ; // truyền dữ liệu tới PCF8574 của chúng tôi ) )
Chúng tôi biên dịch và chạy chương trình của mình và thấy rằng các đèn LED của chúng tôi nhấp nháy luân phiên.
Tôi đã kết nối cực âm của đèn LED với PCF8574 của chúng tôi là có lý do. Vấn đề là khi mức logic 0 được cung cấp cho đầu ra, vi mạch sẽ kéo đầu ra của nó xuống đất một cách trung thực, nhưng khi mức logic 1 được áp dụng, nó sẽ kết nối nó với nguồn + thông qua nguồn hiện tại 100 μA. Nghĩa là, bạn không thể nhận được logic 1 “trung thực” ở đầu ra. Và bạn không thể thắp sáng đèn LED với 100 µA. Điều này được thực hiện để định cấu hình đầu ra PCF8574 thành đầu vào mà không cần đăng ký bổ sung. Chúng tôi chỉ cần ghi vào thanh ghi đầu ra 1 (về cơ bản là đặt trạng thái chân thành Vdd) và có thể chỉ cần rút ngắn nó xuống đất. Nguồn hiện tại sẽ không cho phép giai đoạn đầu ra của bộ mở rộng I/O của chúng tôi “hết nguồn”. Nếu chân được kéo xuống đất thì điện thế tiếp đất sẽ ở trên đó và logic 0 được đọc. Nếu chân được kéo về + thì logic 1 một mặt sẽ được đọc, nhưng mặt khác, nó sẽ được đọc. bạn phải luôn nhớ điều này khi làm việc với các vi mạch này.


Chúng ta hãy thử đọc trạng thái các chân của chip mở rộng của chúng ta.

#define PCF8574A_ADDR 0x3F //Địa chỉ của PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Tạo tín hiệu START I2C1_Write(PCF8574A_ADDR, &wData, 1, END_MODE_STOP); // Truyền 1 byte dữ liệu và tạo STOP signal ) void I2C_PCF8574_ReadReg (unsigned char rData) ( I2C1_Start(); // Tạo tín hiệu START I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Đọc 1 byte dữ liệu và tạo tín hiệu STOP) char PCF8574A_reg; //biến mà chúng ta ghi vào PCF8574 char PCF8574A_out; // biến chúng ta đọc vào và PCF8574 char lad_state; //đèn LED của chúng ta bật hoặc tắt void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Bắt đầu I2C delay_ms(25); // Đợi một chút PCF8574A_reg.b0 = 0; // bật đèn LED đầu tiên PCF8574A_reg.b1 = 1; / / tắt đèn LED thứ hai PCF8574A_reg.b6 = 1; // Kết nối chân 6 và 7 với nguồn PCF8574A_reg.b7 = 1; // ghi dữ liệu vào PCF8574 I2C_PCF8574_ReadReg (PCF8) 574A_out ); // đọc từ PCF8574 nếu (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // Nếu nhấn nút 1 (bit 6 của byte đọc từ PCF8574 là 0, thì bật bật/tắt đèn LED của chúng tôi) if (~PCF8574A_out .b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // tương tự cho 2 nút và 2 đèn LED ) )
Bây giờ bằng cách nhấn các nút, chúng ta bật hoặc tắt đèn LED. Vi mạch có đầu ra khác INT. Một xung được tạo ra trên đó mỗi khi trạng thái của các chân của bộ mở rộng I/O của chúng tôi thay đổi. Bằng cách kết nối nó với đầu vào ngắt bên ngoài của MK của chúng tôi (Tôi sẽ cho bạn biết cách định cấu hình các ngắt bên ngoài và cách làm việc với chúng trong một trong các bài viết sau).

Hãy sử dụng bộ mở rộng cổng của chúng tôi để kết nối màn hình ký tự thông qua nó. Có rất nhiều trong số này, nhưng hầu hết tất cả chúng đều được xây dựng trên cơ sở chip điều khiển HD44780 và bản sao của anh ấy. Ví dụ: tôi đã sử dụng màn hình LCD2004.


Bảng dữ liệu cho nó và bộ điều khiển HD44780 có thể dễ dàng tìm thấy trên Internet. Hãy kết nối màn hình của chúng tôi với PCF8574 và của cô ấy tương ứng với STM32 của chúng tôi.

HD44780 sử dụng giao diện cổng song song. Dữ liệu được truyền bởi 8 xung cổng (trong một chu kỳ xung nhịp) hoặc 4 (trong 2 chu kỳ xung nhịp) ở đầu ra E. (được bộ điều khiển hiển thị đọc ở cạnh giảm dần, chuyển từ 1 sang 0). Phần kết luận R.S. cho biết liệu chúng tôi có đang gửi dữ liệu tới màn hình của mình hay không ( RS = 1) (các ký tự nó sẽ hiển thị thực tế là mã ASCII) hoặc lệnh ( RS = 0). RW cho biết hướng truyền dữ liệu, ghi hoặc đọc. Thông thường chúng ta ghi dữ liệu lên màn hình, vì vậy ( RW=0). Điện trở R6 điều khiển độ tương phản của màn hình. Bạn không thể chỉ kết nối đầu vào điều chỉnh độ tương phản với mặt đất hoặc nguồn điện, nếu không bạn sẽ không thấy gì cả.. VT1 dùng để bật tắt đèn nền màn hình theo lệnh MK. MicroC có một thư viện để làm việc với những màn hình như vậy thông qua giao diện song song, nhưng thông thường việc dành 8 chân cho một màn hình sẽ rất tốn kém, vì vậy tôi hầu như luôn sử dụng PCF8574 để làm việc với những màn hình như vậy. (Nếu ai quan tâm thì mình sẽ viết bài về cách làm việc với màn hình HD44780 được tích hợp trong MicroC thông qua giao diện song song.) Giao thức trao đổi không đặc biệt phức tạp (chúng ta sẽ sử dụng 4 dòng dữ liệu và truyền thông tin trong 2 chu kỳ xung nhịp), nó được thể hiện rõ ràng qua sơ đồ thời gian sau:


Trước khi truyền dữ liệu sang màn hình của chúng tôi, nó phải được khởi tạo bằng cách truyền các lệnh dịch vụ. (được mô tả trong biểu dữ liệu, ở đây chúng tôi chỉ trình bày những cái được sử dụng nhiều nhất)

  • 0x28- giao tiếp với chỉ báo qua 4 dòng
  • 0x0C- bật đầu ra hình ảnh, tắt hiển thị con trỏ
  • 0x0E- bật đầu ra hình ảnh, bật hiển thị con trỏ
  • 0x01- xóa chỉ báo
  • 0x08- vô hiệu hóa đầu ra hình ảnh
  • 0x06- sau khi biểu tượng hiển thị, con trỏ di chuyển qua 1 vị trí quen thuộc
Vì chúng tôi sẽ phải làm việc với chỉ báo này khá thường xuyên nên chúng tôi sẽ tạo một thư viện trình cắm "i2c_lcd.h" . Với mục đích này trong Quản lý dự án Tệp tiêu đề và lựa chọn Thêm tập tin mới . Hãy tạo tập tin tiêu đề của chúng tôi.

#define PCF8574A_ADDR 0x3F // Địa chỉ của PCF8574 của chúng tôi #define DB4 b4 // Sự tương ứng giữa các chân PCF8574 và chỉ báo #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 #define RS b1 #define BL b0 // điều khiển đèn nền #define phân bổ 20 // số ký tự trong dòng hiển thị của chúng tôi static unsigned char BL_status; // biến lưu trữ trạng thái đèn nền (bật/tắt) void lcd_I2C_Init(void); // Hàm khởi tạo hiển thị và PCF8574 void lcd_I2C_txt(char *pnt); // Hiển thị một dòng văn bản, tham số là con trỏ tới dòng này void lcd_I2C_int(int pnt); // Hiển thị giá trị của biến số nguyên, tham số là giá trị đầu ra void lcd_I2C_Goto(unsigned short row, unsigned short col); // di chuyển con trỏ đến vị trí đã chỉ định, tham số row - line (từ 1 đến 2 hoặc 4 tùy theo màn hình hiển thị) và col - (từ 1 đến displenth)) void lcd_I2C_cls(); // Xóa màn hình void lcd_I2C_backlight (trạng thái unsigned short int); // Bật (khi truyền 1 và tắt - khi truyền 0, đèn nền màn hình)
Bây giờ hãy mô tả các chức năng của chúng tôi, một lần nữa chúng tôi đi đến Quản lý dự án nhấp chuột phải vào thư mục Nguồn và lựa chọn Thêm tập tin mới . Tạo một tập tin "i2c_lcd.с" .

#include "i2c_lcd.h" //bao gồm tệp tiêu đề của chúng tôi char lcd_reg; // Thanh ghi lưu trữ tạm thời dữ liệu được gửi tới PCF8574 VOID I2C_PCF8574_WRITEREG (UNSIGNED ChaR WDATA) // Chức năng của dữ liệu I2C trong chip PCF8574 (I2C1_START (); i2C1_WRITE (PCF8574A_ADDR, & WDATA, 1 , End_mode_stop) /chức năng gửi một lệnh cho màn hình của chúng tôi ( lcd_reg = 0; // ghi 0 vào thanh ghi tạm thời lcd_reg.BL = BL_status.b0; // đặt chân đèn nền theo giá trị của biến lưu trữ trạng thái đèn nền lcd_reg.DB4 = com. b4; // đặt 4 bit quan trọng nhất của lệnh của chúng tôi vào bus dữ liệu chỉ báo lcd_reg.DB5 = com.b6; lcd_reg.DB7 = com.b7; // đặt đầu ra nhấp nháy thành 1 lcd_reg); Đăng ký PCF8574, thực sự gửi dữ liệu đến chỉ báo delay_us (300); // đợi hết thời gian lcd_reg.EN = 0; // đặt lại xung nhấp nháy về 0, chỉ báo sẽ đọc dữ liệu I2C_PCF8574_WriteReg (lcd_reg = 0; .BL = BL_status.b0; lcd_reg.DB4 = com.b0; // tương tự cho 4 bit thấp lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.EN = 1; I2C_PCF8574_WriteReg(lcd_reg); độ trễ_us(300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg(lcd_reg); độ trễ_us(300); ) void LCD_CHAR (unsigned char com) // gửi dữ liệu (mã ký tự ASCII) đến chỉ báo ( lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; // gửi một ký tự khác với việc gửi lệnh bằng cách đặt bit RS thành 1 lcd_reg.DB4 = com.b4; // đặt 4 bit quan trọng nhất ở đầu vào lcd_reg.DB5 = com.b5; 300); lcd_reg.EN = 0; // đặt lại nhấp nháy về 0, chỉ báo đọc dữ liệu I2C_PCF8574_Reg (lcd_reg); lcd_reg.RS = 1; .DB5 = com.b1; lcd_reg.DB7 = com.b3; delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg delay_us (300); 200) ;lcd_Command(0x28); // Hiển thị ở 4 bit trên mỗi chế độ đồng hồ delay_ms (5); lcd_Command(0x08); // Vô hiệu hóa dữ liệu xuất ra màn hình delay_ms (5); lcd_Command(0x01); //Xóa độ trễ hiển thị_ms (5); lcd_Command(0x06); //Cho phép dịch chuyển con trỏ tự động sau khi hiển thị ký hiệu delay_ms (5); lcd_Command(0x0C); //Bật hiển thị thông tin không hiển thị con trỏ delay_ms (25); ) void lcd_I2C_txt(char *pnt) // Xuất một chuỗi ký tự ra màn hình ( unsigned short int i; // biến chỉ mục mảng ký tự tạm thời char tmp_str; // mảng ký tự tạm thời, dài hơn 1 so với độ dài của màn hình dòng, vì dòng này cần được kết thúc bằng ký tự NULL ASCII 0x00 strncpy(tmp_str, pnt, displenth); //sao chép không quá các ký tự phân tách của chuỗi gốc vào chuỗi tạm thời của chúng ta cho (i=0; i Bây giờ hãy kết nối thư viện vừa tạo với tệp bằng chức năng chính của chúng tôi:

#include "i2c_lcd.h" //bao gồm tệp tiêu đề của chúng tôi unsigned int i; //bộ đếm biến tạm thời void main() ( lcd_I2C_Init(); //khởi tạo màn hình lcd_I2C_backlight (1); //bật đèn nền lcd_I2C_txt ("Xin chào habrahabr"); //hiển thị dòng trong khi (1) ( delay_ms( 1000); lcd_I2C_Goto (2,1); // đi tới ký tự đầu tiên của dòng thứ 2 lcd_i2c_int (i); // hiển thị giá trị i++; // tăng bộ đếm ) )

Nếu mọi thứ được lắp ráp chính xác thì chúng ta sẽ thấy văn bản trên chỉ báo và bộ đếm tăng dần mỗi giây. Nói chung là không có gì phức tạp :)

Trong bài viết tiếp theo, chúng ta sẽ tiếp tục tìm hiểu giao thức i2c và các thiết bị hoạt động với nó. Hãy xem xét làm việc với bộ nhớ EEPROM 24XX và gia tốc kế/con quay hồi chuyển MPU6050.

Những bước đầu tiên với trình biên dịch STM32 và mikroC cho kiến ​​trúc ARM - Phần 4 - Kết nối LCD dựa trên I2C, pcf8574 và HD4478

Tôi muốn dành bài viết tiếp theo để làm việc với giao diện i2c phổ biến, giao diện này thường được sử dụng trong các vi mạch khác nhau được kết nối với vi điều khiển.

I2C là một bus hoạt động trên hai kết nối vật lý (ngoài dây chung). Có khá nhiều bài viết về nó trên Internet; có những bài viết hay trên Wikipedia. Ngoài ra, thuật toán vận hành bus được mô tả rất rõ ràng. Tóm lại, bus là bus đồng bộ hai dây. Có thể có tối đa 127 thiết bị trên bus cùng lúc (địa chỉ thiết bị là 7 bit, chúng ta sẽ quay lại vấn đề này sau). Dưới đây là sơ đồ điển hình để kết nối các thiết bị với bus i2c, với MK là thiết bị chính.


Đối với i2c, tất cả các thiết bị (cả chính và phụ) đều sử dụng đầu ra cống mở. Nói một cách đơn giản, họ CHỈ có thể hút lốp xe xuống ĐẤT. Mức bus cao được đảm bảo bằng điện trở kéo lên. Giá trị của các điện trở này thường được chọn trong khoảng từ 4,7 đến 10 kOhm. i2c khá nhạy cảm với các đường dây vật lý kết nối các thiết bị, vì vậy nếu sử dụng kết nối có điện dung lớn (ví dụ: cáp dài mỏng hoặc được che chắn), ảnh hưởng của điện dung này có thể “làm mờ” các cạnh của tín hiệu và gây nhiễu hoạt động bình thường của xe buýt. Điện trở kéo lên càng nhỏ thì điện dung này càng ít ảnh hưởng đến đặc tính của các cạnh tín hiệu, nhưng TẢI LỚN hơn trên các bóng bán dẫn đầu ra trên giao diện i2c. Giá trị của các điện trở này được chọn cho từng cách thực hiện cụ thể, nhưng chúng không được nhỏ hơn 2,2 kOhms, nếu không bạn có thể chỉ cần đốt các bóng bán dẫn đầu ra trong các thiết bị hoạt động với bus.

Bus bao gồm hai dòng: SDA (dòng dữ liệu) và SCL (tín hiệu đồng hồ). Đồng hồ xe buýt Thiết bị chủ, thường là MK của chúng tôi. Khi SCL ở mức cao, thông tin sẽ được đọc từ bus dữ liệu. Trạng thái SDA chỉ có thể được thay đổi khi tín hiệu đồng hồ ở mức thấp.. Khi mức SCL cao, tín hiệu trên SDA sẽ thay đổi khi tạo tín hiệu BẮT ĐẦU (khi SCL ở mức cao, tín hiệu trên SDA thay đổi từ cao xuống thấp) và DỪNG LẠI - khi mức SCL cao, tín hiệu trên SDA thay đổi từ thấp sang cao).

Riêng biệt, cần phải nói rằng trong i2c, địa chỉ được chỉ định là số 7 bit. 8 - bit có ý nghĩa nhỏ nhất cho biết hướng truyền dữ liệu 0 - nghĩa là Slave sẽ truyền dữ liệu, 1 - nhận.. Tóm lại, thuật toán làm việc với i2c như sau:

  • Mức độ cao trên SDA và SCL- xe buýt miễn phí, bạn có thể bắt đầu làm việc
  • Thang máy chính SCL lên 1 và thay đổi trạng thái S.D.A. từ 1 đến 0 - hút nó xuống đất - tín hiệu được hình thành BẮT ĐẦU
  • Master truyền địa chỉ Slave 7-bit với một bit định hướng (dữ liệu trên S.D.A.được trưng bày khi SCL kéo xuống đất và được nô lệ đọc khi nó được thả ra). Nếu nô lệ không có thời gian để “chộp lấy” bit trước đó, nó sẽ thu hút SCL xuống đất, nói rõ với chủ rằng không cần thay đổi trạng thái của bus dữ liệu: “Tôi vẫn đang đọc cái trước”. Sau khi chủ đã nhả lốp ra, anh ta kiểm tra nô lệ có để cô ấy đi không?.
  • Sau khi truyền 8 bit địa chỉ, thiết bị chủ tạo chu kỳ xung nhịp thứ 9 và giải phóng bus dữ liệu. Nếu người nô lệ nghe được địa chỉ của anh ta và chấp nhận nó thì anh ta sẽ nhấn S.D.A. xuống đất. Đây là cách tín hiệu được hình thành HỎI- chấp nhận, mọi thứ đều ổn. Nếu người nô lệ không hiểu gì, hoặc đơn giản là anh ta không có mặt ở đó thì sẽ không có ai ép lốp. chủ sẽ đợi một thời gian chờ và hiểu rằng anh ta không được hiểu.
  • Sau khi truyền địa chỉ, nếu chúng ta đã thiết lập hướng từ chủ đến nô lệ(8 bit địa chỉ bằng 1), sau đó master truyền dữ liệu đến Slave, không quên kiểm tra sự có mặt của HỎI từ Slave, chờ thiết bị Slave xử lý thông tin đến.
  • Khi master nhận dữ liệu từ Slave, master sẽ tự tạo tín hiệu HỎI sau khi nhận được từng byte và nô lệ sẽ kiểm soát sự hiện diện của nó. Bậc thầy có thể không gửi cụ thể HỎI trước khi gửi lệnh DỪNG LẠI, thường nói rõ với nô lệ rằng không cần cung cấp thêm bất kỳ dữ liệu nào.
  • Nếu sau khi gửi dữ liệu từ master (chế độ ghi), cần phải đọc dữ liệu từ Slave, sau đó chủ tạo lại tín hiệu BẮT ĐẦU , gửi địa chỉ nô lệ bằng cờ đọc. (nếu trước lệnh BẮT ĐẦUđã không được chuyển giao DỪNG LẠI sau đó một đội được thành lập KHỞI ĐỘNG LẠI). Điều này được sử dụng để thay đổi hướng giao tiếp chủ-nô lệ. Ví dụ: chúng tôi chuyển địa chỉ thanh ghi cho nô lệ và sau đó đọc dữ liệu từ nó.)
  • Sau khi hoàn thành công việc với nô lệ, chủ sẽ tạo ra tín hiệu DỪNG LẠI- ở mức cao của tín hiệu đồng hồ, nó tạo thành sự chuyển đổi bus dữ liệu từ 0 sang 1.
STM 32 có bộ thu phát bus i2c được triển khai bằng phần cứng. Có thể có 2 hoặc 3 mô-đun như vậy trong MK. Để định cấu hình chúng, các thanh ghi đặc biệt được sử dụng, được mô tả trong tài liệu tham khảo dành cho MK được sử dụng.

Trong MicroC, trước khi sử dụng i2c (cũng như bất kỳ thiết bị ngoại vi nào), nó phải được khởi tạo đúng cách. Để thực hiện việc này, chúng tôi sử dụng chức năng sau (Khởi tạo với tư cách chính):

I2Cn_Init_Advanced(dài không dấu: I2C_ClockSpeed, const Module_Struct *mô-đun);

  • N- ví dụ: số lượng mô-đun được sử dụng I2C1 hoặc I2C2.
  • I2C_Tốc độ đồng hồ- tốc độ bus, 100000 (100 kbs, chế độ tiêu chuẩn) hoặc 400000 (400 kbs, chế độ nhanh). Cái thứ hai nhanh hơn 4 lần, nhưng không phải thiết bị nào cũng hỗ trợ nó
  • *mô-đun- ví dụ: con trỏ tới một mô-đun ngoại vi &_GPIO_MODULE_I2C1_PB67, chúng ta đừng quên ở đây rằng Trợ lý mã (dấu cách ctrl ) giúp đỡ rất nhiều.
Đầu tiên, hãy kiểm tra xem xe buýt có rảnh không, có chức năng nào cho việc này không I2Cn_Is_Idle(); trả về 1 nếu xe buýt trống và 0 nếu có trao đổi trên đó.

I2Cn_Start();
Ở đâu N- số lượng mô-đun i2c được sử dụng của vi điều khiển của chúng tôi. Hàm sẽ trả về 0 nếu có lỗi trên bus và 1 nếu mọi thứ đều ổn.

Để truyền dữ liệu đến Slave chúng ta sử dụng hàm:

I2Cn_Write(địa chỉ nô lệ char không dấu, char *buf không dấu, số đếm dài không dấu, chế độ END_mode dài không dấu);

  • N- số lượng mô-đun được sử dụng
  • địa chỉ nô lệ- Địa chỉ nô lệ 7 bit.
  • *buf- một con trỏ tới dữ liệu của chúng tôi - một mảng byte hoặc byte.
  • đếm- số byte dữ liệu được truyền.
  • chế độ END_- phải làm gì sau khi chuyển dữ liệu sang nô lệ, END_MODE_STOP - truyền tín hiệu DỪNG LẠI, hoặc END_MODE_RESTART gửi lại BẮT ĐẦU, tạo ra tín hiệu KHỞI ĐỘNG LẠI và nói rõ với bộ phận rằng phiên làm việc với anh ấy vẫn chưa kết thúc và dữ liệu sẽ được đọc từ anh ấy.
Để đọc dữ liệu từ Slave, hãy sử dụng hàm:

I2Cn_Read(char Slav_address, char *ptrdata, số lượng dài không dấu, chế độ END_mode dài không dấu);

  • N- số lượng mô-đun được sử dụng
  • địa chỉ nô lệ- Địa chỉ nô lệ 7 bit.
  • *buf- một con trỏ tới một biến hoặc mảng mà chúng ta nhận dữ liệu vào đó, gõ char hoặc short int
  • đếm- số byte dữ liệu nhận được.
  • chế độ END_- phải làm gì sau khi nhận được dữ liệu từ nô lệ - END_MODE_STOP - truyền tín hiệu DỪNG LẠI, hoặc END_MODE_RESTART gửi tín hiệu KHỞI ĐỘNG LẠI.
Hãy thử kết nối thứ gì đó với MK của chúng ta. Để bắt đầu với: vi mạch PCF8574(A) phổ biến, là bộ mở rộng các cổng đầu vào/đầu ra được điều khiển thông qua bus i2c. Con chip này chỉ chứa một thanh ghi bên trong, đó là cổng I/O vật lý của nó. Nghĩa là, nếu bạn chuyển một byte cho cô ấy, nó sẽ ngay lập tức đưa ra kết luận của cô ấy. Nếu bạn đếm một byte từ nó (Truyền BẮT ĐẦUđịa chỉ có cờ đọc, tín hiệu KHỞI LẠI,đọc dữ liệu và cuối cùng tạo ra tín hiệu DỪNG LẠI) thì nó sẽ phản ánh các trạng thái logic trên đầu ra của nó. Hãy kết nối vi mạch của chúng tôi theo bảng dữ liệu:


Địa chỉ của vi mạch được hình thành từ trạng thái của các chân A0, A1, A2. Đối với vi mạch PCF8574địa chỉ sẽ là: 0100A0A1A2. (Ví dụ ta có A0, A1, A2 ở mức cao nên địa chỉ của vi mạch của ta sẽ là 0b0100 111 = 0x27). Vì PCF8574A - 0111A0A1A2, với sơ đồ kết nối của chúng tôi sẽ cung cấp địa chỉ 0b0111 111 = 0x3F. Ví dụ: nếu A2 được nối đất thì địa chỉ của PCF8574A sẽ 0x3B. Tổng cộng, bạn có thể treo đồng thời 16 vi mạch trên một bus i2c, mỗi vi mạch 8 PCF8574A và PCF8574.

Hãy thử chuyển một cái gì đó, khởi tạo bus i2c và chuyển một cái gì đó sang PCF8574 của chúng tôi.

#define PCF8574A_ADDR 0x3F //Địa chỉ của PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Tạo tín hiệu START I2C1_Write(PCF8574A_ADDR,&wData, 1, END_MODE_STOP); // Truyền 1 byte dữ liệu và tạo STOP tín hiệu ) char PCF8574A_reg ; // biến mà chúng ta viết trong PCF8574 void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Bắt đầu I2C delay_ms(25); // Đợi một chút PCF8574A_reg.b0 = 0; // bật đèn LED đầu tiên PCF8574A_reg.b1 = 1; // tắt đèn LED thứ hai trong khi (1) ( delay_ms(500); PCF8574A_reg.b0 = ~PCF8574A_reg.b0; PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // đảo ngược trạng thái của đèn LED I2C_PCF8574_WriteReg (PCF8574A_reg) ; // truyền dữ liệu tới PCF8574 của chúng tôi ) )
Chúng tôi biên dịch và chạy chương trình của mình và thấy rằng các đèn LED của chúng tôi nhấp nháy luân phiên.
Tôi đã kết nối cực âm của đèn LED với PCF8574 của chúng tôi là có lý do. Vấn đề là khi mức logic 0 được cung cấp cho đầu ra, vi mạch sẽ kéo đầu ra của nó xuống đất một cách trung thực, nhưng khi mức logic 1 được áp dụng, nó sẽ kết nối nó với nguồn + thông qua nguồn hiện tại 100 μA. Nghĩa là, bạn không thể nhận được logic 1 “trung thực” ở đầu ra. Và bạn không thể thắp sáng đèn LED với 100 µA. Điều này được thực hiện để định cấu hình đầu ra PCF8574 thành đầu vào mà không cần đăng ký bổ sung. Chúng tôi chỉ cần ghi vào thanh ghi đầu ra 1 (về cơ bản là đặt trạng thái chân thành Vdd) và có thể chỉ cần rút ngắn nó xuống đất. Nguồn hiện tại sẽ không cho phép giai đoạn đầu ra của bộ mở rộng I/O của chúng tôi “hết nguồn”. Nếu chân được kéo xuống đất thì điện thế tiếp đất sẽ ở trên đó và logic 0 được đọc. Nếu chân được kéo về + thì logic 1 một mặt sẽ được đọc, nhưng mặt khác, nó sẽ được đọc. bạn phải luôn nhớ điều này khi làm việc với các vi mạch này.


Chúng ta hãy thử đọc trạng thái các chân của chip mở rộng của chúng ta.

#define PCF8574A_ADDR 0x3F //Địa chỉ của PCF8574 void I2C_PCF8574_WriteReg(unsigned char wData) ( I2C1_Start(); // Tạo tín hiệu START I2C1_Write(PCF8574A_ADDR, &wData, 1, END_MODE_STOP); // Truyền 1 byte dữ liệu và tạo STOP signal ) void I2C_PCF8574_ReadReg (unsigned char rData) ( I2C1_Start(); // Tạo tín hiệu START I2C1_Read(PCF8574A_ADDR, &rData, 1, END_MODE_STOP); // Đọc 1 byte dữ liệu và tạo tín hiệu STOP) char PCF8574A_reg; //biến mà chúng ta ghi vào PCF8574 char PCF8574A_out; // biến chúng ta đọc vào và PCF8574 char lad_state; //đèn LED của chúng ta bật hoặc tắt void main () ( I2C1_Init_Advanced(400000, &_GPIO_MODULE_I2C1_PB67); // Bắt đầu I2C delay_ms(25); // Đợi một chút PCF8574A_reg.b0 = 0; // bật đèn LED đầu tiên PCF8574A_reg.b1 = 1; / / tắt đèn LED thứ hai PCF8574A_reg.b6 = 1; // Kết nối chân 6 và 7 với nguồn PCF8574A_reg.b7 = 1; // ghi dữ liệu vào PCF8574 I2C_PCF8574_ReadReg (PCF8) 574A_out ); // đọc từ PCF8574 nếu (~PCF8574A_out.b6) PCF8574A_reg.b0 = ~PCF8574A_reg.b0; // Nếu nhấn nút 1 (bit 6 của byte đọc từ PCF8574 là 0, thì bật bật/tắt đèn LED của chúng tôi) if (~PCF8574A_out .b7) PCF8574A_reg.b1 = ~PCF8574A_reg.b1; // tương tự cho 2 nút và 2 đèn LED ) )
Bây giờ bằng cách nhấn các nút, chúng ta bật hoặc tắt đèn LED. Vi mạch có đầu ra khác INT. Một xung được tạo ra trên đó mỗi khi trạng thái của các chân của bộ mở rộng I/O của chúng tôi thay đổi. Bằng cách kết nối nó với đầu vào ngắt bên ngoài của MK của chúng tôi (Tôi sẽ cho bạn biết cách định cấu hình các ngắt bên ngoài và cách làm việc với chúng trong một trong các bài viết sau).

Hãy sử dụng bộ mở rộng cổng của chúng tôi để kết nối màn hình ký tự thông qua nó. Có rất nhiều trong số này, nhưng hầu hết tất cả chúng đều được xây dựng trên cơ sở chip điều khiển HD44780 và bản sao của anh ấy. Ví dụ: tôi đã sử dụng màn hình LCD2004.


Bảng dữ liệu cho nó và bộ điều khiển HD44780 có thể dễ dàng tìm thấy trên Internet. Hãy kết nối màn hình của chúng tôi với PCF8574 và của cô ấy tương ứng với STM32 của chúng tôi.

HD44780 sử dụng giao diện cổng song song. Dữ liệu được truyền bởi 8 xung cổng (trong một chu kỳ xung nhịp) hoặc 4 (trong 2 chu kỳ xung nhịp) ở đầu ra E. (được bộ điều khiển hiển thị đọc ở cạnh giảm dần, chuyển từ 1 sang 0). Phần kết luận R.S. cho biết liệu chúng tôi có đang gửi dữ liệu tới màn hình của mình hay không ( RS = 1) (các ký tự nó sẽ hiển thị thực tế là mã ASCII) hoặc lệnh ( RS = 0). RW cho biết hướng truyền dữ liệu, ghi hoặc đọc. Thông thường chúng ta ghi dữ liệu lên màn hình, vì vậy ( RW=0). Điện trở R6 điều khiển độ tương phản của màn hình. Bạn không thể chỉ kết nối đầu vào điều chỉnh độ tương phản với mặt đất hoặc nguồn điện, nếu không bạn sẽ không thấy gì cả.. VT1 dùng để bật tắt đèn nền màn hình theo lệnh MK. MicroC có một thư viện để làm việc với những màn hình như vậy thông qua giao diện song song, nhưng thông thường việc dành 8 chân cho một màn hình sẽ rất tốn kém, vì vậy tôi hầu như luôn sử dụng PCF8574 để làm việc với những màn hình như vậy. (Nếu ai quan tâm thì mình sẽ viết bài về cách làm việc với màn hình HD44780 được tích hợp trong MicroC thông qua giao diện song song.) Giao thức trao đổi không đặc biệt phức tạp (chúng ta sẽ sử dụng 4 dòng dữ liệu và truyền thông tin trong 2 chu kỳ xung nhịp), nó được thể hiện rõ ràng qua sơ đồ thời gian sau:


Trước khi truyền dữ liệu sang màn hình của chúng tôi, nó phải được khởi tạo bằng cách truyền các lệnh dịch vụ. (được mô tả trong biểu dữ liệu, ở đây chúng tôi chỉ trình bày những cái được sử dụng nhiều nhất)

  • 0x28- giao tiếp với chỉ báo qua 4 dòng
  • 0x0C- bật đầu ra hình ảnh, tắt hiển thị con trỏ
  • 0x0E- bật đầu ra hình ảnh, bật hiển thị con trỏ
  • 0x01- xóa chỉ báo
  • 0x08- vô hiệu hóa đầu ra hình ảnh
  • 0x06- sau khi biểu tượng hiển thị, con trỏ di chuyển qua 1 vị trí quen thuộc
Vì chúng tôi sẽ phải làm việc với chỉ báo này khá thường xuyên nên chúng tôi sẽ tạo một thư viện trình cắm "i2c_lcd.h" . Với mục đích này trong Quản lý dự án Tệp tiêu đề và lựa chọn Thêm tập tin mới . Hãy tạo tập tin tiêu đề của chúng tôi.

#define PCF8574A_ADDR 0x3F // Địa chỉ của PCF8574 của chúng tôi #define DB4 b4 // Sự tương ứng giữa các chân PCF8574 và chỉ báo #define DB5 b5 #define DB6 b6 #define DB7 b7 #define EN b3 #define RW b2 #define RS b1 #define BL b0 // điều khiển đèn nền #define phân bổ 20 // số ký tự trong dòng hiển thị của chúng tôi static unsigned char BL_status; // biến lưu trữ trạng thái đèn nền (bật/tắt) void lcd_I2C_Init(void); // Hàm khởi tạo hiển thị và PCF8574 void lcd_I2C_txt(char *pnt); // Hiển thị một dòng văn bản, tham số là con trỏ tới dòng này void lcd_I2C_int(int pnt); // Hiển thị giá trị của biến số nguyên, tham số là giá trị đầu ra void lcd_I2C_Goto(unsigned short row, unsigned short col); // di chuyển con trỏ đến vị trí đã chỉ định, tham số row - line (từ 1 đến 2 hoặc 4 tùy theo màn hình hiển thị) và col - (từ 1 đến displenth)) void lcd_I2C_cls(); // Xóa màn hình void lcd_I2C_backlight (trạng thái unsigned short int); // Bật (khi truyền 1 và tắt - khi truyền 0, đèn nền màn hình)
Bây giờ hãy mô tả các chức năng của chúng tôi, một lần nữa chúng tôi đi đến Quản lý dự án nhấp chuột phải vào thư mục Nguồn và lựa chọn Thêm tập tin mới . Tạo một tập tin "i2c_lcd.с" .

#include "i2c_lcd.h" //bao gồm tệp tiêu đề của chúng tôi char lcd_reg; // Thanh ghi lưu trữ tạm thời dữ liệu được gửi tới PCF8574 VOID I2C_PCF8574_WRITEREG (UNSIGNED ChaR WDATA) // Chức năng của dữ liệu I2C trong chip PCF8574 (I2C1_START (); i2C1_WRITE (PCF8574A_ADDR, & WDATA, 1 , End_mode_stop) /chức năng gửi một lệnh cho màn hình của chúng tôi ( lcd_reg = 0; // ghi 0 vào thanh ghi tạm thời lcd_reg.BL = BL_status.b0; // đặt chân đèn nền theo giá trị của biến lưu trữ trạng thái đèn nền lcd_reg.DB4 = com. b4; // đặt 4 bit quan trọng nhất của lệnh của chúng tôi vào bus dữ liệu chỉ báo lcd_reg.DB5 = com.b6; lcd_reg.DB7 = com.b7; // đặt đầu ra nhấp nháy thành 1 lcd_reg); Đăng ký PCF8574, thực sự gửi dữ liệu đến chỉ báo delay_us (300); // đợi hết thời gian lcd_reg.EN = 0; // đặt lại xung nhấp nháy về 0, chỉ báo sẽ đọc dữ liệu I2C_PCF8574_WriteReg (lcd_reg = 0; .BL = BL_status.b0; lcd_reg.DB4 = com.b0; // tương tự cho 4 bit thấp lcd_reg.DB5 = com.b1; lcd_reg.DB6 = com.b2; lcd_reg.DB7 = com.b3; lcd_reg.EN = 1; I2C_PCF8574_WriteReg(lcd_reg); độ trễ_us(300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg(lcd_reg); độ trễ_us(300); ) void LCD_CHAR (unsigned char com) // gửi dữ liệu (mã ký tự ASCII) đến chỉ báo ( lcd_reg = 0; lcd_reg.BL = BL_status.b0; lcd_reg.EN = 1; lcd_reg.RS = 1; // gửi một ký tự khác với việc gửi lệnh bằng cách đặt bit RS thành 1 lcd_reg.DB4 = com.b4; // đặt 4 bit quan trọng nhất ở đầu vào lcd_reg.DB5 = com.b5; 300); lcd_reg.EN = 0; // đặt lại nhấp nháy về 0, chỉ báo đọc dữ liệu I2C_PCF8574_Reg (lcd_reg); lcd_reg.RS = 1; .DB5 = com.b1; lcd_reg.DB7 = com.b3; delay_us (300); lcd_reg.EN = 0; I2C_PCF8574_WriteReg (lcd_reg delay_us (300); 200) ;lcd_Command(0x28); // Hiển thị ở 4 bit trên mỗi chế độ đồng hồ delay_ms (5); lcd_Command(0x08); // Vô hiệu hóa dữ liệu xuất ra màn hình delay_ms (5); lcd_Command(0x01); //Xóa độ trễ hiển thị_ms (5); lcd_Command(0x06); //Cho phép dịch chuyển con trỏ tự động sau khi hiển thị ký hiệu delay_ms (5); lcd_Command(0x0C); //Bật hiển thị thông tin không hiển thị con trỏ delay_ms (25); ) void lcd_I2C_txt(char *pnt) // Xuất một chuỗi ký tự ra màn hình ( unsigned short int i; // biến chỉ mục mảng ký tự tạm thời char tmp_str; // mảng ký tự tạm thời, dài hơn 1 so với độ dài của màn hình dòng, vì dòng này cần được kết thúc bằng ký tự NULL ASCII 0x00 strncpy(tmp_str, pnt, displenth); //sao chép không quá các ký tự phân tách của chuỗi gốc vào chuỗi tạm thời của chúng ta cho (i=0; i Bây giờ hãy kết nối thư viện vừa tạo với tệp bằng chức năng chính của chúng tôi:

#include "i2c_lcd.h" //bao gồm tệp tiêu đề của chúng tôi unsigned int i; //bộ đếm biến tạm thời void main() ( lcd_I2C_Init(); //khởi tạo màn hình lcd_I2C_backlight (1); //bật đèn nền lcd_I2C_txt ("Xin chào habrahabr"); //hiển thị dòng trong khi (1) ( delay_ms( 1000); lcd_I2C_Goto (2,1); // đi tới ký tự đầu tiên của dòng thứ 2 lcd_i2c_int (i); // hiển thị giá trị i++; // tăng bộ đếm ) )

Nếu mọi thứ được lắp ráp chính xác thì chúng ta sẽ thấy văn bản trên chỉ báo và bộ đếm tăng dần mỗi giây. Nói chung là không có gì phức tạp :)

Trong bài viết tiếp theo, chúng ta sẽ tiếp tục tìm hiểu giao thức i2c và các thiết bị hoạt động với nó. Hãy xem xét làm việc với bộ nhớ EEPROM 24XX và gia tốc kế/con quay hồi chuyển MPU6050.

Xuất bản 26/10/2016

Trong bài viết trước, chúng ta đã xem xét hoạt động của STM32 với bus I 2 C làm Master. Tức là anh ta là người cầm đầu và thẩm vấn cảm biến. Bây giờ, hãy biến STM32 thành Slave và đáp ứng các yêu cầu, nghĩa là bản thân nó hoạt động như một cảm biến. Chúng tôi sẽ phân bổ 255 byte bộ nhớ cho các thanh ghi có địa chỉ từ 0 đến 0xFF và cho phép Master ghi/đọc chúng. Và để làm cho ví dụ không đơn giản như vậy, hãy biến STM32 của chúng ta thành một bộ chuyển đổi analog sang digital với giao diện I 2 C. ADC sẽ xử lý 8 kênh. Bộ điều khiển sẽ đưa ra kết quả của các phép biến đổi cho Master khi đọc từ các thanh ghi. Vì kết quả của chuyển đổi ADC là 12 bit nên chúng ta cần 2 thanh ghi (2 byte) cho mỗi kênh ADC.

i2c_slave.h chứa các cài đặt:

I2CSLAVE_ADDR– địa chỉ thiết bị của chúng tôi;

ADC_ADDR_START– địa chỉ bắt đầu của các thanh ghi chịu trách nhiệm về kết quả chuyển đổi ADC.

Trong tập tin i2c_slave.c chúng tôi quan tâm nhất đến các chức năng get_i2c1_ramset_i2c1_ram. Chức năng get_i2c1_ram chịu trách nhiệm đọc dữ liệu từ các thanh ghi. Nó trả về dữ liệu từ địa chỉ đã chỉ định, địa chỉ này được cấp cho Master. Trong trường hợp của chúng tôi, dữ liệu được đọc từ mảng i2c1_ram, nhưng nếu Master yêu cầu địa chỉ thanh ghi trong phạm vi được phân bổ cho kết quả ADC thì dữ liệu chuyển đổi ADC sẽ được gửi.

get_i2c1_ram:

Uint8_t get_i2c1_ram(uint8_t adr) ( //Dữ liệu ADC if ((ADC_ADDR_START<= adr) & (adr < ADC_ADDR_START + ADC_CHANNELS*2)) { return ADCBuffer; } else { // Other addresses return i2c1_ram; } }

Chức năng set_i2c1_ram– ghi dữ liệu nhận được từ Master vào các thanh ghi có địa chỉ được chỉ định. Trong trường hợp của chúng tôi, dữ liệu chỉ được ghi vào một mảng i2c1_ram. Nhưng đây là tùy chọn. Ví dụ: bạn có thể thêm séc và khi một số nhất định đến một địa chỉ nhất định, hãy thực hiện một số hành động. Bằng cách này bạn có thể gửi các lệnh khác nhau đến vi điều khiển.

set_i2c1_ram:

Vô hiệu set_i2c1_ram(uint8_t adr, uint8_t val) ( i2c1_ram = val; return; )

Việc khởi tạo khá đơn giản:

Int main(void) ( SetSysClockTo72(); ADC_DMA_init(); I2C1_Slave_init(); while(1) ( ) )

Đầu tiên chúng ta đặt tần số hoạt động tối đa của bộ điều khiển. Cần có tốc độ tối đa khi cần tránh bất kỳ độ trễ nào trên bus I 2 C. Sau đó, chúng tôi bắt đầu vận hành ADC bằng DMA. VỀ . VỀ . Và cuối cùng, chúng ta khởi tạo bus I 2 C như Nô lệ. Như bạn có thể thấy, không có gì phức tạp.

Bây giờ hãy kết nối mô-đun STM32 của chúng tôi với Raspberry Pi. Hãy kết nối chiết áp với các kênh ADC. Và chúng tôi sẽ đọc các chỉ số ADC từ bộ điều khiển của chúng tôi. Đừng quên rằng để bus I 2 C hoạt động, bạn cần lắp các điện trở kéo lên trên mỗi dòng của bus.

Trong bảng điều khiển Raspberry, hãy kiểm tra xem thiết bị của chúng tôi có hiển thị trên bus I 2 C hay không (về điều đó):

I2c detect -y 1

Như bạn có thể thấy, địa chỉ thiết bị 0x27, mặc dù chúng tôi đã chỉ định 0x4E. Khi bạn có thời gian, hãy nghĩ xem tại sao điều này lại xảy ra.

Để đọc từ các thanh ghi của thiết bị I 2 C-Slave, hãy thực hiện lệnh:

I2cget -y 1 0x27 0x00

Ở đâu:
0x27– địa chỉ thiết bị,
0x00– địa chỉ đăng ký (0x00…0xFF).

Để ghi vào các thanh ghi của thiết bị I 2 C-Slave, hãy thực hiện lệnh:

I2cset -y 1 0x27 0xA0 0xDD

De:
0x27– địa chỉ thiết bị,
0xA0- đăng ký địa chỉ
0xDD Dữ liệu -8 bit (0x00…0xFF)

Lệnh trước ghi số 0xDD vào thanh ghi 0xA0(bạn có thể ghi vào 16 thanh ghi đầu tiên, nhưng không có ích gì mà chúng được dành riêng cho ADC). Bây giờ chúng ta hãy đọc:

I2cget -y 1 0x27 0xA0

Để đơn giản hóa quá trình đọc dữ liệu kênh ADC, tôi đã viết một đoạn script:

#!/usr/bin/env python nhập smbus thời gian nhập bus = smbus.SMBus(1) địa chỉ = 0x27 while (1): ADC = (); đối với i trong phạm vi (0, 8): LBS = bus.read_byte_data(địa chỉ, 0x00+i*2) MBS = bus.read_byte_data(địa chỉ, 0x00+i*2+1) ADC[i] = MBS*256 + LBS in thời gian ADC.sleep(0.2)

Nó thăm dò và hiển thị kết quả của tất cả 8 kênh ADC ra bảng điều khiển.

Theo cách tương tự, bạn có thể kết hợp một số bộ vi điều khiển. Một trong số đó phải là Master(), Slave còn lại.

Chúc các bạn thành công!