[TOC]

任务一:GPIO输入输出

原理(实现步骤)

  • 选择两个GPIO引脚,PB0负责输出,PB1作为输入端。
  1. GPIO配置
    • 设置GPIOB0,B1为推挽输出模式
  2. 控制逻辑
    • 第一个gpio输出高低电平,控制第一个灯亮灭
    • 每次循环开始时,ReadInputDataBit函数读取GPIOB0的输入寄存器(IDR),记为int led1_status
    • 根据led1_status,延时200ms将GPIOB1调整为与GPIO0 一致
    • 调整一致以后,延时200ms将GPIO0的状态反过来,看起来就是led1先灭了
    • 如此往复,产生led2跟着led1跑的的感觉

代码如下:(lLEDD_ON 和LED_OFF为我自定义的函数,详见任务三——代码模块化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "stm32f10x.h"      // Device header
#include "LED.h"
#include "DELAY.h"


int main(void)
{
LED_Init(1);
LED_Init(2);

while(1)
{
int led1_status = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0);
if(led1_status)
{
Delay_ms(200);//ÑÓʱһÏÂ
LED_OFF(2);
Delay_ms(200);
LED_ON(1);
}
else
{
Delay_ms(200);
LED_ON(2);
Delay_ms(200);
LED_OFF(1);
}
}
}

视频:

点击查看视频

任务二 PWM控制舵机

1.pwm驱动呼吸灯

概念:(oc为输出比较,oi为输入捕获,cc为输入捕获和输出比较的单元)

image-20241017103420122

原理

ccr为我们自己设定的一个数值,cnt为周期自增的,每当cnt结束一个周期,就触发一次事件(重新自增),在这个周期内,比较cnt和ccr的大小,来调控pwm输出image-20241017104240355

image-20241017104928031

输出模式控制器的不同状态:

image-20241017105149834

  • 冻结:用于暂停

  • 置有效电平 == 置高电平 置无效电平 == 置低电平

  • 电平反转:方便的输出一个频率可调,占空比为50%的pwm波

  • 强制:可调高低的冻结

  • pwm模式:最常用image-20241017105859083

    image-20241017105938711

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include "stm32f10x.h"                  // Device header

void PWM_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//使能afio时钟
GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2,ENABLE);//重映射(如果是jatg调试端口,再加上下面一句)
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE); //用afio将jtag解除

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//注意这个位置设置为复用推挽输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);




TIM_InternalClockConfig(TIM2);

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //ָ设置时钟分频(1分频)
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStructure.TIM_Period=100-1; //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=720-1; //72mzh / 7200 = 10k ,72mhz / 7200 = 10k //PSC
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数(高级计时器有,现在不用)
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

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;//设置ccr寄存器值
TIM_OC1Init(TIM2,&TIM_OCInitStructure);

TIM_Cmd(TIM2,ENABLE);
}

void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2,Compare);
}



//下面是mian函数
#include "stm32f10x.h" // Device header
#include "LED.h"
#include "DELAY.h"
#include "OLED.h"
#include "Timer.h"
#include "PWM.h"

uint8_t i;
int main()
{
PWM_Init();
OLED_Init();

while(1)
{
for(i=0;i<=100;i++)
{
OLED_ShowNum(2,1,i,3);
PWM_SetCompare1(i);
Delay_ms(10);
}
}
}

视频:

点击查看视频

2.pwm驱动舵机(主任务2)

技术点介绍

定时器的比较输出:

原理:主要依靠cnt(自增计数器)和ccr(我们自己设定的一个值)进行比较,当cnt等于ccr的时候,触发事件–重新计数(详见pwm驱动呼吸点灯中的图片)

详细过程:

计数器(CNT)根据定时器的时钟频率和预分频器的设置自动增加(或减少)。当计数器达到 ARR(自动重装载寄存器)的值时,计数器会重置,并从头开始计数。

比较值(CCR)每个定时器通道都有一个比较寄存器(CCRx),它存储定时器的比较值。当计数器的值(CNT)与比较寄存器的值相等时,定时器会触发一个事件,改变输出引脚的状态

ARR(自动重装载寄存器)设置了计数器的最大值,也就是 PWM 信号的周期。计数器在每次达到 ARR的值后会自动重置,开始新的周期。

CCR(捕获比较寄存器)用于设置比较值,也就是定时器在这个值时改变输出状态

pwm是什么:

在一个很短的周期内,我们规定一定一定时间高电平,一段时间的低电平(即占空比),这样电平就会以较高的频率进行高低变化,输出一个方波,这个波就是pwm波,可以用来驱动舵机

pwm的实现:

就像上面说的那样,pwm的实现首先要规定一个很小的周期,以舵机为例,想要控制舵机,周期就要设置为20ms,对应的arr和psc要符合20ms的周期

核心代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	TIM_TimeBaseInitStructure.TIM_Period=20000-1;   //ARR
TIM_TimeBaseInitStructure.TIM_Prescaler=72-1; //PSC
/*
计算定时器的计数频率
定时器计数频率由 系统时钟频率 / (PSC + 1) 决定。
比如,如果 PSC = 71,那么定时器的计数频率为:
计数频率=72MHz除以(71+1)=1MHz

这意味着定时器每微秒计数一次。

设置 ARR 值
在 1MHz 计数频率下,20ms 对应的计数值为:
ARR=20ms×1MHz=20000
因此,将 ARR 设置为 20000-1 就可以产生一个 20ms 周期的 PWM 信号。*/

void PWM_SetCompare2(uint16_t Compare)//初始化哪个通道,就compare几
{
TIM_SetCompare2(TIM5,Compare); //占空比
}
void Servo_SetAngle(float Angele)
{
PWM_SetCompare2(Angele/180*2000+500); //把角度线性映射到占空比

}

总代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "stm32f10x.h"      // Device header
#include "LED.h"
#include "DELAY.h"
#include "OLED.h"
#include "Timer.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNumAfter = 0;
uint8_t KeyNumBefore = 0;

float Angle = 90;
int main()
{
Servo_Init();
OLED_Init();
OLED_ShowString(1,1,"Angle:");
OLED_ShowString(2,1,"KNum:");
Key_Init();

Servo_SetAngle(Angle);

while(1)
{

KeyNumAfter = Key_GetNum();//设置一个中间缓冲变量,防止oled上的key标识一闪而过
if(KeyNumAfter!=0)
{
KeyNumBefore = KeyNumAfter;
}
OLED_ShowNum(2,7,KeyNumBefore,5);
if(KeyNumAfter)
{
Angle+=30;
if(Angle>180)
{
Angle = 0;
}
Delay_ms(30);
}
Servo_SetAngle(Angle);
OLED_ShowNum(1,7,Angle,3);


}
}


视频现象:

点击查看视频

任务三:阅读参考手册

gpio原理:

5d9aacdc3cbe0d2f22295cf8c98733d

输入
  1. 图中上半部分是输入示意图,信号从io引脚进入后,先经过image-20241017202833215这个结构(用于控制是上拉输入还是下拉输入的结构),然后下面是这个结构image-20241017203040197这个肖特基结构的作用是把信号分为高低电平(模拟信号转化为数字信号(当然更有可能是把数字信号处理一下变得更稳定)),如果在这个结构直接就直接输入,那就是直接输入模拟信号(走最上面一路)(记为模拟输入),经过肖特基触发器以后的信号就被转化为了稳定的数字信号
  2. 从肖特基触发器出来以后,有两条路,一条是复用(使用remap函数)输入,一条是直接放入输入数据寄存器,直接输入了
输出
  1. 下面有两条路径进入,一条是进入位设置/清除寄存器,这个寄存器的作用是单独设置某位,而不影响其他位
  2. 第二条直接控制输出数据寄存器,这个是直接控制整个gpio口的所有位
  3. 最下面一条是来自片上外设的复用功能输入,例子可以看前面的pwm控制led(将gpioa15复用)

tim计时器相关

详见文末 其他——tim定时器学习笔记

进阶任务–函数封装–流水灯

技术介绍

跨文件调用

在主函数中include头文件,这个头文件用来存放函数声明

他所声明的函数存放于同名的.c文件中

封装的意义

代码的封装能使代码更加简洁,更重要的是,它可以实现编程的模块化,对各个功能的增添,修补等更方便,如果工程较大的话,代码的封装是必不可少的

条件编译的使用

以刚刚写的 waterfall.h为例

1
2
3
4
5
6
7
#ifndef __WATERFALL_H //如果没有定义这个头文件
#define __WATERFALL_H//那么定义它

void led_waterfall(uint8_t leds);

#endif//定义结束标志

条件编译的使用,能够防止重复定义导致错误,因为c语言引入头文件的方式就是直接把代码块复制粘贴到相应位置,如果同一个头文件在程序中粘贴两次,那就会出现大量重复定义的错误

实现过程

  1. 首先,定义一个枚举类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    typedef enum 
    {
    led1 = 0x01 << 0,
    led2 = 0x01 << 1,
    led3 = 0x01 << 2,
    led4 = 0x01 << 3,
    led5 = 0x01 << 4,
    led6 = 0x01 << 5
    }leds;
  2. 创建一个头文件,一个库文件

  3. 首先,定义最关键的函数,也是唯一和main函数链接的函数led_waterfall(uint8_t leds)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    void led_waterfall(uint8_t leds)
    {
    LED_Init();
    TIM_Init();
    if ((leds & 0x01) == 0x01)
    {
    led_should_on[led_count] = 1;
    led_count++;
    }
    if ((leds & (0x01 << 1)) == (0x01 << 1))
    {
    led_should_on[led_count] = 2;
    led_count++;
    }

    if ((leds & (0x01 << 2)) == (0x01 << 2))
    {
    led_should_on[led_count] = 3;
    led_count++;
    }
    if ((leds & (0x01 << 3)) == (0x01 << 3))
    {
    led_should_on[led_count] = 4;
    led_count++;
    }
    if ((leds & (0x01 << 4)) == (0x01 << 4))
    {
    led_should_on[led_count] = 5;
    led_count++;
    }
    if ((leds & (0x01 << 5)) == (0x01 << 5))
    {
    led_should_on[led_count] = 6;
    led_count++;
    }


    }
    • 这个函数巧妙地运用了二进制的位运算,函数的形参看上去只能传入一个整数,实际上通过之前的枚举类型,再结合二进制的按位或运算,可以只用一个二进制数把多个信息传递进函数(每位的0和1代表每个灯的参数)
    • 进入函数后,再把mix得到的一个二进制数和各自只有自己为1的二进制数进行按位与操作,就能把自身从按位或之后的二进制数中剥离出来
  4. 接着,初始化led灯,配置tim2计时器,配置nvic

    (其中包含对pb4的复用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    //初始化GPIO

    void LED_Init(void)
    {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);

    //给pb4 remap
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//afio (to remap pb4)
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //用afio将jtag解除


    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    //pb0
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_0);
    //pb1
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_1);
    //pb5
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_5);
    //pf11
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_Init(GPIOF,&GPIO_InitStructure);
    GPIO_SetBits(GPIOF,GPIO_Pin_11);
    //pf12
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_Init(GPIOF,&GPIO_InitStructure);
    GPIO_SetBits(GPIOF,GPIO_Pin_12);


    //pb4
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_4);
    }

    //===============================
    //开启tim定时器
    void TIM_Init(void)
    {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    TIM_TimeBaseInitTypeDef TIM_InitStructure;
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_InitStructure.TIM_Period = 10000-1;
    TIM_InitStructure.TIM_Prescaler = 7200-1;
    TIM_InitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2,&TIM_InitStructure);

    TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清空中断标志位
    TIM_ITConfig(TIM2,TIM_FLAG_Update,ENABLE);//开启中断到nvic的通路

    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);



    TIM_Cmd(TIM2,ENABLE);
    }
  5. 最后一步,事件函数的编写

    • 每秒钟进行一次操作,先打开当前要开的灯,再根据上一个灯的位置坝上一个灯熄灭
    • 根据数组中下一个元素是否为0来判断i应该加一还是清零
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    int i = 0;

    void TIM2_IRQHandler(void)
    {
    if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
    {
    LED_ON(led_should_on[i]);
    if(i>0)
    {
    LED_OFF(led_should_on[i-1]);
    }

    if(i==0)
    {
    int j = led_count-1;
    LED_OFF(led_should_on[j]);
    }

    if(led_should_on[i+1]==0)
    {
    i = 0;
    }
    else
    {
    i++;
    }
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    }
    }

    完整代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    #include "stm32f10x.h"                  // Device header


    //============================
    //初始化GPIO

    void LED_Init(void)
    {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);

    //给pb4 remap
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//afio (to remap pb4)
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //用afio将jtag解除


    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    //pb0
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_0);
    //pb1
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_1);
    //pb5
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_5);
    //pf11
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
    GPIO_Init(GPIOF,&GPIO_InitStructure);
    GPIO_SetBits(GPIOF,GPIO_Pin_11);
    //pf12
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
    GPIO_Init(GPIOF,&GPIO_InitStructure);
    GPIO_SetBits(GPIOF,GPIO_Pin_12);


    //pb4
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOB,&GPIO_InitStructure);
    GPIO_SetBits(GPIOB,GPIO_Pin_4);
    }
    //=================================================



    //=================================================
    //设置开关led的函数

    void LED_ON(int i)
    {
    switch(i)
    {
    case 1:
    GPIO_ResetBits(GPIOB,GPIO_Pin_0);
    break;
    case 2:
    GPIO_ResetBits(GPIOB,GPIO_Pin_1);
    break;
    case 3:
    GPIO_ResetBits(GPIOB,GPIO_Pin_5);
    break;
    case 4:
    GPIO_ResetBits(GPIOB,GPIO_Pin_4);
    break;
    case 5:
    GPIO_ResetBits(GPIOF,GPIO_Pin_11);
    break;
    case 6:
    GPIO_ResetBits(GPIOF,GPIO_Pin_12);
    break;
    default:
    break;
    }

    }

    void LED_OFF(int i)
    {
    switch(i)
    {
    case 1:
    GPIO_SetBits(GPIOB,GPIO_Pin_0);
    break;
    case 2:
    GPIO_SetBits(GPIOB,GPIO_Pin_1);
    break;
    case 3:
    GPIO_SetBits(GPIOB,GPIO_Pin_5);
    break;
    case 4:
    GPIO_SetBits(GPIOB,GPIO_Pin_4);
    break;
    case 5:
    GPIO_SetBits(GPIOF,GPIO_Pin_11);
    break;
    case 6:
    GPIO_SetBits(GPIOF,GPIO_Pin_12);
    break;
    default:
    break;
    }

    }


    //====================================================





    //===============================
    //开启tim定时器
    void TIM_Init(void)
    {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
    TIM_TimeBaseInitTypeDef TIM_InitStructure;
    TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_InitStructure.TIM_Period = 10000-1;
    TIM_InitStructure.TIM_Prescaler = 7200-1;
    TIM_InitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2,&TIM_InitStructure);

    TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清空中断标志位
    TIM_ITConfig(TIM2,TIM_FLAG_Update,ENABLE);//开启中断到nvic的通路

    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);



    TIM_Cmd(TIM2,ENABLE);
    }
    //=================================================

    int led_should_on[6]; //存要开关的灯,多留一个0位,用于标记
    int led_count = 0;

    void led_waterfall(uint8_t leds)
    {
    LED_Init();
    TIM_Init();
    if ((leds & 0x01) == 0x01)
    {
    led_should_on[led_count] = 1;
    led_count++;
    }
    if ((leds & (0x01 << 1)) == (0x01 << 1))
    {
    led_should_on[led_count] = 2;
    led_count++;
    }

    if ((leds & (0x01 << 2)) == (0x01 << 2))
    {
    led_should_on[led_count] = 3;
    led_count++;
    }
    if ((leds & (0x01 << 3)) == (0x01 << 3))
    {
    led_should_on[led_count] = 4;
    led_count++;
    }
    if ((leds & (0x01 << 4)) == (0x01 << 4))
    {
    led_should_on[led_count] = 5;
    led_count++;
    }
    if ((leds & (0x01 << 5)) == (0x01 << 5))
    {
    led_should_on[led_count] = 6;
    led_count++;
    }


    }


    //=================================================
    //具体事件编写

    int i = 0;


    void TIM2_IRQHandler(void)
    {
    if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
    {
    LED_ON(led_should_on[i]);
    if(i>0)
    {
    LED_OFF(led_should_on[i-1]);
    }

    if(i==0)
    {
    int j = led_count-1;
    LED_OFF(led_should_on[j]);
    }

    if(led_should_on[i+1]==0)
    {
    i = 0;
    }
    else
    {
    i++;
    }
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
    }
    }



    视频:

    点击查看视频

进阶任务:共地和上拉\下拉电阻的作用

1. 共地

共地是指电路中的不同部分(如电源、传感器、外设、处理器等)共享同一个GND作为电压为零的参考点,所有信号电压都是相对于这个参考点来测量的,避免信号传输过程中产生误差。

如果不共地,信号可能会不稳定,不共地的电路可能会在地线之间形成环路,产生电磁干扰,可能损坏电路元件

2. 上拉电阻和下拉电阻

上拉电阻 和 下拉电阻 是用来稳定数字电路中未连接状态时的信号电平的电阻器。它们用于防止输入引脚悬空,并确保其处于已知的逻辑电平。上拉电阻连接在 信号引脚和电源正极 之间。当输入引脚没有驱动时,上拉电阻将引脚电压拉高到高电平,确保信号处于稳定的高电平。下拉电阻连接信号引脚和地 之间。当输入引脚没有驱动时,下拉电阻将引脚电压拉低到逻辑低电平,确保信号处于稳定的低电平。

分析图片

image-20241018005115582这个图片中,当开关断开时,nrst直接连在上拉电阻上,为稳定的高电平,当开关闭合时,nrst接地,为低电平

其它——tim定时器学习笔记

定时器外部中断

image-20241016182413622

image-20241016182924178

  • 主从触发模式的作用:它能让内部的硬件在不受程序的控制下实现自动运行(可以减轻cpu的负担)

基本定时器工作原理:(只能向上计数)

image-20241016184838791

  • 把下面那个U映射到触发输出(TRGO),无需频繁中断,也可进行dac转换,触发定时器的的更新
  • 上面那个ui,计数值等于更新重装值产生的中断,叫做“更新中断”

通用定时器和高级定时器还支持向下计数和中央对齐计数

通用定时器

模式二——最简单,最直接

image-20241016203406894

模式1——trgi当作外部时钟使用:(主要使用etr)

image-20241016202456391

  • 定时器的级联:黄色那条线,一个tim计时器的事件可以驱动另一个计时器的itr,这个计时器通过trgi输出,就实现了两个计时器的级联

三种定时器的区别:

基本定时器:基本定时器有计数器、预分频器和自动重装载寄存器,能完成基本的计数和触发中断,主要用于生成定时中断。没有输入捕获、输出比较和 PWM 功能。常用于 DAC 触发或简单的时间间隔生成

通用定时器:通用定时器具备计数、捕获比较、PWM 生成等功能。

高级定时器:包括复杂的 PWM 输出、死区控制、互补输出等功能

定时中断基本结构

image-20241016204528525

中断输出控制:中断输出的允许位,在一个时钟配置里,可能有很多地方申请中断,如果用不到,就把中断输出允许位设置为不允许,要用的话就设为允许

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "stm32f10x.h"      // Device header
#include "LED.h"
#include "DELAY.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;
void TIM2_IRQHandler(void);

int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Num:");

while(1)
{
OLED_ShowNum(1,5,Num,5);
}
}

void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
//这个程序使用内部时钟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//Timer.c

#include "stm32f10x.h" // Device header

void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能时钟

TIM_InternalClockConfig(TIM2);

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //ָ设置时钟分频(1分频)
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInitStructure.TIM_Period=10000-1;
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1; //72mzh / 7200 = 10k ,72mhz / 7200 = 10k
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数(高级计时器有,现在不用)
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);

TIM_ClearFlag(TIM2,TIM_FLAG_Update); //清除TIM2的更新中断标志位,确保定时器开始时没有残留的中断标志

TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//开启更新中断到nvic通路

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel= TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);

}
/* //用的时候复制进mian
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/

红外对管
pwm驱动呼吸灯

更新中