系列文章目录

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

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

RT-Thread (3) 为RTT增加SP485驱动||RTT UART设备

目录

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_SMPrt_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);
#endifhw_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_INITrt_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片选CSenc28j60_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_SCKPA6     ------> SPI1_MISOPA7     ------> 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测试网络连接,结果如下所示:

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

  1. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  2. Linux kernel SPI源码分析之SPI设备驱动源码分析(linux kernel 5.18)

    SPI基础支持此处不再赘述,直接分析linux中的SPI驱动源码. 1.SPI设备驱动架构图 2.源码分析 本次分析基于kernel5.18,linux/drivers/spi/spidev.c 设备 ...

  3. 二十、SPI设备驱动及应用(一)

    先给出Linux SPI子系统的体系结构图: SPI子系统体系结构 下面开始分析SPI子系统. Linux中SPI子系统的初始化是从drivers/spi/spi.c文件中的spi_init函数开始的 ...

  4. linux中流设备_[快速上手Linux设备驱动]之块设备驱动流程详解一

    [快速上手Linux设备驱动]之块设备驱动流程详解一 walfred已经在[快速上手Linux设备驱动]之我看字符设备驱动一 文中详细讲解了linux下字符设备驱动,并紧接着用四篇文章描述了Linux ...

  5. Linux SPI设备驱动

    实现了SPI OLED外设驱动,OLED型号为SH1106. 1.主机驱动与外设驱动分离 Linux中的I2C.SPI.USB等总线驱动,都采用了主机(控制器)驱动与外设(设备)驱动分离的思想.主机端 ...

  6. imx6ul spi 设备驱动开发

    imx6ul spi 设备驱动开发 spi设备树格式 spi设备树配置 spi 驱动 设备树解析 spi设备驱动使用 spi通用设备驱动 spi测试工具 spi时序对比 spi api 接口 spi设 ...

  7. 驱动程序开发:SPI设备驱动

    目录 Linux下SPI驱动简介 SPI架构概述 SPI适配器(控制器) SPI设备驱动 spi_driver注册示例 SPI 设备和驱动匹配过程 编写imc20608六轴传感器SPI驱动 设备树编写 ...

  8. stm32f407单片机rt thread 片外spi flash OTA升级配置示例

    参考地址https://www.rt-thread.org/document/site/application-note/system/rtboot/an0028-rtboot/ 第一步,生成Boot ...

  9. linux spi屏驱动程序,65 linux spi设备驱动之spi LCD屏驱动

    SPI的控制器驱动由平台设备与平台驱动来实现. 驱动后用spi_master对象来描述.在设备驱动中就可以通过函数spi_write, spi_read, spi_w8r16, spi_w8r8等函数 ...

最新文章

  1. 这味儿上头,前有文言文,又来东北话,中文编程玩得挺得劲!
  2. 运维工程师必备技能:网络排错
  3. 如何使用SAP CRM增强工具AET创建Table表格类型的增强
  4. file标签样式修改
  5. 微信小程序php java_PHP实现微信小程序用户授权的工具类示例
  6. SQL中关于where后面不能放聚合函数(如sum等)的解决办法
  7. GitHub命令使用步骤
  8. tp6 获取session_thinkphp6不能设置session以及模板无法获取session的坑附解决办法
  9. 那些好用的firefox扩展插件分享
  10. 重启 WMI 服务。
  11. 不外昨夜下战书当店的裘姓值班司理则称
  12. 作为亚马逊小白卖家不建议盲目进入亚马逊市场
  13. java script总结1
  14. pytorch的训练测试流程总结,以及model.evel(), model.train(),torch.no_grad()作用
  15. 9.27通则康威技术面
  16. 编码01--极性编码详解(极性码、单极性码、双极性码产生原理)
  17. 【今日CV 计算机视觉论文速览 第114期】Thu, 9 May 2019
  18. 人生感悟:如何与别人合作共同创业
  19. 硬件课程设计:基于STM32的多功能播放器之小说阅读
  20. axure内联框架和动态面板_Axure教程:如何使用动态面板?动态面板功能详解

热门文章

  1. C#使用VLCPlayer播放视频并对视频抓图失败的解决方法
  2. 成功斩获腾讯offer,分享我的面试经历(附书籍推荐)
  3. MGC Group区块链、人工智能的A面与B面
  4. 房地产术语,住宅类型:公寓、其它、别墅、四合院、平房、排屋、新里洋房、普通住宅、暂无数据、老公房、酒店公寓 写字楼类型:写字楼、创意园区、商业综合体、商业街、商住楼、酒店写字楼...
  5. 华为鸿蒙os用时间长了会卡吗,外媒再放狠话!华为鸿蒙OS系统和安卓没区别:同样也会越用越卡顿...
  6. PPTV面试算法思考-最长对称子字符串
  7. 07【字符串的扩展】
  8. nginx 拦截非法字符,nginx禁止url访问特定字符,nginx过滤url访问
  9. 学完计算机系统配置的心得,计算机课学习心得体会(精选3篇)
  10. Pycharm完整中文教程