只出不进什么意思| 单招是什么学历| 负罪感什么意思| 吃什么长头发快| 时尚是什么意思| 什么产品美白效果最好最快| 弦脉是什么意思| 时点是什么意思| 北京晚上有什么好玩的景点| 滴虫性阴道炎吃什么药| 吃什么对肝好| 春风十里不如你什么意思| 中戏是什么学校| 我好想你是什么歌| 看对眼是什么意思| 百日咳是什么| 梅肉是什么肉| 女人左下眼皮跳是什么预兆| 天克地冲是什么意思| 榴莲的寓意是什么意思| 老人手抖是什么原因| 预估是什么意思| 黑科技是什么意思| 鼻窦炎挂什么科| 脚底红是什么原因| 山合念什么| 咽喉肿痛吃什么消炎药| 小心眼是什么意思| 鼻子大说明什么| 杏仁吃了有什么好处| 中国的国宝是什么| marni是什么品牌| 蜻蜓点水是什么行为| 什么时候敷面膜效果最好| 桢字五行属什么| 鼻子上的痣有什么寓意| 耳鸣是什么病的前兆| 唐僧最后成了什么佛| 99年属兔的是什么命| 血糖和尿糖有什么区别| 吸烟人吃什么清肺最快| 中国四大国粹是什么| 什么是白癜风| 红斑狼疮是什么病图片| 女性尿血是什么原因引起的| 老掉头发是什么原因| 地球属于什么星系| 40岁属什么生肖| 肾上腺素有什么用| 什么叫空调病| 卡其色裙子配什么颜色上衣好看| 绯色是什么颜色| 无冕之王是什么意思| 血脂和血糖有什么区别| 腰扭伤了挂什么科| 高大上的意思是什么| 拉谷谷女装什么档次的| mc什么意思| 头皮脂溢性皮炎用什么药| 俏皮话是什么意思| 古人的婚礼在什么时候举行| 素金是什么意思| 甲亢是什么原因| 广西古代叫什么| 姨妈不正常是什么原因| ppd试验是什么意思| 朝圣者是什么意思| 嘴唇神经跳动是什么原因| 梦见掉头发是什么意思| 层出不穷是什么意思| 梦到死人了有什么兆头| 5月9号什么星座| 乳腺结节低回声是什么意思| 什么牌子的助听器好| 宫颈囊肿有什么症状表现| 洗礼是什么意思| 什么自若| 柯什么意思| 什么是干咳| 内痔用什么药治最好效果最快| 皮肤过敏忌口什么食物| o型血的孩子父母是什么血型| 儿童便秘吃什么药| 良字少一点是什么字| 突然头晕眼花站不稳是什么原因| 腿部浮肿吃什么药| 文火是什么火| 被隐翅虫咬了涂什么药| 测子女缘什么时候到来| 胸疼挂什么科室| 什么昆虫最值钱| 书香门第的书香指什么| 什么的面目| 容易犯困是什么原因| 如花似玉是什么生肖| 七月22号是什么星座| 大步向前走永远不回头是什么歌| 不晨勃是什么原因| 香奶奶是什么牌子| 天伦之乐是什么意思| pku什么意思| 什么是卒中| 人工智能是什么意思| 灵媒是什么意思| 扁桃体肥大有什么影响| 什么是直男| 9月14号是什么星座| tfboys什么意思| 过期酸奶有什么用| 05年属鸡的是什么命| 身上有红色的小红点是什么原因| 什么是包皮手术| 子宫痒是什么原因| 梦见很多鱼是什么意思| 脓血症是什么病严重吗| 东南大学什么专业最牛| 血热吃什么药| 指什么生肖| 跳舞有什么好处| 梯子是什么| nicu是什么意思| 汗青是什么意思| 韧带和筋有什么区别| 什么是超声检查| 牙痛吃什么好| 慢性胰腺炎吃什么药| 养心吃什么食物好| 买买提是什么意思| 葡萄糖阴性什么意思| 什么水果蛋白质含量高| 富强粉是什么面粉| 不拘小节是什么意思| 紧急避孕药什么时候吃最有效| 不齿是什么意思| 增加免疫力吃什么好| hc是什么意思| 企业hr是什么意思| 红红的苹果像什么句子| 女生学什么专业好| 92年的属什么| 肋骨疼是什么原因| 现在什么最赚钱| 什么什么的沙滩| 副研究员什么级别| 胃潴留是什么病| 一什么冰雹| 党委常委是什么级别| 生化是检查什么的| 舌头烧灼感是什么原因| 嘴唇上火起泡是什么原因| 黑暗料理是什么意思| 殊胜的意思是什么| 消炎药是什么药| 肚子痛什么原因| 梦见孩子哭是什么意思| 封神榜是什么| 什么是慢阻肺| 一月14号是什么星座| 堪堪是什么意思| 五味子不适合什么人喝| 211是什么| 脸过敏吃什么药| 眼睛肿痛什么原因| f什么意思| 什么是钙化| 眼睛红了是什么原因| 感染hpv用什么药| 喝什么降血糖| 胡子长的快是什么原因| 什么是医美| 圆脸适合什么眼镜| 土是念什么| 头发一半白一半黑是什么原因| 元旦唱什么歌| 巴氏征阳性是什么意思| 九月29号是什么星座| 纪念礼物送什么好| 86年属虎是什么命| 6月初三是什么日子| 枫叶什么颜色| 虾虎鱼吃什么| 飞机票号是什么意思| 右边小腹疼是什么原因| 小脑萎缩吃什么药好| 食指中指交叉什么意思| 屁股长痘痘用什么药膏| 小结节是什么意思| 决明子是什么东西| 孙红雷的老婆叫什么名字| 3s是什么意思| 轻奢什么意思| 操逼什么意思| 1月16日是什么星座| 金晨为什么叫大喜| 病理会诊是什么意思| 拘泥是什么意思| 窦骁的父母是干什么的| 紫外线过敏什么症状| 点睛之笔是什么意思| 菁字五行属什么| 职业病是什么意思| 人死了是什么感觉| 我国的国花是什么| 吃饭就吐是什么原因| 男性硬不起来什么原因| 晨跑有什么好处| 肝肿瘤吃什么食物好| 子午相冲是什么生肖| 味增是什么| 伟哥叫什么| 孩子胆子小用什么方法可以改变| 发际线长痘痘是什么原因| 1997年是什么生肖| 开水烫伤用什么药膏好得快| 救世主是什么意思| 浙江有什么城市| 特别想睡觉是什么原因| 克罗恩病有什么症状| 什么破壁机好用| 什么人不能吃石斛| 2022年属虎的是什么命| 十滴水泡脚有什么好处| 溃疡吃什么水果| 什么是禅定| 什么无比| 贝珠是什么| 孩子肚脐眼下面疼是什么原因| 云南古代叫什么| 西葫芦炒什么好吃| 掉头发是什么原因导致的| 认识是什么意思| 88年什么命| 为什么嗓子总有痰| fed是什么意思| 牛与什么生肖最配| 冰丝纤维是什么面料| 吃什么长头发| 豆角不能和什么一起吃| 探望病人买什么水果| 沸去掉三点水念什么| 手心痒痒是什么预兆| 马鲛鱼是什么鱼| 什么水果可以解酒| 嗔是什么意思| 江西古代叫什么| 男人更年期吃什么药| 石楠花是什么| 浣碧什么时候背叛甄嬛| 经期可以喝什么茶| 胃炎糜烂吃什么食物好| 王炸是什么意思| 猪横利是什么| 坐月子吃什么水果| 来例假吃什么好| 尿道尿血是什么原因| 蒲地蓝消炎片治什么病| 晚上睡觉老做梦是什么原因| 出院记录是什么| 羊胡子疮用什么药膏| 低烧吃什么药最好| 牛市是什么意思| 异类是什么意思| 身上老出汗是什么原因引起的| 稀料对人体有什么危害| 百度
这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 ? 论坛首页 ? 嵌入式开发 ? 软件与操作系统 ? rtthread软件SPI框架解析

共3条 1/1 1 跳转至

rtthread软件SPI框架解析

高工
2025-08-04 19:16:19     打赏
百度 日本千叶县香取市农协(JA香取)JA香取理事长竹田先生介绍说,日本目前一共有620多个农协组织。

背景

   前面分析了硬件SPI框架,明白了RTT硬件SPI的框架。而我们实际应用中,不少设备并不支持SPI,或者在做项目时,所选型的器件硬件SPI接口不够,而有需要SPI功能,这个时候可以考虑做软件SPI。而rtthread框架下,在组件层也实现了master模式的SPI适配(在这里,还是想吐槽一下现在的软件SPI实现,完全可以组件层实现的玩意,非得把gpio控制部分封装到驱动层去,纯粹的多此一举)。

RTT软件SPI框架解析

   相比较于软件I2C,个人认为软件SPI实现比软件I2C简单,本质上软件SPI还是clk和data的组合,存在三线制和四线制的区别。另外,SPI存在四种工作模式的设置。相比较于I2C通信各种加地址,加ack检测,SPI仅 仅通过CS脚的高低电平就实现了这部分处理,降低了总线资源消耗,更重要的是,SPI总线的通信逻辑变得异常简单。

公共入口

static const struct rt_spi_ops spi_bit_bus_ops =
{
    .configure = spi_bit_configure,
    .xfer      = spi_bit_xfer,
};

rt_err_t rt_spi_bit_add_bus(struct rt_spi_bit_obj *obj,
                            const char            *bus_name,
                            struct rt_spi_bit_ops *ops)
{
    obj->ops = ops;
    obj->config.data_width = 8;
    obj->config.max_hz     = 1 * 1000 * 1000;
    obj->config.mode       = RT_SPI_MASTER | RT_SPI_MSB | RT_SPI_MODE_0;

    /* idle status */
    if (obj->config.mode & RT_SPI_CPOL) SCLK_H(ops);
    else                                SCLK_L(ops);

    // 注册SPI总线设备并返回执行结果,此接口与硬件SPI注册接口一致
    return rt_spi_bus_register(&obj->bus, bus_name, &spi_bit_bus_ops);
}

注册接口

   从代码上看,软件SPI实际上还是和硬件SPI共用一套框架,对上层来说还是一样的接口,但由于其是纯逻辑的东西,因此不适合放置于驱动层实现。

   另外,我们会发现一个问题,软件SPI的入口,居然是带一堆参数的,而组件层并未写这块的入口调用,因此为了适配软件SPI,bsp层不得不对应的实现一套ops实现,这也是我吐槽的点,为啥一个纯逻辑的东西还留一些接口放硬件驱动层。

SPI配置接口

rt_err_t spi_bit_configure(struct rt_spi_device *device, struct rt_spi_configuration *configuration)
{
    struct rt_spi_bit_obj *obj = rt_container_of(device->bus, struct rt_spi_bit_obj, bus);
    struct rt_spi_bit_ops *ops = obj->ops;

    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    // 如果定义了相关io口的初始化,则初始化spi相关io口
    if(ops->pin_init != RT_NULL)
    {
        ops->pin_init();
    }

    // 未实现slave 模式的软件spi
    if (configuration->mode & RT_SPI_SLAVE)
    {
        return -RT_EIO;
    }

    // 工作模式设置
    if (configuration->mode & RT_SPI_CPOL)
    {
        SCLK_H(ops);
    }
    else
    {
        SCLK_L(ops);
    }

    // 不同速率下的延时设置
    if (configuration->max_hz < 200000)
    {
        ops->delay_us = 1;
    }
    else
    {
        ops->delay_us = 0;
    }

    // 把配置信息存储至obj->config,以便后续不同线程调用时使用
    rt_memcpy(&obj->config, configuration, sizeof(struct rt_spi_configuration));

    return RT_EOK;
}

   从配置函数上看,我们已经知道软件spi不支持从模式,而实际上,根据RTT文档描述,整个RTT的SPI框架都未考虑spi从模式这种应用场景,在使用时需要注意这块。

SPI传输接口

rt_ssize_t spi_bit_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
{
    struct rt_spi_bit_obj *obj = rt_container_of(device->bus, struct rt_spi_bit_obj, bus);
    struct rt_spi_bit_ops *ops = obj->ops;
    struct rt_spi_configuration *config = &obj->config;
    rt_base_t cs_pin = device->cs_pin;

    RT_ASSERT(device != NULL);
    RT_ASSERT(message != NULL);

#ifdef RT_SPI_BITOPS_DEBUG
    if (!ops->tog_sclk || !ops->set_sclk || !ops->get_sclk)
    {
        LOG_E("SPI bus error, SCLK line not defined");
    }
    if (!ops->set_mosi || !ops->get_mosi)
    {
        LOG_E("SPI bus error, MOSI line not defined");
    }
    if (!ops->set_miso || !ops->get_miso)
    {
        LOG_E("SPI bus error, MISO line not defined");
    }
#endif

    /* 如果定义了cs脚,则拉低cs信号,代表可以传输 */
    if (message->cs_take && (cs_pin != PIN_NONE))
    {
        LOG_I("spi take cs\n");
        rt_pin_write(cs_pin, PIN_LOW);
        spi_delay(ops);

        /* spi时钟相位初始化 */
        if (config->mode & RT_SPI_CPHA)
        {
            spi_delay(ops);
            TOG_SCLK(ops);
        }
    }

    // 数据传输实现
    if (config->mode & RT_SPI_3WIRE)
    {
        if (config->data_width <= 8)
        {
            spi_xfer_3line_data8(ops,
                                 config,
                                 message->send_buf,
                                 message->recv_buf,
                                 message->length);
        }
        else if (config->data_width <= 16)
        {
            spi_xfer_3line_data16(ops,
                                  config,
                                  message->send_buf,
                                  message->recv_buf,
                                  message->length);
        }
    }
    else
    {
        if (config->data_width <= 8)
        {
            spi_xfer_4line_data8(ops,
                                 config,
                                 message->send_buf,
                                 message->recv_buf,
                                 message->length);
        }
        else if (config->data_width <= 16)
        {
            spi_xfer_4line_data16(ops,
                                  config,
                                  message->send_buf,
                                  message->recv_buf,
                                  message->length);
        }
    }

    /* 释放 CS信号,结束spi写 */
    if (message->cs_release && (cs_pin != PIN_NONE))
    {
        spi_delay(ops);
        rt_pin_write(cs_pin, PIN_HIGH);
        LOG_I("spi release cs\n");
    }

    return message->length;
}

三线制SPI

    在初始化spi设备时,有一个参数obj->config.mode,此参数初始化时,如果给了 RT_SPI_3WIRE这个配置,则代表此SPI总线工作在3线制模式。而对应的实现入口如下:

// spi_bit_xfer --> spi_xfer_3line_data8  // config->data_width <= 8
//              --> spi_xfer_3line_data16  // 8 < config->data_width <= 16

spi_xfer_3line_data8

rt_inline rt_ssize_t spi_xfer_3line_data8(struct rt_spi_bit_ops       *ops,
                                         struct rt_spi_configuration *config,
                                         const void                  *send_buf,
                                         void                        *recv_buf,
                                         rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint8_t *send_ptr = send_buf;
        rt_uint8_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;
        rt_uint8_t send_flg = 0;

        // 若为发送数据,则置MOSI为输出,若为接收数据,则置MOSI为输入,但这写法明显存在语法漏洞
        if ((send_buf != RT_NULL) || (recv_buf == RT_NULL))
        {
            MOSI_OUT(ops);
            send_flg = 1;
        }
        else
        {
            MOSI_IN(ops);
        }

        while (size--)
        {
            rt_uint8_t tx_data = 0xFF;
            rt_uint8_t rx_data = 0xFF;
            rt_uint8_t bit  = 0;

            // 准备发送数据
            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            if (send_flg)
            {
                for (i = 0; i < 8; i++)
                {
                    // 准备发送数据
                    if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (7 - i)); }
                    else                           { bit = tx_data & (0x1 << i); }

                    // 发送数据
                    if (bit) MOSI_H(ops);
                    else     MOSI_L(ops);

                    // 延时并翻转时钟
                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    //延时并翻转时钟
                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                    {
                        TOG_SCLK(ops);
                    }
                }

                rx_data = tx_data;
            }
            else
            {
                for (i = 0; i < 8; i++)
                {
                    // 延时并翻转clk
                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    //准备接收数据
                    if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x01; }
                    else                           { rx_data >>= 1; bit = 0x80; }

                    // 接收数据
                    if (GET_MOSI(ops)) { rx_data |=  bit; }
                    else               { rx_data &= ~bit; }

                    // 延时并翻转clk
                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                    {
                        TOG_SCLK(ops);
                    }
                }

            }

            // 保存接收到的数据
            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }

        // 接收完毕,改为输出口
        if (!send_flg)
        {
            MOSI_OUT(ops);
        }
    }

    return length;
}

       这实现,老实说,槽点太多,个人认为完全可以继续优化,初步修改的实现如下(未验证效果):

rt_inline rt_ssize_t spi_xfer_3line_data8(struct rt_spi_bit_ops       *ops,
                                         struct rt_spi_configuration *config,
                                         const void                  *send_buf,
                                         void                        *recv_buf,
                                         rt_size_t                    length)
{
    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    rt_uint32_t size = length;
    int i = 0;
    
    if (send_buf != RT_NULL)
    {
        const rt_uint8_t *send_ptr = send_buf;
        rt_uint8_t tx_data = 0xFF;
        rt_uint8_t bit  = 0;

        MOSI_OUT(ops);
        while (size--)
        {
            tx_data = *send_ptr++;

            for (i = 0; i < 8; i++)
            {
                if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (7 - i)); }
                else                           { bit = tx_data & (0x1 << i); }

                if (bit) MOSI_H(ops);
                else     MOSI_L(ops);

                spi_delay2(ops);

                TOG_SCLK(ops);

                spi_delay2(ops);

                if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                {
                    TOG_SCLK(ops);
                }
            }
        }
    }
    else if(recv_buf != RT_NULL)
    {
        rt_uint8_t *recv_ptr = recv_buf;
        rt_uint8_t rx_data = 0xFF;
        rt_uint8_t bit  = (config->mode & RT_SPI_MSB) ? 0x01 : 0x80;

        MOSI_IN(ops);
            
        while (size--)
        {
            for (i = 0; i < 8; i++)
            {
                spi_delay2(ops);

                TOG_SCLK(ops);

                if (config->mode & RT_SPI_MSB) { rx_data <<= 1;}
                else                           { rx_data >>= 1;}

                if (GET_MOSI(ops)) { rx_data |=  bit; }
                else               { rx_data &= ~bit; }

                spi_delay2(ops);

                if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                {
                    TOG_SCLK(ops);
                }
            }

            *recv_ptr++ = rx_data;
        }

        MOSI_OUT(ops);
    }

    return length - size;
}

spi_xfer_3line_data16

rt_inline rt_ssize_t spi_xfer_3line_data16(struct rt_spi_bit_ops       *ops,
                                          struct rt_spi_configuration *config,
                                          const void                  *send_buf,
                                          void                        *recv_buf,
                                          rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint16_t *send_ptr = send_buf;
        rt_uint16_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;
        rt_uint8_t send_flg = 0;

        if ((send_buf != RT_NULL) || (recv_buf == RT_NULL))
        {
            MOSI_OUT(ops);
            send_flg = 1;
        }
        else
        {
            MOSI_IN(ops);
        }

        while (size--)
        {
            rt_uint16_t tx_data = 0xFFFF;
            rt_uint16_t rx_data = 0xFFFF;
            rt_uint16_t bit  = 0;

            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            if (send_flg)
            {
                for (i = 0; i < 16; i++)
                {
                    if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (15 - i)); }
                    else                           { bit = tx_data & (0x1 << i); }

                    if (bit) MOSI_H(ops);
                    else     MOSI_L(ops);

                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 15))
                    {
                        TOG_SCLK(ops);
                    }
                }

                rx_data = tx_data;
            }
            else
            {
                for (i = 0; i < 16; i++)
                {
                    spi_delay2(ops);

                    TOG_SCLK(ops);

                    if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x0001; }
                    else                           { rx_data >>= 1; bit = 0x8000; }

                    if (GET_MOSI(ops)) { rx_data |=  bit; }
                    else               { rx_data &= ~bit; }

                    spi_delay2(ops);

                    if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 15))
                    {
                        TOG_SCLK(ops);
                    }
                }

            }

            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }

        if (!send_flg)
        {
            MOSI_OUT(ops);
        }
    }

    return length;
}

    这代码如果不细看,很容易被误认为就是复制粘贴了8bit位宽的实现。而仔细看之后,会发现相比较于8bit以内的位宽,实际上这个的处理也仅仅是传输数据量多少的区别,以及接收和发送缓冲区按1字节处理还是2字节处理的区别。个人觉得有机会实现统一8bit和16bit的代码实现。

四线制SPI

     相比较于三线制SPI,四线制SPI最大的特点是支持异步传输,虽然个人认为SPI的异步传输就是个伪命题(时钟和使能由master端控制,而这个时钟和使能还时有时无的,导致异步传输带来的好处并不能明显的表示出来,反而其劣势被放大了)。

       同三线制初始化配置,若参数obj->config.mode未给RT_SPI_3WIRE这个配置,则代表此SPI总线工作在4线制模式,其对应的实现入口如下:

// spi_bit_xfer --> spi_xfer_4line_data8  // config->data_width <= 8
//              --> spi_xfer_4line_data16  // 8 < config->data_width <= 16

spi_xfer_4line_data8

rt_inline rt_ssize_t spi_xfer_4line_data8(struct rt_spi_bit_ops       *ops,
                                         struct rt_spi_configuration *config,
                                         const void                  *send_buf,
                                         void                        *recv_buf,
                                         rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint8_t *send_ptr = send_buf;
        rt_uint8_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;

        while (size--)
        {
            rt_uint8_t tx_data = 0xFF;
            rt_uint8_t rx_data = 0xFF;
            rt_uint8_t bit  = 0;

            // 获取下一个待发送的数据
            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            for (i = 0; i < 8; i++)
            {
                // 置发送标记
                if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (7 - i)); }
                else                           { bit = tx_data & (0x1 << i); }

                // 按照发送标记设置高低电平
                if (bit) MOSI_H(ops);
                else     MOSI_L(ops);

                // 时钟翻转
                spi_delay2(ops);

                TOG_SCLK(ops);

                // 置读数据位
                if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x01; }
                else                           { rx_data >>= 1; bit = 0x80; }

                // 读取接收端数据
                if (GET_MISO(ops)) { rx_data |=  bit; }
                else               { rx_data &= ~bit; }

                // 延时并准备下一次接收
                spi_delay2(ops);

                if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 7))
                {
                    TOG_SCLK(ops);
                }
            }

            // 保存接收到的数据
            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }
    }

    return length;
}

     相比较于三线制的实现,四线制的实现逻辑清晰了不少。

spi_xfer_4line_data16

rt_inline rt_ssize_t spi_xfer_4line_data16(struct rt_spi_bit_ops       *ops,
                                          struct rt_spi_configuration *config,
                                          const void                  *send_buf,
                                          void                        *recv_buf,
                                          rt_size_t                    length)
{
    int i = 0;

    RT_ASSERT(ops != RT_NULL);
    RT_ASSERT(length != 0);

    {
        const rt_uint16_t *send_ptr = send_buf;
        rt_uint16_t *recv_ptr = recv_buf;
        rt_uint32_t size = length;

        while (size--)
        {
            rt_uint16_t tx_data = 0xFFFF;
            rt_uint16_t rx_data = 0xFFFF;
            rt_uint16_t bit  = 0;

            if (send_buf != RT_NULL)
            {
                tx_data = *send_ptr++;
            }

            for (i = 0; i < 16; i++)
            {
                if (config->mode & RT_SPI_MSB) { bit = tx_data & (0x1 << (15 - i)); }
                else                           { bit = tx_data & (0x1 << i); }

                if (bit) MOSI_H(ops);
                else     MOSI_L(ops);

                spi_delay2(ops);

                TOG_SCLK(ops);

                if (config->mode & RT_SPI_MSB) { rx_data <<= 1; bit = 0x0001; }
                else                           { rx_data >>= 1; bit = 0x8000; }

                if (GET_MISO(ops)) { rx_data |=  bit; }
                else               { rx_data &= ~bit; }

                spi_delay2(ops);

                if (!(config->mode & RT_SPI_CPHA) || (size != 0) || (i < 15))
                {
                    TOG_SCLK(ops);
                }
            }

            if (recv_buf != RT_NULL)
            {
                *recv_ptr++ = rx_data;
            }
        }
    }

    return length;
}

     与三线制类似,其实四线制的16bit实现逻辑和8bit的实现逻辑差不多,唯一的区别是8bit传输变成了16bit传输,有机会做到8bit和16bit统一实现。

结论

   分析完RTT的软件spi框架,我们可以发现相比较于软件i2c框架,RTT的软件spi还有巨大的改进空间。目前明显可以看出来可以改进的点如下:

      a.所有依赖于硬件驱动的部分,完全可以做类似于软件i2c实现方法的优化,做到不需要在硬件层单独写drv_soft_spi.c接口

      b.spi 的主频和延时的对应关系,完全可以更加精细化计算

      c.数据位宽8bit和16bit的传输,是否有机会合并成一套实现?从实现上看,两个实现仅仅是传递bit数和传输变量类型的区别。

      d.三线制的实现明显未考虑清楚,从接口实现上看,问题一堆:

            i.三线制的特点决定了其只能实现单发送或单接收,因此实现上应只考虑单发送或单接收的情况

            ii.变量的置位操作位置,明显未详细考量,实现位置不在最优位置上,如bit位的置位

            iii.嵌套逻辑还可以继续优化

      e.三线制的收发实现,有机会共用四线制实现,原因是从软件实现上看,实际上三线制实现可认为是读了后不用的四线制写以及写了之后不用的四线制读

       虽然目前RTT集成的软件SPI框架代码有不少优化空间,但不可否认,这套框架确实能够实现gpio模拟SPI的功能,可以应用在硬件不支持spi的平台或spi接口数不够的项目中。






关键词: rtthread     软件     框架     spi    

专家
2025-08-04 21:00:11     打赏
2楼

谢谢分享!


专家
2025-08-04 11:26:24     打赏
3楼

感谢楼主分享


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]
早上起床喉咙有痰是什么原因 一月来两次月经是什么原因 缺钾是什么原因引起 增强免疫力打什么针 普拉提是什么意思
两重天什么意思 为什么积食发烧很难退 杜甫世称什么 眉毛下方有痣代表什么 家庭烧烤准备什么食材
排卵日和排卵期有什么区别 iq什么意思 什么样的人不适合吃人参 c4是什么驾驶证 眉目传情什么意思
背动态心电图要注意什么 小觑是什么意思 酒酿蛋什么时候吃效果最好 嗜碱性粒细胞偏低说明什么 说辞是什么意思
摸头杀是什么意思hcv8jop5ns4r.cn 药敏试验是什么意思hcv7jop9ns9r.cn 渺渺是什么意思hcv7jop7ns2r.cn 尿葡萄糖是什么意思hcv9jop1ns9r.cn 什么是神话故事hcv9jop2ns5r.cn
吃二甲双胍为什么会瘦dayuxmw.com 下巴长痘痘是什么原因hcv7jop5ns1r.cn 百合什么时候种植hcv8jop4ns8r.cn 金蝉什么时候出土hcv7jop7ns4r.cn 甲沟炎医院挂什么科bjhyzcsm.com
四方八面是什么生肖hcv9jop1ns4r.cn 出水痘吃什么药hcv9jop4ns3r.cn 妃子笑是什么茶hcv8jop5ns1r.cn 错付是什么意思hcv9jop3ns1r.cn 肝实质回声密集是什么意思hcv8jop1ns8r.cn
口渴是什么病的前兆hcv8jop7ns0r.cn 五行水多代表什么hcv8jop1ns8r.cn 玉露茶属于什么茶hcv8jop0ns1r.cn 泰国是一个什么样的国家helloaicloud.com 猴子偷桃是什么生肖hcv7jop4ns6r.cn
百度