【STM32】TIM定时器输入捕获

1 输入捕获

1.1 输入捕获简介

IC(Input Capture)输入捕获

输入捕获模式下,当通道输入引脚出现指定电平跳变时(上升沿/下降沿),当前CNT的值将被锁存到CCR中(把CNT的值读出来,写入到CCR中),可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数

每个高级定时器和通用定时器都拥有4个输入捕获通道

可配置为PWMI模式(PWM的输入模式),同时测量频率和占空比

可配合主从触发模式,实现硬件全自动测量

对于同一个定时器,输入捕获和输出比较只能使用其中一个,不能同时使用(共用)

输出比较是根据CNT和CCT的大小关系来执行输出动作;

输入捕获是接收到输入信号,执行CNT锁存到CCR的动作;

1.2 频率测量

**左边频率大于右边频率。**STM32也只能测数字信号。

测频法:上升沿出现的次数/闸门时间。适合高频信号,测量结果更新慢

测周法:两个上升沿的持续时间。fc是标准频率/计次。适合低频信号,测量结果更新快

中界频率:测频法与测周法误差相等的频率点

当待测频率小于中界频率时,测周法误差更小;反之测频法误差更小。

本节使用的是测周法

1.3 输入捕获电路

最左边是4个通道的引脚,参考引脚定义表就可以知道这个引脚复用在哪个位置。接下来是三输入的异或门,接到了CH1、CH2、CH3,异或门的逻辑是当三个输入引脚的任何一个有电平翻转时,输出引脚就产生一次电平翻转,之后,输出通过数据选择器,到达输入捕获通道1。数据选择器如果选择上面的,就是3个引脚的异或值;如果选择下面一个,那异或门就没有用,4通道各用各的引脚。异或门是为了三相无刷电机服务的。

输入信号此时来到输入滤波器和边沿检测器,输入滤波器对信号进行滤波,避免高频的毛刺信号误触发;边沿检测器处可以选择高电平触发/低电平触发(类似中断),当出现指定电平时,边沿检测电路就会触发后续电路执行动作。有两套电路,分别输出TI1FP1(TI1 Filter Polarity 1,输出给通道1的后续电路)和TI1FP2(TI1 Filter Polarity 2,输出给 下面通道2的后续电路),下面同理。有一个交叉,即两个信号进来可以选择各走各的,也可以是让CH1引脚输入给通道2,让CH2引脚输入给通道1。**目的:(1)可以灵活切换后续捕获电路的输入;(2)可以把一个引脚的输入同时映射到两个捕获单元。**这也是PWMI模式的经典结构。

第一个通道使用上升沿触发,用来捕获周期;

第二个通道使用下降沿触发,用来捕获占空比;两个通道同时对一个引脚进行捕获,就可以同时测量频率和占空比,这就是PWMI模式。

TRC信号也是为了无刷电机的驱动。

经过预分频器之后的信号,就可以触发捕获电路进行工作了。每来一个触发信号,CNT的值,就会向CCR转运(写入/存储)一次,转运的同时,会发生一个捕获事件,这个事件会在状态寄存器置标志位,同时也可以产生中断,可以开启这个中断处理事情。CNT的计数器是由内部标准时钟驱动的,所以CNT的数值其实就可以记录两个上升沿之间的时间间隔,这个时间间隔就是周期,取个倒数就是频率,这就是测周法测量频率

上升沿用于触发输入捕获,CNT用于计数计时,每来一个上升沿,取一下CNT的值,自动存在CCR里,CCR捕获到的值就是计数值N,CNT的驱动时钟就是fc。每次捕获之后,需要把CNT清零(主从触发模式自动处理)。

1.4 输入捕获通道

引脚进来,还行先经过一个滤波器,滤波器的输入就是TI1,就是CH1的引脚,输出TI1F的就是滤波后的信号。fDTS是滤波器的采样时钟来源,下面的CCMR1寄存器的ICF位可以控制滤波器的参数。

这个滤波器的工作原理:以采样频率对输入信号进行采样,当连续N个值都是高电平时,输出才是高电平;连续N个值都是低电平时,输出才是低电平。采样频率越低,采样个数N越大,滤波效果就越好。

滤波之后信号通过边沿检测器,捕获上升沿/下降沿,用CCER寄存器里面的CC1P位就可以选择极性了,最终得到TI1FP1触发信号,通过数据选择器,进入通道1后续的捕获电路(还有TI1FP2触发信号;同样通道2有TI2FP1和TI2FP2信号,也就是前面的交叉)。CCMR寄存器的CC1S位可以对数据选择器进行选择,之后ICPS位可以配置这里的预分频器(不分频/2分频/4分频/8分频),最后CCER寄存器的CC1E位控制输出使能或者失能。如果使能了输出,输入端产生指定边沿信号经过层层电路,就可以让这里的CNT的值,转运到CCR里面来,硬件电路自动完成捕获之后的CNT清零工作。

这个从模式里面有电路完成CNT的自动清零

1.5 主从触发模式

主模式可以将定时器内部的信号映射到TRGO引脚,用于触发别的外设,所以这部分叫主模式。

从模式就是接收其他外设或者自身外设的一些信号,用于控制自身定时器的允运行,也就是被别的信号控制,所以这部分叫从模式。

触发源选择就是选择从模式的触发信号源(是从模式的一部分),选择一个指定的信号,得到TRGI,TRGI去触发从模式,从模式可以在列表中选择一项操作来自动执行。

TI1FP1信号自动触发CNT清零操作选择线路。

手册

主模式选择:

从模式触发源选择

从模式选择

1.6 输入捕获基本结构

只使用了一个通道,目前只能测频率。右上角是时基单元(配置好),启动定时器,CNT就会在预分频器之后的时钟驱动下不断自增(CNT就是测周法计数计时的参数),经过预分频之后的时钟频率就是驱动CNT的标准频率fc(fc = 72M / 预分频系数),

下面输入捕获通道1的GPIO口,输入一个左上角这样的方波信号,经过滤波器和边沿检测,选择TI1FP1为上升沿触发,之后输出选择直连的通道,分频器选择不分频;当TI1FP1出现上升沿后,CNT的当前计数值转运到CCR1里;同时触发源选择,选中TI1FP1为触发信号,从模式选择复位操作。有先后顺序,先转运CNT的值到CCR里去,再触发从模式给CNT清零。

信号出现一个上升沿,CCR1 = CNT,就是把CNT的值转运到CCR1里面去,这是输入捕获自动执行的,然后CNT = 0,清零计数器,这是从模式自动执行的;然后在一个周期之内,CNT在标准时钟的驱动下,不断自增,并且由于之前清零过了,所以CNT就是从上升沿开始,从0开始计数,一直自增,直到下一次上升沿来临,然后执行相同的操作。

第二次捕获的时候,CNT的值就是之前的计数值,这个计数值就自动放在CCR1里;然后下一个周期里,CNT从0开始自增,直到下一个上升沿,这是CCR1刷新为第二个周期即CNT计数值;不断重复。所以当这个电路工作时,CCR1的值始终保持为最新一个周期的计数值(就是测周法的N)

注意事项:

(1)CNT的值是有上限的(65535),如果信号频率太低,CNT可能会溢出;

(2)从模式的触发源选择,只有TI1FP1和TI2FP2,没有TI3和TI4

1.7 PWMI基本结构

PWMI模式使用了两个通道同时捕获一个引脚,可以同时测量周期和占空比。下面多了一个通道。首先TI1FP1配置上升沿触发,触发捕获和清零CNT。这是再来一个TI1FP2,将其配置为下降沿触发,通过交叉通道,器触发通道2的捕获单元,见左上角图。

最开始上升沿,CCR1捕获,同时清零CNT,之后CNT一直自增;然后在下降沿这个时刻,触发CCR2捕获,所以CCR2捕获的值就是高电平所计数的CNT的值(好巧妙),这样CCR1的值是整个周期的值,而CCR2的值是高电平的值,就可以求出占空比(CCR2/CCR1),这就是PWMI模式,使用两个通道来捕获频率和占空比。

手册

2 TIM输入捕获之测频率

2.1 接线图

目前测量信号的输入引脚是PA6,信号从PA6进来,待测的PWM信号也是STM32自己生成的,输出引脚是PA0。也就是STM32产生的信号从PA0输出,而STM32测频率的接口是PA6。

2.2 模块封装

PWM.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header

// PWM初始化函数
void PWM_Init(void)
{
	// 1RCC开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
	TIM_InternalClockConfig(TIM2);
	
	// 2配置时基单元(预分频器、自动重装器、计数模式等)
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		// 时钟分频,影响不大
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	// 计数模式,向上计数
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			// 重复计数器的值
	// 关键参数
	TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;				// ARR自动重装器的值,固定ARR
	TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;			// PSC预分频器的值,调节PSC来实现调节频率
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

	// 3配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能)
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCStructInit(&TIM_OCInitStructure);               					 // 先初始化,后面再按需修改
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;     					 // 输出比较模式,PWM模式1
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;                // 输出比较极性,高级性,极性不反转,有效电平是高电平
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;            // 输出状态输出使能
	TIM_OCInitStructure.TIM_Pulse = 0;            							 // CCR的值w为50,先设置为0,后面变化
	// 这个函数的选择按照GPIO口需求来,PA0口对应的是第一个输出比较通道
	TIM_OC1Init(TIM2, &TIM_OCInitStructure);
	
	// 频率1kHz,占空比50% ,分辨率为1%。 CCR = 50 ,
	/**
	PWM分辨率:	Reso = 1 / (ARR + 1) = 1%      						==> ARR= 100 - 1
	PWM占空比:	Duty = CCR / (ARR + 1) = 50%   						==> CCR = 50
	PWM频率:	Freq = CK_PSC(72M) / (PSC + 1) / (ARR + 1) = 1K  	==> PSC = 720 - 1
	*/
	
	// 4配置GPIO(初始化为复用推挽输出,参考引脚定义表)
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;				// 复用推挽输出。定时器控制引脚,输出控制权转移给片上外设
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	// 5运行控制,启动计数器
	TIM_Cmd(TIM2, ENABLE);
}

// 更改通道1的CCR值,改变占空比
void PWM_SetCompare1(uint16_t Compare)
{
	TIM_SetCompare1(TIM2, Compare);
}

// 修改PSC,改变频率
void PWM_SetPrescaler(uint16_t prescaler)
{
	// 立刻生效
	TIM_PrescalerConfig(TIM2, prescaler, TIM_PSCReloadMode_Immediate);
}

输入捕获的初始化按照这个步骤

库函数

cpp 复制代码
// TIM输入捕获的函数,4个通道公用一个函数(电路中公用模块)
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
// TIM输入捕获的函数,可以快速配置两个通道(PWMI模式)
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

// 输入捕获结构体赋值一个初始值
void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);

// 选择输入触发源TRGI(从模式的触发源选择)
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

// 选择输出触发源TRGO(主模式输出的触发源)
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);

// 选择从模式(从模式选择)
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);

// 分别单独配置通道1234的分频器
void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);

// 分别读取4个通道的CCR。输出比较模式下,CCR是只写的,使用set
// 输入捕获模式下CCR是只读的,使用get
uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

选择TIM3的通道,选择引脚。使用TIM3的通道1引脚,即选择PA6

IC.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header


// 输入捕获初始化
void IC_Init(void)
{
	// 1RCC开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2GPIO初始化(输入模式,上拉/浮空)
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;         // 上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	// 选择内部时钟
	TIM_InternalClockConfig(TIM3);
	
	// 3配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;                    	// PSC预分频器的值,72分频,通过PSC来调节
	TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1;                     	// ARR自动重装器的值 两个合起来计数1秒,固定
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;      	// 向上计数
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;			// 不分频
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                	// 重复计数器的值
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	// 4配置输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;					// 选择通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;								// 输入捕获滤波器,数越大,滤波效果越好
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; 		// 上升沿触发
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 不分频,触发信号分频器,不分频就是每次都有效
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;		// 选择触发信号从哪个引脚输入,直连通道/交叉通道
	TIM_ICInit(TIM3, &TIM_ICInitStructure);
	
	// 5选择从模式的触发源(TI1FP1)
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	
	// 6选择触发之后的操作(从模式reset操作)
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	// 7开启定时器
	TIM_Cmd(TIM3, ENABLE);
}

// 获得频率函数
uint32_t IC_GetFreq(void)
{
	// fx = fc / N
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

2.3 主函数

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"


int main()
{
	OLED_Init();					// 初始化OLED
	PWM_Init();						// PWM初始化
	IC_Init();						// IC初始化
	
	OLED_ShowString(1, 1, "Freq:00000Hz");   	// 显示字符串
	
	PWM_SetPrescaler(720 - 1);		// Freq = 72M / (PSC + 1) / (ARR + 1 = 100) = 1000Hz
	PWM_SetCompare1(50);			// 设置CCR的值,占空比Duty = CCR / (ARR + 1 = 100) = 50%
	// 上面这里输出信号是1000Hz的PWM波
	
	while (1)
	{
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);
	}
}

现象:OLED上显示1000Kz

3 TIM输入捕获之PWMI模式测频率占空比

3.1 接线图

3.2 模块封装

按照这个配置

测周法

PWM.c不变

IC.c

cpp 复制代码
#include "stm32f10x.h"                  // Device header


// 输入捕获初始化
void IC_Init(void)
{
	// 1RCC开启时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2GPIO初始化(输入模式,上拉/浮空)
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;         // 上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	// 选择内部时钟
	TIM_InternalClockConfig(TIM3);
	
	// 3配置时基单元
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
	TIM_TimeBaseInitStruct.TIM_Prescaler = 72 - 1;                    	// PSC预分频器的值,72分频,通过PSC来调节
	TIM_TimeBaseInitStruct.TIM_Period = 65536 - 1;                     	// ARR自动重装器的值 两个合起来计数1秒,固定
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;      	// 向上计数
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;			// 不分频
	TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;                	// 重复计数器的值
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
	
	// 4配置输入捕获单元,修改成两个通道同时捕获同一个引脚的模式
	TIM_ICInitTypeDef TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;					// 选择通道1
	TIM_ICInitStructure.TIM_ICFilter = 0xF;								// 输入捕获滤波器,数越大,滤波效果越好
	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; 		// 上升沿触发
	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 不分频,触发信号分频器,不分频就是每次都有效
	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;		// 选择触发信号从哪个引脚输入,直连通道/交叉通道
	// 法2,直接把另一个通道置成相反的配置,通道2/交叉/下降沿
	TIM_PWMIConfig(TIM3, &TIM_ICInitStructure);							// 配置PWMI模式
	
	// 法1
//	TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;					// 选择通道1
//	TIM_ICInitStructure.TIM_ICFilter = 0xF;								// 输入捕获滤波器,数越大,滤波效果越好
//	TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; 		// 下降沿触发
//	TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;				// 不分频,触发信号分频器,不分频就是每次都有效
//	TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;	// 选择触发信号从哪个引脚输入,交叉通道
//	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	
	
	// 5选择从模式的触发源(TI1FP1)
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	
	// 6选择触发之后的操作(从模式reset操作)
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	// 7开启定时器
	TIM_Cmd(TIM3, ENABLE);
}

// 获得频率函数
uint32_t IC_GetFreq(void)
{
	// fx = fc / N
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

// 获取占空比函数
uint32_t IC_GetDuty(void)
{
	// CCR2 / CCR1
	return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1);
}

3.3 主函数

cpp 复制代码
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"


int main()
{
	OLED_Init();					// 初始化OLED
	PWM_Init();						// PWM初始化
	IC_Init();						// IC初始化
	
	OLED_ShowString(1, 1, "Freq:00000Hz");   	// 显示字符串
	OLED_ShowString(2, 1, "Duty:00%");   		// 显示字符串
	
	PWM_SetPrescaler(720 - 1);		// Freq = 72M / (PSC + 1) / (ARR + 1 = 100) = 1000Hz
	PWM_SetCompare1(50);			// 设置CCR的值,占空比Duty = CCR / (ARR + 1 = 100) = 50%
	// 上面这里输出信号是1000Hz的PWM波
	
	while (1)
	{
		OLED_ShowNum(1, 6, IC_GetFreq(), 5);
		OLED_ShowNum(2, 6, IC_GetDuty(), 2);
	}
}
相关推荐
cjy_Somnr3 小时前
keil5报错显示stm32的SWDIO未连接不能烧录
stm32·单片机·嵌入式硬件
Lay_鑫辰4 小时前
西门子诊断-状态和错误位(“轴”工艺对象 V1...3)
服务器·网络·单片机·嵌入式硬件·自动化
无垠的广袤6 小时前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
雲烟8 小时前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞8 小时前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
田甲8 小时前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up9 小时前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
纳祥科技18 小时前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
单片机日志19 小时前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息
松涛和鸣20 小时前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法