stm32学习

本文最后更新于 2024年9月4日 下午

在b站大学学习stm32时候做的笔记 [1-1] 课程简介_哔哩哔哩_bilibili

1. STM32F103C8T6概要

STM32F103C8T6

片上资源/外设(Peripheral)

片上外设

绿色部分是内核内的外设,其他就是内核外的外设(C8T6没有后面四个外设

  1. NVIC:内核中用来管理中断的设备,配置中断优先级
  2. SysTick:内核中的一个定时器,主要用于给操作系统提供定时服务,但我们不用操作系统,主要就用它来实现delay函数的功能
  3. RCC:对系统的时钟进行配置,使能各模块的时钟,在stm32中,其他外设在上电情况下是默认没有时钟的,不给时钟的情况下,操作外设无效,外设不会工作,目的是降低功耗。所以在操作外设之前必须要使能它的时钟,RCC完成时钟的使能
  4. GPIO:通用的IO口,用来电点灯,读取按键
  5. AFIO:复用IO口,完成复用功能端口的重定义,中断端口的配置
  6. EXTI:外部中断,配置好外部中断,当引脚有电平变化时就可以触发中断,让cpu处理任务
  7. TIM:stm32最常用,功能最多的外设,分为高级定时器,通用定时器(常用,可以完成定时中断,测频率,生成pwm波,配置成专用的编码器接口),基本定时器
  8. ADC:该stm32内置了12位的ADC,可以直接读取io口的模拟电压值,无需外部连接AD芯片
  9. DMA:帮助cpu完成搬运大量数据
  10. USART:UART时异步串口
  11. 通信协议:I2C SPI CAN USB
  12. CRC:一种数据校验方式,用于判断数据的正确性
  13. 看门狗:当单片机出现死循环时,看门狗可以复位程序

命名规则

命名规则

系统结构

系统结构

参照这张图,我们可以知道哪些外设对应哪些总线

芯片引脚定义

引脚定义

2. 新建stm32工程(工程模板)

[2-2] 新建工程_哔哩哔哩_bilibili

总结

  • 建立工程文件夹,Keil中新建工程,选择型号

  • 工程文件夹里建立Start、Library、User等文件夹,复制固件库里面的文件到工程文件夹

  • 工程里对应建立Start、Library、User等同名称的分组,然后将文件夹内的文件添加到工程分组里

  • 工程选项,C/C++,Include Paths内声明所有包含头文件的文件夹

  • 工程选项,C/C++,Define内定义USE_STDPERIPH_DRIVER

  • 工程选项,Debug,下拉列表选择对应调试器,Settings,Flash Download里勾选Reset and Run

    总体工程架构

1. 在keil5中新建工程

建立工程文件夹存放工程,工程文件夹名称以后还可以改,但是工程名称后面再改就比较麻烦,所以起工程模板的名称要比较通用

工程文件夹名称
工程名称
选择芯片系列
新建工程小助手

这个是keil软件的额一个新建工程小助手,可以帮助我们快速新建工程,这里直接cancel或者叉掉就行

第一步完成

2. 建立Start文件夹存放stm32的启动文件

1. 在文件资源管理器中建立Start文件夹添加所有文件

文件资源管理器中新建Start文件夹

stm32的启动文件,在\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup文件夹下,全都复制到Start文件夹下(但实际我们后面要添加到工程里的是根据我们的芯片来确定的,这个芯片要用的是后缀md.s

启动文件

选择的启动文件和芯片型号的关系:

缩写 释义 Flash容量 型号
LD_VL 小容量产品超值系列 16~32K STM32F100
MD_VL 中容量产品超值系列 64~128K STM32F100
HD_VL 大容量产品超值系列 256~512K STM32F100
LD 小容量产品 16~32K STM32F101/102/103
MD 中容量产品 64~128K STM32F101/102/103
HD 大容量产品 256~512K STM32F101/102/103
XL 加大容量产品 大于512K STM32F101/102/103
CL 互联型产品 - STM32F105/107

外核寄存器描述文件和时钟配置文件:在\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x

外核寄存器描述文件和时钟配置文件
  1. stm32f10x.h是stm32的外设寄存器描述文件,描述stm32有哪些寄存器和它对应的地址的
  2. 两个system文件是用来配置时钟的

这三个文件也复制到Start文件夹下

内核寄存器描述文件:在\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport

内核寄存器描述文件

也是复制到Start文件夹下

现在Start文件夹下是这样的:

Start文件夹

2. 在keil5里建一个组Start来添加我们需要的文件

  1. 先改名成Start,然后添加已存在文件到组”Start“中

    keil新建Start组
  2. 添加后如下(小钥匙的标识表示只读,在添加时候记得文件类型选择all files,不然有些文件你找不到,按住ctrl键依次选择我们需要的文件)

    添加所有所需文件后

3. 在工程选项中添加上这个文件夹的头文件路径,否则软件是找不到.h文件的

魔术棒 ==> c/c++ ==>include Paths 将Start文件夹路径添加进来

C/C++编译配置1

要注意的是Code Generation这里要选版本5

C/C++编译配置2

3. 建立User文件夹存放我们的代码(main.c)

  1. 在文件资源管理器中新建User文件夹

  2. 在keil5中新建User组然后新建文件main.c,注意路径要选择User文件夹下

新建User组
  1. 右键插入头文件,选择stm32f10x.h

    插入头文件
  2. 点击右上的小扳手可以对编辑器进行一些修改,tap的缩进,字体大小,编码格式等,有需要可以进行更改

    编辑器配置修改
  3. 完善代码,注意最后一行必须是空行,不然会警告

    最后一行空行

如果想要使用寄存器开发,那么工程建立到这就行了

操作寄存器开发的代码很简洁,但是不太方便,需要一直查数据手册看每一位是干啥的

4. 下载调试

魔术棒 ==> Debugger 选择我们的调试器,这里选ST-Lingk

选择调试器

然后进到setting里,如果正常连接ST-LinkSW Device是下面这样的,否则就会有error

检查是否正常连接

最后是Flash Download 勾选Reset and Run 下载程序以后立马复位并执行,注意Pack里要把Enable的打勾给去掉才能下载程序后立马执行

下载后立刻复位并执行

5. 为工程添加库函数

  1. 在文件资源管理器中新建Library文件夹用来存放库函数

  2. \STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\src下都是库函数的源文件,在\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc下都是库函数的头文件,这些全都复制到Library文件夹中

  3. 在keil5中新建组Library,添加Library文件夹下的所有文件,在工程中添加文件夹的头文件路径

但是这些库函数还不能使用,在\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template下有这几个文件是我们需要的:

添加库函数配置文件和中断文件
  1. conf文件是用来配置库函数头文件的包含关系的,还有用来参数检查的函数定义,都是所有库函数需要的

  2. 两个it文件时用来存放中断函数的

  3. 在文件资源管理器中把这3个文件复制到User目录下,然后再keil5中把这3个文件添加到User组里,然后记得把User也添加头文件路径 Include Paths

    放到我们的User文件夹下
  4. 最后还需要一个宏定义,打开stm32f10x.h ,滑到最后找到USE_STDPERIPH_DRIVER

    找到宏定义
    宏定义

    条件编译,意思是你定义了USE_STDPERIPH_DRIVER这个字符串,下面这个include conf.h才有效

    所以我们需要复制这个语句USE_STDPERIPH_DRIVER,打开工程选项,在c/c++的Define中粘贴这个字符串

    添加宏定义在C/C++的Define中

这样,我们基于库函数的工程就建完了。

3. GPIO

简要说明

•GPIO(General Purpose Input Output)通用输入输出口

•可配置为8种输入输出模式

•引脚电平:0V~3.3V,部分引脚可容忍5V(输入5v但输出最高就是3.3v)

•输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等

•输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等

GPIO基本结构

•通过配置GPIO的端口配置寄存器,端口可以配置成以下8种模式

模式名称 性质 特征
浮空输入 GPIO_Mode_IN_FLOATING 数字输入 可读取引脚电平,若引脚悬空,则电平不确定
上拉输入 GPIO_Mode_IPU 数字输入 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
下拉输入 GPIO_Mode_IPD 数字输入 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
模拟输入 GPIO_Mode_AIN 模拟输入 GPIO无效,引脚直接接入内部ADC
开漏输出 GPIO_Mode_Out_OD 数字输出 可输出引脚电平,高电平为高阻态,低电平接VSS
推挽输出 GPIO_Mode_Out_PP 数字输出 可输出引脚电平,高电平接VDD,低电平接VSS
复用开漏输出 GPIO_Mode_AF_OD 数字输出 由片上外设控制,高电平为高阻态,低电平接VSS
复用推挽输出 GPIO_Mode_AF_PP 数字输出 由片上外设控制,高电平接VDD,低电平接VSS
输入配置图
复用功能配置

3个步骤

操作GPIO的3个步骤:

  1. 使用RCC开始GPIO的时钟
  2. 使用GPIO_Init函数初始化GPIO
  3. 使用输出或者输入函数控制GPIO口

重要函数

通过相关的头文件找到函数声明,再转到对应的.c文件中查看函数定义,如前3个函数与RCC相关的都在stm32f10x_rcc.h中,GPIO相关函数在stm32f10x_gpio.h中,要学习库函数怎么使用就是去看对应的源文件

  1. AHB外设总线控制

    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
    /**
    * @brief Enables or disables the AHB peripheral clock.
    * @param RCC_AHBPeriph: specifies the AHB peripheral to gates its clock.
    *
    * For @b STM32_Connectivity_line_devices, this parameter can be any combination
    * of the following values:
    * @arg RCC_AHBPeriph_DMA1
    * @arg RCC_AHBPeriph_DMA2
    * @arg RCC_AHBPeriph_SRAM
    * @arg RCC_AHBPeriph_FLITF
    * @arg RCC_AHBPeriph_CRC
    * @arg RCC_AHBPeriph_OTG_FS
    * @arg RCC_AHBPeriph_ETH_MAC
    * @arg RCC_AHBPeriph_ETH_MAC_Tx
    * @arg RCC_AHBPeriph_ETH_MAC_Rx
    *
    * For @b other_STM32_devices, this parameter can be any combination of the
    * following values:
    * @arg RCC_AHBPeriph_DMA1
    * @arg RCC_AHBPeriph_DMA2
    * @arg RCC_AHBPeriph_SRAM
    * @arg RCC_AHBPeriph_FLITF
    * @arg RCC_AHBPeriph_CRC
    * @arg RCC_AHBPeriph_FSMC
    * @arg RCC_AHBPeriph_SDIO
    *
    * @note SRAM and FLITF clock can be disabled only during sleep mode.
    * @param NewState: new state of the specified peripheral clock.
    * This parameter can be: ENABLE or DISABLE.
    * @retval None
    */
    void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState)
  2. APB2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * @brief Enables or disables the High Speed APB (APB2) peripheral clock.
    * @param RCC_APB2Periph: specifies the APB2 peripheral to gates its clock.
    * This parameter can be any combination of the following values:
    * @arg RCC_APB2Periph_AFIO, RCC_APB2Periph_GPIOA, RCC_APB2Periph_GPIOB,
    * RCC_APB2Periph_GPIOC, RCC_APB2Periph_GPIOD, RCC_APB2Periph_GPIOE,
    * RCC_APB2Periph_GPIOF, RCC_APB2Periph_GPIOG, RCC_APB2Periph_ADC1,
    * RCC_APB2Periph_ADC2, RCC_APB2Periph_TIM1, RCC_APB2Periph_SPI1,
    * RCC_APB2Periph_TIM8, RCC_APB2Periph_USART1, RCC_APB2Periph_ADC3,
    * RCC_APB2Periph_TIM15, RCC_APB2Periph_TIM16, RCC_APB2Periph_TIM17,
    * RCC_APB2Periph_TIM9, RCC_APB2Periph_TIM10, RCC_APB2Periph_TIM11
    * @param NewState: new state of the specified peripheral clock.
    * This parameter can be: ENABLE or DISABLE.
    * @retval None
    */
    void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState)
  3. APB1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * @brief Enables or disables the Low Speed APB (APB1) peripheral clock.
    * @param RCC_APB1Periph: specifies the APB1 peripheral to gates its clock.
    * This parameter can be any combination of the following values:
    * @arg RCC_APB1Periph_TIM2, RCC_APB1Periph_TIM3, RCC_APB1Periph_TIM4,
    * RCC_APB1Periph_TIM5, RCC_APB1Periph_TIM6, RCC_APB1Periph_TIM7,
    * RCC_APB1Periph_WWDG, RCC_APB1Periph_SPI2, RCC_APB1Periph_SPI3,
    * RCC_APB1Periph_USART2, RCC_APB1Periph_USART3, RCC_APB1Periph_USART4,
    * RCC_APB1Periph_USART5, RCC_APB1Periph_I2C1, RCC_APB1Periph_I2C2,
    * RCC_APB1Periph_USB, RCC_APB1Periph_CAN1, RCC_APB1Periph_BKP,
    * RCC_APB1Periph_PWR, RCC_APB1Periph_DAC, RCC_APB1Periph_CEC,
    * RCC_APB1Periph_TIM12, RCC_APB1Periph_TIM13, RCC_APB1Periph_TIM14
    * @param NewState: new state of the specified peripheral clock.
    * This parameter can be: ENABLE or DISABLE.
    * @retval None
    */
    void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState)
  4. GPIO控制函数(常用,一个初始化GPIO,一个初始化GPIO结构体,四个读取GPIO,四个写入GPIO)具体还是看gpio.c文件吧

    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
       /**
    * @brief Initializes the GPIOx peripheral according to the specified
    * parameters in the GPIO_InitStruct.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @param GPIO_InitStruct: pointer to a GPIO_InitTypeDef structure that
    * contains the configuration information for the specified GPIO peripheral.
    * @retval None
    */
    void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

    /**
    * @brief Fills each GPIO_InitStruct member with its default value.
    * @param GPIO_InitStruct : pointer to a GPIO_InitTypeDef structure which will
    * be initialized.
    * @retval None
    */
    void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct)


    /**
    * @brief Reads the specified input port pin.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @param GPIO_Pin: specifies the port bit to read.
    * This parameter can be GPIO_Pin_x where x can be (0..15).
    * @retval The input port pin value.
    */
    uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)


    /**
    * @brief Reads the specified GPIO input data port.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @retval GPIO input data port value.
    */
    uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx)


    /**
    * @brief Reads the specified output data port bit.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @param GPIO_Pin: specifies the port bit to read.
    * This parameter can be GPIO_Pin_x where x can be (0..15).
    * @retval The output port pin value.
    */
    uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)


    /**
    * @brief Reads the specified GPIO output data port.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @retval GPIO output data port value.
    */
    uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx)


    /**
    * @brief Sets the selected data port bits.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @param GPIO_Pin: specifies the port bits to be written.
    * This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
    * @retval None
    */
    void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)


    /**
    * @brief Clears the selected data port bits.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @param GPIO_Pin: specifies the port bits to be written.
    * This parameter can be any combination of GPIO_Pin_x where x can be (0..15).
    * @retval None
    */
    void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)


    /**
    * @brief Sets or clears the selected data port bit.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @param GPIO_Pin: specifies the port bit to be written.
    * This parameter can be one of GPIO_Pin_x where x can be (0..15).
    * @param BitVal: specifies the value to be written to the selected bit.
    * This parameter can be one of the BitAction enum values:
    * @arg Bit_RESET: to clear the port pin
    * @arg Bit_SET: to set the port pin
    * @retval None
    */
    void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal)

    /**

    * @brief Writes data to the specified GPIO data port.
    * @param GPIOx: where x can be (A..G) to select the GPIO peripheral.
    * @param PortVal: specifies the value to be written to the port output data register.
    * @retval None
    */
    void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal)



    ## LED闪烁代码

    工程架构(延时函数是用的江协提供的,放到System文件夹下)

    ![工程架构](https://cdn.jsdelivr.net/gh/Qingyun0118/myblog@master/img/image-20240902095126221.png)

    `main.c`

    ```c
    #include "stm32f10x.h" // Device header
    #include "Delay.h"

    int main(void)
    {
    /*开启时钟*/
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
    //使用各个外设前必须开启时钟,否则对外设的操作无效

    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO引脚,赋值为第0号引脚
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz

    GPIO_Init(GPIOA, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
    //函数内部会自动根据结构体的参数配置相应寄存器
    //实现GPIOA的初始化

    /*主循环,循环体内的代码会一直循环执行*/
    while (1)
    {
    /*设置PA0引脚的高低电平,实现LED闪烁,下面展示3种方法*/

    /*方法1:GPIO_ResetBits设置低电平,GPIO_SetBits设置高电平*/
    GPIO_ResetBits(GPIOA, GPIO_Pin_0); //将PA0引脚设置为低电平
    Delay_ms(500); //延时500ms
    GPIO_SetBits(GPIOA, GPIO_Pin_0); //将PA0引脚设置为高电平
    Delay_ms(500); //延时500ms

    /*方法2:GPIO_WriteBit设置低/高电平,由Bit_RESET/Bit_SET指定*/
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_RESET); //将PA0引脚设置为低电平
    Delay_ms(500); //延时500ms
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, Bit_SET); //将PA0引脚设置为高电平
    Delay_ms(500); //延时500ms

    /*方法3:GPIO_WriteBit设置低/高电平,由数据0/1指定,数据需要强转为BitAction类型*/
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)0); //将PA0引脚设置为低电平
    Delay_ms(500); //延时500ms
    GPIO_WriteBit(GPIOA, GPIO_Pin_0, (BitAction)1); //将PA0引脚设置为高电平
    Delay_ms(500); //延时500ms
    }
    }

LED流水灯代码

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; //GPIO引脚,赋值为所有引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz

GPIO_Init(GPIOA, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOA的初始化

/*主循环,循环体内的代码会一直循环执行*/
while (1)
{
/*使用GPIO_Write,同时设置GPIOA所有引脚的高低电平,实现LED流水灯*/
GPIO_Write(GPIOA, ~0x0001); //0000 0000 0000 0001,PA0引脚为低电平,其他引脚均为高电平,注意数据有按位取反
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0002); //0000 0000 0000 0010,PA1引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0004); //0000 0000 0000 0100,PA2引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0008); //0000 0000 0000 1000,PA3引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0010); //0000 0000 0001 0000,PA4引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0020); //0000 0000 0010 0000,PA5引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0040); //0000 0000 0100 0000,PA6引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
GPIO_Write(GPIOA, ~0x0080); //0000 0000 1000 0000,PA7引脚为低电平,其他引脚均为高电平
Delay_ms(100); //延时100ms
}
}

蜂鸣器代码

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

int main(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
//使用各个外设前必须开启时钟,否则对外设的操作无效

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure; //定义结构体变量

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //GPIO模式,赋值为推挽输出模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //GPIO引脚,赋值为第12号引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //GPIO速度,赋值为50MHz

GPIO_Init(GPIOB, &GPIO_InitStructure); //将赋值后的构体变量传递给GPIO_Init函数
//函数内部会自动根据结构体的参数配置相应寄存器
//实现GPIOB的初始化

/*主循环,循环体内的代码会一直循环执行*/
while (1)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //将PB12引脚设置为低电平,蜂鸣器鸣叫
Delay_ms(100); //延时100ms
GPIO_SetBits(GPIOB, GPIO_Pin_12); //将PB12引脚设置为高电平,蜂鸣器停止
Delay_ms(100); //延时100ms
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //将PB12引脚设置为低电平,蜂鸣器鸣叫
Delay_ms(100); //延时100ms
GPIO_SetBits(GPIOB, GPIO_Pin_12); //将PB12引脚设置为高电平,蜂鸣器停止
Delay_ms(700); //延时700ms
}
}

按键控制LED代码

工程架构(Hardware文件夹存放硬件驱动代码):

工程架构

LED.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef __LED_H
#define __LED_H

void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED1_Turn(void);
void LED2_ON(void);
void LED2_OFF(void);
void LED2_Turn(void);

#endif

LED.c

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
#include "stm32f10x.h"                  // Device header

/**
* 函 数:LED初始化
* 参 数:无
* 返 回 值:无
*/
void LED_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA1和PA2引脚初始化为推挽输出

/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); //设置PA1和PA2引脚为高电平
}

/**
* 函 数:LED1开启
* 参 数:无
* 返 回 值:无
*/
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为低电平
}

/**
* 函 数:LED1关闭
* 参 数:无
* 返 回 值:无
*/
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //设置PA1引脚为高电平
}

/**
* 函 数:LED1状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1); //则设置PA1引脚为低电平
}
}

/**
* 函 数:LED2开启
* 参 数:无
* 返 回 值:无
*/
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为低电平
}

/**
* 函 数:LED2关闭
* 参 数:无
* 返 回 值:无
*/
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //设置PA2引脚为高电平
}

/**
* 函 数:LED2状态翻转
* 参 数:无
* 返 回 值:无
*/
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); //则设置PA2引脚为低电平
}
}

key.h

1
2
3
4
5
6
7
8
#ifndef __KEY_H
#define __KEY_H

void Key_Init(void);
uint8_t Key_GetNum(void);

#endif

key.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
* 函 数:按键初始化
* 参 数:无
* 返 回 值:无
*/
void Key_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB1和PB11引脚初始化为上拉输入
}

/**
* 函 数:按键获取键码
* 参 数:无
* 返 回 值:按下按键的键码值,范围:0~2,返回0代表没有按键按下
* 注意事项:此函数是阻塞式操作,当按键按住不放时,函数会卡住,直到按键松手
*/
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0; //定义变量,默认键码值为0

if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //读PB1输入寄存器的状态,如果为0,则代表按键1按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 1; //置键码为1
}

if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0) //读PB11输入寄存器的状态,如果为0,则代表按键2按下
{
Delay_ms(20); //延时消抖
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0); //等待按键松手
Delay_ms(20); //延时消抖
KeyNum = 2; //置键码为2
}

return KeyNum; //返回键码值,如果没有按键按下,所有if都不成立,则键码为默认值0
}

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"

uint8_t KeyNum; //定义用于接收按键键码的变量

int main(void)
{
/*模块初始化*/
LED_Init(); //LED初始化
Key_Init(); //按键初始化

while (1)
{
KeyNum = Key_GetNum(); //获取按键键码

if (KeyNum == 1) //按键1按下
{
LED1_Turn(); //LED1翻转
}

if (KeyNum == 2) //按键2按下
{
LED2_Turn(); //LED2翻转
}
}
}

光敏传感器控制蜂鸣器代码

工程框架:

工程架构

Buzzer.h

1
2
3
4
5
6
7
8
9
10
#ifndef __BUZZER_H
#define __BUZZER_H

void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
void Buzzer_Turn(void);

#endif

Buzzer.c

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
#include "stm32f10x.h"                  // Device header

/**
* 函 数:蜂鸣器初始化
* 参 数:无
* 返 回 值:无
*/
void Buzzer_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB12引脚初始化为推挽输出

/*设置GPIO初始化后的默认电平*/
GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为高电平
}

/**
* 函 数:蜂鸣器开启
* 参 数:无
* 返 回 值:无
*/
void Buzzer_ON(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为低电平
}

/**
* 函 数:蜂鸣器关闭
* 参 数:无
* 返 回 值:无
*/
void Buzzer_OFF(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12); //设置PB12引脚为高电平
}

/**
* 函 数:蜂鸣器状态翻转
* 参 数:无
* 返 回 值:无
*/
void Buzzer_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0) //获取输出寄存器的状态,如果当前引脚输出低电平
{
GPIO_SetBits(GPIOB, GPIO_Pin_12); //则设置PB12引脚为高电平
}
else //否则,即当前引脚输出高电平
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //则设置PB12引脚为低电平
}
}

LightSensor.h

1
2
3
4
5
6
7
8
#ifndef __LIGHT_SENSOR_H
#define __LIGHT_SENSOR_H

void LightSensor_Init(void);
uint8_t LightSensor_Get(void);

#endif

LightSensor.c

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

/**
* 函 数:光敏传感器初始化
* 参 数:无
* 返 回 值:无
*/
void LightSensor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB13引脚初始化为上拉输入
}

/**
* 函 数:获取当前光敏传感器输出的高低电平
* 参 数:无
* 返 回 值:光敏传感器输出的高低电平,范围:0/1
*/
uint8_t LightSensor_Get(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13); //返回PB13输入寄存器的状态
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"

int main(void)
{
/*模块初始化*/
Buzzer_Init(); //蜂鸣器初始化
LightSensor_Init(); //光敏传感器初始化

while (1)
{
if (LightSensor_Get() == 1) //如果当前光敏输出1
{
Buzzer_ON(); //蜂鸣器开启
}
else //否则
{
Buzzer_OFF(); //蜂鸣器关闭
}
}
}

4. 调试方式

串口调试:通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息

显示屏调试:直接将显示屏连接到单片机,将调试信息打印在显示屏上

OLED

image-20240902112755328

OLED相关代码(放入Hardware文件夹中)

OLED.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef __OLED_H
#define __OLED_H

void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);

#endif

OLED.c

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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#include "stm32f10x.h"
#include "OLED_Font.h"

/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))

/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);

OLED_W_SCL(1);
OLED_W_SDA(1);
}

/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}

/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}

/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}

/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}

/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}

/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y); //设置Y位置
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4)); //设置X位置高4位
OLED_WriteCommand(0x00 | (X & 0x0F)); //设置X位置低4位
}

/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}

/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]); //显示下半部分内容
}
}

/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}

/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}

/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}

/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}

/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}

/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}

/**
* @brief OLED初始化
* @param 无
* @retval 无
*/
void OLED_Init(void)
{
uint32_t i, j;

for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}

OLED_I2C_Init(); //端口初始化

OLED_WriteCommand(0xAE); //关闭显示

OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);

OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);

OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);

OLED_WriteCommand(0x40); //设置显示开始行

OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置

OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置

OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);

OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);

OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);

OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);

OLED_WriteCommand(0xA4); //设置整个显示打开/关闭

OLED_WriteCommand(0xA6); //设置正常/倒转显示

OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);

OLED_WriteCommand(0xAF); //开启显示

OLED_Clear(); //OLED清屏
}

OLED_Font.h

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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
#ifndef __OLED_FONT_H
#define __OLED_FONT_H

/*OLED字模库,宽8像素,高16像素*/
const uint8_t OLED_F8x16[][16]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 0

0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1

0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2

0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3

0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4

0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5

0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6

0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7

0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8

0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9

0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10

0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14

0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15

0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16

0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17

0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18

0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19

0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20

0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21

0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22

0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23

0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24

0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25

0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26

0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27

0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28

0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29

0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30

0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31

0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32

0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33

0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34

0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35

0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36

0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37

0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38

0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39

0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40

0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41

0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42

0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43

0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44

0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45

0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46

0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47

0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48

0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49

0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50

0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51

0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52

0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53

0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54

0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55

0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56

0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57

0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58

0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59

0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60

0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61

0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62

0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63

0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64

0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65

0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66

0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67

0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68

0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69

0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70

0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71

0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72

0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73

0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74

0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75

0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76

0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77

0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78

0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79

0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80

0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81

0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82

0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83

0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84

0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85

0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86

0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87

0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88

0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89

0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90

0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91

0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92

0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93

0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};

#endif

Keil调试模式:借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能

以LED闪烁工程为例

魔术棒==>Debug==>勾选左边的Use Simulator (使用电脑模拟仿真stm32进行调试,不需要连接硬件)

右边是硬件在线调试,需要将硬件都连接好

仿真调试

勾选右边使用硬件在线调试,点击调试按钮

开始调试

进入调试界面

调试界面

上方窗口是C语言翻译成的汇编语言(蓝色箭头指向的c语言对应相应的汇编语言,黄色箭头是下一步将要执行的代码)

左边窗口时寄存器组和状态标志位的信息

下面7个按钮分别是:重头开始运行;全速运行;停止全速运行;单步运行;跳过当前行单步运行;跳出当前函数单步运行;跳到光标指定行单步运行

image-20240902114126978

再看下面几个按钮的功能:

image-20240902114909994
  1. 打开或关闭命令行窗口(即左下方的comman窗口)

  2. 打开或关闭反汇编窗口

  3. 符号窗口,打开后可以实时查看程序中所有变量的值(右边的Symbols)

    image-20240902115416593

    例如想要查看结构体中某个具体的值,右键添加到watch1窗口就会显示再下方的窗口watch1中

    image-20240902115522440
    1. 寄存器窗口显示(右边)

    遇到的小问题解决方法:关于keil 5的system viewer找不到子选项 - hifish - 博客园 (cnblogs.com)

    image-20240902122010145

5. EXTI

简要说明

image-20240902155329731
中断执行流程
NVIC基本结构
NVIC优先级分组
image-20240902161033047
image-20240902161230738

EXTI9_5指的是EXTI5到9触发的是同一个中断函数,EXTI15_10同理

对照上面的图就可以知道如何去配置:从GPIO到NVIC这一路中出现的外设模块都配置好

  1. 配置RCC,将这里涉及的外设时钟都打开(NVIC不用,因为NVIC是内核中的外设,RCC只负责配置内核外的外设)

  2. 配置GPIO,选择我们的端口模式(输入还是输出) 其他外设使用GPIO,不清楚配置为什么模式,可以去参考手册中找外设的GPIO配置表查看。参考手册链接

  3. 配置AFIO,选择我们这一路的GPIO,连接到后面的EXTI,AFIO相关函数声明也在gpio.h中

  4. 配置EXTI,选择边沿触发方式,如上升沿,下降沿或者双边沿,选择触发响应方式:中断响应和事件响应

  5. 配置NVIC,给中断选择一个合适的优先级

    【STM32入门教程-2023版 细致讲解 中文字幕】 【精准空降到 03:06】 https://www.bilibili.com/video/BV1th411z7sn/?p=12&share_source=copy_web&vd_source=94cb1c7c1ef54b6d1579d0017943c57e&t=186

[5-1] EXTI外部中断_哔哩哔哩_bilibili

旋转编码器计次代码

工程框架: image-20240902183118981

Encoder.h

1
2
3
4
5
6
7
8
#ifndef __ENCODER_H
#define __ENCODER_H

void Encoder_Init(void);
int16_t Encoder_Get(void);

#endif

Encoder.c

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
#include "stm32f10x.h"                  // Device header

int16_t Encoder_Count; //全局变量,用于计数旋转编码器的增量值

/**
* 函 数:旋转编码器初始化
* 参 数:无
* 返 回 值:无
*/
void Encoder_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,外部中断必须开启AFIO的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure); //将PB0和PB1引脚初始化为上拉输入

/*AFIO选择中断引脚*/
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚

/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设

/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置

/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设

NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; //选择配置NVIC的EXTI1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为2
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
}

/**
* 函 数:旋转编码器获取增量值
* 参 数:无
* 返 回 值:自上此调用此函数后,旋转编码器的增量值
*/
int16_t Encoder_Get(void)
{
/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
/*在这里,也可以直接返回Encoder_Count
但这样就不是获取增量值的操作方法了
也可以实现功能,只是思路不一样*/
int16_t Temp;
Temp = Encoder_Count;
Encoder_Count = 0;
return Temp;
}

/**
* 函 数:EXTI0外部中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == SET) //判断是否是外部中断0号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0) //PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
{
Encoder_Count --; //此方向定义为反转,计数变量自减
}
}
EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}

/**
* 函 数:EXTI1外部中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET) //判断是否是外部中断1号线触发的中断
{
/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) //PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
{
Encoder_Count ++; //此方向定义为正转,计数变量自增
}
}
EXTI_ClearITPendingBit(EXTI_Line1); //清除外部中断1号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"

int16_t Num; //定义待被旋转编码器调节的变量

int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Encoder_Init(); //旋转编码器初始化

/*显示静态字符串*/
OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:

while (1)
{
Num += Encoder_Get(); //获取自上此调用此函数后,旋转编码器的增量值,并将增量值加到Num上
OLED_ShowSignedNum(1, 5, Num, 5); //显示Num
}
}

6. TIM

TIM定时中断简要说明

类型 编号 总线 功能
高级定时器 TIM1、TIM8 APB2 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能
通用定时器 TIM2、TIM3、TIM4、TIM5 APB1 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能
基本定时器 TIM6、TIM7 APB1 拥有定时中断、主模式触发DAC的功能

STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

image-20240903105621132
image-20240903111515897
image-20240903112611933

[6-1] TIM定时中断_哔哩哔哩_bilibili

TIM定时中断与外部时钟源的选择代码

  1. 定时中断

    工程框架:

    image-20240903164110970

    Timer.h

    1
    2
    3
    4
    5
    6
    7
    #ifndef __TIMER_H
    #define __TIMER_H

    void Timer_Init(void);

    #endif

    Timer.c

    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
    #include "stm32f10x.h"                  // Device header

    /**
    * 函 数:定时中断初始化
    * 参 数:无
    * 返 回 值:无
    */
    void Timer_Init(void)
    {
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟

    /*配置时钟源*/
    TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟

    /*时基单元初始化*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //计数周期,即ARR的值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元

    /*中断输出配置*/
    TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
    //TIM_TimeBaseInit函数末尾,手动产生了更新事件
    //若不清除此标志位,则开启中断后,会立刻进入一次中断
    //如果不介意此问题,则不清除此标志位也可

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断

    /*NVIC中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
    //即抢占优先级范围:0~3,响应优先级范围:0~3
    //此分组配置在整个工程中仅需调用一次
    //若有多个中断,可以把此代码放在main函数内,while循环之前
    //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置

    /*NVIC配置*/
    NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
    NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设

    /*TIM使能*/
    TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
    }

    /* 定时器中断函数,可以复制到使用它的地方
    void TIM2_IRQHandler(void)
    {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
    }
    */

    main.c

    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
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"

    uint16_t Num; //定义在定时器中断里自增的变量

    int main(void)
    {
    /*模块初始化*/
    OLED_Init(); //OLED初始化
    Timer_Init(); //定时中断初始化

    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:

    while (1)
    {
    OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量
    }
    }

    /**
    * 函 数:TIM2中断函数
    * 参 数:无
    * 返 回 值:无
    * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
    * 函数名为预留的指定名称,可以从启动文件复制
    * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
    */
    void TIM2_IRQHandler(void)
    {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
    {
    Num ++; //Num变量自增,用于测试定时中断
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
    //中断标志位必须清除
    //否则中断将连续不断地触发,导致主程序卡死
    }
    }

  2. 定时器外部时钟

    Timer.h

    1
    2
    3
    4
    5
    6
    7
    8
    #ifndef __TIMER_H
    #define __TIMER_H

    void Timer_Init(void);
    uint16_t Timer_GetCounter(void);

    #endif

    Timer.c

    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
    #include "stm32f10x.h"                  // Device header

    /**
    * 函 数:定时中断初始化
    * 参 数:无
    * 返 回 值:无
    * 注意事项:此函数配置为外部时钟,定时器相当于计数器
    */
    void Timer_Init(void)
    {
    /*开启时钟*/
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

    /*GPIO初始化*/
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为上拉输入

    /*外部时钟配置*/
    TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
    //选择外部时钟模式2,时钟从TIM_ETR引脚输入
    //注意TIM2的ETR引脚固定为PA0,无法随意更改
    //最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动

    /*时基单元初始化*/
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
    TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //计数周期,即ARR的值
    TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //预分频器,即PSC的值
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元

    /*中断输出配置*/
    TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
    //TIM_TimeBaseInit函数末尾,手动产生了更新事件
    //若不清除此标志位,则开启中断后,会立刻进入一次中断
    //如果不介意此问题,则不清除此标志位也可

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断

    /*NVIC中断分组*/
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
    //即抢占优先级范围:0~3,响应优先级范围:0~3
    //此分组配置在整个工程中仅需调用一次
    //若有多个中断,可以把此代码放在main函数内,while循环之前
    //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置

    /*NVIC配置*/
    NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
    NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设

    /*TIM使能*/
    TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
    }

    /**
    * 函 数:返回定时器CNT的值
    * 参 数:无
    * 返 回 值:定时器CNT的值,范围:0~65535
    */
    uint16_t Timer_GetCounter(void)
    {
    return TIM_GetCounter(TIM2); //返回定时器TIM2的CNT
    }

    /* 定时器中断函数,可以复制到使用它的地方
    void TIM2_IRQHandler(void)
    {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
    {

    TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
    }
    */

    main.c

    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
    #include "stm32f10x.h"                  // Device header
    #include "Delay.h"
    #include "OLED.h"
    #include "Timer.h"

    uint16_t Num; //定义在定时器中断里自增的变量

    int main(void)
    {
    /*模块初始化*/
    OLED_Init(); //OLED初始化
    Timer_Init(); //定时中断初始化

    /*显示静态字符串*/
    OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
    OLED_ShowString(2, 1, "CNT:"); //2行1列显示字符串CNT:

    while (1)
    {
    OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量
    OLED_ShowNum(2, 5, Timer_GetCounter(), 5); //不断刷新显示CNT的值
    }
    }

    /**
    * 函 数:TIM2中断函数
    * 参 数:无
    * 返 回 值:无
    * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
    * 函数名为预留的指定名称,可以从启动文件复制
    * 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
    */
    void TIM2_IRQHandler(void)
    {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
    {
    Num ++; //Num变量自增,用于测试定时中断
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
    //中断标志位必须清除
    //否则中断将连续不断地触发,导致主程序卡死
    }
    }

TIM输出比较简要说明

image-20240903181004661

模式 描述
冻结 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置无效电平
image-20240903182951964
image-20240903183525704

TIM输出比较代码

[6-4] PWM驱动LED呼吸灯&PWM驱动舵机&PWM驱动直流电机_哔哩哔哩_bilibili

1. PWM驱动LED呼吸灯代码

PWM.h

1
2
3
4
5
6
7
8
#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);

#endif

PWM.c

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
#include "stm32f10x.h"                  // Device header

/**
* 函 数:PWM初始化
* 参 数:无
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

/*GPIO重映射*/
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式

/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟

/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置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; //初始的CCR值
TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1

/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}

/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"

uint8_t i; //定义for循环的变量

int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
PWM_Init(); //PWM初始化

while (1)
{
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
Delay_ms(10); //延时10ms
}
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
Delay_ms(10); //延时10ms
}
}
}

2. PWM驱动舵机代码

PWM.h

1
2
3
4
5
6
7
8
#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare2(uint16_t Compare);

#endif

PWM.c

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
#include "stm32f10x.h"                  // Device header

/**
* 函 数:PWM初始化
* 参 数:无
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

/*GPIO初始化*/
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); //将PA1引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式

/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟

/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置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; //初始的CCR值
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC2Init,配置TIM2的输出比较通道2

/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}

/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare2(uint16_t Compare)
{
TIM_SetCompare2(TIM2, Compare); //设置CCR2的值
}

注意,LED呼吸灯的PWM代码与舵机的PWM代码有些区别:LED呼吸灯用TIM2的通道1输出PWM,舵机用TIM2的通道2输出PWM。因为有四个通道,所以可以使用一个定时器的四个通道输出PWM(共用一个定时器,所以四个通道的频率必须相同;占空比是由各自的CCR决定,所以占空比可以各自设定)

舵机相关的头文件和源文件放在Hardware文件夹下

Servo.h

1
2
3
4
5
6
7
8
#ifndef __SERVO_H
#define __SERVO_H

void Servo_Init(void);
void Servo_SetAngle(float Angle);

#endif

Servo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

/**
* 函 数:舵机初始化
* 参 数:无
* 返 回 值:无
*/
void Servo_Init(void)
{
PWM_Init(); //初始化舵机的底层PWM
}

/**
* 函 数:舵机设置角度
* 参 数:Angle 要设置的舵机角度,范围:0~180
* 返 回 值:无
*/
void Servo_SetAngle(float Angle)
{
PWM_SetCompare2(Angle / 180 * 2000 + 500); //设置占空比
//将角度线性变换,对应到舵机要求的占空比范围上
}

main.c

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
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Servo.h"
#include "Key.h"

uint8_t KeyNum; //定义用于接收键码的变量
float Angle; //定义角度变量

int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Servo_Init(); //舵机初始化
Key_Init(); //按键初始化

/*显示静态字符串*/
OLED_ShowString(1, 1, "Angle:"); //1行1列显示字符串Angle:

while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
Angle += 30; //角度变量自增30
if (Angle > 180) //角度变量超过180后
{
Angle = 0; //角度变量归零
}
}
Servo_SetAngle(Angle); //设置舵机的角度为角度变量
OLED_ShowNum(1, 7, Angle, 3); //OLED显示角度变量
}
}

3. PWM驱动直流电机代码

PWM.h

1
2
3
4
5
6
7
8
#ifndef __PWM_H
#define __PWM_H

void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare);

#endif

PWM.c

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
#include "stm32f10x.h"                  // Device header

/**
* 函 数:PWM初始化
* 参 数:无
* 返 回 值:无
*/
void PWM_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式

/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟

/*时基单元初始化*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 36 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置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; //初始的CCR值
TIM_OC3Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC3Init,配置TIM2的输出比较通道3

/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}

/**
* 函 数:PWM设置CCR
* 参 数:Compare 要写入的CCR的值,范围:0~100
* 返 回 值:无
* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比
* 占空比Duty = CCR / (ARR + 1)
*/
void PWM_SetCompare3(uint16_t Compare)
{
TIM_SetCompare3(TIM2, Compare); //设置CCR3的值
}

Motor.h

1
2
3
4
5
6
7
8
#ifndef __MOTOR_H
#define __MOTOR_H

void Motor_Init(void);
void Motor_SetSpeed(int8_t Speed);

#endif

Motor.c

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
#include "stm32f10x.h"                  // Device header
#include "PWM.h"

/**
* 函 数:直流电机初始化
* 参 数:无
* 返 回 值:无
*/
void Motor_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟

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); //将PA4和PA5引脚初始化为推挽输出

PWM_Init(); //初始化直流电机的底层PWM
}

/**
* 函 数:直流电机设置速度
* 参 数:Speed 要设置的速度,范围:-100~100
* 返 回 值:无
*/
void Motor_SetSpeed(int8_t Speed)
{
if (Speed >= 0) //如果设置正转的速度值
{
GPIO_SetBits(GPIOA, GPIO_Pin_4); //PA4置高电平
GPIO_ResetBits(GPIOA, GPIO_Pin_5); //PA5置低电平,设置方向为正转
PWM_SetCompare3(Speed); //PWM设置为速度值
}
else //否则,即设置反转的速度值
{
GPIO_ResetBits(GPIOA, GPIO_Pin_4); //PA4置低电平
GPIO_SetBits(GPIOA, GPIO_Pin_5); //PA5置高电平,设置方向为反转
PWM_SetCompare3(-Speed); //PWM设置为负的速度值,因为此时速度值为负数,而PWM只能给正数
}
}

main.c

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
#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(); //OLED初始化
Motor_Init(); //直流电机初始化
Key_Init(); //按键初始化

/*显示静态字符串*/
OLED_ShowString(1, 1, "Speed:"); //1行1列显示字符串Speed:

while (1)
{
KeyNum = Key_GetNum(); //获取按键键码
if (KeyNum == 1) //按键1按下
{
Speed += 20; //速度变量自增20
if (Speed > 100) //速度变量超过100后
{
Speed = -100; //速度变量变为-100
//此操作会让电机旋转方向突然改变,可能会因供电不足而导致单片机复位
//若出现了此现象,则应避免使用这样的操作
}
}
Motor_SetSpeed(Speed); //设置直流电机的速度为速度变量
OLED_ShowSignedNum(1, 7, Speed, 3); //OLED显示速度变量
}
}

TIM 输入捕获简要说明

TIM 输入捕获代码

TIM编码器接口简要说明

TIM编码器代码

一些tips

小魔方==>向上or向下的箭头可以调整组的位置,也可以使用小魔方来新建组和添加我们需要的文件

image-20240901193314212

可以通过查看一些库函数的定义来明确所需的参数,直接复制对应参数也可以提高速度

image-20240901193616058
image-20240901193656847

ctrl+F搜索

\STM32F10x_StdPeriph_Lib_V3.5.0\STM32F10x_StdPeriph_Lib_V3.5.0下有对应版本的库函数帮助手册,必要时可以参考一下

image-20240902100558927
关键字 位数 表示范围 stdint关键字 ST关键字
char 8 -128 ~ 127 int8_t s8
unsigned char 8 0 ~ 255 uint8_t u8
short 16 -32768 ~ 32767 int16_t s16
unsigned short 16 0 ~ 65535 uint16_t u16
int 32 -2147483648 ~ 2147483647 int32_t s32
unsigned int 32 0 ~ 4294967295 uint32_t u32
long 32 -2147483648 ~ 2147483647
unsigned long 32 0 ~ 4294967295
long long 64 -(2^64)/2 ~ (2^64)/2-1 int64_t
unsigned long long 64 0 ~ (2^64)-1 uint64_t
float 32 -3.4e38 ~ 3.4e38
double 64 -1.7e308 ~ 1.7e308

ctrl+alt+空格可以弹出代码提示框,如果发现ctrl+空格会切换中英文输入法:右键桌面右下的中/英然后按键设置,取消ctrl+空格

keil5工具栏中的蓝色小旗子是添加书签,通过添加书签可以方便定位一些函数

跨越不同.c文件使用同一个变量(声明和使用不在同一个.c文件中)需要在使用的文件中使用extern关键字,告诉编译器,我有这个变量,但是不在这个文件里,让它自个去找

一些小问题的解决

关于keil 5的system viewer找不到子选项 - hifish - 博客园 (cnblogs.com)

Keil MDK-ARM软件勾选Reset and Run不起作用的解决办法_keil点击run不运行-CSDN博客


stm32学习
https://qingyun0118.github.io/2024/08/31/stm32学习/
作者
Wei Jicheng
发布于
2024年8月31日
许可协议