在获取到工程模板后,学习某个CPU的第一步通常都是IO口操作。因此按钮输入和点灯,就是本次学习的第一个程序。先从简单入手。
和GPIO操作有关的函数如下:
__HAL_RCC_GPIOA_CLK_ENABLE();//使能GPIOA时钟
__HAL_RCC_GPIOB_CLK_ENABLE();//使能GPIOB时钟
__HAL_RCC_GPIOC_CLK_ENABLE();//使能GPIOC时钟
__HAL_RCC_GPIOD_CLK_ENABLE();//使能GPIOD时钟
__HAL_RCC_GPIOF_CLK_ENABLE();//使能GPIOF时钟
HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
根据GPIO_InitTypeDef型结构变量指定的参数初始化GPIOx的外设寄存器
HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
PinState=0,GPIOx端口的GPIO_Pin引脚输出低电平;
PinState=1,GPIOx端口的GPIO_Pin引脚输出高电平
HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
GPIOx端口的GPIO_Pin的输出电平翻转
HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
读取"GPIOx端口的第GPIO_Pin引脚"输入的电平值
HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin)
将"GPIOx端口的GPIO_Pin引脚"的外设寄存器恢复到"复位时的默认值"
HAL_GPIO_LockPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
锁定GPIO引脚GPIO_Pin的配置;当GPIO_Pin执行了"锁键的写入时序"后,在下次系统复位前将不能再更改端口位的配置;
1、LED灯初始化
#include "LED.h"
void LED_Init(void);
//函数功能:配置PC13为输出,无上拉或下拉,输出速度为5MHz
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE(); //GPIOC时钟使能
GPIO_InitStruct.Pin = GPIO_PIN_13; //选择引脚号码
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; //推挽输出模式
GPIO_InitStruct.Pull = GPIO_NOPULL; //引脚上拉和下拉都没有被激活
// GPIO_InitStruct.Pull = GPIO_PULLUP; //设置上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; //引脚的输出速度为5MHz
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
//根据GPIO_InitStruct结构变量指定的参数初始化GPIOC的外设寄存器
LED1_Off();
}
#ifndef __LED_H__
#define __LED_H__
#include "stm32g4xx_hal.h"
#define LED1_On() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET)
//输出低电平开灯
#define LED1_Off() HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET)
//输出高电平关灯
#define LED1_Toggle() HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13)
//LED1引脚输出电平翻转
extern void LED_Init(void);
#endif /*__ GPIO_H__ */
2、Key初始化程序
#include "Key.h"
void Key_Init(void);
//函数功能:将PA12引脚配置为输入引脚
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE(); //GPIOB时钟使能
GPIO_InitStruct.Pin = GPIO_PIN_15; //选择第15脚
GPIO_InitStruct.Pull = GPIO_PULLUP; //引脚上拉被激活
// GPIO_InitStruct.Pull = GPIO_NOPULL; //引脚上拉和下拉都没有被激活
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; //配置GPIO速度为中速
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; //设置引脚工作模式为输入模式
HAL_GPIO_Init(GPIOB,&GPIO_InitStruct);
//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
}
#ifndef __Key_H__
#define __Key_H__
#include "stm32g4xx_hal.h"
#define Key_LevelValue() HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_15) //读取Key1的电平值
extern void Key_Init(void);
#endif /* __Key_H */
3、delay.c程序
#include "delay.h"
static uint8_t fac_us=0; //us延时倍乘数
static uint16_t fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数
void My_delay_us(__IO uint32_t nCount);
//systick中断服务函数,1ms中断一次
//在调用HAL_Init()时,SysTick定时器初始化为使能中断
void SysTick_Handler(void)
{
}
//函数功能:delay函数初始化
//在调用HAL_Init()时,已经对SysTick定时器初始化了,因此这里无需初始化
void delay_init(void)
{
uint32_t reload;
fac_us=SystemCoreClock/1000000;
//相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us
reload=SystemCoreClock/1000000;
//相当于将170MHz经过1000000分频用作SysTick的输入时钟,即周期为1us
reload*=1000;
//SysTick溢出时间为1000*1us=1ms
//reload为24位寄存器,最大值:16777216,在170MHz下,约合0.098s左右
fac_ms=1;
}
//函数功能:延时nus微妙
//nus:要延时的us数.
//nus:0~204522252(最大值即2^32/fac_us@fac_us=168)
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told,tnow,tcnt=0;
uint32_t reload=SysTick->LOAD;
//读取SysTick的LOAD寄存器的值
ticks=nus*fac_us; //计算需要的节拍数
told=SysTick->VAL;//刚进入时的计数器值
while(1)
{
tnow=SysTick->VAL;//读SYSTICK计数器值
if(tnow!=told)
{
if(tnow<told)tcnt+=told-tnow; //SysTick定时器是一个递减的计数器.
else tcnt+=reload-tnow+told;
told=tnow;
if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.
}
}
}
//函数功能:延时nms毫秒
//nms:要延时的ms数
//nms:0~65535
void delay_ms(uint32_t nms)
{
delay_us((uint32_t)(nms*1000));//普通方式延时
}
//延时nms,不会引起任务调度
//nms:要延时的ms数
void delay_xms(uint32_t nms)
{
uint32_t i;
for(i=0;i<nms;i++) delay_us(1000);
}
4、Clock_Config.c程序
#include "Clock_Config.h"
#include "stdio.h" //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
void SystemClock_Config(void);
void Print_HCLK_PCLK1_PCLK2(void);
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
//配置主内部调节器输出电压
//修改PWR->CR1寄存器bit10:9(VOS[1:0]),VOS[1:0]=01b,电压缩放范围选择"Range 1"
//Configure the main internal regulator output voltage
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
//记录修改对象,告诉后面的函数将要对“HSE时钟”进行配置
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV2;
//RCC_PLLCFGR寄存器bit7:4(PLLM[3:0]),PLLM[3:0]=0001b,PLLM的值为2
RCC_OscInitStruct.PLL.PLLN = 85;
//RCC_PLLCFGR寄存器bit14:8(PLLN[3:0]),PLLN[6:0]=0x55,PLLN为的值为85
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
//RCC_PLLCFGR寄存器bit17(PLLP),PLLP=1,PLLP的分频值为17
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
//RCC_PLLCFGR寄存器bit22:21(PLLQ[1:0]),PLLQ[1:0]=01,PLLQ的分频值为2
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
//RCC_PLLCFGR寄存器bit26:25(PLLR[1:0]),PLLR[1:0]=01,PLLR的分频值为2
/*
f(VCO clock) = (HSE_VALUE / PLLM) * PLLN
f(PLL_P) = f(VCO clock) / PLLP
f(PLL_Q) = f(VCO clock) / PLLQ
f(PLL_R) = f(VCO clock) / PLLR
HSE_VALUE = 8000000,外部晶振为8MHz
PLL_VCO = (HSE_VALUE / PLLM) * PLLN = (8000000/2)*85=340000000Hz=340MHz
SYSCLK = PLL_VCO / PLLR = 340000000 / 2 = 170000000Hz = 170MHz
PLL “P” clock = PLL_VCO / 2 = 340000000/2 =170000000Hz = 170MHz
PLL “Q” output clock frequency = VCO frequency / PLLQ = 340000000 / 2 = 170000000Hz = 170MHz
*/
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{//配置“HSE时钟”和“PLL时钟”
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
//记录修改对象,告诉后面的函数将要修改HCLK,SYSCLK,PCLK1,PCLK2等时钟
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
//记录修改对象,告诉后面的函数将PLLCLK用作系统时钟
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
//AHB时钟(HCLK)的分频值
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
//配置RCC_CFGR寄存器bit10:8(PPRE1[2:0]),APB1时钟(PCLK1)的分频值
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
//配置RCC_CFGR寄存器bit13:11(PPRE2[2:0]),APB2时钟(PCLK2)的分频值
//APB2外设:HRTIM,TIM1,TIM8,TIM15,TIM16,TIM17,TIM20,SPI1,SPI4,USART1,SAI1,SYSCFG/COMP/OPAMP/VREFBUF
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{//配置HCLK,SYSCLK,PCLK1,PCLK2时钟
//HCLK为170MHz,PCLK1时钟为170MHz,PCLK2时钟为170MHz
Error_Handler();
}
SystemCoreClockUpdate();//更新SystemCoreClock的值
}
5、main.c程序
#include "main.h"
//#include "cmsis_os.h"
#include "stdio.h" //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
#include "sys.h"
#include "Clock_Config.h"
#include "Key.h"
#include "LED.h"
#include "delay.h"
//STM32G474输入输出测试程序
int main(void)
{
STACK_Init();
HAL_Init();
//复位所有的外设
//初始化FLASH接口
//将SysTick定时器配置1ms中断
SystemClock_Config();
//Configure the system clock
delay_init();
delay_ms(1000);
Key_Init();
LED_Init();//配置PC13为输出,无上拉或下拉,输出速度为5MHz
while (1)
{
if( Key_LevelValue()==0 )//如果按钮一直被按下,则灯会闪烁
{
LED1_Toggle(); //LED1引脚输出电平翻转
delay_ms(500);
}
}
}
//函数功能:在发生错误时,将执行此函数。
void Error_Handler(void)
{
__disable_irq();
while (1)
{
printf("Error\r\n");
}
}
6、测试结果
当一直按下PB15时,绿色LED灯会不停地闪烁。
STM32G474最小系统板,BOOT0需要10K/15K电阻到GND,保证程序从FLASH启动。商家的开发问题还是有的。