干货 | 如何榨干SysTick的每一滴汁水?

嵌入式大杂烩 2021-07-01 22:40

关注「嵌入式大杂烩」,星标公众号,一起进步!

来源:裸机思维


【说在前面的话】


相信很多人都遇到过这样的情况:在一个Cortex-M嵌入式应用中要实现一个精确的毫秒级延时并不困难——如果你有RTOS,在任务中使用诸如 os_sleep(<休眠时间>) 之类的函数就可以轻松实现;如果你是裸机,也可以使用每个Cortex-M芯片都默认携带的SysTick来实现一个,甚至Arm官方的CMSIS都提供了现成的API,即SysTick_Config(<中断间隔的时钟周期数>)
static volatile uint32_t s_wMSCounter = 0;
extern uint32_t SystemCoreClock;/*! \brief initialise platform before main() */__attribute__((constructor(101)))void platform_init(void){ SystemCoreClockUpdate(); /* Generate interrupt each 1 ms */ SysTick_Config(SystemCoreClock / 1000); }
__attribute__((weak))void systimer_1ms_handler(void){    /* default systimer 1ms hander      * you can override it by implement a non-weak version     */}
void SysTick_Handler (void) { if (s_wMSCounter) { s_wMSCounter--; }
systimer_1ms_handler();}
void delay_ms(uint32_t wMillisecond){ s_wMSCounter = wMillisecond; while( s_wMSCounter > 0 );}

上述代码非常典型,唯一需要强调的是SystemCoreClock是一个定义在启动文件system_<芯片型号>.c 里的全局变量,负责保存当前处理器的工作频率——上面的平台初始化函数 platform_init() 就是借助这一变量把 SysTick 初始化为以“1ms为间隔产生中断”的。

如果要实现一个微秒级延时却并不那么一帆风顺。首先,不同人关于实现方案就有不同的想法,比如:
  • 有的人习惯于直接用软件方法堆积NOP()来实现——这种方法所产生的延时效果“可能”容易受到编译器优化等级的影响——据说这也是很多人惧怕开启编译器的原因之一,因为一开优化,很多对时间敏感的硬件时序就因为延时函数的不稳定而一起变得不可捉摸;

extern uint32_t SystemCoreClock;
#ifndef DELAY_US_CALIBRATION/*! \brief 不要问我为啥是 8, 我也不知道,但在当前这个工程下,8貌似最准 *!        你如果不服,就自己测一个,然后定义这个宏…… *!        如果你头铁改了工程的优化等级,请也无比亲自测一下……具体怎么 *!        测,我也不知道。如果你也怕麻烦,就不要改优化等级。 */# define DELAY_US_CALIBRATION 8#endif
void delay_us(uint32_t wUS){    //! calcluate how many cycles required for 1us    uint32_t wCyclesPerUS = SystemCoreClock / 1000000ul;        /*! subtract some cycles from wCyclesPerUS based on the      *! experience or actual measurement in current optimisation     */    wCyclesPerUS -= DELAY_US_CALIBRATION;
    for (int i = 0; i < wUS; i++) {        for (j = 0; j < wCyclesPerUS; j++) {         __NOP();        }    }}
  • 有的人提倡使用定时器来实现精确延时,这一方案显然不太惧怕编译器优化的“血腥巨斧”。想法是没错的,但如果要保证这样写出来的延时库有一定的可移植性,就需要保证 delay_us() 函数实现所依赖的硬件定时器是“通用的”和“普遍存在”的——符合这一要求的第一选择是SysTick——然而既然SysTick已经被 delay_ms() 占用了,又如何能抽的开身呢?


既然 SysTick 被占用了,那有没有别的符合要求的硬件呢?如果不算Cortex-M0/M0+的话,从某种程度上说还真有——DWT。这是一个系统外设,专门用来为Cortex-M3及其以上芯片提供调试和追踪的硬件辅助功能。在【裸机思维】往期转载的文章中,就有使用DWT实现延时的内容。这个方法好是好,但缺点也是非常突出的:
  • DWT 根本就不是设计给用户用的,它是Cortex-M处理器预留给上位机调试软件(例如MDK)进行调试和追踪的。换句话说,上位机调试软件觉得这是自己的私人财产,从来没想过用户会去使用它——这就导致调试过程中,IDE会按照自己的意思随意修改它的配置——啥时候会改呢?这要看IDE的心情。如果你的程序依赖了DWT进行延时,那么调试的时候,IDE的一个无心之举可能就会毁了你的时序——这一知识点非常容易忽略掉,从而导致很多人遇到调试的时候,系统随机性的功能不正常的坑,从而浪费大把的时间,往往还想不到是DWT导致的——说这一方法是天坑可能一点也不为过

  • DWT 不是所有 Cortex-M 芯片都有……(Cortex-M0/M0+就没有)


既然 SysTick 被占用、DWT 又是天坑,是不是意味着我们就只能使用芯片的普通定时器了?——这每个厂家都不一样……每个应用对定时器的使用情况也都不同,那我还怎么做通用的延时库啊?

别急,今天我们就来介绍一种在完全不影响 SysTick 已有功能的前提下,继续把它榨干——提供更多功能的方法。为了避免误解,我把这种方法的目标需求列举如下:
  • 提供一个精确的 delay_us() 函数;

  • 提供一个精确测量任意代码块所实际占用系统周期数的方法;

  • 实现一个记录从进入 main() 函数以来总共经历了多少个时钟周期(且在合理的时间范围内不会溢出)的计数器(时间戳);

  • 用户已有的 SysTick 功能不能受到干扰

    • 比如用户使用 SysTick 作为RTOS的基准时钟(非Tickless模式);

    • 比如用户使用 SysTick 作为普通的毫秒级延时(就像前面例子代码所展示的那样);

  • 用户不需要修改自己任何已有的 SysTick 代码



【部署 perf_counter 库】


要实现上述功能,可以直接借助一个叫做 perf_counter 的库,这是我基于这几年在代码性能分析中总结出来的,我已经把它放在 github 上进行开源,其地址为:https://github.com/GorgonMeducer/perf_counter

这个库目前支持 Arm Compiler 5(armcc) 和 Arm Compiler 6(armclang)。它不仅提供了源代码,还提供了编译好的 library (.lib)可供全系列Cortex-M处理器使用。

第一步,下载最新的release:


解压缩后可以看到如下的内容:

如果只是普通的使用,直接拷贝 lib 目录到你的工程即可。


第二步,将库加入到已有的 MDK 工程中:



别忘记在工程的头文件搜寻路劲中包含 perf_counter.h 所在文件夹,例如(具体位置根据你工程的情况而定,不要死脑经):



第三步:编译并调整一些工程选项

如果你编译后很顺利,则请跳过下面的内容,快进到 0 error 0 warning的图片之后。



好,下面让我们来谈谈你可能遇到的问题,以及对应的解决方案:


问题一:提示找不到 $Super$$SysTick_Handler

.\Out\example.axf: Error: L6218E: Undefined symbol $Super$$SysTick_Handler (referred from systick_wrapper_ual.o).Not enough information to list image symbols.Not enough information to list load addresses in the image map.Finished: 2 information, 0 warning and 1 error messages.".\Out\example.axf" - 1 Error(s), 0 Warning(s).


perf_counter 库是一个“附加型”库——它假设你自己已经实现了一个SysTick的中断处理程序,并开启了中断模式——如果你没有,直接加一个空的就好了:

void SysTick_Handler (void) {}

好,问题解决。什么?你的工程也根本没有用SysTick?好办,请在进入main后调用函数init_cycle_counter() 并传递false,例如:

int main(void){    ...    init_cycle_counter(false);    ...}

这样做的目的是告诉 perf_counter:“请自己玩的开心”。


问题二:wchar和enum的尺寸不兼容:


需要强调的是,perf_counter.lib 库在编译的时候,开启了 Short enums/wchar(分别对应命令行的 -fshort-enums -fshort-wchar)。这么做其实没什么特别的原因,但如果你的工程使用了不同的配置,例如:


下图的工程配置中,没有勾选 "Short enums/wchar"


你一定会看到这样的编译错误:

.\Out\example.axf: Error: L6242E: Cannot link object perf_counter.o as its attributes are incompatible with the image attributes.   ... wchart-16 clashes with wchart-32.   ... packed-enum clashes with enum_is_int.


既然知道了原因,解决方法就很简单,要么在工程配置中勾选上这一选项;要么使用源代码编译(不使用lib):



也就是图中所示的:perf_counter.c systick_wrapper_ual.s



    perf_counter.c 依赖了 CMSIS,所以确保你的工程中加入了对CMSIS的支持——推荐的是使用MDK自带的 CMSIS,在RTE配置界面中勾选:



如果你使用的是工程自带的CMSIS(很多STM32工程就是这样),请确保你的CMSIS 是较新的版本(判断标准就是是否带有 cmsis_compiler.h)。


此外,这里的 systick_wrapper_ual.s 是一个汇编源程序,使用的是Arm的老语法(Unified Assembly Language),如果你的工程使用的是 Arm Compiler 5(armcc),这里就没什么需要特别注意的了;如果你的工程使用的是 Arm Compiler 6(armclang),则你需要检查工程配置,以确保MDK能正确的选择对应的Assembler


注意这里的 Assembler Option,根据你MDK版本的不同,它可能有以下几个有效选项:
  • armclang(Auto Select)——我吐血推荐选这个

  • armclang(GNU Syntax)—— 这个意思就是使用 GNU的汇编语法,显然不能选它;

  • armclang(Arm Syntax)——这是最新MDK(从5.32开始)才有的选项,选了也行;

  • armasm(Arm Syntax)——这就是 Arm Compiler 5里一直使用的老汇编器,选他当然兼容性最好。


做好了以上两个准备工作,编译应该就很顺利了。是不是觉得有点头大?头大就用 .lib 啊……完全不用经历这些痛苦






至此,我们完成了 perf_counter 库在工程中的部署。那么它带给我们哪些功能呢?


【SysTick第一吃:微秒级精确延时】



#include "perf_counter.h"
...delay_us(30);    //!< delay 30 us...


再也不用担心编译器优化导致延时不准啦!!!

再也不担心库不通用啦!!!

再也不用担心芯片不支持DWT啦!!!!!!

再也不用担心调试/追踪会干扰DWT啦!!!!





【SysTick第二吃:精确测量代码的时钟周期】


perf_counter.h 提供了两个函数,用于精确测量任意代码片段所消耗的CPU时钟周期数(不是us数哦):
extern void start_cycle_counter(void);extern int32_t stop_cycle_counter(void);
它们的使用非常简单直接,例如:
start_cycle_counter();//! 测量 打印  "Hello World\r\n" 究竟用了多少个时钟周期printf("Hello World! \r\n");int32_t iCycleUsed = stop_cycle_counter(void);printf("Cycle Used: %d", iCycleUsed);

当然,如果你的工程环境允许你用printf的话,还可以用 perf_counter.h 自带的宏将上述代码简化一下:

//! the demo of __cycleof__()__cycleof__() {    printf("Hello World\r\n");}

其运行结果为:

(以上结果为FVP仿真结果,CPU周期数值不可以做参考)


我们甚至还可以添加一点注释性的字符串,帮助我们区分测试的范围:

//! the demo of __cycleof__()__cycleof__("Print string") {    printf("Hello World\r\n");}

我们看到,传递给__cycleof__的提示字符串"Print string"被添加到了"total cycle count:..." 的前面,一目了然。


实际上,start_cycle_counter()stop_cycle_counter() 的组合还可以用来测量中断处理程序实际使用的系统周期数——读过我【实时性迷思】系列文章的小伙伴,一定知道测量“事件处理函数所需时间”的意义:

volatile int32_t g_nMaxHandlingCycles = 0;
void USART0_RX_Handler(void){    start_cycle_counter();        //! 你的USART0 接收中断处理程序实际内容    ...        int32_t nCycles = stop_cycle_counter();    g_nMaxHandlingCycles = MAX(nCycles, g_nMaxHandlingCycles);}


从此一举告别“拍脑袋凭感觉”说中断处理时间要多长的旧世界。


此外,start_cycle_counter() 和 stop_cycle_counter() 还支持类似体育老师所使用的秒表的功能,即,起跑后、可以分别记录每一个学生所用的时间。具体表现为:

int32_t nCycles = 0;start_cycle_counter();           //!< 开始总计时...nCycles = stop_cycle_counter();  //!< 第一次获取从开始以来的时间...nCycles = stop_cycle_counter();  //!< 第二次获取从开始以来的时间...nCycles = stop_cycle_counter();  //!< 第三次获取从开始以来的时间...

具体什么情况下要用到这样的方式就见仁见智了,这里就不再继续展开。


最后,需要强调一下,虽然 start_cycle_counter()stop_cycle_counter()startstop 的字样,但这只是逻辑上的,并不会真正的干扰 SysTick 的功能(也就是不会开启或者关闭 SysTick)。这也是这个库敢于声称自己不会影响用户已有的 SysTick 功能的原因。


【SysTick第三吃:系统时间戳】


阅读到这里,聪明的你一定已经发现了:无论是 perf_counter(performance counter)库名的明示,还是 start_cycle_counter()stop_cycle_counter() 的强大功能,都暗示其实这个库应该不是专门用来提供微秒延时函数 delay_us() 的,实际上,只要你稍微看一眼源代码就会发现上述猜想完全没错—— delay_us() 其实才是附赠的:
void delay_us(int32_t iUs){    iUs *= SystemCoreClock / 1000000ul;        start_cycle_counter();    while(stop_cycle_counter() < iUs);}


看到真相的你,有没有意识到,在 start_cycle_counter()stop_cycle_counter() 之间不能调用 delay_us() 呢?


既然 delay_us() 都是“cycle counter”送的,还有啥别的功能是附赠的么?——还真有:系统时间戳。想象一下,既然 start_cycle_counter()stop_cycle_counter() 的组合可以获得从开始以来的时间,那么如果我在进入main()之前就执行 start_cycle_counter() ,然后在需要的时候调用 stop_cycle_counter() 是不是就可以获取“从main()开始已经执行了多少个周期”的系统时间戳呢?
Bingo!答对了,原理上就是这样,只不过实际上,为了保留 start_cycle_counter() stop_cycle_counter() 给用户使用,per_counter库就自己独立实现了对应的逻辑——用户可以通过调用函数 clock() 来获取这一信息:
#ifdef __PERF_CNT_USE_LONG_CLOCK____attribute__((nothrow)) extern int64_t clock(void);#endif
熟悉系统库 <time.h>朋友可能会立即产生疑问,真正的定义不应该是这样的么:
extern _ARMABI clock_t clock(void);

clock_t 在 Cortex-M环境下定义如下:

typedef unsigned int clock_t;    /* cpu time type */

为什么perf_counter.h 要采用不一样的定义呢?


说起来也简单:clock() 函数返回的是系统周期数,而不是什么以 us 或者 ms 为单位的时间——考虑到现在处理器频率动辄几百兆赫兹,有的甚至达到了1GHz(比如 NXP的RT系列),如果用 int32_t (哪怕用 uint32_t)也撑不了几秒钟。



假设系统频率为1GHz,使用 uint32_t 来计数,由于32bit整数取值范围是0~4G,因此,最多4秒就撑不住了……



那究竟多长才够呢?



当我们使用 int64_t 的时候,哪怕系统频率是 4GHz,2G 秒 ≈ 24855 天 ≈ 68年。虽然没有一万年那么久,不过多半一个嵌入式设备也没法用这么久(千年虫警告),但考虑到大部分Cortex-M嵌入式系统估计没有4GHz这么夸张,轻松跑个1000多年不溢出应该是没有问题的


既然我们铁了心要用 int64_t 来取代 clock_t 原本的 int32_t,怎么解决这里的冲突呢?——显然去修改系统头文件 <time.h>是不允许的


翻开Arm的隐藏宝典:AAPCS,我们发现以下的规则:

32位系统下,

  • 如果函数的返回值其大小不超过32bit,则保存在寄存器 r0中;

  • 如果函数的返回值其大小为64bit,则其低 32bit 保存在 r0中、高32bit保存在 r1中。


显然,当我们实现clock()函数时返回 int64_t的值与 返回 int32_t其实是兼容的——因为低32bit的内容实际上都是保存在 r0 里的,此时如果用户调用clock() 的时候:

  • 使用的是<time.h>里定义的函数原型,即  clock_t clock(void),则,当函数返回时,r1里保存的值会被无视,只有r0里的值被视作返回值;

  • 使用的是我们自己定义的函数原型,即 int64_t clock(void),则你可以获得完整的 int64_t 时间戳。


既然原理清楚了,再看 perf_counter.h 里面的定义,我们会发现clock()的函数原型被一个宏 __PERF_CNT_USE_LONG_CLOCK__ 保护着:

#ifdef __PERF_CNT_USE_LONG_CLOCK____attribute__((nothrow)) extern int64_t clock(void);#endif

这实际上是告诉我们,如果我们想获得 int64_t 时间戳时,只要在工程中定义宏  __PERF_CNT_USE_LONG_CLOCK__ 就可以了。



忙活了半天,有的小伙伴可能会疑惑了:饶了这么一大圈,clock() 究竟有啥用处呢?这玩法就多了,快一键三联~ 下次我们好好来说说。



【后记】


perf_counter(https://github.com/GorgonMeducer/perf_counter)是我在工作中总结和整理出的一个库,它的特点是在不干扰已有 SysTick 功能的前提下额外为我们提供系统周期测量的功能——并在这基础上衍生出了 delay_us() 和 系统时间戳的功能——正可谓一鸭三吃,把SysTick榨干到了极致。
perf_counter 库的原理其实很简单,但其中要处理的 corner case 确实很恼人,我也是历经一年多才真正想明白这里面的弯弯绕。后面如果阅读量不错的话,我会考虑专门出一篇介绍 perf_counter 原理的文章。其中,关于如何“不影响现有SysTick中断处理程序”的功能,已经在之前的文章《【嵌入式秘术】手把手教你如何劫持RTOS》中进行了详细介绍,有兴趣的小伙伴可以再回味回味。
在开源的过程中,为了简化用户的使用,我做了如下的优化:
  • 在 Arm Compiler 5(armcc)和 Arm Compiler 6中,不需要用户手工对库进行初始化——库会在进入main()之前“自己做”;

  • Lib中的perf_counter.lib适用于包含Cortex-M0在内的全系列Cortex-M处理器,做到全覆盖;

  • perf_counter.h 几乎不依赖 <stdint.h><stdbool.h>之外的库。使用.lib进行部署,非常简洁方便。



如果你要用 safe_atom_code(),则需要 __disable_irq() 和 __set_PRIMASK() 的定义,一般Cortex-M工程都有。这些定义是由 CMSIS提供的。一般来说,普通的 perf_counter 功能并不需要涉及任何这些内容。



perf_counter库的使用当然也存在限制,重要的事情在最后说:
  • 如果你原本的 RTOS 使用了 SysTick并开启了Tickless模式,perf_counter虽然不会干扰原有的 SysTick功能,但自己的计时功能却会受到 Tickless模式的干扰;

  • perf_counter库假设你原本的SysTick应用会保持一个固定的定时周期——也就是 LOAD寄存器的内容是固定的、不会随着程序的执行而经常变化。其实RTOStickless模式会干扰perf_counter的计数可靠性也是这个原因



一般来说,大部分RTOS和普通的周期性定时功能都不会经常动态的去改变SysTick的计数周期,所以不必太担心。



原创不易,

如果你喜欢我的思维、觉得我的文章对你有所启发,

请务必 “点赞、收藏、转发” 三连,这对我很重要!谢谢!


欢迎订阅 裸机思维


猜你喜欢:

Linux GNU C 与 ANSI C 有什么区别?


长文 | 有C基础,如何快速过度到C++?


分享一篇很好的C指针文章,查缺补漏!


在公众号聊天界面回复1024,可获取嵌入式资源;回复 ,可查看文章汇总。

嵌入式大杂烩 专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论
  • 食物浪费已成为全球亟待解决的严峻挑战,并对环境和经济造成了重大影响。最新统计数据显示,全球高达三分之一的粮食在生产过程中损失或被无谓浪费,这不仅导致了资源消耗,还加剧了温室气体排放,并带来了巨大经济损失。全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,艾迈斯欧司朗基于AS7341多光谱传感器开发的创新应用来解决食物浪费这一全球性难题。其多光谱传感解决方案为农业与食品行业带来深远变革,该技术通过精确判定最佳收获时机,提升质量控制水平,并在整个供应链中有效减少浪费。 在2024
    艾迈斯欧司朗 2025-01-14 18:45 72浏览
  • 根据Global Info Research(环洋市场咨询)项目团队最新调研,预计2030年全球无人机电池和电源产值达到2834百万美元,2024-2030年期间年复合增长率CAGR为10.1%。 无人机电池是为无人机提供动力并使其飞行的关键。无人机使用的电池类型因无人机的大小和型号而异。一些常见的无人机电池类型包括锂聚合物(LiPo)电池、锂离子电池和镍氢(NiMH)电池。锂聚合物电池是最常用的无人机电池类型,因为其能量密度高、设计轻巧。这些电池以输出功率大、飞行时间长而著称。不过,它们需要
    GIRtina 2025-01-13 10:49 204浏览
  • 数字隔离芯片是现代电气工程师在进行电路设计时所必须考虑的一种电子元件,主要用于保护低压控制电路中敏感电子设备的稳定运行与操作人员的人身安全。其不仅能隔离两个或多个高低压回路之间的电气联系,还能防止漏电流、共模噪声与浪涌等干扰信号的传播,有效增强电路间信号传输的抗干扰能力,同时提升电子系统的电磁兼容性与通信稳定性。容耦隔离芯片的典型应用原理图值得一提的是,在电子电路中引入隔离措施会带来传输延迟、功耗增加、成本增加与尺寸增加等问题,而数字隔离芯片的目标就是尽可能消除这些不利影响,同时满足安全法规的要
    华普微HOPERF 2025-01-15 09:48 96浏览
  • 01. 什么是过程能力分析?过程能力研究利用生产过程中初始一批产品的数据,预测制造过程是否能够稳定地生产符合规格的产品。可以把它想象成一种预测。通过历史数据的分析,推断未来是否可以依赖该工艺持续生产高质量产品。客户可能会要求将过程能力研究作为生产件批准程序 (PPAP) 的一部分。这是为了确保制造过程能够持续稳定地生产合格的产品。02. 基本概念在定义制造过程时,目标是确保生产的零件符合上下规格限 (USL 和 LSL)。过程能力衡量制造过程能多大程度上稳定地生产符合规格的产品。核心概念很简单:
    优思学院 2025-01-12 15:43 540浏览
  • PNT、GNSS、GPS均是卫星定位和导航相关领域中的常见缩写词,他们经常会被用到,且在很多情况下会被等同使用或替换使用。我们会把定位导航功能测试叫做PNT性能测试,也会叫做GNSS性能测试。我们会把定位导航终端叫做GNSS模块,也会叫做GPS模块。但是实际上他们之间是有一些重要的区别。伴随着技术发展与越发深入,我们有必要对这三个词汇做以清晰的区分。一、什么是GPS?GPS是Global Positioning System(全球定位系统)的缩写,它是美国建立的全球卫星定位导航系统,是GNSS概
    德思特测试测量 2025-01-13 15:42 512浏览
  • 新年伊始,又到了对去年做总结,对今年做展望的时刻 不知道你在2024年初立的Flag都实现了吗? 2025年对自己又有什么新的期待呢? 2024年注定是不平凡的一年, 一年里我测评了50余块开发板, 写出了很多科普文章, 从一个小小的工作室成长为科工公司。 展望2025年, 中国香河英茂科工, 会继续深耕于,具身机器人、飞行器、物联网等方面的研发, 我觉得,要向未来学习未来, 未来是什么? 是掌握在孩子们生活中的发现,和精历, 把最好的技术带给孩子,
    丙丁先生 2025-01-11 11:35 466浏览
  • ARMv8-A是ARM公司为满足新需求而重新设计的一个架构,是近20年来ARM架构变动最大的一次。以下是对ARMv8-A的详细介绍: 1. 背景介绍    ARM公司最初并未涉足PC市场,其产品主要针对功耗敏感的移动设备。     随着技术的发展和市场需求的变化,ARM开始扩展到企业设备、服务器等领域,这要求其架构能够支持更大的内存和更复杂的计算任务。 2. 架构特点    ARMv8-A引入了Execution State(执行状
    丙丁先生 2025-01-12 10:30 480浏览
  •   在信号处理过程中,由于信号的时域截断会导致频谱扩展泄露现象。那么导致频谱泄露发生的根本原因是什么?又该采取什么样的改善方法。本文以ADC性能指标的测试场景为例,探讨了对ADC的输出结果进行非周期截断所带来的影响及问题总结。 两个点   为了更好的分析或处理信号,实际应用时需要从频域而非时域的角度观察原信号。但物理意义上只能直接获取信号的时域信息,为了得到信号的频域信息需要利用傅里叶变换这个工具计算出原信号的频谱函数。但对于计算机来说实现这种计算需要面对两个问题: 1.
    TIAN301 2025-01-14 14:15 118浏览
  • 流量传感器是实现对燃气、废气、生活用水、污水、冷却液、石油等各种流体流量精准计量的关键手段。但随着工业自动化、数字化、智能化与低碳化进程的不断加速,采用传统机械式检测方式的流量传感器已不能满足当代流体计量行业对于测量精度、测量范围、使用寿命与维护成本等方面的精细需求。流量传感器的应用场景(部分)超声波流量传感器,是一种利用超声波技术测量流体流量的新型传感器,其主要通过发射超声波信号并接收反射回来的信号,根据超声波在流体中传播的时间、幅度或相位变化等参数,间接计算流体的流量,具有非侵入式测量、高精
    华普微HOPERF 2025-01-13 14:18 502浏览
  • 随着数字化的不断推进,LED显示屏行业对4K、8K等超高清画质的需求日益提升。与此同时,Mini及Micro LED技术的日益成熟,推动了间距小于1.2 Pitch的Mini、Micro LED显示屏的快速发展。这类显示屏不仅画质卓越,而且尺寸适中,通常在110至1000英寸之间,非常适合应用于电影院、监控中心、大型会议、以及电影拍摄等多种室内场景。鉴于室内LED显示屏与用户距离较近,因此对于噪音控制、体积小型化、冗余备份能力及电气安全性的要求尤为严格。为满足这一市场需求,开关电源技术推出了专为
    晶台光耦 2025-01-13 10:42 519浏览
  • 随着通信技术的迅速发展,现代通信设备需要更高效、可靠且紧凑的解决方案来应对日益复杂的系统。中国自主研发和制造的国产接口芯片,正逐渐成为通信设备(从5G基站到工业通信模块)中的重要基石。这些芯片凭借卓越性能、成本效益及灵活性,满足了现代通信基础设施的多样化需求。 1. 接口芯片在通信设备中的关键作用接口芯片作为数据交互的桥梁,是通信设备中不可或缺的核心组件。它们在设备内的各种子系统之间实现无缝数据传输,支持高速数据交换、协议转换和信号调节等功能。无论是5G基站中的数据处理,还是物联网网关
    克里雅半导体科技 2025-01-10 16:20 451浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦