• 【MM32F5270】RT-Thread SPI 驱动适配指南


    本文记录了我在社区“Rice我叫加饭?”大佬移植的RT-Thread系统源码基础上,为MM32F5370开发板添加SPI驱动支持的过程。适配完成后,我使用W25Q128模组对SPI驱动的正确性进行了验证。这是我第一次给RT-Thread添加芯片SPI驱动,本文试图尽可能详细的描述整个适配过程。希望初学者通过阅读本文,能够复现本文描述的整个过程,或者参考本文可以为其他芯片添加RT-Thread SPI驱动支持。

    drv_spi.h

    目前RT-Thread的bsp中,STM32驱动貌似是最为完整的。因此,决定开始MM32F5270的RT-Thread SPI驱动适配后,我首先参考了RT-Thread主仓STM32 BSP下面的drv_spi.c和drv_spi.h文件。

    rt_hw_spi_device_attach接口声明

    首先是drv_spi.h文件,它需要向外部提供一个rt_hw_spi_device_attach接口。该用于附加一个SPI设备到SPI总线上,并将SPI设备的片选GPIO端口和pin脚编号传入。函数声明如下:

    rt_err_t rt_hw_spi_device_attach(const char *bus_name,
                                     const char *device_name,
                                     GPIO_Type *cs_gpio,
                                     uint16_t cs_pin);
    
    • 1
    • 2
    • 3
    • 4

    这里的GPIO_Type,是MindSDK中的类型,不同于STM32。

    STM32的drv_spi.h文件中还有其他类型的定义,仔细查看发现仅在drv_spi.c中使用,别的文件并没有用到。因此,另外的那些类型定义,放到.c文件中更合适。

    从这个接口这里可以看出来,RT-Thread的多个SPI设备是可以公用一个SPI总线的。具体使用 放肆可以参考官方文档:SPI设备 (rt-thread.org)

    drv_spi.c

    通过总结STM32的drv_spi.c文件,可以知道在drv_spi.c文件中,我们需要实现如下内容:

    • rt_hw_spi_device_attach 接口
    • rt_spi_ops 接口,包括configure和xfer两个接口
    • SPI总线设备注册,并将rt_spi_ops 实例绑定到SPI总线设备

    好了,接下来就可以动手实现这些内容了。

    SPI总线配置

    SPI总线配置类型定义:

    struct mm32_spi_config
    {
        SPI_Type *spi_handle;
        const char *bus_name;
        rt_uint32_t clock_freq; // 主模式才会用到,将传给 SPI_Master_Init_Type.ClockFreqHz
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    查阅MM32F5270 SDK的hal_spi.h文件,可以知道MM32F5270一共有三个SPI。其中,SPI1在片内的APB2总线上,SPI2和SPI3在片内的APB1总线上。当然,查看数据手册的系统框图也可以知道:

    system_block_diagram.png

    因此,SPI总线基础配置数组,定义如下:

    static struct mm32_spi_config spi_config[] = {
        {SPI1, "spi1", CLOCK_APB2_FREQ},
        {SPI2, "spi2", CLOCK_APB1_FREQ},
        {SPI3, "spi3", CLOCK_APB1_FREQ},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    类似的,可以定义用于索引数组的枚举:

    enum
    {
        SPI1_INDEX,
        SPI2_INDEX,
        SPI3_INDEX,
        SPI_MAX
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这里为了代码清晰,暂时没有添加每个SPI的配置项。后期调通之后,可以通过Kconfig为每个SPI添加配置项;只在配置打开后,才编译相关代码。

    SPI总线对象

    SPI总线对象类型定义,如下:

    struct mm32_spi
    {
        SPI_Type *spi_handle;
        struct mm32_spi_config *soc_cfg;            // SPI总线配置,用于给底层接口传参数
        const struct rt_spi_configuration *drv_cfg; // RT-Thread SPI驱动框架配置,由上层传下来
        struct rt_spi_bus spi_bus;
    };
    
    static struct mm32_spi spi_bus_obj[SPI_MAX] = {0}; // rt_hw_spi_bus_init 中会用到
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    SPI总线设备注册

    有了前面的类型定义,就可以实现SPI总线设备注册了。模仿STM32,编写rt_hw_spi_bus_init函数,并让其在启动时自动执行:

    static int rt_hw_spi_bus_init(void)
    {
        rt_err_t result = RT_EOK;
    
        for (rt_size_t i = 0; i < SPI_MAX; i++)
        {
            spi_bus_obj[i].soc_cfg = &spi_config[i];
            spi_bus_obj[i].spi_bus.parent.user_data = &spi_config[i];
            spi_bus_obj[i].spi_handle = spi_config[i].spi_handle;
    
            result = rt_spi_bus_register(&spi_bus_obj[i].spi_bus, spi_config[i].bus_name, &mm32_spi_ops);
            RT_ASSERT(result == RT_EOK);
    
            LOG_D("SPI bus device %s register done!", spi_config[i].bus_name);
        }
        return result;
    }
    INIT_BOARD_EXPORT(rt_hw_spi_bus_init);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    rt_hw_spi_bus_init函数中,调用了rt_spi_bus_register函数,向系统注册了spi1、spi2、spi3设备;同时,将SPI总线对象数组(spi_bus_obj)中的部分指针成员指向spi_config数组中的成员,方便后续其他接口中进行操作。

    这里的mm32_spi_ops,定义如下:

    static const struct rt_spi_ops mm32_spi_ops = {
        .configure = mm32_spi_configure,
        .xfer = mm32_spi_xfer,
    };
    
    • 1
    • 2
    • 3
    • 4

    rt_hw_spi_device_attach接口实现

    类似的,可以实现rt_hw_spi_device_attach函数:

    static const struct
    {
        GPIO_Type *gpio;
        rt_uint32_t rcc;
    } gpio_rcc_table[] = { // 用于初始化对应的GPIO AHB时钟
        {GPIOA, RCC_AHB1_PERIPH_GPIOA},
        {GPIOB, RCC_AHB1_PERIPH_GPIOB},
        {GPIOC, RCC_AHB1_PERIPH_GPIOC},
        {GPIOD, RCC_AHB1_PERIPH_GPIOD},
        {GPIOE, RCC_AHB1_PERIPH_GPIOE},
        {GPIOF, RCC_AHB1_PERIPH_GPIOF},
        {GPIOG, RCC_AHB1_PERIPH_GPIOG},
        {GPIOH, RCC_AHB1_PERIPH_GPIOH},
        {GPIOI, RCC_AHB1_PERIPH_GPIOI},
    };
    
    rt_err_t rt_hw_spi_device_attach(const char *bus_name, const char *device_name, GPIO_Type *cs_gpio, uint16_t cs_pin)
    {
        RT_ASSERT(bus_name != RT_NULL);
        RT_ASSERT(device_name != RT_NULL);
        RT_ASSERT(cs_gpio != RT_NULL);
    
        rt_err_t result = RT_EOK;
        struct rt_spi_device *spi_device = RT_NULL;
        struct mm32_spi_cs *spi_cs_pin = RT_NULL;
    
        // enable and reset cs GPIO clock
        for (rt_size_t i = 0; i < sizeof(gpio_rcc_table) / sizeof(gpio_rcc_table[0]); i++)
        {
            if (gpio_rcc_table[i].gpio == cs_gpio)
            {
                RCC_EnableAHB1Periphs(gpio_rcc_table[i].rcc, true);
                RCC_ResetAHB1Periphs(gpio_rcc_table[i].rcc);
                break;
            }
        }
    
        // prepare and init GPIO
        GPIO_Init_Type gpio_init;
        gpio_init.Pins = cs_pin;
        gpio_init.Speed = GPIO_Speed_50MHz;
        gpio_init.PinMode = GPIO_PinMode_Out_PushPull;
        GPIO_Init(cs_gpio, &gpio_init);
    
        // create mm32_spi_cs object
        spi_cs_pin = (struct mm32_spi_cs *)rt_calloc(sizeof(struct mm32_spi_cs), 1);
        spi_cs_pin->gpio = cs_gpio;
        spi_cs_pin->pin = cs_pin;
    
        // create spi_device object
        spi_device = (struct rt_spi_device *)rt_calloc(sizeof(struct rt_spi_device), 1);
        RT_ASSERT(spi_device != RT_NULL);
    
        // attach spi_device to spi bus
        result = rt_spi_bus_attach_device(spi_device, device_name, bus_name, (void *)spi_cs_pin);
        if (result != RT_EOK)
        {
            LOG_E("%s attach to bus %s faild, %d\n", device_name, bus_name, result);
            return result;
        }
    
        LOG_D("%s attach to bus %s done", device_name, bus_name);
        return RT_EOK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    rt_hw_spi_device_attach函数中,首先初始化了cs_gpio的时钟和功能,然后创建mm32_spi_cs和rt_spi_device对象,最后调用rt_spi_bus_attach_device,将SPI设备关联到SPI总线上,并将mm32_spi_cs对象指针作为额外参数(填充在rt_spi_device.parent.user_data上)。

    rt_spi_ops接口定义

    rt_hw_spi_bus_init函数中引用的mm32_spi_ops变量,其中记录了RT-Thread SPI驱动框架预留给芯片平台的两个SPI操作接口。struct rt_spi_ops类型定义如下:

    struct rt_spi_ops
    {
        rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *configuration);
        rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们实现的mm32_spi_configure和mm32_spi_xfer函数,它们的参数和返回值需要和rt_spi_ops里面的一致。

    rt_spi_ops.configure接口实现

    rt_spi_ops.configure接口的职责是——将上层rt_spi_configure接口的配置参数传递给底层,完成SPI相关参数设置。MM32F5270的具体实现如下:

    static rt_err_t mm32_spi_configure(struct rt_spi_device *device,
                                       struct rt_spi_configuration *configuration)
    {
        RT_ASSERT(device != RT_NULL);
        RT_ASSERT(configuration != RT_NULL);
    
        struct mm32_spi *spi_bus = rt_container_of(device->bus, struct mm32_spi, spi_bus);
        spi_bus->drv_cfg = configuration;
    
        struct mm32_spi_cs *cs_pin = (struct mm32_spi_cs *)device->parent.user_data;
        return mm32_spi_init(spi_bus, cs_pin);
    }
    
    void mm32_gpio_pin_init(GPIO_Type *gpiox, uint16_t pin, uint8_t afn, GPIO_PinMode_Type mode, GPIO_Speed_Type speed)
    {
        GPIO_Init_Type gpio_init;
        gpio_init.Pins = pin;
        gpio_init.PinMode = mode;
        gpio_init.Speed = speed;
        GPIO_Init(gpiox, &gpio_init);
        GPIO_PinAFConf(gpiox, pin, afn);
    }
    
    // 这里配置SPI实际引脚和外设时钟,可能需要根据实际情况修改
    static void mm32_spi_bus_pin_init(SPI_Type *spi_handle, rt_bool_t master_mode)
    {
        if (spi_handle == SPI3)
        {
            // SPI3, enable and reset clock
            RCC_EnableAPB1Periphs(RCC_APB1_PERIPH_SPI3, true);
            RCC_ResetAPB1Periphs(RCC_APB1_PERIPH_SPI3);
    
            // GPIOC. for PC10, PC11, PC12
            RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOC, true);
            RCC_ResetAHB1Periphs(RCC_AHB1_PERIPH_GPIOC);
    
            // PC12 GPIO_AF_6: SPI3_MOSI
            mm32_gpio_pin_init(GPIOC, GPIO_PIN_12, GPIO_AF_6, GPIO_PinMode_AF_PushPull, GPIO_Speed_50MHz);
    
            // PC11 GPIO_AF_6: SPI3_MISO
            mm32_gpio_pin_init(GPIOC, GPIO_PIN_11, GPIO_AF_6, GPIO_PinMode_In_Floating, GPIO_Speed_50MHz);
    
            if (master_mode)
            {
                // PC10 GPIO_AF_6: SPI3_SCK
                mm32_gpio_pin_init(GPIOC, GPIO_PIN_10, GPIO_AF_6, GPIO_PinMode_AF_PushPull, GPIO_Speed_50MHz);
            }
        }
        else
        {
            LOG_E("%s: invalid spi_handle, no SPI1 and SPI2 configurations!", __func__);
        }
    }
    
    // typedef enum
    // {
    //     SPI_PolPha_Alt0 = 0u, /*!< CPOL = 0, CPHA = 1, Clock line is low when idle, Data valid when at falling edge */
    //     SPI_PolPha_Alt1 = 1u, /*!< CPOL = 0, CPHA = 0, Clock line is low when idle, Data valid when at rising edge */
    //     SPI_PolPha_Alt2 = 2u, /*!< CPOL = 1, CPHA = 1, Clock line is high when idle, Data valid when at rising edge */
    //     SPI_PolPha_Alt3 = 3u, /*!< CPOL = 1, CPHA = 0, Clock line is high when idle, Data valid when at falling edge */
    // }   SPI_PolPha_Type;
    static SPI_PolPha_Type mm32_polpha_from_mode(rt_uint8_t mode)
    {
        rt_bool_t cpol = (mode & RT_SPI_CPOL) ? RT_TRUE : RT_FALSE;
        rt_bool_t cpha = (mode & RT_SPI_CPHA) ? RT_TRUE : RT_FALSE;
    
        if (cpol && cpha)
        {
            return SPI_PolPha_Alt2; // CPOL = 1, CPHA = 1
        }
    
        if (cpol)
        {
            return SPI_PolPha_Alt3; // CPOL = 1, CPHA = 0
        }
    
        if (cpha)
        {
            return SPI_PolPha_Alt0; // CPOL = 0, CPHA = 1
        }
    
        return SPI_PolPha_Alt1; // CPOL = 0, CPHA = 0
    }
    
    static rt_err_t mm32_spi_init(struct mm32_spi *spi_bus, struct mm32_spi_cs *cs_pin)
    {
        struct mm32_spi_config *soc_cfg = spi_bus->soc_cfg;
        const struct rt_spi_configuration *drv_cfg = spi_bus->drv_cfg;
        RT_ASSERT(spi_bus != RT_NULL);
        RT_ASSERT((drv_cfg->data_width % 8) == 0);
    
        // check is master mode or not
        rt_bool_t master_mode = (drv_cfg->mode & RT_SPI_SLAVE) ? RT_FALSE : RT_TRUE;
        LOG_D("master_mode=%d, peed=%d, data_width=%d", master_mode, drv_cfg->max_hz, drv_cfg->data_width);
    
        // set SPI bus pins as SPI function
        mm32_spi_bus_pin_init(soc_cfg->spi_handle, master_mode);
    
        // prepare SPI config, setup SPI master/slave
        if (master_mode)
        {
            SPI_Master_Init_Type master_cfg;
            master_cfg.ClockFreqHz = soc_cfg->clock_freq;
            master_cfg.BaudRate = drv_cfg->max_hz;
            master_cfg.PolPha = mm32_polpha_from_mode(drv_cfg->mode);
            master_cfg.DataWidth = drv_cfg->data_width;
            master_cfg.XferMode = SPI_XferMode_TxRx;
            master_cfg.AutoCS = cs_pin ? false : true;
            master_cfg.LSB = (drv_cfg->mode & RT_SPI_MSB) ? false : true;
            SPI_InitMaster(soc_cfg->spi_handle, &master_cfg);
        }
        else
        {
            SPI_Slave_Init_Type slave_cfg;
            slave_cfg.PolPha = mm32_polpha_from_mode(drv_cfg->mode);
            slave_cfg.DataWidth = drv_cfg->data_width;
            slave_cfg.XferMode = SPI_XferMode_TxRx;
            slave_cfg.AutoCS = false;
            slave_cfg.LSB = (drv_cfg->mode & RT_SPI_MSB) ? false : true;
            SPI_InitSlave(soc_cfg->spi_handle, &slave_cfg);
        }
    
        // eanble SPI master/slave
        SPI_Enable(soc_cfg->spi_handle, true);
        LOG_I("%s done!", __func__);
        return RT_EOK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    这段代码中,mm32_spi_bus_pin_init函数类似于STM32的HAL_SPI_MspInit,用于初始化SPI的实际引脚及其时钟。看了一下开发板原理图,整个板子很多引脚几乎全都被占用了。最终选了PC10、PC11、PC12引脚作为SPI3功能进行后续测试,所以这里暂时只有PC10、PC11、PC12引脚初始化为SPI3的代码。

    rt_spi_ops.xfer接口实现

    rt_spi_ops.xfer接口的职责是——和上层rt_spi_transfer/rt_spi_send/rt_spi_recv之类的发送、接收接口对接,实现SPI发送或接收功能。MM32F5270的具体实现如下:

    static rt_uint32_t mm32_spi_xfer(struct rt_spi_device *device, struct rt_spi_message *message)
    {
        rt_size_t xfer_len;
        rt_uint8_t *recv_buf;
        rt_uint8_t *send_buf;
        SPI_Type *spi = RT_NULL;
        rt_err_t stat = RT_EOK;
    
        RT_ASSERT(device != RT_NULL);
        RT_ASSERT(message != RT_NULL);
        RT_ASSERT(device->parent.user_data != RT_NULL);
    
        struct mm32_spi *spi_bus = rt_container_of(device->bus, struct mm32_spi, spi_bus);
        struct mm32_spi_cs *cs = (struct mm32_spi_cs *)device->parent.user_data;
    
        if (message->cs_take && !(device->config.mode & RT_SPI_NO_CS))
        {
            LOG_D("%s: cs_take, mode=%d", __func__, device->config.mode);
            if (device->config.mode & RT_SPI_CS_HIGH)
            {
                GPIO_SetBits(cs->gpio, cs->pin);
            }
            else
            {
                GPIO_ClearBits(cs->gpio, cs->pin);
            }
        }
    
        spi = spi_bus->spi_handle;
        xfer_len = message->length;
        recv_buf = (rt_uint8_t *)message->recv_buf;
        send_buf = (rt_uint8_t *)message->send_buf;
        LOG_D("%s: xfer_len=%d, %s", __func__, xfer_len, recv_buf ? "recv" : "send");
    
        if (message->send_buf && message->recv_buf)
        {
            // 同时发送、接收,上层需要保证 send_buf、recv_buf 可访问长度 不小于 xfer_len
            stat = _spi_xfer(spi, send_buf, recv_buf, xfer_len, SPI_TIME_OUT);
        }
        else if (message->send_buf)
        {
            // 只发送
            stat = _spi_xfer(spi, send_buf, RT_NULL, xfer_len, SPI_TIME_OUT);
        }
        else if (message->recv_buf)
        {
            rt_memset(recv_buf, 0xff, xfer_len);
            stat = _spi_xfer(spi,
                             recv_buf, // 生成足够的时钟信号
                             recv_buf, xfer_len, SPI_TIME_OUT);
        }
        else
        {
            LOG_E("%s: both send_buf and recv_buf is null!", __FUNCTION__);
            stat = RT_EIO;
        }
    
        if (message->cs_release && !(device->config.mode & RT_SPI_NO_CS))
        {
            LOG_D("%s: cs_release, mode=%d", __func__, device->config.mode);
            if (device->config.mode & RT_SPI_CS_HIGH)
            {
                GPIO_ClearBits(cs->gpio, cs->pin);
            }
            else
            {
                GPIO_SetBits(cs->gpio, cs->pin);
            }
        }
    
        if (stat != RT_EOK)
        {
            xfer_len = 0;
        }
    
        return xfer_len;
    }
    
    static rt_err_t _spi_xfer(SPI_Type *spi,
                              const rt_uint8_t *tx_buff,
                              rt_uint8_t *rx_buff,
                              rt_uint32_t length,
                              rt_uint32_t timeout)
    {
        rt_uint32_t start = rt_tick_get();
        rt_uint8_t dat = 0;
    
        if ((tx_buff == RT_NULL) || (length == 0))
        {
            return RT_EIO;
        }
    
        while (length--)
        {
            // wait for tx not full
            while (SPI_GetStatus(spi) & SPI_STATUS_TX_FULL)
            {
                if ((rt_tick_get() - start) > timeout)
                {
                    return RT_ETIMEOUT;
                }
            }
            // send a byte
            SPI_PutData(spi, *tx_buff++);
    
            // wait for rx done
            while (!(SPI_GetStatus(spi) & SPI_STATUS_RX_DONE))
            {
                if ((rt_tick_get() - start) > timeout)
                {
                    return RT_ETIMEOUT;
                }
            }
    
            // recv a byte
            dat = SPI_GetData(spi);
            if (rx_buff)
            {
                *rx_buff++ = dat;
            }
        }
        return RT_EOK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123

    _spi_xfer代码参考了SDK里面的部分代码,实现了SPI上的数据首发。

    SPI功能测试

    测试外设——W25Q128

    为了能够同时测试SPI的发送和接收功能,这里选用了SPI Flash模组,芯片为华邦的W25Q128(16MB)。模组外观(图片来自某宝):

    W25Q128FV

    板子和模块的连接关系如下图:

    board_w25q_mod.png

    SFUD和FAL组件配置

    由于RT-Thread系统本身带有SFUD(Serial Flash Universal Driver)组件、FAL(Flash Abstract Layer)组件,以及FlashDB软件包。因此,通过简单的配置就可以驱动SPI Flash模组,并使用FAL组件附带的命令对Flash芯片进行读取、写入、擦除等操作,以及使用FlashDB实现键值存储(KVDB)。

    通过使用env工具的menuconfig命令,进行配置的具体操作这里不再描述。

    使用SFUD、FAL组件,所需的具体配置项如下(menuconfig界面中可以使用键“/”进行搜索):

    CONFIG_RT_USING_FAL=y
    CONFIG_FAL_DEBUG_CONFIG=y
    CONFIG_FAL_DEBUG=1
    CONFIG_FAL_PART_HAS_TABLE_CFG=y
    CONFIG_FAL_USING_SFUD_PORT=y
    CONFIG_FAL_USING_NOR_FLASH_DEV_NAME="norflash0"
    CONFIG_RT_USING_SPI=y
    CONFIG_RT_USING_SFUD=y
    CONFIG_RT_SFUD_USING_SFDP=y
    CONFIG_RT_SFUD_USING_FLASH_INFO_TABLE=y
    CONFIG_RT_SFUD_SPI_MAX_HZ=1000000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    配置完成后,使用如下命令重新生成mdk项目:

    scons --target=mdk5
    
    • 1

    这个命令本身也会编译项目,但是需要保证rtconfig.py中的配置正确。

    MDK项目生成完成后,使用Keil编译、烧录。

    板子再次启动时,看到SFUD、FAL初始化成功的日志,则表示SPI功能正常了。

    正常的话,启动日志如下图:

    boot_fal.png

    可以看到,成功识别到。

    也可以取消drv_spi.c中的#define DEBUG注释,打开调试日志,可以看到SPI发送和接收的日志信息:

    boot_spi_debug_log.png

    FlashDB软件包测试

    首先在menuconfig界面中启用FlashDB软件包:

    RT-Thread online packages → system packages → FlashDB

    然后,进入FlashDB组件配置项,关闭暂时用不到的TSDB;

    重新生成mdk项目:

    scons --target=mdk5
    
    • 1

    编译、烧录、运行。

    测试运行如下:

    kvdb_test.png

    测试正常!

    代码仓

    文中贴出来的只是代码片段,完整代码请参考码云代码仓:https://gitee.com/MM32F5270/mm32f5270_rtt

    问题解决

    关于配置和生成项目

    这里我配置使用的是RT-Thread的env命令行工具(env.exe),跳转到项目目录后,执行menuconfig命令,进行配置的。

    除此之外,也可以在git bash中使用scons --pyconfig命令,对项目进行配置;但执行该命令之前,需要先设置ENV_ROOT环境变量,并将其设置为你的env工具所在目录,例如:

    export ENV_ROOT=/d/RT-ThreadStudio/platform/env_released/env
    
    # cmd 中使用如下命令设置:
    # set ENV_ROOT=D:\RT-ThreadStudio\platform\env_released\env
    
    • 1
    • 2
    • 3
    • 4

    没有设置ENV_ROOT环境变量的话,scons --pyconfig命令会有如下报错:
    pyconfig_failed.png

    参考链接

    1. 【极术社区】【MM32F5270开发板试用】RT-Thread的移植: https://aijishu.com/a/1060000000347637
    2. 【RT-Thread文档中心】SPI 设备: https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/spi/spi
  • 相关阅读:
    开发 dApp 的三个步骤
    [云原生] [kubernetes] K8S安装存储类 - StorageClass
    日撸 Java 三百行day54-55
    数据校验(深入篇)
    小马识途营销顾问分享百度百科词条创建的实战技巧
    Flutter命令行全局配置(command not found: flutter)
    Spring Cloud Function Spel表达式注入
    Postman(2): postman发送带参数的GET请求
    c语言入门——操作符
    交叉导轨这样使用,能省不少事!
  • 原文地址:https://blog.csdn.net/xusiwei1236/article/details/126682280