STM32

STM32是STMicroelectronics(意法半导体)推出的一系列基于ARM Cortex-M内核的32位微控制器(MCU)产品。这些微控制器提供了广泛的产品系列,覆盖了多种不同的性能和功能需求,适用于各种应用领域,包括工业控制、汽车电子、消费类电子、医疗设备等。

STM32系列微控制器以其高性能、低功耗、丰富的外设接口和灵活的开发工具而闻名。它们通常具有丰富的存储器、多种通信接口(如UART、SPI、I2C、CAN等)、模拟数字转换器(ADC)、定时器、PWM输出等功能,以满足不同应用场景下的需求。

STM32微控制器通常使用标准的ARM Cortex-M内核,包括Cortex-M0、M0+、M3、M4和M7等,这些内核具有不同的性能和功耗特性,可根据具体应用的需求进行选择。此外,STM32系列还提供了多种封装和引脚配置,以满足不同尺寸和集成度的要求。

STMicroelectronics为STM32系列提供了丰富的开发工具和支持资源,包括基于ARM开发环境的集成开发环境(IDE)、调试器、评估板和参考设计等。这些工具和资源有助于开发人员快速开发和部署他们的应用,并提供了全面的技术支持和文档资料,帮助用户充分发挥STM32微控制器的性能和功能优势。

本文我们来学习下STM32的待机唤醒功能。要实现的功能是:系统运行时 D1 指示灯闪烁,5 秒后进入待机模式,D1 指示灯熄灭,同时串口 printf输出相关提示信息,可通过 K_UP 按键实现唤醒。学习本内容可以参考《STM32F10x中文参考手册》-4 电源控制器(PWR)章节。

STM32低功耗模式介绍

很多单片机具有低功耗模式,比如 MSP430、STM8L等。我们的STM32也不例外,相关文章:STM32低功耗模式。默认情况下,系统复位或上电复位后,微控制器进入运行模式。在运行模式下,HCLK 为 CPU 提供时钟,并执行程序代码。当 CPU 不需继续运行(例如等待外部事件)时,可以利用多种低功耗模式来节省功耗。用户需要根据最低电源消耗、最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。

当然在运行模式下,也可以通过如下方式降低功耗:

(1)降低系统时钟速度

(2)不使用 APBx 和 AHB 外设时,将对应的外设时钟关闭

STM32 提供了 3 种低功耗模式,以达到不同层次的降低功耗的目的,这三种模式如下:

(1)睡眠模式( CM3 内核停止工作,外设仍在运行)

(2)停止模式(所有时钟都停止)

(3)待机模式( 1.8 V 内核电源关闭)

这三种模式所需的功耗是逐级递减,也就是说待机模式功耗是最低的。三种低功耗模式汇总表如下图所示:

“STM32实例-待机唤醒实验"

我们仅对 STM32 的待机模式进行介绍,其他 2 种模式可以参考《STM32F10x 中文参考手册》-4电源控制器(PWR)章节,里面有详细的介绍。

(1)待机模式

在睡眠模式中,仅关闭了内核时钟,内核停止运行,但其片上外设, CM3 核心的外设全都照常运行。在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。在待机模式中, 它除了关闭所有的时钟, 还把 1.8V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 BOOT 条件,从头开始执行程序。低功耗开发相关文章:STM32低功耗开发时,需要注意的GPIO配置问题。

那么我们如何进入待机模式呢?其实很简单,只要按下图所示待机模式进入与退出步骤的步骤执行就可以了。

“STM32实例-待机唤醒实验"

上图还列出了退出待机模式的操作,当检测到外部复位(NRST 引脚)、

IWDG 复位、 WKUP 引脚上升沿、 RTC 闹钟事件的上升沿时,微控制器退出待机模式。本文我们是通过 WKUP 引脚(PA0)上升沿来退出待机模式,当然也可以直接通过芯片复位管脚 NRST退出。

从待机模式唤醒后,除了电源控制/状态寄存器(PWR_CSR),所有的寄存器豆被复位,程序将按照复位(启动引脚采样、复位向量已获取等)后的方式重新执行。电源控制/状态寄存器(PWR_CSR)将会指示内核由待机状态退出。

在进入待机模式后,除了复位引脚以及被设置为防侵入或校准输出时的

TAMPER (PC13)引脚和被使能的唤醒引脚( WK_UP 脚(PA0)),其他的 IO 引脚都将处于高阻态。

由于篇幅限制,本文并没有对待机模式相关寄存器进行介绍,大家可以参考《STM32F10x 中文参考手册》-4 电源控制器(PWR)章节,里面有详细的讲解。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。

待机模式配置步骤

接下来我们介绍下如何使用库函数进入和退出待机模式。这个也是在编写程序中必须要了解的。

具体步骤如下:(电源管理相关库函数在 stm32f10x_pwr.c和 stm32f10x_pwr.h 文件中)

(1)使能电源时钟

因为低功耗模式是通过 STM32 电源(PWR)系统进行管理的,所以需要使能电源时钟,调用的库函数为:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能 PWR 外设时钟

(2)设置 WK_UP 引脚为唤醒源

待机唤醒方式有很多种,我们选择 WK_UP 引脚(PA0)上升沿来退出待机模式。在库函数中,设置使能 WK_UP 用于唤醒 CPU 待机模式的函数是:

PWR_WakeUpPinCmd(ENABLE);

因为按键 K_UP 连接在PA0 管脚上,并且是高电平有效,这样一来就可以使用 K_UP按键来退出待机模式。

(3)进入待机模式

进入待机模式, 首先要设置 SLEEPDEEP 位 ( 详见 《 Cortex M3 权威指南(中文)》 , chpt13 Cortex-M3 的其它特性--电源管理章节) ,接着我们通过 PWR_CR设置 PDDS 位,使得 CPU 进入深度睡眠时进入待机模式,最后执行 WFI 指令开始进入待机模式,并等待 WK_UP 中断的到来。整个操作可以通过一个库函数完成,如下:

PWR_EnterSTANDBYMode();//进入待机模式

通常在进入待机模式前,我们会清除唤醒标志,以等待下次进入。清除唤醒标志库函数为:

PWR_ClearFlag(PWR_FLAG_WU);//清除 Wake-up 标志

以上几步全部配置好后,我们就可以正常进入待机模式了,并且可以通过按键 K_UP或者复位按键唤醒。

特别提醒下,如果学到 RTC 实时时钟实验的时候,需要进入待机模式,如果使能了 RTC 闹钟中断的时候,进入待机模式前,必须按如下操作处理:

1.禁止 RTC 中断( ALRAIE、 ALRBIE、 WUTIE、 TAMPIE 和 TSIE 等)。

2.清零对应中断标志位。

3.清除 PWR 唤醒(WUF)标志(通过设置 PWR_CR 的 CWUF 位实现)。

4.重新使能 RTC 对应中断。

5.进入低功耗模式。

本实验使用到硬件资源如下:

(1)D1 指示灯

(2)串口 1

(3)K_UP 按键

D1指示灯、K_UP 按键、串口 1 电路在前面章节都介绍过,这里不多说。D1指示灯用来提示系统正常运行,K_UP 按键用来唤醒待机模式,串口 1 用来输出提示信息。

所要实现的功能是:系统运行时 D1 指示灯闪烁,5 秒后进入待机模式,D1 指示灯熄灭,同时串口 printf 输出相关提示信息,通过 K_UP 按键实现唤醒。

程序框架如下:

(1)配置进入与退出待机模式

(2)编写主函数

前面介绍待机模式配置步骤时,就已经讲解如何配置。下面我们打开“待机唤醒实验”工程,在 APP 工程组中可以看到添加了wkup.c文件(里面包含了待机模式驱动程序),在 StdPeriph_Driver 工程组中添加了 stm32f10x_pwr.c 库文件。电源系统管理相关操作的库函数都放在stm32f10x_pwr.c 和 stm32f10x_pwr.h 文件中,所以使用到电源系统管理就必须加入 stm32f10x_pwr.c 文件,同时还要包含对应的头文件路径。

这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。

待机模式配置函数

要让系统进入待机模式,我们必须对它进行配置。进入待机模式代码如下:

/****************************************************************
* 函 数 名 : Enter_Standby_Mode
* 函数功能 : 进入待机模式
* 输 入 : 无
* 输 出 : 无
*****************************************************************/
void Enter_Standby_Mode(void)
{
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能 PWR 外设时钟
  PWR_ClearFlag(PWR_FLAG_WU);//清除 Wake-up 标志
  PWR_WakeUpPinCmd(ENABLE);//使能唤醒管脚 使能或者失能唤醒管脚功能
  PWR_EnterSTANDBYMode();//进入待机模式
}

该函数首先使能电源PWR时钟,然后清除唤醒标志位,并使能 WK_UP管脚为唤醒方式,最后进入待机模式。这一过程在前面步骤介绍中已经提了。

主函数

配置待机模式后,我们就可以编写主函数,代码如下:

/****************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*****************************************************************/
int main()
{
  SysTick_Init(72);
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组
  LED_Init();
  USART1_Init(9600);
  while(1)
  {
    printf("time: 5\r\n");
    led1=0;
    delay_ms(1000); //隔 1 秒显示计数
    printf("time: 4\r\n");
    led1=1;
    delay_ms(1000);
    printf("time: 3\r\n");
    led1=0;
    delay_ms(1000);
    printf("time: 2\r\n");
    led1=1;
    delay_ms(1000);
    printf("time: 1\r\n");
    led1=0;
    delay_ms(1000);
    printf("进入系统待机模式\r\n");
    Enter_Standby_Mode();
  }
}

主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括

SysTick 系统时钟,中断分组,LED 初始化等。然后进入 while 循环,每间隔一秒让 printf 输出一个信息,同时指示灯状态发生变化。倒计 5 秒钟后,调用函数 Enter_Standby_Mode进入待机模式,此时指示灯熄灭。

将工程程序编译后下载到开发板内,可以看到系统运行时 D1 指示灯不断闪烁,5 秒钟后进入待机模式,此时 D1 指示灯熄灭。当按下 K_UP 按键或复位按键时,待机模式被唤醒,系统重新运行,同时串口打印提示信息。如果想在串口调试助手上看到输出信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR 框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住, 通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到 printf发送过来的信息。(串口助手上先勾选下标号1 DTR框,然后再取消勾选)如下图所示。

“STM32实例-待机唤醒实验"

实验说明:下载待机唤醒实验程序后,若使用普中 ARM 仿真器下载其他的程序会出现报警,这是因为处于低功耗模式时,所有外设时钟都已关闭,所以需要在下载程序前先复位下系统。

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 253

1、FreeRTOS

由于RTOS需占用一定的系统资源(尤其是RAM资源),只有μC/OS-II、embOS、salvo、FreeRTOS等少数实时操作系统能在小RAM单片机上运行。

相对于C/OS-II、 embOS等商业操作系统,FreeRTOS操作系统是完全免费的操作系统,具有源码公开、可移植、可裁减、调度策略灵活的特点,可以方便地移植到各种单片机上运行,其最新版本为6.0版。

作为一个轻量级的操作系统,FreeRTOS提供的功能包括:任务管理、时间管理、信号量、消息队列、内存管理、记录功能等,可基本满足较小系统的需要。

FreeRTOS内核支持优先级调度算法,每个任务可根据重要程度的不同被赋予一定的优先级,CPU总是让处于就绪态的、优先级最高的任务先运行。

FreeRT0S内核同时支持轮换调度算法,系统允许不同的任务使用相同的优先级,在没有更高优先级任务就绪的情况下,同一优先级的任务共享CPU的使用时间。

FreeRTOS的不足:相对于常见的μC/OS—II操作系统,FreeRTOS操作系统既有优点也存在不足。其不足之处, 一方面体现在系统的服务功能上,如FreeRTOS只提供了消息队列和信号量的实现,无法以后进先出的顺序向消息队列发送消息;另一方 面,FreeRTOS只是一个操作系统内核,需外扩第三方的GUI(图形用户界面)、TCP/IP协议栈、FS(文件系统)等才能实现一个较复杂的系统, 不像μC/OS-II可以和μC/GUI、μC/FS、μC/TCP-IP等无缝结合。

“STM32嵌入式开发中的RTOS,你用过哪些?"

在STM32CubeIDE中直接可以配置FreeRTOS,如上图。STM32使用FreeRTOS的相关文章,请移步此处:使用STM32CubeMx工具,写FreeRTOS的demo程序。

FreeRTOS资料多、生态活跃,在Cube中通过配置界面,三两下上手这款操作系统,推荐拿来入门。

基于不同的需求,下文再介绍下其他RTOS。

2、μClinux

μClinux是一种优秀的嵌入式Linux版本,其全称为micro-control Linux,从字面意思看是指微控制Linux。

同标准的Linux相比,μClinux的内核非常小,但是它仍然继承了Linux操作系统的主要特性,包括良好的稳定性和移植性、强大的网络功能、出色的文件系统支持、标准丰富的API,以及TCP/IP网络协议等。因为没有MMU内存管理单元,所以其多任务的实现需要一定技巧。

μClinux在结构上继承了标准Linux的多任务实现方式,分为实时进程和普通进程,分别采用先来先服务和时间片轮转调度,仅针对中低档嵌入式CPU特点进行改良,且不支持内核抢占,实时性一般。

综上可知,μClinux最大特点在于针对无MMU处理器设计,这对于没有MMU功能的STM32F103来说是合适的,但移植此系统需要至少512KB的RAM空间,1MB的ROM/FLASH空间,而STM32F103拥有256K的FLASH,需要外接存储器,这就增加了硬件设计的成本。

μClinux结构复杂,移植相对困难,内核也较大,其实时性也差一些,若开发的嵌入式产品注重文件系统和与网络应用则μClinux是一个不错的选择。

“STM32嵌入式开发中的RTOS,你用过哪些?"

3、μC/OS-II

μC/OS-II是在μC/OS的基础上发展起来的,是用C语言编写的一个结构小巧、抢占式的多任务实时内核。μC/OS-II能管理64个任务,并提供任务调度与管理、内存管理、任务间同步与通信、时间管理和中断服务等功能,具有执行效率高、占用空间小、实时性能优良和扩展性强等特点。

在文件系统的支持方面,由于μC/OS-II是面向中小型嵌入式系统的,即使包含全部功能,编译后内核也不到10 KB,所以系统本身并没有提供对文件系统的支持。但是μC/OS-II具有良好的扩展性能,如果需要也可自行加入文件系统的内容。

在对硬件的支持上,μC/OS-II能够支持当前流行的大部分CPU,μC/OS-II由于本身内核就很小,经过裁剪后的代码最小可以为2KB,所需的最小数据RAM空间为4 KB,μC/OS-II的移植相对比较简单,只需要修改与处理器相关的代码就可以。

综上可知,μC/OS-II是一个结构简单、功能完备和实时性很强的嵌入式操作系统内核,针对于没有MMU功能的CPU,它是非常合适的。它需要很少的内核代码空间和数据存储空间,拥有良好的实时性,良好的可扩展性能,并且是开源的,网上拥有很多的资料和实例,所以很适合向STM32F103这款CPU上移植。

“STM32嵌入式开发中的RTOS,你用过哪些?"

4、eCos

eCos(embedded Configurable operating system),即嵌入式可配置操作系统。它是一个源代码开放的可配置、可移植、面向深度嵌入式应用的实时操作系统。

最大特点是配置灵活,采用模块化设计,核心部分由小同的组件构成,包括内核、C语言库和底层运行包等。

每个组件可提供大量的配置选项(实时内核也可作为可选配置),使用eCos提供的配置工具可以很方便地配置,并通过不同的配置使得eCos能够满足不同的嵌入式应用要求。

eCos操作系统的可配置性非常强大,用户可以自己加入所需的文件系统。eCos操作系统同样支持当前流行的大部分嵌入式CPU,eCos操作系统可以在16位、32位和64位等不同体系结构之间移植。

“STM32嵌入式开发中的RTOS,你用过哪些?"

eCos由于本身内核就很小,经过裁剪后的代码最小可以为10 KB,所需的最小数据RAM空间为10 KB。

在系统移植方面 eCos操作系统的可移植性很好,要比μC/OS-II和μClinux容易。

综上所述,eCos最大特点是配置灵活,并且支持无MMU的CPU的移植,开源且具有很好的移植性,也比较合适于移植到STM32平台的CPU上。但eCOS的应用还不是太广泛,还没有像μC/OS-II那样普遍,并且资料也没有μC/OS-II多。eCos适合用于一些商业级或工业级对成本敏感的嵌入式系统,例如消费电子领域中的一些应用。

5、mbed OS

开源嵌入式操作系统,ARM公司将mbed OS免费提供给所有厂商使用,mbed提供了一个相对更加系统和更加全面的智能硬件开发环境。

主要功能:

提供用于开发物联网设备的通用操作系统基础,以解决嵌入式设计的碎片化问题。支持所有重要的连接性与设备管理开放标准,以实现面向未来的设计。使安全可升级的边缘设备支持新增处理能力与功能。通过自动电源管理解决复杂的能耗问题。

主要特点:

开发速度快,功能强大,安全性高,为了量产化而设计,可离线开发,也可以在网页上编辑。

“STM32嵌入式开发中的RTOS,你用过哪些?"

6、RTX

是ARM公司的一款嵌入式实时操作系统,使用标准的C结构编写,运用RealView编译器进行编译。不仅仅是一个实时内核,还具备丰富的中间层组件,不但免费,而且代码也是开放的。

主要功能:

开始和停止任务(进程),除此之外还支持进程通信,例如任务的同步、共享资源(外设或内存)的管理、任务之间消息的传递。开发者可以使用基本函数去开启实时运行器,去开始和终结任务,以及去传递任务间的控制(轮转调度)。开发者可以赋予任务优先级。

主要特点:

支持时间片,抢占式和合作式调度。不限制数量的任务,每个任务都具有254的优先级。不限制数量的信号量,互斥信号量,消息邮箱和软定时器。支持多线程和线程安全操作。使用MDK基于对话框的配置向导,可以很方便的完成MDK的配置。

7、都江堰

都江堰操作系统,简称djyos,得名于一个伟大的水利工程:都江堰。

与传统操作系统不同,djyos不是以线程而是以事件为调度核心,这种调度算法使程序员摆脱模拟计算机执行过程编写程序的思维方式,而是按人类认知世界的方式编写应用程序,就如同在嵌入式编程中引入了VC似的。

djyos的调度算法使程序员可以摆脱线程和进程的束缚,djyos没有有关线程的api,一个完全不懂线程知识的程序员也可以顺利地在djyos下编写应用程序。

djyos 操作系统是以事件为核心进行调度的,这种调度策略使程序员可以按人类认知事物的习惯而不是计算机的习惯来编程。

“STM32嵌入式开发中的RTOS,你用过哪些?"

8、RT-Thread

嵌入式操作系统RTOS介绍,RT-Thread是一个集实时操作系统(RTOS)内核、中间件组件和开发者社区于一体的技术平台,由熊谱翔先生带领并集合开源社区力量开发而成,RT-Thread也是一个组件完整丰富、高度可伸缩、简易开发、超低功耗、高安全性的物联网操作系统。

RT-Thread具备一个IoT OS平台所需的所有关键组件,例如GUI、网络协议栈、安全传输、低功耗组件等等。经过11年的累积发展,RT-Thread已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过两千万台,成为国人自主开发、国内最成熟稳定和装机量最大的开源RTOS。

国内最有可能成为Top 1,优势在于丰富的组件,中立立场!赶上了时机,得到诸多芯片厂商的支持,也挺受开发者喜欢的。缺点在于本身的教程文档和freertos等之类的比还是很弱。

来源:http://www.sylixos.com/

直接来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 947

最近因为要为芯片选定核,所以就在了解哪些核合适且性价比好,这是一个需要结合产品各类技术、市场分析的活,看似简单却还是需要一些储备的,今天选了一篇ARM Cortex系列的科普文章与大家分享。

众所周知,英国的ARM公司是嵌入式微处理器世界当中的佼佼者。ARM一直以来都是自己研发微处理器内核架构,然后将这些架构的知识产权授权给各个芯片厂商,精简的CPU架构,高效的处理能力以及成功的商业模式让ARM公司获得了巨大的成功,使它迅速占据了32位嵌入式微处理器的大部分市场份额。

目前,随着对嵌入式系统的要求越来越高,作为其核心的嵌入式微处理器的综合性能也受到日益严峻的考验,现在一个高端智能手机的处理能力几乎可以和几年前的笔记本电脑相当。为了迎合市场的需求,ARM公司也在加紧研发他们最新的 ARM架构,Cortex系列就是这样的产品。那么我们今天就不妨好好了解一下ARM Cortex系列处理器知识点汇总。

“​

ARM Cortex系列处理器

ARM公司在经典处理器ARM11以后的产品改用Cortex命名,并分成A、R和M三类,旨在为各种不同的市场提供服务。

1、Cortex-A:面向尖端的基于虚拟内存的操作系统和用户应用

2、Cortex-R:针对实时系统

3、Cortex-M:微控制器

“​

ARM Cortex系列处理器——Cortex-A

ARM Cortex-A 系列是一系列用于复杂操作系统和用户应用程序的应用程序处理器。Cortex-A 系列处理器支持 ARM、Thumb 和 Thumb-2 指令集。

RM公司的Cortex-A系列处理器适用于具有高计算要求、运行丰富操作系统以及提供交互媒体和图形体验的应用领域。

“​

如图所示,绿色的部分都是v7-A的架构,蓝色的是v8-A架构,基本上绿色都是可以支持到32和64位的,除了A32,只支持到32位。在右边的每个部分,比如说需要高效能的最上面的A15-A73这个部分是最高效的,接下来就是比较注重整个效率的部分了,中间那个部分是比较高效率的,最下面那栏的是效率最好的,在电池的效能方面达到了最好的标准。

如果非要给他们一个排序的话,从高到低大体上可排序为:Cortex-A73处理器、Cortex-A72处理器、Cortex-A57处理器、Cortex-A53处理器、Cortex-A35处理器、Cortex-A32处理器、Cortex-A17处理器、Cortex-A15处理器、Cortex-A7处理器、Cortex-A9处理器、Cortex-A8处理器、Cortex-A5处理器。

“​

ARM Cortex系列处理器——Cortex-M

Cortex-M处理器家族更多的集中在低性能端,但是这些处理器相比于许多微控制器使用的传统处理器性能仍然很强大。例如,Cortex-M4和Cortex-M7处理器应用在许多高性能的微控制器产品中,最大的时钟频率可以达到400Mhz。

当然,性能不是选择处理器的唯一指标。在许多应用中,低功耗和成本是关键的选择指标。因此,Cortex-M处理器家族包含各种产品来满足不同的需求:

“​

不同于老的经典ARM处理器(例如,ARM7TDMI, ARM9), Cortex-M处理器有一个非常不同的架构。例如:

—仅支持ARM Thumb指令,已扩展到同时支持16位和32位指令Thumb-2版本

—内置的嵌套向量中断控制负责中断处理,自动处理中断优先级,中断屏蔽,中断嵌套和系统异常处理。

—中断处理函数可以使用标准的C语言编程,嵌套中断处理机制避免了使用软件判断哪一个中断需要响应处理。同时,中断响应速度是确定性的,低延迟的。

—向量表从跳转指令变为中断和系统异常处理函数的起始地址。

—寄存器组和某些编程模式也做了改变。

这些变化意味着许多为经典ARM处理器编写的汇编代码需要修改,老的项目需要修改和重新编译才能迁移到Cortex-M的产品上。

“​

ARM Cortex系列处理器——Cortex-R

R4:第一个基于ARMv7-R体系的嵌入式实时处理器。专用于大容量深层嵌入式片上系统应用,如硬盘驱动控制器、无限基带处理器、消费产品手机MTK平台和汽车系统的电子控制单元。

R5:2010年推出,基于ARMv7-R体系,扩展了 Cortex-R4 处理器的功能集,支持在可靠的实时系统中获得更高级别的系统性能、提高效率和可靠性并加强错误管理。这些系统级功能包括高优先级的低延迟外设端口 (LLPP) 和加速器一致性端口 (ACP),前者用于快速外设读写,后来用于提高效率并与外部数据源达成更可靠的高速缓存一致性。

基于 40 nm G 工艺,Cortex-R5 处理器可以实现以将近 1 GHz 的频率运行,此时它可提供 1,500 Dhrystone MIPS 的性能。该处理器提供高度灵活且有效的双周期本地内存接口,使 SoC 设计者可以最大限度地降低系统成本和功耗。

R7:Cortex-R7 处理器是性能最高的 Cortex-R 系列处理器。它是高性能实时 SoC 的标准。Cortex-R7 处理器是为基于 65 nm 至 28 nm 的高级芯片工艺的实现而设计的,此外其设计重点在于提升能效、实时响应性、高级功能和简化系统设计。基于 40 nm G 工艺,Cortex-R7 处理器可以实现以超过 1 GHz 的频率运行,此时它可提供 2700 Dhrystone MIPS 的性能。该处理器提供支持紧密耦合内存 (TCM) 本地共享内存和外设端口的灵活的本地内存系统,使 SoC 设计人员可在受限制的芯片资源内达到高标准的硬实时要求。

“​

“​

“​

来源:嵌入式资讯精选
直接转载来源:
大鱼机器人
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 559

如今电池供电的产品很多,电池供电通常设计到一个问题,那就是低功耗。本文为大家讲讲基于STM32、FreeRTOS实现低功耗思想和原理。

1、低功耗设计常规思路

应用中使用的 RTOS 一般采用基于时间片轮转的抢占式任务调度机制,一般的低功耗设计思路如下:

1)当 Idle 任务运行时,进入低功耗模式;
2)在适当的条件下,通过中断或者外部事件唤醒 MCU。

但是, 从第二点可以看出,每次当 OS 系统定时器产生中断时,也会将 MCU 从低功耗模式中唤醒,而频繁的进入低功耗模式/从低功耗模式中唤醒会使得 MCU 无法进入深度睡眠,对低功耗设计而言也是不合理的。

在 FreeRTOS 中给出了一种低功耗设计模式 ——Tickless Idle Mode, 这个方法可以让 MCU 更长时间的处于低功耗模式。

2、Tickless Idle Mode原理及实现

1)情景分析

FreeRTOS各任务情况:

“基于STM32、FreeRTOS低功耗设计思路和原理"

上图是任务调度示意图,横轴是时间轴, T1, T2, T3, T4 是 RTOS 的时间片基准,有四个任务分别是 TaskA,B,C,D。

Task A:周期性任务
Task B:周期性任务
Task C:突发性任务
Task D:周期性任务

从图中可以看出在四个任务进行调度之间,会有四次空闲期间(此时 RTOS 会调度 Idle 任务运行, 软件设计的目标应该是尽可能使 MCU 在 Idle 任务运行时处于低功耗模式) 。

Idle1: Idle 任务运行期间,会产生一次系统时钟滴答,此时会唤醒 MCU,唤醒后 MCU 又会进入低功耗模式, 这次唤醒是无意义的。期望使 MCU 在 Idle1 期间一直处于低功耗模式, 因此适当调整系统定时器中断使得 T1 时不触发系统时钟中断, 中断触发点设置为 Task B 到来时;

Idle2:Task C 在系统滴答到达前唤醒 MCU(外部事件) , MCU 可以在 Idle2 中可以一直处于低功耗模式;

Idle3: 与 Idle2 情况相同,但 Idle3 时间很短,如果这个时间很短,那么进入低功耗模式的意义并不大,因此在进入低功耗模式时软件应该添加策略;

Idle4: 与 Idle1 情况相同。

2)Tickless Idle Mode 的软件设计原理

Tickless Idle Mode 的设计思想在于尽可能得在 MCU 空闲时使其进入低功耗模式。从上述情景中可以看出软件设计需要解决的问题有:

a、合理的进入低功耗模式(避免频繁使 MCU 在低功耗模式和运行模式下进行不必要的切换) ;

RTOS 的系统时钟源于硬件的某个周期性定时器(Cortex-M 系列内核多数采用 SysTick) ,RTOS 的任务调度器可以预期到下一个周期性任务(或者定时器任务) 的触发时间,如上文所述,调整系统时钟定时器中断触发时间,可以避免 RTOS 进入不必要的时间中断,从而更长的时间停留在低功耗模式中,此时 RTOS 的时钟不再是周期的而是动态的(在原有的时钟基准时将不再产生中断,即 Tickless) ;

b、当 MCU 被唤醒时,通过某种方式提供为系统时钟提供补偿。

MCU 可能被两种情况所唤醒, 动态调整过的系统时钟中断或者突发性的外部事件,无论是哪一种情况,都可以通过运行在低功耗模式下的某种定时器来计算出 MCU 处于低功耗模式下的时间,在 MCU 唤醒后对系统时间进行软件补偿;

c、软件实现时,要根据具体的应用情景和 MCU 低功耗特性来处理问题。

尤其是 MCU 的低功耗特性, 不同 MCU 处于不同的低功耗模式下所能使用的外设(主要是定时器) 是不同的, RTOS 的系统时钟可以进行适当的调整。

3)Tickless Idle Mode 的实现

这里以 STM32F407 系列的 MCU 为例, 首先需要明确的是 MCU 的低功耗模式, F407 有 3 种低功耗模式:Sleep、Stop、 Standby。

“基于STM32、FreeRTOS低功耗设计思路和原理"

在 RTOS 平台时, SRAM 和寄存器的数据不应丢失, 此外需要一个定时器为 RTOS 提供系统时钟, 这里选择 Sleep 模式下进行实现。

使能Tickless Idle:

#define configUSE_TICKLESS_IDLE 1

RTOS空闲任务(空闲时自动调用)实现:

/* Idle 任务 */
void prvIdleTask( void *pvParameters )
{
  for( ; ; )
  {
    //...
#if(configUSE_TICKLESS_IDLE != 0)
    {
      TickType_t xExpectedIdleTime;
      /* 用户策略以决定是否需要进入 Tickless Mode */
      xExpectedIdleTime = prvGetExpectedIdleTime();
      if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
      {
        vTaskSuspendAll(); // 挂起调度器
        {
          configASSERT( xNextTaskUnblockTime >= xTickCount );
          xExpectedIdleTime = prvGetExpectedIdleTime();
          if( xExpectedIdleTime >=
          configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
          {
            /* 用户函数接口 */
            /* 1. 进入低功耗模式和如何退出低功耗模式 */
            /* 2. 系统时间补偿 */
            portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
          }
        }
        (void) xTaskResumeAll(); // 恢复调度器
      }
    }
#endif /* configUSE_TICKLESS_IDLE */
    //...
  }
}

然后,低功耗模式处理(根据 MCU 的低功耗模式编写代码, 代码有点长……)

void vPortSuppressTicksAndSleep( portTickType xExpectedIdleTime )
{
  unsigned long ulReloadValue, ulCompleteTickPeriods,
  ulCompletedSysTickDecrements;
  portTickType xModifiableIdleTime;
  /* 最长睡眠时间不可以超过定时器的最大定时值 */
  /* 通过调整定时器的时间基准可以获得更理想的最大定时值 */
  if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
  {
    xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
  }
  /* 停止 SysTick */
  portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT;
  
  /* 计算唤醒时的系统时间,用于唤醒后的系统时间补偿 */
  ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
  
  if( ulReloadValue > ulStoppedTimerCompensation )
  {
    ulReloadValue -= ulStoppedTimerCompensation;
  }
  __disable_interrupt();
  /* 确认下是否可以进入低功耗模式 */
  if( eTaskConfirmSleepModeStatus() == eAbortSleep )
  {
    /* 不可以,重新启动系统定时器 */
    portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    portNVIC_SYSTICK_INT_BIT |
    portNVIC_SYSTICK_ENABLE_BIT;
    portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
    __enable_interrupt();
  }
  else
  {
    /* 可以进入低功耗模式 */
    /* 保存时间补偿,重启系统定时器 */
    portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    portNVIC_SYSTICK_INT_BIT |
    portNVIC_SYSTICK_ENABLE_BIT;
    /* 进入低功耗模式,可以通过 configPRE_SLEEP_PROCESSING 函数进行低功耗模式下
    时钟及外设的配置*/
    xModifiableIdleTime = xExpectedIdleTime;
    configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
    if( xModifiableIdleTime > 0 )
    {
      __DSB();
      __WFI();
      __ISB();
    }
    /* 退出低功耗模式 */
    configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
    portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
    portNVIC_SYSTICK_INT_BIT;
    __disable_interrupt()
    __enable_interrupt();
    /*唤醒有两种情况:系统定时器或者外部事件(中断) */
    if((portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT) != 0)
    {
      /* 系统定时器唤醒,时间补偿 */
      unsigned long ulCalculatedLoadValue;
      ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) –
      ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
      if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) ||
      ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
      {
      ulCalculatedLoadValue = (ulTimerCountsForOneTick - 1UL);
      }
      portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
      ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
    }
    else
    {
      /* 外部事件(中断)唤醒 */
      ulCompletedSysTickDecrements = ( xExpectedIdleTime *
      ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
      ulCompleteTickPeriods = ulCompletedSysTickDecrements /
      ulTimerCountsForOneTick;portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1 ) *
      ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
    }

    /* 重启 Systick,调整系统定时器中断为正常值 */
    portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
    portENTER_CRITICAL();
    {
      portNVIC_SYSTICK_CTRL_REG = portNVIC_SYSTICK_CLK_BIT |
                                  portNVIC_SYSTICK_INT_BIT |
                                  portNVIC_SYSTICK_ENABLE_BIT;
      vTaskStepTick( ulCompleteTickPeriods );
      portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
    }
    portEXIT_CRITICAL();
  }
}

3、最后

低功耗的设计存在很多影响功耗的因素,比如电路设计、IO引脚配置等。

MCU实现低功耗的方法和种类有很多,设计时需要注意一些低功耗细节问题。

最后,以上方法仅供学习参考,具体请按照实际项目选择合理的低功耗设计方案。

来源:strongerHuang
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 485

意法半导体发布了最新版的STM32* 微控制器 (MCU)图形界面开发软件TouchGFX Version 4.18 ,新增了视频播放功能,改进了多名开发者协同开发工具,支持新的 X-NUCLEO 显示板。

“意法半导体更新TouchGFX软件,增加视频功能,丰富STM32用户体验"

通过可以播放 Motion-JPEG (MJPEG) 视频文件,TouchGFX 为家庭自动化产品、穿戴设备、医疗设备和工业传感器等小型设备带来了更多产品力,实现丰富的功能,例如,用户操作指南或有趣的启动和暂停屏幕,提升图形用户界面的视觉效果。开发人员可以使用自己的 MJPEG 文件或从软件提供的电影示例中选择。TouchGFX Designer中的新视频小程序有开始、停止、重复和转到帧等属性,可以简化原型设计,并可以拖放到应用程序中。

TouchGFX 4.18可以用软件或硬件进行视频解码。硬件解码可以用带有适合的解码外设的 STM32 MCU,例如,STM32F769 和 STM32H7B3。除STM32G0外,全系都有软件解码功能。视频缓冲采用不同的策略方法,包括直接渲染到帧缓冲区和双缓冲区技术,有助于优化内存要求和性能。

TouchGFX 4.18的其他新功能包括加强对协作开发的支持,使用 XML 存储文本数据和翻译。当有多个团队成员参与项目开发时,XML可以简化团队共享和整合不同的项目模块。最新的TouchGFX保留了上一版本中的强大而便利的功能,包括用于小RAM的部分帧缓冲区、防止图像断裂的超高效渲染,以及对低成本非内存映射 SPI闪存的支持。

为了方便GUI 开发项目轻松启动,TouchGFX 4.18附带一些示例,演示如何在热门的 STM32 Discovery 开发板上使用新的视频功能和必要的 TouchGFX 开发板设置 (TBS)。

意法半导体还更新并增加了显示板的种类,帮助开发人员加快用户界面项目开发。更新后的Nucleo 64开发板的显示板X-NUCLEO-GFX01M2 有 2.2 英寸 QVGA 串口显示器,现在支持NUCLEO-WB55RG开发板,方便在Bluetooth®蓝牙应用设备中加装显示器。新的与Nucleo 144开发板配套的 X-NUCLEO-GFX02Z1 显示板配备高速并行接口和QSPI 闪存,支持 NUCLEO-U575ZI-Q等开发板。两款显示板都在 TouchGFX 4.18的支持设备列表内。

TouchGFX 4.18现在可以从 st.com 免费下载使用。新的 X-NUCLEO-GFX02Z1 Nucleo 144 Display Shield 显示板也已上市,X-NUCLEO-GFX01M2 将于 12 月上市。

来源:意法半导体
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 42

有一些电子设备需要频率高度稳定的交流信号,而LC振荡器稳定性较差,频率容易漂移,即产生的交流信号频率容易变化。

在振荡器中采用一个特殊的元件——石英晶体,可以产生高度稳定的信号,这种采用石英晶体的振荡器称为晶体振荡器,简称晶振,如下图是各种各样的晶振。

“晶振为什么没有封装进STM32芯片内部?"

电子元器件的小型化趋势,有力促进了当下社会的发展进步,电子元器件越小,为主板节约的空间越大,因此,有人异想天开,如果能将晶振电路封装到IC芯片(如时钟芯片)内部将是多么完美,就如同有源晶振在无源晶振的基础内置振动芯片,就无需外部的电容电阻等元器件了。

但实际出于各种原因,晶振并没有内置到IC芯片中。这究竟是为什么呢?

原因 1

早些年,芯片的生产制作工艺也许还不能够将晶振做进芯片内部,但是现在可以了。这个问题主要还是实用性和成本决定的。

原因 2

芯片和晶振的材料是不同的,芯片 (集成电路) 的材料是硅,而晶体则是石英 (二氧化硅),没法做在一起,但是可以封装在一起,目前已经可以实现了,但是成本就比较高了。

原因 3

晶振一旦封装进芯片内部,频率也固定死了,想再更换频率的话,基本也是不可能的了,而放在外面,就可以自由的更换晶振来给芯片提供不同的频率。

有人说,芯片内部有 PLL,管它晶振频率是多少,用 PLL 倍频/分频不就可以了,那么这有回到成本的问题上来了,100M 的晶振集成到芯片里, 但我用不了那么高的频率,我只想用 10M 的频率,那我为何要去买你集成了 100M 晶振的芯片呢,又贵又浪费。

我们通常所说的 "片内时钟",实际上片内根本没有晶振,只有RC振荡器。

“STM32的时钟框图"
STM32的时钟框图

可以看出STM32系统时钟的供给可以有3种方式:

  • HSI,高速内部时钟信号STM32单片机内带的时钟 (8M频率), 精度较差。
  • HSE,高速外部时钟信号,精度高。
  • PLL,低速外部晶体32.768kHz主要提供一个精确的时钟源 一般作为RTC时钟使用

如果选用内部时钟作为系统时钟,其倍频达不到72Mhz,最多也就8Mhz/2*16 = 64Mhz。

如果使用内部RC振荡器而不使用外部晶振,请按照如下方法处理:
① 对于100脚或144脚的产品,OSC_IN应接地,OSC_OUT应悬空。
② 对于少于100脚的产品,有2种接法:

  • OSC_IN和OSC_OUT分别通过10K电阻接地。此方法可提高EMC性能。

  • 分别重映射OSC_IN和OSC_OUT至PD0和PD1,再配置PD0和PD1为推挽输出并输出'0'。此方法可以减小功耗并(相对上面i)节省2个外部电阻。

时钟是STM32单片机的脉搏,是单片机的驱动源。

使用任何一个外设都必须打开相应的时钟。这样的好处就是,如果不使用一个外设的时候,就把它的时钟关掉,从而可以降低系统的功耗,达到节能,实现低功耗的效果。

来源:网络
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 98

做STM32智能小车的实验中会用到定时器PWM输出,来改变直流电机的转速。分享本文了解如何通过PWM实现对电机速度的控制。

PWM控制电机速度的基本原理

PWM(Pulse Width Modulation),也就是脉冲宽度调制。

PWM中有一个比较重要的概念,占空比:是一个脉冲周期内有效电平在整个周期所占的比例。

为了实现IO口上电压的持续性变化,可以调节PWM的占空比。这也能够使外设的功率进行持续性变化,最终控制直流电机转速的快慢。如何调节PWM波形的输出就是重点。相关推荐:STM32中PWM的配置与应用详解。

“STM32通过PWM控制电机速度"

上图中的ARR是我们给定时器的一个预装载值,CCRx的上下变化是产生PWM波的关键。我们假设ARR大于CCRx的部分输出为高电平(即t1-t2、t3-t4、t5-t6),ARR小于CCRx的部分输出为低电平(即0-t1、t2-t3、t4-t5),则改变CCRx的值就能改变输出PWM的占空比。因此,想要控制PWM的输出波形,重要的就是如何设置ARR与CCRx这两个寄存器的值了。

STM32定时器中断

为了便于理解接下来关于PWM应用的内容,先插一段定时器中断的知识。

产生定时中断是定时器的用法之一,与定时器用来进行PWM输出和输入捕获相比,定时器中断更容易理解、掌握。

原理简介

使用通用定时器进行中断的原理,其实和开发板Systick定时器进行中断延时很相似(Stm32入门——Systick定时器),即:用psc(预分频系数)设置好定时器时钟后,arr(预装载值)在每个时钟周期内减1,当arr减为0时触发中断然后进入中断处理程序进行中断处理。以下代码为例:

void TIM3_Int_Init(u16 arr,u16 psc)
{
  RCC->APB1ENR|=1<<1;  //TIM3时钟使能    
   TIM3->ARR=arr;    //设定计数器自动重装值 
  TIM3->PSC=psc;    //预分频器设置
  TIM3->DIER|=1<<0;   //允许更新中断        
  TIM3->CR1|=0x01;    //使能定时器3
    MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占1,子优先级3,组2                   
}
RCC->APB1ENR|=1<<1

解释一下上面这行代码,由于定时器3(TIM3)是挂在APB1上的外设,所以要打开APB1,这里的预分频器值psc是来设置TIM3的时钟频率的,如果系统时钟(SYSTICK)频率为72MHz、psc为7199,则TIM3的时钟频率就为:

72MHz/(7199+1)Hz = 10KHz    //这里的“+1”是手册中规定的。

10KHz是什 么意思呢?就是一秒钟会产生10K个周期,那么一个周期的时间长度就是1/10KHz,如果你想将定时器中断的时间间隔设置为0.5秒,那么你将arr设置为5000即可,因为arr每减1就需要一个周期的时间,减5000次就经过了5000*(1/10KHz)=0.5秒。

TIM3->DIER|=1<<0

再解释下上面这一行,设置允许更新中断,即arr减到0以后可以触发更新中断,还有其他类型的中断。

MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占1,子优先级3,组2

看上面这行代码,中断优先级有抢占优先级和响应(即子优先级)优先级两种,抢占优先级即:若程序1正在使用CPU,这时如果程序2要求使用CPU,并且程序2的抢占优先级高,则CPU被程序2抢占;若两者抢占优先级相同,则就算程序2的响应优先级高于程序1,CPU也不能被抢占;若程序1正在使用CPU,程序2和程序3的抢占优先级等于或低于程序1,且程序2的响应优先级高于程序三,则待CPU空出后,程序2先运行,程序3最后运行。TIM3_IRQn是指定将要运行的中断处理程序号。“组2”是设置中断优先级分组的,这是因为寄存器提供了四位来设置优先级,组2代表的是前两位给抢占优先级,后两位给响应优先级。

PWM模式、有效电平

前面介绍完中断,再说一下PWM工作原理。相关文章:浅析PWM控制电机转速的原理。

假设上图中ARR大于CCRx时输出为高电平,ARR小于CCRx时输出为低电平,但在实际运用中可能并非如此,有可能是相反的情况——ARR大于CCRx时输出为低电平,ARR小于CCRx时输出为高电平,至于到底是哪种情况,还要看PWM是哪种模式、有效电平又设置的是何种极性了。

  • 模式1:ARR小于CCRx时输出为“有效”电平,ARR大于CCRx时输出为“无效”电平。

  • 模式2:ARR小于CCRx时输出为“无效”电平,ARR大于CCRx时输出为“有效”电平。

这里说的是“有效”和“无效”,而不是“高”和“低”,也就是说有效电平可高可低,并非一定就是高电平。PWM模式、效电平极性,需要程序员自己配置相关的寄存器来实现。通过下面的代码来讲解。

TIM1_PWM_Init(899,0);//不分频。PWM频率=72000/(899+1)=80Khz

上一小节讲过关于定时器参数的设置。使用定时器1的通道1来输出一路PWM波,这里的899设置的就是ARR的值,至于那个0是用来设置TIM1的频率的,不分频就代表TIM1的时钟频率和系统时钟相同,这里假设为72MHz。

void TIM1_PWM_Init(u16 arr,u16 psc)
{                
//此部分需手动修改IO口设置
  RCC->APB2ENR|=1<<11;   //TIM1时钟使能    
  GPIOA->CRH&=0XFFFFFFF0;  //PA8清除之前的设置
  GPIOA->CRH|=0X0000000B;  //复用功能输出 

  TIM1->ARR=arr;      //设定计数器自动重装值 
  TIM1->PSC=psc;      //预分频器设置

  TIM1->CCMR1|=7<<4;    //CH1 PWM2模式     
  TIM1->CCMR1|=1<<3;     //CH1预装载使能   
   TIM1->CCER|=0<<1;     //OC1 输出使能     
//TIM1->CCER|=1<<1;

  TIM1->BDTR|=1<<15;     //MOE 主输出使能     

  TIM1->CR1=0x0080;     //ARPE使能 
  TIM1->CR1|=0x01;      //使能定时器1                       
}

下文具体分析上面的代码。

前面4-6行是用来配置GPIO口的。

TIM1->ARR=arr; //设定计数器自动重装值
TIM1->PSC=psc; //预分频器设置

这两行就是我上门提到的设置定时器的频率和重装载值。

TIM1->CCMR1|=7<<4; //CH1 PWM2模式
TIM1->CCMR1|=1<<3; //CH1预装载使能
TIM1->CCER|=0<<1; //OC1 输出使能

这三行是用来设置PWM输出模式和设置通道的,通道是什么呢?简单地讲就是输出PWM波的GPIO口,代码一开始不是设置了PA8这个GPIO口嘛,这个PA8就是通道1。使用通道的话要先进行输入输出方向、通道使能的设置。

TIM1->CCER|=1<<1;

这行代码是用来设置“有效电平”极性的,根据手册,当TIM1->CCER[1]这位置1时,有效电平为低电平,置0时有效电平为高电平,而默认情况下置0。

TIM1->BDTR|=1<<15; //MOE 主输出使能

这行代码只要对高级定时器进行设置,普通定时器无需设置。

TIM1->CR1=0x0080; //ARPE使能

这行代码是用来使能ARPE,ARPE是什么呢,就是当它被置1时,你自己设置的CCRx会立即生效,如果它被置为0,那么你自己设置的CCRx值不会立即生效(可能之前ARPE已经有值了),而是当之前设置的CCRx生效后才会使用你最新设置的CCRx值。

上面的代码里没有对CCRx进行设置,这是因为CCRx常常是一个变化的值,你可以在主函数中用一个for循环+if判断语句对它进行++或–的操作,从而达到连续改变CCRx值得目的,例如:

for(i=0;i<300;i++){
  TIM1->CCR1=i;
if(i==300){
    i=0;
  }
}

PWM波的周期是由定时器时钟频率和预装载值两者决定的,预装载值就是ARR。

预装载值PSC设置为899,那么,当定时器的当前值val从0增加到899时,一共经过了900个时钟周期,这900个时钟周期会产生一个PWM波形,也就是说900个定时器时钟周期才相当于一个PWM周期,那么PWM的频率就为72MHz/900=80KHz,周期为1/80KHz。

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 347

STM32的三种开发方式

通常新手在入门STM32的时候,首先都要先选择一种要用的开发方式,不同的开发方式会导致你编程的架构是完全不一样的。一般大多数都会选用标准库和HAL库,而极少部分人会通过直接配置寄存器进行开发。

网上关于标准库、HAL库的描述相信是数不胜数。可是一个对于很多刚入门的朋友还是没法很直观的去真正了解这些不同开发发方式彼此之间的区别,所以笔者想以一种非常直白的方式,用自己的理解去将这些东西表述出来,如果有描述的不对的地方或者是不同意见的也可以大家提出。

1、直接配置寄存器

不少先学了51的朋友可能会知道,会有一小部分人或是教程是通过汇编语言直接操作寄存器实现功能的,这种方法到了STM32就变得不太容易行得通了,因为STM32的寄存器数量是51单片机的十数倍,如此多的寄存器根本无法全部记忆,开发时需要经常的翻查芯片的数据手册,此时直接操作寄存器就变得非常的费力了。但还是会有很小一部分人,喜欢去直接操作寄存器,因为这样更接近原理,知其然也知其所以然。

2、标准库

上面也提到了,STM32有非常多的寄存器,而导致了开发困难,所以为此ST公司就为每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的。在这些 .c .h文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如GPIO口时钟等。所以我们只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。也是目前最多人使用的方式,也是学习STM32接触最多的一种开发方式,我也就不多阐述了。

3、HAL库

HAL库是ST公司目前主力推的开发方式,全称就是Hardware Abstraction Layer(抽象印象层)。库如其名,很抽象,一眼看上去不太容易知道他的作用是什么。

它的出现比标准库要晚,但其实和标准库一样,都是为了节省程序开发的时期,而且HAL库尤其的有效,如果说标准库把实现功能需要配置的寄存器集成了,那么HAL库的一些函数甚至可以做到某些特定功能的集成。也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了。

并且HAL库也很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相通外设,意思也就是不能无中生有,例如F7比F3要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。

是而且使用ST公司研发的STMcube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件,可以说是方便至极,但是方便的同时也造成了它执行效率的低下,在各种论坛帖子真的是被吐槽的数不胜数。

HAL库固件库安装与 用户手册

1、首先设置让Cube可以自动联网下载相关固件库选择updater Settings
  

“”

设置如下:

“”

2、根据芯片选择所需固件

版本是向下兼容的,可以直接选择最新版。但如果觉得最新版太大,可以阅读下面的Main Changes.能够支持你目前的芯片就好。

“”

选好了,点击Install Now就行,过程可能有点长。建议直接官网下载到本地,再安装文件会被下载到如下位置,建议更改此目录,不要选在C盘!!!

“”

3、寻找用户帮助手册

进入固件所在文件夹,里面包含很多内容。

“”

比如说 官方提供的开发板程序,每个型号下面都有对应功能的实现,用户手册就在Drivers文件夹下面。

“”

“”

STM32 HAL库与标准库的区别

1、句柄

句柄(handle),有多种意义,其中第一种是指程序设计,第二种是指Windows编程。现在大部分都是指程序设计/程序开发这类。

  • 第一种解释:句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。

  • 第二种解释:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。

STM32的标准库中,句柄是一种特殊的指针,通常指向结构体!

在STM32的标准库中,假设我们要初始化一个外设(这里以USART为例),我们首先要初始化他们的各个寄存器。在标准库中,这些操作都是利用固件库结构体变量+固件库Init函数实现的:

USART_InitTypeDef USART_InitStructure;

USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式

USART_Init(USART3, &USART_InitStructure); //初始化串口1

可以看到,要初始化一个串口,需要:

1、对六个位置进行赋值
2、然后引用Init函数

USART_InitStructure并不是一个全局结构体变量,而是只在函数内部的局部变量,初始化完成之后,USART_InitStructure就失去了作用。而在HAL库中,同样是USART初始化结构体变量,我们要定义为全局变量。

UART_HandleTypeDef UART1_Handler;

右键查看结构体成员

typedef struct
{
   USART_TypeDef                 *Instance;        /*!< UART registers base address        */
   UART_InitTypeDef              Init;             /*!< UART communication parameters      */
   uint8_t                       *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */
   uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */
   uint16_t                      TxXferCount;      /*!< UART Tx Transfer Counter           */
   uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */
   uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */
   uint16_t                      RxXferCount;      /*!< UART Rx Transfer Counter           */  
   DMA_HandleTypeDef             *hdmatx;          /*!< UART Tx DMA Handle parameters      */ 
   DMA_HandleTypeDef             *hdmarx;          /*!< UART Rx DMA Handle parameters      */
   HAL_LockTypeDef               Lock;             /*!< Locking object                     */
   __IO HAL_UART_StateTypeDef    State;            /*!< UART communication state           */
   __IO uint32_t                 ErrorCode;        /*!< UART Error code                    */
}UART_HandleTypeDef;

我们发现,与标准库不同的是,该成员不仅:

1、包含了之前标准库就有的六个成员(波特率,数据格式等),

2、还包含过采样、(发送或接收的)数据缓存、数据指针、串口 DMA 相关的变量、各种标志位等等要在整个项目流程中都要设置的各个成员。

该 UART1_Handler就被称为串口的句柄,它被贯穿整个USART收发的流程,比如开启中断:

HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

比如后面要讲到的MSP与Callback回调函数:

void HAL_UART_MspInit(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

在这些函数中,只需要调用初始化时定义的句柄UART1_Handler就好。

2、MSP函数

MSP: MCU Specific Package 单片机的具体方案

MSP是指和MCU相关的初始化,引用一下正点原子的解释,个人觉得说的很明白:

我们要初始化一个串口,首先要设置和 MCU 无关的东西,例如波特率,奇偶校验,停止位等,这些参数设置和 MCU 没有任何关系,可以使用 STM32F1,也可以是 STM32F2/F3/F4/F7上的串口。而一个串口设备它需要一个 MCU 来承载,例如用 STM32F4 来做承载,PA9 做为发送,PA10 做为接收,MSP 就是要初始化 STM32F4 的 PA9,PA10,配置这两个引脚。所以 HAL驱动方式的初始化流程就是:

  • HAL_USART_Init()—>HAL_USART_MspInit() ,先初始化与 MCU无关的串口协议,再初始化与 MCU 相关的串口引脚。

  • 在 STM32 的 HAL 驱动中HAL_PPP_MspInit()作为回调,被 HAL_PPP_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参数内容。

在HAL库中,几乎每初始化一个外设就需要设置该外设与单片机之间的联系,比如IO口,是否复用等等,可见,HAL库相对于标准库多了MSP函数之后,移植性非常强,但与此同时却增加了代码量和代码的嵌套层级。可以说各有利弊。

同样,MSP函数又可以配合句柄,达到非常强的移植性:

void HAL_UART_MspInit(UART_HandleTypeDef *huart);

3、Callback函数

类似于MSP函数,个人认为Callback函数主要帮助用户应用层的代码编写。

还是以USART为例,在标准库中,串口中断了以后,我们要先在中断中判断是否是接收中断,然后读出数据,顺便清除中断标志位,然后再是对数据的处理,这样如果我们在一个中断函数中写这么多代码,就会显得很混乱:

void USART3_IRQHandler(void)                 //串口1中断服务程序
{
 u8 Res;
 if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
 {
  Res =USART_ReceiveData(USART3); //读取接收到的数据
  /*数据处理区*/
  }      
     } 
} 

而在HAL库中,进入串口中断后,直接由HAL库中断函数进行托管:

void USART1_IRQHandler(void)                 
{ 
 HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库中断处理公用函数
 /***************省略无关代码****************/ 
}

HAL_UART_IRQHandler这个函数完成了判断是哪个中断(接收?发送?或者其他?),然后读出数据,保存至缓存区,顺便清除中断标志位等等操作。

比如我提前设置了,串口每接收五个字节,我就要对这五个字节进行处理。在一开始我定义了一个串口接收缓存区:

/*HAL库使用的串口接收缓冲,处理逻辑由HAL库控制,接收完这个数组就会调用HAL_UART_RxCpltCallback进行处理这个数组*/
/*RXBUFFERSIZE=5*/
u8 aRxBuffer[RXBUFFERSIZE];

在初始化中,我在句柄里设置好了缓存区的地址,缓存大小(五个字节)

/*该代码在HAL_UART_Receive_IT函数中,初始化时会引用*/
    huart->pRxBuffPtr = pData;//aRxBuffer
    huart->RxXferSize = Size;//RXBUFFERSIZE
    huart->RxXferCount = Size;//RXBUFFERSIZE

则在接收数据中,每接收完五个字节,HAL_UART_IRQHandler才会执行一次Callback函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

在这个Callback回调函数中,我们只需要对这接收到的五个字节(保存在aRxBuffer[]中)进行处理就好了,完全不用再去手动清除标志位等操作。

所以说Callback函数是一个应用层代码的函数,我们在一开始只设置句柄里面的各个参数,然后就等着HAL库把自己安排好的代码送到手中就可以了~

综上,就是HAL库的三个与标准库不同的地方之个人见解。个人觉得从这三个小点就可以看出HAL库的可移植性之强大,并且用户可以完全不去理会底层各个寄存器的操作,代码也更有逻辑性。但与此带来的是复杂的代码量,极慢的编译速度,略微低下的效率。看怎么取舍了。

STM32 HAL库结构

说到STM32的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX就是以HAL库为基础的,且目前仅支持HAL库及LL库!首先看一下,官方给出的HAL库的包含结构:

“STM32的HAL库分析及使用"

1)stm32f4xx.h主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件:

#if defined(STM32F405xx)
  #include "stm32f405xx.h"
#elif defined(STM32F415xx)
  #include "stm32f415xx.h"
#elif defined(STM32F407xx)
  #include "stm32f407xx.h"
#elif defined(STM32F417xx)
  #include "stm32f417xx.h"
#else
 #error "Please select first the target STM32F4xx device used in your application (in stm32f2xx.h file)"
#endif

紧接着,其会包含stm32f4xx_hal.h。

2)stm32f4xx_hal.h:stm32f4xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置

3)stm32f4xx_hal_conf.h :该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。

接下来对于HAL库的源码文件进行一下说明,HAL库文件名均以stm32f4xx_hal开头,后面加上_外设或者模块名(如:stm32f4xx_hal_adc.c):

4)库文件:stm32f4xx_hal_ppp.c/.h // 主要的外设或者模块的驱动源文件,包含了该外设的通用API

stm32f4xx_hal_ppp_ex.c/.h // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API。

stm32f4xx_hal.c/.h // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API

5)其他库文件

用户级别文件:

stm32f4xx_hal_msp_template.c // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。

stm32f4xx_hal_conf_template.h // 用户级别的库配置文件模板。使用者复制到自己目录下使用

system_stm32f4xx.c // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。它不在启动时配置系统时钟(与标准库相反)。时钟的配置在用户文件中使用HAL API来完成。startup_stm32f4xx.s // 芯片启动文件,主要包含堆栈定义,终端向量表等 stm32f4xx_it.c/.h // 中断处理函数的相关实现

6)main.c/.h //

根据HAL库的命名规则,其API可以分为以下三大类:

初始化/反初始化函数:

 HAL_PPP_Init(), HAL_PPP_DeInit()

IO 操作函数:

HAL_PPP_Read(),
HAL_PPP_Write(),
HAL_PPP_Transmit(), 
HAL_PPP_Receive()

控制函数:

 HAL_PPP_Set (), 
  HAL_PPP_Get ().

状态和错误:

  ** HAL_PPP_GetState (), 
  HAL_PPP_GetError ().

注意:

目前LL库是和HAL库捆绑发布的,所以在HAL库源码中,还有一些名为 stm32f2xx_ll_ppp的源码文件,这些文件就是新增的LL库文件。使用CubeMX生产项目时,可以选择LL库。

HAL库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分:

  • 处理外设句柄(实现用户功能)
  • 处理MSP
  • 处理各种回调函数

相关知识如下:

1、外设句柄定义

用户代码的第一大部分:对于外设句柄的处理。HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。*所有的函数都是工作在ppp_HandleTypeDef指针之下。

  • 多实例支持:每个外设/模块实例都有自己的句柄。因此,实例资源是独立的
  • 下面,以ADC为例
  • 外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。

/** 
 * @brief  ADC handle Structure definition
 */ 
typedef struct
{
 ADC_TypeDef                   *Instance;                   /*!< Register base address */
 ADC_InitTypeDef               Init;                        /*!< ADC required parameters */
  __IO uint32_t                 NbrOfCurrentConversionRank;  /*!< ADC number of current conversion rank */
 DMA_HandleTypeDef             *DMA_Handle;                 /*!< Pointer DMA Handler */
 HAL_LockTypeDef               Lock;                        /*!< ADC locking object */
 __IO uint32_t                 State;                       /*!< ADC communication state */
 __IO uint32_t                 ErrorCode;                   /*!< ADC Error code */
}ADC_HandleTypeDef;

从上面的定义可以看出,ADC_HandleTypeDef中包含了ADC可能出现的所有定义,对于用户想要使用ADC只要定义一个ADC_HandleTypeDef的变量,给每个变量赋好值,对应的外设就抽象完了。接下来就是具体使用了。

当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。例如以下外设:

  • GPIO

  • SYSTICK

  • NVIC

  • RCC

  • FLASH

以GPIO为例,对于HAL_GPIO_Init() 函数,其只需要GPIO 地址以及其初始化参数即可。

2、 三种编程方式

HAL库对所有的函数模型也进行了统一。在HAL库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):

HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);

HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);

其中,带_IT的表示工作在中断模式下;带_DMA的工作在DMA模式下(注意:DMA模式下也是开中断的);什么都没带的就是轮询模式(没有开启中断的)。至于使用者使用何种方式,就看自己的选择了。

此外,新的HAL库架构下统一采用宏的形式对各种中断等进行配置(原来标准外设库一般都是各种函数)。针对每种外设主要由以下宏:

__HAL_PPP_ENABLE_IT(HANDLE, INTERRUPT): 使能一个指定的外设中断
__HAL_PPP_DISABLE_IT(HANDLE, INTERRUPT):失能一个指定的外设中断
__HAL_PPP_GET_IT (HANDLE, __ INTERRUPT __):获得一个指定的外设中断状态
__HAL_PPP_CLEAR_IT (HANDLE, __ INTERRUPT __):清除一个指定的外设的中断状态
__HAL_PPP_GET_FLAG (HANDLE, FLAG):获取一个指定的外设的标志状态
__HAL_PPP_CLEAR_FLAG (HANDLE, FLAG):清除一个指定的外设的标志状态
__HAL_PPP_ENABLE(HANDLE) :使能外设
__HAL_PPP_DISABLE(HANDLE) :失能外设
__HAL_PPP_XXXX (HANDLE, PARAM) :指定外设的宏定义
_HAL_PPP_GET IT_SOURCE (HANDLE, __ INTERRUPT __):检查中断源

3、 三大回调函数

在HAL库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
 /*Configure the SysTick to have interrupt in 1ms time basis*/
 HAL_SYSTICK_Config(SystemCoreClock/1000U);
 /*Configure the SysTick IRQ priority */
 HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
 /* Return function status */
 return HAL_OK;
}

有些则没有被实现,例如:

__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hspi);
  /* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file
  */
}

所有带有__weak关键字的函数表示,就可以由用户自己来实现。如果出现了同名函数,且不带__weak关键字,那么连接器就会采用外部实现的同名函数。

通常来说,HAL库负责整个处理和MCU外设的处理逻辑,并将必要部分以回调函数的形式给出到用户,用户只需要在对应的回调函数中做修改即可。HAL库包含如下三种用户级别回调函数(PPP为外设名):

1)外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理):

HAL_PPP_MspInit()和 HAL_PPP_MspDeInit**

例如:

__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)

在HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt)

2)处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),

例如:

__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)

当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用错误处理回调函数:

HAL_PPP_ErrorCallback

例如:

__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef hspi)*

3)当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用

错误处理回调函数:

HAL_PPP_ErrorCallback

例如:

__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef hspi)*

当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用。

绝大多数用户代码均在以上三大回调函数中实现。

HAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的反初始化(DeInit)函数是非常有必要的。

某些外设多次初始化时不调用返回会导致初始化失败。完成回调函数有多中,例如串口的完成回调函数有

HAL_UART_TxCpltCallback 
HAL_UART_TxHalfCpltCallback

(用户代码的第三大部分:对于上面第二点和第三点的各种回调函数的处理)在实际使用中,发现HAL仍有不少问题,例如在使用USB时,其库配置存在问题。

HAL库移植使用

基本步骤:

1)复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。

2)复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库。

3)在使用HAL库时,必须先调用函数:HAL_StatusTypeDef HAL_Init(void)(该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void))

4)HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中)

5)关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。

6)对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。

例如:

Uart中,HAL提供了

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

函数,用户只需要触发中断后,用户只需要调用该函数即可,同时,自己的代码写在对应的回调函数中即可!如下:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);

使用了哪种就用哪个回调函数即可!

基本结构

综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下:

1)配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef huart))

2)编写Msp 例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)

3)实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数

参考文档及网文链接

ST - Description of STM32F4 HAL and LL drivers.pdf

ST - en.stm32_embedded_software_offering.pdf

作者:ZCShoucsdn 来源:CSDN 原文:https://blog.csdn.net/zcshoucsdn/article/details/55213616

作者:ZCShoucsdn 来源:CSDN 原文:https://blog.csdn.net/zcshoucsdn/article/details/55213616

作者:Error_4O4 来源:CSDN 原文:https://blog.csdn.net/weixin_43186792/article/details/88759321

作者:csdnpapa 来源:CSDN 原文:https://blog.csdn.net/csdnpapa/article/details/79309937

来源:嵌入式大杂烩
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 376

页面

订阅 RSS - STM32