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

2022-08-09 21:00:34

目錄

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, 可以將成本控制到非常低, 整合後也利於生產和品控.