Lập trình STM32 sử dụng giao thức SPI sẽ giúp chúng ta hiểu được:
- Nguyên lý truyền nhận của chuẩn SPI
- Cách thiết lập giao tiếp SPI trên STM32 CubeMX
- Lập trình với giao thức SPI
Bài số 12 trong serie Học lập trình STM32 từ A tới Z
Giao thức SPI là gì
Cơ bản về giao thức SPI
SPI (Serial Peripheral Interface) là một chuẩn truyền thông nối tiếp đồng bộ dùng để truyền dữ liệu ở chế độ song công toàn phần (full duplex).
Giao thức SPI thường được sử dụng on board hoặc trên các đường tín hiệu ngắn. Tốc độ của SPI khá cao, phụ thuộc vào tần số xung clock cấp cho bộ SPI.
Giao thức SPI là giao thức dạng Master – Slave, trong đó Master giữ quyền điều khiển xung Clock và chọn Slave nào giao tiếp với mình.
Bus SPI bao gồm 4 đường tín hiệu:
- MOSI (Master Out Slave In): đường truyền tín hiệu từ Master đến Slave
- MISO (Master In Slave Out): đường truyền tín hiệu từ Slave đến Master
- SCK (Serial Clock): đường phát xung đồng hồ, được điều khiển bởi Master
- CS (Chip Select): đường chọn chip giao tiếp với Master, được kéo xuống 0 khi được chọn

Nguyên lý hoạt động của giao thức SPI
Nguyên lý hoạt động như sau:
- Khi muốn truyền nhận dữ liệu với một Slave, đầu tiên Master kéo đường CS nối tới Slave đó xuống 0.
- Master phát xung Clock, ứng với mỗi xung Clock sẽ có 1 bit DATA được gửi trên chân MOSI, tại sườn lên hoặc sườn xuống tùy theo mode đã chọn.
- Cùng lúc đó, Slave cũng gửi ngược lại 1 bit DATA trên chân MISO tới Master.
- Việc truyền nhận diễn ra liên tục theo xung Clock nên SPI thường có tốc độ rất cao.
Các bạn hình dung SPI giống 2 người đứng 2 đầu một băng chuyền vòng: mỗi nhịp clock, Master đẩy 1 bit sang Slave và Slave cũng đẩy 1 bit về Master. Sau 8 nhịp, hai bên đã đổi cho nhau trọn 1 byte.

Chế độ hoạt động của giao thức SPI
Có 4 chế độ hoạt động của SPI dựa trên hai bit CPOL và CPHA.
CPOL (Clock Polarity): xác định mức tín hiệu của Clock lúc nhàn rỗi (Idle). Nếu CPOL = 0, lúc không gửi dữ liệu Clock sẽ ở mức thấp, CPOL = 1 thì ngược lại.
CPHA (Clock Phase): xác định dữ liệu được lấy mẫu ở sườn thứ nhất (CPHA = 0) hay sườn thứ hai (CPHA = 1) của mỗi xung Clock.
Cụ thể 4 mode như sau:
- Mode 0: CPOL = 0 và CPHA = 0. Clock nghỉ ở mức thấp, dữ liệu được lấy mẫu tại sườn lên của xung đồng hồ.
- Mode 1: CPOL = 0 và CPHA = 1. Clock nghỉ ở mức thấp, dữ liệu được lấy mẫu tại sườn xuống của xung đồng hồ.
- Mode 2: CPOL = 1 và CPHA = 0. Clock nghỉ ở mức cao, dữ liệu được lấy mẫu tại sườn xuống của xung đồng hồ.
- Mode 3: CPOL = 1 và CPHA = 1. Clock nghỉ ở mức cao, dữ liệu được lấy mẫu tại sườn lên của xung đồng hồ.

Các kiểu cấu hình giao thức SPI
Có 2 kiểu cấu hình SPI:
- Cấu hình Master và các Slave độc lập (Independent Slave Configuration)
- Cấu hình Daisy Chain (Daisy Chain Configuration)
Trong cấu hình Master và các Slave độc lập, Master dành riêng một đường Slave Select cho từng Slave, nên mỗi Slave có thể được chọn riêng lẻ. Tín hiệu đồng hồ của tất cả các Slave được nối chung với SCK của Master.

Trong cấu hình Daisy Chain, chỉ có một đường Slave Select được nối tới tất cả các Slave. MOSI của Master được nối với MOSI của Slave 1, MISO của Slave 1 được nối với MOSI của Slave 2, cứ như vậy… MISO của Slave cuối cùng được nối về MISO của Master.

Tuy nhiên, cấu hình Daisy Chain không phải lúc nào cũng áp dụng được cho tất cả các thiết bị Slave. Do đó, ta cần phải tham khảo datasheet trước khi tiến hành kết nối.
Cấu hình STM32 SPI trong Cube MX
Trong bài này chúng ta sẽ sử dụng bộ SPI1 ở chế độ Master và SPI2 ở chế độ Slave, truyền nhận ở chế độ ngắt và DMA.
Trong CubeMX cấu hình như sau. SYS Debug: Serial Wire.
SPI1 chế độ Full-Duplex Master, Hardware NSS Signal: Disable. Chân chọn chip ta sẽ dùng một chân GPIO khác, như vậy dễ điều khiển hơn nếu sau này sử dụng nhiều Slave.
Parameter Settings cấu hình như trong hình.

SPI2 chế độ Full-Duplex Slave, Hardware NSS Signal: Hardware NSS Input Signal, bật chân chọn chip cho SPI2.
Parameter Settings cấu hình giống SPI1.
Lưu ý: CPOL, CPHA (Clock Polarity, Clock Phase), Data Size và First Bit của Master và Slave phải giống hệt nhau. Chỉ cần lệch 1 thông số là dữ liệu nhận được sẽ bị dịch bit hoặc sai hoàn toàn, mà nhìn code thì không thấy lỗi gì.

Chọn thêm chân PA4 làm chân Chip Select (GPIO_Output) cho SPI1.

Bật ngắt cho SPI1 và SPI2.

Chọn Toolchain, đặt tên và Gen code.

Lập trình giao thức SPI trong Keil C
Trong Keil C thêm các biến u8_SPI1_TxBuff làm bộ đệm truyền SPI1, u8_SPI2_RxBuff làm bộ đệm nhận của SPI2, biến Count để thấy sự thay đổi của mảng truyền đi.

Tìm hàm HAL_SPI_RxCpltCallback, copy và dán vào phía trên hàm main(). Bên trong chúng ta kiểm tra có phải ngắt SPI2 không, sau đó bật lại ngắt nhận RX để nhận chuỗi tiếp theo.

Trước while(1) chúng ta truyền dữ liệu đầu tiên qua SPI1 bằng hàm:
HAL_SPI_Transmit(&hspi1, u8_SPI1_TxBuff, sizeofBuff, 100);
Sau đó bật nhận SPI2 bằng ngắt với hàm:
HAL_SPI_Receive_IT(&hspi2, u8_SPI2_RxBuff, sizeofBuff);
Trong while(1), copy dữ liệu vào SPI1_TxBuff.
Cho chân CS xuống 0 để chọn chip, gửi dữ liệu, rồi cho chân CS về 1 để kết thúc quá trình gửi.
Tăng biến Count và delay 1s.

Nhấn Build và nạp chương trình. Kết nối phần cứng trên cùng 1 board Blue Pill như sau:
- PA5 (SPI1_SCK) nối PB13 (SPI2_SCK)
- PA6 (SPI1_MISO) nối PB14 (SPI2_MISO)
- PA7 (SPI1_MOSI) nối PB15 (SPI2_MOSI)
- PA4 (CS của SPI1) nối PB12 (SPI2_NSS)
Với SPI, chân cùng tên nối với nhau (MOSI với MOSI, MISO với MISO), khác với UART phải nối chéo nhé.

Vào chế độ Debug, chuột phải vào 2 Buffer truyền và nhận, chọn Add to Watch 1.

Nhấn Run và xem kết quả.

Giá trị truyền và nhận đã giống nhau rồi, đúng không nào.
Nhận dữ liệu bằng DMA
Trong SPI2 ta chuyển sang tab DMA Settings, nhấn Add.
DMA Request là SPI2_RX, Mode là Circular (ta chỉ cần gọi hàm nhận DMA 1 lần, không cần gọi lại trong ngắt).
Data Width là Byte.

Trong Keil C, comment hoặc xóa phần xử lý ngắt (không dùng tới, hoặc dùng để làm tác vụ nào đó khi nhận xong).
Phần trước while(1) thay hàm HAL_SPI_Receive_IT bằng HAL_SPI_Receive_DMA.

Nhấn Build và nạp chương trình, chạy Debug để xem kết quả.

Kết luận
Giao thức SPI là một trong những giao thức cơ bản và phổ biến nhất trong lập trình vi điều khiển. Với SPI bạn có thể giao tiếp với nhiều loại thiết bị khác nhau như: TFT LCD, RFID, thẻ nhớ, Camera…
Giao thức SPI có thể được bắt gặp trong hầu hết các sản phẩm nhúng, hãy rèn luyện và làm chủ giao thức này nhé.


anh có thể làm về giao tiếp stm32 với thẻ sd card qua spi đc ko ạ.
Sẽ có bài đấy sau tết nhé
ad làm bài giao tiếp stm32 với thẻ sd card giúp em với ad ơi
Em làm theo không biết sao chỉ có các ký tự từ 0 đến 12 là bên truyền và nhận giống nhau, còn từ 13 đến 19 thì khác nhau?
Nếu độ dài chuỗi mỗi lần truyền khác nhau thì nên xóa buffer trc khi nhận dữ liệu mới nhé
anh ơi em làm theo nhưng trong phần u8_SPI2_Rxbuff chỉ có kí tự 0 là chữ ‘g’ còn lại từ 1-19 nó k hiện gì ạ
Em thử down phần example anh viết rồi chạy thử xem