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微控制器的性能和功能优势。

前言

看过另一篇实战经验《STM32F091 空片使用 System Bootloader 下载代码》 之后,就会知道 STM32F091 有一项特殊的功能,就是在空片的情况下既可以使用 ST-Link 等编程工具进行编程,也可以使用 System Memory 中的 Bootloader 进行下载代码。这完善了整个编程体系,给用户在编程方案的选择上带来很大的方便。但是,在某些特定应用上,好心也可能会干坏事哦。

下面就来聊聊这个事是怎么来的,并且找出相应的对策。

问题

某客户在其产品的设计中,使用了 STM32F091RCT6。客户使用 ST-Link 对 STM32F091RCT6 进行编程,发现对空片进行编程之后,必须要重新上电才能运行用户代码;但是如果不是空片,则编程后就可以直接运行用户代码。由于客户的测试系统是直接烧写完芯片后在不断电的情况直接进入测试模式,如果空片烧写需要断电的话,带来一定的麻烦。客户希望搞明白这件事,并希望找到办法,能在空片编程后也可以直接运行用户代码。

调研

1.还原问题

在这里,使用带有 STM32F091RCT6 的 NUCLEO-F091RC 板来进行问题还原,将此 Nucleo 板通过 USB 线连接到电脑。打开 STM32 ST-LINK Utility,点击“Connect to the target”按钮进行芯片连接,连接后打开一个准备好的 LED 灯闪烁的.hex 文件代码,点击“Program verify”按钮准备进行编程。

STM32F091 空片使用 ST-LINK 编程后不能直接运行用户代码

在这里,我们勾选了“Reset after programming”,目的在于编程后对芯片进行复位,可以运行用户代码。然后点击“Start”按钮开始进行编程。

编程之后,按道理可以看到 LED 灯闪烁的,但是并没有出现。需要给 MCU 进行断电后,重新上电才能看到 LED 灯闪烁。也就是说需要一次上电复位才能运行用户代码。

2.分析问题

先来回顾一下 STM32F091 的参考手册 RM0091 对于 Empty Check 的描述:

STM32F091 空片使用 ST-LINK 编程后不能直接运行用户代码

首先,芯片内部存在一个查空标志,用来标志芯片是否为空片。这个标志位在 BOOT0 脚被定义到从 Main Flash memory 启动的时候使用。当这个标志位被置“1”的时候,此芯片被认为是空的,系统将从 System memory 中启动 Bootloader,以允许用户进行代码下载,即使现在 BOOT0 脚定义的是从 Main Flash memory 启动。此标志位只在载入 Option bytes 时更新:

当地址 0x0800 0000 读出的内容为 0xFFFF FFFF 时,此标志位置“1”,否则为“0”。这意味着当烧写完一个空片后需要在系统复位后执行用户代码的话,是必须要重新上电以产生或者在 FLASH_CR 寄存器中置位 OBL_LAUNCH 来启动 Option byte loader reset,以清除此查空标志。

现在就可以来分析目前所遇到的情况了:

当空片通过 SWD 连接到 ST-Link 进行烧写的情况下,由于上电时空片检测检测到此芯片为空片,查空标志被置位,所以系统此时从 System memory 中启动 Bootloader 开始运行。通过简单的 SWD 接口对芯片进行编程,勾选的“Reset after programming”将在编程结束后在 RESET 引脚上产生一个复位信号,但是不幸的是这个复位并不能清除查空标志,导致复位后仍然从 System memory 中启动 Bootloader,而没有运行用户代码,也就是我们之前遇到的现象。

一般情况下,我们都可以通过重新上电来产生 POR 以清除查空标志,从 Main Flash memory 启动运行用户代码。但是,客户目前的这种特殊需求就会带来一定的麻烦。还有一种应用也会比较麻烦,也就是使用锂电池的产品,而且这个电池直接焊接到用户板上,无法方便地进行断电上电。此时,若是空片是焊接在板子上进行在线编程,那么,问题来了。空片编程之后,由于不方便进行断电,而无法完成 POR 的动作,不能运行用户代码也就无法实现一个 Option byte loader reset。查空标志无法清除,程序运行将锁死在 System memory 的 Bootloader。

3.问题解决

这种问题呢,解决方法当然有很多种,下面来大概地探讨一下:

1) 从生产上来解决:芯片在编程器上进行单独编程,之后再上板子,避开空片烧写后没有 POR。

2) 从硬件上来解决:使用一个跳线,或者使用其他方式,比如在夹具上想办法,以达到通过人工的断电再连通上电,实现一个 POR。需要在 PCB 板上预留。大家可自行选择对策。但是这会增加生产上的麻烦,降低效率。

3) 从编程方法来解决:不使用 ST-Link 进行编程,直接使用 Bootloader 进行串口升级,升级后跳转到 Main Flash memory 去运行用户代码。需要在用户代码中加入将 Main Flash memory 映射到 0x0000 0000 的代码。

4) 前面几种方式大家一看就明白如果去解决了。但是,如果一定要使用 ST-Link 通过 SWD 进行烧写的话,就另当别论,我们下面来探讨这种方式。

一般看到这种问题,直观思维就是思考是否有办法,可以在 ST-LINK 烧写后通过一定的 ST-LINK 命令跳转到用户代码去运行用户代码。方法看起来可行,但是有点复杂。第一,STM32 ST-LINK Utility 没有提供类似的功能,需要用户自行使用 ST-LINK_CLI 命令;第二,需要在用户代码中加入别忘了将 Main Flash memory 映射到 0x0000 0000 的代码;第三,由于查空标志未清除,需担心意外的复位信号或干扰,导致复位后又跑回 System Memory,还需要在用户代码中加入“每次运行都判断是否为 Option Bytes Loader reset,如果不是,就直接执行一次 Option Bytes Loader reset 以清除查空标志”。

我们的直观思维都是出现问题解决问题,但是看了上面的描述,这样的解决办法还真有点麻烦。那有没有什么其他简单的办法呢?答案是有的,我们不要把思维停留在出现问题解决问题上,而是如何去避免产生问题。下面来理一理思路:

这个问题的根源在于查空标志的存在,所以需要思考的是怎么避免查空标志的影响?
来看一下查空标志产生的条件:

a) 使用了 BOOT0 引脚
b) BOOT0 引脚为低电平,启动区域指向 Main Flash memory
c) 读取 0x0800 0000 地址的值为 0xFFFF FFFF

由于是空片编程,所以第三种条件是肯定是成立的;由于硬件设计,BOOT0 引脚的电平也不方便改来改去;所以需要把关注点放在第一个条件上——“使用了 BOOT0 引脚”。由于 STM32F091 的特性,刚好有机会可以不使用BOOT0 引脚,而是直接使用选项字节,所以解决的办法有了。

步骤如下:
i. 打开 STM32 ST-LINK Utility,点击“Connect to the target”按钮进行连接;
ii. 从菜单“Target → Option Bytes”调出选项字节对话框

STM32F091 空片使用 ST-LINK 编程后不能直接运行用户代码

将“nBoot0_SW_Cfg”的打勾去掉,点击“Apply”,改成使用选项字节中的 nBoot0 和 nBoot1 来控制启动区域
iii. 再打开需要烧写的代码文件,点击“Program Verify”按钮,对话框中勾选“Reset after programming”,点击“Start”完成烧写动作就可以了。
STM32F091 空片使用 ST-LINK 编程后不能直接运行用户代码

这样就可以看到用户代码已经在运行了,是不是很简单。
如果,希望更简单的完成,可以使用 ST-LINK_CLI,写一个批处理文件,包含以下动作:
 ST-LINK_CLI -c SWD UR
 ST-LINK_CLI -ME
 ST-LINK_CLI -p xxxxxxxx.hex -v “while_programming”
 ST-LINK_CLI -OB nBOOT0_SW_Cfg=0
 ST-LINK_CLI -Rst 

ST-LINK_CLI.exe 位于 STM32 ST-LINK Utility 安装目录里,关于命令请参考《ST-LINK Utility UM.pdf》。

结论

由于查空检测机制,导致 STM32F091 空片在使用 ST-LINK 编程后,不断电的情况下复位将回到 System Memory,无法进入Main Flash memory 去运行用户代码。所以,在特殊应用中,如果无法进行断电再上电,需要使用办法对这种机制进行破坏。

处理

将 Boot 启动配置为用选项字节进行控制,而不是使用 Boot0 引脚,以此来破坏查空机制的影响。

建议

对于问题的解决,一般从两个方向进行思考:一是出现了问题再来找解决问题的办法;二是如何避免出现问题。很多时候,由于思维惯性,很多工程师可能会更喜欢直接从第一种方向去思考问题;然而,事实上,如果能从第二种方向思考,阻止问题的产生,那才是最好的办法。

来源:ST

围观 567

一、STM32中断分组:

STM32 的每一个GPIO都能配置成一个外部中断触发源,这点也是 STM32 的强大之处。STM32 通过根据引脚的序号不同将众多中断触发源分成不同的组,比如:PA0,PB0,PC0,PD0,PE0,PF0,PG0为第一组,那么依此类推,我们能得出一共有16 组,STM32 规定,每一组中同时只能有一个中断触发源工作,那么,最多工作的也就是16个外部中断。STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。STM32F103 的19 个外部中断为:

线 0~15:对应外部 IO 口的输入中断。
STM32 GPIO外部中断总结
线 16:连接到 PVD 输出。

线 17:连接到 RTC 闹钟事件。

线 18:连接到 USB 唤醒事件。

二:外部中断的配置过程:

1、配置触发源GPIO口:

因为GPIO口作为触发源使用,所以将GPIO口配置成输入模式,触发模式有以下几种:

a.GPIO_Mode_AIN ,模拟输入(ADC模拟输入,或者低功耗下省电)

b.GPIO_Mode_IN_FLOATING ,浮空输入

c.GPIO_Mode_IPD ,带下拉输入

d.GPIO_Mode_IPU ,带上拉输入 

  GPIO_InitTypeDef GPIO_InitStructure;//定义结构体

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);//使能时钟

  GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_2;//选择IO口   PE2

  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//设置成上拉输入

  GPIO_Init(GPIOE, &GPIO_InitStructure);//使用结构体信息进行初始化IO口

2、使能AFIO复用时钟功能:

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); 

3、将GPIO口与中断线映射起来: 

  GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);

4、中断线上进行中断初始化: 

  EXTI_InitTypeDef EXTI_InitStructure;//定义初始化结构体

  EXTI_InitStructure.EXTI_Line=EXTI_Line2; //中断线的标号 取值范围为EXTI_Line0~EXTI_Line15

  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event。

  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发EXTI_Trigger_Rising_Falling

  EXTI_InitStructure.EXTI_LineCmd = ENABLE;

  EXTI_Init(&EXTI_InitStructure);//根据结构体信息进行初始化

5、中断优先级配置: 

  NVIC_InitTypeDef NVIC_InitStructure;//定义结构体

  NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能外部中断所在的通道

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2, 

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道 

  NVIC_Init(&NVIC_InitStructure); //根据结构体信息进行优先级初始化 

6、外部中断服务函数的编写:

外部中断函数分别为:

EXPORT EXTI0_IRQHandler

EXPORT EXTI1_IRQHandler

EXPOR T EXTI2_IRQHandler

EXPORT EXTI3_IRQHandler

EXPORT EXTI4_IRQHandler

EXPORT EXTI9_5_IRQHandler

EXPORT EXTI15_10_IRQHandler

中断线 0-4 每个中断线对应一个中断函数,中断线 5-9 共用中断函数 EXTI9_5_IRQHandler,中断线 10-15 共用中断函数 EXTI15_10_IRQHandler。 

  void EXTI2_IRQHandler(void)
  {
    if(EXTI_GetITStatus(EXTI_Line2)!=RESET)//判断某个线上的中断是否发生 

    {
      中断逻辑…
      EXTI_ClearITPendingBit(EXTI_Line2);   //清除 LINE 上的中断标志位
    }     
  }

三、关于使用GPIO口接按键进行外部中断的配置说明:

使用按键进行外部中断的时候,一般都需要进行按键延时消抖以及松手检测的相关处理,中断函数可以参看以下代码:

  void EXTI2_IRQHandler(void)
  {
    delay_ms(10);//延时消抖
    if(KEY2==0)    //按键真的被按下
      {
        LED0=!LED0;
      }
    while(KEY2!=0);//等待松手
    EXTI_ClearITPendingBit(EXTI_Line2); //清楚中断标志位 
  }

当然,如果你的按键是允许长按功能的,那么就进行别的逻辑操作,这里不作研究。

转自: 粥巴坨的博客

围观 544

前言

我们在《STM32F09x 不使用 BOOT 脚实现 System Bootloader 升级代码》中实现了通过修改 Option Bytes 来达到控制BOOT0 和 BOOT1,在不需要外部 BOOT 脚的情况下实现了使用 System Bootloader 进行代码升级的功能。可是,这个功能必须在程序中预先烧写了功能程序后,才能不断地进行升级。也就是说,在第一次空片烧写时,必须使用 SWD 进行烧写的。

那有没有可能在空片烧写时,就可以直接使用 System Bootloader 进行烧写呢?这样我们就可以完全使用串口进行烧写和升级了。现在,我们来探讨这个问题。

问题

在使用 STM32F091RCT6 时,能否进行对空片的串口烧写,结合《STM32F09x 不使用 BOOT 脚实现 System Bootloader 升级代码》实现完全使用串口进行烧写?

调研

1.认识一下 STMF09x 和 STM32F04x 在 Boot Configuration 中的新特性打开参考手册 RM0091,翻到 Boot Configuration 那一节,我们可以看到 Table 3 中对 Boot Mode 进行了描述,如下:

STM32F091 空片使用 System Bootloader 下载代码

从表格中,在《STM32F09x 不使用 BOOT 脚实现 System Bootloader 升级代码》一文中我们只提到了第 1 条注释:
“Grey options are available on STM32F04x and STM32F09x devices only.”
现在,我们再来看一下第 2 条注释:
“For STM32F04x and STM32F09x devices, see also Empty check description.”
那我们再来看一下 Empty check 的描述:

STM32F091 空片使用 System Bootloader 下载代码

首先,我们看到,“Empty Check”是只有 STM32F04x 和 STM32F09x 才有的功能,内部有一个查空标志,可用于使用Bootloader 对未编程过的芯片进行简单编程。这个标志位在 BOOT0 脚被定义到从 Main Flash memory 启动的时候使用。当这个标志位被置“1”的时候,此芯片被认为是空的,系统将从 System memory 中启动 Bootloader,以允许用户进行代码下载,即使现在 BOOT0 脚定义的是从 Main Flash memory 启动。此标志位只在载入 Option bytes 时更新:当地址 0x08000000 读出的内容为 0xFFFF FFFF 时,此标志位置“1”,否则为“0”。这意味着当烧写完一个空片后需要在系统复位后执行代码的话,是必须要重新上电或者在 FLASH_CR 寄存器中置位 OBL_LAUNCH。

也就是说,当我们把 BOOT0 设置为 0,设置为从 Main Flash memory 中启动时,当上电时或者置位 OBL_LAUNCH 启动一个带载入新的 Option bytes 的复位后。当 Option bytes 载入的时候,STM32F04x/STM32F09x 会读取读取 0x0800 0000 的内容,如果其值为 0xFFFF FFFF,认为此芯片为空片,直接进入 System Memory 中,使用 Bootloader 启动;如果其值不是0xFFFF FFFF,则认为此芯片不是空片,直接从用户代码启动。

2.实验验证

实验使用工具:PC 一台,NUCLEO-F091RC 一块,USB 线 1 条(用来连接 NUCLEO 板与 PC)
实验使用软件:STM32 ST-LINK Utility,Flash Loader Demonstrator
实验使用软件库:STM32Cube_FW_F0_V1.2.0
我们先来看第一个实验:

• 实验 1:空片测试

1) 使用 USB 线连接 PC 与 NUCLEO-F091RC
2) 打开 STM32 ST-LINK Utility 软件,点击“Connect to the target”按钮,连接 STM32F091。然后点击“Full chip erase”按钮对 Main Flash Memory 进行擦除。结果如图所示:

STM32F091 空片使用 System Bootloader 下载代码

3) 我们再点击菜单“Target→Option Bytes”检查一下 Option Bytes 的配置确实是初始值的状态。如下图:

STM32F091 空片使用 System Bootloader 下载代码

4) 若是确认 Option Bytes 是初始值,直接点“Cancel”退出对话框;若是 Option Bytes 为非初始值,修改为初始值后点“Apply”完成 Option Bytes 的更新并退出对话框。
5) 回到 STM32 ST-LINK Utility,点击“Disconnect”按钮断开连接。
6) 断开 USB 连接线,并重新连接。为 STM32F091 重新上电。
7) 打开 Flash Loader Demonstrator,选择正确的配置,比如下图:

STM32F091 空片使用 System Bootloader 下载代码

NUCLEO 板上的 ST-LINK 自带虚拟串口,而且已经连接到 STM32F091 芯片上。此时虚拟串口对应的是
COM16,所以选择 COM16。
8) 点击“Next”测试连接,并继续往下测试烧写。
9) 测试结果:成功!
10) 到此,我们证明空片是可以直接使用 Flash Loader Demonstrator 进行串口烧写的。但是我们再来探讨一些其他情况。

• 实验 2:测试 0x0800 0000 地址的值为 0xFFFF FFFF 的情况。

按参考手册 RM0091 的描述,只要 0x0800 0000 地址的值为 0xFFFF FFFF 就可以进入 System bootloader 了,即使BOOT 配置为 Main Flash memory 启动。所以我们再来做一个实验:

1) 使用 USB 线连接 PC 与 NUCLEO-F091RC
2) 打开 STM32 ST-LINK Utility 软件,点击“Connect to the target”按钮,连接 STM32F091。并点击“Open file”按钮,打开位于\ STM32Cube_FW_F0_V1.2.0\Projects\STM32F091RC-Nucleo\Demonstrations\Binary 的示例代码文件 STM32CubeF0_Demo_STM32F091RC-Nucleo.hex。如图:

STM32F091 空片使用 System Bootloader 下载代码

3) 点击“Program verify”按钮进行烧写。
4) 点击“Disconnect”按钮断开连接。
5) 断开 USB 连接线,并重新连接。为 STM32F091 重新上电。
6) 打开 Flash Loader Demonstrator,点击“Next”,就会弹出下图所示的警告,证明无法连接。失败!

STM32F091 空片使用 System Bootloader 下载代码

7) 切回 STM32 ST-LINK Utility 软件,点击“Connect to the target”按钮,连接 STM32F091。在“Device
Memory @ 0x08000000:”页面中直接将 0x0800 0000 地址中的数值修改为 0xFFFF FFFF,这时软件会自动更新代码,将修改过的代码烧写到 STM32F091 中,如下图:

STM32F091 空片使用 System Bootloader 下载代码

8) 点击“Disconnect”按钮断开连接。
9) 断开 USB 连接线,并重新连接。为 STM32F091 重新上电。
10) 打开 Flash Loader Demonstrator,点击“Next”测试连接,并继续往下测试烧写。
11) 测试结果:成功!
12) 到此,我们证明确实在 Main Flash memory 中只要 0x0800 0000 地址中的数值是 0xFFFF FFFF 就可以直接使用 Flash Loader Demonstrator 进行串口烧写的。其他地址的数值并没有关系。那么,是不是只要这个条件就能保证可以串口烧写呢?我们再来看下一个实验。

• 实验 3:Option Bytes 测试

1) 使用 USB 线连接 PC 与 NUCLEO-F091RC
2) 打开 STM32 ST-LINK Utility 软件,点击“Connect to the target”按钮,连接 STM32F091。然后点击“Full chip erase”按钮对 Main Flash Memory 进行擦除。结果如图所示:

STM32F091 空片使用 System Bootloader 下载代码

3) 我们再点击菜单“Target→Option Bytes”,将 Option Bytes 中的 nBOOT0_SW_Cfg 位(也就是 BOOT_SEL)的打勾去掉。如下图:

STM32F091 空片使用 System Bootloader 下载代码

4) 点击 “Apply”完成 Option Bytes 的烧写并退出对话框。
5) 回到 STM32 ST-LINK Utility,点击“Disconnect”按钮断开连接。
6) 断开 USB 连接线,并重新连接。为 STM32F091 重新上电。
7) 打开 Flash Loader Demonstrator,点击“Next”,又见到弹出下图所示的警告,证明无法连接。失败!

STM32F091 空片使用 System Bootloader 下载代码

8) 那么,这是否证明如果 Option Bytes 的值不是初始值的话,Empty Check 的功能将失效?同样的过程,我们再来试另外一个配置:

STM32F091 空片使用 System Bootloader 下载代码

在这个配置中,我们将 nBOOT0_SW_Cfg 位改回来“打勾”,再将其他打勾的项都取消掉。

9) 点击 “Apply”完成 Option Bytes 的烧写并退出对话框。
10) 回到 STM32 ST-LINK Utility,点击“Disconnect”按钮断开连接。
11) 断开 USB 连接线,并重新连接。为 STM32F091 重新上电。
12) 打开 Flash Loader Demonstrator,点击“Next”测试连接,并继续往下测试烧写。
13) 测试结果:成功!
14) 到此,我们证明 Empty Check 是会对 Option Bytes 中的 BOOT_SEL 位进行检测的。只要 BOOT_SEL 的值为“0”,而不是“1”,Empty Check 就认为这不是一个空片,不会跳往 System Memory 去执行 Bootloader。

结论

关于 Empty Check 的断定条件,不仅仅是 RM0091 所描述的检查 Main Flash memory 中地址 0x0800 0000 的值是否为0xFFFF FFFF,还检查了 Option Bytes 中 BOOT_SEL 位的值是否为“1”。只有在 Main Flash memory 中地址 0x08000000 的值为 0xFFFF FFFF,且 Option Bytes 中 BOOT_SEL 位的值为“1”的情况下,才会跳往 System Memory 去执行Bootloader。

到此,结合上一篇应用文档《STM32F09x 不使用 BOOT 脚实现 System Bootloader 升级代码》,我们就可真正地完全使用串口来进行代码烧写了。

来源:ST

围观 641

一些医疗检测仪器在检测时需要模拟人体温度环境以确保检测的精确性,本文以STM32为主控制器,电机驱动芯片DRV8834 为驱动器,驱动半导体致冷器(帕尔贴)给散热片加热或者制冷。但由于常规的温度控制存在惯性温度误差的问题,无法兼顾高精度和高速性的严格要求,所以采用模糊自适应PID控制方法在线实时调整PID参数,计算PID参数Kp、Ki、Kd调整控制脉冲来控制驱动器的使能。从simulink仿真的和实验结果来看模糊PID控制系统精度高、响应速度快,能达到预期效果。

温度参数是工业生产中常用的被控对象之一,在化工生产、冶金工业、电力工程和食品加工等领域广泛应用,在医疗检测设备中时常需要模拟人体温度进行成分检测。采用直流电机驱动芯片DRV8834驱动帕尔贴的制冷和加热过程。温度随时间的变化率和变化的方向不确定且可能大幅度的变化,要求系统的实际温度快速和精确地跟踪设定温度以满足加工工艺的要求。时间程序温度控制系统具有强烈的非线性、强耦合、大时滞和时变等特点,传统PID控制虽然算法简单易于实现且调整时间较快、精度较高,但是抗干扰能力不强,容易产生振荡;模糊PID不需要精确的数学模型,能较好的处理时变、非线性、滞后等问题,有很好的鲁棒性,响应速度快。

1 过程分析及常规控制方法

恒温控制系统具有制冷、加热等功能,箱体内的温度传感器DS18B20通过不断地检测温度,与设置的很定温度作比较,当室内温度低于设置温度值时,加热模块工作,使DRV 8834输出正向直流,驱动帕尔贴元器件,使其加热;当温度高于设置温度值时,使DRV8834输出反向直流,驱动帕尔贴元器件,使其工作在制冷功能。使室内温度在设定值范围内震荡,最终趋向于稳定。同时,控制系统将协调控制制冷和加热系统,以达到箱温波动值最小、高精度控温的目标。所以温度控制成为恒温控制系统的核心问题。

2 模糊PID温度控制系统的硬件电路设计

如图1,系统主要包括以下几个部分:

基于STM32的半导体制冷片控制系统设计

1)数字温度传感器:DS18B20是一种“一线总线”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、适用电压宽、与微处理器接口简单的数字化温度传感器,实现温度的采集。

2)控制器:采用STM32模块和存储器构成,以其丰富的外部资源和高达72 MHz的主频完成大量的PID运算。

3)加热模块:采用驱动芯片DRV8834,是一款双路桥式步进器或者直流电机驱动器。由于加热器帕尔贴是由直流控制发热或者制冷,所以用DRV8834用作直流电机驱动器来驱动帕尔贴。

DRV8834能够驱动两个直流电机或者一个步进电机,每个H桥的电流输出为1.5 A,2.2 A峰值电流,所以用1.5 A电流驱动帕尔贴加热元器件。该器件提供了带有一个故障输出引脚的内部关断功能,此功能用于过流保护、短路保护、欠压闭锁和过热。另外,还提供了一种低功耗睡眠模式以节约电能和增加元器件使用寿命。

基于STM32的半导体制冷片控制系统设计

如图2,nSLEEP引脚控制驱动芯片的睡眠模式,低电平进入睡眠模式,由STM32的I/O控制;AOUT1和AOUT2为桥A的两个输出端,(这里接帕尔贴的两个输入端),并且在AOUT端串联一个0.1欧姆的电阻和1uH的电感来模拟直流电机负载;VREFO为参考电压的输出;AVREF和 BVREF通过滑动变阻器改变输入的电压,结合AISEN端的电阻设置斩波电流的输出,斩波电流计算公式:

斩波电流计算公式

基于STM32的半导体制冷片控制系统设计

AENABL引脚是DRV8834芯片的使能芯片;DIR引脚控制桥电流的输出方向,这里可以控制帕尔贴的加热或制冷;nFAULT引脚在芯片正常工作时输出高电平,当输出低电平时表示芯片过温、过流或者欠压以指示芯片的工作状态;VM输入5 V电压供电。

3 模糊PID温度控制系统的软件设计

3. 1 模糊控制基本原理

模糊控制是以模糊集合理论、模糊语言及模糊逻辑为基础的控制,它是模糊数学在控制系统中的应用,是一种非线性智能控制。

基于STM32的半导体制冷片控制系统设计

本文在常规PID基础上,以温度反馈值与目标值的误差e和误差变化率ec作为输入,一方面送入模糊控制器用模糊推理的方法计算PID参数的调整系数,进行在线自整定,以满足不同e和ec对控制器参数的不同要求。

温度控制的软件设计主要包含3个部分:系统的初始化,模糊PID的计算,驱动电路的控制。其控制流程如图4所示。

基于STM32的半导体制冷片控制系统设计

其中系统初始化包含STM32系统时钟的初始化,I/O口的初始化,数字温度传感器DS18B20的初始化,显示模块的初始化等。模糊PID的计算是 e(k)和ec(k)的值输入到模糊控制规则表然后去模糊化算出Kp、Ki、Kd的当前值。PID控制输出的控制量是STM32定时器的计数值以控制 PWM输出的占空比,PWM输出连接到帕尔贴驱动器的使能引脚控制电流的输出的通断,从而控制帕尔贴的发热量。

3.2 模糊划分及模糊化

设温度偏差e的基本论域为[-30℃,+30℃],温度偏差变化率ec的基本论域为[-12,+12],输出u的基本论域为 [-0.4,+0.4],e、ec和u的语言变量E、EC和U,均划分为7个变量等级(NB,NM,NS,Z,PS,PM,PB),各个变量的模糊论域范围为:

{E)={-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6};

{Ec}={-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6};

{U}={-7,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,7}。

对于模糊控制器而言,温度偏差及其变化率都是精确输入量,为了对确定的精确量进行模糊化,必须把它们转换成模糊集合的隶属函数。由于三角形函数计算较简单、性能较好,输入/输出变量的隶属度函数都采用三角形分布。

3.3 模糊控制规则

确定模糊控制规则的原则必须是系统输出响应的动、静态特性达到最佳。当误差大或较大时,选择控制量以尽快消除误差为主;而当误差较小时,选择控制量要注意防止超调,以系统的稳定性为主要出发点。

本研究根据实际运行经验进行了试验、分析、归纳,并得出一系列控制规则为:

基于STM32的半导体制冷片控制系统设计

3.4 simulink的仿真对比

采用模糊自适应PID控制与常规PID控制作对比,体现出模糊自适应在温度控制方面的优越性。帕尔贴加热散热片可以看成是一个具有时滞特性的一阶惯性环节,其传递函数为:

基于STM32的半导体制冷片控制系统设计

其中k取4,τ取500,延时部分在simulink中串联一个Transport Delay模块,延时时间取3 s。

如图5,上半部分为模糊自适应PID控制,输入的信号通过迷糊控制器算出Kp、Ki、Kd的修正值,然后加上Kp、Ki、Kd的经验值来对传递函数起作用。下面半部分就为普通的PID控制。通过虚拟的示波器观察两种控制方法的控制效果。

基于STM32的半导体制冷片控制系统设计

红色曲线为普通PID控制的输出曲线,黄色曲线为模糊自适应PID控制的输出曲线,通过对比可以发现,传统的PID控制存在严重的超调,并在预期值上下震荡,调整时间长。模糊自适应PID控制很好的解决了这个问题,以最快的时间最小的超调达到系统稳定。

4 结束语

本次实验采用了常用的直流电机驱动器通过适当调整电路运用到半导体制冷器件上,电路简单成本也较低。软件设计上运用PID模糊控制有效的解决了温度控制的惯性和延迟问题,实验基于STM32控制器充分利用其固件库函数大大减少了开发周期,提高了效率。此系统可以运用在医疗设备、家用小电器等一些用到温度控制的场合中,具有一定代表性。

来源: 电子工程世界

围观 440

TFT-LCD即薄膜晶体管液晶显示器。其英文全称为:Thin Film Transistor-Liquid Crystal Display。TFT-LCD与无源TN-LCD、STN-LCD的简单矩阵不同,它在液晶显示屏的每一个象素上都设置有一个薄膜晶体管(TFT),可有效地克服非选通时的串扰,使显示液晶屏的静态特性与扫描线数无关,因此大大提高了图像质量。TFT-LCD也被叫做真彩液晶显示器。

TFT液晶原理:

STM32之TFT-LCD液晶学习

*背光模组:提供光源
*上下偏光片,TFT Glass Substrate, 液晶:形成偏振光,控制光线的通过与否
*彩色滤光片:提供TFT LCD R/G/B(三原色)的来源
*ITO透明导电层:提供透明的导电通路
*Photo Spacer:提供一个固定高度给彩色滤光片和TFT Glass Substrate,作为灌入液晶的空间,以及做为上下两层Glass的支撑

液晶特性:

TFT-LCD使用的液晶为TN(Twist Nematic)型液晶,分子成椭圆状。TN型液晶一般是顺着长轴方向串接,长轴间彼此平行方式排列;当接触到槽状表面时,液晶分子就会顺着槽的方向排列与槽中

STM32之TFT-LCD液晶学习

当液晶被包含在两个槽状表面中间,且槽的方向相互垂直,则液晶分子的排列为:
a)上表面分子:沿着a方向;
b)下表面分子:沿着b方向;
c)介于上下表面中间的分子:产生旋转的效应。
因此液晶分子在两槽状表面间产生90°的旋转

STM32之TFT-LCD液晶学习

当线性偏极光射入上层槽状表面时,此光线随着液晶分子的旋转也产生旋转。
当线性偏极光射出下层槽状表面时,此光线已经产生了90度的旋转

STM32之TFT-LCD液晶学习

当在上下表面之间加电压时,液晶分子会顺着电场方向排列,此时入射光线不再会旋转,因而光线直线射出下表面

STM32之TFT-LCD液晶学习

偏光片特性:
将非偏极光(一般光线)过滤成偏振光。
当非偏极光通过a方向的偏光片时,光线被过滤成与a方向平行的线性偏极光
上图:偏振方向相同,线性偏极光继续前进,通过第二片偏光片时,光线通过。
下图:偏振方向不同,通过第二片时,光线被完全阻挡

STM32之TFT-LCD液晶学习

偏振光透过液晶分子,偏振方向发生旋转,光线可通过偏光片

STM32之TFT-LCD液晶学习

当液晶分子呈如图方向排列时,光线偏振方向将不再发生旋转,最终无法通过偏光片

STM32之TFT-LCD液晶学习

TFT上下各有一片偏振方向垂直的偏光片,背光板发出的光经背光模组散射后,先通过下层偏光片形成偏振光
之后通过液晶分子,并由液晶分子的旋转角度决定通过液晶分子后的偏振方向
在经过彩色滤光片产生红、绿、蓝三色光,最后通过上偏光片,并由偏振光偏振方向与偏光片偏振方向夹角决定最终输出的光强,以形成不同的色彩。

STM32之TFT-LCD液晶学习STM32之TFT-LCD液晶学习

发光强弱由MOS管控制液晶偏转角度,从而控制光线出口强弱达到控制色彩目的.
假设240*320分辨率液晶则由于 基本色彩是3原色 所以总共有240*320*3个 MOS管

“像素”(Pixel) 是由 Picture(图像) 和 Element(元素)这两个单词的字母所组成的,是用来计算数码影像的一种单位,如同摄影的相片一样,数码影像也具有连续性的浓淡阶调,我们若把影像放大数倍,会发现这些连续色调其实是由许多色彩相近的小方点所组成,这些小方点就是构成影像的最小单位“像素”(Pixel)。这种最小的图形的单元能在屏幕上显示通常是单个的染色点。越高位的像素,其拥有的色板也就越丰富,越能表达颜色的真实感。

每个点显示的颜色如何由确定?
由于TFT 液晶我使用的是2.8寸的240*320分辨率(像素),16位真彩显示(接近自然色)
该模块采用的是显尚光电的DST2001PH TFTLCD,DST2001PH的控制器为ILI9320(可能为其他),采用16位的80并口。
驱动芯片显存GRAM与色彩关系:

STM32之TFT-LCD液晶学习

由于是16为数据,所以最低5位代表蓝色,中间6位为绿色,最高5位为红色。数值越大,表示该颜色越深。
就是向显存里面写入不同数据,产生不同的颜色.
常见颜色确定:

STM32之TFT-LCD液晶学习

利用画图工具里面3原色可能确定需要的显存数据。

本人使用ALIENTEK MiniSTM32开发板自配2.8寸液晶
液晶驱动芯片硬件接口:

STM32之TFT-LCD液晶学习

采用16位数据线(低了速度太慢,用彩色就没什么效果了)。该模块的80并口有如下一些信号线:
CS:TFTLCD片选信号。
WR:向TFTLCD写入数据。
RD:从TFTLCD读取数据。
D[15:0]:16位双向数据线。
RST:硬复位TFTLCD。
RS:命令/数据标志(0,读写命令;1,读写数据)。

ILI9320常用寄存器指令:

STM32之TFT-LCD液晶学习

R0,这个命令,有两个功能,如果对它写,则最低位为OSC,用于开启或关闭振荡器。而如果对它读操作,则返回的是控制器的型号。这个命令最大的功能就是通过读它可以得到控制器的型号,而我们代码在知道了控制器的型号之后,可以针对不同型号的控制器,进行不同的初始化。因为93xx系列的初始化,其实都比较类似,我们完全可以用一个代码兼容好几个控制器。
R3,入口模式命令。我们重点关注的是I/D0、I/D1、AM这3个位,因为这3个位控制了屏幕的显示方向。

AM:控制GRAM更新方向。当AM=0的时候,地址以行方向更新。当AM=1的时候,地址以列方向更新。
I/D[1:0]:当更新了一个数据之后,根据这两个位的设置来控制地址计数器自动增加/减少1,

STM32之TFT-LCD液晶学习

R7:显示控制命令。该命令CL位用来控制是8位彩色,还是26万色。为0时26万色,为1时八位色。D1、D0、BASEE这三个位用来控制显示开关与否的。当全部设置为1的时候开启显示,全0是关闭。我们一般通过该命令的设置来开启或关闭显示器,以降低功耗。

R32,R33:设置GRAM的行地址和列地址。R32用于设置列地址(X坐标,0~239),R33用于设置行地址(Y坐标,0~319)。当我们要在某个指定点写入一个颜色的时候,先通过这两个命令设置到改点,然后写入颜色值就可以了.

R34:写数据到GRAM命令,当写入了这个命令之后,地址计数器才会自动的增加和减少。该命令是我们要介绍的这一组命令里面唯一的单个操作的命令,只需要写入该值就可以了,其他的都是要先写入命令编号,然后写入操作数.

R80~R83:行列GRAM地址位置设置。这几个命令用于设定你显示区域的大小,我们整个屏的大小为240*320,但是有时候我们只需要在其中的一部分区域写入数据,如果用先写坐标,后写数据这样的方式来实现,则速度大打折扣。此时我们就可以通过这几个命令,在其中开辟一个区域,然后不停的丢数据,地址计数器就会根据R3的设置自动增加/减少,这样就不需要频繁的写地址了,大大提高了刷新的速度。

我们接下来看看要如何才能驱动ALIENTEK TFTLCD模块,TFTLCD显示需要的相关设置步骤如下:
1)设置STM32与TFTLCD模块相连接的IO。
这一步,先将我们与TFTLCD模块相连的IO口设置为输出,具体使用哪些IO口,这里需要根据连接电路以及TFTLCD模块的设置来确定。
2)初始化TFTLCD模块。
其实这里就是上和上面OLED模块的初始化过程差不多。通过向TFTLCD写入一系列的设置,来启动TFTLCD的显示。为后续显示字符和数字做准备。
3)通过函数将字符和数字显示到TFTLCD模块上。

MiniSTM32开发板的IO口对应关系如下:
LCD_LED对应PC10;
LCD_CS对应PC9;
LCD _RS对应PC8;
LCD _WR对应PC7;
LCD _RD对应PC6;
LCD _D[17:1]对应PB[15:0];
基本GUI接口函数简介(一)
80并口时序图:

STM32之TFT-LCD液晶学习

(一) :
//------写数据函数--------- 这里我们采用了宏定义的方式,以提高速度(由于显示图像写入读出频繁):
#define LCD_WR_DATA(data){\

LCD_RS_SET;\ //选择数据
LCD_CS_CLR;\ //选择片
DATAOUT(data);\ //把数据放入端口
LCD_WR_CLR;\ //WR写数据来个上升沿(将数据写入)
LCD_WR_SET;\
LCD_CS_SET;\ //CS上升沿 写入数据完成
}
上面函数中的‘\’是C语言中的一个转义字符,用来连接上下文,因为宏定义只能是一个串,而当你的串过长(超过一行的时候),就需要换行了,此时就必须通过反斜杠来连接上下文。这里的‘\’正是起这个作用
(二):
因为该函数使用频率不是很高 ,不使用宏定义
//----向寄存器发送指令函数------
void LCD_WR_REG(u8 data)
{
LCD_RS_CLR; //选择指令
LCD_CS_CLR; //选中芯片
DATAOUT(data); //端口放上指令
LCD_WR_CLR; //WR写数据来个上升沿(将数据写入)
LCD_WR_SET;
LCD_CS_SET; //CS上啦完成操作
}
(三) :
由下面2个寄存器设置快速IO

STM32之TFT-LCD液晶学习STM32之TFT-LCD液晶学习

//-------读取寄存器值函数---------
u16 LCD_ReadReg(u8 LCD_Reg)
{
u16 t;
LCD_WR_REG(LCD_Reg); //写入要读的寄存器号
GPIOB->CRL=0x88888888; //将端口PORTB设置成输入模式
GPIOB->CRH=0x88888888;
GPIOB->ODR=0xffff; //端口上拉预备输入
#ifdef LCD_FAST_IO //判断快速IO口是否宏定义过
LCD_RS_SET; //运用快速IO口 (例:#define LCD_CS_SET GPIOC->BSRR=1<<9 //片选口 PC9)
LCD_CS_CLR;
LCD_RD_CLR;
LCD_RD_SET; //RD脚产生上升沿
t=DATAIN;
LCD_CS_SET;
#endif
GPIOB->CRL=0x33333333; //恢复输出状态
GPIOB->CRH=0x33333333;
GPIOB->ODR=0xffff;
return t;
}

转自: eeworld.com

围观 597

一、stm32的pwm输出引脚是使用的IO口的复用功能。

二、T2~T5这4个通用定时器均可输出4路PWM——CH1~CH4。

三、我们以tim3的CH1路pwm输出为例来进行图文讲解(其它类似),并在最后给出tim3的ch1和ch2两路pwm输出的c代码(已在STM32F103RBT6上测试成功,大家放心使用!)。

四、给出了PWM频率和占空比的计算公式。

步骤如下:

1、使能TIM3时钟

详解STM32的PWM输出及频率和脉宽(占空比)的计算

RCC->APB1ENR |= 1 << 1;
2、配置对应引脚(PA6)的复用输出功能

详解STM32的PWM输出及频率和脉宽(占空比)的计算

GPIOA->CRL &= 0XF0FFFFFF;//PA6清0
GPIOA->CRL |= 0X0B000000;//复用功能输出(推挽50MHz输出)
GPIOA->ODR |= 1 << 6;//PA6上拉

3、设定计数器自动重装值及是否分频

TIM3->ARR = arr;//设定计数器自动重装值(决定PWM的频率)
TIM3->PSC = psc;//预分频器,0为不分频

4、设置PWM的模式(有1和2两种模式,区别在于输出电平极性相反),根据需求选一种即可

详解STM32的PWM输出及频率和脉宽(占空比)的计算

注:TIMX_CCMR1决定CH1~CH2路,TIMX_CCMR2决定CH3~CH4路。

详解STM32的PWM输出及频率和脉宽(占空比)的计算

//TIM3->CCMR1 |= 6 << 4;//CH1 PWM1模式
TIM3->CCMR1 |= 7 << 4;//CH1 PWM2模式
TIM3->CCMR1 |= 1 << 3;//CH1预装载使能

5、输出使能设置

详解STM32的PWM输出及频率和脉宽(占空比)的计算

TIM3->CCER |= 1 << 0;//输入/捕获1输出使能

6、自动重装载预装载允许位(ARPE) 及定时器使能

详解STM32的PWM输出及频率和脉宽(占空比)的计算

TIM3->CR1 = 0X0080;//ARPE使能(此句不配置也行)
TIM3->CR1 |= 0X01;//使能定时器3

下面给出TIM3_CH1及TIM3_CH2的PWM输出代码:
void PWM_Init_TIM3_CH1(u16 arr, u16 psc)
{
//1、使能TIM3时钟
RCC->APB1ENR |= 1 << 1;//使能TIM3时钟
//2、配置对应引脚(PA6)的复用输出功能
GPIOA->CRL &= 0XF0FFFFFF;//PA6清0
GPIOA->CRL |= 0X0B000000;//复用功能输出(推挽50MHz输出)
GPIOA->ODR |= 1 << 6;//PA6上拉
//3、设定计数器自动重装值及是否分频
TIM3->ARR = arr;//设定计数器自动重装值(决定PWM的频率)
TIM3->PSC = psc;//预分频器 0为不分频
//4、设置PWM的模式
TIM3->CCMR1 |= 7 << 4;//CH1 PWM2模式
TIM3->CCMR1 |= 1 << 3;//CH1预装载使能
//5、输出使能设置
TIM3->CCER |= 1 << 0;//输入/捕获1输出使能
//6、自动重装载预装载允许位(ARPE) 及 定时器 使能
TIM3->CR1 = 0X0080;//ARPE使能
TIM3->CR1 |= 0X01;//使能定时器3
}
void PWM_Init_TIM3_CH2(u16 arr, u16 psc)
{
RCC->APB1ENR |= 1 << 1;//使能TIM3时钟

GPIOA->CRL &= 0X0FFFFFFF;//PA7清0
GPIOA->CRL |= 0XB0000000;//复用功能输出(推挽50MHz输出)
GPIOA->ODR |= 1 << 7;//PA7上拉
TIM3->ARR = arr;//设定计数器自动重装值(决定PWM的频率)
TIM3->PSC = psc;//预分频器 不分频
TIM3->CCMR1 |= 7 << 12;//CH2 PWM2模式
TIM3->CCMR1 |= 1 << 11;//CH2预装载使能
TIM3->CCER |= 1 << 4;//输入/捕获2输出使能
TIM3->CR1 = 0X0080;//ARPE使能
TIM3->CR1 |= 0X01;//使能定时器3
}
频率:
//Fpwm = 72M / ((arr+1)*(psc+1))(单位:Hz)
占空比:
//duty circle = TIM3->CCR1 / arr(单位:%)
注:本文方法的一个定时器4个通道的pwm输出的频率是相同的,但占空比可以不同!

转自: akunainiannian的专栏

围观 558

前言

一般芯片开发过程中,存在 Bin 文件的拼接过程,比如在做 IAP 程序+用户程序过程中,最终烧写 bin 文件或 hex 文件需要将两个或者三个单独编译通过的程序拼接在一起作为最终烧录文件,本文就如何操作做详细说明。

示例案例

使用芯片为 STM32F030R8T6,IAP_Boot.bin 是程序 IAP 程序编译后生成的 bin 文件,地址在 0x0800 0000 ~ 0x0800 1FFF;APP.bin 是客户运行程序,地址在 0x0800 2000 ~ 0x0800 FFFF;而整个 Flash 空间将会是 0x0800 0000 ~ 0x0800 FFFF;

具体操作

下载任意一款支持 STM32F030 芯片烧录的烧写器 PC 软件,比如 raisonance,segger,hilosystems 等等,可根据自身所使用的量产烧写器型号进行选择,这边举例如果客户使用了 Xeltek 烧写器,其他厂商的操作大体相同。

在 xeltek 官网下载下载器的 PC 软件,www.xeltek.com

安装后打开,选择芯片型号。

用于量产烧录的拼接 Bin 文件操作

打开 IAP_Boot.bin 文件

用于量产烧录的拼接 Bin 文件操作

然后再打开 APP.bin 文件,操作注意选择地址以及不可覆盖选项

用于量产烧录的拼接 Bin 文件操作

察看拼接是否成功

会在 Buffer 中看到地址 0x0000 0000 存入的是 IAP_Boot.bin 的内容,在地址 0x0000 2000 地址上存入的是 APP.bin 文件中内容,此时即拼接成功。

用于量产烧录的拼接 Bin 文件操作用于量产烧录的拼接 Bin 文件操作

保存拼接后的文件
注意选择数据大小,生成最终量产烧写文件 Com.bin

用于量产烧录的拼接 Bin 文件操作

总结

通过上面的操作,即可生成对应的拼接文件,同样如果是几个文件的拼接也可以相同进行操作,拼接后的 bin 文件可以通过量产烧写器进行自动下载操作。

来源: ST

围观 887

页面

订阅 RSS - STM32