OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 6303|回复: 0

《MiniPRO H750开发指南》第十三章 跑马灯实验(下)

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4896
金钱
4896
注册时间
2019-5-8
在线时间
1248 小时
发表于 2022-12-13 15:32:09 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2022-12-26 15:24 编辑

第十三章 跑马灯实验

1)实验平台:正点原子MiniPRO STM32H750 开发板

2)  章节摘自【正点原子】《MiniPRO H750开发指南》

3)购买链接:https://detail.tmall.com/item.htm?id=677017430560

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boar ... 32h750_minipro.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32技术交流群:170313895

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

13.2 硬件设计
1. 例程功能RGB灯:LED0(红)、LED1(绿)和LED2(蓝)每过500ms一次交替闪烁,实现类似跑马灯的效果。
2. 硬件资源
1)RGB灯
   LEDR : LED0 - PB4(红)
   LEDG : LED1 - PE6(绿)
   LEDB : LED2 - PE5(蓝)

3. 原理图
本章用到的硬件用到RGB灯:LEDR、LEDG和LEDB。电路在开发板上已经连接好,所以在硬件上不需要动任何东西,直接下载代码就可以测试使用。其连接原理图如图13.2.1所示:
image001.png
图13.2.1 LED与STM32H750连接原理图
下面,给大家介绍一下LED灯的压降和额定电流,供大家设计硬件参考。

直插超亮发光二极管压降,主要有三种颜色,然而三种发光二极管的压降都不相同,具体压降参考值如下:
红色发光二极管的压降为2.0V-2.2V。
黄色发光二极管的压降为1.8V-2.0V。
绿色发光二极管的压降为3.0V-3.2V。
正常发光时的额定电流约为20mA。

贴片LED压降:
红色的压降为1.82-1.88V,电流5-8mA。
绿色的压降为1.75-1.82V,电流3-5mA。
橙色的压降为1.7-1.8V,电流3-5mA。
蓝色的压降为3.1-3.3V,电流8-10mA。
白色的压降为3-3.2V,电流10-15mA。

LED发光颜色的不同,需要的驱动电压也有差异。我们使用的LED0和LED1是红绿两个颜色的贴片灯珠,它的正常工作驱动电流典型值约为25mA/1.8V。而STM32的GPIO的输出电流也大概是25mA/3.3V,所以可以直接用STM32的IO驱动这种LED。但STM32对总的输出电流有限制,为了经过LED的电流更稳定,我们一般不用STM32的IO直接输出电流驱动LED,而是通过外接电阻的方式限制灌入STM32的电流。如我们使用红灯时灌到IO口的最大电流约为(3.3-1.8)V/510R≈2.94mA。
image003.png
13.2.2 STM32 IO的电气参数

13.3 程序设计
了解了GPIO的结构原理和寄存器,还有我们的实验功能,下面开始设计程序。

13.3.1 GPIOHAL库驱动分析
HAL库中关于GPIO的驱动程序在stm32h7xx_hal_gpio.c文件以及其对应的头文件。

1.HAL_GPIO_Init函数
要使用一个外设我们首先要对它进行初始化,所以我们先看外设GPIO的初始化函数。其声明如下:
  1. <font size="3">void  HAL_GPIO_Init(GPIO_TypeDef  *GPIOx, GPIO_InitTypeDef *GPIO_Init);</font>
复制代码
函数描述:
用于配置GPIO功能模式,还可以设置EXTI功能。

函数形参:
形参1是端口号,可以有以下的选择:
  1. <font size="3">#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
  2. #define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
  3. #define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
  4. #define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
  5. #define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
  6. #define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
  7. #define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
  8. #define GPIOH               ((GPIO_TypeDef *) GPIOH_BASE)
  9. #define GPIOI               ((GPIO_TypeDef *) GPIOI_BASE)
  10. #define GPIOJ               ((GPIO_TypeDef *) GPIOJ_BASE)
  11. #define GPIOK               ((GPIO_TypeDef *) GPIOK_BASE)</font>
复制代码
这是库里面的选择项,实际上我们的芯片只能从GPIOA~GPIOE,因为我们只有5组IO口。

形参2是GPIO_InitTypeDef类型的结构体指针变量,其定义如下:
  1. <font size="3">#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
  2. #define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
  3. #define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
  4. #define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
  5. #define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
  6. #define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
  7. #define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
  8. #define GPIOH               ((GPIO_TypeDef *) GPIOH_BASE)
  9. #define GPIOI               ((GPIO_TypeDef *) GPIOI_BASE)
  10. #define GPIOJ               ((GPIO_TypeDef *) GPIOJ_BASE)
  11. #define GPIOK               ((GPIO_TypeDef *) GPIOK_BASE)
  12. typedef struct
  13. {
  14.   uint32_t Pin;               /* 引脚号 */
  15.   uint32_t Mode;              /* 模式设置 */
  16.   uint32_t Pull;              /* 上拉下拉设置 */
  17.   uint32_t Speed;             /* 速度设置 */
  18.   uint32_t Alternate;        /* 复用功能 */
  19. } GPIO_InitTypeDef;
  20. </font>
复制代码
该结构体很重要,下面对每个成员介绍一下。

成员Pin表示引脚号,范围:GPIO_PIN_0到 GPIO_PIN_15,另外还有GPIO_PIN_All和GPIO_PIN_MASK可选。
成员Mode是GPIO的模式选择,有以下选择项:
  1. <font size="3">#define  GPIO_MODE_INPUT                  (0x00000000U)          /* 输入模式 */
  2. #define  GPIO_MODE_OUTPUT_PP             (0x00000001U)          /* 推挽输出 */
  3. #define  GPIO_MODE_OUTPUT_OD             (0x00000011U)          /* 开漏输出 */
  4. #define  GPIO_MODE_AF_PP                  (0x00000002U)          /* 推挽式复用 */
  5. #define  GPIO_MODE_AF_OD                  (0x00000012U)          /* 开漏式复用 */

  6. #define  GPIO_MODE_ANALOG                 (0x00000003U)         /* 模拟模式 */

  7. #define  GPIO_MODE_IT_RISING             (0x11110000U)  /* 外部中断,上升沿触发检测 */
  8. #define  GPIO_MODE_IT_FALLING            (0x11210000U)  /* 外部中断,下降沿触发检测 */
  9. /* 外部中断,上升和下降双沿触发检测 */
  10. #define  GPIO_MODE_IT_RISING_FALLING    (0x11310000U)  

  11. #define  GPIO_MODE_EVT_RISING            (0x11120000U) /* 外部事件,上升沿触发检测 */
  12. #define  GPIO_MODE_EVT_FALLING           (0x11220000U) /* 外部事件,下降沿触发检测 */
  13. /* 外部事件,上升和下降双沿触发检测 */
  14. #define  GPIO_MODE_EVT_RISING_FALLING   (0x11320000U)</font>
复制代码
成员Pull用于配置上下拉电阻,有以下选择项:
  1. <font size="3">#define  GPIO_NOPULL        (0x00000000U)           /* 无上下拉 */
  2. #define  GPIO_PULLUP        (0x00000001U)           /* 上拉 */
  3. #define  GPIO_PULLDOWN      (0x00000002U)          /* 下拉 */
  4. </font>
复制代码
成员Speed用于配置GPIO的速度,有以下选择项:
  1. <font size="3">#define  GPIO_SPEED_FREQ_LOW         (0x00000000U)          /* 低速 */
  2. #define  GPIO_SPEED_FREQ_MEDIUM      (0x00000001U)          /* 中速 */
  3. #define  GPIO_SPEED_FREQ_HIGH        (0x00000002U)          /* 快速 */
  4. #define  GPIO_SPEED_FREQ_VERY_HIGH  (0x00000003U)          /* 高速 */
  5. </font>
复制代码
成员Alternate用于配置具体的复用功能,不同的GPIO口可以复用的功能不同,具体可参考数据手册《STM32H750VBT6.pdf》。复用功能的选择在stm32h7xx_hal_gpio_ex.h文件里进行了定义,后面具体用到了,我们在进行讲解。

函数返回值:

注意事项:
HAL库的EXTI外部中断的设置功能整合到此函数里面,而不是单独独立一个文件。这个我们到外部中断实验再细讲。

2.HAL_GPIO_WritePin函数
HAL_GPIO_WritePin函数是GPIO口的写引脚函数。其声明如下:
  1. <font size="3">void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx,
  2. uint16_t GPIO_Pin, GPIO_PinState PinState);</font>
复制代码
函数描述:
用于设置引脚输出高电平或者低电平,通过BSRR寄存器复位或者置位操作。

函数形参:
形参1是端口号,可以选择范围:GPIOA~GPIOK。
形参2是引脚号,可以选择范围:GPIO_PIN_0到 GPIO_PIN_15。
形参3是要设置输出的状态,是枚举型有两个选择:GPIO_PIN_SET 表示高电平,GPIO_PIN_RESET表示低电平。

函数返回值:

3.HAL_GPIO_TogglePin函数
HAL_GPIO_TogglePin函数是GPIO口的电平翻转函数。其声明如下:
  1. <font size="3">voidHAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);</font>
复制代码
函数描述:
用于设置引脚的电平翻转,也是通过BSRR寄存器复位或者置位操作。

函数形参:
形参1是端口号,可以选择范围:GPIOA~GPIOK。
形参2是引脚号,可以选择范围:GPIO_PIN_0到 GPIO_PIN_15。

函数返回值:

本实验我们用到上面三个函数,其他的API函数后面用到再进行讲解。

GPIO输出配置步骤
1)使能对应GPIO时钟
STM32在使用任何外设之前,我们都要先使能其时钟(下同)。本实验用到PB5和PE5两个IO口,因此需要先使能GPIOB和GPIOE的时钟,代码如下:
  1. <font size="3">__HAL_RCC_GPIOB_CLK_ENABLE();
  2. __HAL_RCC_GPIOE_CLK_ENABLE();</font>
复制代码
2)设置对应GPIO工作模式(推挽输出)
本实验GPIO使用推挽输出模式,控制LED亮灭,通过函数HAL_GPIO_Init设置实现。

3)控制GPIO引脚输出高低电平
    在配置好GPIO工作模式后,我们就可以通过HAL_GPIO_WritePin函数控制GPIO引脚输出高低电平,从而控制LED的亮灭了。

13.3.2 程序流程图
程序流程图能帮助我们更好的理解一个工程的功能和实现的过程,对学习和设计工程有很好的主导作用。
下面看看本实验的程序流程图:
   
image006.png
图13.3.2.1 跑马灯实验程序流程图

13.3.3 程序解析
1. led驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。LED驱动源码包括两个文件:led.c和led.h。
下面我们先解析led.h的程序,我们把它分两部分功能进行讲解。
由硬件设计小节,我们知道RGB灯在硬件上分别连接到PB4、PE5、PE6,再结合HAL库,我们做了下面的引脚定义。
  1. <font size="3">/*****************************************************************************/
  2. /* 引脚 定义 */
  3. #define LED0_GPIO_PORT                GPIOB
  4. #define LED0_GPIO_PIN                 GPIO_PIN_4
  5. /* PB口时钟使能 */
  6. #define LED0_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)

  7. #define LED1_GPIO_PORT                GPIOE
  8. #define LED1_GPIO_PIN                 GPIO_PIN_6
  9. /* PE口时钟使能 */
  10. #define LED1_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)

  11. #define LED2_GPIO_PORT                GPIOE
  12. #define LED2_GPIO_PIN                 GPIO_PIN_5
  13. /* PE口时钟使能 */
  14. #define LED2_GPIO_CLK_ENABLE()      do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)
  15. /*****************************************************************************/
  16. </font>
复制代码
这样的好处是进一步隔离底层函数操作,移植更加方便,函数命名更亲近实际的开发板。比如:当我们看到LED0_GPIO_PORT这个宏定义,我们就知道这是灯LED0的端口号;看到LED0_GPIO_PIN这个宏定义,就知道这是灯LED0的引脚号;看到LED0_GPIO_CLK_ENABLE这个宏定义,就知道这是灯LED0的时钟使能函数。大家后面学习时间长了就会慢慢熟悉这样的命名方式。

特别注意:这里的时钟使能函数宏定义,使用了do{ }while(0)结构,是为了避免在某些使用场景出错的问题(下同),详见《嵌入式单片机 C代码规范与风格》第六章第2点。

__HAL_RCC_GPIOx_CLK_ENABLE函数是HAL库的IO口时钟使能函数,x=A到K。

为了后续对RGB灯进行便捷的操作,我们为RGB灯操作函数做了下面的定义。
  1. <font size="3">/* LED端口定义 */
  2. #define LED0(x)   do{ x ? \
  3.                           HAL_GPIO_WritePin(LED0_GPIO_PORT,
  4. LED0_GPIO_PIN, GPIO_PIN_SET) : \
  5.                           HAL_GPIO_WritePin(LED0_GPIO_PORT,
  6.                                           LED0_GPIO_PIN, GPIO_PIN_RESET); \
  7.                      }while(0)       /* LED0 = RED */

  8. #define LED1(x)   do{ x ? \
  9.                           HAL_GPIO_WritePin(LED1_GPIO_PORT,
  10. LED1_GPIO_PIN, GPIO_PIN_SET) : \
  11.                           HAL_GPIO_WritePin(LED1_GPIO_PORT,
  12. LED1_GPIO_PIN, GPIO_PIN_RESET); \
  13.                      }while(0)       /* LED1 = GREEN */

  14. #define LED2(x)   do{ x ? \
  15.                           HAL_GPIO_WritePin(LED2_GPIO_PORT,
  16. LED2_GPIO_PIN, GPIO_PIN_SET) : \
  17.                           HAL_GPIO_WritePin(LED2_GPIO_PORT,
  18. LED2_GPIO_PIN, GPIO_PIN_RESET); \
  19.                      }while(0)       /* LED2 = BLUE */

  20. /* LED取反定义 */
  21. #define LED0_TOGGLE()    do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT,
  22. LED0_GPIO_PIN); }while(0)   /* LED0 = !LED0 */
  23. #define LED1_TOGGLE()    do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT,
  24. LED1_GPIO_PIN); }while(0)   /* LED1 = !LED1 */
  25. #define LED2_TOGGLE()    do{ HAL_GPIO_TogglePin(LED2_GPIO_PORT,
  26. LED2_GPIO_PIN); }while(0)   /* LED2 = !LED2 */
  27. </font>
复制代码
LED0、LED1和LED2这三个宏定义,分别是控制LED0、LED1和LED2的亮灭。例如:对于宏定义标识符LED0(x),它的值是通过条件运算符来确定:当x=0时,宏定义的值为HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET),也就是设置PB4输出低电平,当n!=0时,宏定义的值为HAL_GPIO_WritePin (LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET),也就是设置PB4输出高电平。所以如果要设置LED0输出低电平,那么调用宏定义LED0(0)即可,如果要设置LED0输出高电平,调用宏定义LED0(1)即可。宏定义LED1(x)和LED0(x)同理。

因为STM32H7不支持位带操作,所以这里我们并没有像F1/F4一样通过位带操作来实现IO口输出输入电平控制。
LED0_TOGGLE、LED1_TOGGLE和LED2_TOGGLE这三个宏定义,分别是控制LED0、LED1和LED2的翻转。这里利用HAL_GPIO_TogglePin函数实现IO口输出电平取反操作。

下面我们再解析led.c的程序,这里只有一个函数led_init,这是RGB灯的初始化函数,其定义如下:
  1. <font size="3">/**
  2. * @brief       初始化LED相关IO口, 并使能时钟
  3. * @param       无
  4. * @retval      无
  5. */
  6. void led_init(void)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct;
  9.     LED0_GPIO_CLK_ENABLE(); /* LED0时钟使能 */
  10.     LED1_GPIO_CLK_ENABLE(); /* LED1时钟使能 */
  11.     LED2_GPIO_CLK_ENABLE(); /* LED2时钟使能 */

  12.     gpio_init_struct.Pin = LED0_GPIO_PIN;                 /* LED0引脚 */
  13.     gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;                /* 推挽输出 */
  14.     gpio_init_struct.Pull = GPIO_PULLUP;                          /* 上拉 */
  15.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_MEDIUM;           /* 中速 */
  16.     HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct);         /* 初始化LED0引脚 */

  17.     gpio_init_struct.Pin = LED1_GPIO_PIN;                 /* LED1引脚 */
  18.     HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);          /* 初始化LED1引脚 */
  19.    
  20.     gpio_init_struct.Pin = LED2_GPIO_PIN;                 /* LED2引脚 */
  21.     HAL_GPIO_Init(LED2_GPIO_PORT, &gpio_init_struct);          /* 初始化LED2引脚 */
  22.    
  23.     LED0(1);    /* 关闭 LED0 */
  24.     LED1(1);    /* 关闭 LED1 */
  25.     LED2(1);    /* 关闭 LED2 */
  26. }
  27. </font>
复制代码
对RGB灯的三个引脚都设置为中速上拉的推挽输出。最后关闭RGB的三个LED灯,防止没有操作就亮了。

2. main.c代码
在main.c里面编写如下代码:
  1. <font size="3">#include "./SYSTEM/sys/sys.h"
  2. #include "./SYSTEM/usart/usart.h"
  3. #include "./SYSTEM/delay/delay.h"
  4. #include "./BSP/LED/led.h"
  5. int main(void)
  6. {
  7.     sys_cache_enable();                             /* 打开L1-Cache */
  8.     HAL_Init();                                       /* 初始化HAL库 */
  9.     sys_stm32_clock_init(240, 2, 2, 4);          /* 设置时钟, 480Mhz */
  10.     delay_init(480);                                 /* 延时初始化 */
  11.     led_init();                                       /* 初始化LED */

  12.     while (1)
  13.     {
  14.         LED2(1);                         /* LED2(BLUE)  灭 */
  15.         LED0(0);                         /* LED0(RED)   亮 */
  16.         delay_ms(500);
  17.         LED0(1);                         /* LED0(RED)   灭 */
  18.         LED1(0);                         /* LED1(GREEN) 亮 */
  19.         delay_ms(500);
  20.         LED1(1);                         /* LED1(GREEN) 灭 */
  21.         LED2(0);                         /* LED2(BLUE)  亮 */
  22.         delay_ms(500);
  23.     }
  24. }
  25. </font>
复制代码
首先是调用系统级别的初始化:sys_cache_enable函数使能I-Cache和D-Cache,然后初始化 HAL库、系统时钟和延时函数。接下来,调用led_init来初始化RGB灯。最后在无限循环里面实现LED0、LED1和LED2间隔500ms交替闪烁一次。

13.4 下载验证
我们先来看看编译结果,如图13.4.1所示。
image007.png

图13.4.1 编译结果
可以看到没有0错误,0警告。从编译信息可以看出,我们的代码占用FLASH大小为:13364字节(12874+714),所用的SRAM大小为:2424个字节(32+2392)。这里我们解释一下,编译结果里面的几个数据的意义:
       Code:表示程序所占用FLASH的大小(FLASH)。
       RO-data:即Read Only-data,表示程序定义的常量(FLASH)。
       RW-data:即Read Write-data,表示已被初始化的变量(SRAM
       ZI-data:即Zero Init-data,表示未被初始化的变量(SRAM)
有了这个就可以知道你当前使用的flash和sram大小了,所以,一定要注意的是程序的大小不是.hex文件的大小,而是编译后的Code和RO-data之和。
接下来,大家就可以下载验证了。这里我们使用DAP仿真器下载(也可以通过其他仿真器下载,如果是JLINK,必须是V9或者以上版本,才可以支持STM32H750!! 下同)。
下载完之后,可以看到RGB灯的LED0(红)、LED1(绿)和LED2(蓝)轮流亮。

至此,我们的跑马灯实验的学习就结束了,本章介绍了STM32H750的IO口的使用及注意事项,是后面学习的基础,希望大家好好理解。


正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-2-24 23:05

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表