(STM32)从零开始的RT-Thread之旅--SPI驱动ST7735(1)
创始人
2024-01-28 05:30:04
0

上一篇:

(STM32)从零开始的RT-Thread之旅--GPIO

我使用的开发板是WeAct的H743板子,板子带一个0.96的SPI驱动的LCD,给的有现成的测试用例,看源码应该是ST的工程师写的ST7735的驱动,打算把这个驱动直接拿到RTT工程里面使用。这里按正常流程来,先打通SPI,再进行上层功能实现。一般当我们用SPI读取到LCD的ID时,即认为SPI没问题了。

这里这块由ST7735驱动的LCD屏幕的SPI接口和一般的不太一样,接线如下:

首先SPI是3线制的,MOSI可以读也可以写,然后通过一根线控制读写的是寄存器还是缓存。它和CS一样,低电平有效(低电平对应寄存器)。这里在配置SPI的时候要注意。 

我们知道,如果没有使用RTT的时候,我们需要使用SPI,只需调用HAL库相关初始化函数,把相关外设初始化完成,就可以直接调用HAL库的发送函数使用SPI了,而现在当我们想要在RTT的驱动框架内使用SPI时,则需要两步:

1.初始化硬件SPI,然后告诉RTT内核我们初始化了哪个SPI,内核可以使用它了

2.在框架内调用注册函数,其实就是从内核可用SPI列表找一个,然后使用它的时候就可以用一种规范的方式发送、接收

这就是驱动应用分离,Linux下通常都是这么开发的,简单来说就是,驱动开发负责底层初始化等,然后把它们加到内核设备列表里,应用开发需要使用SPI传输时,向内核请求使用SPI,然后直接调用内核定义的发送函数就行,而不需要考虑底层SPI怎么配置的。

这里,我把驱动干的事叫做内核之下--驱动:即底层配置,应用干的事叫做内核之上--应用:即实际使用。

1.内核之下

我们打开drivers/board.h里面有使用SPI的提示:

双击左侧RT-Thread Settings可以看到软件配置里有SPI:

直接打开就可以,然后我这里驱动屏幕用的SPI4,所以在board.h里面定义:

然后根据提示让我们去找cubemx生成的配置代码,这个代码在stm32h7xx_hal_msp.c中,由于我在用CubeMX生成代码的时候没有选择每个外设单独生成一个.c和.h,所以有关SPI配置的函数一共有两个,一个是stm32h7xx_hal_msp.c中的 HAL_SPI_MspInit ,另一个是main中的和修改HAL库配置头文件。但是这里我们并不需要做这两步,为什么?

首先由上一章我们已经把cubemx所有生成的文件都包含进来了(除了特别说明的那两个),所以我们可以直接调用HAL_SPI_MspInit函数了,其次第一章已经说过了,在用CubeMX生成文件的时候,有一个文件被重命名了(stm32h7xx_hal_conf_bak.h),那个文件就是HAL的配置文件,所以如果在CubeMX里面配置过就不需要再设置了。

根据CubeMX生成的SPI配置,我们可以知道SPI主要分为两部分配置,第一部分是SPI功能配置,在函数 MX_SPI4_Init 中,请记住这个函数:

另一部分是SPI的物理引脚配置,在函数 MX_SPI4_Init 中,请也记住这个函数:

假如我们单单看CubeMX生成的代码,发现并没有直接调用HAL_SPI_MspInit这个函数,那是在哪调用的呢?

答案在 MX_SPI4_Init  的:

HAL_SPI_Init这个函数中有:

关键这个函数在 stm32h7xx_hal_spi.c 这个文件中,这一下RTT如何把我们生成的代码包含进驱动框架的思路就清晰了,很简单,搜索这个文件:

我们可以看到这个文件CubeMX生成一个,而RTT使用的是自己生成的,其实第一章我们就讲过,我们没有用CubeMX生成的,而是用的RTT创建工程后自带的,包含文件的时候忽略了Drivers下的,但是HAL_SPI_MspInit这个函数是在stm32h7xx_hal_msp.c中的,它被我们包含了进去。如果没有CubeMX生成的 HAL_SPI_MspInit 这个函数,RTT内核本身使用的是 stm32h7xx_hal_spi.c 这个文件里面的:

同样熟悉的weak修饰,在我们把cubemx下的包含进去后,使用的就是我们生成的 HAL_SPI_MspInit 函数了。现在 HAL_SPI_MspInit 如何被包含进去我们知道了,那 MX_SPI4_Init 里面的配置呢?又如何包含进内核里?这就在下一节,内核之上里了。

2.内核之上

关于驱动所有使用均可参考官方文档:

官方文档

里面有API的相关介绍及使用案例,我觉得这点做的很好。

在上一章的BSP文件夹下新建spi的源文件:

细心的小伙伴会发现我把上一章的文件名称改了,因为发现有重名文件!所以起名要谨慎。

初始化有:

void mspi_rw_gpio_init(void)
{rt_pin_mode(SPI_RD_PIN_NUM, PIN_MODE_OUTPUT);rt_pin_write(SPI_RD_PIN_NUM, PIN_HIGH);
}
void mspi_init(void)
{struct rt_spi_configuration cfg;mspi_rw_gpio_init();rt_hw_spi_device_attach("spi4", "spi40", GPIOE, GPIO_PIN_11);spi_lcd = (struct rt_spi_device *)rt_device_find("spi40");if(!spi_lcd){rt_kprintf("spi40 can't find\n");}else{spi_lcd->bus->owner = spi_lcd;cfg.data_width = 8;cfg.mode = RT_SPI_MASTER | RT_SPI_3WIRE | RT_SPI_MODE_0 | RT_SPI_MSB;cfg.max_hz = 12.5 * 1000 * 1000;rt_spi_configure(spi_lcd, &cfg);}
}

这里需要说明几点:

SPI_RD_PIN_NUM 这个和上一章的LED一样的配置方法,这个引脚就是最开始说的控制寄存器和缓存切换的。

然后 rt_hw_spi_device_attach("spi4", "spi40", GPIOE, GPIO_PIN_11) 里面传入的引脚就是CS引脚,我们在调用内核提供的API时,它会自动设置这个引脚。而"spi4"和"spi40",第一个是我们最开始在board.h中使用了SPI4的宏定义,内核会自动关联,它表示spi4总线。"spi40"是指的spi4总线的第0个设备。

创建完设备后用rt_device_find函数关联到本地一个变量,这里我申请的变量是 spi_lcd

而 spi_lcd->bus->owner = spi_lcd; 则是把 spi_lcd 的总线的使用者设置为 spi_lcd。这是啥意思?

因为SPI4总线其实可以注册很多设备,但是同一时间,只能被其中一个设备使用,所以每当一个设备成功申请到总线的使用权时,会把总线的使用者指向自己。设备在使用完总线后,并不会把使用者指向NULL,而是留给下个使用者判断,如果上个使用者不是自己,则会重新初始化总线的配置。从这里也可以看出来,总线能不能申请到,并不是看有没有使用者,而是用的另一个总线初始化的互斥量判断。

最后,我们第一节提到的剩下的另一个SPI配置就在这里被使用:

这个cfg的变量里保存所有SPI的配置,定义如下:

可以看到主要集中在mode里:

 

但是对比CubeMX生成的配置,总感觉还是少了很多,可能RTT对H7的支持还不是很完善,所以我们需要自己修改。还有需要特别注意的就是最大频率max_hz我们这里如果自己选的SPI时钟源,则需要手动修改源码,这个值我们不使用。 

打开 rt_spi_configure 函数,可以看到,设置SPI参数的函数是:

可以看到这里判断了总线当前的拥有者是不是传入的设备,如果不是,就不会初始化!那是不是

spi_lcd->bus->owner = spi_lcd 这行代码就一定要加? 其实上文说的很明白了,每次使用总线的时候都会判断,这里没有初始化也没有关系,当使用的时候总线拥有者不是自己自然会初始化,这里重要的是把配置保存在设备信息里:

查看configure函数发现跳转的是一个函数指针的定义处:

在很多开源项目中,很喜欢使用函数指针,究其原因是为了兼容二字,用函数指针可以只改实现不用改接口。

这里教一个搜索技巧,遇到这种函数我们搜索名字是很难搜索出来的,这个时候可以搜索参数!因为接口名字再改,参数是不会改的(别抬变参函数)。这里我搜索完整的参数:

发现没有匹配的函数,为什么?

因为有些人写代码的时候,函数过长会折到下一行,所以匹配不到,这时候就要搜索参数的一部分了,这里看到函数是配置函数,第一个参数是 struct rt_spi_device *device 不用说肯定一搜一大把,而第二个参数 struct rt_spi_configuration *configuration 一看只和配置有关,肯定就少得多了。这里我们选择第二个参数搜索:
 

可以看到一下就搜索出来了,而且我们也可以看到这个接口格式还有可能被用作QSPI设备。在这个函数中我们可以找到SPI初始化函数:

这个函数完全可以对比CubeMX生成的SPI配置代码一点点对比,具体不再赘述,只讲最重要的:

首先我们看到我们设置的最大频率参数影响的其实是SPI的分频参数。参考芯片手册可以知道:

H7的SPI4的主机模式最高频率为100MHz,这里分频最小二分频,也就是实际通信速率50Mbps,为什么我们通过设置最大频率直接设置?

因为内核默认的SPI时钟源是APB,而我在设置时钟时,选择的是:

 所以这里计算的肯定不对。这个地方我建议先设置分频大一点,然后逐渐缩小,因为你很难确认从机实际最大频率是多少。这里我选择8分频:

然后在下面可以看到M7内核专属的一些SPI配置:

这些需要我们手动根据需求更改。

完成这些后,我们可以先写一个读取寄存器的函数,来读取LCD的ID:

int mspi_read_reg(uint8_t reg,uint8_t *data)
{struct rt_spi_message msg;uint32_t remsg = RT_NULL;uint8_t reg1 = reg;msg.send_buf = ®1;msg.recv_buf = RT_NULL;msg.length = 1;msg.cs_take = 1;msg.cs_release = 0;msg.next = RT_NULL;LCD_RD_REG;remsg = (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);LCD_RD_DATA;if(remsg == 0){msg.send_buf = RT_NULL;msg.recv_buf = data;msg.length = 1;msg.cs_take = 0;msg.cs_release = 1;msg.next = RT_NULL;remsg += (uint32_t)rt_spi_transfer_message(spi_lcd,&msg);}if(remsg!=RT_NULL)return -1;elsereturn 0;
}

重新创建lcd.c及头文件:


int mlcd_readid(uint8_t *id)
{if(mspi_read_reg(ST7735_READ_ID1,&id[0]))LOG_E("ID1\n");else if(mspi_read_reg(ST7735_READ_ID2,&id[1]))LOG_E("ID2\n");else if(mspi_read_reg(ST7735_READ_ID3,&id[2]))LOG_E("ID3\n");else{LOG_I("ID:%02x%02x%02x",id[0],id[1],id[2]);return 0;}return -1;
}void mlcd_init(void)
{mspi_init();
}

在main中调用:

注意最好在循环中尝试循环读取,因为有时候CS或RD引脚配置有问题会导致只有第一次读取正常,或者只有第一次读取不正常。还有就是mlcd.c中需要使用LOG_D或其他日志打印,需要添加:

实际效果: 

 

 

 

 

 

 

 

 

 

 

 

相关内容

热门资讯

欢乐过暑假,安全健康不放假 |...   暑假期间,孩子们宅家吃喝、外出玩乐时,如何做好安全防护,避免意外伤害?关于暑假期间安全与健康的几...
事关“地下生命”,有新发现!   研究显示:地震可为“地下生命”提供“燃料”  一项最新研究显示,地震等地壳内部构造活动瞬间释放的...
俄媒首次公开攻击型无人机生产基...   当地时间7月20日,俄罗斯红星电视台首次曝光位于俄联邦鞑靼斯坦共和国境内的一个无人机生产基地。这...
旅客突发疾病呕吐晕厥 特警队员...   7月2日,一位乘客在T307列车兰州到西宁路段突发疾病晕厥,患者口鼻被分泌物堵塞,无法进行自主呼...
狭窄山路惊现“拦路石” 正要倒...   近日,贵州毕节,狭窄山路惊现“拦路石”,正要倒车避让对向车结果垮塌,网友:是你的善良救了你。(编...
最新报告!179人遇难!韩国空...   据环球网援引韩国《朝鲜日报》21日报道,韩国国土交通部下属航空与铁路事故调查委员会(ARAIB)...
11.23亿!你是其中之一   新华社权威快报|我国网民规模达11.23亿人  海报制作:胡戈  中国互联网络信息中心7月21日...
国家航天局:商业航天项目承担方...   中新网7月21日电 据国家航天局网站消息,7月21日,国家航天局发布《关于加强商业航天项目质量监...
(辉煌60载 魅力新西藏)一个...   中新网西藏那曲7月20日电(记者 程小路)今年5月,西藏那曲市比如县白嘎乡的冬虫夏草(以下简称“...
焦点访谈丨全国多地进入“烧烤”...   7月20日,我国正式进入了今年的三伏天。对于高温,最近这些天我国部分地方已经开启“烧烤”模式,很...