使用Raspberry Pi 4B控制Amiccom RF IC做Basic T/RX Link (此篇使用A5133為例)
本篇介紹使用嵌入式Raspberry Pi安裝ubuntu系統,撰寫SPI Driver控制Amiccom A5133
安裝好ubuntu系統後,我們需要先安裝Raspberry Pi相關的GPIO Driver的C lib,網上有許多推薦的Source,Ex:wiringPi,pigpio,BCM2835等等都可以,重要只要能夠驅動Raspberry pi的GPIO SPI Write/Read即可,本篇選擇安裝wiringPi的GPIO C lib
安裝步驟:
先git 下載WiringPi的安裝包
git clone https://github.com/WiringPi/WiringPi.git
下載完後切換到此資料夾,然後開始安裝
cd WiringPi sudo ./build
看到All Done字樣,表示WiringPi的lib已經安裝完成
執行"gpio -v"可以查看安裝的wiringPi版本
gpio -v
安裝好WiringPi 後基本上,我們就已經取得了驅動RPI(Raspberry Pi)的SPI最基本的Write/Read驅動
請注意WiringPi的SPI是使用RPI硬件的4 Wire SPI,這裡我們選用SPI_0,因此我們需要將RPI上的SPI0_MOSI(GPIO 10),SPI0_MISO(GPIO 9),SPI0_SCLK(GPIO 11),SPI0_CS0(GPIO 8),拉出來對接至A5133的SPI接口
之前介紹過Amiccom 的RF IC都為Slave端,且預設是3 Wire SPI,若要改成4 Wire SPI就需要使用的A5133的GIO1改成SDO Mode
詳細接線圖:
Raspberry Pi | Amiccom A5133 Module |
MOSI(GPIO 10) Output | SDI Input |
MISO(GPIO 9) Input | GIO1 Output |
SCK(GPIO 11) Output | SCK Input |
CS(GPIO 8) Output | SCS Input |
接下來我們就需要將Amiccom 提供的51核Code,移植到RPI上面
因為ubuntu上無法像在windows上可以安裝IDE的debug工具,建議可以準備台邏輯分析儀,可以同步觀察SPI的時序狀態
我們需要安裝gcc 編譯器
sudo apt install gcc
安裝完成後一樣先看一下版本
gcc --version
Demo code:
Raspberry Pi_ FIFO mode_0.5M_1M_2M_4Mbps_Colad9p_V0.7.zip
p.s.這邊v0.7版是follow Amiccom 提供的第0.7版Ref Code抄寫過去的
底下Demo code會一步一步的去對比邏輯分析儀的數據,來說明其實只要掌握住控制SPI Write/Read的方法,基本上就沒問題
把該Project會用到的.h檔都先加入進來
這邊需手動撰寫define.h,A5133reg.h,A5133Config.h,都按照Amiccom提供的Ref code照抄即可,其他的.h為系統夾帶的
#include "define.h" #include "A5133reg.h" #include "A5133Config.h" #include <wiringPi.h> #include <stdint.h> // 包含标准的整数类型定义 #include <wiringPiSPI.h> #include <stdio.h>
2.定義一個Flag_MASTER用來區分A5133要執行TX端或RX端,剩下的是定義給RX時計算封包量與誤碼量的變數
uint8_t Flag_MASTER = 1;//1=TX;0=RX uint16_t RxCnt,Err_Loss,Err_Frame; uint32_t Err_BitCnt; uint8_t tmpbuf[64];
3.定義Amiccom A5133會用到的ID CODE與Payload內容
const Uint8 BitCount_Tab[16] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4}; const Uint8 ID_Tab[8]={0xC3,0x3C,0x95,0x6A,0x36,0x75,0xC5,0x2A};//ID code //const Uint8 //code ID_Tab[8]={0x34,0x75,0xC5,0x2A,0xC7,0x33,0x45,0xEA};//ID code //const Uint8 code ID_Tab[8]={0x55,0x55,0x55,0x55,0x34,0x75,0xC5,0x6A}; //ID2 code //const Uint8 code ID_Tab[8]={0xFF,0xFF,0xFF,0xFF,0x36,0x75,0xC5,0xBA}; //ID2 code //const Uint8 code ID_Tab[8]={0x34,0x75,0xC5,0x2A,0x34,0x75,0xC5,0x2A}; //ID2 code const Uint8 KeyData_Tab[16]={0x00,0x00,0x00,0x00,0x00,0x0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; //keyData code const Uint8 FCB_Tab[20]={0x00,0x00,0x00,0x00,0x00,0x15,0x20,0x25,0x30,0x35,0x40,0x45,0x50,0x55,0x60,0x65,0x70,0x75,0x80,0x85}; //keyData code const Uint8 PN9_Tab[]= { 0xFF,0x83,0xDF,0x17,0x32,0x09,0x4E,0xD1, 0xE7,0xCD,0x8A,0x91,0xC6,0xD5,0xC4,0xC4, 0x40,0x21,0x18,0x4E,0x55,0x86,0xF4,0xDC, 0x8A,0x15,0xA7,0xEC,0x92,0xDF,0x93,0x53, 0x30,0x18,0xCA,0x34,0xBF,0xA2,0xC7,0x59, 0x67,0x8F,0xBA,0x0D,0x6D,0xD8,0x2D,0x7D, 0x54,0x0A,0x57,0x97,0x70,0x39,0xD2,0x7A, 0xEA,0x24,0x33,0x85,0xED,0x9A,0x1D,0xE0, };
4. 定義一下接下來會用到的SPI Channel '0',& SPI 速度,A5133的MAX SPI data rate up to 10 Mbps,因為是basic FIFO資料都是緩存在RF IC Buf裡,所以我們這邊使用低速1Mbps即可,這邊RPI需要先定義一隻普通I/O pin,用來讀取A5133 GIO2 Output出來的logic變化,目的是確認RF狀態
#define SPI_CHANNEL 0 // SPI channel (0 or 1) #define SPI_SPEED 1000000 // SPI speed in Hz #define GIO2 7 // Define the GPIO 4 pin number (WiringPi numbering 7)
Main Code介紹
1.初始化I/O & 設定GIO2 為Input模式(對於RPI來說),設為下來模式(Normal LOW)
2.初始化SPI部分
//INIT GPIO if (wiringPiSetup() == -1) { // Alternatively, you can use wiringPiSetupGpio() if using BCM numbering printf("Failed to initialize WiringPi\n"); return -1; } pinMode(GIO2, INPUT);//use check RF State // Enable pull-down resistor pullUpDnControl(GIO2, PUD_DOWN); //INIT SPI int fd = wiringPiSPISetup(SPI_CHANNEL, SPI_SPEED); if (fd == -1) { printf("Failed to initialize SPI\n"); } else { printf("SPI initialized successfully\n"); } delay(100);
3. 若Flag_MASTER=1,進入TX端,執行初始化A5133 RF
if(RF_Init())/* init RF */ Err_State();
詳細初始化介紹:
1.對A5133 Reset,也就是對Address 0x00,寫入0x00 Value
Uint8 RF_Init(void) { Uint8 i; Uint8 id[8] = {0}; RF_Reset();/* reset RF chip */
LA圖:
void RF_Reset(void) { RF_WriteReg(MODE_REG, 0x00); //reset RF chip MODE_REG=0x00 }
RF_WriteReg函數為2Byte SPI(1 Address+1Value)
將Address定義為data[0],Value定義為data[1]
調用 wiringPiSPIDataRW(SPI_CHANNEL, data, 2);將2 byte SPI送出
void RF_WriteReg(Uint8 addr, Uint8 dataByte) { uint8_t data[2]; data[0]=addr; data[1]=dataByte; printf("Sending data over SPI:Reg=0x%02X,Vaule=0x%02X,", data[0], data[1]); int result = wiringPiSPIDataRW(SPI_CHANNEL, data, 2); if (result == -1) { printf("Failed to send data over SPI\n"); } else { // Print the received data printf("Send Finish\n"); } }
2. 寫ID code,這邊需注意Amiccom RF IC的SPI Format,都是一次性CS拉LOW後,一連貫的Address , Value寫完後CS才有拉High,而不是每寫一個Byte,CS就拉LOW/High一次,所以撰寫時需要注意
RF_WriteID(ID_Tab);/* write ID code */
這邊Address+8Byte的ID Code總共預留9Byte的數組空間
IDCODE_REG=0x06,指向data[0],後面ID內容為之前定義過的
const Uint8 ID_Tab[8]={0xC3,0x3C,0x95,0x6A,0x36,0x75,0xC5,0x2A};//ID code
這邊將ID內容指向數組data[1]~data[8]
一次性的MOSI Output這9Byte資料給A5133
void RF_WriteID(const uint8_t *ptr) { Uint8 i; uint8_t data[9];//1byte adderss & 8 byte ID Code data[0]=IDCODE_REG;//address for (i=0; i < 8; i++) { data[i+1]= *ptr; ptr++; } printf("Sending data over SPI:Reg=0x%02X,Vaule=0x%02X,0x%02X,0x%02X,0x%02X,0x%02X,0x%02X,0x%02X,0x%02X", data[0],data[1],data[2],data[3],data[4],data[5],data[6], data[7],data[8]); wiringPiSPIDataRW(SPI_CHANNEL, data, 9); printf(",ID Send Finish\n"); }
LA圖:
3.Read ID,此動作是為了確保ID是否真正寫入A5133,Write/Read ID的目的也是為了Check SPI的MOSI/MISO是否都能正常work。
由於是4線SPI,所以我們需要先將A5133的GIO1設為SDO mode,才能正確讀取A5133回傳的MISO訊號
RF_WriteReg(GIO1_REG, 0x19);
將SPI MISO輸出的ID CODE與預設的ID_Tab做一個比對,確認是否一致正確
RF_ReadID(id); for (i=0; i<8; i++) { if (id[i] ^ ID_Tab[i]) return 1;/* fail */ }
Read ID部分,一樣跟Write ID一樣,需定義好9Byte的buffer數組(1Byte Address+8Byte Value)
buffer[0]=IDCODE_REG | 0x40,Amiccom的RF IC Address | 0x40(bit6=1)的目的就是為了去Read此Address,詳細可以參考Amiccom 手冊SPI章節(bit6=0=write mode)/(bit6=1=read mode)
這邊我們需要先把buffer[1]~buffer[8]都先定一個Dummy Value(因為這是Wiringpi SPI Lib的寫法,其他驅動lib可能有所不同,請注意)
然後再執行wiringPiSPIDataRW(SPI_CHANNEL, buffer, 9);,這邊應該wiringPi的SPI是全雙工的,所以Read/Write可以在同一段code完成
我們可以將buffer[1]~buffer[8] print出來看看是否與寫入的一致
void RF_ReadID(Uint8* ptr) { uint8_t buffer[9]; buffer[0] =(IDCODE_REG | 0x40); for (int i = 1; i < 9; i++) { buffer[i] = 0x00; // Dummy data } wiringPiSPIDataRW(SPI_CHANNEL, buffer, 9); printf("Read ID Code="); for (int i=1; i<9; i++) { *ptr++ = buffer[i]; printf("0x%02X ", buffer[i]); } printf("\n"); }
LA圖:設GIO1 REG(Address=0x0B),Value=0x19,改成SDO Mode,RPI下0x46(Read 0x06Reg),MISO開始輸出8Byte ID
4.Load Config,這邊需要注意,因為Amiccom原始提供的config GIO1並不是0x19 SDO,雖然一開始有手動修改,但這邊Load Config後,又會強制改回跟config.h一致,所以我們需要先去手動編輯A5133Config.h GIO1部分改成0x19,這樣load config後GIO1才回維持SDO Mode
Load Config部分只需要按Amiccom提供的Ref code照抄即可
void RF_Config(void) { Uint8 i; printf("Load Config Start\n"); for (i=0x01; i<=0x04; i++) RF_WriteReg(i, A5133_RFConfigTab_Main[i]); for (i=0x07; i<=0x1F; i++) RF_WriteReg(i, A5133_RFConfigTab_Main[i]); for (i=0; i<=12; i++)//0x20 code1 RF_WritePage(0x20, A5133_RFConfigTab_Addr0x20[i], i); for (i=0; i<=12; i++)//0x21 code2 RF_WritePage(0x21, A5133_RFConfigTab_Addr0x21[i], i); for (i=0; i<=5; i++)//0x22 code3 RF_WritePage(0x22, A5133_RFConfigTab_Addr0x22[i], i); for (i=0x23; i<=0x29; i++) RF_WriteReg(i, A5133_RFConfigTab_Main[i]); for (i=0; i<=12; i++)//0x2A DAS RF_WritePage(0x2A, A5133_RFConfigTab_Addr0x2A[i], i); for (i=0x2B; i<=0x35; i++) RF_WriteReg(i, A5133_RFConfigTab_Main[i]); RF_WriteReg(0x37, A5133_RFConfigTab_Main[0x37]); for (i=0; i<=11; i++)//0x38 ROM RF_WritePage(0x38, A5133_RFConfigTab_Addr0x38[i], i); for (i=0x39; i<=0x3C; i++) RF_WriteReg(i, A5133_RFConfigTab_Main[i]); RF_WriteReg(0x3E, A5133_RFConfigTab_Main[0x3E]); printf("Load Config Finish\n"); } void RF_WritePage(Uint8 addr, Uint8 wbyte, Uint8 page) { RF_WriteReg(RFANALOG_REG, (A5133_RFConfigTab_Main[0x35]&0x0F) | page<<4); RF_WriteReg(addr, wbyte); }
這邊可以使用LA自帶軟件輸出SPI 封包csv檔,即可快速對比資料是否與A5133Config.h寫入的一致
LA圖:
5.RF_TrimmedValue_Init();/* load trimming value */
此部分照Amiccom Ref Code照抄即可,為cal RF的一部分
void RF_TrimmedValue_Init(void) { Uint8 i; Uint8 trimValue[8]; //Uint8 tmp_checksum; //trimValue[0]=FBG //trimValue[1]=CTR //trimValue[2]=BDC //trimValue[3]=STM //trimValue[4]=Checksum for trimvalue[0]~trimvalue[3] //trimValue[5]=CSXTL //trimValue[6]=FBG_CP //trimValue[7]=Checksum for customer RF_WritePage(ROMP_REG, A5133_RFConfigTab_Addr0x38[9] | 0xA0, 9);//enable EFSW=1, EFRE=1 delay(5); //wait for stability Uint8 buffer[9]; // Total 9 bytes (1 byte to send + 8 bytes to read) buffer[0] = USID_REG | 0x40; // Example: 1-byte command to send for (i = 1; i < 9; i++) { buffer[i] = 0x00; // Fill the rest with dummy data (zeros) } wiringPiSPIDataRW(SPI_CHANNEL, buffer, 9); for (i = 1; i < 9; i++) { trimValue[i-1]=buffer[i]; } RF_WritePage(ROMP_REG, A5133_RFConfigTab_Addr0x38[9], 9);//disable EFSW=1, EFRE=1 if((trimValue[0] + trimValue[1]) == trimValue[4]) //case1-only FT { if((trimValue[0]!=0) && (trimValue[1]!=0)) { RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[1] & 0xE0) | trimValue[0], 1);//FBG RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[2] & 0xC0) | trimValue[1], 2);//CTR } else Err_State(); } else if((trimValue[0] + trimValue[1] + trimValue[2] + trimValue[3]) == trimValue[4]) //case2-CP+FT { if((trimValue[0]!=0) && (trimValue[1]!=0) && (trimValue[2]!=0) && (trimValue[3]!=0)) { RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[1] & 0xE0) | trimValue[0], 1);//FBG RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[2] & 0xC0) | trimValue[1], 2);//CTR RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[0] & 0x03) | (trimValue[2]<<2), 0);//BDC RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[4] & 0x40) | trimValue[3], 4);//STM } else Err_State(); } else //only CP { if((trimValue[0]==0) && (trimValue[1]!=0) && (trimValue[2]!=0) && (trimValue[3]!=0) && (trimValue[4]==0) && (trimValue[6]!=0)) { RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[1] & 0xE0) | trimValue[6], 1);//FBG RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[2] & 0xC0) | trimValue[1], 2);//CTR RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[0] & 0x03) | (trimValue[2]<<2), 0);//BDC RF_WritePage(ROMP_REG, (A5133_RFConfigTab_Addr0x38[4] & 0x40) | trimValue[3], 4);//STM } else Err_State(); } }
LA圖:
6.RF Calibration
按Amiccom Ref Code照抄即可,RF_Cal流程為固定流程,若出現校準不過,需先檢查模塊HW,請勿任意修改門檻數值,任意修改可能會造成後續射頻出現問題
Uint8 RF_Cal(void) { Uint8 tmp; Uint8 rhc,rlc,fb,fbcf,fcd; RF_StrobeCmd(CMD_PLL); //calibration @PLL state RF_WriteReg(RFANALOG_REG, 0); //IF,RSSI,RC procedure RF_WriteReg(CALIBRATION_REG, 0x23); do{ tmp = RF_ReadReg(CALIBRATION_REG)&0x23; }while(tmp); //calibration VBC,VDC procedure if(RF_Cal_CHGroup(20)) //calibrate channel group Bank I return 1; if(RF_Cal_CHGroup(60)) //calibrate channel group Bank II return 1; if(RF_Cal_CHGroup(100)) //calibrate channel group Bank III return 1; RF_StrobeCmd(CMD_STBY); //return to STBY state //for check tmp = RF_ReadReg(IFCAL1_REG); fb = tmp & 0x0F; fbcf = (tmp>>4) & 0x01; tmp = RF_ReadReg(IFCAL2_REG); fcd = tmp & 0x1F; rhc = RF_ReadReg(RXGAIN2_REG); rlc = RF_ReadReg(RXGAIN3_REG); Uint8 Mem_RH = rhc; Uint8 Mem_RL = rlc; if(fbcf) return 1;//error return 0; } Uint8 RF_Cal_CHGroup(Uint8 ch) { Uint8 tmp; Uint8 vb,vbcf,vcb,vccf; Uint8 deva,adev; RF_WriteReg(PLL1_REG, ch); RF_WriteReg(CALIBRATION_REG, 0x1C); do{ tmp = RF_ReadReg(CALIBRATION_REG)&0x1C; }while (tmp); //for check tmp = RF_ReadReg(VCOCCAL_REG); vcb = tmp & 0x0F; vccf = (tmp>>4) & 0x01; tmp = RF_ReadReg(VCOCAL1_REG); vb = tmp & 0x0F; vbcf = (tmp >>4) & 0x01; tmp = RF_ReadReg(VCODEVCAL1_REG); deva = tmp; tmp = RF_ReadReg(VCODEVCAL2_REG); adev = tmp; if(vbcf) return 1;//error return 0; }
Waiting IF,RSSI,RC Procedure
當Read CALIBRATION_REG0x02 為0x00時,表示IF,RSSI,RC 皆cal pass
並接下去運行RF_Cal_CHGroup,分別CH20 calibrate channel group Bank I,CH60 calibrate channel group Bank II,CH100 calibrate channel group Bank III
Cal完全部CHGroup後,下一個standby cmd=0xA0
for Check fb,fbcf,fcd,rhc,rlc Value
若fbcf不等於1,表示cal pass,若等於1,表示cal fail,需找出cal fail的原因
7.進入RF部分
首先設定GIO1,GIO2,CKO的相應Mode
RF_WriteReg(GIO1_REG,0x19);/* gio1-SDO */ RF_WriteReg(GIO2_REG,0x01);/* gio2-WTR */ RF_WriteReg(CKO_REG,0x02);/* cko-dck */
設定FIFO長度,並下PLL CMD
RF_FIFOLength(64-1);//64 bytes RF_StrobeCmd(CMD_PLL);
設定RF中心頻率
RF_SetCH(80);//freq=5805.001MHz 80
write data to tx fifo buffer(0x05)
Data為PN9_Tab內容,0xFF,0x83,0xDF,0x17,0x32,0x09,0x4E,0xD1...........
const Uint8 PN9_Tab[]= { 0xFF,0x83,0xDF,0x17,0x32,0x09,0x4E,0xD1, 0xE7,0xCD,0x8A,0x91,0xC6,0xD5,0xC4,0xC4, 0x40,0x21,0x18,0x4E,0x55,0x86,0xF4,0xDC, 0x8A,0x15,0xA7,0xEC,0x92,0xDF,0x93,0x53, 0x30,0x18,0xCA,0x34,0xBF,0xA2,0xC7,0x59, 0x67,0x8F,0xBA,0x0D,0x6D,0xD8,0x2D,0x7D, 0x54,0x0A,0x57,0x97,0x70,0x39,0xD2,0x7A, 0xEA,0x24,0x33,0x85,0xED,0x9A,0x1D,0xE0, };
RF_Low Voltage Reset_Check,此Function是確保A5133處於正常運作電壓1.8V以上,若<1.8V VCOCAL2_REG讀出來數值0xFF,則return 1重新進入RF_Init重跑一次
tmp = RF_ReadReg(VCOCAL2_REG); if(tmp == 0xFF)//default reset value 0xFF return 1;
下CMD_TX,此時可以看到A5133的GIO2 Pin Go High
RF_StrobeCmd(CMD_TX);
delay 10us waiting TX Modulation setting
While Wait GIO2(WTR) GO low,表示成功發送完1包TX Data
重新Reset TX FIFO Point,寫入下一包封包FIFO Data to TX FIFO Buffer(Address:0x05)
delayMicroseconds(10); // 10 microseconds delay while(digitalRead(GIO2));
到此我們已經完成使用Raspberry Pi撰寫SPI Write/Read A5133成功控制發送TX Data