Diving into the STM32 HAL-----Interrupts

        硬件管理就是处理异步事件。其中大部分来自硬件外围设备。例如,计时器达到配置的 period 值,或者 UART 在数据到达时发出警告。

        中断是一个异步事件,它会导致按优先级停止执行当前代码(中断越重要,其优先级越高;这将导致优先级较低的中断被挂起)。为中断提供服务的代码称为 Interrupt Service Routine (ISR)。

        中断可以由硬件和软件本身产生。ARM 架构区分了两种类型:由硬件发起的中断,由软件发起的异常(例如,访问无效的内存位置)。在 ARM 术语中,中断是一种异常。Cortex-M 处理器提供了一个专门用于异常管理的单元。这称为嵌套向量中断控制器 (NVIC)。

1、NVIC中断控制器

        NVIC 是基于 Cortex-M 的微控制器内部的专用硬件单元,负责异常处理。下图是NVIC 单元、处理器内核和外设之间的关系。必须区分两种类型的外设:CortexM 内核外部同时也是STM32 MCU 内部的外设(例如定时器、UARTS 等),以及 MCU 外部的外设(中断源是 MCU I/O)。

       ARM 区分源自 CPU 内核内部的系统异常和来自外部外设的硬件异常,也称为中断请求 (IRQ)。程序员只需要使用特定的 ISR 管理异常(通常使用 C 语言编写)。处理器知道在哪里找到这些例程,这要归功于一个包含 Interrupt Service Routines 内存中地址的间接表。这个表通常称为矢量表,每个 STM32 微控制器都定义了自己的表。异常类型如下:

        向量表包含处理程序例程的地址(实际上是一个间接表),Cortex-M 内核需要一种方法来在内存中查找向量表。按照惯例,在所有基于 Cortex-M 的处理器中,向量表都从硬件地址 0x0000 0000 开始。如果我们的固件设计为矢量表驻留在内部闪存中(一种非常常见的情况),那么矢量表将从所有 STM32 MCU 中的 0x0800 0000 地址开始放置。然而,在第 1 章中,我们看到当 CPU 启动时,0x0800 0000 地址会自动别名到 0x0000 0000。

2、使能中断

        当 STM32 MCU 启动时,默认情况下仅启用 Reset、NMI 和 Hard Fault 异常。其余的异常和外设中断被禁用,并且必须根据请求启用它们。
        为了启用 IRQ,CubeHAL 提供了以下功能:

void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);

void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);

        其中 IRQn_Type 是为该特定 MCU 定义的所有异常和中断的枚举。IRQn_Type 枚举是 ST 驱动程序 HAL 的一部分,它在 stm32XXxx.h头文件中定义。

        需要注意的是,上面两个函数在 NVIC 控制器级别启用/禁用中断。从前面中可以看出,连接到该线路的外设断言了一条中断线路。例如,USART2 外设断言与 NVIC 控制器内部的 USART2_IRQn 中断线相对应的中断线。这意味着必须正确配置单个外设才能在中断模式下工作。大多数 STM32 外设都设计为在中断模式下工作。通过使用特定的 HAL 例程,我们可以在外设级别启用中断。例如,使用 HAL_USART_Transmit_IT(), 我们在中断模式下隐式配置 USART 外设。显然,还需要通过调用 HAL_NVIC_EnableIRQ() 在 NVIC 级别启用相应的中断。

2.1、中断线

        如下图,所有 Px0 引脚都连接到 EXTI0,所有 Px10 引脚都连接到 EXTI10,所有 Px15 引脚都连接到 EXTI15。但是,EXTI 10 和 15 线路在 NVIC 内共享相同的 IRQ(因此由相同的 ISR 提供服务)。

例如,PC13按键手动控制PB5 LED闪烁:

int main(void) {
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/* MCU Configuration----------------------------------------------------------*/
	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();
	/* Configure the system clock */
	SystemClock_Config();
	
	/* GPIOA and GPIOC Configuration----------------------------------------------*/
	/* GPIO Ports Clock Enable */
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
	
	/*Configure GPIO pin : PC13 */
	GPIO_InitStruct.Pin = GPIO_PIN_13;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
	GPIO_InitStruct.Pull = GPIO_PULLDOWN;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	
	/*Configure GPIO pin : PB5 */
	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	/* Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
	
	/* EXTI interrupt init*/
	HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
	
	while (1);
}

void EXTI15_10_IRQHandler(void) {
	if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_13) != RESET) {
		__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_13);
		HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
	}
}

        首先,GPIO PC13 配置为每次从低电平变为高电平时触发中断。这是通过设置 GPIO 来实现的。Mode 设置为 GPIO_MODE_IT_RISING。

        接下来,启用与 Px13 引脚关联的 EXTI 线的中断,即 EXTI15_10_IRQn。

        最后,定义函数 void EXTI15_10_IRQHandler(),这是与向量表中 EXTI15_10  IRQ 关联的 ISR 例程。ISR 的内容非常简单。由于 EXTI15_10 线连接到不同的引脚,检查 PC13 是否是触发中断的引脚。如果是这样,切换 PB5 I/O 并清除与 EXTI 线关联的pending处理位。

        幸运的是,ST HAL 提供了一种抽象机制,除非我们需要处理它们,否则我们无需处理所有这些细节。前面的例子可以按以下方式重写:

int main(void) {
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/* MCU Configuration----------------------------------------------------------*/
	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();
	/* Configure the system clock */
	SystemClock_Config();
	
	/* GPIOA and GPIOC Configuration----------------------------------------------*/
	/* GPIO Ports Clock Enable */
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();
	
	/*Configure GPIO pin : PC13 */
	GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
	GPIO_InitStruct.Pull = GPIO_PULLDOWN;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	
	/*Configure GPIO pin : PB5 */
	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	/* Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
	
	/* EXTI interrupt init*/
	HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
	
	while (1);
}

void EXTI15_10_IRQHandler(void) {
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}
	
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if(GPIO_Pin == GPIO_PIN_13)
	    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, SET);
	else if(GPIO_Pin == GPIO_PIN_12)
	    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, RESET);
}

        这次我们将引脚 PC13 和 PC12 都配置为中断源。当调用 EXTI15_10_IRQHandler() ISR 时,我们将控制权转移到 HAL 内的 HAL_GPIO_EXTI_IRQHandler() 函数。这将为我们执行所有与中断相关的活动,并且它将调用 HAL_GPIO_EXTI_Callback() 例程,该例程传递生成 IRQ 的实际 GPIO(请记住,PC12 和 PC13 连接到同一条外线中断线)。

        下图清楚地显示了从 IRQ生成的调用序列。HAL 中几乎所有的 IRQ 处理程序例程都使用这种机制。

2.2、使用 CubeMX 启用中断

        CubeMX 会自动将已启用的 ISR 添加到 Core/Src/stm32XXxx_it.c 文件中,并负责启用 IRQ。此外,它还为我们添加了要调用的相应 HAL 处理程序例程,如下所示:

/**
* @brief This function handles EXTI line[15:10] interrupts.
*/
void EXTI15_10_IRQHandler(void) {
	/* USER CODE BEGIN EXTI15_10_IRQn 0 */
	/* USER CODE END EXTI15_10_IRQn 0 */
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
	/* USER CODE BEGIN EXTI15_10_IRQn 1 */
	/* USER CODE END EXTI15_10_IRQn 1 */
}

我们只需要在应用程序代码中添加相应的回调函数(例如 HAL_GPIO_EXTI_Callback() 例程)。

        注意:stm32XX_hal_cortex.c 模块清楚地展示了 ST HAL 和 CMSIS 包之间的交互,因为它完全依赖官方的 ARM 包来处理底层的 Cortex-M NVIC 控制器。每个 HAL_NVIC_xxx() 函数都是相应 CMSIS NVIC_xxx() 函数的包装。这意味着我们可以使用 CMSIS API 对 NVIC 控制器进行编程。

3、中断生命周期

        一旦处理了中断,了解它们的生命周期就非常重要了。尽管 Cortex-M 内核会自动为我们完成大部分工作,但我们必须注意在中断管理过程中可能造成混淆的某些方面。

        中断可以:

        1. 禁用 (默认行为) 或启用:我们调用 HAL_NVIC_EnableIRQ()/HAL_NVIC_DisableIRQ()函数启用/禁用它;

        2. 处于待处理状态pending(请求正在等待送达)或未待处理not pending;

        3. 处于 Active (正在服务) 或 Inactive 状态。

        我们已经在上一段中看到过第一种情况。现在,研究发生中断时会发生什么很重要。当中断触发时,它会被标记为 pending (挂起),直到处理器可以处理它。如果当前没有其他中断正在处理,则处理器会自动清除其 pending状态,处理器几乎立即开始为其提供服务。

        特别注意:按理解,这里的pending bit是NVIC给出的ISR pending bit,与后面说的IRQ pending bit不是一个。

        上图显示了其工作原理。中断 A 在时间 t0 触发,由于 CPU 没有为另一个中断提供服务,因此其pending bit位被清除,其执行立即开始(中断变为活动状态)。在时间 t1 触发 B 中断,但在这里我们假设它的优先级低于 A。因此,它将处于待处理状态pending,直到 A ISR 结束其操作。发生这种情况时,pending位会自动清除,ISR 变为活动状态。

        在这里,重要的是要理解“立即”这个词,我们并不是说中断执行立即开始。如果没有其他中断正在运行,Cortex-M3/4/7/33 内核在 12 个 CPU 周期内提供中断,而 Cortex-M0 在 15 个周期内提供中断,Cortex-M0+ 在 16 个周期内提供中断。

        上图显示了另一个重要情况。在这里,我们发现中断A触发,CPU 可以立即为它提供服务。中断B在A提供服务时触发,因此它保持待处理状态pending,直到 A 完成。发生这种情况时,中断B的 pending bit 将被清除,并且它变为活动状态。但是,一段时间后,中断A再次触发,由于它具有更高的优先级,因此中断B被暂停(变为非活动状态),并立即开始执行A。完成此操作后,中断B将再次变为活动状态,并完成其作业。

        NVIC 为程序员提供了高度的灵活性。如上图所示中断在执行过程中可以强制再次触发,只需再次设置其pending bit 位。Cortex-M 架构的设计目的是,如果一个中断触发,而处理器已经在为另一个中断提供服务,则无需恢复之前入栈的应用程序即可对其进行服务。这种技术称为尾链,它可以加快中断管理速度并降低功耗。

        同样,当中断处于 pending 状态时,可以通过清除其 pending bit 来取消中断的执行,如下图所示。

        当中断发生时,大多数 STM32 外设都会置位一个连接到 NVIC 的特定信号,该信号映射到外设存储器中的某一特定位---Interrupt Request 位。外设的Interrupt Request 位将保持高电平,直到应用程序代码手动清除它。例如,在前面必须使用宏 __HAL_GPIO_EXTI_CLEAR_IT() 显式清除 EXTI中断线 IRQ 挂起位。如果我们不清除该位,将触发新的中断,直到它被清除。

        上图清楚地显示了外设IRQ pending状态和 ISR pending状态之间的关系。信号 I/O 是驱动 I/O 的外部外设(例如,连接到引脚的触摸开关)。当信号电平发生变化时,连接到该 I/O 的 EXTI 中断线会生成一个 IRQ 并置位相应的Interrupt Request位。因此,NVIC 会生成中断。当处理器开始为 ISR 提供服务时,ISR 挂起位会自动清除,但外设 IRQ 挂起位(Interrupt Request)将保持高电平,直到应用程序代码清除它。

        上图显示了另一种情况。这里我们强制执行 ISR 设置其 pending bit。由于此时不涉及外设,因此无需清除相应的 IRQ pending bit。由于 IRQ pending位的存在取决于外设,因此使用 ST HAL 函数来管理中断总是合适的,将所有底层细节留给 HAL 实现。但是,请记住,为避免丢失重要的中断,在开始处理 ISR 时就清除外设的 IRQ 挂起状态位是一种很好的做法。处理器内核不跟踪多个中断(它不会对中断进行排队),因此如果我们在 ISR 末尾清除外设pending 位,我们可能会丢失在中间触发的重要 IRQ。

        要查看中断是否处于待处理状态pending(即已触发但未运行),我们可以使用 HAL 函数:

uint32_t HAL_NVIC_GetPendingIRQ(IRQn_Type IRQn);

如果 IRQ not pending,则返回 0,否则返回 1。

        要设置 IRQ 的pending bit,我们可以使用 HAL 函数:

void HAL_NVIC_SetPendingIRQ(IRQn_Type IRQn);

这将导致中断触发,因为它本应该是由硬件生成的。Cortex-M 处理器的一个显着特点是,可以在一个中断的 ISR 例程内触发另一个中断。

        相反,要清除 IRQ 的pending bit,我们可以使用函数:

void HAL_NVIC_ClearPendingIRQ(IRQn_Type IRQn);

同样,可以在为一个ISR 内部取消另一处理处于pending状态的中断的执行。

        要检查 ISR 是否处于活动状态(IRQ 正在服务),我们可以使用以下函数:

uint32_t HAL_NVIC_GetActive(IRQn_Type IRQn);

如果 IRQ 处于活动状态,则返回 1,否则返回 0。

4、中断优先级

        ARM Cortex-M 架构的一个显著特点是能够对中断进行优先级排序(具有固定优先级的前三个软件异常除外)。中断优先级允许定义两件事:在并发中断的情况下定义首先执行的 ISR;高优先级的 ISR抢占。Cortex-M0/0+ 和 Cortex-M3/4/7/33 内核之间的 NVIC 优先级机制有很大不同。

4.1、Cortex-M0/0+

        基于 Cortex-M0/0+ 的微控制器具有更简单的中断优先级机制。这意味着 STM32F0/G0/L0 MCU 的行为与其他 STM32 微控制器不同。如果要在 STM32 系列之间移植代码,则必须特别注意。在 Cortex-M0/0+ 内核中,每个中断的优先级通过称为 IPR 的 8 位寄存器定义。在 ARMv6-M 内核架构中,仅使用该寄存器的 4 位,允许多达 16 个不同的优先级。然而,在实践中,实现这些内核的 STM32 MCU 仅使用该寄存器的两个高位,将所有其他位视为零。

        图         上图显示了IPR。这意味着我们只有四个最大优先级:0x00、0x40、0x80 0xC0。此数字越低,优先级越高。也就是说,优先级等于 0x40 的 IRQ 的优先级高于优先级级别等于 0xC0的 IRQ。如果两个中断同时触发,则优先级较高的中断将首先得到服务。如果处理器已经在为中断提供服务,并且触发了更高优先级的中断,则当前中断将挂起,控制权将传递到更高优先级的中断。完成此操作后,如果在此期间没有其他优先级更高的中断发生,则执行将返回到上一个中断。这种机制称为中断抢占。

        上图显示了中断抢占的示例。A 是在时间 t0 触发的优先级较低的 IRQ。ISR 开始执行,但具有较高优先级(较低优先级数字)的 IRQ B 在时间 t1 触发,并且 A ISR 的执行已停止。当 B 完成其作业时,A ISR 的执行将继续,直到完成。这种由中断优先级引起的 “嵌套” 机制导致了 NVIC 控制器的名称,即 Nested Vectored Interrupt Controller。

        Cortex-M0/0+ 与 Cortex-3/4/7/33 内核相比有一个重要的区别。中断优先级是静态的。这意味着一旦启用中断,它的优先级就不能再改变,直到我们再次禁用 IRQ。

        CubeHAL 提供了以下函数来为 IRQ 分配优先级:

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);

        HAL_NVIC_SetPriority() 函数接受我们将要配置的 IRQn 和 PreemptPriority,这是我们将要分配给 IRQ 的抢占优先级。CMSIS API 以及 CubeHAL 库的设计使得 PreemptPriority 的优先级编号范围为 0 到 4。该值在内部自动转移到最高有效位。这简化了将代码移植到具有不同优先级位数的其他 MCU 的过程(这就是为什么芯片供应商仅使用 IPR 寄存器的左侧部分的原因)。

        HAL_NVIC_SetPriority() 函数还接受附加参数 SubPriority,该参数在 CubeF0 和 CubeL0 HAL 中被忽略,因为底层 Cortex-M 处理器不支持中断子优先级。在这里,ST决定对基于 Cortex-M3/4/7/33 的处理器使用相同 API。他们决定这样做可能是为了简化不同 STM32 MCU 之间的移植代码。奇怪的是,他们决定定义相应的函数来检索 IRQ 的优先级,方式如下:

uint32_t HAL_NVIC_GetPriority(IRQn_Type IRQn);

这与 HAL 中为基于 Cortex-M3/4/7/33 的处理器定义的完全不同。

        以下示例显示了中断优先级机制的工作原理。

uint8_t blink = 0;
	
int main(void) {
	GPIO_InitTypeDef GPIO_InitStruct;
	
	HAL_Init();
	
	/* GPIO Ports Clock Enable */
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_GPIOA_CLK_ENABLE();
	
	/*Configure GPIO pin : PC13 - USER BUTTON */
	GPIO_InitStruct.Pin = GPIO_PIN_13 ;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
	GPIO_InitStruct.Pull = GPIO_PULLDOWN;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	
	/*Configure GPIO pin : PA0 */
	GPIO_InitStruct.Pin = GPIO_PIN_0 ;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	/*Configure GPIO pin : PB5 - LD2 LED */
	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0x1, 0);
	HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
	
	HAL_NVIC_SetPriority(EXTI0_IRQn, 0x0, 0);
	HAL_NVIC_EnableIRQ(EXTI0_IRQn);
	
	while(1);
}
	
void EXTI15_10_IRQHandler(void) {
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}
	
void EXTI0_IRQHandler(void) {
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
}
	
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if(GPIO_Pin == GPIO_PIN_13) {
		blink = 1;
		while(blink) {
			HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
			for(volatile int i = 0; i < 100000; i++) {
			/* Busy wait */
			}
		}
	}
	else {
		blink = 0;
	}
}

        这里我们有两个与 EXTI2 和 EXTI13 关联的 IRQ。相应的 ISR 调用 HAL HAL_GPIO_EXTI_IRQHandler(),后者反过来调用 HAL_GPIO_EXTI_Callback() 回调,传递中断中涉及的 GPIO。当按下连接到 PC13 信号的用户按钮时,ISR 开始无限循环,直到闪烁全局变量为 >0。此循环使LED 快速闪烁。当引脚被置为低电平时,EXTI0_IRQHandler() 触发,这会导致 HAL_GPIO_EXTI_IRQHandler() 将 blink 变量设置为 0。EXTI15_10_IRQHandler() 现在可以结束了。每个中断的优先级配置。由于中断优先级在基于 Cortex-M0/0+ 的 MCU 中是静态的,因此我们必须在启用相应的中断之前对其进行设置。

        请注意,这是处理中断的一种非常糟糕的方法。将 MCU 锁定在中断内(ISR中的while)是一种糟糕的编程风格,它是嵌入式编程中万恶之源。不幸的是,这是作者想到的唯一例子,考虑到此时这本书仍然涵盖很少的主题。每个 ISR 都必须设计为尽可能短的持续时间,否则其他基本 ISR 可能会长时间被掩盖,从而丢失来自其他外围设备的重要信息。

        作为练习,尝试使用中断优先级,看看如果两个中断具有相同的优先级会发生什么。

        可能会出现一个很好的问题:为什么不使用 HAL_Delay() 函数而是 busywait?答案很简单,它与中断优先级直接相关。HAL_Delay() 在等待 SysTick 计时器递增全局 uwTick 变量(以 1KHZ 频率递增的无符号 32 位整数)时执行忙等待。然而,正如我们将在后面看到的那样,SysTick 定时器被配置为在中断模式下工作,并且中断优先级由 CubeMX 设置 - 默认情况下 - 尽可能低的优先级(查看TICK_INT_PRIORITY)。通过将 EXTI15_10_IRQn 中断的优先级设置为 1 将导致 SysTick 计时器的 ISR 永远不会触发,直到 blink 变量变为 0 并且 ISR 可以终止。

        您可能会注意到,通常只需触摸电线即可触发中断,即使它没有接地。为什么会这样?基本上有两个原因会导致中断 “意外” 触发。首先,现代微控制器试图最大限度地减少与使用内部上拉/下拉电阻器相关的功率泄漏。因此,这些电阻器的值选择较高(大约 50kΩ)。如果你明白分压器方程,你会发现当上拉/下拉电阻器具有高电阻值时,很容易将 I/O 拉低或拉高。其次,这里我们没有对输入引脚进行足够的去抖。去抖动是将“不稳定”源(例如机械开关)产生的反弹影响降至最低的过程。通常,在硬件或软件中执行去抖动,方法是计算从输入状态的第一个变化开始经过多少时间:在我们的例子中,如果输入保持低电平的时间超过给定的时间(通常在 100 毫秒到 200 毫秒之间就足够了),那么我们可以说输入已经有效地接地。正如我们将在后面看到的那样,我们还可以使用配置为在输入捕获模式下工作的 timer 的一个通道来检测 GPIO 何时更改状态。这使我们能够自动计算从第一个事件开始经过的时间。此外,定时器通道支持集成和可编程的硬件滤波器,这使我们能够减少外部组件的数量,以对 I/O 进行去抖。

4.2、Cortex-M3/4/7/33

        Cortex-M3/4/7/33 中的中断优先机制比基于 Cortex-M0/0+ 的微控制器中的中断优先机制更先进。开发人员具有更高的灵活性,这通常是新手头疼的几个根源。此外,ARM 和 ST 文档中呈现中断优先级的方式有点违反直觉。在 Cortex-M3/4/7/33 内核中,每个中断的优先级是通过 IPR 寄存器定义的。这是 ARMv7-M 内核架构中的 8 位寄存器,最多支持 255 个不同的优先级。然而,在实践中,实现 Cortex-M3/4/7 内核的 STM32 MCU 仅使用该寄存器的四个高位,而基于 Cortex-M33 内核的 STM32 MCU 仅使用该寄存器的三个高位。

        上图清楚地显示了IPR 的内容。这意味着我们只有 16 个最高优先级级别:0x00、0x10、0x20、0x30、0x40、0x50、0x60、0x70、0x80、0x90、0xA0、0xB0、0xC0、0xD0、0xE0、0xF0。此数字越低,优先级越高。也就是说,优先级等于 0x10 的 IRQ 的优先级高于优先级等于 0xA0的 IRQ。如果两个中断同时触发,则优先级较高的中断将首先得到服务。如果处理器已经在为中断提供服务,并且触发了更高优先级的中断,则当前中断将挂起,控制权将传递给更高优先级的中断。完成此操作后,如果在此期间没有其他优先级更高的中断发生,则执行将返回到上一个中断。

        到目前为止,其机制与 Cortex-M0/0+ 基本相同。复杂性在于 IPR 寄存器可以在逻辑上细分为两部分:定义抢占优先级的一系列位和定义子优先级的一系列位。第一个优先级控制 ISR 之间的抢占优先级。如果一个 ISR 的优先级高于另一个 ISR,它将抢占较低优先级 ISR 的执行,以防它触发。子优先级确定在多个待处理 ISR 的情况下将首先执行什么 ISR,但它不会对 ISR 抢占执行操作。

        使中断优先级的理解复杂化的是,在官方文档中,有时抢占优先级也称为组优先级。这会导致很多混淆,因为新手倾向于认为这些位定义了一种访问控制列表 (ACL) 权限。这里,为了简化对这件事的理解,我们只说抢占优先级别。

        上图显示了中断抢占的示例。A 是在时间 t0 触发的优先级最低的 IRQ。ISR 开始执行,但具有较高优先级(较低优先级)的 IRQ B 在时间 t1 触发,并且 A ISR 的执行已停止。一段时间后,C IRQ 在时间 t2 触发,B ISR 停止,C ISR 开始执行。完成此操作后,B ISR 的执行将恢复,直到完成。发生这种情况时,将恢复 A ISR 的执行。这种由中断优先级引起的 “嵌套” 机制导致了 NVIC 控制器的名称,即 Nested Vectored Interrupt Controller。

        上图显示了子优先级如何影响多个待处理 ISR 的执行。这里我们有三个中断,都具有相同的最大优先级。在时间 t0 ,IRQ A 触发并立即进行服务。在 t1 B IRQ 触发时,但由于它与其他 IRQ 具有相同的优先级,因此它处于待处理状态。在时间 t2 时,C IRQ 也会触发,但与之前相同的原因,处理器将其保留为 pending 状态。当 A ISR 完成时,首先服务C IRQ,因为它的子优先级高于 B。只有当 C ISR 完成时,才能服务B IRQ。IPR 位的逻辑细分方式由 SCB->AIRCR 寄存器(系统控制块 (SCB) 寄存器的子组)定义,并且从一开始就强调这种解释 IPR 寄存器内容的方式对所有 ISR 都是全局的,这一点很重要。一旦我们定义了一个优先级方案(在 HAL 中也称为优先级分组),它就与系统中使用的所有中断通用。

        上图显示了 IPR 寄存器的所有五个可能的细分,而下表显示了每个细分方案允许的抢占优先级和子优先级级别的最大数量。如前所述,基于 Cortex-M33 内核的 STM32 MCU 仅使用 IPR 寄存器的三个高位。这意味着最多有 8 个优先级和 3 个优先级组。因此,NVIC_PRIORITYGROUP_4 根本不可用。

        CubeHAL 提供了以下函数来为 IRQ 分配优先级:

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);

        HAL 库的设计使 PreemptPriority 和 SubPriority 可以配置从 0 到 16 的优先级编号。该值在内部自动转移到最高有效位。这简化了将代码移植到具有不同优先级位数的其他 MCU 的过程(这就是为什么芯片供应商仅使用 IPR 寄存器的左侧部分的原因)。相反,要定义优先级分组,即如何在抢占优先级和子优先级之间细分 IPR 寄存器,可以使用以下函数:

void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);

        其中 PriorityGroup 参数是上表 2 中 NVIC Priority Group 列中的宏之一。以下示例显示了中断优先级机制的工作原理。

void SystemClock_Config(void);
uint8_t blink=0;
	
int main(void) {
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/* MCU Configuration----------------------------------------------------------*/
	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();
	/* Configure the system clock */
	SystemClock_Config();
	
	/* GPIOA, GPIOB and GPIOC Configuration----------------------------------------------*/
	/* GPIO Ports Clock Enable */
	__HAL_RCC_GPIOA_CLK_ENABLE();
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
	
	/*Configure GPIO pin : PC13 */
	GPIO_InitStruct.Pin = GPIO_PIN_13;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
	GPIO_InitStruct.Pull = GPIO_PULLDOWN;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	
	/*Configure GPIO pin : PA0 */
	GPIO_InitStruct.Pin = GPIO_PIN_0;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	
	/*Configure GPIO pin : PA5 */
	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	/* Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
	
	/* EXTI interrupt init*/
	HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0x1, 0x0);
	HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
	
	HAL_NVIC_SetPriority(EXTI0_IRQn, 0x0, 0x0);
	HAL_NVIC_EnableIRQ(EXTI0_IRQn);
	
	while (1);
}
	
void EXTI15_10_IRQHandler(void) {
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}
	
void EXTI0_IRQHandler(void) {
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}
	
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if (GPIO_Pin == GPIO_PIN_13) {
		blink = 1;
		while (blink) {
			HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
			for (volatile int i = 0; i < 100000; i++) {
				/* Busy wait */
			}
		}
	} else {
		blink = 0;
	}
}

        重要的是要注意一些基本的事情。首先,与基于 Cortex-M0/0+ 的微控制器不同,Cortex-M3/4/7/33 内核允许动态改变中断的优先级,即使已经开启了中断。其次,当优先级分组动态降低时,必须小心。

        让我们考虑以下示例。假设我们有三个 ISR,具有三个递减的优先级(优先级在括号内指定):A(0x0)、B(0x10)、C(0x20)。假设我们在优先级分组等于 NVIC_PRIORITYGROUP_4 时定义了这些优先级。如果我们将其降低到 NVIC_PRIORITYGROUP_1 级别,则当前的抢占级别将被解释为次级优先级。这将导致中断服务程序 A、B 和 C 具有相同的抢占级别(即 0x0),并且无法抢占它们。例如,查看下图,我们可以看到当优先级分组从 4 降低到 1 时,ISR C 的优先级会发生什么变化。当优先级分组设置为 4 时,C ISR 的优先级仅比最大优先级低两级,即 0(下一个最高级别是 0x10,这是 B 的优先级)。这意味着 C 可以被 A 和 B 抢占。但是,如果我们将优先级分组降低到 1,则 C 的优先级变为 0x0(只有位 7 充当优先级),其余位由 NVIC 控制器解释为次优先级。这可能导致以下情况: 1、所有中断将无法相互抢占;2、如果触发了 C 中断,并且 CPU 没有为另一个中断提供服务,则立即为 C 提供服务;3、如果 CPU 正在为 C ISR 提供服务,然后在短时间内触发 A 和 B,则 CPU 将在完成服务 C 后为 A 和 B 提供服务;4、如果 CPU 正在为另一个 ISR 提供服务,如果 C 触发,然后在片刻后触发 A 和 B,则首先为 A 提供服务,然后是 B,然后是 C。

        为了获得中断的优先级,HAL 定义了以下函数:

void HAL_NVIC_GetPriority(IRQn_Type IRQn, uint32_t PriorityGroup, uint32_t* pPreemptPriority, uint32_t* pSubPriority);

        这个函数与 HAL_NVIC_SetPriority() 不同:这里我们还必须指定 PriorityGroup,而 HAL_NVIC_SetPriority() 函数在内部计算它。

        可以使用以下函数获取当前的优先级分组:

uint32_t HAL_NVIC_GetPriorityGrouping(void);

4.3、在 CubeMX 中设置中断优先级

        CubeMX 还可用于设置 IRQ 优先级和优先级分组架构。此配置是通过 Configuration 视图完成的,单击 NVIC 按钮。此时将显示可启用的 ISR 列表,如下图所示。

        使用 Priority Group 组合框,我们可以设置优先级分组架构,然后为每个中断分配单独的优先级和子优先级。CubeMX 将自动生成相应的 C 代码,以在 MX_GPIO_Init() 函数中设置 IRQ 优先级。相反,全局优先级分组架构是在 HAL_MspInit() 函数中配置的。

5、中断重新触发

        让我们假设重新编排上面示例 ,使其使用引脚 PC12 而不是 PA0。在这种情况下,由于 EXTI12 和 EXTI13 共享相同的 IRQ,因此LED永远不会停止闪烁。由于在 Cortex-M 处理器中实现优先级机制的方式(即,具有给定优先级的异常不能被具有相同优先级的另一个异常抢占),异常和中断不是可重入的。因此,它们不能递归调用。但是,在大多数情况下,我们的代码可以重新编排以解决此限制。在以下示例中,闪烁代码在 main() 函数内执行,仅让 ISR 负责设置全局 blink 变量。

void SystemClock_Config(void);
uint8_t blink=0;
	
int main(void) {
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	
	/* MCU Configuration----------------------------------------------------------*/
	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();
	/* Configure the system clock */
	SystemClock_Config();
	
	/* GPIOA, GPIOB and GPIOC Configuration----------------------------------------------*/
	/* GPIO Ports Clock Enable */
	__HAL_RCC_GPIOA_CLK_ENABLE();
	__HAL_RCC_GPIOC_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
	
	/*Configure GPIO pin : PC12 & PC13 */
	GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
	GPIO_InitStruct.Pull = GPIO_PULLDOWN;
	HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
	
	/*Configure GPIO pin : PB5 */
	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	/* Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_RESET);
	
	/* EXTI interrupt init*/
	HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_1);
	HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
	HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0x0, 0);
	
	while (1) {
		if(blink) {
			HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
			for(int i = 0; i < 100000; i++);
		}
	}
}
	
void EXTI15_10_IRQHandler(void) {
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
	HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
}
	
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
	if(GPIO_Pin == GPIO_PIN_13)
		blink = 1;
	else
		blink = 0;
}

6、 一次屏蔽所有中断或优先屏蔽所有中断

        有时我们希望确保我们的代码没有被抢占以允许执行中断或更多特权代码。也就是说,我们希望确保我们的代码是线程安全的。基于 Cortex-M 的处理器允许暂时屏蔽所有中断和异常的执行,而无需一一禁用。两个名为 PRIMASK 和 FAULTMASK 的特殊寄存器分别允许禁用所有中断和异常。

        即使这些寄存器是 32 位宽的,也只使用第一位来启用/禁用中断和异常。ARM 汇编指令 CPSID i 通过将 PRIMASK 位设置为 1 来禁用所有中断,而 CPSIE i 指令通过将 PRIMASK 设置为零来启用它们。相反,指令 CPSID f 通过将 FAULTMASK 位设置为 1 来禁用所有异常(NMI 异常除外),而 CPSIE f 指令则启用它们。

        CMSIS-Core 包提供了几个宏,我们可以使用它们来执行这些操作:__disable_irq() 和 __enable_irq() 自动设置和清除 PRIMASK。任何关键任务都可以放在这两个宏之间,如下所示:

...
__disable_irq();
/* 所有具有可配置优先级的异常都将被暂时禁用。你可以在这里放置关键代码 */
...
__enable_irq();

        但是,请记住,作为一般规则,中断必须仅在非常短的时间内被屏蔽,否则您可能会丢失重要的中断。请记住,中断不会排队。

        我们可以使用的另一个宏是 __set_PRIMASK(x) 值,其中 x 是 PRIMASK 寄存器的内容(0 或 1)。宏 __get_PRIMARK() 返回 PRIMASK 寄存器的内容。相反,宏 __set_FAULTMASK(x) 和 __get_FAULTMASK() 允许操作 FAULTMASK 寄存器。

        需要注意的是,一旦 PRIMASK 寄存器再次设置为零,所有挂起的中断都根据其优先级进行处理: PRIMASK 导致中断挂起位被设置,但 ISR 没有得到处理。这就是为什么我们说中断被屏蔽而不是禁用的原因。一旦清除 PRIMASK,就会开始为中断提供服务。

        Cortex-M3/4/7/33 内核允许根据优先级选择性地屏蔽中断。BASEPRI 寄存器在优先级级别屏蔽异常或中断。BASEPRI 寄存器的宽度与 IPR 寄存器相同,在基于 Cortex-M3/4/7 内核的 STM32 MCU 中持续 4 位,在基于 Cortex-M33 的 STM32 MCU 中持续 3 位。当 BASEPRI 设置为 0 时,它将被禁用。当它设置为非零值时,它会阻止具有相同或较低优先级的异常(包括中断),同时仍允许处理器接受具有较高优先级的异常。例如,如果 BASEPRI 寄存器设置为 0x60,则所有优先级介于 0x60~0xFF之间的中断都将被禁用。请记住,在 Cortex-M 内核中,优先级数字越高,中断优先级级别越低。__set_BASEPRI(x) 宏允许设置 BASEPRI 寄存器的内容:再次记住,HAL 会自动将优先级转移到 MSB 位。因此,如果我们想禁用所有优先级高于 2 的中断,那么我们必须将值 0x20 传递给 __set_BASEPRI()宏。或者,我们可以使用以下代码:__set_BASEPRI(2 << (8 - __NVIC_PRIO_BITS));

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/907286.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

clickhouse运维篇(三):生产环境一键生成配置并快速部署ck集群

前提条件&#xff1a;先了解集群搭建流程是什么样&#xff0c;需要改哪些配置&#xff0c;有哪些环境&#xff0c;这个文章目的是简化部署。 clickhouse运维篇&#xff08;一&#xff09;&#xff1a;docker-compose 快速部署clickhouse集群 clickhouse运维篇&#xff08;二&am…

「C/C++」C++11 之<thread>多线程编程

✨博客主页何曾参静谧的博客📌文章专栏「C/C++」C/C++程序设计📚全部专栏「VS」Visual Studio「C/C++」C/C++程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasolid函数说明目…

【微服务】Spring AI 使用详解

目录 一、前言 二、Spring AI 概述 2.1 什么是Spring AI 2.2 Spring AI 特点 2.3 Spring AI 带来的便利 2.4 Spring AI 应用领域 2.4.1 聊天模型 2.4.2 文本到图像模型 2.4.3 音频转文本 2.4.4 嵌入大模型使用 2.4.5 矢量数据库支持 2.4.6 数据工程ETL框架 三、Sp…

【浪潮商城-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

[SAP ABAP] SMW0上传模板

通常来说&#xff0c;一个批量导入的程序必须使用指定的模板&#xff0c;我们需要将模板保存到SAP系统中&#xff0c;以便用户下载并更改。这里我们可以使用事务码SMW0解决上述的问题 1.选择二进制类型 2.输入存放的包 3.创建对象 选择需要进行上传的本地模板文件到SAP系统中 …

HTML前端页面设计静态网站

浅浅分享一下前端作业&#xff0c;大佬轻喷~ <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>一个网…

金媒婚恋相亲系统10.4择爱开源旗舰版支持微信小程和抖音小程序上架

最近大家应该注意到了&#xff0c;金媒婚恋相亲系统已经更新至最新的10.4版本了&#xff01;本人作为商业用户也已经更新至最新的旗舰版了&#xff0c;更新的内容是啥&#xff01;这个官方都有列出&#xff0c;一个方面就是更新了多端的登录逻辑和UI 和后台CRM及很多细节的优化…

Flink CDC 同步 Mysql 数据

文章目录 一、Flink CDC、Flink、CDC各有啥关系1.1 概述1.2 和 jdbc Connectors 对比 二、使用2.1 Mysql 打开 bin-log 功能2.2 在 Mysql 中建库建表准备2.3 遇到的坑2.4 测试 三、番外 一、Flink CDC、Flink、CDC各有啥关系 Flink&#xff1a;流式计算框架&#xff0c;不包含 …

软件标准研发管理流程文件,项目管理,项目经理管理(word原件)

为了规范系统开发流程&#xff0c;我们制定了详尽的规范文档&#xff0c;旨在通过标准化、系统化的方法提升开发效率与项目质量。该流程从明确需求阶段开始&#xff0c;通过详细的设计规划确保解决方案的可行性与前瞻性&#xff0c;随后进入高效的编码实现阶段&#xff0c;遵循…

AAA 数据库事务隔离级别及死锁

目录 一、事务的四大特性&#xff08;ACID&#xff09; 1. 原子性(atomicity)&#xff1a; 2. 一致性(consistency)&#xff1a; 3. 隔离性(isolation)&#xff1a; 4. 持久性(durability)&#xff1a; 二、死锁的产生及解决方法 三、事务的四种隔离级别 0 .封锁协议 …

固定电话怎么认证显示公司名称?

在当今商业环境中&#xff0c;企业的联系方式&#xff0c;尤其是固定电话&#xff0c;不仅是与客户沟通的重要桥梁&#xff0c;也是展示企业专业形象的一部分。许多企业希望通过拨打固定电话联系客户时&#xff0c;能够对外显示公司名称&#xff0c;以此来增加电话的可信度和品…

使用 MMDetection 实现 Pascal VOC 数据集的目标检测项目练习(二) ubuntu的下载安装

首先&#xff0c;Linux系统是人工智能和深度学习首选系统。原因如下: 开放性和自由度&#xff1a;Linux 是一个开源操作系统&#xff0c;允许开发者自由修改和分发代码。这在开发和研究阶段非常有用&#xff0c;因为开发者可以轻松地访问和修改底层代码。社区支持&#xff1a;…

GetX的一些高级API

目录 前言 一、一些常用的API 二、局部状态组件 1.可选的全局设置和手动配置 2.局部状态组件 1.ValueBuilder 1.特点 2.基本用法 2.ObxValue 1.特点 2.基本用法 前言 这篇文章主要讲解GetX的一些高级API和一些有用的小组件。 一、一些常用的API GetX提供了一些高级…

做一个干电池的电量检测器03:数值拟合与电路仿真

首先在表格中进行详细的计算&#xff0c;整理出所需的数据。接着&#xff0c;我们运用MATLAB的强大功能对这些数据进行插值处理&#xff0c;生成了一个离散的数值数组。这个数组的每个数值都精确地对应着模数转换器&#xff08;ADC&#xff09;采样到的信号。通过这些数值&…

# linux从入门到精通-从基础学起,逐步提升,探索linux奥秘(十九)--mysql数据库基本操作

linux从入门到精通-从基础学起&#xff0c;逐步提升&#xff0c;探索linux奥秘&#xff08;十九&#xff09;–mysql数据库基本操作 一、MySQL的基本操作&#xff08;1&#xff09;&#xff08;难点&#xff09; 1、名词介绍 以Excel文件举例&#xff1a; 数据库&#xff1a…

关于我的编程语言——C/C++——第四篇(深入1)

&#xff08;叠甲&#xff1a;如有侵权请联系&#xff0c;内容都是自己学习的总结&#xff0c;一定不全面&#xff0c;仅当互相交流&#xff08;轻点骂&#xff09;我也只是站在巨人肩膀上的一个小卡拉米&#xff0c;已老实&#xff0c;求放过&#xff09; 字符类型介绍 char…

【python】OpenCV—Tracking(10.4)—Centroid

文章目录 1、任务描述2、人脸检测模型3、完整代码4、结果展示5、涉及到的库函数6、参考 1、任务描述 基于质心实现多目标&#xff08;以人脸为例&#xff09;跟踪 人脸检测采用深度学习的方法 核心步骤&#xff1a; 步骤#1&#xff1a;接受边界框坐标并计算质心 步骤#2&…

Unity核心笔记

1、认识模型的制作 1.建模 2.展UV 3.材质和纹理贴图 4.骨骼绑定 5.动画制作 总结 2、图片导入概述 1.Unity支持的图片格式 2.图片设置的6大部分 3、纹理类型设置 1.纹理类型主要是设置什么 2.参数讲解 4、纹理形状设置 1.纹理形状主要设置什么 2.参数讲解 5、纹理高级设置 …

(57)MATLAB使用迫零均衡器和MMSE均衡器的BPSK调制系统仿真

文章目录 前言一、仿真测试模型二、仿真代码三、仿真结果四、迫零均衡器和MMSE均衡器的实现1.均衡器的MATLAB实现2.均衡器的性能测试 总结 前言 本文给出仿真模型与MATLAB代码&#xff0c;分别使用具有ISI的三个不同传输特性的信道&#xff0c;仿真测试了使用迫零均衡器和MMSE…

[项目] C++基于多设计模式下的同步异步日志系统

[项目] C基于多设计模式下的同步&异步日志系统 文章目录 [项目] C基于多设计模式下的同步&异步日志系统日志系统1、项目介绍2、开发环境3、核心技术4、日志系统介绍4.1 日志系统的价值4.2 日志系统技术实现4.2.1 同步写日志4.2.2 异步写日志 5、相关技术知识5.1 不定参…