STM32学习笔记1.3 寄存器、STD库和HAL库的实例:对IO口电平的操作

言归正传,我们今天就来讲一讲利用寄存器、标准库和HAL库开发的基本流程和区别,在这里我将贴出详细步骤,以方便你对这三种开发方式的对比。之后的学习中,我们将从较为贴近底层的标准库起手,在了解各外设的基本原理之后,一步步推进到使用HAL库。

需求分析和方案制定

身为未来工程师,你必须有充分分析用户需求并设计实现方案的能力。身为学习者,我们也应该从小养成培养工程思维的良好习惯。
这次,我们的“客户”提出的需求非常简易:利用STM32F103C8T6最小系统板和三只小LED制作一个可以控制一组四盏LED灯以一定频率和顺序轮流点亮的系统,每个时刻仅点亮一盏LED。对这一需求的硬件连接和软件分析如下
(事实上这个系统真的十分简单,但是是我们一切的基础,所以还请你认真看下面的内容。)

  1. 小LED灯可以用单片机的IO口直接驱动,因此只需要配置和使用4个GPIO接口。将IO口分别连接到LED的正极,再把它们的负极接地就可以了。此次的四个端口选用GPIOA的P0到P3。
  2. LED灯点亮后的延时可以使用死循环,也可以利用定时器。这里采用前者。轮流点亮需要保证一盏点亮的同时下一盏及时熄灭。
  3. 对寄存器开发,需要配置GPIO对应的外设时钟使能寄存器、GPIO控制寄存器;对标准库,需要执行外设时钟使能函数,GPIO初始化函数和GPIO控制函数。对HAL库,需要在CubeMX中配置好时钟和GPIO管脚,再在代码中执行GPIO电平控制函数。
    对需求的分析到此结束,下面我们来看看,这三种操作的步骤有何区别,在操作上的难易程度又如何。

寄存器

我们的开发工具使用Keil μVision5,寄存器开发的工程中至少需要五组库文件(官方提供的文件):stm32f10x.h、core_cm3.c、startup_stmf10x_hd.s、system_stm32f10x.c,和stm32f10x_it.c,以及它们中包含的头文件,请将它们添加到你的空工程中。这些文件能帮你启动单片机的核心和时钟,并保证你能够运行你写的那些外设。你还需要自己建一个.c文件来写你自己的代码,一般叫它main.c。
main.c的代码如下,每一行代码我都会给出注释,请认真阅读,遇到看不懂的不要紧,我们之后会讲。过程中的寄存器会给出名称,请在参考手册中查询,了解其大概作用。

#include "stm32f10x.h"//这个头文件里包含了所有的寄存器映射,也就是能允许你用结构体形式访问所有的寄存器
int main(){//和X86的C语言一样,STM32的C语言也要以main作为入口执行
	//记得我们说过的外设配置步骤吗,第一步,挂时钟,需要修改RCC外设的外设时钟使能寄存器,查阅F1参考手册中文译版得知,需要修改的寄存器为RCC_APB2ENR,修改位2为1
	RCC->APB2ENR |= 1<<2;//RCC外设的寄存器以结构体形式封装,只需要修改结构体中APB2ENR字段的第二位为1,“|=”与你用过的“+=”等是类似的原理,1<<2表示你把1左移2位,变成二进制的100
	//第二步,初始化该外设,这里GPIOA需要使用推挽输出,无上下拉模式,开启P0到P3.IO口使用低速模式即可。初始化IO的步骤比较复杂,一些行不给出注释,你只需要是在修改对应寄存器就好。
	{//大括号内为GPIO初始化步骤
	//所谓初始化,是指使用寄存器配置外设的工作模式和各项参数
	GPIOA->CRL = 0x44442222;//这个值是怎么来的,可以查阅手册,之后会讲到
	//经过上一步,四个引脚已经配置为输出模式
	GPIOA->ODR = 0x0000;
	//初始状态,GPIOA所有引脚输出低电平
	}
	//第三步,连接中断/IO,GPIO作为基本外设,不需要这样操作
	//第四部,使能,GPIO也不需要
	
	//硬件部分的初始化完成了,我们可以开始让系统工作了
	//先设置一个状态机来表示亮灯的状态,其值等于当前输出高电平的引脚号
	uint8_t flag = 0;
	whils(1){
		bsp_Delay();//延时一段
		GPIOA->ODR = 0x000F&(0x0001<<flag);//点亮Flag对应的IO口LED
		if(flag < 3 ) ++flag;
		else if(flag == 3 ) flag = 0;//这两句实现flag的轮转以实现灯轮流点亮
	}
}

void bsp_Delay(){//简单的延时函数,通过死循环的方式实现
	for(uint8_t i = 0; i<100000 ; i++);
}

就这么多,编译,烧录到你连接好的系统中看看效果吧!如果你实现轮流点亮的逻辑与我上边的不一样,也可以尝试,GPIOA的ODR寄存器的0到16位对应PA0到PA16的电平高低。
需要指出的是,main函数结尾的while(1)死循环是必要的,即使你并不需要程序的某部分循环执行也一定要记得添加。

标准库

标准库的编程需要我们之前搭建的工程模板,或者你需要在寄存器编程的基础上添加一些外设的库文件:
stm32f10x_gpio.c和对应的头文件
stm32f10x_rcc.c和对应的头文件
misc.c和misc.h
把它们复制到工程目录的对应文件夹并添加进工程之后,在main.c里写如下的代码。这一次,我少加一点注释,你可以自己根据很多变量和方法的名字去猜测它们的作用。

#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"

void main(){
	//第一步,挂时钟
	RCC_APB2PeriphClockCmd(APB2Periph_GPIOA,ENABLE);

	//第二步,初始化
	GPIO_InitTypeDef GPIO_InitStruct;//GPIO初始化结构体,用于初始化GPIO,其中包含这项外设的所有可设置参数
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出模式
	GPIO_Initstruct.GPIO_Speed = GPIO_Speed_10MHz;//低速10兆赫
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_4;//只将这四个引脚配置为以上状态。
	
	GPIO_Init(GPIOA,$GPIO_InitSStruct);
	//硬件配置结束
	
	uint8_t flag = 0;
	while(1){
		bsp_Delay();//延时与之前一样,略
		GPIO_Write(GPIOA,0x0001<<flag);
		if(flag < 3 ) ++flag;
		else if(flag == 3 ) flag = 0;
	}
}

编译运行应该会得到和之前差不多的效果。
标准库的代码其实比寄存器的实际行数要多。但现在还别急着把本篇关掉,并批评笔者欺世盗名——虽然看着确实是多了。对阅读者,多出来的东西直接说明了外设的设定参数和操作的具体意义,而不需要像看寄存器的代码时对着“APB2ENER”、“0x44442222”之类的奇怪符号一头雾水并只能去慢慢翻参考手册了(记忆大师请无视)。对编写者,也不需要记忆纷繁复杂的寄存器,只需要凭印象写下英文单词就可以了(大雾)。
库函数的用法我们之后细讲,你需要到官网(或者随便什么网)去找找STM32F1和F4系列的库函数手册,之后会用得到。

HAL库

方便地实现HAL库编程需要准备STM32 CubeMX工具,去官网下载且一路默认安装即可。下载地址:
https://www.st.com/zh/development-tools/stm32cubemx.html
其间需要填写邮箱和姓名,随后下载链接会发到邮箱。

基于HAL库的开发一般基于两个软件进行——于CubeMX完成处理器和(片上)外设的配置,再在IDE中完成硬件操作细节和软件代码的编写。

  1. .CubeMX内的操作
    (1) Start My Project from MCU,选择芯片,这里是F103C8Tx
    在这里插入图片描述

选择芯片型号
(2) 进入主界面
界面左侧选择所需配置硬件资源,中间选择资源的工作模式和参数,右边则是芯片封装略图和引脚单独配置用。
主界面·
我们所需的操作是:
① 最小系统板有8MHz晶振,为了使用之,在SystemCore>RCC下配置成如上图状态(HSE晶振输入),可以发现右侧芯片视图的PD0和PD1变绿(两引脚为晶振提供起振用的外围电路,并承接晶振输入的时钟信号)。
随后,进入ClockConfiguration选项卡,
在这里插入图片描述
在HCLK处输入72,回车,系统会自动选择合适的时钟源,这是F103系列芯片允许的最高设计频率。
② 配置GPIO,直接在芯片略图上点选要配置的引脚,在菜单中选择GPIO_Output。在这里插入图片描述
选择后,对应引脚也会变绿(工字钉表明该引脚被直接选定,无法被其他外设的复用占据,这个以后细讲)
这里选择PA0-A4。
③ 配置完毕!现在进入Project Manager选项卡,给你的工程选个路径,命名,选择使用的IDE(Keil V5),然后Generate Code!
在这里插入图片描述
生成的工程包含CUBE MX管理的外层配置信息和IDE管理的程序设计部分,打开MDK ARM文件夹,就可以看到工程文件了,打开它进入我们的下一部分。

  1. IDE里的操作
    在自动生成的工程里找到Core>main.c,CubeMX已经为我们写好了所有的片上外设配置代码。找到其中的main函数,这样修改它:
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */
  uint8_t i = 0;
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  //注意,编程时的所有代码都必须卸载USER_CODE_BEGIN和END之间,否则更新工程时会被配置工具吃掉1
  while (1)
  {
  	for(i = 0 ; i < 4 ; i++){
		switch(i){
			case 0:
			{
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_0 , GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_1 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_2 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_3 , GPIO_PIN_RESET);
				break;
			}
			case 1:
			{
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_0 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_1 , GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_2 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_3 , GPIO_PIN_RESET);
				break;
			}
			case 2:
			{
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_0 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_1 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_2 , GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_3 , GPIO_PIN_RESET);
				break;
			}
			case 3:
			{
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_0 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_1 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_2 , GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOA , GPIO_PIN_3 , GPIO_PIN_SET);
				break;
			}
		}
		HAL_Delay(500);
	}
    /* USER CODE END WHILE */
	
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

等下,是不是有些不对?为什么号称简单的HAL库写得比标准库都长了?究竟是徒有虚名还是有什么大棋?
好吧,其实只是HAL库中的外设操作函数功能较为固定,不如标准库灵活。这样写出来的效率也不如标准库代码高。这在许多实时低功耗应用中是致命的。解决这个问题有两种办法:
① 混搭寄存器编程,由于CubeMX生成的工程中同样包含了<stm32f10x.h>,因此也可以直接操作寄存器,于是那个Switch Case语句就可以改成寄存器编程时的操作,你可以尝试下。
② LL库——ST为解决HAL开发效率和灵活性问题推出的,同样基于CubeMX的,能进行更底层操作的库。我们将在之后提到。

结语

本章节介绍了了STM32编程的相关概念,学习方式和编程方式。事实上,本章包含了太多超前内容,毕竟我们的标题是“笔记”,这更像一个学习过的家伙对基础内容进行复习时得出的更深入理解。不过没关系,如果你能跟完这个系列,再回过头来看这个章节,必定能获得豁然开朗的体验。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值