STM32 TIM(一)定时中断

news2024/11/16 18:07:48

STM32 TIM(一)定时中断

定一个时间,然后让定时器每隔这个时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,比如你要做个时钟、秒表,或者使用一些程序算法的时候,都需要用到定时中断的这个功能。

TIM简介

  • TIM(Timer)定时器

  • 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断

    • 定时器就是一个计数器,当这个计数器的输入是一个准确可靠的基准时钟的时候,那在对这个基准时钟进行计数的过程,实际上就是计时的过程。
      • 比如在STM32中,定时器的基准时钟一般都是主频72MHz,比如我对72MHz计72个数,所记时间就是72*1/72000000=1us;如果计72000个数,那就是72000*1/72000000=1ms。
  • 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时

    • 计数器就是用来执行技术定时的一个寄存器,每来一个时钟,计数器加1。
    • 预分频器可以对计数器的时钟进行分频,让计数更加灵活。
    • 自动重装寄存器就是计数的目标值,就是想要计多少个时钟申请中断。
    • 计数器、预分频器、自动重装寄存器构成了定时器最核心的部分,我们把这一块电路称为时基单元,均为16位寄存器,216=65536,如果预分频器设置最大,自动重装也设置最大,那定时器的最大的定时时间就1/72000000*65535*65535=59.65s,接近一分钟。

    如果想这里的最大定时时间仍满足不了需求,STM32的定时器还支持级联的模式,也就是一个定时器的输出,当作另一个定时器的输入,这样,最大定时时间就是1/72000000*65536*65536*65536*65536 这个时间大概是8000多年,如果还嫌短,那就再级联一个定时器,最大定时时间还会延长65536*65536倍,这个时间大概是34万亿年。

  • 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

  • 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型

定时器类型

  • 这三种定时器是由高级到低级向下兼容的
    • 高级定时器拥有通用定时器的全部功能
    • 通用定时器拥有基本定时器的全部功能
类型编号总线功能
高级定时器TIM1、TIM8APB2拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器TIM2、TIM3、TIM4、TIM5APB1拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器TIM6、TIM7APB1拥有定时中断、主模式触发DAC的功能
  • STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
    • 1个高级定时器和3个通用定时器

高级定时器

  • 增加重复次数计数器,可以实现每隔几个计数周期,才发生一次更新事件和更新中断,相当于对输出的更新信号又做了一次分频,将最大计数时间延长了65536倍。原来的结构是每个计数周期完成后都会发生更新。
  • DTG(Dead Time Generate)死区生成电路,右边的输出引脚由一个变为了两个互补的输出,可以输出一对互补的PWM波,用于驱动三相无刷电机。
    • 四轴飞行器、电动车的后轮、电钻等,都可能用的是三相无刷电机,三相无刷电机的驱动电路一般需要3个桥臂,每隔桥臂2个大功率开关来控制,总共需要6个大功率开关管来控制,所以这里输出PWM引脚的前三路就变为了互补的输出,而地思路却没什么变化,因为三相无刷电机只需要三路就行了。为了防止互补输出的PWM驱动桥臂时,在开关切换的瞬间,由于器件的不理想,造成短暂的直通现象,所以前面这里就加上了死区生成电路,在开关切换的瞬间,产生一定时长的死区,让桥臂的上下管全都关断,防止直通现象。
  • 刹车输入,为电机驱动提供安全保障,如果外部引脚BKIN(Break IN)产生了刹车信号,或者内部时钟失效,产生了故障,那么控制电路就会自动切断电机的输出,防止意外的发生。

在这里插入图片描述

通用定时器

带黑色阴影的寄存器,都是有影子寄存器这样的缓冲机制的。

包括预分频器、自动重装载寄存器和捕获比较寄存器。

在这里插入图片描述

结构组成

  • 时基单元,由预分频器、计数器、自动重装载寄存器构成,预分频器对时钟进行预分频,计数器自增计数,当计数器计到自动重装值时,计数值清零同时产生更新中断和更新事件。
    • 对于通用定时器而言,计数器的计数模式就不止向上计数这一种了,通用定时器支持向上计数模式,向下计数模式和中央对齐模式。
      • 向上计数模式,计数器从0开始,向上自增,计到重装值,清零同时申请中断,然后开始下一轮,依次循环。
      • 向下计数模式,从重装值开始,向下自减,减到0之后,回到重装值同时申请中断,然后继续下一轮,依次循环。
      • 中央对齐模式,就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断,然后继续下一轮,依次循环。
  • 内外时钟源选择,对于基本定时器而言,基准计数时钟只能选择内部时钟,也就是系统频率72MHz,而对于通用定时器,时钟源可以选择内部的72MHz时钟,还可以选择外部时钟
    • TIMx_ETR引脚上的外部时钟。
      • 比如可以在TIM2_ETR(External)引脚(复用在了PA0)上接一个外部方波时钟,然后配置一下内部的极性、边沿检测和预分频器电路,再配置一下输入滤波电路,这些电路可以对外部时钟进行一定整形,因为是外部引脚的时钟,所以难免会有毛刺,所以需要对输入的波形进行滤波。
      • 滤波后的信号
        • 一路ETRF,进入触发控制器,可以直接选择作为时基单元的时钟,或者相对ETR时钟进行计数,把这个定时器当作计数器来使用,就可以配置这一路的电路,在STM32中,这一路也叫做“外部时钟模式2
        • 一路TRGI,主要是用作触发输入来使用的,可以触发定时器的从模式或者将触发输入作为外部时钟来使用,当TRGI当作外部时钟来使用的时候,这一路就叫做“外部时钟模式1”
  • 编码器接口,读取正交编码器的输出波形。
  • 主模式输出,可以把定时器内部的一些事件映射到TRGO引脚上,用于触发其他定时器、DAC或者ADC,比如将更新事件映射到TRGO,用于触发DAC。
  • 输出比较电路,总共有4个通道,分别对应CH1到CH4的引脚,可以用于输出PWM波形,驱动电机。
  • 输入捕获电路,总共有4个通道,分别对应CH1到CH4的引脚,可以用于测量输入方波的频率、占空比等。
  • 捕获/比较寄存器,由输入捕获和输出比较电路共用,因为输入捕获和输出比较不能同时使用,所以寄存器和引脚都共用。

外部时钟模式1

  • 外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1(输入捕获/输出比较)引脚的边沿、CH1引脚和CH2引脚(输入捕获/输出比较)的TI1FP1和TI2FP2
  • 扩大时钟输入的范围
  • 实现定时器的级联
  • 实现输入捕获,测频率
  • 通过TRGI的时钟

    • ETR引脚的信号
    • ITR信号,这一部分的时钟信号是来自其他定时器,主模式的输出TRGO可以通向其他定时器,通向其他定时器的时候,其实就接到了其他定时器的ITR引脚上来了,这个ITR0~ITR3分别来自其他4个定时器的TRGO输出。例如,由下图可知,TIM2的ITR0是接在了TIM1的TRGO上,ITR1接在了TIM8的TRGO上,ITR2接在了TIM3的TRGO上,ITR3接在了TIM4的TRGO上。
      • 通过这一路,我们可以实现定时器级联的功能。比如我们可以先初始化TIM3,然后使用主模式把它的更新事件映射到TRGO上,接着再初始化TIM2,这里选择ITR2,对应的就是TIM3的TRGO,然后再选择时钟为外部模式1,这样TIM3的更新事件就可以驱动TIM2的时基单元,也就实现了定时器的级联。
    • TI1F_ED(Edge 边沿),连接的是输入捕获单元的CH1引脚,也就是从CH1引脚获得时钟,通过这一路输入的时钟,检测边沿信号,上升沿和下降沿均有效。
    • TI1FP1和TI2FP2,通过输入滤波器和边沿检测器后的时钟。

    在这里插入图片描述

基本定时器

  • 图中向上的折线箭头UI,代表这里会产生中断信号,像这种计数值等于自动重装值产生的中断,一般叫做更新中断,这个更新中断之后会通往NVIC,再配置好NVIC的定时器通道,那定时器的更新中断就能得到CPU的响应了。

  • 向下的箭头U,代表的是会产生一个事件,这里对应的事件就叫做“更新事件”,更新事件不会触发中断,但可以触发内部其他电路的工作。

基本流程

  • 从基准时钟,到预分频器,再到计数器,计数器计数自增,同时不断地与自动重装载寄存器进行比较,计数值与自动重装值相等时,即更新时间到,这时会产生一个更新中断和更新事件,CPU响应更新中断,就完成了定时中断的任务。

在这里插入图片描述

基本结构

  • 预分频器、计数器和自动重装寄存器,构成了最基本的计数计时电路,将这一块电路称作时基单元。
  • 预分频器之前,连接的是基准计数时钟的输入,由于基本定时器只能选择内部时钟,所以可以直接认为,CK_PSC时钟线直接连到了内部时钟CK_INT上。
    • 内部时钟CK_INT的来源是RCC_TIMxCLK,这里的频率值一般都是系统的主频72MHz,所以通向时基单元的计数基准频率就是72MHz。
  • 预分频器,可以对72MHz的计数时钟进行预分频。
    • 比如这个寄存器写0,就是不分频,或者说1分频,输出频率=输入频率=72MHz。如果预分频器写1,就是2分频,输出频率=输入频率/2=36MHz,以此类推。预分频器的值和实际的分频系数相差了1,即实际分频系数=预分频器的值+1。
    • 16位寄存器,可以写的最大值为216-1=65536,也就是65536分频,对输入的基准频率提前进行一个分频的操作。
  • 计数器,可以对预分频后的计数时钟进行计数,计数时钟每来一个上升沿,计数器的值就+1。
    • 16位寄存器,里面的值可以从0一直加到65535,如果再加的话,计数器就会回到0重新开始。计数器的值在计时过程中会不断地自增运行。当自增运行到目标值时,产生中断,就完成了定时的任务。
  • 自动重装载寄存器,存储目标值
    • 16位寄存器,存储我们写入的计数目标,在运行的过程中,计数值不断自增,自动重装值是固定的,当计数值等于自动重装值时,也就是定时时间到了,就会产生一个中断信号,并且清零计数器。计数器自动开始下一次的计数计时。

主模式触发DAC

  • 主从触发模式,可以让内部的硬件在不受程序的控制下实现自动运行,可以减轻CPU的负担。

  • 正常情况下,如果想要每隔一段时间触发DAC,就要先设置一个定时器产生中断,每隔一段时间在中断程序中调用代码手动触发一次DAC转换,然后DAC输出,会使主程序处于频繁被中断的状态。会影响主程序的运行和其他中断的响应。

  • 使用主模式可以把定时器的更新事件,TRGO(Trigger Out)的位置,然后TRGO直接接到DAC的触发转换引脚上,这样定时器的更新就不需要通过中断来触发DAC转换了。仅需要把更新事件通过主模式映射到TRGO,然后TRGO就会直接去触发DAC了。整个过程不需要软件的参与,实现了硬件的自动化,这就是主模式的作用。

定时中断基本结构

  • PSC预分频器、CNT计数器、ARR自动重装载寄存器构成的时基单元。
  • 运行控制,表示控制寄存器的一些位,比如启动停止,向上和向下计数等等,操作这些寄存器就能控制时基单元的运行了。
  • 可选时钟源
    • RCC提供的内部时钟 (内部时钟模式)
    • ETR引脚提供的外部时钟 (外部时钟模式2)
    • 触发输入(TRGI)作为外部时钟 (外部时钟模式1)
      • ETR外部时钟
      • ITRx其他定时器
      • TIx输入捕获通道
    • 编码器模式
  • 产生更新中断,中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。
    • 中断输出控制,由于定时器模块很多地方都要申请中断,比如更新要申请中断,触发信号也要申请中断,输入捕获和输出比较匹配时也会申请中断,这些中断都要经过中断输出控制,如果需要这个中断,就允许,如果不要这个中断,就禁止。中断输出控制就是一个中断输出的允许位。

在这里插入图片描述

预分频器时序

基本定义

  • CK_PSC,表示预分频器的输入时钟。
  • CNT_EN,计数器使能,高电平计数器正常运行,低电平计数器停止。
  • CK_CNT,计数器时钟,既是预分频器的时钟输出,也是计数器的时钟输入。

基本流程

  • 开始时,计数器未使能,计数器时钟不运行。
  • 使能后,前半段,预分频器系数为1(1分频),计数器的时钟等于预分频器前的时钟,后半段,预分频器系数变为2(2分频),计数器的时钟就也变为预分频器前时钟的一半了。
  • 在计数器时钟的驱动下,下面的计数器寄存器也跟随时钟的上升沿不断自增,在计数值到达FC(ARR自动重装值)之后,计数值变为0。当计数值计到和重装值相等,并且下一个时钟来临时,计数值才清零。同时产生一个更新事件。

缓冲机制

  • 预分频寄存器实际上有两个,预分频控制寄存器只供读写用,并不直接决定分频系数。
  • 缓冲寄存器(也叫做影子寄存器),才是真正起作用的寄存器
    • 比如在某个时刻,把预分频寄存器由0改成1,如果在此时立刻改变时钟的分频系数,就会导致,在一个计数周期内,前半部分和后半部分的频率不一样,计数记到一半,计数频率突然就改变了,虽然一般不会有什么太大影响。
    • 缓冲寄存器,使得我们在计数计到一半的时候改变了分频值,但是这个变化并不会立刻生效,而是会等到本次计数周期结束时,产生了更新事件,预分频寄存器的值才会被传递到缓冲寄存器里面去,此时才会生效。所以,有了缓冲机制,即使我们在计数中途改变了预分频值,计数频率仍然会保持为原来的频率,直到本轮计数完成,在下一轮计数时,改变后的分频值才会起作用。
  • 预分频计数器内部实际上也是靠计数来完成分频的,当预分频值为0时,计数器就一直为0,直接输出原频率,当预分频值为1时,计数器就0、1、0、1这样计数,在回到0 的时候,输出一个脉冲,这样输出频率就是输入频率的2分频。

在这里插入图片描述

  • 计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)

计时器时序

基本定义

  • CK_INT 内部时钟72MHz
  • CNT_EN 时钟使能,高电平启动
  • CK_CNT 计数器时钟,这里分频系数为2,2分频

基本流程

  • 计数器在时钟的每个上升沿自增,当增到0036(自动重装值)的时候,发生溢出,再来一个上升沿,计数器清零,计数器溢出,产生一个更新事件脉冲。另外还会置一个更新中断标志位UIF,这个标志位只要置1了,就会去申请中断,中断响应后,需要在中断程序中手动清零该标志位。

在这里插入图片描述

  • 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR + 1)

计时器无预装时序

没有缓存寄存器的情况,通过设置ARPE位,就可以选择是否使用预装功能。

  • 计数器正在进行自增计数,比如突然更改了自动重装寄存器,由FF改成36,那计数值的目标值就由FF改成了36,所以这里计到36之后,就直接更新,开始下一轮计数。

在这里插入图片描述

计时器有预装时序

有缓存寄存器的情况,通过设置ARPE位,就可以选择是否使用预装功能。

  • 在计数的途中,突然把计数目标值由F5改成了36,自动加载影子寄存器在这里起真正作用,目前计数的目标还是计到F5,等计数器计到F5,产生更新事件的同时,要更改的36才被传递到影子寄存器,在下一个技术周期,这个更改的36才有效。引入影子寄存器的目的实际上是为了同步,就是让值的变化和更新事件同步发生,防止在运行途中更改造成错误。

在这个例子可以看出,如果这里不使用影子寄存器,F5改到36立刻生效,但此时计数值已经到F1,超过了36,F1只能增加,但它的目标值却还是36,比它还小,这样F1就只能一直加,一直加到FFFF,再回到0,再加到36,才能产生更新,这样就会造成一些小问题。

在这里插入图片描述

RCC时钟树

基本介绍

  • STM32用来产生和配置时钟,并且把配置好的时钟发送到各个外设的系统,时钟是所有外设运行的基础,所以时钟也是最先需要配置的东西。程序中主函数之前,还会执行一个SystemInit()函数,这个函数就是用来配置这个时钟树的。

时钟产生电路

  • SYSCLK 系统时钟 72MHz
  • 时钟产生电路,有四个振荡源
    • 内部的8MHz高速RC振荡器 (HSI 内部高速时钟)
    • 外部的4~16MHz高速石英晶体振荡器,也就是晶振(OSC),一般接8MHz (HSE 外部高速时钟)
    • 外部的32.768KHz低速晶振(OSC),一般为RTC提供时钟。(LSE 外部低速时钟)
    • 内部的40KHz低速RC振荡器,给看门狗提供时钟。(LSI 内部低速时钟)
    • HSI和HSE两个高速时钟,用于提供系统时钟,AHB、APB1、APB2的时钟都是来源于这两个高速时钟。

SystemInit()函数

  • 首先启动内部高速时钟(HSI),选择内部8MHz为系统时钟,暂时以内部8MHz的时钟运行。
  • 然后启动外部高速时钟(HSE),配置外部时钟不分频进入PLLXTPRE,之后进入PLL锁相环进行倍频,8MHz倍频9倍,得到72MHz,等到锁相环输出稳定后,选择锁相环输出为系统时钟,这样就把系统时钟由8MHz切换为了72MHz。
  • 如果外部晶振出问题了,可能会导致一个现象,程序的时钟大概慢了10倍,比如用定时器定了一个1S的事件,结果过了大概10s才进中断。如果外部晶振出问题了,系统时钟就无法切换到72MHz,那它就会以内部的8MHz运行,8MHz相比较72MHz,大概就慢了10倍。

CSS(Clock Secerity System),时钟安全系统,负责切换时钟,可以检测外部时钟的运行状态,一旦外部时钟失效,就会自动把外部时钟切换回内部时钟,保证系统时钟的运行,防止程序卡死造成事故。

时钟分配电路

  • 系统时钟72MHz进入AHB总线,AHB总线有个预分频器,在SystemInit()里配置的分频系数为1,那AHB的时钟就是72MHz。

    • 然后进入APB1总线,这里配置的分频系数为2,所以APB1总线的时钟为72MHz/2=36MHz。
      • 通向定时器2~7的时钟单开了一路,如果APB1预分频系数=1,则频率不变,否则频率×2,由于这里APB1配置分频系数为2,所以频率要×2,那么通向定时器2~7的时钟就又变成了72MHz。
    • APB2预分频系数设为1,APB2的时钟和AHB一样,都是72MHz。这里接在APB2上的高级定时器(TIM1和TIM8)也单开了一路如,果APB2预分频系数=1,则频率不变,否则频率×2。这里APB2预分频系数为1,所以频率不变。

    在这些时钟输出这里,都有一个与门进行输出控制,控制位写的是外部时钟使能,这就是我们在程序中写RCC_APB2/1PeriphClockCmd()作用的地方,打开时钟,就是在这个位置写1,让左边的时钟能够通过与门输出给外设。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1130314.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

06数据结构——图

6.2图的存储及基本操作 6.2.1邻接矩阵法 图的邻接矩阵存储结构定义如下: #define MaxVertexNUm 100 //顶点数目的最大值 typedef char VertexType; //顶点的数据类型 typedef int EdgeType; //带权图中边上权值的数据类型 ty…

webrtc-stream编译报错记录

磁盘空间不足错误 错误信息 677.2 fatal: cannot create directory at blink/web_tests/external/wpt: No space left on device说明:这个错误是由于本地在配置docker资源时所给磁盘空间太小导致,直接根据镜像大小合理分配资源大小即可 pushd和popd执…

Android Studio新功能-设备镜像Device mirroring-在电脑侧显示手机实时画面并可控制

下载最新的灰测版本-蜥蜴 成功运行到真机后,点击右侧Running Devices选项卡,再点击号 选中当前设备; 非常丝滑同步,在电脑侧也可以顺畅控制真机 该功能大大方便了我们视线保持在显示器上专注开发,并且便于与UI视觉进行…

【Rust日报】2023-10-22 Korvin - 一个 WASM 前端框架,比基线 vanillajs 实现快了 33%!...

Yazi v0.1.5 发布 - 有史以来最大的更新 Yazi - 💥 用 Rust 编写的基于异步 I/O 的快速终端文件管理器。 嘿伙计!我很高兴在这里宣布Yazi v0.1.5 发布了! 这是有史以来最大的更新,也是周期最长的更新。该版本带来了许多有意义的变…

【进程VS容器VS虚拟机】

进程 VS 容器 VS 虚拟机 如果站在技术实现原理的角度来看,其实容器更像进程,而非虚拟机。 但是如果我们讨论这门技术的应用场景、解决的问题、终端用户是如何使用的,就会发现容器跟虚拟机非常相似,它们解决的是同样的问题&#…

渗透测试工具-sqlmap

sqlmap是一个开源渗透测试的自动化工具,可以自动检测和利用SQL注入漏洞并接管数据库服务器。它配备了一个强大的检测引擎,许多用于终极渗透测试的利基功能,以及广泛的开关,包括数据库指纹识别、从数据库中获取数据、访问底层文件系…

悟空crm安装搭建 报错[0] RedisException in Redis.php line 56问题处理办法

相信很多朋友进行安装悟空crm的时候 提示错误: [0] RedisException in Redis.php line 56 Connection refused 不知道怎么样处理是吧~~~ $this->options array_merge($this->options, $options);}# redis 密码$password config(cache.password);if (!empty…

1024特别剪辑: 使用Python Turtle 库绘制一棵随机生成的树

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨ 🐻强烈推荐优质专栏: 🍔🍟🌯C的世界(持续更新中) 🐻推荐专栏1: 🍔🍟🌯C语言初阶 🐻推荐专栏2: 🍔…

【excel技巧】excel单元格内如何换行?

Excel表格,在制作完成之后,在输入数据的时候,总是会遇到内容长度太长导致无法全部显示或者破坏表格整体格式。几天分享4个单元格换行的方法给大家。 方法一: 首先我们先介绍一个,通过调整列宽的方式来达到显示全部内…

xcode The document “...“ could not be saved

Today when I tried to save a file on my project I get an error message saying: The document “nameOfFile.m” could not be saved. I tried reinstalling xcode but no luck. The file can be edited with other editors and I see the same behavior on all my project…

C# Enum.TryParse字符串既可以使用名称也可以使用数字值

var tryParse Enum.TryParse<LicenseType>("0", out var licenseType);以前没留意&#xff0c;以为将字符串装枚举的方法Enum.TryParse的字符串只能填名称&#xff0c;最近发现字符串是数字值也可以转换成相应的枚举

DevOps持续集成-Jenkins(4)

❤️作者简介&#xff1a;2022新星计划第三季云原生与云计算赛道Top5&#x1f3c5;、华为云享专家&#x1f3c5;、云原生领域潜力新星&#x1f3c5; &#x1f49b;博客首页&#xff1a;C站个人主页&#x1f31e; &#x1f497;作者目的&#xff1a;如有错误请指正&#xff0c;将…

【Android Studio】工程中文件Annotate with Git Blame 不能点击

问题描述 工程文件中想要查看代码提交信息但是相关按钮不可点击 解决方法 Android Studio -> Preferences -> Version Control-> 在Unregistered roots里找到你想要的工程文件 点击左上角➕号 然后右下角Apply即可

扩展 Calcite 中的 SQL 解析语法

Calcite中 JavaCC 的使用方法 Calcite 默认采用 JavaCC 来生成词法分析器和语法分析器。 1&#xff09;使用 JavaCC 解析器 Calcite中&#xff0c;JavaCC 的依赖已经被封装到 calcite-core 模块当中&#xff0c;如果使用 Maven 作为依赖管理工具&#xff0c;只需要添加对应的…

PCL 视图变换(OpenGL)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在OpenGL中存在这样一个过程,即模拟人类看东西的过程,通过一种视图变换方式将物体置入观察空间内,以此让我们可以看到这个物体。这个过程有点类似于将一个照相机移到了模型前方的某个位置,然后再设置一下照相机…

【广州华锐互动】VR消防员模拟灭火:身临其境的火场救援

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐渗透到各个领域&#xff0c;为我们带来了前所未有的沉浸式体验。在这其中&#xff0c;VR模拟消防员灭火体验无疑是一种极具创新性和实用性的应用。通过这项技术&#xff0c;人们可以亲身体验到消…

RabbitMQ原理(五):消费者的可靠性

文章目录 3.消费者的可靠性3.1.消费者确认机制3.2.失败重试机制3.3.失败处理策略3.4.业务幂等性3.4.1.唯一消息ID3.4.2.业务判断 3.5.兜底方案 3.消费者的可靠性 当RabbitMQ向消费者投递消息以后&#xff0c;需要知道消费者的处理状态如何。因为消息投递给消费者并不代表就一定…

STM32 invalid UTF-8 in comment 警告解决办法

这里写自定义目录标题 STM32 invalid UTF-8 in comment 警告解决办法问题描述解决办法 STM32 invalid UTF-8 in comment 警告解决办法 问题描述 …/…/libraries/CMSIS/CM3/DeviceSupport/ST/STM32F10x\stm32f10x.h(18): warning: invalid UTF-8 in comment [-Winvalid-utf8]…

正点原子嵌入式linux驱动开发——Linux PWM驱动

PWM是很常用到功能&#xff0c;可以通过PWM来控制电机速度&#xff0c;也可以使用PWM来控制LCD的背光亮度。本章就来学习一下如何在Linux下进行PWM驱动开发。 PWM驱动解析 不在介绍PWM是什么了&#xff0c;直接进入使用。 给LCD的背光引脚输入一个PWM信号&#xff0c;这样就…

Node编写更新用户信息接口

目录 前言 定义路由和处理函数 验证表单数据 实现更新用户基本信息的功能 前言 继前面几篇文章&#xff0c;本文介绍如何编写更新用户信息接口 定义路由和处理函数 路由 // 更新用户信息接口 router.post(/userinfo, userinfo_handler.updateUserinfo) 处理函数 // 导…