Ci24R1是Si24R1的SOP8封裝簡化版, 廠商為南京中科微, 他們還有一個比較常見的型號是Si24R1, Si24R1就是應用極廣的nRF24L1的克隆版. Ci24R1的通訊協定和Si24R1, nRF24L01是相容的, 另外支援藍芽BLE4.2標準.
具體到引數上, 與nRF24L01類似, 都是2.4GHz頻段的無線通訊晶片, 官網的介紹: 低成本高效能2.4GHz 無線收發晶片(支援藍芽版). 專為低功耗無線場合設計,整合嵌入式ARQ基頻協定引擎的無線收發器晶片. 工作頻率範圍為2400MHz-2525MHz,共有126個1MHz頻寬的通道, 支援2Mbps,1Mbps,250Kbps三種資料速率, 支援發射BLE4.2標準的封包,可以方便的向手機傳輸資料.
Ci24R1對標的是2.4G SOP8晶片, 主要是面向廉價的有無線通訊需求的產品, 這類晶片主要有 XN297, XN297L, XL2400/WL2400, 都是三線SPI通訊, 只需要一個晶振和一兩個電容, 外圍電路極少. Ci24R1的優勢是同時支援 2.4GHz 和 BLE4.2.
這幾個型號晶片的管腳佈局各有不同, 並且驅動方式也不太一樣.
SOP8封裝(左) 和 DFN8封裝(右)
PIN | Name | I/O | 說明 |
---|---|---|---|
1 | CSN | DI | SPI 片選訊號 |
2 | SCK | DI | SPI 時鐘訊號 |
3 | DATA/IRQ | IO | SPI 資料輸入/輸出/中斷訊號 |
4 | XC1 | AI | 晶振輸入 |
5 | XC2 | AO | 晶振輸出 |
6 | VDD | Power | 電源(+2.1 ~ +3.6V,DC) |
7 | ANT | RF | 天線介面 |
8 | VSS | GND | 地 |
廠商提供的測試程式碼, 都是基於GPIO模擬SPI驅動, 開始以為可以用硬體SPI驅動, 後來在STC8H上測試, 發現不可行, 主要存在兩個問題
範例程式碼中, 使用了與硬體SPI一樣的Pin, 實際上換成其他Pin也一樣, 因為都是通過GPIO模擬驅動.
P35(SS, Ignored) => CSN
P34(MOSI) => DATA
P32(SPCLK) => SCK
VDD1 => 3.3V
XC1,XC2 => 16MHz OSC
GND => GND
程式碼下載地址
切換收發模式, 通過main.c中的
// 0:TX, 1:RX
#define CI24R1_MODE 1
因為涉及到對MOSI Pin的模式切換, 涉及到對CE電平的操作(暫存器寫), 這部分都用宏定義保證效能
#define CI24R1_CSN P35
#define CI24R1_MOSI P34
#define CI24R1_SCK P32
#define CI24R1_DATA_OUT() GPIO_P3_SetMode(GPIO_Pin_4, GPIO_Mode_Output_PP)
#define CI24R1_DATA_IN() GPIO_P3_SetMode(GPIO_Pin_4, GPIO_Mode_Input_HIP)
#define CI24R1_DATA_LOW() CI24R1_MOSI = 0
#define CI24R1_DATA_HIGH() CI24R1_MOSI = 1
#define CI24R1_DATA_READ() CI24R1_MOSI
#define CI24R1_CLK_LOW() CI24R1_SCK = 0
#define CI24R1_CLK_HIGH() CI24R1_SCK = 1
#define CI24R1_NSS_LOW() CI24R1_CSN = 0
#define CI24R1_NSS_HIGH() CI24R1_CSN = 1
#define CI24R1_CE_LOW() CI24R1_WriteReg(CI24R1_CMD_CE_OFF, CI24R1_CMD_NOP)
#define CI24R1_CE_HIGH() CI24R1_WriteReg(CI24R1_CMD_CE_ON, CI24R1_CMD_NOP)
void CI24R1_WriteByte(uint8_t value)
{
uint8_t i = 0;
CI24R1_CLK_LOW();
CI24R1_DATA_OUT();
for (i = 0; i < 8; i++)
{
CI24R1_CLK_LOW();
if (value & 0x80)
{
CI24R1_DATA_HIGH();
}
else
{
CI24R1_DATA_LOW();
}
CI24R1_CLK_HIGH();
value = value << 1;
}
CI24R1_CLK_LOW();
}
uint8_t CI24R1_ReadByte(void)
{
uint8_t i = 0, RxData;
CI24R1_DATA_IN();
CI24R1_CLK_LOW();
for (i = 0; i < 8; i++)
{
RxData = RxData << 1;
CI24R1_CLK_HIGH();
if (CI24R1_DATA_READ())
{
RxData |= 0x01;
}
else
{
RxData &= 0xfe;
}
CI24R1_CLK_LOW();
}
CI24R1_CLK_LOW();
return RxData;
}
對nRF24L01熟悉的都知道其暫存器讀寫的方式, 其實是兩個位元組的通訊, Ci24R1比較特殊的地方在於有一個單位元組的寫命令, 用於切換DATA Pin的模式
void CI24R1_WriteReg(uint8_t reg,uint8_t value)
{
CI24R1_NSS_LOW();
CI24R1_WriteByte(reg);
CI24R1_WriteByte(value);
CI24R1_NSS_HIGH();
}
uint8_t CI24R1_ReadReg(uint8_t reg)
{
uint8_t reg_val;
CI24R1_NSS_LOW();
CI24R1_WriteByte(reg);
reg_val = CI24R1_ReadByte();
CI24R1_NSS_HIGH();
return reg_val;
}
void CI24R1_WriteCmd(uint8_t cmd)
{
CI24R1_NSS_LOW();
CI24R1_WriteByte(cmd);
CI24R1_NSS_HIGH();
}
void CI24R1_WriteFromBuf(uint8_t reg, const uint8_t *pBuf, uint8_t len)
{
uint8_t ctr;
CI24R1_NSS_LOW();
CI24R1_WriteByte(reg);
for (ctr = 0; ctr < len; ctr++)
{
CI24R1_WriteByte(*pBuf++);
}
CI24R1_NSS_HIGH();
}
void CI24R1_ReadToBuf(uint8_t reg, uint8_t *pBuf, uint8_t len)
{
uint8_t ctr;
CI24R1_NSS_LOW();
CI24R1_WriteByte(reg);
for (ctr = 0; ctr < len; ctr++)
{
pBuf[ctr] = CI24R1_ReadByte();
}
CI24R1_NSS_HIGH();
}
初始化有幾個需要注意的點
開始測試時, 可以使用低位元速率(250Kbps)加大功率(11dB), 另外模組可以靠的近一點, 例如五六公分, 避免非程式的問題導致偵錯失敗
void CI24R1_Init(void)
{
CI24R1_CE_LOW();
#if (CI24R1_PLOAD_WIDTH == 0)
// Enable dynamic payload length on pipe 0 and pipe 1
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_DYNPD, 0x03);
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_FEATURE, 0x07);
#else
// Fixed payload length
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_DYNPD, 0x00);
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_FEATURE, 0x03);
// Length of pipe 0
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_RX_PW_P0, CI24R1_PLOAD_WIDTH);
// Length of pipe 1
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_RX_PW_P1, CI24R1_PLOAD_WIDTH);
#endif
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_CONFIG, 0x0E);
// Enable auto ack all pipes
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_EN_AA, 0x3F);
// Enable all pipes
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_EN_RXADDR, 0x3F);
// Address width, 0x1:3bytes, 0x02:4bytes, 0x3:5bytes
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_SETUP_AW, 0x03);
// Resend 500us and 3 times. interval: 250us * ([0, 15] + 1), retries: [0, 15]
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_SETUP_RETR, (0x01 << 4) | 0x03);
// RF Data Rate 250K 11db
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_RF_SETUP, CI24R1_RF_SETUP_1M | CI24R1_RF_SETUP_11DB);
CI24R1_CE_HIGH();
}
傳送沿用了廠商給的例子, 在寫入傳送內容, 拉高CE後, 立即切換IO到輸入狀態等待傳送結果的中斷. 如果是MAX_RT中斷, 說明傳送失敗, 需要清空TX_FIFO和標誌位.
void CI24R1_SetTxMode(void)
{
uint8_t value;
value = CI24R1_ReadReg(CI24R1_CMD_R_REGISTER | CI24R1_REG_CONFIG);
value &= 0xFE;
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_CONFIG, value);
}
uint8_t CI24R1_Tx(uint8_t *ucPayload, uint8_t length)
{
uint8_t status;
#if (CI24R1_PLOAD_WIDTH == 0)
CI24R1_WriteFromBuf(CI24R1_CMD_W_TX_PAYLOAD, ucPayload, length);
#else
CI24R1_WriteFromBuf(CI24R1_CMD_W_TX_PAYLOAD, ucPayload, CI24R1_PLOAD_WIDTH);
#endif
CI24R1_CE_HIGH();
CI24R1_WriteCmd(CI24R1_CMD_SELIRQ);
CI24R1_DATA_IN();
while (CI24R1_DATA_READ());
CI24R1_DATA_OUT();
CI24R1_WriteCmd(CI24R1_CMD_SELSPI);
status = CI24R1_ReadStatus();
if (status & CI24R1_FLAG_MAX_RT)
{
CI24R1_WriteReg(CI24R1_CMD_FLUSH_TX, CI24R1_CMD_NOP);
}
// Clear status flags
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_STATUS, status);
return status;
}
也沿用了廠商的例子, 切換到輸入狀態後, 阻塞等待接收中斷. 如果測試中, SPI讀寫沒問題, 距離也夠近, 但是一直沒中斷, 可以檢查一下
兩個模組的TX地址和RX_P0地址, RF Channel是否一致, 是否開啟了對應RX Pipe, 如果是固定寬度, 是否在對應的接收pipe上正確設定了.
void CI24R1_SetRxMode(void)
{
uint8_t value;
value = CI24R1_ReadReg(CI24R1_CMD_R_REGISTER | CI24R1_REG_CONFIG);
value |= 0x01;
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_CONFIG, value);
}
uint8_t CI24R1_Rx(void)
{
uint8_t i, status, rxplWidth;
CI24R1_WriteReg(CI24R1_CMD_FLUSH_RX, CI24R1_CMD_NOP);
CI24R1_WriteReg(CI24R1_CMD_SELIRQ, CI24R1_CMD_NOP);
CI24R1_DATA_IN();
while(CI24R1_DATA_READ());
CI24R1_DATA_OUT();
CI24R1_WriteReg(CI24R1_CMD_SELSPI, CI24R1_CMD_NOP);
status = CI24R1_ReadStatus();
UART1_TxChar('>');
UART1_TxHex(status);
if (status & CI24R1_FLAG_RX_READY)
{
#if CI24R1_PLOAD_WIDTH == 0
rxplWidth = CI24R1_ReadReg(CI24R1_CMD_R_RX_PL_WID);
#else
rxplWidth = CI24R1_PLOAD_WIDTH;
#endif
// Read RX to buffer
CI24R1_ReadToBuf(CI24R1_CMD_R_RX_PAYLOAD, xbuf, rxplWidth);
// Clear status flags
CI24R1_WriteReg(CI24R1_CMD_W_REGISTER | CI24R1_REG_STATUS, status);
UART1_TxChar('>');
for (i = 0; i < rxplWidth; i++)
{
UART1_TxHex(*(xbuf_data + i));
}
}
return status;
}
測試中Ci24R1的通訊還是比較穩定的, 因為IO轉換加上模擬SPI, 通訊的速率和4線SPI的nRF24L01和Si24R1比肯定會有差距, 好處是省了一個IO.
這種晶片市場指向非常明顯, 就是面向成本和尺寸敏感的市場, 僅需要GPIO就能使用, 幾乎所有的MCU都能相容. 廉價的玩具和家用電器的遙控, 這些產品大量使用8pin的8位元MCU, 這種MCU總共只有6個可用IO, 省一個IO就能增加不少可能性. 市場上還有同型別整合了MCU的型號, 例如XL2401, XL2402, SOP16封裝連無線帶MCU不到1.4CNY, 可以將成本控制到非常低, 整合後也利於生產和品控.