STC8H開發(十五): GPIO驅動Ci24R1無線模塊

目錄

Ci24R1 簡介

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標準的數據包,可以方便的向手機傳輸數據.

主要特性

  • 頻段: 2.4GHz ISM
  • 調製方式: GFSK/FSK
  • 數據速率: 2Mbps/1Mbps/250Kbps
  • 關斷功耗: 1uA
  • 待機功耗: 15uA
  • 快速啟動時間: ≤ 130uS
  • 內部集成高PSRR LDO
  • 寬電源電壓範圍: 1.9-3.6V
  • 寬數字I/O電壓範圍:1.9-5.25V
  • 低成本晶振: 16MHz±60ppm
  • 接收靈敏度: -83dBm @2MHz
  • 最高發射功率: 7dBm
  • 接收電流(2Mbps): 15mA
  • 發射電流(2Mbps): 12mA(0dBm)
  • 支持三線SPI接口
  • 內部集成智能ARQ基帶協議引擎
  • 收發數據硬件中斷輸出
  • 支持1bit RSSI 輸出
  • 極少外圍器件,降低系統應用成本
  • 封裝: SOP8, DFN8(220.8mm)

對標的芯片

Ci24R1對標的是2.4G SOP8芯片, 主要是面向廉價的有無線通信需求的產品, 這類芯片主要有 XN297, XN297L, XL2400/WL2400, 都是三線SPI通信, 只需要一個晶振和一兩個電容, 外圍電路極少. Ci24R1的優勢是同時支持 2.4GHz 和 BLE4.2.

這幾個型號芯片的管腳布局各有不同, 並且驅動方式也不太一樣.

Ci24R1 管腳和典型電路

管腳布局

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

電路

STC8H 驅動 Ci24R1

驅動說明

廠商提供的測試代碼, 都是基於GPIO模擬SPI驅動, 開始以為可以用硬件SPI驅動, 後來在STC8H上測試, 發現不可行, 主要存在兩個問題

  1. Ci24R1僅僅提供了一個DATA口, 對應SPI的MOSI, 但是還復用IRQ, 所以使用硬件SPI的話, 需要隨時切換MOSI pin的工作狀態
  2. STC8H的硬件SPI驅動時, 會有一半概率無法正確讀取, 得到的全是0xFF
  3. STC8H即使用GPIO模擬驅動SPI, 也必須將IO模式設置為推挽, 使用准雙向時讀寫正常, 但是發送會失敗, 尚不清楚原因

接線

示例代碼中, 使用了與硬件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)

模擬SPI基礎通信

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;
}

Ci24R1 單位元組命令, 寄存器讀寫

對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();
}

Ci24R1 的多位元組讀寫命令

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();

}

Ci24R1 的初始化

初始化有幾個需要注意的點

  1. CONFIG的最後一個bit標識是TX還是RX
  2. 地址寬度沒有特殊情況都用5 bytes
  3. payload是否變寬, 如果是, 則不需要定義每個pipe的payload寬度, 如果否, 則必須定義對應pipe的payload寬度(CI24R1_REG_RX_PW_Px), 否則不會有接收
  4. 是否變寬還會影響到 CI24R1_REG_FEATURE 中的一位
  5. 如果開啟ACK, TX地址和RX P0地址一定是一樣的, 兩個模塊之間通信可以使用完全一樣的TX和RX P0

開始測試時, 可以使用低碼率(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();
}

Ci24R1 發送

發送沿用了廠商給的例子, 在寫入發送內容, 拉高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;
}

Ci24R1 接收

也沿用了廠商的例子, 切換到輸入狀態後, 阻塞等待接收中斷. 如果測試中, 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, 可以將成本控制到非常低, 集成後也利於生產和品控.