定时器Timer 链接到标题

理论部分参加cd的ppt。这部分记录都是以实际代码为主。

中断 链接到标题

这个在微机原理讲的很详细了,有一个叫NVIC的中断控制器,可以在不占用CPU的情况下,直接响应中断请求。(这个在微机原理也讲过)

CountSensor配置 链接到标题

初始化函数 链接到标题

  • 首先开启外设时钟RCC
  • 然后配置GPIO
  • 之后就是AFIO的配置了,从物理上去理解就是选择哪个GPIO端口的中断信号进入AFIO,这部分用到了一个之前没有了解过的函数GPIO_EXTILineConfig(翻找定义容易了解),
  • 然后就是EXIT了,翻找其函数,用心体会
  • NVIC的配置:也是好烦的查表过程。

中断使用函数 链接到标题

  • 也就是在中断发生时计数一次的函数
  • 这个函数也有一点特殊,属于是启动函数,因此名字要从表中来,并且无需在.h中声明

获取值函数 链接到标题

警告
直接就返回了一个值??

main.c 链接到标题

编写好函数


void CountSensor_Init(void)
/*下面就是配置外部中断的部分了*/
{
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //使能GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //使能AFIO时钟
    //EXTI理论上要配的,但是实际上没有这一项(可能是内置了?)
    /*配置GPIO*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入  
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_14; //PB14
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MHz
    GPIO_Init(GPIOB,&GPIO_InitStructure); //初始化GPIOB14
    
    /*配置AFIO*/
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14); //PB14作为外部中断源
    /*配置EXTI*/
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line=EXTI_Line14; //PB14作为外部中断线
    EXTI_InitStructure.EXTI_LineCmd=ENABLE; //使能外部中断线
    EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; //中断模式
    EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; //下降沿触发
    EXTI_Init(&EXTI_InitStructure); //初始化外部中断

    /*配置NVIC*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//需要注意的是每一个函数只有一个优先级,所以理论上这个函数应该放在主函数里(当然,放在模块里也没有问题,不过要保证在主函数里面的所有模块的优先级都是一样的) 
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn; //中断通道号(这个需要在工程文档中去找)
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //使能中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; //抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; //响应优先级
    NVIC_Init(&NVIC_InitStructure); //初始化中断
    
}
uint16_t CountSensor_Count;
uint16_t CountSensor_Get(void)
{
    return CountSensor_Count; //返回计数值
}
void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line14) != RESET) //判断中断是否发生
    {
        CountSensor_Count++; //计数加1
        EXTI_ClearITPendingBit(EXTI_Line14); //清除中断标志位
    }
}
#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H

void CountSensor_Init(void);
/*中断函数不需要调用,因为他是在启动文件自动执行的*/
uint16_t CountSensor_Get(void);
#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"      
#include "OLED.h"
#include "CountSensor.h"
int main(void)
{
	OLED_Init();	//初始化OLED
	CountSensor_Init();
	OLED_ShowString(1,1,"Count:");
	while (1)
	{
		OLED_ShowNum(1,7,CountSensor_Get(),5); //显示计数值
	}
	
}
于是就通过中断实现了光电门计数的功能。

编码计数器 链接到标题

同样涉及中断计数,这里放上我的逻辑错误。

  • 漏掉
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

从而在一开始就跑不了。

  • 加上了那一句,但是效果还是很怪,所以对照标准样例接着debug,发现是我再GPIO1的中断函数中没有同时对两个GPIO口判断电位。
    void EXTI1_IRQHandler(void)
    {
    	if (EXTI_GetITStatus(EXTI_Line1) == SET)
    	{
    		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
    		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
    		{
    			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
    			{
    				Encoder_Count ++;
    			}
    		}
    		EXTI_ClearITPendingBit(EXTI_Line1);
    	}
    }
    
    void EXTI1_IRQHandler(void)
    {
        if(EXTI_GetITStatus(EXTI_Line1) ==SET) //判断中断是否发生
        {
            if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //即判断是否是正转
            {
               Encoder_Count++;
            }
            EXTI_ClearITPendingBit(EXTI_Line1); //清除中断标志位
        }
    }
    

定时器定时中断 链接到标题

这部分将用到更多中断的知识。 首先当然还是建立模块Timer.c和Timer.h,

初始化函数 链接到标题

目的就是要打通下面的这一幅图:

alt text

  • 需要看定时器本身的库函数stm32f10x_tim.h
    提示
    这里可以学习一下kel本身的标签工具(可以将经常用的函数标记出来),对代码运行没有影响,仅标记而已
  • 这里有大把的函数,需要根据想要选图中的那一条路径构造定时器合理选择。
  • 这里选用TIM2(通用定时器)
  • 基础操作不说,在时钟频率的确定有一点细节
    注释

    计数器溢出频率 $$ CK_CNT_OV= \frac{CK_PSC(ARR+1)}{(PSC+1)} $$ 所以参数设置可设

    TIM_TimeBaseStructure.TIM_Period = 10000-1; //ARR
    TIM_TimeBaseStructure.TIM_Prescaler = 7200-1; //PSC
    

    -1是从公式里面看的,由于是除法,意思是对72Mhz的时钟进行7200分频,分频后为10KHz,要计10000个数,也就是1s的时间。

中断服务函数的使用 链接到标题

这属于是C语言里面的知识了,即计数数Num需要在主程序main.c中输出使用,但是对其的定时累加操作是在出于Timer.c中过的中断函数中进行的。如果直接在主文件

unit16_t Num = 0;

会报错,这里有两种解决方法

使用全局变量 链接到标题

需要使用全局变量,即在Timer.c中声明

extern uint16_t Num;

然后就可以正常使用

将中断函数复制到主程序 链接到标题

这其实就是属于一种非阻塞式的编程方式了,

最终写出来的代码如下

#include"stm32f10x.h"
extern uint16_t Num; 
void Timer_Init(void)
{
    /*选用TIM2*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2时钟
    TIM_InternalClockConfig(TIM2);//这行可写可不写,因为默认就是内部时钟

    /*配置时基单元*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化TIM2
    //给结构体成员赋值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//用于设置时钟分频,从而用于给滤波器工作
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
    //下面的三个参数就是设置计数器的频率的了,比如如果想定时1s
    TIM_TimeBaseStructure.TIM_Period = 100-1; //计数器的值
    TIM_TimeBaseStructure.TIM_Prescaler = 7200-1; //分频值
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //重复计数器值,高级定时器才有,这里没有就给0
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化TIM2

    /*配置中断*/
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启了中断和NVIC通路

    /*NVIC配置*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC分组

    NVIC_InitTypeDef NVIC_InitStructure; 
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断(之前配置的是GPIO中断,这里配置的是时钟中断)具体也是去跳转定义看文档
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能TIM2中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //响应优先级
    NVIC_Init(&NVIC_InitStructure); //初始化NVIC

    /*使能定时器*/
    TIM_Cmd(TIM2,ENABLE); //使能TIM2
}

void TIM2_IRQHandler(void) //TIM2中断服务函数
{
    if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET) //判断是否是TIM2的更新中断
    {
        Num++; //Num自加1
        TIM_ClearITPendingBit(TIM2,TIM_IT_Update); //清除TIM2的更新中断标志位
        //在这里添加你想要执行的代码
    }
}
#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif
#include "stm32f10x.h"                  // Device header
#include "Delay.h"      
#include "OLED.h"
#include "Encoder.h"
#include "Timer.h"

uint16_t Num = 0; 
int main(void)
{
	OLED_Init();	//初始化OLED
	Timer_Init();	//初始化定时器
	OLED_ShowString(1,1,"Num:");	//显示Num
	while (1)
	{
		OLED_ShowNum(1,5,Num,4);	//显示Num
		//下面的这个是测试的,就是直接用内置的时钟显示器来与我们写的定时器对比
		OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);	//显示定时器计数值
	}
	
}

用光电门来模拟计数器 链接到标题

即通过传感器光电门通过GPIO端口产生电平信号,由这个信号来模拟时钟。

初始化函数的更改 链接到标题

  • 这时候就不能是默认了,手动改为外部时钟,
/*通过ETR配置外部时钟模式2*/
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00); //配置外部时钟模式2:不需要分频,不反向(指敏感性,上升沿还是下降沿等等),不使用滤波器
  • 然后就是之前操作过的对GPIO口的配置
/*配置GIOP*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入(也可以使用上拉)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA
  • 分频设置的 预分频自动重装值改小一点

    提示
    因为手动模拟没有这么快

  • 然后为了调试方便加一个

uint16_t Timer_GetCounter(void) //获取定时器计数值
{
    return TIM_GetCounter(TIM2); //返回TIM2的计数值
}

用来看TM2的计数值

输出比较OC 链接到标题

通过比较CNT与CCR的值来实现PWM输出

信息
PWM:通过改变占空比(也就是频率)来控制比如灯的亮度以及电机的速度这些连续量,下面这个图就很清楚了。 alt text
核心器件是 输出模式控制器alt text 具体看表

模式 描述
冻结 CNT=CCR时,REF保持为原状态
匹配时置有效电平 CNT=CCR时,REF置有效电平
匹配时置无效电平 CNT=CCR时,REF置无效电平
匹配时电平翻转 CNT=CCR时,REF电平翻转
强制为无效电平 CNT与CCR无效,REF强制为无效电平
强制为有效电平 CNT与CCR无效,REF强制为有效电平
PWM模式1 向上计数:CNT<CCR时,REF置有效电平,CNT≥CCR时,REF置无效电平
向下计数:CNT>CCR时,REF置无效电平,CNT≤CCR时,REF置有效电平
PWM模式2 向上计数:CNT<CCR时,REF置无效电平,CNT≥CCR时,REF置有效电平
向下计数:CNT>CCR时,REF置有效电平,CNT≤CCR时,REF置无效电平

其中PWM模式是常用的,可以用于处理连续信号, 更多的也是见cd的ppt。

PWM模块 链接到标题

初始化函数 链接到标题

目标是打通 alt text

GPIO 链接到标题

  • copy 一下GPIO.c的GPIO初始化函数,或者……前面很多写好的模块都有,更改一下对应的是输出引脚
  • 注意GPIO口的模式要设置为复用输出模式GPIO_Mode_AF_PP,原因如下图: alt text 即若要让输出控制交给定时器,就要设置为复用输出模式

时基单元 链接到标题

也是copy一下Timer.c的时基单元配置,可以去掉NVIC的配置

输出比较模式 链接到标题

  • 使用TIM_OC……Init类函数,比如这里使用的TIM_OC1Init,
  • 这个函数自带一个初始化函数,给结构体赋一个默认值,可以在调用后再调整个别自己需要更改的值。
    提示
    • 在初始上面的结构体的时候有一个量是GPIO口,代表着这个PWM会出输出在哪个GPIO口上,关于如何配置实际上是有讲究的,在引脚定义表 alt text 中已经详细给出了哪些功能只能出现在哪些引脚上(默认复用功能),
    • 除此之外为了防止多个信号在一个GPIO口上的冲突,stm32还添加了一定的重定义功能,允许在某些情况下将冲突的端口分开。这在进一步的编写中会遇到

参数计算 链接到标题

  • 频率: $$ f_{PWM} = \frac{CK_PSC}{(PSC+1)(ARR+1)} $$
  • 占空比: $$ Duty = \frac{CCR}{ARR+1} $$
  • 分辨率: $$ Reso=\frac{1}{ARR+1} $$ 这里CK_PSC是时钟频率,是已知的,其他都是自己设置,CCR通过 TIM_OCInitStructure.TIM_Pulse来设置,以获得想要的参数。

单独更改比较器值 链接到标题

一个内置函数即可

呼吸灯 链接到标题

使用上述的函数,编写程序,在主函数中不断改变占空比(比如先加到最大值,然后递减,减到最小值,如此周期往复),但是在写好程序后笔者发现自己的程序一直在报错

User\main.c(5): error:  #130: expected a "{"

即使删剩主死循环也无果,推测是模块冲突?只好重建了一下test文件目录。

int main(void)

{
	OLED_Init();
	PWM_Init();
	while(1)
	{
		for(i=0;i<=100;i++)
		{
			PWM_SetCompare1(i);
			Delay_ms(10);
		}
		for(i=100;i>=0;i--)
		{
			PWM_SetCompare1(i);
			Delay_ms(10);
		}
	}
}
#include"stm32f10x.h"

void PWM_Init(void)
{

    //初始化时基单元
    /*选用TIM2*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2时钟

    /*配置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);

    TIM_InternalClockConfig(TIM2);//这行可写可不写,因为默认就是内部时钟

    /*配置时基单元*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化TIM2
    //给结构体成员赋值
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//用于设置时钟分频,从而用于给滤波器工作
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
    //下面的三个参数就是设置计数器的频率的了,比如如果想定时1s
    TIM_TimeBaseStructure.TIM_Period = 100-1; //计数器的值
    TIM_TimeBaseStructure.TIM_Prescaler = 720-1; //分频值
    TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //重复计数器值,高级定时器才有,这里没有就给0
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化TIM2


    /*使能定时器*/
    TIM_Cmd(TIM2,ENABLE); //使能TIM2

    //初始化输出比较单元
    ///先对结构体初始化
    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; //设置比较值,决定占空比
    TIM_OC1Init(TIM2, &TIM_OCInitStructure); 
    
}
void PWM_SetCompare1(uint16_t Compare)
{
    TIM_SetCompare1(TIM2,Compare);
}

引脚重映射 链接到标题

通过AFIO的配置来实现引脚的重映射,首先就是要开启其时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //使能AFIO时钟

然后通过下面的两句分别复用以及解除原来的复用

//重映射
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //重映射TIM2到PA0     GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //重映射TIM2到PA0
警告

然后,笔者在这里贪图ai补全一时爽,错写成了

GPIO_Remap_SWJ_GDisable

意味着调试端口被禁用,导致无法再用stlink调试,stm32秒变砖头, 解决方案

  • 视频中说用串口发送一个正常程序过去
  • 还有一种方法来源网上,即按下reset键后迅速在电脑上启动烧录,在第一次之后无需再按reset即可正常烧录

如此,PA0就挪到了PA15上了,之后的GPIO口都用PA15来代替PA0了,即下面这一行

GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;

PWM驱动舵机 链接到标题

好,不过这一章节无需重映射,所以援引呼吸灯编好的模块,将上面的重映射部分注释掉。

  • 这里需要注意的是舵机的PWM频率是50Hz,所以ARR要设置为20000-1 然后改的主要是主程序
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"      
    #include "OLED.h"
    #include "Servo.h"
    #include "Key.h"
    uint8_t KeyNum;
    float Angle = 0;
    int main(void)
    
    {
    	OLED_Init();
    	Servo_Init();
    	Key_Init();
    	OLED_ShowString(1,1, "Angle:");
    	while (1)
    	{
    		KeyNum = Key_GetNum();
    		if (KeyNum == 1)
    		{
    			Angle += 30;
    			if (Angle > 180)
    				Angle = 0;
    
    		}
    		OLED_ShowNum(1,7, Angle, 3);
    		Servo_SetAngle(Angle);
    	}
    	
    }
    

    增加了一个角度变换函数而已,为了方便再模块化了一下

    void Servo_Init(void)
    {
        PWM_Init(); 
    }
    void Servo_SetAngle(float Angle)
    {
        PWM_SetCompare2(Angle/180*2000+500); //500~2500
    }
    
    • Pin_0改为Pin_1(我们用的PA1来控制舵机)
    • 改通道:
      • TIM_OC1InitTIM_OC2Init
      • PWM_SetCompare1PWM_SetCompare2 {< notice tip >}} 原因参见中文手册119页的表43,有如下片段
    复用功能 没有重映像 ……
    TIM2_CH1_ETR PA0 ……
    TIM2_CH2 PA1 ……

    已清晰标明对应的GPIO要用哪个时钟和通道了 所以改完后的代码:

    #include"stm32f10x.h"
    
    void PWM_Init(void)
    {
    
        //初始化时基单元
        /*选用TIM2*/
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能TIM2时钟
        //使能AFIO时钟(用于下面的重映射)
        // RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); 
        // //重映射
        // GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //重映射TIM2到PA0
        //  GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //重映射TIM2到PA0
        /*配置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_1;
        GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
        GPIO_Init(GPIOA,&GPIO_InitStructure);
    
        TIM_InternalClockConfig(TIM2);//这行可写可不写,因为默认就是内部时钟
    
        /*配置时基单元*/
        TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
        TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化TIM2
        //给结构体成员赋值
        TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//用于设置时钟分频,从而用于给滤波器工作
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
        //下面的三个参数就是设置计数器的频率的了,比如如果想定时1s
        TIM_TimeBaseStructure.TIM_Period = 20000-1; //计数器的值
        TIM_TimeBaseStructure.TIM_Prescaler = 72-1; //分频值
        TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //重复计数器值,高级定时器才有,这里没有就给0
        TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化TIM2
    
    
        //初始化输出比较单元
        ///先对结构体初始化
        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; //设置比较值,决定占空比
        TIM_OC2Init(TIM2, &TIM_OCInitStructure); 
    		
        /*使能定时器*/
        TIM_Cmd(TIM2,ENABLE); //使能TIM2
    
        
    }
    void PWM_SetCompare2(uint16_t Compare)
    {
        TIM_SetCompare2(TIM2,Compare);
    }
    
    如此就实现了舵机的定位控制。 当然,最后来说说debug心得,本次出的低级bug为忘记改GPIO_Pin_0为GPIO_Pin_1……😂

驱动直流电机 链接到标题

在舵机的基础上增加了控制方向以及速度的函数,可以理解为一个定速过程了。

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Motor.h"
#include "Key.h"

uint8_t KeyNum;
int8_t Speed;

int main(void)
{
	OLED_Init();
	Motor_Init();
	Key_Init();
	
	OLED_ShowString(1, 1, "Speed:");
	
	while (1)
	{
		KeyNum = Key_GetNum();
		if (KeyNum == 1)
		{
			Speed += 20;
			if (Speed > 100)
			{
				Speed = -100;
			}
		}
		Motor_SetSpeed(Speed);
		OLED_ShowSignedNum(1, 7, Speed, 3);
	}
}
void Motor_Init(void)
{
    ///这一段来自于LED.c的更改
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	PWM_Init();
}

void Motor_SetSpeed(int8_t Speed)
{
	if (Speed >= 0)
	{
		GPIO_SetBits(GPIOA, GPIO_Pin_4);
		GPIO_ResetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(Speed);
	}
	else
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_4);
		GPIO_SetBits(GPIOA, GPIO_Pin_5);
		PWM_SetCompare3(-Speed);
	}
}
提示
令人非常烦躁的是,讲解者似乎对PWM_SetComparex这个函数用得非常随意,已经从1、2、3都用过了,每次都要重新定义……