type
status
date
slug
summary
tags
category
icon
password
上次编辑时间
Oct 22, 2025 02:49 PM

printf重定位

freertos 任务内存分配时,动态分配内存和静态分配内存的适用场景是什么?

动态内存分配

适用场景:
动态内存分配适用于任务数量和资源需求在运行时可能变化的应用场景。例如,当系统需要根据实时数据调整任务数量时,这时候动态分配就比较灵活,并且可以在任务删除后回收内存,节约内存资源
平常一般使用动态内存分配,使用heap4进行内存管理(不了解heap4的话,看下freertos内存管理模型)
优点:
开发者无需在编译时确定所有任务的内存需求,内存分配由系统在运行时自动处理,并且可以根据实际运行条件动态调整资源配置
缺点:
内存碎片:动态分配可能导致内存碎片化,影响系统的长期稳定性和性能,所以航空、汽车、工业这种要求比较严格的行业,都是使用静态分配

静态内存分配

适用场景:
  • 对于要求高可靠性的嵌入式系统,静态分配是更安全的选择,因为它避免了运行时的内存管理开销和内存碎片等问题
  • 当任务数量和资源需求在编译时就已知且不会变化时,静态分配是比较理想的选择,例如工控系统中所有内存需求在编译时就已确定,减少了运行时的开销和不确定性,同时还避免了动态分配带来的内存碎片化问题,设备运行久了,内存碎片一多就容易卡死
像消费电子设备,重启一下就能解决,但是对于工控、航空、汽车这种设备,是绝对不能接受的
缺点:
  • 一旦编译完成,任务数量和资源配置就无法再调整,就不适合需要根据用户情况动态调整任务数量的使用场景
  • 需要开发者提前设计和配置所有任务的内存需求,对开发人员要求比较高

串口为什么要重定向?

  1. 主要是为了统一输出接口,统一使用printf函数,大家一看到就知道你这里是在打log,相比于直接用串口发送函数,不仅不用指定串口,可读性也提升了
  1. 而且printf的f,也就代表format,支持格式化输出,如果直接用串口函数,要发送整型、浮点数等变量还要额外处理格式化数据
  1. 移植性,说个最简单的,你要是换了一个串口,那你需要把所有串口函数都改掉,printf封装后只需要改一个函数
  1. 而且printf不是只能封装串口,只不过串口最常用,后面我们还会介绍SEGGER-RTT,还有easylogger日志库,也是另一套不同的搭配
  1. 使用printf为什么要勾选Microlib? 不勾选的话进入半主机模式,会直接卡死,进入debug会发现要点三次运行才能正常 解决办法:1. 加几行代码关闭半主机模式 2. 使用microlib

关于 Timebase 的选择问题

notion image
notion image
Systick 是ARM 内核层面的一个特殊定时器,其主要设计目的是为操作系统提供统一的时钟信号。这样,所有基于ARM 内核的单片机在运行操作系统时,都可以直接使用Systick 提供的时钟,无需因为不同单片机型号而修改代码。这种设计的核心是为了提高代码的可移植性。
在没有操作系统的情况下,STM32 默认会使用Systick 为HAL 库提供时基,用于HAL_Delay()HAL_GetTick() 等函数的时间管理。因此,这时候Systick 的优先级非常高。
然而,当使用FreeRTOS 作为操作系统时,Systick 被占用来为FreeRTOS 的调度器提供时钟信号。为了确保其他外部中断的优先级高于操作系统调度,保证系统的实时性,Systick 的优先级通常被设置为最低。这就导致了一个问题:如果在中断处理程序中调用依赖HAL 时基的函数(例如HAL_Delay()),由于Systick 的优先级低于中断,无法及时响应,就会导致系统卡死。
为了解决这个问题,通常会选择一个未使用的定时器(通常是功能较少的基本定时器,如TIM6)作为HAL 时基的替代源。这样,可以将这个定时器的优先级设置得更高,确保在中断中调用HAL 函数时不受Systick 优先级限制,保持系统的稳定和实时性
notion image
 

串口通信时的GND是否一定要接? VCC是否一定要接?

在串口通信中,GND(接地)是必须连接的。 GND作为公共参考电位,为发送端和接收端提供了一个共同的电压基准,确保了数据信号的正确传输和解读。没有GND的连接,双方设备之间就没有共同的零电位参考点,信号的高低电平无法被对方正确识别,通信将无法正常进行。
VCC(供电电压)并不是必须连接的。串口通信主要依赖RX(接收)、TX(发送)和GND(接地)这三个引脚来完成数据传输。 VCC通常用于为设备供电或确保双方设备的电平兼容,但在实际应用中,如果两台通信设备都有各自的电源供应,并且它们的逻辑电平兼容(例如,都遵循TTL或CMOS电平标准),那么直接连接RX、TX和GND就可以实现通信,不一定需要共享VCC。不过,如果设备间需要进行电平转换(如RS-232与TTL电平之间的转换)或者需要通过串口提供额外的电源(如某些简单的传感器或模块),这时就需要连接VCC。

什么是git stash , 什么时候会使用git stash? git stash pop?

  1. 当遇到紧急开发任务,但是当前任务还没开发完,不能提交时,会先执行git stash,然后进行紧急任务的开发,然后通过git stash pop取出暂存区的内容进行继续开发;
  1. 当切换分支时,但是当前分支已经修改的内容还不能提交时,会先执行git stash再进行分支切换,等回来当前分支进行git stash pop取出。

什么是printf的重定位?

printf函数的重定向是一种将标准输出重定向到其他输出设备或接口的技术。默认情况下,printf函数通常会将输出发送到标准输出设备,例如计算机上的控制台(终端)。但是,在嵌入式系统中,通常没有类似于计算机上的标准输出设备(终端),因此需要通过重定向技术,将printf的输出重定向到适合的输出接口,例如串口、LCD显示器、SD卡等。因此需要做printf的重定向的原因如下:
  • 适应嵌入式环境:嵌入式系统中通常没有标准的控制台或显示器,所以需要将printf输出转移到实际可用的输出设备上,如串口以便通过调试器或串口终端查看输出。
  • 节省资源:printf函数本身是比较庞大的,依赖于标准库函数和缓冲区。通过重定向,可以实现更轻量级和适应性更强的输出功能,节省内存和处理器资源。
  • 定制输出:通过重定向,可以将printf的输出格式和目的地定制为特定应用需求(不一定只能重定向到串口),例如将输出日志信息到SD卡、显示器或通过网络发送。
一句话总结: 把标准输入输出库中的printf打印函数的实际调用函数putc重定向到用串口函数输出。

设置gitee公钥的时候没有自动创建known_hosts文件怎么办?

在git bash中

Debug Check List

出了问题先按照这个顺序检查
1.程序可能爆栈了:在启动文件里调整下栈大小,如果是在RTOS 里,则调整一下任务的栈大小
2.程序可能被过度优化:调整优化等级,建议0
3.程序可能进入死循环:进入调试模式,然后全速运行程序然后按暂停,看是否进入Hardfault,然后用栈回溯)
4.可能程序执行错误:打印每个相关函数的返回值!
5.可能指针为空:打断点到指针运行处,看指针里面的值是不是0x00000000
6.可能API 接口用错:Freertos 最好使用Freertos 本身的api,比如使用queue.h 文件里面的函数,task.h 里面的函数,而不是CMSIS_os_V2.h 里面的函数。
7.可能有些程序片段根本没执行到:比如按键操作如果用polling 的方式,它运行很快,打断点不方便观察,应该在每个状态机或if 语句里放置printf(“1/r/n”);printf( “2/r/n”) ; 等
8.可能有些线程被饿死了:在线程里面加一些vTaskDelay(100)延时,防止有些线程被饿死。
9.可能有些线程没有while(1)循环:检查是不是有些线程没有死循环,直接退出了
10.可能是jlink或DAP调试器电源不稳定,接上开发板的typec接口,保证电源稳定11.所有的局部变量一定要赋初值

SWV (Serial Wire Viewer) 的工作流程

notion image
想象一下你是一个公司的老板(CPU核心),你想知道你的员工们(代码)在做什么,以及他们工作效率如何。
  1. 你需要一个“监工”系统:这个系统就是 SWV。它能让你看到很多详细的信息,而不是只知道一个结果。
  1. 监工系统需要接入你的办公室
      • SWD (Serial Wire Debug) 协议:就像是老板办公室的“控制台”。它有两个通道:
        • SWCLK (时钟线)“老板的指挥钟”。它发出命令,控制整个监工系统的节奏,确保所有人的动作都同步。
        • SWDIO (双向数据线)“老板的对讲机”。你可以通过这个对讲机向员工下达指令(比如“暂停工作!”),员工也可以通过它向你汇报一些简单状态。
      • SWO (Serial Wire Output) 引脚:这是**“员工的汇报通道”。它是一个单向的通道,就像一个员工专门用来向老板汇报的麦克风**。所有详细的汇报信息都通过这个麦克风发送出去。
  1. 员工(代码)开始工作,并开始汇报
      • ITM (Instrumentation Trace Macrocell):这是**“公司的日志部门”。它负责生成各种详细的日志**。
        • 多通道调试数据生成:就像公司里有很多不同的部门(通道),比如0号部门专门写日报printf),汇报每天的工作内容。其他部门则汇报各种事件标记,比如**“项目切换了”(任务切换),或者“突然遇到一个紧急情况,需要中断处理”**(中断触发)。
      • DWT (Data Watchpoint and Trace):这是**“公司的绩效考核部门”。它负责记录员工的效率和行为**。
        • 性能计数:就像记录每个员工完成一个任务用了多长时间(CPU周期),或者处理一个紧急情况(中断)花了多久。
        • 数据监测:就像记录某个重要文件(内存地址)是否被修改了
  1. 汇报信息如何到达老板那里?
      • TPIU (Trace Port Interface Unit):这是**“公司的信息整理与传输部门”。它把所有日志部门(ITM)和绩效考核部门(DWT)生成的海量原始数据整理、打包、格式化**。
      • 整理好的信息,通过那个专门的汇报通道 SWO传输给外部的调试器
  1. 老板(调试器)查看汇报
      • 外部的调试器(比如J-LinkST-Link)接收到这些打包好的数据后,通过软件(比如Keil、IAR等)进行解码和显示
      • 于是,你这个老板(开发者)就可以在电脑上实时看到员工们(代码)的详细工作情况,比如打印的日志信息、任务切换事件、函数执行时间等等。
总结一下:
SWV就像一个高级的监工系统。它通过 SWO 这个专门的单向通道,把由 ITMDWT 生成的详细日志和性能数据,通过 TPIU 整理后,实时发送给外部调试器。开发者就可以通过软件界面,实时观察代码的内部运行情况,而不仅仅是暂停和单步调试。这对于调试复杂的实时系统和性能分析非常有用。

git push 要注意的

为了避免不必要的麻烦,最好的习惯是始终明确指定你要推送的远程仓库和分支。
推荐的命令格式:
git push <远程仓库名> <本地分支名>:<远程分支名>
如果你本地分支名和远程分支名相同,可以简化为:
git push origin <分支名>

MDK\ARM\ARMCC\include 目录下的 stdio.h 和普通C调用的 stdio.h 有什么区别?(MDK-ARM 与标准 C 中的 stdio.h:嵌入式与通用计算环境的差异)

在 MDK-ARM 开发环境中,路径 MDK\ARM\ARMCC\include下的 stdio.h 文件与普通C语言开发环境中(例如在Windows上使用MinGW或Linux上使用GCC)所包含的stdio.h,虽然在API(应用程序编程接口)层面遵循C标准,提供了如 printfscanffopen 等标准输入输出函数的声明,但其底层实现和设计理念却存在本质区别。这些差异主要源于它们所服务的不同目标环境:嵌入式系统通用计算环境
特性
MDK\ARM\ARMCC\include\stdio.h (ARM C 库)
普通C调用的stdio.h (标准C库)
目标环境
嵌入式系统、微控制器 (MCU),资源受限
桌面、服务器等通用计算机,资源丰富
底层I/O实现
需要用户“重定向”(Retargeting)
由操作系统提供标准输入/输出/错误流
文件系统
默认不支持或需重写底层文件操作函数
由操作系统管理和提供完整的虚拟文件系统
库的规模
高度可配置,提供标准库和微型库(Microlib)
功能完整,体积较大
调试支持
支持“半主机模式”(Semihosting)
使用标准控制台进行调试输出

核心区别概览


详细解析

1. 底层I/O实现与“重定向” (Retargeting)

这是两者之间最显著的区别。在标准的C环境中,printf 默认将数据输出到操作系统的标准输出流(通常是控制台或终端窗口)。这个过程由操作系统和标准库自动处理,开发者无需关心其底层细节。
然而,在嵌入式的“裸机”或实时操作系统(RTOS)环境中,并没有预先存在的显示器或控制台。printf 函数如何输出字符?是将它发送到 UART (串口)USB CDC,还是写入 LCD 显示屏?ARM C库的设计者无法预知最终的硬件配置。
因此,MDK-ARM中的stdio.h及其关联的C库采用了一种“重定向”机制。它定义了一些底层的弱函数(weak functions),例如 fputc。开发者必须在自己的代码中重新实现这些函数,将标准I/O流“重定向”到具体的物理外设。
示例:重定向 printf 到 UART
为了让printf通过UART发送数据,开发者通常需要:
  1. 初始化微控制器的UART外设。
  1. 实现 fputc 函数,使其将接收到的单个字符通过UART发送出去。
通过这种方式,所有调用printf的地方最终都会通过你实现的fputc,将数据流引向UART。而标准的C库则不需要也无法进行这种操作,因为它依赖于操作系统提供的服务。

2. 半主机模式 (Semihosting)

为了在没有物理输出设备(如串口)的早期开发阶段方便调试,ARM提供了一种名为“半主机”的机制。这允许嵌入式设备上的代码通过调试探针(如ULINK、J-Link)与主机(PC)进行I/O通信。
当在MDK-ARM中使用printf等函数时,如果开启了半主机模式,这些I/O请求会被暂停,并通过调试接口发送到主机的调试器窗口(例如Keil MDK的Debug Viewer)。这是一种强大的调试功能,但运行速度较慢,且依赖于调试探针,不适用于最终发布的产品。
普通C环境中的stdio.h则完全没有半主机的概念。

3. C库的多样性与微型库 (Microlib)

MDK-ARM为适应不同资源级别的微控制器,提供了多种C库实现:
  • 标准ARM C库 (Standard ARM C library):功能相对完整,遵循C99标准,但体积较大。
  • 微型C库 (Microlib):一个为深度嵌入式系统优化的、高度精简的C库。它不完全遵循所有C标准,例如,printf可能不支持浮点数格式化,以极大地减小代码体积。
开发者可以根据项目需求(如Flash和RAM的限制)选择使用哪种库。stdio.h虽然是同一个头文件,但其背后的实现会根据所选的库而变化。
标准C环境通常只提供一个功能完备的、符合标准的库(如glibc on Linux),不会特别为资源极度受限的场景提供替代选项。

4. 文件系统支持

标准C环境中的stdio.h提供了丰富的本地文件操作函数,如fopenfwritefread等,这些都无缝地运行在操作系统的文件系统之上。
在MDK-ARM中,虽然stdio.h也声明了这些函数,但默认情况下它们是无法工作的,因为嵌入式设备上通常没有现成的文件系统。要使用这些函数,开发者必须:
  • 在硬件上集成存储介质,如SD卡或Flash芯片。
  • 移植或编写一个文件系统(如FatFs)。
  • 重定向文件操作的底层函数,将标准库的文件API与你的文件系统实现连接起来。

结论

总而言之,MDK\ARM\ARMCC\include目录下的stdio.h是为嵌入式系统量身定做的,它提供了一套标准的接口,但将底层的具体实现交给了开发者,使其能够灵活地将标准I/O适配到各种各样的硬件上。而普通C调用的stdio.h则是一个“开箱即用”的版本,它构建于功能完善的操作系统之上,为开发者隐藏了所有底层硬件的复杂性。
理解这两者的区别对于从通用软件开发转向嵌入式开发的工程师至关重要,因为这直接关系到如何在资源受限的环境中成功地实现最基本的输入输出功能。

gitee 提交和克隆远程仓库时 用 https 和 ssh 有什么区别?

这是两种远程传输协议:
使用https 协议克隆 对初学者来说会比较方便 ,复制 https url 然后到 git Bash 里面直接用 clone 命令克隆到本地就好了,但是每次 fetch 和 push 代码都需要输入账号和密码,这也是https 协议的麻烦之处。
而使用SSH 协议克隆需要在克隆之前先配置和添加好 SSH key,因此,如果用户想要使用 SSH url 克隆的话,必须是这个仓库的拥有者。
另外,使用 SSH 协议 默认是每次 fetch 和 push 代码都不需要输入账号和密码。
在使用命令上,两种协议使用的命令没有过多差异。git clone、git pull、git push 等命令都是一样的。
SSH 为 Secure Shell 的缩写,由 IETF 的网络小组(Network Working Group)所制定;SSH 为建立在应用层基础上的安全协议。SSH 是较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 最初是 UNIX 系统上的一个程序,后来又迅速扩展到其他操作平台。SSH 在正确使用时可弥补网络中的漏洞。SSH 客户端适用于多种平台。几乎所有 UNIX 平台—包括 HP-UXLinuxAIXSolaris、Digital UNIXIrix,以及其他平台,都可运行 SSH。
传统的网络服务程序,如:ftp、pop 和 telnet 在本质上都是不安全的,因为它们在网络上用明文传送口令和数据,别有用心的人非常容易就可以截获这些口令和数据。而且,这些服务程序的安全验证方式也是有其弱点的, 就是很容易受到“中间人”(man-in-the-middle)这种方式的攻击。所谓“中间人”的攻击方式, 就是“中间人”冒充真正的服务器接收你传给服务器的数据,然后再冒充你把数据传给真正的服务器。服务器和你之间的数据传送被“中间人”一转手做了手脚之后,就会出现很严重的问题。通过使用 SSH,你可以把所有传输的数据进行加密,这样"中间人"这种攻击方式就不可能实现了,而且也能够防止 DNS 欺骗和 IP 欺骗。使用 SSH,还有一个额外的好处就是传输的数据是经过压缩的,所以可以加快传输的速度
SSH 提供 基于密匙的安全验证
需要依靠密匙,也就是你必须为自己创建一对密匙,并把公用密匙放在需要访问的服务器上。如果你要连接到 SSH 服务器上,客户端软件就会向服务器发出请求,请求用你的密匙进行安全验证。服务器收到请求之后,先在该服务器上你的主目录下寻找你的公用密匙,然后把它和你发送过来的公用密匙进行比较。如果两个密匙一致,服务器就用公用密匙加密“质询”(challenge)并把它发送给客户端软件。客户端软件收到“质询”之后就可以用你的私人密匙解密再把它发送给服务器。

怎么排查栈错误(栈溢出)?

  • 在 FreeRTOS 里面:
方法一:在程序运行过程中,通过操作系统 API 去打印当前可用的最小栈空间,来观察栈的使用情况
方法二:通过进入特定语句的断点,观察当前语句时栈指针的位置,来查看栈的情况。
一般情况下,会给栈留百分之二十的余量(视项目具体情况而定)
方法三:启动栈溢出检测函数功能
https://blog.csdn.net/HuangChen666/article/details/131911863
  • 在裸机里面:
直接实时打印 SP 指针,
方法一:在需要观察栈指针的位置连上仿真器打断点(一般在函数调用的层次最多的地方),然后观察 register 窗口中 MSP 的值(register 窗口中的值不会即时刷新,当打断点停止运行时才会更新)。(注:需要注意当前程序打断点是否会造成具有破坏性的后果)
方法二:使用 core_m3.c 中的函数__ASM uint32_t __get_MSP(void)获取当前的 MSP 寄存器的栈地址,函数的返回值即为当前栈指针。函数中第一句汇编 mrs r0, msp 表示将 msp 寄存器值装载进 R0 寄存器(这里用作保存返回值的寄存器),bx lr 表示函数返回。
原文链接:https://blog.csdn.net/baidu_36743954/article/details/107326024

配置时钟时RCC和SYS的区别?

notion image
notion image