处理器中的中断
在处理器中,中断是一个过程,即CPU在正常执行程序的过程中,遇到外部/内部的紧急事件需要处理,暂时中止当前程序的执行,转而去为处理紧急的事件,待处理完毕后再返回被打断的程序处继续往下执行。中断在计算机多任务处理,尤其是即时系统中尤为重要。比如uCOS,FreeRTOS等。
意义
中断能提高CPU的效率,同时能对突发事件做出实时处理。实现程序的并行化,实现嵌入式系统进程之间的切换
中断处理过程
简单说,当主程序发现中断发生,就通过中断向量指向执行中断程序,执行完中断程序后返回到原来的主程序,继续运行。如果我们要顺利返回到主程序,我们就需要做现场保护,把状态、参数、下一次执行的程序地址等信息压栈来保存,返回的时候只要出栈就可以顺利回到主程序。
STM32分成两部分,内核和片内外设,其中内核中的NVIC用来管理内外中断。NVIC主要有三个功能:中断管理、支持异常及中断向量处理 和 支持嵌套中断。
Cortex-M4 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有256 级的可编程中断优先级设置。但 STM32F407 并没有使用 Cortex-M4 内核的全部东西(只使用了10个内部中断和82个外部中断)。在Cortex-M4处理器中,每一个外部中断都可以被使能或者禁止,并且可以被设置为挂起状态或者清除状态。这也是中断管理的五个功能:中断优先级设置、使能中断、禁止中断、设置挂起状态和清楚状态。
NVIC是一个中断管理的部件,其内部还是由一系列寄存器构成,下面是一些寄存器的介绍。
寄存器 | 功能 |
---|---|
ISER | 使能中断 |
ICER | 清除中断使能,为了禁止这个中断使用,在这里我们不是直接在使能寄存器 上把使能的寄存器复位来禁止,而是在对应的清除使能寄存器上置位。 |
ISPR | 挂起中断,若中断产生但没有立即执行,它就会被挂起 |
ICPR | 清除挂起,中断处理完成后应该清除挂起,表示已处理 |
IABR | 每个外部中断都有一个活跃状态位,当处理器正在处理这个中断时,该位会被置1 |
IP | 用于设置中断的优先级 |
这里解释下为什么很多寄存器后面都有 [0] - [7]。前面我们有提到Cortex-M4一共有256个中断,那么我们要想要去使能这么多中断,我们就需要256位,然而一个寄存器只有32位,因此我们需要 0 - 7 ,一共8个寄存器来控制这么多中断。
CM4 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断。STM32F407实际上只使用了10个内部异常和82个外部中断。当异常或中断发生时,处理器会把PC设置为一个特定地址,这一地址就称为异常向量。每一类异常源都对应一个特定的入口地址,这些地址按照优先级排列以后就组成一张异常向量表。
⋮ \vdots ⋮
上图就是中断向量表,灰色部分是内部中断,也就是异常;从7-88是外部中断。0x0000 0000 开头的四个字节存放的是栈的地址,后面的四个字节就是复位程序,这在之前stm32的启动流程里有提到过。这样向量化处理中断的好处是采用向量表处理异常,处理器会从存储器的向量表中,自动定位异常的程序入口。从发生异常到异常的处理中间的时间被缩减。
在说一下中断和异常的区别:中断是微处理器外部发送的,通过中断通道送入处理器内部,一般是硬件引起的,比如串口接收中断;而异常通常是微处理器内部发生的,大多是软件引起的,比如除法出错异常,特权调用异常等待。不管是中断还是异常,微处理器通常都有相应的中断/异常服务程序
简单来说,嵌套中断就是在我们执行一个中断的时候有另一个优先级特别高的中断发生,这个时候我们中断当前执行的中断,去执行那个优先级更高的中断。下面主要说两个问题。优先级是怎么定的?是不是优先级高的中断一定就会发生中断的嵌套?
总结:
在片内外设中,基本上所有的外设都可以产生中断,对普通的GPIO来说,EXTI就是用来管理外部GPIO因电平变化而产生的中断。
我们知道,STM32外部有上百个管脚,那EXTI怎么来管理这么多管脚呢?在这里一共有16个外部中断/事件线。其中每一组编号相同的接到同一个事件线,比如PA0、PB0、…、PI0都由EXTI0来管理,以此类推。那具体PAx, …, PIx中的哪一个管脚触发的中断,这是由一个另一个寄存器SYSCFG来控制的。
它和NVIC有一点类似,都有一组配置寄存器(SYSCFG_EXTICR1-4)。系统配置控制器的主要用途如下
这个寄存器只有0-15位有效,每四位分成一组,以0-3位为例,这里表示的是EXTI0,如果是0000,那么就是PA0引脚作为EXTI0外部中断的源输入。一个寄存器只能配置4个EXTI,我们一共有16个EXTI,因此我们需要4个寄存器
这张图只针对外部中断,外部管脚在1处输入,然后通过边缘检测电路检测在什么时候触发中断(上升沿或下降沿),一旦符合条件能放行到3,这是一个或门,不管是事件或是外部的电平信号,都能通过3。如果是事件变化,就会去到5,通过一个脉冲发生器来触发其他硬件。如果是一个电平变化而导致的中断,就走上面,如果内部没有被屏蔽,也就是说使能了这个中断,就可以进入与门(4),就可以挂起这个中断,等按照优先级排到这个中断的时候就可以把信号发送给NVIC中断控制器来触发中断。
上图是按键原理图,在这次实例中,我们按下KEY1后,通过中断与USART,在屏幕上显示按键被按下。
首先,在CubeMX上新建一个项目,通过下图设置中断。
在PI9的配置中设置GPIO mode为External Interrupt Mode with Falling edge trigger detection。也就是做个下降沿检测,按下按钮的时候触发中断。与此同时,我们还要设置NVIC,使能中断,具体看下图。
先设置Priority Group为2位抢占优先级和2位响应优先级,然后,使能EXTI line[9:5] interrupt,同时设置Preemption Priority 和 sub priority都为1,为什么这么设置的原因在后面会说。这时候就可以导出工程了。至于为什么是使能EXTI line[9:5] interrupt,我们可以通过查看中断向量表找到原因。
我们知道外部中断都是用EXTI0-EXTI15来控制的,我们可以通过EXTI9来控制PI9,通过查表知道EXTI9需要用EXTI9_5来控制。
下面是main.c
的代码,因为我们想要用printf
来输出,所以我们需要覆盖fputc
,具体解释可以看上一篇笔记。然后我们可以看一下MX_GPIO_Init()
里写了什么
#include "main.h"
#include "usart.h"
#include "gpio.h"
void SystemClock_Config(void);
int fputc(int ch, FILE *p)
{
while(!(USART1->SR & (1<<7)));
USART1->DR = ch;
return ch;
}
oid)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("This is KEY test\n");
while (1)
{
}
}
下面是gpio.c
,包含MX_GPIO_Init
和中断回调函数void HAL_GPIO_EXTI_Callback
#include "gpio.h"
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOI_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin : PI9 */
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿检测
GPIO_InitStruct.Pull = GPIO_NOPULL; // 不上拉和下拉电阻
HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI9_5_IRQn, 1, 1); // 设置抢占优先级和响应优先级
HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); // 使能中断
}
/* USER CODE BEGIN 2 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_9)
{
HAL_Delay(20);
if (HAL_GPIO_ReadPin(GPIOI, GPIO_PIN_9) == GPIO_PIN_RESET)
{
printf("Key1\n");
}
}
}
然后我们可以看一下stm32f4xx_it.c
,这里有所有中断的回调函数,我们可以看到很多中断回调函数都是空的或者死循环,在这里我们主要要看EXTI9_5_IRQHandler
#include "main.h"
#include "stm32f4xx_it.h"
void NMI_Handler(void) {while (1){}}
void HardFault_Handler(void) {while (1){}}
void MemManage_Handler(void) {while (1){}}
void BusFault_Handler(void) {while (1){}}
void UsageFault_Handler(void) {while (1){}}
void SVC_Handler(void){}
void DebugMon_Handler(void){}
void PendSV_Handler(void){}
void SysTick_Handler(void)
{
HAL_IncTick();
}
/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f4xx.s). */
/******************************************************************************/
/**
* @brief This function handles EXTI line[9:5] interrupts.
*/
void EXTI9_5_IRQHandler(void)
{
/* USER CODE BEGIN EXTI9_5_IRQn 0 */
/* USER CODE END EXTI9_5_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
/* USER CODE BEGIN EXTI9_5_IRQn 1 */
/* USER CODE END EXTI9_5_IRQn 1 */
}
我们可以继续看一下HAL_GPIO_EXTI_IRQHandler
的定义
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); // 先清楚挂起状态
HAL_GPIO_EXTI_Callback(GPIO_Pin); // 调用回调函数
}
}
这个回调函数需要我们自己定义,因此我们最好在看一下HAL_GPIO_EXTI_Callback
的定义,__weak
在这里证明这是个弱定义,也就是说我们在别的地方定义的话会自动覆盖这里的函数
/**
* @brief EXTI line detection callbacks.
* @param GPIO_Pin Specifies the pins connected EXTI line
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
因此我们在gpio.c
里定义了这个函数,函数如下。在这里有一点需要关注,我们用了HAL_Delay
,如上图所示,它也是Time base: System tick timer
来控制的。我们知道,在中断中调用另一个中断是嵌套中断。如果两个中断优先级一样,这样就会导致系统死在那里,因此我们需要把GPIO的外部中断的Preemption Priority设置为1,让抢占优先级为0的Hal_Delay
可以发生嵌套中断,以此来顺利运行。
/* USER CODE BEGIN 2 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_9) // 如果是pin9发生的中断
{
HAL_Delay(20); //先延时20 ms
if (HAL_GPIO_ReadPin(GPIOI, GPIO_PIN_9) == GPIO_PIN_RESET) // 在判断按钮是否还是被按下,消除防抖
{
printf("Key1\n"); // 通过USART,给串口发送Key1
}
}
}
至此就可以上传到板子上运行程序了。
设置USART与其对应的中断,然后导出项目。USART有两个功能,发送和接收。之前我们有用过两个函数HAL_UART_Transmit
和 HAL_UART_Receive
,但那两个不能用作中断的发送和接收,要用HAL_UART_Transmit_IT
和 HAL_UART_Receive_IT
。
先说HAL_UART_Transmit_IT
/**
* @brief Sends an amount of data in non blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the sent data is handled as a set of u16. In this case, Size must indicate the number
* of u16 provided through pData.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be sent
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size){}
下面是一个例子
HAL_UART_Transmit_IT(&huart1, (uint8_t *)"UART is sending message\n", 24); // USART1,要发送的信息,信息的长度
但到这里还没有体现中断,我们举一个例子,如果要在发送完成以后产生一个中断,打印一句Message has sent
,那我们就需要设置控制这个中断的回调函数。我们先打开startup_stm32f407xx.s
,找到里面的中断向量表。
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack // 异常
.
.
.
DCD SysTick_Handler ; SysTick Handler
; External Interrupts // 外部中断
DCD WWDG_IRQHandler ; Window WatchDog
DCD PVD_IRQHandler ; PVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
.
.
.
DCD USART1_IRQHandler ; USART1 // USART1中断的回调函数的地址
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
.
.
.
DCD HASH_RNG_IRQHandler ; Hash and Rng
DCD FPU_IRQHandler ; FPU
__Vectors_End
查看USART1_IRQHandler
的定义
/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers */
/* Add here the Interrupt Handlers for the used peripherals. */
/* For the available peripheral interrupt handler names, */
/* please refer to the startup file (startup_stm32f4xx.s). */
/******************************************************************************/
/**
* @brief This function handles USART1 global interrupt.
*/
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
继续追查到HAL_UART_IRQHandler
,我们可以把这个函数分成四部分,UART在接收模式、有报错的情况、发送模式 和 发送模式结束的时候
/**
* @brief This function handles UART interrupt request.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
uint32_t dmarequest = 0x00U;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET)
{
/* UART 在接收模式 -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
}
/* If some errors occur 如果报错,这里很长我没截完整 */
if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
{ } /* End if some error occurs */
/* UART 在发送模式 ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
/* UART 当发送模式结束的时候 --------------------------------------------*/
if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
UART_EndTransmit_IT(huart);
return;
}
}
按照我们之前的需求,是发送完成的时候要产生一个中断,然后发送一句message has sent
,这里我们就需要继续看UART_EndTransmit_IT
这个函数的定义
/**
* @brief Wraps up transmission in non blocking mode.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval HAL status
*/
static HAL_StatusTypeDef UART_EndTransmit_IT(UART_HandleTypeDef *huart)
{
/* Disable the UART Transmit Complete Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_TC);
/* Tx process is ended, restore huart->gState to Ready */
huart->gState = HAL_UART_STATE_READY;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Tx complete callback*/
huart->TxCpltCallback(huart);
#else
/*Call legacy weak Tx complete callback*/
HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
return HAL_OK;
}
这里HAL_UART_TxCpltCallback
,就是我们修改的回调函数。我们可以在去追一下定义
/**
* @brief Tx Transfer completed callbacks.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_TxCpltCallback could be implemented in the user file
*/
}
我们可以在usart.c
里重新定义一下这个函数
/* USER CODE BEGIN 1 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart){
if (huart->Instance == USART1)
{
printf("Message has sent\n");
}
}
我们可以在写一下main.c
函数
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("Uart test\n");
HAL_UART_Transmit_IT(&huart1, (uint8_t *)"UART is sending message\n", 24);
while (1){}
}
上传到板子上后,我们通过uart assistant
上设置好配置,连接后会显示以下Data log
[2022-08-01 05:03:37.709]# RECV ASCII>
Uart test
UART is sending message // 发送数据
Message has sent // 数据发生后产生的中断
/**
* @brief Receives an amount of data in non blocking mode.
* @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01),
* the received data is handled as a set of u16. In this case, Size must indicate the number
* of u16 available through pData.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @param pData Pointer to data buffer (u8 or u16 data elements).
* @param Size Amount of data elements (u8 or u16) to be received.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size){}
举个例子
HAL_UART_Receive_IT(&huart1, REV, 2); // USART1,接收的数据,接收数据的长度
要设置中断,还是需要看之前的继续追查到的HAL_UART_IRQHandler
里面的接收模式,
/**
* @brief This function handles UART interrupt request.
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval None
*/
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t isrflags = READ_REG(huart->Instance->SR);
uint32_t cr1its = READ_REG(huart->Instance->CR1);
uint32_t cr3its = READ_REG(huart->Instance->CR3);
uint32_t errorflags = 0x00U;
uint32_t dmarequest = 0x00U;
/* If no error occurs */
errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
if (errorflags == RESET)
{
/* UART 在接收模式 -------------------------------------------------*/
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
UART_Receive_IT(huart);
return;
}
}
/* If some errors occur 如果报错,这里很长我没截完整 */
if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
{ } /* End if some error occurs */
/* UART 在发送模式 ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
/* UART 当发送模式结束的时候 --------------------------------------------*/
if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
{
UART_EndTransmit_IT(huart);
return;
}
}
这里我们继续追查UART_Receive_IT
的定义
/**
* @brief Receives an amount of data in non blocking mode
* @param huart Pointer to a UART_HandleTypeDef structure that contains
* the configuration information for the specified UART module.
* @retval HAL status
*/
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint16_t *tmp;
/* Check that a Rx process is ongoing */
.
.
.
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx complete callback*/
huart->RxCpltCallback(huart);
#else
/*Call legacy weak Rx complete callback*/
HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
return HAL_OK;
}
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
这里我们看到了HAL_UART_RxCpltCallback
,追查下去看下它的定义,也就是我们需要修改的回调函数
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(huart);
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback could be implemented in the user file
*/
}
我们修改回调函数如下,当接收到指定长度的数据的时候,会发生中断,调用回调函数HAL_UART_RxCpltCallback
extern uint8_t REV[2];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
printf("REV DATA: REV[0]=%x, REV[1]=%x\n", REV[0], REV[1]);
}
}
/* USER CODE END 1 */
这时候我们修改下main
函数
#include "main.h"
#include "usart.h"
#include "gpio.h"
/* USER CODE BEGIN PV */
uint8_t REV[2] = {0};
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
int fputc(int ch, FILE *p)
{
while (!(USART1->SR & (1<<7)));
USART1->DR = ch;
return ch;
}
/* USER CODE END PFP */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, REV, 2);
/* USER CODE END 2 */
while (1)
{}
}
下载到板子,在串口助手里输入12
,会返回
[2022-08-01 05:03:41.491]# RECV ASCII>
REV DATA: REV[0]=1, REV[1]=2