草庐IT

STC8H开发(十五): GPIO驱动Ci24R1无线模块

Milton 2023-03-28 原文

目录

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, 可以将成本控制到非常低, 集成后也利于生产和品控.

有关STC8H开发(十五): GPIO驱动Ci24R1无线模块的更多相关文章

  1. ruby-on-rails - 如何在 Ruby on Rails 中实现由 JSF 2.0 (Primefaces) 驱动的 UI 魔法 - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭10年前。问题1)我想知道ruby​​onrails是否有功能类似于primefaces的gem。我问的原因是如果您使用primefaces(http://www.primefaces.org/showcase-labs/ui/home.jsf),开发人员无需担心javascript或jquery的东西。据我所知,JSF是一个规范,基于规范的各种可用实现,prim

  2. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  3. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  4. 神州数码无线产品(AC+AP)配置 - 2

    注意:本文主要掌握DCN自研无线产品的基本配置方法和注意事项,能够进行一般的项目实施、调试与运维AP基本配置命令AP登录用户名和密码均为:adminAP默认IP地址为:192.168.1.10AP默认情况下DHCP开启AP静态地址配置:setmanagementstatic-ip192.168.10.1AP开启/关闭DHCP功能:setmanagementdhcp-statusup/downAP设置默认网关:setstatic-ip-routegeteway192.168.10.254查看AP基本信息:getsystemgetmanagementgetmanaged-apgetrouteAP配

  5. ruby - 运行测试时静音 Chrome 驱动程序控制台输出 - 2

    我使用的是最新版本的Chrome(32.0.1700.107)和Chrome驱动程序(V2.8)。但是当我在Ruby中使用以下代码运行示例测试时:require'selenium-webdriver'WAIT=Selenium::WebDriver::Wait.new(timeout:100)$driver=Selenium::WebDriver.for:chrome$driver.manage.window.maximize$driver.navigate.to'https://www.google.co.in'defapps_hoverele_hover=$driver.find_

  6. node.js - 从未编写过任何自动化测试,我应该如何开始行为驱动开发? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。多年来,我一直在使用多种语言进行编程,并且认为自己总体上相当擅长。但是,我从未编写过任何自动化测试:没有单元测试,没有TDD,没有BDD,什么都没有。我已经尝试开始为我的项目编写适当的测试套件。我可以看到在进行任何更改后能够自动测试项目中所有代码的理论值(value)。我可以看到像RSpec和Mocha这样的测试框架应该如何使设置和运行所述测试变得相当容易

  7. ruby-on-rails - 在 Rails/Capybara/Poltergeist 规范中使用 url_for 将驱动程序发送到 example.com 而不是应用程序 - 2

    如果我在功能规范中调用url_for,它会返回一个以http://www.example.com/开头的绝对URL.Capybara会很乐意尝试加载该站点上的页面,但这与我的应用程序无关。以下是重现该问题的最少步骤:从这个Gemfile开始:source'https://rubygems.org'gem"sqlite3"gem"jquery-rails"gem"draper"gem"rails",'4.1.0'gem"therubyracer"gem"uglifier"gem"rspec-rails"gem"capybara"gem"poltergeist"gem"launchy"运行

  8. 驱动开发:内核无痕隐藏自身分析 - 2

    在笔者前面有一篇文章《驱动开发:断链隐藏驱动程序自身》通过摘除驱动的链表实现了断链隐藏自身的目的,但此方法恢复时会触发PG会蓝屏,偶然间在网上找到了一个作者介绍的一种方法,觉得有必要详细分析一下他是如何实现的进程隐藏的,总体来说作者的思路是最终寻找到MiProcessLoaderEntry的入口地址,该函数的作用是将驱动信息加入链表和移除链表,运用这个函数即可动态处理驱动的添加和移除问题。MiProcessLoaderEntry(pDriverObject->DriverSection,1)添加MiProcessLoaderEntry(pDriverObject->DriverSection,

  9. ruby - 事件/观察者驱动的 Ruby on Rails - 2

    我有一个适用于事件/监听器模型的应用程序。发布了几种不同类型的数据(事件),然后许多不同的事情可能需要也可能不需要对该数据(监听器)采取行动。监听器的发生没有特定的顺序,每个监听器将决定是否需要对事件采取行动。Rails应用程序有哪些工具可以完成此任务?我希望自己不必这样做(尽管我可以。这没什么大不了的。)编辑:观察者模式可能是更好的选择 最佳答案 查看EventMachine.它是一个非常流行的Ruby事件处理库。它看起来相当不错,而且很多其他库似乎都在利用它(Cramp)。这是一个很好的介绍:http://rubylearnin

  10. AT24C04、AT24C08、AT24C16系列EEPROM芯片单片机读写驱动程序 - 2

    一、概述在之前的一篇博文中,记录了AT24C01、AT24C02芯片的读写驱动,先将之前的相关文章include一下:1.IIC驱动:4位数码管显示模块TM1637芯片C语言驱动程序2.AT24C01/AT24C02读写:AT24C01/AT24C02系列EEPROM芯片单片机读写驱动程序本文记录分享AT24C04、AT24C08、AT24C16芯片的单片机C语言读写驱动程序。二、芯片对比介绍型号容量bit容量byte页数字节/页器件寻址位可寻址器件数WordAddress位数/字节数备注AT24C044k5123216A2A149/1WordAddress使用P0位AT24C088k1024

随机推荐