STM32实战:基于STM32F103的看门狗(IWDG/WWDG)应用与系统复位

文章目录

一、前言

1.1 技术背景

在嵌入式系统开发中,系统稳定性是至关重要的。由于电磁干扰、软件缺陷或硬件故障等原因,微控制器可能会出现程序"跑飞"或陷入死循环的情况。看门狗(Watchdog)机制就是一种用于监控系统运行状态、在异常情况下自动复位系统的硬件保护机制。

STM32F103系列微控制器内置了两种看门狗:

  • 独立看门狗(IWDG):基于独立的低速内部RC振荡器(LSI),即使主时钟失效也能正常工作
  • 窗口看门狗(WWDG):基于主时钟,提供精确的定时窗口监控

1.2 应用场景

看门狗广泛应用于以下场景:

  • 工业控制系统:确保关键设备持续稳定运行
  • 物联网设备:远程设备需要自我恢复能力
  • 汽车电子:安全关键系统必须可靠
  • 消费电子:提升产品稳定性和用户体验

1.3 本文目标

通过本教程,你将学会:

  • 理解IWDG和WWDG的工作原理和区别
  • 掌握看门狗的配置和初始化方法
  • 实现喂狗操作和系统复位处理
  • 处理看门狗相关的故障排查

技术栈:

  • 芯片型号:STM32F103C8T6
  • 开发环境:STM32CubeIDE
  • 固件库:HAL库
  • 调试工具:ST-Link V2

二、环境准备

2.1 硬件要求

项目 规格
开发板 STM32F103C8T6最小系统板
调试器 ST-Link V2
电源 USB 5V供电
LED 3个(用于状态指示)

2.2 软件环境

STM32CubeIDE安装:

  1. 访问ST官网下载STM32CubeIDE
  2. 安装并配置工作空间
  3. 安装STM32F1系列HAL库

工程创建:

  1. File → New → STM32 Project
  2. 选择MCU:STM32F103C8Tx
  3. 配置时钟:HSE Crystal,72MHz
  4. 启用SWD调试

三、独立看门狗(IWDG)实现

3.1 IWDG工作原理

独立看门狗使用内部低速RC振荡器(LSI,约40kHz)作为时钟源,具有完全独立的时钟,即使主时钟失效也能正常工作。

关键特性:

  • 自由运行的递减计数器
  • 时钟由独立的RC振荡器提供
  • 当计数器减到0时产生复位
  • 在键寄存器写入特定值时重装载计数器



系统启动
初始化IWDG
启动计数器
程序正常运行?
定时喂狗
计数器减到0
系统复位

3.2 IWDG配置代码

📄 创建文件:Core/Inc/iwdg_handler.h

c 复制代码
/**
 * @file iwdg_handler.h
 * @brief 独立看门狗处理头文件
 * 
 * 本文件定义了IWDG相关的函数声明和配置参数
 */

#ifndef __IWDG_HANDLER_H
#define __IWDG_HANDLER_H

#include "stm32f1xx_hal.h"

/* IWDG超时时间计算(基于40kHz LSI)
 * 超时时间 = (Prescaler / LSI_Freq) * ReloadValue
 * 例如:Prescaler=64, Reload=625
 * 超时时间 = (64/40000) * 625 = 1秒
 */

#define IWDG_PRESCALER    IWDG_PRESCALER_64   // 预分频系数64
#define IWDG_RELOAD_VALUE 625                 // 重装载值625
#define IWDG_TIMEOUT_MS   1000                // 超时时间1秒

/**
 * @brief 初始化独立看门狗
 * @retval HAL_StatusTypeDef 初始化状态
 */
HAL_StatusTypeDef IWDG_Init(void);

/**
 * @brief 喂狗操作(刷新看门狗计数器)
 * @retval HAL_StatusTypeDef 操作状态
 */
HAL_StatusTypeDef IWDG_Refresh(void);

/**
 * @brief 检查是否为看门狗复位
 * @retval uint8_t 1表示是看门狗复位,0表示不是
 */
uint8_t IWDG_IsReset(void);

/**
 * @brief 清除复位标志
 */
void IWDG_ClearResetFlag(void);

#endif /* __IWDG_HANDLER_H */

📄 创建文件:Core/Src/iwdg_handler.c

c 复制代码
/**
 * @file iwdg_handler.c
 * @brief 独立看门狗处理实现
 * 
 * 本文件实现了IWDG的初始化、喂狗和状态检测功能
 */

#include "iwdg_handler.h"
#include <stdio.h>

/**
 * @brief IWDG句柄结构体
 * 
 * HAL库使用此结构体管理IWDG外设
 */
static IWDG_HandleTypeDef hiwdg;

/**
 * @brief 初始化独立看门狗
 * 
 * 配置IWDG的预分频器和重装载值,启动看门狗
 * 
 * @retval HAL_OK 初始化成功
 * @retval HAL_ERROR 初始化失败
 */
HAL_StatusTypeDef IWDG_Init(void)
{
    HAL_StatusTypeDef status;
    
    /* 配置IWDG参数 */
    hiwdg.Instance = IWDG;                          // IWDG外设实例
    hiwdg.Init.Prescaler = IWDG_PRESCALER;          // 设置预分频器
    hiwdg.Init.Reload = IWDG_RELOAD_VALUE;          // 设置重装载值
    
    /* 初始化IWDG */
    status = HAL_IWDG_Init(&hiwdg);
    
    if (status != HAL_OK) {
        printf("IWDG初始化失败!\r\n");
        return HAL_ERROR;
    }
    
    printf("IWDG初始化成功,超时时间:%d ms\r\n", IWDG_TIMEOUT_MS);
    return HAL_OK;
}

/**
 * @brief 喂狗操作
 * 
 * 向IWDG键寄存器写入重装载值,刷新计数器
 * 必须在计数器减到0之前调用此函数
 * 
 * @retval HAL_OK 刷新成功
 * @retval HAL_ERROR 刷新失败
 */
HAL_StatusTypeDef IWDG_Refresh(void)
{
    HAL_StatusTypeDef status;
    
    /* 刷新IWDG计数器 */
    status = HAL_IWDG_Refresh(&hiwdg);
    
    if (status != HAL_OK) {
        printf("IWDG喂狗失败!\r\n");
        return HAL_ERROR;
    }
    
    return HAL_OK;
}

/**
 * @brief 检查是否为看门狗复位
 * 
 * 通过检查RCC_CSR寄存器的IWDGRSTF位判断
 * 
 * @retval 1 是看门狗复位
 * @retval 0 不是看门狗复位
 */
uint8_t IWDG_IsReset(void)
{
    /* 检查IWDG复位标志位 */
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
        return 1;
    }
    return 0;
}

/**
 * @brief 清除复位标志
 * 
 * 清除RCC_CSR寄存器中的所有复位标志
 * 必须在读取复位原因后调用
 */
void IWDG_ClearResetFlag(void)
{
    /* 清除所有复位标志 */
    __HAL_RCC_CLEAR_RESET_FLAGS();
}

3.3 主程序实现

📄 创建文件:Core/Src/main_iwdg.c

c 复制代码
/**
 * @file main_iwdg.c
 * @brief IWDG测试主程序
 * 
 * 本程序演示IWDG的基本使用,包括初始化、喂狗和复位检测
 */

#include "main.h"
#include "iwdg_handler.h"
#include <stdio.h>

/* 私有函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

/* UART句柄 */
UART_HandleTypeDef huart1;

/**
 * @brief 主函数
 * 
 * 程序入口,初始化外设,启动IWDG,进入主循环
 */
int main(void)
{
    /* HAL库初始化 */
    HAL_Init();
    
    /* 配置系统时钟 */
    SystemClock_Config();
    
    /* 初始化外设 */
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    
    /* 检查是否为看门狗复位 */
    if (IWDG_IsReset()) {
        printf("\r\n========================================\r\n");
        printf("系统由IWDG看门狗复位重启!\r\n");
        printf("========================================\r\n");
        IWDG_ClearResetFlag();
    } else {
        printf("\r\n系统正常启动\r\n");
    }
    
    /* 初始化IWDG */
    if (IWDG_Init() != HAL_OK) {
        printf("IWDG初始化失败,系统停止!\r\n");
        while (1) {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);  // LED闪烁表示错误
            HAL_Delay(100);
        }
    }
    
    printf("系统正常运行,开始喂狗...\r\n");
    
    /* 主循环 */
    while (1) {
        /* 正常喂狗 */
        if (IWDG_Refresh() != HAL_OK) {
            printf("喂狗失败!\r\n");
        }
        
        /* LED闪烁表示系统运行正常 */
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        
        /* 模拟正常工作的延时(500ms,小于超时时间1秒) */
        HAL_Delay(500);
        
        printf("系统运行正常,已喂狗\r\n");
    }
}

/**
 * @brief 模拟死循环(用于测试看门狗复位)
 * 
 * 调用此函数会导致看门狗超时复位
 * 仅在测试时使用
 */
void SimulateDeadLoop(void)
{
    printf("进入死循环,等待看门狗复位...\r\n");
    while (1) {
        /* 不喂狗,等待复位 */
    }
}

/**
 * @brief 模拟长时间任务(用于测试看门狗复位)
 * 
 * 延时超过看门狗超时时间,触发复位
 */
void SimulateLongTask(void)
{
    printf("开始长时间任务(2秒)...\r\n");
    HAL_Delay(2000);  // 2秒延时,超过1秒超时时间
    printf("长时间任务完成\r\n");
}

/**
 * @brief GPIO初始化
 */
static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    /* 使能GPIOC时钟 */
    __HAL_RCC_GPIOC_CLK_ENABLE();
    
    /* 配置PC13为输出(板载LED) */
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}

/**
 * @brief USART1初始化(用于printf输出)
 */
static void MX_USART1_UART_Init(void)
{
    huart1.Instance = USART1;
    huart1.Init.BaudRate = 115200;
    huart1.Init.WordLength = UART_WORDLENGTH_8B;
    huart1.Init.StopBits = UART_STOPBITS_1;
    huart1.Init.Parity = UART_PARITY_NONE;
    huart1.Init.Mode = UART_MODE_TX_RX;
    huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart1.Init.OverSampling = UART_OVERSAMPLING_16;
    
    if (HAL_UART_Init(&huart1) != HAL_OK) {
        Error_Handler();
    }
}

/**
 * @brief 系统时钟配置
 */
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    
    /* 配置HSE和PLL */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
    
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        Error_Handler();
    }
    
    /* 配置系统时钟 */
    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
    
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) {
        Error_Handler();
    }
}

/**
 * @brief 错误处理函数
 */
void Error_Handler(void)
{
    __disable_irq();
    while (1) {
        /* 错误时LED快速闪烁 */
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        HAL_Delay(50);
    }
}

/* printf重定向 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif

PUTCHAR_PROTOTYPE
{
    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
    return ch;
}

四、窗口看门狗(WWDG)实现

4.1 WWDG工作原理

窗口看门狗基于主时钟(PCLK1)工作,提供更精确的监控机制。与IWDG不同,WWDG要求喂狗操作必须在特定的时间窗口内完成。

关键特性:

  • 基于主时钟,可精确控制时序
  • 具有可编程的窗口值
  • 过早或过晚喂狗都会触发复位
  • 可产生早期唤醒中断(EWI)

时间轴





计数器=127
递减计数
计数器 > 窗口值?
禁止喂狗
复位
允许喂狗
喂狗成功
计数器=0x40?
早期唤醒中断
计数器<0x40?
复位

4.2 WWDG配置代码

📄 创建文件:Core/Inc/wwdg_handler.h

c 复制代码
/**
 * @file wwdg_handler.h
 * @brief 窗口看门狗处理头文件
 */

#ifndef __WWDG_HANDLER_H
#define __WWDG_HANDLER_H

#include "stm32f1xx_hal.h"

/* WWDG配置参数
 * 时钟源:PCLK1 = 36MHz (APB1)
 * 预分频:8
 * 计数器时钟 = 36MHz / 4096 / 8 = 1.098kHz
 * 超时时间 = (127-64) / 1.098kHz = 57.4ms
 * 窗口值 = 80
 * 窗口时间 = (127-80) / 1.098kHz = 42.8ms
 */

#define WWDG_PRESCALER    WWDG_PRESCALER_8    // 预分频系数8
#define WWDG_COUNTER      127                 // 计数器初始值
#define WWDG_WINDOW       80                  // 窗口值
#define WWDG_EWI_ENABLE   1                   // 启用早期唤醒中断

/**
 * @brief 初始化窗口看门狗
 * @retval HAL_StatusTypeDef 初始化状态
 */
HAL_StatusTypeDef WWDG_Init(void);

/**
 * @brief 喂狗操作
 * @retval HAL_StatusTypeDef 操作状态
 */
HAL_StatusTypeDef WWDG_Refresh(void);

/**
 * @brief 检查是否为WWDG复位
 * @retval uint8_t 1表示是WWDG复位,0表示不是
 */
uint8_t WWDG_IsReset(void);

/**
 * @brief WWDG早期唤醒中断回调
 */
void WWDG_EarlyWakeupCallback(void);

#endif /* __WWDG_HANDLER_H */

📄 创建文件:Core/Src/wwdg_handler.c

c 复制代码
/**
 * @file wwdg_handler.c
 * @brief 窗口看门狗处理实现
 */

#include "wwdg_handler.h"
#include <stdio.h>

static WWDG_HandleTypeDef hwwdg;

/**
 * @brief 初始化窗口看门狗
 */
HAL_StatusTypeDef WWDG_Init(void)
{
    HAL_StatusTypeDef status;
    
    /* 使能WWDG时钟 */
    __HAL_RCC_WWDG_CLK_ENABLE();
    
    /* 配置WWDG参数 */
    hwwdg.Instance = WWDG;
    hwwdg.Init.Prescaler = WWDG_PRESCALER;      // 预分频
    hwwdg.Init.Window = WWDG_WINDOW;            // 窗口值
    hwwdg.Init.Counter = WWDG_COUNTER;          // 计数器初始值
    hwwdg.Init.EWIMode = WWDG_EWI_ENABLE ?     // 早期唤醒中断
                         WWDG_EWI_ENABLE : WWDG_EWI_DISABLE;
    
    /* 初始化WWDG */
    status = HAL_WWDG_Init(&hwwdg);
    
    if (status != HAL_OK) {
        printf("WWDG初始化失败!\r\n");
        return HAL_ERROR;
    }
    
    printf("WWDG初始化成功\r\n");
    printf("  计数器值:%d\r\n", WWDG_COUNTER);
    printf("  窗口值:%d\r\n", WWDG_WINDOW);
    printf("  必须在计数器%d到%d之间喂狗\r\n", WWDG_WINDOW, 0x40);
    
    return HAL_OK;
}

/**
 * @brief 喂狗操作
 */
HAL_StatusTypeDef WWDG_Refresh(void)
{
    HAL_StatusTypeDef status;
    
    /* 刷新WWDG计数器 */
    status = HAL_WWDG_Refresh(&hwwdg);
    
    if (status != HAL_OK) {
        printf("WWDG喂狗失败!\r\n");
        return HAL_ERROR;
    }
    
    return HAL_OK;
}

/**
 * @brief 检查是否为WWDG复位
 */
uint8_t WWDG_IsReset(void)
{
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) {
        return 1;
    }
    return 0;
}

/**
 * @brief WWDG中断服务程序
 */
void WWDG_IRQHandler(void)
{
    HAL_WWDG_IRQHandler(&hwwdg);
}

/**
 * @brief 早期唤醒中断回调
 * 
 * 当计数器减到0x40时触发此中断
 * 可以在此中断中执行紧急保存操作
 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
    printf("WWDG早期唤醒中断!计数器即将溢出\r\n");
    /* 可以在这里执行紧急操作,如保存关键数据 */
}

五、IWDG与WWDG对比

特性 IWDG(独立看门狗) WWDG(窗口看门狗)
时钟源 内部LSI(40kHz) PCLK1(36MHz)
独立性 完全独立,主时钟失效也能工作 依赖主时钟
精度 较低(RC振荡器有偏差) 较高
窗口功能 有(必须在窗口内喂狗)
中断 早期唤醒中断(EWI)
适用场景 防止程序跑飞 精确时序监控

六、故障排查与问题解决

6.1 常见问题

问题1:看门狗无法初始化

现象:

复制代码
IWDG初始化失败!

原因分析:

  • 时钟配置错误
  • 参数超出有效范围
  • HAL库版本不匹配

解决方案:

c 复制代码
/* 检查LSI时钟是否使能 */
if (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET) {
    /* LSI未就绪,手动使能 */
    __HAL_RCC_LSI_ENABLE();
    /* 等待LSI稳定 */
    uint32_t timeout = 1000;
    while (__HAL_RCC_GET_FLAG(RCC_FLAG_LSIRDY) == RESET) {
        if (--timeout == 0) {
            printf("LSI启动超时!\r\n");
            return HAL_ERROR;
        }
    }
}
问题2:系统频繁复位

现象:

系统不断重启,串口输出复位信息

原因分析:

  • 喂狗周期太长,超过超时时间
  • 程序中有死循环或长时间阻塞
  • 中断优先级配置错误

解决方案:

c 复制代码
/* 方案1:缩短喂狗周期 */
while (1) {
    IWDG_Refresh();  // 每次循环都喂狗
    
    /* 长时间任务分段执行 */
    for (int i = 0; i < 10; i++) {
        DoPartialWork(i);  // 分段处理
        IWDG_Refresh();    // 中间喂狗
    }
}

/* 方案2:使用中断喂狗 */
void TIM2_IRQHandler(void)
{
    if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) {
        __HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
        IWDG_Refresh();  // 定时器中断中喂狗
    }
}
问题3:WWDG过早喂狗导致复位

现象:

复制代码
系统由WWDG看门狗复位重启!

原因分析:

  • 喂狗时间早于窗口值
  • 窗口值设置不合理

解决方案:

c 复制代码
/* 检查当前计数器值 */
uint32_t counter = WWDG->CR & 0x7F;
printf("当前计数器值:%lu\r\n", counter);

/* 确保在窗口内喂狗 */
if (counter <= WWDG_WINDOW && counter > 0x40) {
    WWDG_Refresh();
} else {
    printf("不在喂狗窗口内,跳过喂狗\r\n");
}

6.2 调试技巧

使用调试器观察看门狗寄存器:

c 复制代码
/* 在调试时暂停看门狗 */
void Debug_IWDG_Pause(void)
{
    /* 设置调试寄存器,暂停IWDG */
    DBGMCU->CR |= DBGMCU_CR_DBG_IWDG_STOP;
}

/* 恢复看门狗 */
void Debug_IWDG_Resume(void)
{
    DBGMCU->CR &= ~DBGMCU_CR_DBG_IWDG_STOP;
}

打印看门狗状态:

c 复制代码
void Print_Watchdog_Status(void)
{
    printf("===== 看门狗状态 =====\r\n");
    printf("IWDG状态:\r\n");
    printf("  KR寄存器:0x%08X\r\n", (unsigned int)IWDG->KR);
    printf("  PR寄存器:0x%08X\r\n", (unsigned int)IWDG->PR);
    printf("  RLR寄存器:0x%08X\r\n", (unsigned int)IWDG->RLR);
    printf("  SR寄存器:0x%08X\r\n", (unsigned int)IWDG->SR);
    
    printf("WWDG状态:\r\n");
    printf("  CR寄存器:0x%08X\r\n", (unsigned int)WWDG->CR);
    printf("  CFR寄存器:0x%08X\r\n", (unsigned int)WWDG->CFR);
    printf("  SR寄存器:0x%08X\r\n", (unsigned int)WWDG->SR);
}

七、总结

7.1 核心知识点

  1. IWDG特点:独立时钟、简单可靠、适合防止程序跑飞
  2. WWDG特点:精确时序、窗口监控、适合时间敏感应用
  3. 喂狗时机:必须在超时前完成,WWDG还需在窗口内
  4. 复位检测:通过RCC_CSR寄存器判断复位原因

7.2 最佳实践

  • 合理设置超时时间:既要保证响应速度,又要避免过于频繁喂狗
  • 分散喂狗点:在关键代码路径多处喂狗,避免单点故障
  • 记录复位原因:保存复位日志,便于问题分析
  • 调试时暂停看门狗:避免调试过程中触发复位

7.3 扩展学习

  • 结合RTC实现低功耗模式下的看门狗管理
  • 使用备份寄存器保存复位计数和错误信息
  • 实现多级看门狗策略(IWDG+WWDG同时使用)

7.4 学习资源

官方文档:

官方GitHub:

相关推荐
ytttr8732 小时前
STM32 USB HID 源码方案
stm32·单片机·嵌入式硬件
C语言小火车2 小时前
嵌入式实习面试问题:那个动态内存是怎么样分配的?
c语言·开发语言·c++·嵌入式硬件·面试
cici158742 小时前
最简单的51单片机舵机控制器DIY
单片机·嵌入式硬件·51单片机
KWTXX2 小时前
两种方式实现51单片机五路循迹,经测试可实现
单片机·嵌入式硬件·51单片机
0南城逆流03 小时前
【STM32】知识点介绍九:看门狗功能
stm32·单片机·嵌入式硬件
VBsemi-专注于MOSFET研发定制3 小时前
面向高端汽车暖风系统控制器的功率MOSFET选型策略与器件适配手册
单片机·嵌入式硬件
Byron Loong3 小时前
【常识】通俗易懂的讲CPU,GPU,MCU,FPGA,DSP的区别和特点
单片机·嵌入式硬件·fpga开发
zmj3203243 小时前
工业通信--CRC校验分类及实现细节
人工智能·单片机·数据挖掘·can
潜创微科技3 小时前
CH398:高集成度 USB 3.0 转千兆以太网芯片方案
嵌入式硬件·音视频