Bài viết hôm nay tôi sẽ giới thiệu tới các bạn một chuẩn truyền thông khá phổ biến và thông dụng trong lĩnh vực lập trình nhúng. Nó được ứng dụng rộng rãi trên hầu hết các dòng chip. Đó là chuẩn truyền thông SPI. Bài viết được tôi chia làm 3 phần chính. Đầu tiên tôi sẽ giới thiệu về giao thức SPI. Tiếp theo tôi sẽ giới thiệu một các tiếp cận mới trên chuẩn SPI là “SPI mềm” và cuối cùng tôi sẽ đưa ra một ví dụ để các bạn tiện theo dõi.
I. Vậy SPI là gì?
1. Trước hết chúng ta cùng tìm hiểu SPI là gì?
SPI (Serial Peripheral Bus) là một chuẩn truyền thông nối tiếp tốc độ cao do hãng Motorola đề xuất. Đây là kiểu truyền thông Master-Slave, trong đó có 1 chip Master điều phối quá trình tuyền thông và các chip Slaves được điều khiển bởi Master vì thế truyền thông chỉ xảy ra giữa Master và Slave. SPI là một cách truyền song công (full duplex) nghĩa là tại cùng một thời điểm quá trình truyền và nhận có thể xảy ra đồng thời. SPI đôi khi được gọi là chuẩn truyền thông “4 dây” vì có 4 đường giao tiếp trong chuẩn này đó là SCK (Serial Clock), MISO (Master Input Slave Output), MOSI (Master Ouput Slave Input) và SS (Slave Select). Hình 1 thể hiện một kết SPI giữa một chip Master và 3 chip Slave thông qua 4 đường.
SCK: Xung giữ nhịp cho giao tiếp SPI, vì SPI là chuẩn truyền đồng bộ nên cần 1 đường giữ nhịp, mỗi nhịp trên chân SCK báo 1 bit dữ liệu đến hoặc đi. Đây là điểm khác biệt với truyền thông không đồng bộ mà chúng ta đã biết trong chuẩn UART. Sự tồn tại của chân SCK giúp quá trình truyền ít bị lỗi và vì thế tốc độ truyền của SPI có thể đạt rất cao. Xung nhịp chỉ được tạo ra bởi chip Master.
MISO – Master Input / Slave Output: nếu là chip Master thì đây là đường Input còn nếu là chip Slave thì MISO lại là Output. MISO của Master và các Slaves được nối trực tiếp với nhau.
MOSI – Master Output / Slave Input: nếu là chip Master thì đây là đường Output còn nếu là chip Slave thì MOSI là Input. MOSI của Master và các Slaves được nối trực tiếp với nhau.
SS – Slave Select: SS là đường chọn Slave cần giap tiếp, trên các chip Slave đường SS sẽ ở mức cao khi không làm việc. Nếu chip Master kéo đường SS của một Slave nào đó xuống mức thấp thì việc giao tiếp sẽ xảy ra giữa Master và Slave đó. Chỉ có 1 đường SS trên mỗi Slave nhưng có thể có nhiều đường điều khiển SS trên Master, tùy thuộc vào thiết kế của người dùng.
Hình 1. Giao diện SPI.
Đôi khi chuẩn SPI được sử dụng chỉ để ghi dữ liệu từ Master ra Slaver thì chân MISO sẽ không được dùng.
2. Cơ chế hoạt động: mỗi chip Master hay Slave có một thanh ghi dữ liệu 8 bits. Cứ mỗi xung nhịp do Master tạo ra trên đường giữ nhịp SCK, một bit trong thanh ghi dữ liệu của Master được truyền qua Slave trên đường MOSI, đồng thời một bit trong thanh ghi dữ liệu của chip Slave cũng được truyền qua Master trên đường MISO. Do 2 gói dữ liệu trên 2 chip được gởi qua lại đồng thời nên quá trình truyền dữ liệu này được gọi là “song công”. Hình 2 mô tả quá trình truyền 1 gói dữ liệu thực hiện bởi module SPI trong AVR, bên trái là chip Master và bên phải là Slave.
Hình 2. Quá trình truyền dữ liệu.
3. Các chế độ hoạt động
– Cực của xung giữ nhịp (Clock Polarity): được gọi tắt là CPOL là khái niệm dùng chỉ trạng thái của chân SCK ở trạng thái nghỉ. Ở trạng thái nghỉ (Idle), chân SCK có thể được giữ ở mức cao (CPOL=1) hoặc thấp (CPOL=0).
– Phase (CPHA): dùng để chỉ cách mà dữ liệu được lấy mẫu (sample) theo xung giữ nhịp. Dữ liệu có thể được lấy mẫu ở cạnh lên của SCK (CPHA=0) hoặc cạnh xuống (CPHA=1).
Sự kết hợp của SPOL và CPHA làm nên 4 chế độ hoạt động của SPI. Nhìn chung việc chọn 1 trong 4 chế độ này không ảnh hưởng đến chất lượng truyền thông mà chỉ cốt sao cho có sự tương thích giữa Master và Slave. Khi giao tiếp vi điều khiển giữa các ic khác các bạn phải chú ý xem ic đó hoạt động ở chế độ nào mà cấu hình vi điều khiển cho phù hợp
CPHA=0
CPHA=1
Hình 3. Các chế độ hoạt động của SPI.
2. Khái niệm về “SPI mềm”?
Trên mỗi dòng vi điều khiển khác nhau module SPI sẽ được tích hợp, điều khiển bởi các thanh ghi,phần cứng, IO khác nhau, đấy gọi là SPI cứng (hardware SPI). Như vậy bản chất chuẩn truyền thông SPI giống nhau trên mỗi chip nhưng lại được cài đặt và sử dụng không giống nhau. Điều này gây thêm phiền toái cho người sử dụng khi bạn bắt đầu tìm hiểu một dòng vi điều khiển mới, bạn sẽ phải nhớ các chân MISO, SS, MOSI, SCK mỗi chip khác nhau, nhớ các thanh ghi, các chế độ hoạt động và cách cài đặt trên các dòng vi điều khiển khác nhau. Có cách nào khắc phục điều này không?
Để khắc phục nhược điểm trên tôi xin giới thiệu một cách lập trình giả lập SPI cứng đó là “SPI mềm”. Thực chất SPI mềm là cách “bắt trước” bằng cách tạo ra một giao thức truyền thông giống SPI nhưng chỉ sử dụng các cổng vào ra của vi điều khiển. Như vậy chỉ với việc điều khiển GPIO của chip bạn hoàn toàn có thể thực hiện giao thức SPI. Điều này cũng có nghĩa bạn có thể sử dụng giao thức này trên bất kì vi điều khiển nào mà không cần phải nhớ thanh ghi hay các chân phần cứng.
3. Ví dụ về cách sử dụng SPI mềm
Sau đây tôi xin trình bày một cách chi tiết việc sử dụng SPI mềm trên một ví dụ để các bạn cùng tham khảo:
Hình 4: Thực hiện chuyển đổi DAC thông qua chip MCP41010.
Trên hình chip ATMEGA 16 được sử dụng như một master, còn MCP41010 là slaver. Chip MCP41010 thực chất là một DAC 8 bits giao tiếp bằng chuẩn SPI. Đầu vào là 1 số 8 bits và đầu ra là mức điện áp tương ứng trên chân PW0 (chân số 6).
· – Chân số 1: CS (chip select) tương ứng chân SS(trong chuẩn SPI) đã được giới thiệu ở trên, chip thực hiện việc giao tiếp với Master khi chân này ở mức logic 0.
· – Chân số 2 là chân SCK giữ nhịp cho đường truyền.
· – Chân số 3 SI (slaver input) là chân dữ liệu truyền từ Master sang Slaver(Giống chân MOSI), dữ liệu từ MCP41010 không phản hồi ngược lại vi điều khiển nên không có chân MISO.
· – Chân PA0 (chân 5) được sử dụng làm điện áp tham chiếu đầu ra. Chip MCP41010 thực chất là bộ chuyển đổi DAC 8 bits, đầu vào là giá trị số có giá trị từ 0-255(tương ứng 8 bits) gọi là Vin, và đầu ra cho mức điện áp tương ứng 0V-Vref(điện áp tham khảo trên chân PA0). Điện áp ta thu được trên chân PW0 được tính theo công thức:
Vra=Vin/255*Vref; Vref ở đây là giá trị điện áp tham chiếu được sử dụng trên chân PA0 (có giá trị Vref=2.7-5.5V);
· – Chi tiết chip MCP41010 tham khảo datasheet tại: https://www.allMOSIsheet.com/MOSIsheet-pdf/pdf/91948/MICROCHIP/MCP41010.html
Bước đầu tiên ta định nghĩa cho 3 chân sử dụng trong chương trình:
#define SCK PORTC.0 // chân SCK
#define MOSI PORTC.1 // Chân MOSI
#define SS PORTC.2 // Chân SS
#define MISO PORTC.3 // chân MISO trong bài không dùng tới
Khai báo các chương trình con được sử dụng trong chương trình
void SPI_init();// chương trình khởi tạo SPI
void clock();// chương trình tạo 1 xung clock
void send_byte(unsigned char MOSI);// chương trình gửi 1 byte từ ATMEGA 16 ra chip
unsigned char receiver_byte();// chương trình nhận về 1 byte MCP41010.
Sau đây chúng ta đi tìm hiểu kĩ các hàm có trong chương trình.
Đầu tiên là hàm khởi tạo SPI:
void SPI_init()
{
DDRC=0xf7; // Cấu hình chân MOSI,SS, SCK là cổng ra và MISO là cổng vào
SCK=0; // Đặt MCP41010 ở trạng thái chưa hoạt động
MOSI=0;
SS=1;
}
Hàm tạo xung clock.
void clock()
{
SCK=1;delay_us(5);
SCK=0;delay_us(5);
}
1 xung clock được phát ra thì 1 bits sẽ được gửi đi hoặc đọc về trên đường dữ liệu,tốc độ gửi hay đọc phụ thuộc vào thời gian phát 1 xung clock,các bạn sẽ tùy vào từng thông số từng chip khác nhau để thời gian delay_us(x) tương ứng.
Hàm gửi 1 byte từ ATMEGA 16 ra MCP41010
void send_byte(unsigned char data)
{
unsigned int i=0,x=0;// byte cao có giá trị 0x11 là mã lệnh ghi vào chip của MCP41010
SS=0;// cho phép chip MCP 41010 hoạt động
for(i=0;i<8;i++)// gửi ra 1 byte tương ứng 8 bits
{
x=data&0x80;// đưa bit cần truyền lên chân MOSI
if (x>0) MOSI=1;
else MOSI=0;
clock();// phát 1 xung clock gửi dữ liệu đi
data=data<<1;// dịch dữ liệu cần gửi lên 1 bits
}
SS=1;//Đã gửi xong,đưa chân SS lên mức 1
}
Các bạn thấy đó thật sự giao tiếp qua SPI mềm chương trình cực kì đơn giản dễ thực hiện. Với chip MCP 41010 thì không có chiều dữ liệu từ Slaver tới Master tuy nhiên với những chip khác có chiều dữ liệu từ Slaver tới Matster thì hàm đọc dữ liệu vô cùng đơn giản:
unsigned char receiver_byte()// chương trình nhận về 1 byte
{
unsigned char data=0x00,i=0;
while(i<8)
{
data=data<<1;// dịch dữ liệu lên 1 bit
data=data|MISO;//đọc chân MISO
clock();// phát một xung clock để đọc bit tiếp theo
}
return data;// trả về dữ liệu đọc được
}
Mình sẽ thực hiện ví dụ tạo ra 1 điện áp nửa sin có biên độ 5V trên codevision
Trước khi thực hiện ví dụ các bạn khai báo thêm một số thư viện:
#include <mega16.h>
#include <math.h>
#include <delay.h>
unsigned int i=0;
unsigned char MOSI;
float h=(3.1415/50);// tạo ra một hình sin 50 bậc trên nửa chu kì
Cuối cùng ví dụ trong thân hàm main:
void main(void)
{
SPI_init();// Khởi tạo SPI
send_byte(0x00);// Xóa sạch đầu ra
delay_ms(20);
unsigned char data=0;
while (1)
{
for(i=0;i<50;i++)
{
data=(unsigned char)((5*sin(h*i))/5.0*255);// gửi ra giá trị điện áp cần tạo ra
send_byte(0x11);// đây là mã lệnh yêu cầu ghi giữ liệu vào MCP41010
send_byte(data);// dữ liệu cần chuyển đổi
delay_ms(30);
}
}
}
Và kết quả là:
Các bạn đã tạo ra một điện áp nửa sin như hình.
Xin cám ơn.