如同所有编程入门的第一个教程——打印"Hello world"一样,点亮LED可以算得上是嵌入式开发中的"Hello world",所有的单片机开发入门都从这里开始。
在点亮LED前,我们需要先了解一个重要概念——GPIO(General Purpose Input Output),即通用输入输出端口。一个单片机上通常有许许多多的端口,这些端口通常都可用作GPIO输入或输出。当其被作为GPIO输出时,我们可以通过编程,使该端口输出高电平或低电平,从而实现点灯操作;当其被作为GPIO输入时,可以将该端口外接某电路的某一接口(如按键),从而编程读取该端口的电平状态,为我们的其他功能服务。在对GPIO有一个初步了解之后,我们就可以通过电路原理图来看看如何点亮LED。(下图在选手资源数据包——CT117E-M4产品手册中)
可以看到,STM32G431RBT6是通过控制PC8~PC15以及PD2的电平状态来控制LED的亮灭的。以LD1为例,PD2端口为使能位(LE——LED ENABLE),当PD2置高时,才能对LED进行操作;而当PC8置低时,二极管导通,LD1被点亮,当PC8置高时,二极管截止,LD1熄灭。因此,我们要将这些端口设置为GPIO输出模式。
在Cube中,我们进行如下配置,从而设置需要用到的端口为GPIO输出模式:
点亮LD1的代码实现如下:
/* 点亮LD1一秒后熄灭 */
void LD1_TEST(void)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); //PD2置高,使能LED
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET); //PC8置低,点亮LD1
HAL_Delay(1000); //延时1s
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET); //PC8置高,熄灭LD1
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET); //PD2置低,关闭LED操作
}
这是最基础的LED操作。学会点亮LD1之后,就可以随心所欲地对LED进行操作,实现各种功能啦!
#define DELAY_TIME 100 //延时100ms
/* LD1~LD8轮流亮灭,实现跑马灯 */
void LED_TEST(void)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_8); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_9); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_10); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_11); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_12); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_14); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_15); //翻转电平
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
HAL_Delay(DELAY_TIME);
}
其实不止点亮LED,单片机端口的GPIO输出功能还能应用于各种各样的场景。当某一电路的某一接口需要外部输入高低电平来控制某一功能时,就可以使用到这一功能。HAL库中与GPIO输出有关的函数如下:
/**
* @brief Set or clear the selected data port bit.
*
* @note This function uses GPIOx_BSRR and GPIOx_BRR registers to allow atomic read/modify
* accesses. In this way, there is no risk of an IRQ occurring between
* the read and the modify access.
*
* @param GPIOx where x can be (A..G) to select the GPIO peripheral for STM32G4xx family
* @param GPIO_Pin specifies the port bit to be written.
* This parameter can be any combination of GPIO_PIN_x where x can be (0..15).
* @param PinState specifies the value to be written to the selected bit.
* This parameter can be one of the GPIO_PinState enum values:
* @arg GPIO_PIN_RESET: to clear the port pin
* @arg GPIO_PIN_SET: to set the port pin
* @retval None
*/
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
/**
* @brief Toggle the specified GPIO pin.
* @param GPIOx where x can be (A..G) to select the GPIO peripheral for STM32G4xx family
* @param GPIO_Pin specifies the pin to be toggled.
* This parameter can be any combination of GPIO_PIN_x where x can be (0..15).
* @retval None
*/
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
另外,LED操作中还使用到了一个非常常用的函数——延时函数,它是以ms为单位进行延时的。
/**
* @brief This function provides minimum delay (in milliseconds) based
* on variable incremented.
* @note In the default implementation , SysTick timer is the source of time base.
* It is used to generate interrupts at regular time intervals where uwTick
* is incremented.
* @note This function is declared as __weak to be overwritten in case of other
* implementations in user file.
* @param Delay specifies the delay time length, in milliseconds.
* @retval None
*/
__weak void HAL_Delay(uint32_t Delay);
为方便,我们可以将LED操作写成一个库,这样在需要的时候直接调用库即可。这种模块化思想在复杂的嵌入式系统中是极为关键的。
/* led.c */
#include "led.h"
/* 点亮LED */
void LED_On(uint16_t LED_Pin)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, LED_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/* 熄灭LED */
void LED_Off(uint16_t LED_Pin)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOC, LED_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/* 翻转LED */
void LED_Toggle(uint16_t LED_Pin)
{
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
HAL_GPIO_TogglePin(GPIOC, LED_Pin);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
}
/* led.h */
#ifndef __LED_H
#define __LED_H
#include "main.h"
#define LD1 GPIO_PIN_8
#define LD2 GPIO_PIN_9
#define LD3 GPIO_PIN_10
#define LD4 GPIO_PIN_11
#define LD5 GPIO_PIN_12
#define LD6 GPIO_PIN_13
#define LD7 GPIO_PIN_14
#define LD8 GPIO_PIN_15
#define LD_None 0x0000
#define LD_All 0xFF00
void LED_On(uint16_t LED_Pin);
void LED_Off(uint16_t LED_Pin);
void LED_Toggle(uint16_t LED_Pin);
#endif /* __LED_H */
下面我们以第十四届省赛题为例,总结本文所讲内容:
/* Cube自动生成的头文件引用 */
#include "led.h"
int main(void)
{
/* Cube自动生成的初始化 */
LED_Off(LD_All);
while(1)
{
if (数据界面)
{
LED_On(LD1);
}
else
{
LED_Off(LD1);
}
if (模式切换)
{
LED_Toggle(LD2);
HAL_Delay(100);
}
if (锁定状态)
{
LED_On(LD3);
}
else
{
LED_Off(LD3);
}
}
}