1.LED电路原理
打开《CT117E-M4产品手册》,在第11页可以看到LED灯的电路原理图:
图中所示的属于LED共阳接法,LED负极接低电平则LED点亮。
所以如何将LED的负极赋值为低电平呢?就需要看74HC573这个锁存器的用法。
锁存器一边接的是LED灯的负极一端,而另一边是单片机的PC8~PC15端口。
而为什么不直接将单片机的PC8~PC15端口接到LED灯的负极一端呢?是因为在这个开发板上,PC8-PC15端口还在LCD的驱动方面起着作用,如下图。所以这几个端口有多个复用功能,无法直接连接到LED灯的负极。
所以点亮LED灯需要用到这几个端口的复用功能。
74HC573锁存器相当于“门锁”,PD2是打开“门锁”的“钥匙”;PD2为高电平时,D和Q的电平相同,也就是可以将D端的数据传到Q端。PD2是低电平时,Q的电平保持,D端电平变化并不会影响到D端。
OE管脚是芯片的使能管脚,把OE管脚接地则可以使芯片处于工作状态。
所以如果想让LED1点亮,LED2熄灭,则先将PC1赋值为低电平,PC2赋值为高电平,然后将PD2赋值为高电平(也就是打开门锁),则此时可以将D端的数据传入到Q端,然后即使关闭门锁。从而实现点亮LED1,熄灭LED2的操作。
2.操作锁存器的原则
在打开锁存器前,需要保持PC8-PC15数据的稳定;在关闭锁存器后,才可以改变数据。
所以必须先让PC口输出数据控制LED,再打开锁存器(PD2高电平),再关闭锁存器(PD2低电平),让Q端数据保持稳定。
3.程序设计(CubeMX与keil)
1)程序设计步骤:
- 从【赛场资源】里复制一份程序作为【模板】;一份作为【编程工程】(【模板】作为STM32CubeMX生成代码的工程;【编程工程】作为自己编写代码的工程,并从【模板】里移植代码)
- 设置LED相关的GPIO为【推挽输出】模式;(PC8~PC15和PD2)
- STM32CubeMX设置将每个外设单独生成.c和.h文件,并生成代码;
- 下载程序时,需要设置Debug-Flash Download-添加stm32G4的下载算法;(下载程序,查看LCD显示是否正常)
- 编写led.c和led.h文件,并添加到工程中,编写LED_Control函数用于控制LED灯模块
- 在main.c添加led.h,并调用LED_Control函数,完成LED控制
2)实际操作
赛场资源包一共会提供两个工程文件,一个是基于HAL库的,一个是基于LL库的,里边都包含液晶显示屏的驱动例程(因为这个编写比较难,所以一般都会提供),我们一般使用的是HAL库。
我们复制两份HAL库的工程文件到我们创建的文件夹,一份作为模板工程,一份作为编程工程。分别命名为programming和template
由于LED灯编程比较简单,本次可以直接使用编程工程进行stm32cube的代码生成。
首先打开programming工程文件的cube文件:
打开后会弹出让更新固件包的提示框,因为我们用的是1.20版本的固件包,而最新的是1.40版本的,但是为了和比赛所用的保持一致,我们最好保持不变,仍旧用1.20版本的固件包,所以选择continue即可。
打开工程文件后我们需要设置PC8~PC15和PD2为推挽输出模式,但是打开后会发现PC8-PC15已经设置为推挽输出模式了,因为液晶屏幕的初始化需要用到这些端口,所以我们只需要设置PD2为输出模式即可。
最终界面应该如下所示:
然后点击左边栏的System Core下边的GPIO选项,可以查看每一个GPIO设置的什么模式,通过查看其中的“GPIO mode”一栏可以看到每一个都默认配置的是推挽输出功能。
而旁边的“GPIO output level”这一栏显示的是管脚一上电会被赋予的电平高低值,这个保持默认即可,无需改动。
右边的“GPIO-pull up/pull down”一栏显示的是是否配置上拉下拉电阻。由于我们用的是推挽输出模式,所以不用配置上下拉电阻。
点到RCC部分发现使用的是外部晶振,也已经配置好了。
时钟树也是配置好的,就都保持默认即可。
接下来来到工程配置界面,将IDE改成keilV5。在code generator中将“生成.c/.h文件”这个选项勾上。
然后生成代码。
然后进入工程文件:
首先点击“Rebuild”,,查看有没有编译错误。发现没有编译错误之后可以编写自己的文件。
我们尽量实现模块化编程,所以将led的功能单独放到“led.c”和“led.h”文件中。
点击此处的“新建文件”,新建文件之后点击“保存”:
在programming 工程的src目录下保存该文件,文件名为"led.c"。
以相同的操作在programming 工程的Inc目录下添加“led.h”文件。
在keil工程中添加"led.c"文件。(双击“Application/User”选项,找到Src路径,选中相应文件,点击添加)
之后就可以开始"led.c"与"led.h"文件的编写了。
有两种写法:
①led.h文件:
#ifndef __led_H
#define __led_H//这两句话,还有最后一句的endif,是头文件的基本格式,作用是防止某些宏定义的重复定义
#include "main.h"
void LED_Control(uint16_t led_ctrl);//对led.c中定义的函数进行声明
#endif
①led.c文件:
#include "led.h"
//传入的参数之所以类型是uint16_t ,是因为GPIO_PIN_X的数据类型都是uint16_t 。
void LED_Control(uint16_t led_ctrl)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15|GPIO_PIN_14|GPIO_PIN_13|GPIO_PIN_12|GPIO_PIN_11|GPIO_PIN_10|GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_SET);
//上面一句作用是用“HAL_GPIO_WritePin”函数将所有位都置为高电平
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
//通过开关锁存器将高电平从D端输送到Q端,并保持。这里实现的功能是将所有led灯熄灭
HAL_GPIO_WritePin(GPIOC,led_ctrl,GPIO_PIN_RESET);
//具体把哪一位置为低电平,由传入特定的参数而定
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
//把特定位的led灯点亮
}
②led.h:
#ifndef __led_H
#define __led_H//这两句话,还有最后一句的endif,是头文件的基本格式,作用是防止某些宏定义的重复定义
#include "main.h"
void LED_Control(u8 led_ctrl);//对led.c中定义的函数进行声明
#endif
②led.c:
#include "led.h"
void LED_Control(u8 led_ctrl)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_15|GPIO_PIN_14|GPIO_PIN_13|GPIO_PIN_12|GPIO_PIN_11|GPIO_PIN_10|GPIO_PIN_9|GPIO_PIN_8,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,led_ctrl<<8,GPIO_PIN_RESET);
//因为u8的位数就是0x01,0x02这样,但是GPIO_PIN_X的位数是0x0100这样的,所以我们需要把我们的u8进行移位,就可以得到u16的数了。例如:0x01<<8=0x0100=GPIO_Pin_8.
//输入0x55是间隔点亮
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
然后编写main.c文件:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2021 STMicroelectronics.
* All rights reserved.</center></h2>
*
* This software component is licensed by ST under BSD 3-Clause license,
* the "License"; You may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
* opensource.org/licenses/BSD-3-Clause
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "led.h" //此处一定要记得添加自己编写的led.h头文件,因为里边包含LED_Control函数的声明,不包含的话编译器找不到此函数,会报错
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
LCD_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
LCD_Clear(Blue);
LCD_SetBackColor(Blue);
LCD_SetTextColor(White);
LCD_DisplayStringLine(Line0, (uint8_t *)" ");
LCD_DisplayStringLine(Line1, (uint8_t *)" ");
LCD_DisplayStringLine(Line2, (uint8_t *)" LCD Test ");
LCD_DisplayStringLine(Line3, (uint8_t *)" ");
LCD_DisplayStringLine(Line4, (uint8_t *)" ");
LCD_SetBackColor(White);
LCD_SetTextColor(Blue);
LCD_DisplayStringLine(Line5, (uint8_t *)" ");
LCD_DisplayStringLine(Line6, (uint8_t *)" HAL LIB ");
LCD_DisplayStringLine(Line7, (uint8_t *)" ");
LCD_DisplayStringLine(Line8, (uint8_t *)" @80 ");
LCD_DisplayStringLine(Line9, (uint8_t *)" ");
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
//自己真正编写的部分都要写在“USER CODE BEGIN”后边,此处只有一句话,功能是点亮led8
LED_Control(GPIO_PIN_15);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
编译无错之后就可以准备下载了。
最后需要配置一下debug下载算法才能下载到板子上:
首先选择用“CMSIS-DAP Debugger”
之后点击"Settings":
最后点击“下载”,即可将代码下载到板子上。最后可以看到板子的led8是亮的,其他led都是处于熄灭状态,则该项目就编写成功了。
如果有想要工程文件的小伙伴可以留言,我看有没有人要,有人要的话我就传一下。