stm32利用LED配置基础寄存器+体验滴答定时器+hal库环境配置

P1 LED控制与流水灯效果实现

概述

大家好,今天我们来学习一下如何在STM32上控制LED灯,并且实现一个流水灯的效果。这不仅是一个基础的实践,也是嵌入式开发中非常常见的需求。

LED控制

1. LED初始化

首先,我们需要对LED灯对应的GPIO端口进行初始化。这个过程包括三个步骤:

  1. 开启GPIOA的时钟信号。
  2. 配置具体的IO端口的工作模式。
  3. 决定输出高电平还是低电平。
c 复制代码
void LED_Init(void) {
    // 开启GPIOA的时钟信号
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    // 配置PA8为通用推挽输出模式
    GPIOA->CRH |= GPIO_CRH_MODE8;
    GPIOA->CRH &= ~GPIO_CRH_CNF8;

    // 设置PA8的初始状态为熄灭(输出低电平)
    LED_Off(LED3);
}

2. LED点亮与熄灭

接下来,我们实现了LED灯的点亮和熄灭函数。这些函数通过操作GPIOA的ODR寄存器来控制LED灯的状态。

c 复制代码
// 点亮指定的LED灯
void LED_On(uint16_t led) {
    GPIOA->ODR &= ~led;
}

// 熄灭指定的LED灯
void LED_Off(uint16_t led) {
    GPIOA->ODR |= led;
}

3. LED状态反转

我们还实现了一个LED状态反转的函数,通过异或操作来实现LED灯状态的快速切换。

c 复制代码
// 反转指定LED灯的状态
void LED_Toggle(uint16_t led) {
    GPIOA->ODR ^= led;
}

流水灯效果实现

在主函数中,我们通过循环调用LED的点亮和熄灭函数,实现了流水灯效果。

c 复制代码
int main(void) {
    LED_Init(); // 初始化LED

    while (1) {
        LED_On(LED1);
        Delay_ms(500); // 延时500ms
        LED_Off(LED1);

        LED_On(LED2);
        Delay_ms(500); // 延时500ms
        LED_Off(LED2);

        LED_On(LED3);
        Delay_ms(500); // 延时500ms
        LED_Off(LED3);
    }
}

生活中的比喻

想象一下,你在家里控制节日的彩灯串。每个LED灯就像一个小灯泡,而STM32就像一个智能开关,通过编写程序,你可以控制哪些灯泡亮起,哪些熄灭,以及它们亮起的顺序和时间。这样,你就可以创建出各种动态的灯光效果,就像编程控制STM32实现流水灯效果一样。

练习题及答案

题目: 如何在STM32上实现一个呼吸灯效果?

答案: 呼吸灯效果可以通过PWM(脉冲宽度调制)来实现。你需要配置一个定时器产生PWM波,然后通过改变PWM的占空比来控制LED的亮度,从而实现呼吸灯的效果。

好的,根据您提供的字幕内容,我将模仿尚硅谷老师的风格,将课程内容整理成Markdown格式的博客,并包含练习题及答案。以下是博客内容:


P2 RCC时钟使能的重要性

概述

大家好,今天我们来聊聊STM32中的RCC时钟使能。很多同学可能会疑惑,为什么在STM32中,我们需要先使能时钟才能配置外设寄存器。这个问题其实涉及到STM32的功耗管理和外设初始化。

RCC时钟使能的必要性

1. 省电设计

首先,我们来想想,为什么STM32要有一个专门的RCC模块来管理时钟信号。其实,这个设计的主要目的是为了省电。时钟信号是不停地在拉高和拉低,即使电路处于高阻态或断路状态,也会有微弱的电流消耗。因此,RCC模块允许我们只对需要使用的外设提供时钟信号,从而节省电量。

2. 时钟使能的顺序

在配置任何外设之前,我们必须先使能该外设的时钟信号。如果我们先配置外设寄存器,然后再使能时钟,会发生什么呢?答案是:外设可能不会按预期工作。这是因为,外设的寄存器需要时钟信号来更新其状态,如果没有时钟信号,寄存器的配置就不会生效。

3. 实践中的测试

我们可以通过一个简单的测试来验证这一点。如果我们将时钟使能放在GPIO配置之后,然后尝试点亮一个LED,我们会发现LED不会亮起。这就是因为我们没有在正确的顺序下使能时钟信号。

4. 为什么时钟信号是必须的

在数字电路中,存储电路(如触发器和寄存器)需要时钟信号来同步数据的存储和更新。如果没有时钟信号,数据就无法正确地写入寄存器。这就是为什么我们在操作外设寄存器之前,必须先使能时钟信号。

实践题及答案

题目: 如何确保STM32的外设正确初始化?

答案: 在配置任何外设寄存器之前,确保先使能该外设的时钟信号。例如,如果你要初始化GPIOA,你应该先使能GPIOA的时钟信号,然后再配置GPIOA的模式和输出。

c 复制代码
// 使能GPIOA的时钟信号
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

// 配置GPIOA的模式和输出
GPIOA->CRL |= GPIO_CRL_MODE0;
GPIOA->CRL &= ~GPIO_CRL_CNF0;

好的,根据您提供的字幕内容,我将模仿尚硅谷老师的风格,将课程内容整理成Markdown格式的博客,并包含练习题及答案。以下是博客内容:


P3 关闭时钟信号节能的原理

概述

大家好,今天我们来聊聊为什么在STM32中关闭时钟信号可以节省电量。这个问题其实涉及到了我们对MOS管和时钟信号的理解。

时钟信号与功耗

1. MOS管的工作原理

首先,我们来回顾一下MOS管的工作原理。MOS管,也就是金属氧化物半导体场效应管,它有一个特点,就是通过改变栅极(山极)的电压来控制源极和漏极之间的导通状态。

2. 时钟信号的动态过程

时钟信号是周期性变化的,它不停地在高电平和低电平之间切换。这个过程中,即使是MOS管没有工作,也会因为时钟信号的切换而产生电荷的消耗。比如,当VCC给MOS管的栅极充电时,会产生电荷积累;而当信号变为低电平时,这些电荷又被迅速带走。这个过程即使没有电流通过,也会消耗电量。

3. RCC的作用

在STM32中,RCC(Reset and Clock Control)模块负责管理片上外设的时钟信号。当我们不需要使用某个外设时,可以通过RCC关闭该外设的时钟信号,从而切断对其内部MOS管的充电和放电,达到节省电量的目的。

4. 节能原理

关闭时钟信号可以节省电能的原理其实很简单:减少了MOS管的充电和放电次数,也就减少了电量的消耗。这对于电池供电的设备来说尤其重要,因为它们需要尽可能地延长电池寿命。

实践题及答案

题目: 如何在STM32中通过关闭时钟信号来节省电量?

答案: 在STM32中,你可以通过RCC模块来管理外设的时钟信号。当你确定某个外设暂时不需要使用时,可以通过RCC关闭该外设的时钟信号,从而节省电量。

c 复制代码
// 假设我们要关闭GPIOA的时钟信号
RCC->APB2ENR &= ~RCC_APB2ENR_IOPAEN;

好的,根据您提供的字幕内容,我将模仿尚硅谷老师的风格,将课程内容整理成Markdown格式的博客,并包含练习题及答案。以下是博客内容:


P4 Keil Assistant插件的安装与配置

概述

大家好,今天我们来聊聊如何在VSCode中安装和配置Keil Assistant插件。这个插件虽然已经停止维护,但对于我们学习和开发STM32项目来说,还是非常有用的。

Keil Assistant插件安装

1. 插件选择

如果你有能力根据教程或者网上的文章去配置EIDE,那当然是最好的。但如果你想要一个简单快捷的解决方案,Keil Assistant插件是一个不错的选择。

2. 安装步骤

安装Keil Assistant插件非常简单,你只需要在VSCode中搜索qassistant,然后选择下载量最大的那个版本进行安装。

markdown 复制代码
步骤:
1. 打开VSCode。
2. 转到插件市场,搜索`qassistant`。
3. 选择下载量最大的版本,点击安装。

3. 插件配置

安装完成后,我们需要进行一些配置,主要是指定Keil MDK的安装路径。

markdown 复制代码
配置步骤:
1. 打开VSCode的设置(可以通过点击左下角的齿轮图标或使用快捷键`Ctrl + ,`)。
2. 搜索`qassistant`。
3. 找到Keil MDK的配置项,指定Keil MDK的安装路径。

4. 路径指定

在指定路径时,注意不要复制前面的字母C,因为Windows有一个老bug,会导致路径中的某些字符出现问题。

markdown 复制代码
如何正确复制路径:
1. 右键Keil MDK的快捷方式,选择"属性"。
2. 复制"目标"中的路径,但不要复制字母`C`。
3. 在VSCode的插件配置中,粘贴路径,并在前面补上`C:`。

实践题及答案

题目: 如何在VSCode中安装Keil Assistant插件?

答案:

  1. 打开VSCode。
  2. 转到插件市场,搜索qassistant
  3. 选择下载量最大的版本,点击安装。
  4. 打开VSCode的设置,搜索qassistant
  5. 找到Keil MDK的配置项,指定Keil MDK的安装路径。
markdown 复制代码
这样,你就完成了Keil Assistant插件的安装和配置,可以开始使用它来开发STM32项目了。

P5 使用VSCode编译和烧录代码

概述

大家好,今天我们来学习如何使用VSCode来编译和烧录STM32的代码。这个技能对于嵌入式开发来说非常重要,也是我们日常开发中经常用到的操作。

安装Keil Assistant插件

首先,我们需要确保VSCode中安装了Keil Assistant插件。这个插件可以帮助我们直接在VSCode中进行编译和烧录操作。

插件安装步骤

  1. 打开VSCode。
  2. 转到插件市场,搜索Keil Assistant
  3. 选择下载量最大的版本,点击安装。

插件配置

安装完成后,我们需要进行一些配置,主要是指定Keil MDK的安装路径。

  1. 打开VSCode的设置(可以通过点击左下角的齿轮图标或使用快捷键Ctrl + ,)。
  2. 搜索Keil Assistant
  3. 找到Keil MDK的配置项,指定Keil MDK的安装路径。

使用VSCode编译和烧录

打开项目

  1. 在VSCode中打开你的STM32项目目录。
  2. 注意不要打开错误的项目,确保打开的是Keil项目。

编译项目

  1. 在VSCode中,点击项目右侧的Build按钮进行编译。
  2. 编译结果会在底部窗口显示,如果有错误或警告,根据提示进行修正。

烧录项目

  1. 编译完成后,点击Download按钮进行烧录。
  2. 烧录成功后,可以通过摄像头查看LED灯的状态,验证烧录是否成功。

常见问题

编码问题

如果你在VSCode中打开项目时遇到编码乱码的问题,可以在VSCode中重新以正确的编码方式打开文件。

编译警告

有时候编译时会出现警告,比如文件最后一行没有空行结束。这通常是历史遗留问题,不影响代码运行,可以忽略。

实践题及答案

题目: 如何在VSCode中编译和烧录STM32项目?

答案:

  1. 确保VSCode中安装了Keil Assistant插件。
  2. 打开STM32项目目录。
  3. 点击Build按钮进行编译。
  4. 编译完成后,点击Download按钮进行烧录。
markdown 复制代码
这样,你就可以在VSCode中完成STM32项目的编译和烧录了。

P6 如何在没有右键菜单的情况下打开Keil项目

概述

大家好,今天我们来解决一个实际问题,就是在没有右键菜单的情况下,如何在VSCode中打开Keil项目。这个问题可能因为VSCode安装时的配置不同而出现。

缺少右键菜单的问题

1. 问题原因

有些同学可能在安装VSCode时没有勾选"通过Code打开"的选项,导致在文件资源管理器中右键点击文件时,没有"通过Code打开"的选项。

2. 解决方案

不要去网上搜索修改注册表的方法,因为这可能会导致更多的问题。注册表是Windows的一个重要组成部分,错误的修改可能会导致系统不稳定。

3. 重新安装VSCode

最简单的解决方案是卸载VSCode,然后重新安装,并在安装过程中勾选"通过Code打开"的选项。

4. 手动打开项目

如果你不想重新安装VSCode,也可以直接在VSCode中手动打开项目。

步骤如下:
  1. 打开VSCode。
  2. 点击左上角的"Explorer"。
  3. 在打开的界面中,点击右侧的"+"号,选择"打开文件夹"。
  4. 导航到你的Keil项目所在的目录,选择项目文件,点击"打开"。

5. 切换工作空间

打开项目后,VSCode会提示你是否要将工作空间切换到项目所在的目录,确保选择"是",这样你就可以在VSCode中正常编辑和编译项目了。

实践题及答案

题目: 如果在文件资源管理器中没有"通过Code打开"的选项,如何手动在VSCode中打开Keil项目?

答案:

  1. 打开VSCode。
  2. 点击左上角的"Explorer"。
  3. 点击"+"号,选择"打开文件夹"。
  4. 导航到Keil项目目录,选择项目文件,点击"打开"。
  5. 根据提示,切换工作空间到项目目录。

markdown

markdown 复制代码
通过以上步骤,你就可以在没有右键菜单的情况下,手动在VSCode中打开Keil项目了。

P7 如何从旧项目创建新项目

概述

大家好,今天我们来学习一下如何从旧项目创建新项目。这是一个很常见的需求,特别是在我们做嵌入式开发的时候,很多项目都有相似的基础结构。

从旧项目创建新项目

1. 复制旧项目

首先,我们可以复制一个已经存在的项目,这样可以节省我们配置环境的时间。

markdown

markdown 复制代码
步骤:
1. 使用`Ctrl+C`复制旧项目的文件夹。
2. 使用`Ctrl+V`粘贴到新的位置,创建一个新的项目文件夹。

2. 重命名项目

接下来,我们需要给新项目重新命名,以区分旧项目。

markdown

markdown 复制代码
步骤:
1. 将粘贴后的文件夹重命名为`02_LEDFlow`,其中`Flow`表示流水灯的意思。
2. 保持后缀为`Register`,因为我们仍然使用寄存器来实现功能。

3. 删除自动生成的文件

在复制的项目中,有些文件是Keil自动生成的,包含了旧项目的信息,我们需要将它们删除。

markdown

markdown 复制代码
步骤:
1. 删除`Objects`、`Listings`、`Debug`和`Release`文件夹。
2. 删除`UVOPTX`和`SCVD`文件,这些通常是Keil的配置文件。

4. 重命名Keil项目文件

Keil项目文件的名称会影响项目和编译文件的识别,所以我们需要重命名。

markdown

markdown 复制代码
步骤:
1. 将`.uvprojx`文件重命名为`LED_Flow.uvprojx`。

5. 配置新项目

新项目创建后,我们需要做一些配置,以确保项目能够正常编译和下载。

markdown

markdown 复制代码
步骤:
1. 打开Keil项目。
2. 在下载器配置中,将下载器改回`ST-Link`。
3. 在`Options for Target`中,关闭`Reset and Run`和`Pageable`选项。

6. 使用VSCode打开新项目

最后,我们可以使用VSCode来打开新项目,并开始编码。

markdown

markdown 复制代码
步骤:
1. 通过VSCode打开新项目的文件夹。
2. 使用VSCode的Keil插件打开Keil项目文件。

实践题及答案

题目: 如何在Keil中从旧项目创建新项目?

答案:

  1. 复制旧项目的文件夹到新的位置。
  2. 重命名新文件夹为02_LEDFlow
  3. 删除新项目中的ObjectsListingsDebugRelease文件夹以及UVOPTXSCVD文件。
  4. 重命名.uvprojx文件为LED_Flow.uvprojx
  5. 打开Keil项目,配置下载器为ST-Link,并关闭Reset and RunPageable选项。
  6. 使用VSCode打开新项目的文件夹,并使用VSCode的Keil插件打开Keil项目文件。

markdown

markdown 复制代码
通过以上步骤,你就可以在Keil中成功地从旧项目创建新项目,并开始新的开发工作了。

P8 创建新文件和目录后如何在Keil里进行添加

概述

大家好,今天我们来学习一下在Keil中如何添加新创建的文件和目录。这对于我们组织和管理项目代码非常重要。

创建新文件和目录

1. 代码组织

首先,我们来考虑一下代码该如何组织。我们的LED流水灯代码会比较多,所以我们需要合理地组织这些代码。

2. 创建硬件层目录

我们可以创建一个新的目录,比如叫做Hardware,用来存放所有硬件相关的代码。

markdown

markdown 复制代码
步骤:
1. 创建一个新的目录,命名为`Hardware`。
2. 在`Hardware`目录下,创建一个子目录,命名为`LED`,专门用来存放LED相关的代码。

3. 创建源码文件和头文件

接下来,我们需要在LED目录下创建源码文件和头文件。

markdown

markdown 复制代码
步骤:
1. 在`LED`目录下,创建一个文本文档,命名为`LED.c`。
2. 同样地,创建另一个文本文档,命名为`LED.h`。

4. 将新文件添加到Keil项目

现在我们需要将这些新创建的文件添加到Keil项目中。

markdown

markdown 复制代码
步骤:
1. 打开Keil项目。
2. 右键点击项目,选择`Add Files to Group 'Hardware/LED'`。
3. 选择`LED.c`和`LED.h`文件,添加到项目中。

5. 配置Include Path

为了让Keil能够找到我们的头文件,我们需要配置Include Path。

markdown

markdown 复制代码
步骤:
1. 打开Keil的项目设置。
2. 在`C/C++`选项卡下,找到`Include Paths`。
3. 添加`Hardware/LED`目录到Include Path中。

实践题及答案

题目: 如何在Keil项目中添加新创建的LED源码文件和头文件?

答案:

  1. 在项目目录下创建Hardware文件夹,并在其中创建LED子文件夹。
  2. LED文件夹中创建LED.cLED.h文件。
  3. 打开Keil项目,将LED.cLED.h文件添加到Hardware/LED组中。
  4. 配置项目的Include Path,添加Hardware/LED目录。

markdown

markdown 复制代码
通过以上步骤,你就可以在Keil项目中成功地添加新创建的LED源码文件和头文件,并开始编写代码了。

P9 编写LED控制的四个核心函数

概述

大家好,今天我们来学习一下如何在STM32中编写LED控制的四个核心函数,包括LED初始化、开灯、关灯和状态翻转。这些是嵌入式开发中非常基础且重要的操作。

LED控制函数的编写

1. LED初始化函数 LED_Init

首先,我们需要编写一个LED初始化函数,这个函数的作用是配置LED对应的GPIO端口。

c

c 复制代码
void LED_Init(void) {
    // 打开GPIOA的时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    // 配置PA8为通用推挽输出模式
    GPIOA->CRH |= GPIO_CRH_MODE8;
    GPIOA->CRH &= ~GPIO_CRH_CNF8;
    // 初始化所有LED为关闭状态
    LED_Off(LED1 | LED2 | LED3);
}

2. LED开灯函数 LED_On

接下来,我们编写一个开灯函数,通过操作GPIO的ODR寄存器来点亮LED。

c

c 复制代码
void LED_On(uint16_t led) {
    GPIOA->ODR &= ~led;
}

3. LED关灯函数 LED_Off

然后,我们编写一个关灯函数,同样通过操作GPIO的ODR寄存器来熄灭LED。

c

c 复制代码
void LED_Off(uint16_t led) {
    GPIOA->ODR |= led;
}

4. LED状态翻转函数 LED_Toggle

最后,我们编写一个状态翻转函数,使用异或操作来切换LED的状态。

c

c 复制代码
void LED_Toggle(uint16_t led) {
    GPIOA->ODR ^= led;
}

宏定义的使用

为了方便调用,我们可以定义一些宏来代表特定的LED。

c

c 复制代码
#define LED1 (1 << 0)
#define LED2 (1 << 1)
#define LED3 (1 << 8)

这样,我们就可以直接使用LED1LED2LED3来控制对应的LED灯。

实践题及答案

题目: 如何在STM32中编写LED控制函数?

答案:

  1. 编写LED_Init函数来初始化LED对应的GPIO端口。
  2. 编写LED_On函数来点亮LED。
  3. 编写LED_Off函数来熄灭LED。
  4. 编写LED_Toggle函数来切换LED的状态。
  5. 使用宏定义来简化LED控制代码。

markdown

markdown 复制代码
通过以上步骤,你就可以在STM32中成功地编写LED控制函数,并控制LED的亮灭状态了。

P10 编写LED控制函数

概述

大家好,今天我们来学习一下如何在STM32中编写LED控制函数。这些函数包括LED初始化、开灯、关灯和状态翻转,这些都是嵌入式开发中非常基础的操作。

LED控制函数的编写

1. LED初始化函数 LED_Init

首先,我们需要编写一个LED初始化函数,这个函数的作用是配置LED对应的GPIO端口。

c

c 复制代码
void LED_Init(void) {
    // 打开GPIOA的时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    // 配置PA8为通用推挽输出模式
    GPIOA->CRH |= GPIO_CRH_MODE8;
    GPIOA->CRH &= ~GPIO_CRH_CNF8;
    // 初始化所有LED为关闭状态
    LED_Off(LED1 | LED2 | LED3);
}

2. LED开灯函数 LED_On

接下来,我们编写一个开灯函数,通过操作GPIO的ODR寄存器来点亮LED。

c

c 复制代码
void LED_On(uint16_t led) {
    GPIOA->ODR &= ~led;
}

3. LED关灯函数 LED_Off

然后,我们编写一个关灯函数,同样通过操作GPIO的ODR寄存器来熄灭LED。

c

c 复制代码
void LED_Off(uint16_t led) {
    GPIOA->ODR |= led;
}

4. LED状态翻转函数 LED_Toggle

最后,我们编写一个状态翻转函数,使用异或操作来切换LED的状态。

c

c 复制代码
void LED_Toggle(uint16_t led) {
    GPIOA->ODR ^= led;
}

宏定义的使用

为了方便调用,我们可以定义一些宏来代表特定的LED。

c

c 复制代码
#define LED1 (1 << 0)
#define LED2 (1 << 1)
#define LED3 (1 << 8)

这样,我们就可以直接使用LED1LED2LED3来控制对应的LED灯。

实践题及答案

题目: 如何在STM32中编写LED控制函数?

答案:

  1. 编写LED_Init函数来初始化LED对应的GPIO端口。
  2. 编写LED_On函数来点亮LED。
  3. 编写LED_Off函数来熄灭LED。
  4. 编写LED_Toggle函数来切换LED的状态。
  5. 使用宏定义来简化LED控制代码。

markdown复制

markdown 复制代码
通过以上步骤,你就可以在STM32中成功地编写LED控制函数,并控制LED的亮灭状态了。

P11 流水灯效果的实现

概述

大家好,今天我们来学习一下如何在STM32上实现流水灯效果。这个项目需要我们使用之前学过的LED控制函数,包括初始化、开灯、关灯和状态翻转。

实现流水灯效果

1. 准备工作

首先,我们需要确保我们的LED控制函数已经准备好,包括LED_InitLED_OnLED_OffLED_Toggle

2. 主函数编写

接下来,我们在主函数中编写流水灯的逻辑。

c

c 复制代码
int main(void) {
    // 初始化LED
    LED_Init();

    while (1) {
        // 依次点亮LED1,延时,然后熄灭
        LED_On(LED1);
        Delay_ms(500);
        LED_Off(LED1);

        // 依次点亮LED2,延时,然后熄灭
        LED_On(LED2);
        Delay_ms(500);
        LED_Off(LED2);

        // 依次点亮LED3,延时,然后熄灭
        LED_On(LED3);
        Delay_ms(500);
        LED_Off(LED3);
    }
}

3. 延时函数

由于我们需要在每个LED点亮后等待一段时间,所以我们使用了一个简单的延时函数Delay_ms

c

c 复制代码
void Delay_ms(uint16_t ms) {
    uint16_t i, j;
    for (i = 0; i < ms; i++)
        for (j = 0; j < 123; j++); // 简单的循环延时
}

4. 命名风格和输入法技巧

在编程中,我们有两种常见的命名风格:

  • 驼峰命名法 (CamelCase):首字母大写,每个单词的首字母大写,如BadDelay
  • 下划线命名法 (SnakeCase):单词间用下划线分隔,如bad_delay

此外,老师还提到了输入法的u模式,帮助我们快速输入不认识的汉字。

5. 编译和烧录

最后,我们编译和烧录代码,检查流水灯效果。

实践题及答案

题目: 如何在STM32上实现流水灯效果?

答案:

  1. 编写LED_Init函数初始化LED。
  2. 在主函数中,依次点亮每个LED,并使用延时函数Delay_ms控制每个LED的亮灭时间。
  3. 使用驼峰命名法或下划线命名法来命名函数,提高代码的可读性。
  4. 编译和烧录代码,检查流水灯效果。

markdown复制

markdown 复制代码
通过以上步骤,你就可以在STM32上成功地实现流水灯效果了。

P12 主动阅读Error信息的重要性

概述

大家好,今天我们来聊聊在编程过程中,如何正确地处理和阅读错误信息。这对于我们调试程序和解决问题非常重要。

刷录不成功的问题

1. 问题描述

有些同学在尝试烧录程序时遇到了问题,编译可以通过,但是烧录不成功,会报错如errorflashlologfileattachketDLLhasbeencancelled,这通常意味着目标连接被取消了。

2. 可能的原因

这个问题大多数是因为STlink没有插牢,或者是Keil中的STlink配置有问题。

3. 解决方案

  • 确保STlink插牢。
  • 检查Keil中的STlink配置是否正确。
  • 确保在配置STlink时点击了OK而不是直接关闭了配置窗口。

阅读错误信息的能力

1. 错误信息的重要性

很多同学在写代码时会遇到各种问题,编译器会在底部的框里给出错误信息,这是我们排查问题的重要线索。

2. 硬读错误信息

如果你对英语阅读有为难情绪,那么你需要克服这个问题。编译器的错误信息是英文的,你需要硬读,即使看不懂也要尝试去理解。

3. 使用翻译软件

可以使用翻译软件辅助阅读错误信息,比如设置截图翻译功能,帮助理解错误信息。

4. 错误信息的解读

错误信息会告诉你具体哪里错了,这是排查问题的起点。

5. 错误信息的级别

有些错误信息可能是警告,比如文件最后一行没有换行符,这通常不影响程序运行,可以忽略。

实践题及答案

题目: 如果在烧录程序时遇到attachketDLLhasbeencancelled错误,应该如何解决?

答案:

  1. 检查STlink是否插牢。
  2. 检查Keil中的STlink配置是否正确。
  3. 确保在配置STlink时点击了OK保存配置。

markdown

markdown 复制代码
通过以上步骤,你应该能够解决烧录时遇到的问题。

P13 系统滴答定时器和时间换算问题

概述

大家好,今天我们来聊聊STM32中的系统滴答定时器(SysTick)和时间换算问题。这是嵌入式开发中非常基础且重要的部分。

系统滴答定时器(SysTick)

1. SysTick定时器的作用

SysTick定时器是ARM Cortex-M系列内核中的一个内置定时器,它提供了系统滴答定时、时间测量和低功耗定时等功能。

2. SysTick定时器的工作原理

SysTick定时器是一个24位的倒计数定时器,当计数到0时,会从RELOAD寄存器中自动重装载定时初值,开始新一轮计数。

3. SysTick定时器的寄存器

  • CTRL(控制寄存器):控制SysTick定时器的启动、停止、中断使能等。
  • LOAD(重载寄存器):设置定时器的重载值。
  • VAL(当前值寄存器):显示当前的计数值。
  • CALIB(校准寄存器):用于校准定时器的精度。

4. SysTick定时器的时钟源

SysTick定时器的时钟源可以是处理器的核心时钟(HCLK)或者核心时钟除以8。这个时钟源为SysTick定时器提供了计数的基准。

时间换算问题

1. 时钟周期的计算

时钟周期是指时钟信号完成一个周期所需的时间。对于72MHz的时钟频率,周期长度为1/72MHz,即大约13.89ns。

1. 微秒和毫秒的换算

  • 微秒:1微秒等于1/1,000,000秒。
  • 毫秒:1毫秒等于1/1,000秒。

2. 公制单位和时间换算

在公制单位中,时间单位的换算遵循千进制,即每三个数量级变化一次,例如从秒到毫秒,再从毫秒到微秒。

3. 计算SysTick定时器的周期

如果我们希望SysTick定时器每500毫秒触发一次中断,我们可以这样计算:

  • 500毫秒 = 500,000微秒
  • 对于72MHz的时钟,每个周期为13.89ns,所以500毫秒内的周期数为500,000 / 13.89 ≈ 36,000周期
  • 因此,我们需要设置SysTick的LOAD寄存器为36,000 - 1 = 35,999。

实践题及答案

题目: 如何设置SysTick定时器每500毫秒触发一次中断?

答案:

  1. 计算所需的周期数:500毫秒 = 500,000微秒,72MHz时钟周期为13.89ns,周期数为500,000 / 13.89 ≈ 36,000周期。
  2. 设置SysTick的LOAD寄存器为36,000 - 1 = 35,999。
  3. 启用SysTick定时器。

markdown

markdown 复制代码
通过以上步骤,你可以设置SysTick定时器每500毫秒触发一次中断。

P14 时间换算快速讲解

概述

大家好,今天我们来快速过一下时间换算的问题。这对于我们理解和使用STM32的系统滴答定时器(SysTick)非常重要。

时间换算的基本概念

1. 时钟频率与周期

我们使用的时钟频率是72MHz,这意味着一秒内有72,000,000个周期。一个周期的时间就是频率的倒数。

2. 时间单位换算

我们需要将时钟周期转换成更直观的时间单位,如微秒(μs)和毫秒(ms)。

3. 单位换算的步骤

  • 秒到毫秒:1秒 = 1,000毫秒。
  • 毫秒到微秒:1毫秒 = 1,000微秒。

4. 计算周期长度

要计算72MHz时钟频率下一个周期的长度,我们可以用1秒除以72,000,000。

5. 单位换算实例

如果我们想要计算72MHz时钟频率下一个周期的长度:

  • 1秒 / 72,000,000 = 13.89微秒(μs)

这意味着每个周期大约是13.89微秒。

6. 公制单位的特点

在公制单位中,每三个数量级变化一次,例如:

  • 1千伏(kV)= 1,000伏(V)
  • 1毫安(mA)= 1,000微安(μA)

实践题及答案

题目: 如何将72MHz的时钟频率转换为周期长度?

答案:

  1. 确定时钟频率:72MHz。
  2. 计算周期长度:1秒 / 72,000,000 = 13.89微秒。

markdown

markdown 复制代码
通过以上步骤,我们可以得出72MHz的时钟频率下一个周期的长度大约是13.89微秒。

P15 创建延时函数的C和H文件

概述

大家好,今天我们来学习一下如何在项目中创建和管理延时函数。我们将创建两个文件:delay.cdelay.h,用于专门存放延时相关的代码。

创建延时函数文件

1. 创建文件

首先,我们需要在项目中创建两个新文件,一个用于实现延时函数(delay.c),另一个用于声明延时函数的原型(delay.h)。

markdown

markdown 复制代码
步骤:
1. 在项目目录中创建一个新的C文件,命名为`delay.c`。
2. 在项目目录中创建一个新的H文件,命名为`delay.h`。

2. 添加文件到项目

接下来,我们需要将这两个新创建的文件添加到Keil项目中。

markdown

markdown 复制代码
步骤:
1. 打开Keil项目。
2. 右键点击项目,选择`Add Files to Group`。
3. 选择`delay.c`和`delay.h`文件,添加到项目中。

3. 编写延时函数原型

delay.h文件中,我们需要声明延时函数的原型,以便在其他文件中调用。

c

c 复制代码
// delay.h
#ifndef __DELAY_H__
#define __DELAY_H__

void Delay_ms(uint16_t ms);
void Delay_us(uint16_t us);

#endif /* __DELAY_H__ */

4. 实现延时函数

delay.c文件中,我们需要实现延时函数的具体逻辑。

c

c 复制代码
// delay.c
#include "delay.h"

void Delay_ms(uint16_t ms) {
    // 实现延时毫秒的逻辑
}

void Delay_us(uint16_t us) {
    // 实现延时微秒的逻辑
}

5. 包含头文件

在需要使用延时函数的其他C文件中,包含delay.h头文件。

c

c 复制代码
#include "delay.h"

实践题及答案

题目: 如何在Keil项目中创建和管理延时函数?

答案:

  1. 在项目目录中创建delay.cdelay.h两个文件。
  2. 将这两个文件添加到Keil项目中。
  3. delay.h中声明延时函数的原型。
  4. delay.c中实现延时函数的具体逻辑。
  5. 在其他C文件中包含delay.h头文件,以便使用延时函数。

markdown复制

markdown 复制代码
通过以上步骤,你就可以在Keil项目中成功地创建和管理延时函数了。

P16 在User路径下管理头文件和Include Path

概述

大家好,今天我们来聊聊在STM32项目中如何管理头文件,以及如何配置Include Path。这是一个常见的问题,特别是当我们在User路径下创建新的头文件时。

User路径下的头文件管理

1. 创建头文件

之前我们提到在User路径下创建了两个文件:delay.cdelay.h。这两个文件分别用于实现和声明延时函数。

2. 配置Include Path

有同学可能会问,我们之前创建文件后都会配置Include Path,这次是否还需要配置。答案是肯定的,因为我们之前可能忘记将User目录添加到Include Path中了。

markdown

markdown 复制代码
步骤:
1. 打开Keil项目。
2. 在项目设置中找到C/C++选项卡。
3. 在Include Paths部分,添加User目录到路径列表中。

3. 为什么需要配置Include Path

如果我们不配置Include Path,编译器将无法找到User目录下的头文件,这会导致编译错误。

4. 添加User目录到Include Path

现在我们需要将User目录添加到Include Path中,以便编译器能够找到delay.h文件。

markdown

markdown 复制代码
步骤:
1. 在Keil的项目设置中,找到Include Paths设置。
2. 点击右侧的"Add"按钮。
3. 浏览到User目录,选择该目录并添加。

实践题及答案

题目: 如何在Keil项目中添加User目录到Include Path?

答案:

  1. 打开Keil项目。
  2. 进入项目设置,找到C/C++选项卡。
  3. 在Include Paths部分,添加User目录到路径列表中。

markdown

markdown 复制代码
通过以上步骤,你可以确保编译器能够找到User目录下的头文件,从而避免编译错误。

P17 编写延时函数的原型

概述

大家好,今天我们来学习一下如何在C语言项目中编写延时函数的原型。这对于嵌入式开发中的定时和延时操作非常重要。

延时函数的编写

1. 准备工作

首先,我们需要确保项目环境已经准备好,相关的文件也已经添加到项目中。

2. 移动函数实现

为了避免主文件越来越长,我们将badDelay函数移动到delay.c文件中,并在其他文件中使用时,需要包含delay.h头文件。

c

c 复制代码
// delay.c
#include "delay.h"

void BadDelay(void) {
    // 延时函数的实现
}

3. 编写函数原型

delay.h文件中,我们需要声明三个延时函数的原型:微秒延时、毫秒延时和秒延时。

c

c 复制代码
// delay.h
#ifndef __DELAY_H__
#define __DELAY_H__

void Delay_us(uint16_t us);
void Delay_ms(uint16_t ms);
void Delay_s(uint16_t s);

#endif /* __DELAY_H__ */

4. 包含头文件

在需要使用延时函数的其他C文件中,包含delay.h头文件。

c

c 复制代码
#include "delay.h"

5. 为什么使用uint16_t

这里我们使用uint16_t作为参数类型,而不是uint8_tuint32_t。这是因为uint16_t可以提供足够的范围来表示毫秒和秒级别的延时,同时避免了uint32_t可能带来的资源消耗。

实践题及答案

题目: 为什么在延时函数中使用uint16_t作为参数类型?

答案:

uint16_t可以提供足够的范围来表示毫秒和秒级别的延时,同时避免了uint32_t可能带来的资源消耗。例如,对于一个最大值为65535的uint16_t变量,我们可以表示的最大延时是65.535秒,这对于大多数嵌入式应用来说是足够的。

markdown

markdown 复制代码
通过以上步骤,你可以在C语言项目中成功地编写和使用延时函数。

P18 延时函数的封装

概述

大家好,今天我们来学习一下如何在C语言中封装延时函数。这将帮助我们更好地控制程序中的延时操作,使代码更加模块化和可重用。

延时函数的封装

1. 延时函数的基本思路

我们已经有了微秒级的延时函数Delay_us,现在我们需要在此基础上封装出毫秒级和秒级的延时函数。

2. 毫秒级延时函数 Delay_ms

毫秒级的延时函数可以通过调用微秒级的延时函数来实现。例如,要实现1毫秒的延时,我们可以调用Delay_us函数1000次。

c

c 复制代码
void Delay_ms(uint16_t ms) {
    while (ms--) {
        Delay_us(1000); // 调用微秒级延时函数1000次,实现1毫秒延时
    }
}

3. 秒级延时函数 Delay_s

同样,秒级的延时函数可以通过调用毫秒级的延时函数来实现。例如,要实现1秒的延时,我们可以调用Delay_ms函数1000次。

c

c 复制代码
void Delay_s(uint16_t s) {
    while (s--) {
        Delay_ms(1000); // 调用毫秒级延时函数1000次,实现1秒延时
    }
}

4. 避免溢出和循环的使用

在实现这些延时函数时,我们需要注意参数的类型和可能的溢出问题。使用循环而不是乘法可以避免溢出,并且使代码更加健壮。

实践题及答案

题目: 如何在C语言中实现毫秒级和秒级的延时函数?

答案:

  1. 通过调用微秒级延时函数Delay_us1000次来实现毫秒级延时函数Delay_ms
  2. 通过调用毫秒级延时函数Delay_ms1000次来实现秒级延时函数Delay_s
  3. 使用循环而不是乘法来避免可能的溢出问题。

markdown

markdown 复制代码
通过以上步骤,你可以在C语言项目中成功地封装毫秒级和秒级的延时函数。

P19 编写 delay_us 函数

概述

大家好,今天我们来学习一下如何在STM32中编写微秒级的延时函数 delay_us。这个函数对于精确控制时间间隔非常有用。

delay_us 函数的实现

1. 理解SysTick定时器

首先,我们需要理解系统滴答定时器(SysTick)的工作原理。SysTick定时器是一个24位的倒计数定时器,它从预设的重载值开始递减,每次递减1,直到达到0,然后重置为初始值。

2. 配置SysTick定时器

我们需要给SysTick定时器的重载寄存器(LOAD)配置一个初始值,这个值决定了定时器溢出的时间。

c

c 复制代码
SysTick->LOAD = 72 * us - 1; // 设置SysTick定时器的重载值

这里,我们假设系统时钟频率为72MHz,所以每微秒需要72个时钟周期。

3. 使能SysTick定时器

接下来,我们需要使能SysTick定时器,并等待它溢出。

c

c 复制代码
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 使能SysTick定时器

4. 等待溢出

然后,我们需要等待SysTick定时器的COUNTFLAG标志位变为1,表示定时器已经溢出。

c

c 复制代码
while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)) {
    // 等待SysTick定时器溢出
}

5. 关闭SysTick定时器

最后,我们需要关闭SysTick定时器,以避免它继续计数。

c

c 复制代码
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭SysTick定时器

实践题及答案

题目: 如何在STM32中实现微秒级的延时函数 delay_us

答案:

  1. 配置SysTick定时器的重载寄存器(LOAD)。
  2. 使能SysTick定时器。
  3. 等待SysTick定时器溢出。
  4. 关闭SysTick定时器。

c

c 复制代码
void delay_us(uint16_t us) {
    SysTick->LOAD = 72 * us - 1; // 设置SysTick定时器的重载值
    SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 使能SysTick定时器
    while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)) {
        // 等待SysTick定时器溢出
    }
    SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭SysTick定时器
}

P20 延时函数效果验证

概述

大家好,今天我们来验证一下我们之前编写的延时函数的效果。我们会通过一个简单的流水灯实验来测试延时函数的准确性。

延时函数测试

1. 测试准备

我们已经在delay.c文件中实现了延时函数,并且将它们声明在delay.h头文件中。现在我们需要在主函数中测试这些函数。

2. 修改主函数

我们将在主函数中使用delay_msdelay_s函数来控制三个LED灯的亮灭时间,以此来验证延时函数的效果。

c

c 复制代码
#include "delay.h"

int main(void) {
    LED_Init(); // 初始化LED

    while (1) {
        LED_On(LED1);
        Delay_ms(500); // 延时500毫秒
        LED_Off(LED1);

        LED_On(LED2);
        Delay_s(1); // 延时1秒
        LED_Off(LED2);

        LED_On(LED3);
        Delay_s(2); // 延时2秒
        LED_Off(LED3);
    }
}

3. 编译和烧录

我们将代码编译并烧录到STM32开发板上,然后观察LED灯的行为是否符合预期。

4. 观察结果

通过观察LED灯的亮灭时间,我们可以验证延时函数是否准确。如果LED灯按照设定的时间亮灭,那么说明我们的延时函数工作正常。

实践题及答案

题目: 如何验证延时函数的效果?

答案:

  1. 在主函数中使用delay_msdelay_s函数控制LED灯的亮灭时间。
  2. 编译并烧录代码到STM32开发板上。
  3. 观察LED灯的亮灭时间是否符合预期。

markdown复制

markdown 复制代码
通过以上步骤,我们可以验证延时函数的准确性。如果LED灯按照设定的时间亮灭,那么说明我们的延时函数工作正常。

P21 延时函数的标志位清零和n减不减一的问题

概述

大家好,今天我们来讨论一下在编写延时函数时遇到的两个常见问题:标志位如何清零,以及在设置定时器时是否需要n减一。

延时函数的标志位清零

1. 问题提出

有同学可能会问,delay_us 函数里面将 VAL 设置为零是否有必要。这个操作看起来好像是多余的,因为一旦开始计数,VAL 的值就会被自动更新。

2. 分析解释

实际上,这个操作确实不是必需的。因为SysTick定时器一旦使能,会自动将 LOAD 的值加载到 VAL 中。所以,即使我们不手动设置 VAL,它也会从 LOAD 的值开始倒数。

3. 编译和烧录测试

我们可以编译和烧录代码,然后观察程序的运行情况。结果表明,即使没有手动设置 VAL 为零,程序也能正常运行,LED灯的流水效果也符合预期。

n减不减一的问题

1. 问题的提出

另一个问题是关于定时器设置时是否需要n减一。这个问题在文档中有不同的说法,有的说要减一,有的说不用减一。

2. 实际影响分析

实际上,无论是减一还是不减一,对定时器的精度影响非常小,只差七十二分之一微秒。因此,这个问题并不需要过于纠结。

3. 手册解释

根据ARM Cortex-M3的权威指南,有两种情况:一种是需要减一(multiple shots),另一种是不需要减一(single shot)。在我们的情况下,因为我们没有让定时器自动重载,所以实际上不需要减一。

实践题及答案

题目: 在STM32中编写延时函数时,是否需要将SysTick定时器的 VAL 设置为零,以及是否需要在设置定时器时n减一?

答案:

  1. VAL 设置为零并不是必需的,因为SysTick定时器会自动从 LOAD 加载值。
  2. 在我们的用法中,不需要减一,因为我们没有让定时器自动重载。

markdown

markdown 复制代码
通过以上分析,我们可以得出结论:在编写延时函数时,这两个操作都不是必需的。

P22 如何安装STM32CubeMX

概述

大家好,今天我们来学习一下如何安装STM32CubeMX,这是一个基于Java开发的软件,对于STM32的开发来说非常重要。

安装前的准备

1. Java运行环境

首先,我们需要确保电脑上已经安装了Java运行环境。STM32CubeMX是基于Java开发的,所以这是安装CubeMX的前提条件。

markdown

markdown 复制代码
步骤:
1. 打开CMD。
2. 输入命令`java -version`检查Java是否已安装。
3. 如果未安装,需要先安装Java运行时环境。

2. 下载Java运行时环境

如果系统中没有Java运行环境,可以从以下链接下载并安装:

markdown

markdown 复制代码
下载地址:[Java Runtime Environment](https://www.oracle.com/java/technologies/javase-jdk8-downloads.html)

安装STM32CubeMX

1. 下载安装包

可以从STM32官方网站下载STM32CubeMX的安装包,或者直接从提供的资料中获取。

markdown

markdown 复制代码
步骤:
1. 访问STM32官方网站或打开提供的资料包。
2. 下载STM32CubeMX的安装包。

2. 运行安装程序

下载完成后,直接双击安装包中的setup.exe文件开始安装。

markdown

markdown 复制代码
步骤:
1. 双击`setup.exe`文件。
2. 根据提示完成安装。

3. 配置安装选项

在安装过程中,可以选择为所有用户安装或仅为当前用户安装,通常我们选择为当前用户安装即可。

markdown

markdown 复制代码
步骤:
1. 选择安装范围(For all users或Just for me)。
2. 接受许可协议。
3. 选择安装路径,避免路径中包含中文。

4. 完成安装

完成上述步骤后,STM32CubeMX就安装成功了。安装完成后,可以在开始菜单或桌面找到STM32CubeMX的快捷方式。

markdown

markdown 复制代码
步骤:
1. 完成安装向导。
2. 在开始菜单或桌面找到STM32CubeMX的快捷方式。

实践题及答案

题目: 如何检查Java运行环境是否已安装?

答案:

打开CMD,输入命令java -version,如果能够显示版本信息,则说明Java运行环境已安装。

markdown

markdown 复制代码
通过以上步骤,你可以确认Java运行环境是否已安装,并继续进行STM32CubeMX的安装。

P23 STM32CubeMX软件安装教程

大家好,今天我们来学习如何给STM32CubeMX安装180的软件开发包。如果你是第一次接触STM32CubeMX,或者需要更新你的软件包,那么这个教程将非常适合你。

软件界面介绍

首先,我们打开STM32CubeMX软件,可以看到界面分为几个部分:

  • 左侧:显示当前电脑上存在的STM32CubeMX项目。
  • 右侧:创建项目的向导。

创建新项目

创建新项目有三种方式:

  1. 从MCU型号创建项目:选择一个MCU型号来创建项目。
  2. 从STboard创建项目:选择一个官方支持的开发板来创建项目。
  3. 从例子开始项目:从官方提供的示例开始一个项目。

通常情况下,我们会选择第三种方式来创建项目。

联网下载MCU数据库

在创建项目之前,STM32CubeMX会联网下载一个包含所有支持的MCU芯片的数据库。如果你的网络条件不佳,这个过程可能会非常缓慢。

选择MCU型号

在软件内部,我们可以直接搜索所需的MCU型号,例如STM32F103C8。找到型号后,选中它,就可以开始创建项目了。

安装芯片支持包

在创建项目之前,我们需要确保已经安装了芯片的支持包。如果没有安装,创建的项目可能会出现问题。

管理嵌入式软件包

我们可以通过点击Help菜单中的Manage Enbedded Software Packages来管理软件包。

本地安装软件包

由于网络问题,我们可能需要从本地安装软件包。以下是步骤:

  1. 打开Help菜单,选择Manage Enbedded Software Packages
  2. 点击From Local,选择本地的软件包进行安装。

判断安装是否成功

安装完成后,我们可以在STM32CubeMX中查看软件包的版本号,如果显示为绿色框,则表示安装成功。


练习题

  1. 如何创建一个新的STM32CubeMX项目?
    • 答案:选择从MCU型号、STboard或从例子开始项目中的一个选项来创建新项目。
  2. 如果网络不佳,如何安装STM32CubeMX的软件包?
    • 答案:选择From Local选项,从本地选择软件包进行安装。
  3. 如何判断STM32CubeMX软件包是否安装成功?
    • 答案:在软件中查看软件包的版本号,如果显示为绿色框,则表示安装成功。
相关推荐
yutian06062 小时前
Keil MDK下载程序后MCU自动重启设置
单片机·嵌入式硬件·keil
析木不会编程5 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
枯无穷肉9 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
不过四级不改名67710 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普10 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
嵌入式大圣10 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp
云山工作室10 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
嵌入式-老费10 小时前
基于海思soc的智能产品开发(mcu读保护的设置)
单片机·嵌入式硬件
qq_3975623112 小时前
MPU6050 , 设置内部低通滤波器,对于输出数据的影响。(简单实验)
单片机
liyinuo201712 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范