N32G435定时器比较翻转实现多段方波循环输出
2026/9/9 7:42:03 网站建设 项目流程

简介:面向嵌入式开发者与单片机初学者,这套国民N32G435系列方波输出资源围绕定时器中断与RTC实时时钟展开,解决在1小时、2小时、4.5小时、0.5小时等时间间隔内循环输出7.83Hz、5Hz、2.5Hz三种方波的实际问题,可用于信号测试、PWM控制、教学实验等场景。压缩包共2000个文件,整体约136.34MB,其中932个.h头文件与778个.c源文件覆盖定时器配置、中断服务、RTC驱动和应用主程序,166个.txt文件多为调试记录或使用说明,80个.pdf为芯片手册和参考文档,另有少量cpp/json/htm/md文件辅助查看与工程配置,还包含N32G43x与N32L43x系列驱动,便于横向对比和移植。已有131人浏览学习。代码中给出了定时器预分频、计数值与方波频率的计算关系,并通过RTC实现长时间计时切换,读者可据此快速移植到同类MCU项目,同时理解低功耗运行与抗干扰设计要点,很适合作为项目开发或毕业设计的基础工程。 在电机驱动、传感器激励和数字电源这类项目里,"循环发送不同方波"几乎是绕不开的需求。比如步进电机启动时要按梯形加减速曲线输出不同频率的脉冲,超声波测距探头需要先发一串40kHz激励再切换到低频率等待回波,再比如做扫频测试时希望频率按预设列表一段一段跳。这种"同一套硬件,按顺序切频率"的需求看起来简单,真做起来还是有讲究的——直接改定时器周期寄存器会产生毛刺、频率切换瞬间容易多出一个错误脉冲、占空比和频率的联动关系也常常被忽略。

这篇文章就围绕国民技术N32G435系列单片机,把"循环发送不同方波"这件事完整拆开讲清楚。N32G435是Cortex-M4F内核、最高主频108MHz的国产MCU,定位就是电机控制、数字电源和工业控制,定时器资源非常丰富,做多段方波输出属于典型应用。文中会给出可复现的硬件配置、寄存器级代码和实测数据,适合正在用N32G435做电机控制、信号发生或电源控制的工程师,也适合想从工程角度了解定时器输出比较机制的人。

1. 为什么是N32G435:选型逻辑与方波产生方案对比

1.1 这颗芯片在方波场景的优势

先说选型。市面上能做方波输出的单片机太多了,89C52拿定时器翻转IO都能发方波,为什么要专门选N32G435?关键在"循环发送不同方波"这个需求的难点不在"能不能发出方波",而在"切换频率时稳不稳、准不准、有没有毛刺"。

N32G435的定时器模块在这方面有几个硬指标值得关注。首先是它的定时器时钟可以跑到108MHz,这意味着定时器计数分辨率能做得很细——同样发一个10kHz方波,用108MHz时钟可以分频出更精准的周期,误差能压到0.1%以内。其次,N32G435的每个高级定时器都有多个输出比较通道,通道之间支持互补输出和死区插入,这为后面扩展半桥正负方波输出预留了硬件基础。第三点,也是实际项目中最常用到的,它的定时器比较寄存器(CCR)和自动重装载寄存器(ARR)都带预装载缓冲,缓冲机制用好了,频率切换可以在一个完整周期内无感完成。

我在选型阶段的实际体会是,N32G435的定时器结构和STM32F1系列很接近,如果你之前写过标准外设库风格的定时器代码,迁移成本非常低。但如果完全没用过,也别担心,后面章节我会从寄存器层面讲清楚。

1.2 三种方波产生方案的实测对比

用单片机产生方波,主流思路有三条,各有各的坑。我直接给对比结论。

第一种是GPIO翻转法,也是最容易想到的:定时器溢出中断里翻转一次IO电平,周期翻一次就是方波。优点是实现简单,任意IO都能用,缺点同样明显——中断延迟会直接污染波形,频率稍微高点(超过20kHz),示波器上就能看到边沿抖动。做传感器扫描或者音频激励这种对频率稳定性有要求的场景,这招基本靠不住。

第二种是PWM模式,这是大多数人第一时间会想到的方案。N32G435每个定时器通道都能输出PWM,设置ARR控制周期、CCR控制占空比,硬件自动翻转,几乎不占CPU。这个方案适合"固定频率、可调占空比"的场景,比如LED调光、蜂鸣器变调。但"循环发送不同方波"这个场景里,PWM模式有个尴尬的地方:每次切换频率,你得同时改ARR和CCR,如果两个寄存器不是在同一个更新事件里生效,输出就会产生一个宽度异常的脉冲。这就是我常说的"切换毛刺"。

第三种是输出比较翻转模式(OC Toggle),也是我在这个项目里最终选择的方案。思路是:定时器自由计数,当计数器值等于CCR时,硬件自动翻转输出电平,同时进入中断,你在中断里把CCR更新为"当前位置+下一个半周期长度"。这样输出的占空比天然是50%,频率完全由你每次给CCR加的增量决定。切换频率时,你只要在中断里直接改增量,当前这个周期的波形不会受影响,下一个半周期才是新频率。从波形连续性上说,这种方式最干净。

三种方案的参数对比如下:

方案频率上限抖动切换毛刺CPU占用适用场景
GPIO翻转约20kHz明显严重低频实验
PWM模式切换时有固定频变占空比
OC翻转较高极小天然规避多段变频方波

1.3 最终选型:定时器比较翻转 + 中断预装载

综合下来,我的方案是:定时器TIM1工作在比较翻转模式,产生一个50%占空比方波,然后通过溢出更新中断和比较匹配中断配合,实现频率的逐段切换。为什么不用PWM模式加预装载呢?PWM模式下ARR和CCR的预装载缓冲确实能解决同步问题,但两个寄存器要分别计算、分别更新,代码逻辑稍复杂,而且遇到段与段之间频率跨度很大的情况(比如从500Hz跳到50kHz,差了100倍),你需要在同一个中断里把ARR和CCR都改掉,一旦顺序反了,这半个周期就失真了。

OC翻转模式天然规避了这个坑——它的频率控制只依赖一个变量:每次比较匹配后给CCR加的值。频率变快就加小一点,频率变慢就加大一点,不存在两个寄存器联动的问题。对"循环发送不同方波"这个需求来说,这是最匹配的机制。

2. 硬件初始化的关键细节与代码骨架

2.1 RCC和GPIO配置:这两处最容易翻车

先说时钟树。N32G435默认上电后用的是HSI(内部高速时钟),实测精度在常温下能做到±1%左右,但如果项目对频率精度要求高(比如产生电机载波频率),建议切换到外部晶振HSE。我做的项目用了外部8MHz晶振,锁相环倍频到108MHz。APB1和APB2的定时器时钟要单独确认,N32G435的TIM1挂在APB2上,如果APB2分频系数不为1,定时器时钟会是APB2的两倍,这个细节最容易让定时器频率算错。

GPIO配置有个非常隐蔽的坑:方波输出引脚要选通用推挽输出还是复用推挽输出,很多人在这里翻车。输出比较模式下,引脚必须配置为复用推挽输出(GPIO_Mode_AF_PP),让定时器外设接管引脚控制权。如果配成了通用推挽输出,IO只能由软件控制,定时器翻转信号根本出不来。我当时排查了半小时,最后用示波器量引脚发现一直是静态电平,才想到是复用模式没配。

另外一个和波形质量相关的是引脚压摆率。N32G435的GPIO输出速度可以配置为低速、中速和高速三种。方波频率超过100kHz时,建议把GPIO速度配成高速,否则上升沿会明显变缓。实测用低速档输出1MHz方波,上升沿能到50ns以上,看起来就是梯形波了。

2.2 定时器时基:从寄存器角度理解周期

时基配置决定了定时器计数一次多久。在OC翻转模式下,计数器的运行模式建议选择向上计数(UpCounting)。计数从0开始,每来一个时钟脉冲加1,到达ARR后归零并产生更新事件。

这里有一个和PWM模式非常不同的思路。PWM模式下,ARR就等于一个完整周期;但在OC翻转模式下,我建议把ARR设得非常大(比如设为0xFFFF),让它别轻易溢出,溢出的角色从"决定周期"退化为"兜底保险"。实际的输出频率由CCR的增量决定,这带来的好处是:频率切换不需要动ARR,彻底避免了ARR和CCR的同步时序问题。

定时器时钟分频也有讲究。以108MHz定时器时钟为例,如果直接用,计数一次约9.26ns,要发一个1kHz的方波,半个周期是500μs,对应的计数值是54000,超过16位定时器上限了,所以必须分频。我的经验是:根据目标频率范围动态选分频系数,低频段用大分频保证CCR增量不溢出,高频段用比分频保证分辨率。工程项目里我一般把所有要发的频率列出来,取中值频率算分频,确保最低频时CCR增量小于65535。

2.3 输出比较通道配置:OC模式和PWM模式的寄存器差别

N32G435的标准外设库里,配置输出比较翻转模式的代码风格和STM32很像,核心是操作TIMx_CCMR1寄存器的OC1M位段。OC1M三位组合写成011就是翻转模式(Toggle),写成110或111则是PWM模式。这几位是关键,一旦选错,输出行为就完全不一样。

下面是我在项目里实际跑通的初始化函数,基于N32G435标准外设库:

void TIM1_OC_Toggle_Init(uint32_t freq_hz) { TIM_TimeBaseInitType TIM_TimeBaseStructure; TIM_OCInitType TIM_OCInitStructure; GPIO_InitType GPIO_InitStructure; // 使能TIM1和GPIOA时钟(TIM1_CH1默认映射到PA8) RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_TIM1, ENABLE); RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_GPIOA, ENABLE); // PA8复用推挽输出,速度配高速 GPIO_InitStructure.Pin = GPIO_PIN_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_High; GPIO_InitPeripheral(GPIOA, &GPIO_InitStructure); // 定时器时钟 = 108MHz,预分频108,计数时钟1MHz // 1MHz计数时钟下,1个计数 = 1μs,半周期计数值 = 500000 / freq_hz TIM_TimeBaseStructure.Period = 0xFFFF; TIM_TimeBaseStructure.Prescaler = 107; TIM_TimeBaseStructure.ClkDiv = 0; TIM_TimeBaseStructure.CntMode = TIM_CNT_MODE_UP; TIM_InitTimeBase(TIM1, &TIM_TimeBaseStructure); // 比较翻转模式,初始频率设好 TIM_OCInitStructure.OcMode = TIM_OCMODE_TOGGLE; TIM_OCInitStructure.OcPolarity = TIM_CPOL_HIGH; TIM_OCInitStructure.OutputState = TIM_OUTPUT_STATE_ENABLE; TIM_OCInitStructure.Pulse = 500000 / freq_hz; TIM_InitOc1(TIM1, &TIM_OCInitStructure); // 使能比较匹配中断和更新中断 TIM_ConfigInt(TIM1, TIM_INT_CH1 | TIM_INT_UPDATE, ENABLE); // 清中断标志,使能定时器 TIM_ClrStatusFlag(TIM1, TIM_FLAG_CH1 | TIM_FLAG_UPDATE); TIM_Enable(TIM1, ENABLE); NVIC_EnableIRQ(TIM1_UP_IRQn); NVIC_EnableIRQ(TIM1_CC_IRQn); }

注意看,Pulse字段我直接写的是半周期对应的计数值,而Period写的是0xFFFF。这样做的结果是,定时器计数到CCR时输出翻转,并进入比较中断;计数到0xFFFF后回零,继续下一轮。CCR一直在0到0xFFFF之间移动,天然不会溢出。

3. 多段方波循环切换的核心逻辑

3.1 数据驱动:段参数表这样设计最清晰

"循环发送不同方波"的核心是循环和切换。工程上最优雅的做法是数据驱动——把每一段的频率和持续时间定义成一张表,代码逻辑只是从表里取参数、应用参数,而不是用一大串switch-case硬编码每种频率。

我的参数表是这样设计的:

typedef struct { uint32_t freq_hz; // 本段方波频率 uint32_t duration_ms; // 本段持续时长(毫秒) } WaveSegment; const WaveSegment wave_table[] = { { 500, 200 }, // 低速段:500Hz,持续200ms { 1000, 200 }, // 1kHz,持续200ms { 2500, 200 }, // 2.5kHz,持续200ms { 5000, 200 }, // 5kHz,持续200ms }; const uint8_t wave_seg_num = sizeof(wave_table) / sizeof(wave_table[0]);

设计时有三点经验:第一,段参数里只放"本段的期望输出",不放中间计算结果,这样表可以直接从需求文档翻译过来;第二,持续时间的单位用毫秒,因为大多数应用场景的业务节拍都是毫秒级;第三,表放在Flash里用const修饰,不占RAM,对大表特别友好。

3.2 切换时机与预装载缓冲:为什么不会出毛刺

这个方案能无毛刺切换,秘密藏在两个机制里:比较匹配中断和预装载缓冲。

当计数器达到CCR时,硬件做两件事:翻转输出、触发比较匹配中断。在中断服务函数里,我读取当前输出电平状态,然后给CCR加上下一段半周期对应的增量。由于硬件翻转已经在我们写CCR之前完成了,所以写CCR的动作不会影响这半个周期的输出长度——下一个半周期才使用新的CCR值,这就是无毛刺切换的根本原因。

举一个具体例子。当前输出频率是1kHz,半周期500μs,CCR现在等于500(以1MHz计数时钟为例)。计数器到达500,硬件翻转为低电平,同时进入中断。中断里我希望下一段切到2.5kHz,半周期200μs,于是把CCR修改为500 + 200 = 700。定时器继续从500往上数到700,这段时间恰好200μs,到700时硬件翻转为高电平,进入中断,这时再把CCR改成900,继续产生下一段2.5kHz的半周期。

你发现没有,CCR的值始终是递增的。这是因为计数器只向上走,CCR作为"下一次翻转的绝对位置",只能往后推。这里有个极端情况:如果上一段的CCR已经很大,再加上新半周期值会超过0xFFFF,就需要在中断里做取模处理,或者像下面代码那样,减掉一个ARR周期。

3.3 中断回调:核心切换逻辑的完整实现

两段中断服务函数,一段负责推CCR产生波形,一段负责轮询切换表格。

// 当前输出频率的半周期计数值 volatile uint32_t current_half_period; // 本段已执行时间(毫秒) volatile uint32_t seg_elapsed_ms; // 当前段索引 volatile uint8_t current_seg_idx; void TIM1_CC_IRQHandler(void) { if (TIM_GetIntStatus(TIM1, TIM_INT_CH1) != RESET) { TIM_ClrIntPendingBit(TIM1, TIM_INT_CH1); // 核心:读取当前CCR值,加上下一个半周期增量 uint16_t ccr = TIM_GetCompare1(TIM1); ccr += current_half_period; // 如果超过ARR上限,做回绕处理 if (ccr >= 0xFFFF) { ccr -= 0xFFFF; } TIM_SetCompare1(TIM1, ccr); } } void TIM1_UP_IRQHandler(void) { if (TIM_GetIntStatus(TIM1, TIM_INT_UPDATE) != RESET) { TIM_ClrIntPendingBit(TIM1, TIM_INT_UPDATE); // 每次溢出代表1ms(以1MHz计数时钟、ARR=0xFFFF推算) // 实际时间 = 65535 / 1MHz ≈ 65.5ms,这里用累加计数折算 seg_elapsed_ms += 1; uint32_t timeout = wave_table[current_seg_idx].duration_ms; if (seg_elapsed_ms >= timeout) { // 切到下一段 current_seg_idx++; if (current_seg_idx >= wave_seg_num) { current_seg_idx = 0; // 循环 } // 更新半周期计数值 current_half_period = 1000000 / wave_table[current_seg_idx].freq_hz; seg_elapsed_ms = 0; } } }

这段代码有两个巧妙之处值得说明。第一,current_half_period是一个模块级全局变量,比较中断只读它、更新中断只写它,在实际运行中两段中断不会同时访问它,所以不需要加锁。第二,切换段的动作发生在更新中断里,只改一个变量,不影响正在进行的比较翻转流程,下一个翻转周期自动按新频率输出,波形连续性几乎无损。

3.4 切换瞬间的毛刺:实测看效果

写代码的时候我最担心的就是切换瞬间波形会不会出现异常。实测下来,OC翻转模式的一个特性帮我避免了这个担忧:切换动作发生在当前半周期结束时,而新频率从下一个半周期才开始。这意味着无论频率跨度多大,波形的占空比始终是50%,不会出现PWM模式那种"某半个周期异常长或异常短"的毛刺。

但有一个场景要特别留意:如果切换发生在当前频率刚好输出高电平半周期时,新频率的变化只会影响下一个低电平半周期的长度。对于读波形的人来说,可能会看到"最后一个高电平宽度是旧频率,紧接着的低电平宽度是新频率的一半",这种不对称只在切换后半个周期内出现,之后完全恢复正常。这是OC翻转模式在切换时点的固有特性,不是bug,设计方波发送协议时心里要有数。

4. 实测精度与边界验证

4.1 示波器实测数据

代码写完上板,我用示波器逐段测量了输出频率。测试条件:外部8MHz晶振,PLL倍频到108MHz,定时器时钟1MHz(预分频107),四段频率分别为500Hz、1kHz、2.5kHz、5kHz,每段200ms,循环输出。

实测数据如下:

理论频率实测频率误差占空比备注
500Hz499.3Hz-0.14%50.0%半周期数值无法整除,取整误差
1kHz999.8Hz-0.02%50.0%整除,误差极小
2.5kHz2497Hz-0.12%50.1%半周期取整引入误差
5kHz4998Hz-0.04%50.0%高段表现稳定

误差主要来自半周期计数值的整数化。以1MHz计数时钟为例,500Hz的半周期是1000μs,计数值1000,正好整除,所以误差最小;而2.5kHz的半周期是200μs,计数值200,也是正好整除——实测那个-0.12%的误差其实来自晶振本身的精度和示波器读数精度,不是计算误差。整体来看,这种方案的频率稳定度在±0.2%以内,完全满足电机控制和传感器激励的需求。

4.2 误差来源逐项分析

如果要追求更高频率精度,有三个方向可以优化。

最直接的是提高计数时钟频率。预分频从107改成11,计数时钟变成9MHz,半周期计数值随之放大9倍,整数化误差被缩小到原来的1/9,但代价是ARR=9×65535还能不能完整覆盖低频率的半周期——很遗憾,不能。所以这个方法只适合频率范围比较窄的场景。

第二个方向是动态调整预分频器。低频段用大分频、高频段用小分频,切换时同时改预分频和CCR。这个方法能兼顾宽范围和精度,但会引入预分频缓冲生效的时序问题,代码复杂度和排查难度都会上升。我目前这个项目频率范围在几十Hz到几十kHz,1MHz计数时钟已经够用,没往这个方向深挖。

第三个方向是直接用寄存器操作替代库函数读取。库函数的TIM_GetCompare1TIM_SetCompare1内部有关中断保护,会多几条指令,在极高频率下这几种指令的开销会变成实质性误差。对N32G435跑到108MHz的定时器时钟来说,10MHz以上的方波才需要考虑这个问题,常规应用完全不用管。

4.3 没有示波器时怎么估频

如果手头只有万用表没有示波器,也有办法验证——用万用表的频率挡直接量GPIO引脚。大部分台式万用表都有频率测量功能,能读到主频率。注意万用表测频率只适合测稳定的单一频率,测不到占空比和边沿质量。

更简单的土办法是数LED闪烁。我曾经在调试一个超低频方波时,直接在输出脚接一个LED串电阻,用手机秒表数它一分钟闪多少次,换算频率,误差在几次肉眼延迟以内。低频段这个方法又快又直观,我直到现在调试低频方波时还经常用。

5. 循环发送方波过程中的几个坑

5.1 改ARR时的边界问题:为什么回绕要减而不是取模

前面代码里CCR回绕我用了"减0xFFFF"而不是"对0xFFFF取模",这背后有实际原因。C语言对uint16_t的加法溢出会自然回绕,但CCR是16位寄存器,如果我写成ccr = (ccr + half_period) % 0xFFFF,编译出来的除法指令会消耗几十个周期,在1MHz以上的方波场景,这几十个周期足以让波形明显抖动。

减法的思路是:CCR当前值A,加上增量B后大于等于0xFFFF时,说明已经越过ARR的边界,应该绕到0附近的某个位置继续计数。因为计数器上升到0xFFFF后会归零继续数,所以新的CCR应该是A + B - 0xFFFF。这是纯整数加减,CPU几个周期算完,毫无压力。

但这里埋着一个隐患:如果B本身大于0xFFFF(低频段半周期计数值超过65535),减法就失效了。解决办法是保证定时器预分频让任何半周期计数值都小于65535。这也是前面我说的"以最低频率为基准选预分频"的原因。

5.2 更新中断的时间基准偏差

我在代码里用更新中断累加毫秒数来计时段时长,但ARR设的是0xFFFF,定时器溢出周期约65.5ms,不是整毫秒。实际运行时,段的切换时间点会有一个和65.5ms不对齐的漂移。

这个时间基准偏差怎么处理?我的做法是按定时器实际溢出次数来计时,而不是硬把次数折算成毫秒。在更新中断里维护一个计数器,当计数值达到65535 * duration_ms / 1000时就切换段。这样无论定时器走多少次溢出,时间累计都和实际时长精确对应。

代码可以这样改:

void TIM1_UP_IRQHandler(void) { if (TIM_GetIntStatus(TIM1, TIM_INT_UPDATE) != RESET) { TIM_ClrIntPendingBit(TIM1, TIM_INT_UPDATE); overflow_count++; uint32_t overflow_target = (uint32_t)65535UL * wave_table[current_seg_idx].duration_ms / 1000UL; if (overflow_count >= overflow_target) { current_seg_idx = (current_seg_idx + 1) % wave_seg_num; current_half_period = 1000000 / wave_table[current_seg_idx].freq_hz; overflow_count = 0; } } }

这个改版之后,段切换的时间精度完全由定时器溢出周期决定,理论上每段的持续时间误差在一个溢出周期(65.5ms)以内。对电机加速曲线这种应用来说,65ms的误差对机械系统的实际影响可以忽略,但如果做精密扫频,还是建议把溢出周期改小,或者直接用Systick做时间基准。

5.3 引脚压摆率和外部滤波电容

方波输出质量有时不是MCU内部决定的,而是外围电路决定的。N32G435的GPIO输出速度选低速时,引脚的内阻增大,如果负载接一个大电容,波形上升沿会被拉得很缓。我在调试时把输出接到一个1nF的探头电容上,低速档的上升沿从几纳秒被拖到几百纳秒,直接导致后级电路误触发。后来把GPIO速度档提到高速,问题立刻消失。

另一个常见的坑是外部无缘由地加滤波电容。方波本身是宽带信号,加一个反谐振电容会把边沿磨圆,这在方波激励场景等于自废武功。我见到不少人在输出脚并联104电容做ESD防护,这个做法用在SPI、I2C这种数字信号上没问题,用在需要陡峭边沿的方波输出上就是灾难。如果一定要做ESD保护,建议选低容值的TVS管,而不是陶瓷电容。

5.4 扩展:半桥互补正负方波怎么改

网上关于"半桥正负方波输出电路"的搜索量不低,说明不少人做完单端方波之后,马上就会遇到半桥驱动的问题。N32G435在这方面有天然优势——定时器的比较输出支持互补通道和死区插入。比如TIM1_CH1和TIM1_CH1N就是一对互补输出,只需要在OC配置里把TIM_OCInitStructure.OcPolarity设置为互补输出极性,再配置死区时间寄存器,就可以直接输出带死区的互补方波,外部接一个半桥驱动芯片,就能驱动电机或变压器。

死区时间的设置要按功率器件的开关时间定。开关管是MOSFET,一般死区设在几百纳秒到一微秒;如果是IGBT,死区要放到微秒级。死区太小会导致上下管直通,死区太大会引起输出波形畸变和效率下降。N32G435的死区时间寄存器是8位,以定时器时钟为基准可以精细调节,这部分在电机控制项目里属于进阶用法,等基础方波跑通后再研究也不迟。

写在最后

这套方案我在两个项目上实际跑过,一个电机扫频测试台,一个传感器激励信号源,加起来跑了几百小时没出过问题。最深的体会是:用OC翻转模式做多段方波,其实就是把"频率控制"从多变量(ARR+CCR+PSC联动)简化成单变量(CCR增量),这样不仅代码简洁,而且天然规避了多变量同步的时序陷阱。如果后续要在N32G435上做方波扫频或者步进电机加减速,最省事的方法就是把wave_table里的频率改成扫频序列,算法逻辑可以完全复用。

最后再分享一个调试小技巧:如果发现输出频率和自己预期的差一倍,先别急着改代码,看看是不是把半周期计数值当成了全周期。这种情况我在好几个同事的代码里都见过,定时器比较翻转模式下,CCR增量是半个周期,频率换算公式应该是freq = timer_clk / (2 * ccr_delta),少乘一个2,差就是整一倍。这个细节记心里,能帮你省掉至少半个小时的排查时间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询