PWM输入输出

PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域。

PWM参数

PWM 中有三个重要参数:频率、占空比(高电平时长占整个周期信号时长的比例)、分辨率(占空比可调精度)。

下图为PWM模式1时的波形图:
image.png
输出PWM波的原理是,利用TIM定时器和输出比较,TIM定时器会周期性地线性增长,当计数器的值低于设定的比较值时输出高电平,大于等于比较值时输出低电平。由于是线性增长,高电平时长占整个周期信号时长的比例是固定的,这个比例被称为“占空比”,英文“Duty Cycle”。
在嵌入式系统中,特别是使用定时器来生成PWM信号时,经常使用的是定时器的比较寄存器(Capture/Compare Register,CCR)和自动重载寄存器(Auto-Reload Register,ARR)来控制PWM的占空比。
给定:

  • CCR:比较寄存器的值(通常用来设置PWM波形的占空比)
  • ARR:自动重载寄存器的值(通常用来设置PWM波形的周期)

那么:Duty=CCR/(ARR+1)


为什么是ARR+1,而不是ARR?

计数范围实际上是从0到ARR,共计ARR+1个计数值。
假设ARR的值为99,CCR的值为50。
小于CCR的数字有0-49共50个,计数范围为0-99共100个,占空比应为50%。
即CCR/(ARR+1)


通过调节CRR,可以修改PWM的占空比。ARR不同,对占空比的调节精度也不同。CCR值加一,那么占空比将提高1/(ARR+1),ARR越大,可以实现的最小步进越小,分辨率越高,对占空比的调节越精细。
PWM的分辨率(Resolution)只与ARR有关:Reso=1/(ARR+1)
最后一个参数是PWM的频率,也就是计数器从0到ARR的变化频率。
定时器时钟频率就是计数器的计数频率,每个周期,计数器值+1。需要从0加到ARR,共ARR+1个时钟周期。
也就是:PWM周期时长=定时器时钟周期时长*(ARR+1)
周期时长取倒数就是频率:PWM频率=定时器频率/(ARR+1)
定时器频率可以通过时钟源频率除以分频因子获得。
给定:

  • CK_PSC:计数单元时钟源频率
  • PSC:分频因子

那么:Freq=CK_PSC/(PSC+1)/(ARR+1)

输出PWM

接下来将以SG90舵机、直流电机、LED灯为例,输出PWM。包括如何查阅文档,进行引脚选取。

事件和中断

image.png
上图下方有“事件”和“中断和DMA输出”

  • 若产生的是更新中断,则该信号会通往配置好的 NVIC 定时器通道,此时 CPU 将会响应定时器的更新中断。
  • 若产生的是更新事件,更新事件不会触发中断,但可以触发内部其他电路的工作。

LED呼吸灯

查询LED灯的引脚,位于哪个定时器的哪个通道。

通过原理图,可以看出LED1对应PA8引脚。但无法通过原理图获取具体位于哪一个通道。
image.png
通过查询引脚定义,可以看到,PA8还是TIM1高级定时器的CH1通道。
image.png

使能TIM1时钟

我们需要先查询TIM1时钟挂载的位置。这可以在库函数定义中查看。
image.png
TIM1出现在RCC_APB2PeriphClockCmd()的参数列表中,这个函数的作用是:控制STM32微控制器中连接到APB2总线上的特定外设的时钟使能或禁用。

  • RCC:代表Reset and Clock Control(复位和时钟控制),是STM32系列微控制器中负责控制时钟的模块。
  • APB2:代表Advanced Peripheral Bus 2(高级外设总线2),是STM32中的一种外设总线,用于连接某些外设到核心。
  • Periph:是Peripheral(外设)的缩写,指的是连接到APB2总线的外设。
  • ClockCmd:是Clock Command(时钟命令)的缩写,指的是该函数用于控制外设时钟的使能或禁用。

同样挂载在APB2总线上的还有GPIOA,可以通过或运算,一行代码使能:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1|RCC_APB2Periph_GPIOA,ENABLE);

GPIO初始化

PA8口目前是TIM1通道,需要将Mode设置为复用推挽输出。

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

定时器初始化

这一步将内部时钟作为时钟源。在stm32f10x_tim.h中找到相关函数。
image.png
通过调用TIM_InternalClockConfig函数,可以将定时器配置为使用内部时钟源。

TIM_InternalClockConfig(TIM1);

配置完时钟源之后,需要配置时基单元。

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 -1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 -1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStructure);
  • TIM_ClockDivision:设置为一分频
  • TIM_CounterMode:选择常用的向上计数模式
  • TIM_Period:目标计数值,达到该数值后会重置为TIM_RepetitionCounter设定的值。
  • TIM_Prescaler:设置为720分频,时钟源发生720个上升沿信号后才计数一次。
  • TIM_RepetitionCounter:达到目标计数值后,寄存器值自动重装为0

配置OC输出比较

每个定时器都有多个通道,在初始化时需要指明通道、定时器。
其中:

  • 定时器通过函数参数指定
  • 通道通过函数名指定
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse=0;
TIM_OC1Init(TIM1,&TIM_OCInitStructure);

TIM_OCInitTypeDef结构体有很多配置项,上面的代码只配置了一部分,剩下的部可以通过TIM_OCStructInit(&TIM_OCInitStructure);进行初始化。这个函数的内容就是为结构体的每一项赋初始值,因为要修改数据,所以参数传递方式为地址传参。
输出比较模式设置为TIM_OCMode_PWM1。这是向上比较模式,也就是本文开头的举例,>=CCR时为低电平。
image.png
更多的TIM_OCMode可以在stm32f10x_tim.h中查找。
TIM_OCPolarity的作用是配置信号的极性:

  • TIM_OCPolarity_High:极性不翻转
  • TIM_OCPolarity_Low:极性翻转

尽管TIM_OCMode_PWM1已经指定了PWM1模式下的工作方式,但为了确保输出信号符合预期并满足外部设备的要求,仍然需要进一步配置输出比较通道的极性。
TIM_OutputState是配置输出使能,设置为TIM_OutputState_Enable才能正常输出。
TIM_Pulse的值就是CCR比较寄存器的值,设置为0,表示复位后为点亮状态,并且为100%亮度。

使能TIM定时器

上面的操作只是配置,没有启动。

TIM_Cmd(TIM1,ENABLE);
TIM_CtrlPWMOutputs(TIM1,ENABLE);

TIM_Cmd(TIM1,ENABLE);的作用是使能TIM1定时器。
在高级定时器中,需要TIM_CtrlPWMOutputs()输出PWM波。

实现呼吸灯效果

需要明确:

  • TIM_TimeBaseInitTypeDef部分是配置时基模块,是时钟+计数
  • TIM_OCInitTypeDef部分是配置输出比较模块,对计数进行处理

image.png
TIM_SetCompare1()的作用是修改定时器的通道1的CCR。

  • 通道1在函数名中指定
  • 定时器在函数参数中指定
  • CCR的值在函数参数中指定

添加延迟是为了让呼吸效果更明显。

驱动SG90舵机

SG90 舵机的控制信号为周期是 20ms 的脉宽调制(PWM)信号,其中脉冲宽度从 0.5ms-2.5ms,相对应舵盘的位置为 0—180 度,呈线性变化。(180°舵机版本)。

也就是说,PWM波的周期为20ms。

定位舵机接口所在引脚

通过原理图,可以看到四个舵机的引脚为SERVO_x对应PB12-PB15
同LED呼吸灯:无法通过原理图获取具体位于哪一个通道。
image.png
在引脚定义的表格中,可以查询默认的复用功能。
image.png
这四个引脚不同于“LED呼吸灯”中的PA8

  • PA8TIM1_CH1
  • PB12TIM1_BKIN:TIM1的备份输入(Break Input)
  • PB13-15TIM1_CHxN:TIM1的通道x的互补通道

在这里,我们仍用PA8输出PWM波,通过飞线,将PWM波输出到舵机的接口上。
这一部分的代码承接自LED部分,只需要修改:

  • pwm波周期:修改为20ms
  • CCR比较值:0.5/1/1.5/2.0/2.5

image.png

修改PWM波周期

TIM_TimeBaseInitStructure.TIM_Period = 2000 -1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 -1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
  • TIM_Prescaler预分频器值为720,系统时钟周期为72M,所以分频后频率为0.1MHz,周期10us。
  • TIM_Period目标计数值为2000,需要2000个时钟周期,TIM的频率为0.1/2000MHz,周期20ms。

按键修改CCR

按键的使能相对简单,需要在原理图中找到按键对应的GPIO口。
image.png
在库函数定义中查找,GPIOB挂载在APB2上。
image.png

void Key_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStructure);
}

按键在按下时,GPIO读取为低电平,松开后应上拉到高电平。即采用GPIO_Mode_IPU上拉输入模式。

  • 如果为下拉输入,那么按不按结果是一样的,读取都是低电平
  • 如果为浮空输入,那么按下一次后,GPIO口始终读取为低电平,只有第一次是有效的。
uint8_t Key_GetNum(void)
{uint8_t KeyNum = 0;if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0){Delay_ms(20);while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0);Delay_ms(20);KeyNum = 1;}if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0){Delay_ms(20);while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);Delay_ms(20);KeyNum = 2;}if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4) == 0){Delay_ms(20);while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_4) == 0);Delay_ms(20);KeyNum = 3;}if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0){Delay_ms(20);while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_5) == 0);Delay_ms(20);KeyNum = 4;}	return KeyNum;
}

Delay_ms的作用是按键防抖,去除毛刺。

根据原理图接线

下图中,我插入的是J4位置,与26号引脚共线。
IMG_20240208_203627.HEIC
将26号引脚与PA8所在的17号引脚用杜邦线连接起来。

VID_20240208_204405

直流电机

不同于LED灯和舵机,直流电机属于大功率器件,需要额外的驱动,普通IO口驱动能力不足。

根据原理图,定位电机驱动的引脚位置

PA0复用作TIM2_CH1_ETR,目前尚未学习。因此本文选择PA2和PA3。对应的是TIM2_CH3TIM2_CH4
image.png

配置TIM2的RCC

TIM2挂载在APB1下,挂载位置在前文有提到:可以通过库函数源文件的注释查看。
image.png
到这里,需要明确:

  • TIM_OCInitTypeDef是对输出比较通道的配置信息。
  • TIM_OCxInit是将配置加载到具体的通道上。由于存在多个TIM定时器,每个定时器有多个通道。因此需要指明将配置文件加载到哪个定时器的哪个通道。定时器通过函数参数指定,通道通过函数名指定。

在前面的LED和舵机中,只需要在一个通道上输出PWM波:

  • LED只有一个输入,另一端焊死在GND上,始终为低电平。
  • SG90舵机也只有一个控制输入。

而在直流电机中,两个输入引脚在不同的高低电平下,状态是不一样的:
image.png
两个引脚都应输出PWM波,而非固定为低电平或高电平。
那么,需要做的就是把配置文件加载到TIM2定时器的CH3和CH4通道上。

TIM_OC3Init(TIM2,&TIM_OCInitStructure);
TIM_OC4Init(TIM2,&TIM_OCInitStructure);

TIM2不是高级定时器,因此不需要TIM_CtrlPWMOutputs()

利用OLED方便调试

image.png
这两个通道的TIM定时器是一样的,变化周期也是一样的。
两个通道的CCR都可以单独指定,实现分别调节两个引脚的电平,但变化周期是一致的。

输入捕获

输入捕获(Input Capture)又称 IC。
在输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数。每个高级定时器和通用定时器都拥有4个输入捕获通道,有两种用途:

  • 配置为PWMI模式,同时测量频率和占空比
  • 配合主从触发模式,实现硬件全自动测量

image.png

  • 测频法:在闸门时间T内,统计上升沿次数N,频率f=N/T
  • 测周法:在两个上升沿内,以标准频率fc计次,次数为N,频率f=fc/N

以上两种测试结果都会存在一个固有误差,即“计次存在正负 1 误差”:

  • 测频法,可能刚计入上升沿就结束计数,也可能结束时即将计入下一个上升沿。即结束时刻位于波形的一个周期内。
  • 测周法,可能刚计入一次标准频率就结束计数,也可能结束计数时即将计入下一次标准频率。即结束时刻位于标准频率的一个周期内。

但是:

  • 测频法适合测试高频信号。在闸门时间内,样本越多(上升沿数量),计次数量就越多则助于减小误差。
  • 测周法适合测试低频信号。低频信号周期长,计次数多,误差越小。
  • 测频法更新速度相较测周法慢,但数值相对稳定。测周法更新速度快,但数值跳变也快。

测频法适用于高频信号,测周法适用于低频信号。那高频信号以及低频信号的范围就会引发争议,即多少频率算高频,多少频率算低频。因此引出一个概念加“中界频率”。频率高于中界频率的信号属于高频信号,使用测频法测量误差更小;频率低于中界频率的信号属于低频信号,使用测周法测量误差更小。
中界频率:对某信号使用测频法和测周法测量频率,两者引起的误差相等,则该信号的频率定义为中界频率。

配置输入通道的RCC

image.png
只需要选择一个CH通道,就可以同时测量PWM频率和占空比:在进入输入滤波器和边沿检测器后,触发后续电路,TI1FP1、TI1FP2两信号任选其一或均产生。

  • CH1、CH2两通道可以交叉使用,CH3、CH4两通道可以交叉使用。
  • CH1可以同时开TI1FP1、TI1FP2两个通道,同时测量信号频率,信号占空比。

image.png
这些通道都是可选的。
在上一步的直流电机中,我们已经使用了PA2和PA3和TIM2_CH3和TIM2_CH4。
现在我们可以选择TIM3作为输入捕获的定时器。由于CH1和CH2在输入时可以交叉使用,任选一条输入都可以分成两条通道。所以CH1和CH2的时基配置和IC配置是一致的,只是初始化的GPIO引脚位置不同。

初始化输入引脚

本文选择TIM3的CH1通道。

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
  • GPIO_Mode:确保在没有外部输入时,引脚被拉高到逻辑高电平,从而防止引脚漂移或无效输入。也可以设置为下拉输入。
  • GPIO_Pin:TIM3_CH1对应PA6,因此初始化的GPIO引脚为GPIO_Pin_6。

时钟源

设置内部时钟作为TIM3的时钟源。

TIM_InternalClockConfig(TIM3);

配置时基单元

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period=65536-1;
TIM_TimeBaseInitStructure.TIM_Period=72-1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);

这段代码对比上文中的PWM输出,好像没有什么区别。
TIM_PeriodTIM_Period的配置原因将在后文解释,解释之前需要铺垫一些内容。
产生一个疑问:还是内部时钟的上升沿触发,TIM负责周期性地累加。是在统计内部时钟的次数,跟输入捕获有什么关系?

配置IC输入捕获

image.png
输入通道在图中给出了二进制表示,可以到库函数定义中查找:
image.png

TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter=0;
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);
  • TIM_Channel:输入捕获通道为TIM_Channel_1,也就是CH1。
  • TIM_ICFilter:滤波器。当连续采集N+1个高电平时,视作高电平。不连续则延续上一个周期的电平。用于过滤毛刺。
  • TIM_ICPolarity:边沿检测,设置为上升沿,下降沿也可以
  • TIM_ICPrescaler:分频因子,设置为1分频,也就是不分频
  • TIM_ICSelection:输入通道。二进制01对应的通道宏定义为TIM_ICSelection_DirectTI。

到这一步,似乎还是不清楚跟输入捕获有什么关系,如何确定输入的频率。
实现自动化测量,需要配置主从模式。

配置从模式

image.png
将TI1FP1信号设置为复位时基单元的触发信号。TI1FP1表示Timer Input 1 Filtered Channel 1,意味着来自通道1的外部信号(经过滤波器)将作为TIM3的输入触发信号。

TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);
  • TIM_SelectInputTrigger:用于选择 TIM3 定时器的输入触发源的函数调用。这意味着 TIM3 定时器将会响应通道1上的外部触发信号,以触发输入捕获操作。
  • TIM_SelectSlaveMode:用于配置TIM3定时器的从模式。在这里,从模式被设置为复位模式TIM_SlaveMode_Reset。

测频过程

  1. 来了一个上升沿,信号会沿着TI1传递到TIM_TS_TI1FP1。
  2. TIM_TS_TI1FP1会触发TIM3定时器的输入事件,已配置的事件响应办法为复位模式。计数器的值会被重置为初始值。已配置的初始值为0。
  3. 下一个上升沿到来之前,TIM定时器会持续计数。
  4. 下一个上升沿到来时,信号会沿着TI1传递到TIM_TS_TI1FP1,触发TIM3定时器的输入事件,输入事件为复位模式。此时,计数器的值为两个上升沿之间的标准频率次数。
  5. 每次上升沿触发输入捕获时,输入捕获通道都会将计数器的当前值存入CCR。再次熟悉,CCR的直译叫作:捕获/比较寄存器。
  6. 也就是说,测的是两个上升沿之间的标准频率次数,实现的是测周法。

读取频率

在时基单元中配置的TIM_Period是72分频,也就是说,标准频率为1MHz。
触发上升沿信号时,CCR寄存器存储定时器中的值,也就是标准频率的次数。
一个上升沿出发了N次标准频率,那么这段PWM的频率为:标准频率/N。

uint16_t IC_GetFreq()
{return 1000000/(TIM_GetCapture1(TIM3) + 1);
}
  • TIM_GetCapture1用于获取输入捕获通道1的CCR的值。由于存在多个定时器,每个定时器存在多个通道,因此需要明确位置。通道通过函数名指定,定时器通过函数参数指定。

为了方便调试,通过OLED输出各项参数的值。

int main(void)
{uint16_t FOR=0;uint16_t BAK=0;	PWM_Init();Key_Init();OLED_Init();IC_Init();OLED_ShowString(1,1,"FOR:");OLED_ShowString(2,1,"BAK:");OLED_ShowString(3,1,"Freq:00000Hz");while(1){uint16_t keyNum=Key_GetNum();if(keyNum==1){PWM_SetCompare3(FOR+=100);PWM_SetCompare4(BAK+=0);}else if(keyNum==2){PWM_SetCompare3(FOR-=100);PWM_SetCompare4(BAK-=0);}else if(keyNum==3){PWM_SetCompare3(FOR+=0);PWM_SetCompare4(BAK+=100);}else if(keyNum==4){PWM_SetCompare3(FOR-=0);PWM_SetCompare4(BAK-=100);}OLED_ShowNum(1,5,FOR,5);OLED_ShowNum(2,5,BAK,5);OLED_ShowNum(3,6,IC_GetFreq(),5);}
}

IMG_20240209_180903.HEIC
尝试修改FOR和BAK,发现结果都是100Hz。
这是因为,100Hz是PWM波的频率,而FOR和BAK是比较寄存器的值。修改的是占空比,而非频率。
修改频率需要修改分频系数和目标周期数。

总结

CCR寄存器在输入输出中均有应用

CCR 寄存器(Capture/Compare Register,捕获/比较寄存器)在输入和输出中有不同的作用:

  • 输入模式:
    • 在输入模式下,CCR寄存器用于记录定时器捕获输入信号的时间。当捕获事件(比如上升沿或下降沿)发生时,定时器的计数值会被保存在对应的CCR寄存器中。
    • 在输入捕获模式下,CCR寄存器通常用于存储捕获事件的时间戳或脉冲宽度。
  • 输出模式:
    • 在输出模式下,CCR寄存器用于设置比较值。定时器计数器的值会与CCR寄存器中设置的比较值进行比较,从而决定输出的行为,比如生成PWM信号或者触发输出比较事件。
    • 在输出比较模式下,CCR寄存器通常用于设置输出比较的触发点或PWM的占空比。

image.png
可以看出,在输出比较中调用的TIM_SetCompare和输入捕获中调用的TIM_GetCapture,访问的都是同一个寄存器,分别进行赋值和取值操作。

频率和占空比

一个输入通道可以分配到两条线路上,分别测量频率和占空比。上面的代码只介绍了频率。
频率和占空比对应的参数是不一样的,不能想当然地通过一条捕获线路全部求出。
在求频率时,直接求得的是CCR寄存器的值,是周期数,实际是“时间”。
要求占空比,可以在线路2捕获下降沿,求出高电平的“时间”。
与整个周期的时间作比,得到的就是占空比。

配置GPIO、时基、OC、IC

命名规范都是:xInitTypeDef xInitStructure
这只是:配置的“信息”,并不是配置的“过程”。设置完成之后,通过xInit(),将配置信息生效到对应的接口。
配置信息的结构体在声明时,并没有明确指定应用到哪个GPIO引脚或者哪个TIM定时器的哪个通道。这些信息,都在初始化方法中指定,或通过函数参数,或通过函数名。

中断与事件

事件不需要实现中断处理函数,比如在输入捕获中,触发的就是事件,可以通过库函数设置为复位模式,硬件自动复位。

  • 若产生的是更新中断,则该信号会通往配置好的 NVIC 定时器通道,此时 CPU 将会响应定时器的更新中断。
  • 若产生的是更新事件,更新事件不会触发中断,但可以触发内部其他电路的工作。

如何查阅文档

获取信息的途径:

  • 原理图和引脚定义:确定引脚之间的关系。比如LED灯1的引脚为PA8,低电平有效,我可以初始化GPIOA_Pin_8引脚为推挽输出,通过GPIOA_Pin_8控制灯的亮灭。引脚不直接与设备相连时,可以通过飞线的方式,比如在舵机操作中,将PWM波的输出引脚GPIOA_Pin_8通过飞线连接到GPIOB_Pin_12。
  • 手册和库函数:哪个设备挂载在哪个总线上,可以在库函数的定义中查询,比如APB1和APB2,库函数定义时指定了挂载的设备。电路走向很难通过代码注释看明白,比如输入捕获时不能通过TIM_ICSelection确定选择的分支,但在宏定义时指明了二进制表示。手册中以二进制形式给出了分支的二进制表示。结合定义和手册,可以确定要填写的是什么。

C语言项目中的“宏定义”与“魔法数”

"魔法数"通常指的是在编程中出现的硬编码数字或常量,这些数字在代码中直接使用,而没有提供明确的解释或者注释。这样的做法可能会导致代码难以理解、维护困难以及可读性差等问题。
尽管手册给出了二进制表示,但实际代码中能用宏就用宏,一串0011会在代码维护上造成不小的麻烦,应尽量避免“魔法数”。使用有意义的命名常量或者枚举来代替,这样可以增加代码的可读性和可维护性。

参考

  • STM32F10xxx参考手册(中文).pdf
  • STM32F103C8T6引脚定义.xlsx
  • 32版开发板原理图.pdf
  • stm32 使用说明+笔记(必读).pdf

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

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

相关文章

寒假作业-day7

1>现有文件test.c\test1.c\main.c , 请编写Makefile. 代码: CCgcc EXEstr OBJS$(patsubst %.c,%.o,$(wildcard *.c)) CFLAGS-c -oall:$(EXE)$(EXE):$(OBJS)$(CC) $^ -o $%.o:%.c$(CC) $(CFLAGS) $ $^head.o:head.hclean:rm $(OBJS) $(EXE) 2>C编程实现&…

封装sku组件

1. 准备模板渲染规格数据 使用Vite快速创建一个Vue项目&#xff0c;在项目中添加请求插件axios&#xff0c;然后新增一个SKU组件&#xff0c;在根组件中把它渲染出来&#xff0c;下面是规格内容的基础模板 <script setup> import { onMounted, ref } from vue import axi…

NOVATEK显示技术系列之CEDSCHPI Training差异简介

CEDS的数据封包格式&#xff1a;首先CEDS数据封包包括三个部分&#xff1a; Training Pattern即Phase1Control Data 即 Phase2RGB Data 即Phase3 Power on Timing&#xff1a; 工作原理&#xff1a; Power ON时&#xff0c;TCON会发Training Pattern&#xff0c;当COF接受Tr…

江科大STM32 终

目录 SPI协议10.1 SPI简介W25Q64简介10.3 SPI软件读写W25Q6410.4 SPI硬件外设读写W25Q64 BKP备份寄存器、PER电源控制器、RTC实时时钟11.0 Unix时间戳代码示例&#xff1a;读写备份寄存器BKP11.2 RTC实时时钟 十二、PWR电源控制12.1 PWR简介代码示例&#xff1a;修改主频12.3 串…

分享90个行业PPT,总有一款适合您

分享90个行业PPT&#xff0c;总有一款适合您 90个行业PPT下载链接&#xff1a;https://pan.baidu.com/s/1bHvhk_42-IFAjNdjPPtMZw?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不易…

106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

题目描述 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 题目示例 输入&#xff1a;inorder [9,3,15,20,7], postorder [9,15,7,20,3] 输出&a…

B2078 含 k 个 3 的数(洛谷)

题目描述 输入两个正整数 m 和 k&#xff0c;其中 1<m≤&#xff0c;1<k≤15 &#xff0c;判断 m 是否恰好含有 k 个 3&#xff0c;如果满足条件&#xff0c;则输出 YES&#xff0c;否则&#xff0c;输出 NO。 输入格式 输入一行&#xff0c;为两个整数 m,k&#xff0…

揭开Markdown的秘籍:标题|文字样式|列表

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;Markdown指南、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️Markdown 标题二. ⛳️Markdown 文字样式2.1 &#x1f514;斜体2.2 &…

python智慧养老系统—养老信息服务平台vue

本论文中实现的智慧养老系统-养老信息服务平台将以管理员和用户的日常信息维护工作为主&#xff0c;主要涵盖了系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;养老资讯管理&#xff0c;养生有道管理&#xff0c;养老机构管理&#xff0c;系统管理等功能&#x…

海外云手机——平台引流的重要媒介

随着互联网的飞速发展&#xff0c;跨境电商、短视频引流以及游戏行业等领域正经历着迅猛的更新换代。在这个信息爆炸的时代&#xff0c;流量成为至关重要的资源&#xff0c;而其中引流环节更是关乎业务成功的关键。海外云手机崭露头角&#xff0c;成为这一传播过程中的重要媒介…

Ubuntu22.04 gnome-builder gnome C 应用程序习练笔记(一)

一、序言 gnome-builder构建器是gnome程序开发的集成环境&#xff0c;支持主力语言C, C, Vala, jscript, python等&#xff0c;界面以最新的 gtk 4.12 为主力&#xff0c;将其下版本的gtk直接压入了depreciated&#xff0c;但gtk4.12与普遍使用的gtk3有很大区别&#xff0c;原…

第6章 智能租房——前期准备

学习目标 了解智能租房项目&#xff0c;能够说出项目中各模块包含的功能 熟悉智能租房项目的开发模式与运行机制&#xff0c;能够复述项目的开发模式与运行机制 掌握智能租房项目的创建&#xff0c;能够独立创建智能租房项目 掌握智能租房项目的配置&#xff0c;能够为智能租…

排序算法---堆排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 堆排序&#xff08;Heap Sort&#xff09;是一种基于二叉堆数据结构的排序算法。它将待排序的元素构建成一个最大堆&#xff08;或最小堆&#xff09;&#xff0c;然后逐步将堆顶元素与堆的最后一个元素交换位置&#xff0c…

求职|基于Springboot的校园求职招聘系统设计与实现(源码+数据库+文档)

校园求职招聘系统目录 目录 基于Springboot的校园求职招聘系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、企业信息管理 3、公告类型管理 4、公告信息管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选…

JVM-双亲委派机制

双亲委派机制定义 双亲委派机制指的是&#xff1a;当一个类加载器接收到加载类的任务时&#xff0c;会自底向上查找是否加载过&#xff0c; 再由顶向下进行加载。 详细流程 每个类加载器都有一个父类加载器。父类加载器的关系如下&#xff0c;启动类加载器没有父类加载器&am…

【前端素材】bootstrap5实现通用果蔬商城网页模板Netta Food(电商适用,附源码)

一、需求分析 通用果蔬商城网页是指专门为销售各类果蔬产品而设计的在线商城网页。它提供了一个方便的平台&#xff0c;使用户能够浏览、选择和购买各种果蔬产品。 通用果蔬商城网页通常具有以下功能&#xff1a; 商品展示&#xff1a;网页上展示各类果蔬产品的图片、价格、产…

「递归算法」:合并两个有序链表

一、题目 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输出&#…

发文新思路!双流卷积!CWT-DSCNN-MSA基于时序特征、cwt小波时频图的双流卷积融合注意力机制的故障识别程序!直接运行!

适用平台&#xff1a;Matlab2023版本及以上 本程序参考中文EI期刊《电力自动化设备》2023年12月29号网络首发文献&#xff1a;《基于格拉姆角场与并行CNN的并网逆变器开关管健康诊断》,此外&#xff0c;在此基础上进一步对模型进行多重改进&#xff0c;每个人都可以构造属于自…

Chrome 沙箱逃逸 -- Plaid CTF 2020 mojo

文章目录 前置知识参考文章环境搭建题目环境调试环境 题目分析附件分析漏洞分析OOBUAF 漏洞利用总结 前置知识 Mojo & Services 简介 chromium mojo 快速入门 Mojo docs Intro to Mojo & Services 译文&#xff1a;利用Mojo IPC的UAF漏洞实现Chrome浏览器沙箱逃逸原文…

【制作100个unity游戏之24】unity制作一个3D动物AI生态系统游戏3(附项目源码)

最终效果 文章目录 最终效果系列目录前言随着地面法线旋转在地形上随机生成动物不同部位颜色不同最终效果源码完结系列目录 前言 欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第24篇中,我们将探索如何用unity制作一…