使用Raspberry Pi 4B SPI_1接口 控制A5133做Basic T/RX Link(本篇使用SPI1接口,需手動驅動SPI)
前幾篇介紹都是使用Raspberry Pi的SPI_0接口,這方面的現成library較多,如WiringPi ,BCM2835這些Lib 默認的SPI接口都是使用SPI_0
若要使用Raspberry Pi的SPI1接口,就需改成使用linux底層的spidev接口與ioctl函數
首先先打開Raspberry Pi的SPI1接口
sudo nano /boot/firmware/config.txt
打開SPI1並定義CS pin腳位
dtoverlay=spi1-1cs,cs0_pin=16
存檔後重開RPI
查看SPI1是否成功開啟
ls /dev/spidev1.*
表示已成功開啟SPI_1
控制A5133的邏輯與行為這邊就不再贅述,基本上就如先前說明的都是些SPI的Write/Read 操作
Demo Code:<Raspberry Pi_SPI_1 FIFO mode_0.5M_1M_2M_4Mbps_Colad9p_V0.7.zip>
P.S.:V0.7為Amiccom第0.7版Ref code抄寫過來的
因為有使用到A5133的GIO2當作RPI的輸入PIN用來判斷RF WTR狀態,因此我們一樣有include bcm2835 lib,只是用來方便判斷I/O使用
全域定義SPI的相關參數,ex:使用的SPI接口,Mode, Speed
#define SPI_DEVICE "/dev/spidev1.0" // SPI1, CS0 #define SPI_MODE SPI_MODE_0 // SPI 模式 0 #define SPI_BITS_PER_WORD 8 // 每字傳輸 8 位元 #define SPI_SPEED 10000000 // SPI 速度 10 MHz
代碼重點說明:
P.S.請注意spi_fd需定義在main外面的全域變數 int spi_fd; // SPI 文件描述符
這邊只說明有關SPI如何驅動SPI1使RPI可以正常發出Write/Read等SPI Format
有關A5133的RF流程ex:InitRF,CalRF,TX.RXCMD都與之前的一樣
因為沒有lib,我們就必須手動定義每個SPI會用到的行為,底下說明
Ex:
Write 1 Byte==>使用在strobeCMD,只需要向A5133 發出1Byte的SPI Format
Write 2 Byte==>使用在對REG寫Value時,因為Amiccom的RF IC 寫入REG 值都是採用All in one CS,所以我們就必須要向A5133連續Write 2Byte的SPI Format
Write 9 Byte==>使用在對ID Reg連續"寫入"8Byte ID Code時,一樣All in one CS,1Byte Addr+8Byte Value=9Byte的SPI Format
Write 1 Byte Read 8Byte==>使用在對ID Reg連續"讀出"8Byte ID Code時,1Byte Addr+8Byte Value=9Byte的SPI Format
Write 1 Byte Read 1Byte==>使用在對REG"讀"Value時
Write 3 Byte==>使用在對FIFO Len REG"寫"Value時,因為A5133可以FIFO Ext to 4KByte,因此FIFO Len Reg又分High& Low Byte
Write "len" byte==>len表示任意數,使用在WriteTXData時,因為FIFO Len是可調的,因此就需要有的SPI Format是可調整Write長度內容
Write 1 Byte Read 64Byte==>使用在RxPacket();,對RXBuffer Reg讀出64Byte內容
int init_spi1(int *spi_fd); int write_spi1_1byte(int spi_fd, uint8_t data); int write_spi1_2bytes(int spi_fd, uint8_t *data); int write_spi1_9bytes(int spi_fd, uint8_t *data); int write1_read8_spi1(int spi_fd, uint8_t write_byte, uint8_t *read_data); int write1_read1_in_cs(int spi_fd, uint8_t write_byte, uint8_t *read_byte); int write3_spi1(int spi_fd, uint8_t *data); int write_len_spi1(int spi_fd, uint8_t *data, size_t len); int write1_read64_in_cs(int spi_fd, uint8_t write_byte, uint8_t *read_buffer);
基本上完成上述的所有相關SPI Format,就可以開始拼湊A5133的Code
底下說明SPI設定函數
範例1:RF_StrobeCmd函數,這裡會調用write_spi1_1byte函數,這裡指的write_byte=cmd
void RF_StrobeCmd(uint8_t cmd) { uint8_t write_byte=cmd; // 執行 1 字節寫入 if (write_spi1_1byte(spi_fd, write_byte) != 0) { printf("SPI 寫入CMD失敗。\n"); } else { printf("已成功向 SPI1 寫入CMD數據:0x%02X\n", write_byte); } }
這裡指的data=StrobeCmd函數write_byte=cmd
// 向 SPI1 寫入 1 字節的函數 int write_spi1_1byte(int spi_fd, uint8_t data) { uint8_t tx_buf[1] = {data}; // 傳輸緩衝區 // 設置 SPI 傳輸結構 struct spi_ioc_transfer spi_tr = { .tx_buf = (unsigned long)tx_buf, .rx_buf = 0, .len = 1, // 1 字節寫入長度 .speed_hz = SPI_SPEED,//定義SPI CLK速度 .bits_per_word = SPI_BITS_PER_WORD,//定義1Word=8bit .delay_usecs = 0, }; // 執行 1次SPI 傳輸 int ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &spi_tr); if (ret < 1) { perror("SPI 傳輸失敗"); return -1; } return 0; }
範例2:Write 1 Byte Read 8Byte
這邊舉例RF_ReadID函數,這邊會調用到write1_read8_spi函數,來完成對0x06Reg做read動作,並從MISO Pin Read 8Byte的ID Code
write_byte=0x46=Read 0x06(IDCODE_REG)
read_byte預留8byte數組
void RF_ReadID(Uint8* ptr) { uint8_t write_byte=IDCODE_REG | 0x40; uint8_t read_data[8]; // 在同一个片选信号周期内发送 1 字节并接收 8 字节 if (write1_read8_spi1(spi_fd, write_byte, read_data) != 0) { printf("SPI Read ID 失敗。\n"); } else { printf("已成功Read ID Code="); for (int i=0; i<8; i++) { *ptr++ = read_data[i]; printf("0x%02X ", read_data[i]); } printf("\n"); } }
write1_read8_spi1函數
tx_buf=write_byte
rx_buf=read_byte
最後記得將rx_buf第1Byte~第8Byte複製到read_data裡,因為SPI是全雙工,所以第0Byte的timing被write_byte占用,所以才會需要複製第1Byte~第8Byte
// 向 SPI1 寫入 1 字節並讀取 8 字節的函數 int write1_read8_spi1(int spi_fd, uint8_t write_byte, uint8_t *read_data) { uint8_t tx_buf[9] = {0}; // 傳輸緩衝區 uint8_t rx_buf[9] = {0}; // 接收緩衝區 // 設置要發送的 1 字節 tx_buf[0] = write_byte; // 設置 SPI 傳輸結構 struct spi_ioc_transfer spi_tr = { .tx_buf = (unsigned long)tx_buf, .rx_buf = (unsigned long)rx_buf, .len = 9, // 1 字節寫入 + 8 字節讀取 .speed_hz = SPI_SPEED, .bits_per_word = SPI_BITS_PER_WORD, .delay_usecs = 0, }; // 執行 SPI 傳輸 int ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &spi_tr); if (ret < 1) { perror("SPI 傳輸失敗"); return -1; } // 將接收到的數據拷貝到 read_data 中 memcpy(read_data, &rx_buf[1], 8); return 0; }
範例3:RF_ReadReg函數,這裡會調用write1_read1_in_cs函數,也就是發1byte然後Read 1 Byte Value,這用在讀取Reg數值時使用
Uint8 RF_ReadReg(Uint8 addr) { uint8_t write_byte = addr | 0x40; // 要寫入的 1 字節 uint8_t read_byte = 0; // 用於存儲讀取的 1 字節 // 執行在一次 CS 片選內的 1 字節寫入和讀取 if (write1_read1_in_cs(spi_fd, write_byte, &read_byte) != 0) { printf("SPI 1 字節寫入和 1 字節讀取失敗。\n"); } else { printf("已成功向 SPI1 寫入 1 字節:0x%02X\n", write_byte); printf("已成功從 SPI1 讀取 1 字節:0x%02X\n", read_byte); } return read_byte; }
write1_read1_in_cs
因為總SPI還是需要2Byte,因此跟之前Write1byte_Read8byte,寫法差不多,tx_buf & rx_buf都要定義2byte數組
其中tx_buf因為1byte=write_byte,另一byte沒用到就填入0x00
最後記得Return時要return rx_buf的第2字節=rx_buf[1],因為第1字節=rx_buf[0]的timing已經被tx_buf給佔用,所以要回傳時記得是回傳第2字節才對
// 在一次 CS 片選內寫入 1 字節並讀取 1 字節的函數 int write1_read1_in_cs(int spi_fd, uint8_t write_byte, uint8_t *read_byte) { uint8_t tx_buf[2] = {write_byte, 0x00}; // 傳輸緩衝區:第一個字節為寫入,第二個字節為讀取 uint8_t rx_buf[2] = {0}; // 接收緩衝區 // 設置 SPI 傳輸結構 struct spi_ioc_transfer spi_tr = { .tx_buf = (unsigned long)tx_buf, .rx_buf = (unsigned long)rx_buf, .len = 2, // 傳輸 2 字節 .speed_hz = SPI_SPEED, .bits_per_word = SPI_BITS_PER_WORD, .delay_usecs = 0, }; // 執行 SPI 傳輸 int ret = ioctl(spi_fd, SPI_IOC_MESSAGE(1), &spi_tr); if (ret < 1) { perror("SPI 傳輸失敗"); return -1; } // 將接收到的數據存入 read_byte *read_byte = rx_buf[1]; return 0; }
基本上掌握住這些,其他的write_spi1_2bytes,write_spi1_9bytes,write_len_spi1,write3_spi1,write1_read64_in_cs函數就都是差不多的形式