[STM32]从零开始的STM32代码移植教程

一、我们为什么需要移植代码?

        在STM32的学习中,我们往往会面临一些复杂设备的驱动的编写,如果你刚接触STM32并且对C语言并不是那么的熟悉的话。编写这些复杂模块的驱动往往会花费你大量的时间,在写完设备的驱动以后可能在运行时出现各种各样的问题,这也非常打击初学者的信心。所以,既然我们自己编写设备的驱动非常困难,为何不尝试一下去移植别人已经写好的驱动呢?当然移植代码不止是将别人已经写好的代码跑起来,我们还要学会灵活运用,如果一个代码例程是标准库的并且代码能够完美运行,但是在项目要求中,要求我们必须使用HAL库,这就面临了第一个问题,标准库如何移植HAL库。再比如说,我们的一个外设的代码是F1工程里面的,但是在项目中要求我们必须使用F4系列进行开发,这就又出现了一个问题,F1系列的设备驱动如何移植到F4。那么,本篇教程就教大家如何移植一个模块的驱动,以及如何跨系列,跨库移植,如果你准备好了,我们就开始吧!

二、谁适合本次教程?

        本次教程已经不面向STM32纯小白了,既然你已经开始移植一些外设的驱动说明你对STM32的外设和对STM32的代码编写方式有一定的了解。本篇教程要求读者了解STM32的基本外设和基本的程序编写方式,了解一些简单的通信协议如IIC,SPI等,了解一些简单的模块,例如MPU6050,OLED等。

三、什么是驱动,什么是通信

        首先我们来讲讲通信,通信的定义是:通信是指不同模块或设备之间的信息交换过程,以实现数据传输和指令执行。通信的核心目的是在处理器、传感器、执行器和外部系统等硬件之间交换数据,以完成特定任务或功能。从上面的定义我们可以简单的总结出通信实现了不同模块或者设备之间的信息交换。是的,通信在所有过程中都只做了一件事,就是实现了不同模块的信息交换。下面我们再来讲讲驱动,驱动的定义是:驱动(Driver)是指一种软件模块,它位于操作系统和硬件设备之间,负责管理硬件的操作和控制,以便为上层应用程序或操作系统提供标准化的接口。驱动程序的核心作用是抽象底层硬件的复杂性,使开发人员可以通过标准接口访问硬件,而无需关心硬件的细节。从驱动可以看出,这里我们的驱动就是一种软件模块,它负责硬件的控制,并且留出标准化接口。简单来讲,驱动就是一个负责硬件工作的软件并且能被上层使用。那么我们怎么将通信和驱动结合起来呢?其实从两者特点就能很容易看出来,我们需要驱动一个外部的模块,需要对模块进行通信,是的就是这么简单。驱动就可以理解为对下层硬件通信的封装从而实现指定功能的软件。这样说可能不是很明确,让我们打开一段代码来看看:

这里我们可以看到这个名为OLED_Init的函数,这个函数我们就可以理解为OLED的初始化驱动函数,在这个函数的下面有很多“OLED_WriteCommand”函数,这就是用于通信的函数,在这个例子中我们就能看出来,在一个驱动的底层,始终是通信。

从上面讲解的内容中我们就可以得出结论,当我们想要移植一个模块的驱动,实际上本质是修改这个模块底层的通信部分与我们的设备进行兼容。这里的底层部分包括的连接的引脚,引脚的输出方式,速率,通信协议等。在驱动移植时我们也需要时刻关注这些参数。下面就让我们来看看如何移植一个OLED模块的驱动吧!

四、移植完整的驱动

        在某些时候,我们购买模块时,卖家会提供给我们相关的模块代码。如果卖家给的代码是一个完整的例程,里面有工程文件和接线图,并且卖家与我们使用的芯片是对应的,我们就可以直接将卖家的例程编译并下载到我们的单片机中,如果有效果的话,那么这样的驱动并不需要我们移植。其实在大多数时候,卖家给的代码都不是一个完整的例程甚至只是几个.c .h文件,或者说卖家的例程中的单片机型号与我们所使用的型号并不匹配,这就需要我们对代码进行移植。下面我将使用STM32F103C8T6与STM32F407ZGT6进行演示,当我们拿到一个例程时,应该如何移植。这里移植的模块我们选择OLED模块,这也是STM32学习过程中一个比较常见的模块。这里我们移植的代码选择“江协科技”已经编写好的OLED代码。代码和本次教程的相关例程都被我放在了下方的资料链接中:

相关资料:https://pan.baidu.com/s/1sU-VS-j_OGOefJJuVTTnKQ?pwd=clxm 
提取码:clxm

打开资料,看到OLED源码文件夹:

下面,我们就要将这一段代码移植到我们的STM32F1与STM32F4的开发板中并且正常运行。

1.STM32F1移植OLED代码

        这里我使用的是STM32F103C8T6,再次声明这里的代码使用的是“江协科技”已经编写好的代码。那么现在就让我们开始吧!

在移植之前,我们还是需要一个STM32F1的工程模板,我们复制一份工程模板:

当我们将工程模板复制过来以后,我们先不着急打开工程,我们可以在工程模板的文件夹下新建一个文件夹用来存放OLED的代码,我这里就命名为HOME了:

下面再将OLED的源代码复制到这个文件夹中:

完成了这一步以后,我们就可以打开工程了:

然后将刚刚的HOME目录和里面的OLED驱动添加进来,这里大家对keil应该非常熟了,所以就不多作解释了:

当我们把文件添加进来以后,就可以开始移植了,这里我们首先看OLED.c文件:

因为“江协科技”的代码本来就是根据STM32F1系列单片机编写的,所以我们并不需要对其进行多的修改,直接使用即可,我们再OLED.c文件中就可以看出,这里与OLED屏幕进行通信的接口为IIC接口,IIC的SCL线接的是PB8,IIC的SDA线接的是PB9,这里我们直接按照程序中的接口接线即可。

下面我们再来看看OLED.h文件:

在OLED.h文件中,我们可以看到定义了许多OLED的驱动函数,当我们解决了底层的通信以后,只需要调用这些函数就可以实现对OLED的控制了。我们可以在main函数中写入下面几条函数:

这里大家在main函数中写OLED相关的函数时记得引用相关的头文件,随后我们对代码进行编译:

不出意外的话就要出意外了,这里我们的代码出现了20个报错。当然,这里大家也不用惊慌,在移植代码时出现报错是非常正常的现象,我们在代码移植过程中主要要解决的问题也是代码报错。现在我们来看看这些报错,可以看到,在这前面的一些报错都是字库中的中文报错,这是字符编码格式引起的:

我们只需要在魔术棒的图中所示处加入以下语句即可:

--no-multibyte-chars

加入以上语句后,我们再次编译:

可以看到这里编译仍然有报错,但是报错已经减少很多了,我们这里跳转到报错处:

可以发现,是一个名为“OLED_DrawTriangle”的函数正在报错,这里我们只是测试驱动,并不会用到这个函数,我们将其全部注释掉即可:

随后我们再次编译:

到现在可以看到,这里已经只有一个错误了。我们再次跳转到错误处:

这里是一个结构体的声明出现了报错,这里错误信息提示我们,“声明不能出现在可执行语句之后”,既然知道了报错的意思,这就好办了,我们直接将这个结构体的声明放在函数的最前面:

再次编译我们可以发现,已经没有错误了:

这里想要教大家的是,当我们移植完了一段代码,出现了非常多的错误时,不要惊慌,这都是正常的,我们需要结合报错信息一步一步对代码进行修改,直到没有报错为止,这一步不能浮躁,只能慢慢来,当移植的代码多了以后,慢慢就会有经验积攒了。

下面我们将我们的OLED按照代码中的接线方式进行接线,并且将代码编译并且下载到单片机中:

这里大家可以看到,我们的OLED屏幕中已经显示了一个字符,这就表明我们的代码移植是成功的。代码移植成功以后,大家可以尝试别的函数,也可以自己编写一个函数。

至少现在,我们是将OLED屏幕给点亮了,但是有出现了新的问题,如果在我的项目中,我的PB8,PB9引脚被占用了,那这些代码不就失效了吗。当然不是,我们可以修改这些代码所使用到的引脚,我们知道这里的OLED使用的是IIC协议,我们只需要修改两个引脚即可,即SCL和SDA引脚。下面我们就来看看如何修改引脚吧。

其实修改引脚就只需要修改几个地方,第一个地方就是我们对引脚的初始化处,这里我们将原本初始化的引脚改为我们自己的引脚即可:

下面我们就来修改以下引脚,这里我们将PB8和PB9修改为PB5与PB6,这里PB5为SCL,PB6为SDA.我们只需要将初始化处的引脚改掉即可:

这里建议大家像我一样,修改引脚时,原本的一行注释掉,在它的下面写我们新的引脚,这样就算出了问题我们也能找到最开始的代码。这样我们初始化部分就已经修改完了,下面我们再来改改函数部分,我们只需要修改两个函数,分别是“OLED_W_SCL”和“OLED_W_SDA”函数:

这两个函数可以修改SCL引脚和SDA引脚的电平,在后面的函数中都会调用这两个函数,所以我们只需要修改这两个函数操作的引脚即可,如图:

这里改引脚太简单了,就不细讲了。我们再次将代码编译,并且烧录到开发板中,这里我们将OLED的SDA与SCL引脚换到PB5和PB6上:

我们可以看到,这里仍然可以正常显示,说明我们修改的引脚是正确的。

如果你在移植了以后屏幕没有显示,首先应该检查接线是否正确,是不是将OLED的线正确的接在了相关的引脚上。如果效果还没有的话可以看看初始化是否正常,再看看相关的函数里面操作的引脚是否正确。如果检查完以上都还是没有效果的话,就可以考虑使用逻辑分析仪抓取引脚的波形,看看有没有正确的产生信号,具体的通信协议调试可以看下面的文章:

IIC协议的讲解:[STM32]从零开始的IIC协议讲解与设备驱动-CSDN博客

对于通信协议的调试,大家尽量结合逻辑分析仪和Debug模式进行调试。

2.STM32F4移植OLED代码

        这里我们需要上一点难度了,如果我们的代码想要跑在F4平台上,就需要将原本是F1的代码移植到F4,“江协科技”的代码是基于F1编写的,下面我们就来将其移植到F4吧,同样的,我们将F4的工程模板复制出来:

这里的工程模板我们就采用“正点原子”的STM32F407ZGT6的工程模板。这里我们同样的在工程目录下新建一个“HOME”文件夹用来存放OLED的源码文件:

然后将OLED的代码复制到HOME文件夹中:

随后我们打开工程:

首先编译一下,我们要保证原本的工程没有报错:

然后我们再将HOME和HOME文件夹中的文件添加进来:

然后再编译一下:

14个报错,这里的报错是正常的。我们慢慢来修,首先我们还是按照F1中的办法将中文相关报错给修改了:

这里我们将中文相关的报错解决掉以后,这里就只有一个报错了,但是,大家不要小看这个报错,因为这是基于F1编写的源码文件,在F4的工程中是肯定没有F1的头文件的,这里因为没找到这个头文件,后面的内容没有被编译,所以只报出了一个错误:

我们将这个头文文件修改为F4的头文件:

这里我们再次编译:

可以看到这里出现了九个报错,我们这里还是将那个会报错的函数给注释掉:

再次编译:

这里就只有最后三个报错了,这三个报错是因为这些函数原本是F1中的,在F4中并没有这些函数,我们现在来修改一下,首先是开启GPIOB时钟的函数,我们这样修改:

//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

然后是GPIO的初始化结构体,这里我们将其配置为开漏输出:

    GPIO_InitTypeDef GPIO_InitStructure;
 	//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);

修改完以上以后,我们再次编译,发现只有一个报错了:

这个错误还是在提示我们声明应该在可执行语句的前面,我们将结构体的声明放在最前面即可:

我们同样的在主函数中写入测试函数:

然后我们将代码编译下载到开发板中,并且按照程序中的接线方式进行接线:

这里可以看到,屏幕中还是有显示的,说明我们的代码移植是成功的。

3.F1标准库移植F1HAL库

        在实际的开发过程中,我们可能会遇到,我们拥有的代码例程是标准库的,但是在项目中要求我们使用HAL库,这就涉及到了标准库移植HAL库的问题,标准库移植HAL库可能会遇到许多函数不兼容的问题,在HAL库中,我们并不需要对外设进行初始化,所以很多标准库中的初始化函数就可以省略了,初始化的话我们在cubeMX进行配置即可。这里我们还是使用“江协科技”的代码进行演示,我们首先打开cubeMX来生成HAL库的工程模板:

因为我们这里主要讲解引脚的配置,其它的配置我们就简单的看看就行,首先开启外部高速时钟:

然后配置系统时钟为72M:

再配置一下调试接口:

最后我们来配置引脚,这里仍然配置PB8和PB9引脚,我们首先让引脚处于输出模式:

然后再来到GPIO处进行详细配置:

因为我们这里是IIC协议,所以引脚首先就要被配置为开漏输出:

基于IIC的性质,我们这里将引脚的初始电平置高,意为释放总线:

这里我们不使用上下拉,并且速率为最快:

下面我们配置好工程的名字与路径,这里的路径不能有中文:

然后进行以下配置:

随后就可以点击生成了:

在生成完成以后,我们打开工程目录:

这里我们同样的在工程目录中创建一个“HOME”的文件夹并且将OLED的相关文件拷贝进去:

然后我们打开keil工程:

这里还是先编译一下:

编译后没有错误也没有警告,说明我们的工程模板没有问题,下面我们将HOME文件夹和里面的文件添加进来:

这里我们编译一次:

可以看到,这里整整出现了25个错误,我们还是依次来看,我们先将中文编码相关的错误给解决了,仍然是在如图所示的地方添加下面的字串:

--no-multibyte-chars

可以看到现在报错已经少了很多了:

我们可以看到,这里有一堆的函数没有被定义的错误,因为我们这里使用的是HAL库,所以标准库的很多函数都没有,我们需要将其删掉或者修改为HAL库中的函数:

HAL库中是不需要初始化的,我们将标准库中的初始化函数删除即可:

然后我们要修改SCL和SDA的控制函数,因为HAL库中并不存在这个函数,所以需要修改:

这里在修改之前大家先引用一下头文件:

然后我们就可以进行如下修改了:

HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,(GPIO_PinState)BitValue);
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,(GPIO_PinState)BitValue);

这里我们在主函数中写入下面的测试代码:

我们同样的将代码编译,下载到开发板中:

可以看到,这里的OLED仍然能正常显示,至此我们将STM32F1标准库的OLED程序移植到HAL库就已经完成了。

五、结语

        代码移植需要长期以来的经验,就单凭本次教程是不可能将代码移植的精髓理解透彻的。大家在移植代码时应注意底层通信是否正常,只有当解决了底层的通信以后,驱动才能移植成功,在通信出现问题时,可以考虑使用逻辑分析仪抓取波形。最后,感谢大家的观看!

猜你喜欢

转载自blog.csdn.net/c858845275/article/details/143285302