使用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
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 
