抑扬顿挫什么意思| 人参是什么参| 肺部不好有什么症状| 亚麻色是什么颜色| norm什么意思| 神经是什么| 马来西亚有什么特产| 馋肉是身体里缺什么| 肌酐高说明什么问题| 身上长小肉揪是什么原因| 松鼠代表什么生肖| 昆虫记是什么类型的书| 低血糖是什么原因引起的| 血管疼是什么原因| 属兔配什么属相最好| 血压太低会有什么危险| 黄花菜都凉了什么意思| 眼镜框什么材质的好| 湿疹抹什么药| 蜂蜜什么时候喝最好| 肛门痒是什么原因男性| 史密斯夫妇是什么意思| 肩膀麻木是什么原因引起的| 痹病是什么意思| 红是什么生肖| hpv男性有什么症状| 寒天是什么| strange是什么意思| 无病呻吟是什么意思| 什么土方治咳嗽最有效| 肾炎吃什么药| 什么是酸性食物| 九二共识是什么| 负荆请罪是什么意思| 心肺气虚吃什么中成药| 猫对什么颜色感兴趣| 陪伴是最长情的告白下一句是什么| 什么澎湃| 猫叫是什么意思| 补肾吃什么食物最好| 什么是水中毒| 口粮是什么意思| 兔死狐悲指什么生肖| 冰山一角是什么生肖| 指甲上的白色月牙代表什么| 火腿是什么动物的腿| 什么是芝士| 吃中药忌口都忌什么| 脱线是什么意思| 淮山和山药有什么区别| 杰字属于五行属什么| 1985年属牛的是什么命| 蜂蜜是什么糖| 睾丸长什么样子| DNA是什么意思啊| 大将军衔相当于什么官| 为什么疤痕会增生| 阳虚是什么原因引起的| 米非司酮片是什么药| 低钠有什么症状和危害| 阴险表情什么意思| 海关是什么| 脂肪肝用什么药物治疗| 冥王星是什么星| 郑五行属什么| 70年产权是什么意思| vsd是什么意思| 骞读什么字| 自私自利是什么意思| 刚出生的宝宝要注意什么| 李自成为什么会失败| 吃西瓜不能吃什么| 孺子可教也什么意思| 啮齿是什么意思| 神经性头疼是什么原因造成的| 挂号特需是什么意思| 全身皮肤瘙痒是什么原因引起的| 什么样的阳光填形容词| 为什么插不进去| 医嘱是什么意思| 外婆的妈妈叫什么| 胃在什么地方| 3月24日是什么星座| 黄芪有什么作用| 血红蛋白是查什么的| 不踏实是什么意思| 眼睛晶体是什么| 处女座男生喜欢什么样的女生| 藏风聚气是什么意思| 黄芪和什么泡水壮阳| 为什么8到10周容易胎停| 文雅什么意思| 无缘无故吐血是什么原因| 白头翁是什么生肖| 早些泄挂什么科| 去医院检查怀孕挂什么科| 熠熠什么意思| 和尚代表什么生肖| 长期失眠看什么科最好| 心慌吃什么药好| 吃什么对胃好| 邦顿手表是什么档次| 枸杞加红枣泡水喝有什么功效| 黑色碳素笔是什么笔| emoji是什么意思| 放屁特别臭是什么原因| 吃生南瓜子有什么好处| 尿酸高去医院挂什么科| 杭州灵隐寺求什么最灵| 央企和国企有什么区别| 长方形纸可以折什么| 胃粘膜脱落什么症状严重吗| 流年不利什么意思| 右脚麻是什么病的前兆| 过敏性鼻炎有什么症状| 天气一热身上就痒是什么原因| 水晶眼镜对眼睛有什么好处| 芒果吃多了有什么坏处| 什么叫囊性结节| 角膜炎吃什么药| 热淋是什么病| 小孩子黑眼圈重是什么原因| 有什么好吃的菜| 什么动物最没有方向感| 结婚13年是什么婚| 结果是什么意思| 脱氢酶高是什么原因| 你喜欢我什么| 为什么排卵期会出血| 出汗是什么原因| 骨密度是查什么的| honor是什么牌子手机| 炒菜用什么油好吃又健康| 为什么会突然头晕| 休息是什么意思| 死党是什么意思| 心跳加速心慌吃什么药| 脱脂是什么意思| 3月5日是什么纪念日| 一张张什么| 爱出汗什么原因| 紫阳茶属于什么茶| 1953年是什么生肖| 什么是植物| 上钟什么意思| 争奇斗艳是什么意思| 龙和什么生肖相冲| 藿香正气水什么人不能喝| 持续是什么意思| 中央型肺ca是什么意思| 为什么会拉血| 经常口腔溃疡挂什么科| 水中毒是什么| 河南古代叫什么| 腰扭了挂什么科| 甲状腺结节什么引起的| apm是什么牌子| 检查胸部应该挂什么科| 植物神经紊乱的症状吃什么药| 什么的羊群| 干什么| 桫椤是什么植物| 病毒长什么样子| 什么情况下需要做心脏支架| 舌苔白什么原因| 男性囊肿是什么原因引起的| 黄鼠狼是什么科| 发瘟是什么意思| 总胆固醇偏低是什么意思| 谷氨酰转移酶高是什么病| 小孩腿抽筋是什么原因引起的| 切洋葱为什么会流泪| 耳道炎是什么原因引起的| 走水是什么意思| 难缠是什么意思| 水潴留是什么意思| 女人梦见蛇是什么预兆| 退行性改变是什么意思| 什锦是什么水果| 肚子疼是什么原因一阵一阵的| 什么的假山| 去海边玩需要带什么| 不拘小节是什么意思| 茹毛饮血什么意思| 组织部是干什么的| 什么茶降血脂最好| 丝瓜为什么会变黑| bgo是什么意思| 皮肤发白一块一块的是什么病| 庙会是什么意思| 丹五行属性是什么| 中蛊的人有什么症状| 金主是什么意思| 对比度是什么意思| 婴儿足底血筛查什么| 肝囊性灶是什么意思| 胃疼吃什么| 什么节日吃饺子| 补体c4偏低是什么意思| 月经期间适合吃什么水果| 焦虑是什么| 芝士是什么做的| 凶神宜忌是什么意思| 指甲很薄很软是为什么| 湦是什么意思| 济南为什么叫泉城| 口咸是什么原因引起的| 枸杞配什么壮阳| 鼻基底填充用什么材料比较好| 麻是什么| 胆囊结石有什么影响| 知识是什么意思| 包公代表什么生肖| 脚腕酸是什么原因| 一什么鱼塘| 鸟衣念什么| 假象是什么意思| 膝盖酸胀是什么原因| 祸从天降是什么生肖| 十羊九不全是什么意思| 泡脚不出汗是什么原因| 成人受到惊吓吃什么药| 漪什么意思| 高血压吃什么菜| 胃痛去药店买什么药| 胆囊炎不能吃什么食物| 牙疼吃什么饭菜比较好| 白是什么结构的字| 阑尾在什么位置| 宝宝拉黑色大便是什么原因| 虾仁配什么蔬菜包饺子| 认贼作父是什么意思| 母亲节送什么颜色的康乃馨| 蜻蜓吃什么食物| 叕什么意思| 气血是什么意思| 螃蟹一般吃什么| 神经炎用什么药| cs是什么元素| 舒张压偏低是什么原因| 子宫内膜脱落是什么意思| 520送男朋友什么礼物| 梦见死去的亲人是什么意思| 住院医师是什么意思| 罄竹难书的罄什么意思| 95年属于什么生肖| 身体缺钾吃什么可以补充| 常山现在叫什么| 坐围和臀围有什么区别| 长方脸适合什么样的发型| 奶茶有什么危害| 补锌吃什么药| 小姑子是什么关系| 脖子疼是什么原因| 发烧腿疼是什么原因| 为什么会得中耳炎| 备孕需要做些什么准备| 圣杯是什么意思| 小便尿出乳白色液体是什么问题| 男人眉毛短是什么面相| 血小板低是什么引起的| 吃什么降血糖| 八股文是什么| 开救护车需要什么驾照| 百度
这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 ? 论坛首页 ? 嵌入式开发 ? 软件与操作系统 ? rtthread软件SPI框架解析

共3条 1/1 1 跳转至

rtthread软件SPI框架解析

高工
2025-08-04 19:16:19     打赏
百度 ”  “撸起袖子加油干,用奋斗报效祖国”  这是一个伟大的新时代,这是一个奋斗的新起点。

背景

   前面分析了硬件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 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]
金灿灿的什么 天衣无缝是什么意思 夏天什么颜色最防晒 男人染上霉菌什么症状 早上起床有眼屎是什么原因
什么是逆商 肺的主要功能是什么 一直咳嗽不好是什么原因 造纸术什么时候发明的 早餐吃什么最营养
哺乳期感冒了能吃什么药 属鼠男和什么属相最配 新生儿干呕是什么原因 麻风病是什么病 做蛋糕需要什么食材
感冒鼻子不通气吃什么药 远香近臭是什么意思 柠檬片泡水喝有什么功效和作用 寒热错杂吃什么中成药 软禁是什么意思
乳腺增生是什么imcecn.com 及笄是什么意思kuyehao.com gif是什么意思mmeoe.com 水命和什么命最配hcv9jop7ns0r.cn 孩子不愿意吃饭是什么原因hcv9jop6ns2r.cn
小河虾吃什么hcv9jop2ns3r.cn 改编是什么意思hcv8jop8ns2r.cn 多吃什么可以长头发hcv9jop7ns0r.cn 肌酐高了是什么原因hcv7jop7ns2r.cn 晕血是什么原因hcv7jop7ns1r.cn
马桶对着卫生间门有什么不好jasonfriends.com 4月份是什么星座youbangsi.com 拆线去医院挂什么科hcv8jop4ns8r.cn 慵懒是什么意思hcv8jop2ns4r.cn 口香糖是什么材料做的hcv8jop3ns7r.cn
阑尾炎吃什么药见效快hcv9jop2ns5r.cn 肠穿孔有什么症状hcv8jop9ns0r.cn 血沉高说明什么hcv9jop1ns0r.cn 毛主席什么时候去世的hcv8jop1ns8r.cn 禾字五行属什么的hcv9jop2ns2r.cn
百度