IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> RT-Thread (2) RTT SPI设备驱动流程 || LWIP + ENC28J60 -> 正文阅读

[嵌入式]RT-Thread (2) RTT SPI设备驱动流程 || LWIP + ENC28J60

系列文章目录

RT-Thread (1) 添加外部内存到内存管理

RT-Thread (2) RTT SPI设备驱动流程 || LWIP + ENC28J60

目录

0 概述

0.0 参考资料

0.1 硬件资源

0.2 软件资源

0.3 RTT的启动与底层驱动初始化流程

1 驱动编写

1.1 配置ENC28J60接口

1.2 RTT设备初始化

1.3 补充HAL_SPI_MspInit

2 LWIP配置

3 总结


0 概述

在上一篇内容中,我适配了STM32的外部SRAM,并将其加载到内存管理中。我目前适配RTT的板子是用于工业物联网远程控制的,因此板载了一个以太网口。我们需要开启RTT的lwip软件包。

由于本文中包含一些RTT源码解析,如果只想按步骤操作的话请按以下顺序查看

1.1 配置ENC28J60接口
1.3 补充HAL_SPI_MspInit
2 LWIP配置

0.0 参考资料

RT-Thread开发指南
RT-Thread Studio环境下lwIP+ENC28J60的启用与调试

0.1 硬件资源

MCU:STM32F103ZET6
外部RAM:XM8A51216
以太网接口芯片:ENC28J60 (10Mbps)

0.2 软件资源

开发环境:RT-Thread Studio
RT-Thread:4.0.3
lwip:2.0.3

0.3 RTT的启动与底层驱动初始化流程

在上一篇中,由于RTT并没有FSMC类型的设备,所以我们并没有涉及到RTT的驱动框架。在这里我们补充一下RTT的启动流程。
RTT的入口在【rt-thread→src→components.c】中调用了rtthread_startup();:

?

转到定义rtthread_startup()的地方,同样在该文件中,如下:

int rtthread_startup(void)
{
    rt_hw_interrupt_disable();

    /* board level initialization
     * NOTE: please initialize heap inside board initialization.
     */
    rt_hw_board_init();

    /* show RT-Thread version */
    rt_show_version();

    /* timer system initialization */
    rt_system_timer_init();

    /* scheduler system initialization */
    rt_system_scheduler_init();

#ifdef RT_USING_SIGNALS
    /* signal system initialization */
    rt_system_signal_init();
#endif

    /* create init_thread */
    rt_application_init();

    /* timer thread initialization */
    rt_system_timer_thread_init();

    /* idle thread initialization */
    rt_thread_idle_init();

#ifdef RT_USING_SMP
    rt_hw_spin_lock(&_cpus_lock);
#endif /*RT_USING_SMP*/

    /* start scheduler */
    rt_system_scheduler_start();

    /* never reach here */
    return 0;
}

RTT依次对各级进行初始化,初始化顺序参考RTT手册:

?

我们转到rt_hw_board_init()的定义。该函数在【driver→board.c】中。如下:

RT_WEAK void rt_hw_board_init()
{
    extern void hw_board_init(char *clock_src, int32_t clock_src_freq, int32_t clock_target_freq);

    /* Heap initialization */
#if defined(RT_USING_HEAP)
    rt_system_heap_init((void *) HEAP_BEGIN, (void *) HEAP_END);
#endif

    hw_board_init(BSP_CLOCK_SOURCE, BSP_CLOCK_SOURCE_FREQ_MHZ, BSP_CLOCK_SYSTEM_FREQ_MHZ);

    /* Set the shell console output device */
#if defined(RT_USING_DEVICE) && defined(RT_USING_CONSOLE)
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif

    /* Board underlying hardware initialization */
#ifdef RT_USING_COMPONENTS_INIT
    rt_components_board_init();
#endif

}

其中有我们上次讲到的内存堆初始化,hw_board_init(...)则是板级硬件的初始化。我们转到该函数的定义。在【driver→drv_common.c】中,如下:

?其中终于涉及到了HAL库相关初始化的东西,我们继续转到HAL_Init()的定义。该定义在【项目\libraries\STM32F1xx_HAL_Driver\Src\stm32f1xx_hal.c】中。

?

我们可以看到在HAL_Init()中前面都是对系统时钟以及中断进行初始化,而在第163行调用了HAL_MspInit()。继续转到HAL_MspInit()的定义。在同文件下面有一个定义,然而这个定义是一个weak类型的,也就是说如果其他地方定义了一个不是weak类型的HAL_MspInit()则执行那个。那么这个文件中的HAL_MspInit()是没意义的,我们在工程中检索一下。发现项目中并没有其他定义,那么RTT是在什么时候初始化相关硬件的呢?

其实,我们只需要打开【项目\driver\drv_spi.c】,可以在里面找到rt_hw_spi_init(...),并且通过INIT_BOARD_EXPORT(rt_hw_spi_init)使其在板级初始化自动运行。也就是在【项目\drvier\board.c】中的rt_hw_board_init()调用的rt_components_board_init()中执行。

?

1 驱动编写

如下图所示,为ENC28J60的电路图以及与STM32的接口。

?

1.1 配置ENC28J60接口

ENC28J60与STM32之间采用SPI接口。在这里我们不直接使用STM32的SPI驱动程序对接ENC28J60的接口,而是使用RTT的接口驱动。
① 打开项目的RT-Thread Settings。

② 使能SPI总线,并使能RTT自带的ENC28J60 SPI以太网接口

?我们使能ENC28J60的接口之后,RTT的lwip协议栈也自动被使能。这是因为ENC28J60接口依赖lwip协议栈。

?保存配置后,在项目工程的【rt-thread】→【components】→【net】目录下可以看到【lwip-2.0.2】的相关文件。

?

lwip的部分我们先不管,首先解决ENC28J60的驱动问题。

③ 在board.h中启用SPI1
ENC28J60在我的板子上是挂载到SPI1接口的,所以我们首先打开board.h。搜索#define BSP_USING_SPI1并取消该注释。取消该注释之后即启用了RTT的spi1驱动。

④ 在stm32f1xx_hal_conf.h中取消#define HAL_SPI_MODULE_ENABLED的注释
这个是启用SPI的HAL库。

⑤ 在【driver】目录下新建drv_enc28j60.c文件
在该文件中,我们需要调用RTT的SPI设备驱动,挂载ENC28J60。

如下图所示,ENC28J60有6个I/O口,其中REST复位引脚可不管,因为RTT的ENC28J60驱动使用软复位。

?


而后在drv_enc28j60.c中添加如下程序:

#include 
#include 
#include 

#define ENC28J60_INT    102//PG6 ENC28J60中断
static int rt_hw_enc28j60_init(void)
{
        rt_hw_spi_device_attach("spi1", "enc28", GPIOG, GPIO_PIN_7);//PG7 ENC28J60片选CS
        enc28j60_attach("enc28");
        rt_pin_mode(ENC28J60_INT, PIN_MODE_INPUT_PULLUP);
        rt_pin_attach_irq(ENC28J60_INT, PIN_IRQ_MODE_FALLING, (void(*)(void*))enc28j60_isr, RT_NULL);
        rt_pin_irq_enable(ENC28J60_INT, PIN_IRQ_ENABLE);
        return 0;
}
INIT_COMPONENT_EXPORT(rt_hw_enc28j60_init);

首先,我们通过rt_hw_spi_device_attach("spi1", "spi10", GPIOC, GPIO_PIN_4)配置SPI1总线上的enc28设备使用PG7作为片选引脚CS。
而后使用enc28j60_attach("enc28")挂载enc28j60,该函数由【项目\rt-thread\components\drivers\spi\enc28j60.c】提供,暂时就不展开了。
而后设置enc28j60的中断引脚。在这里通过宏定义 #define ENC28J60_INT 102定义。102是RTT中PG6引脚的编号,可以在【项目\drvier\drv_gpio.c】中查找到。
通过rt_pin_mode(ENC28J60_INT, PIN_MODE_INPUT_PULLUP)设置102号引脚为上拉输入。
通过rt_pin_attach_irq(ENC28J60_INT, PIN_IRQ_MODE_FALLING, (void()(void))enc28j60_isr, RT_NULL)设置102号引脚为下降沿触发模式,并设置中断服务函数为enc28j60_isr。
最后通过rt_pin_irq_enable(ENC28J60_INT, PIN_IRQ_ENABLE)使能外部中断。

通过INIT_COMPONENT_EXPORT(rt_hw_enc28j60_init)导入到组件级自动初始化。

1.2 RTT设备初始化

我们经过第0.3小节的查找得知,RTT是在板级初始化时自动运行spi初始化程序。那么接下来我们需要了解一下RTT究竟初始化了什么,有没有需要我们补充的。
我们转到【项目\driver\drv_spi.c】,可以在里面找到rt_hw_spi_init(...),其内容如下:

int rt_hw_spi_init(void)
{
    stm32_get_dma_info();
    return rt_hw_spi_bus_init();
}
INIT_BOARD_EXPORT(rt_hw_spi_init);

可以看到里面第一个是配置了SPIDMA相关,第二个则是初始spi总线。我们转到rt_hw_spi_bus_init()。整个函数比较长,这里只放一部分。如下:

static int rt_hw_spi_bus_init(void)
{
    rt_err_t result;
    for (int i = 0; i < sizeof(spi_config) / sizeof(spi_config[0]); i++)
    {
        spi_bus_obj[i].config = &spi_config[i];
        spi_bus_obj[i].spi_bus.parent.user_data = &spi_config[i];
        spi_bus_obj[i].handle.Instance = spi_config[i].Instance;

可以明显的看出这是一个遍历的结构,spi_config[]数组中保存的就是我们的SPI总线列表。也在同文件中定义。可以看到我们只在board.h中启用了SPI1.所以目前列表中只有SPI1.

?

在rt_hw_spi_bus_init()的最后,调用了rt_spi_bus_register(...),并传入了一个结构体stm_spi_ops。该结构体内容如下:

static const struct rt_spi_ops stm_spi_ops =
{
    .configure = spi_configure,
    .xfer = spixfer,
};

至于内核如何调用结构体我们就不管了,但是我们知道 spi_configure这个函数被传递过去了。我们去看一下spi_configure的内容。

static rt_err_t spi_configure(struct rt_spi_device *device,
                              struct rt_spi_configuration *configuration)
{
    RT_ASSERT(device != RT_NULL);
    RT_ASSERT(configuration != RT_NULL);

    struct stm32_spi *spi_drv =  rt_container_of(device->bus, struct stm32_spi, spi_bus);
    spi_drv->cfg = configuration;

    return stm32_spi_init(spi_drv, configuration);
}

可以看到,里面调用了stm32_spi_init,stm32_spi_init中调用了HAL_SPI_Init(spi_handle)。
HAL_SPI_Init定义在stm32f1xx_hal_spi.c中,里面没有对SPI的IO口进行初始化,但是里面又调用了HAL_SPI_MspInit,可是HAL_SPI_MspInit是一个__weak函数,里面没有内容。

这意味着RTT没有初始化SPI1的端口,需要我们补充!!!!

1.3 补充HAL_SPI_MspInit

我们打开board.c,在里面添加如下内容:

void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hspi->Instance==SPI1)
  {
    /* 外设时钟使能 */
    __HAL_RCC_SPI1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**SPI1 GPIO 配置
    PA5     ------> SPI1_SCK
    PA6     ------> SPI1_MISO
    PA7     ------> SPI1_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  }
}

这里我们只补充了SPI1的相关内容,以后如果需要其他SPI接口,则继续补充。

2 LWIP配置

lwip版本我们选择2.0.2,以便获得良好的兼容性。

?①首先,我们使用了ENC28J60作为网络接口设备,所以我们要打开RTT配置中【网络】【网络接口设备】【使能网络接口设备】。

?②修改内存占用相关参数
PBUF的数量
系统默认为16个,修改为8个,一个PBUF要占用1576个字节,而且是静态分配的,PBUF数量越多,接收速度就越快,但也越耗内存。
TCP发送窗口的大小、发送缓冲区的大小
这两项都是动态分配的,修改后能减少一半的内存占用。

?③ 是否使能DCHP自动获取IP地址
自动获取IP

?静态IP

④ 是否启用ping功能?

?

3 总结

完成后编译工程,编译后工程大小为:

我使用的是DHCP自动获取IP,然后通过中断输入ifconfig命令查看网络状态,如下图所示,可以看到已经自动获取到了IP地址。?

?使用ping命令ping www.baidu.com测试网络连接,结果如下所示:

?

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-26 10:20:39  更:2021-09-26 10:20:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 3:49:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码