STM32 CubeMX 安装与使用入门(三)printf重定向,UART串口配置及GPIO按键中断

简介

这部分内容介绍如何使用CubeMx配置UART串口查询式发送,和GPIO中断式按键控制。在这部分结束后,最后会介绍如何重定向printf到串口。

硬件准备

笔者使用正点原子战舰V3开发板,使用任何主控是STM32的硬件设备并且带有GPIO控制的LED和按键,就可以,硬件上没有什么限制。开发板上自带usb转串口,用开发板链接数据线到电脑。

软件准备

  • STM32CubeMx
  • Keil MDK,IAR或类似的编译环境

实际操作部分

需求分析

使用GPIO中断的方式扫描按键来控制LED的亮灭,并通过UART1向电脑发送信息。

上手操作

打开CubeMx,选择ACCESS TO SELECTOR


刚安装好第一次点击会进入一个加载页面,他是链接官网更新芯片库的,如果加载时间过长,也可以关闭加载页面,也能直接进入我们下一步要操作的页面


在右上方选择你的硬件装置搭载的芯片,然后在左下方选择你要用的芯片并双击进入下一个页面。

可以通过界面发现,CubeMx工程的配置步骤非常清晰,从左到右分别为引脚与外设配置,时钟树配置,工程相关配置。从上到下也是系统核心功能到外设的配置。最右方的区域用图形化的方式配置相关的引脚。

1.第一步需要配置时钟源,我在这里选用外部晶振作为外部时钟源。左侧选择后,右侧会自动选择外部时钟源要用的引脚

2.根据原理图找到相应的外设所在的引脚,这里我使用UART1和LED1,KEY1,KEY2

3.在引脚页面中配置相关引脚

上图配置GPIO相关,注意我这里配置的是外部中断下降沿触发,不同的硬件是不一样的,要留意自己的硬件应该是什么触发方式

上图配置USART串口相关,波特率选择115200,8位数据1位停止。

3.因为使用了中断,需要配置NVIC的中断优先级

NVIC全称

Nested vectored interrupt controller

即嵌套向量中断控制器,用来决定中断的优先级。

NVIC在 ARM Conrtex-M 内核中,用一个 8 位的寄存器来配置,总共可以配置256级中断,但是 ST 公司在生产 STM32 的时候,发现一个小小的单片机根本用不了这么多,纯属浪费,所以将该寄存器的低 4 位全部置0,只使用高 4 位来配置,这样一来 STM32 就只有16级中断啦。

  • 配置优先级分组

    这里优先级分组设置为2位抢占优先级2位子优先级
    两个外部中断引脚抢占优先级分别设置为1和2

4.引脚配置完了,接下来配置时钟树

时钟频率,f103zet6最高为72Mhz,通过配置,最后使APB外设的时钟频率达到最高就可以了

5.配置工程相关

有两个地方要注意,生成工程的路径不能有中文,生成的IDE版本要正确,我这里选择的是MDK5.

6.配置完这些步骤后就可以点击GENERATE CODE生成工程了

7.打开工程

  • 串口部分代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* USER CODE BEGIN 2 */

uint8_t recv_buf[100];
/* USER CODE END 2 */

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
//接收12个字节的数据,不超时
if(HAL_OK == HAL_UART_Receive(&huart1, (uint8_t*)recv_buf, 12, 0xFFFF))
{
//将接收到的数据发送
HAL_UART_Transmit(&huart1, (uint8_t*)recv_buf, 12, 0xFFFF);
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

接收什么内容再原路返回

  • 按键外部中断部分代码
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
void EXTI3_IRQHandler(void)
{
/* USER CODE BEGIN EXTI3_IRQn 0 */

/* USER CODE END EXTI3_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
/* USER CODE BEGIN EXTI3_IRQn 1 */

/* USER CODE END EXTI3_IRQn 1 */
}

/**
* @brief This function handles EXTI line4 interrupt.
*/
void EXTI4_IRQHandler(void)
{
/* USER CODE BEGIN EXTI4_IRQn 0 */

/* USER CODE END EXTI4_IRQn 0 */
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
/* USER CODE BEGIN EXTI4_IRQn 1 */

/* USER CODE END EXTI4_IRQn 1 */
}

如上图,在stm32f1xx_it.c中两个外部中断调用的同一个中断函数
不要担心,我们找到这个函数的定义处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
/* EXTI line interrupt detected */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
HAL_GPIO_EXTI_Callback(GPIO_Pin);
}
}

/**
* @brief EXTI line detection callbacks.
* @param GPIO_Pin: Specifies the pins connected EXTI line
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}

发现它使用的是一个用_weak定义的回调函数,这意味这,这个函数我们可以重新编写,完成自己想要的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* 判断哪个引脚触发了中断 */
switch(GPIO_Pin)
{
case GPIO_PIN_3:
/* 处理GPIO3发生的中断 */
//点亮LED
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);
break;
case GPIO_PIN_4:
/* 处理GPIO4发生的中断 */
//熄灭LED
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET);
break;
default:
break;
}
}

在main.c中,我重新定义了这个函数,完成可以控制LED灯亮灭的功能。

8.下载代码观察现象,发现可以实现我们想要的功能:)

9.基本功能已经实现,接下来进行后续工作

实现printf函数重定向至串口

笔者查看了很多博客,最终发现了一种比较好的方式进行重定向

1
2
3
#include "stdarg.h"	 	 
#include "stdio.h"
#include "string.h"

首先在usart.c中引入这些库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* USER CODE BEGIN 1 */
void u1_printf(char* fmt,...)
{
uint16_t i;
uint16_t j;
va_list ap;
va_start(ap,fmt);
vsprintf((char*)USART1_TX_BUF,fmt,ap);
va_end(ap);
i=strlen((const char*)USART1_TX_BUF); //此次发送数据的长度

for(j=0;j<i;j++) //循环发送数据
{
while((USART1->SR&0X40)==0); //循环发送,直到发送完毕
USART1->DR=USART1_TX_BUF[j];
}

}
/* USER CODE END 1 */

在usart.c中添加函数如上

1
2
3
4
5
6
7
8
9
10
11
12
13
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
//接收12个字节的数据,不超时
if(HAL_OK == HAL_UART_Receive(&huart1, (uint8_t*)recv_buf, 12, 0xFFFF))
{
//将接收到的数据发送
u1_printf("your massege is :%s",recv_buf);
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */

将main.c中的代码改成这个形式,编译并下载代码到硬件中进行验证

可以实现想要的功能,这个函数名可以改成任意你想要的名字就行,叫u1_printf,printf都可以,甚至多个uart模块定义多个printf。
如u1_printf,u2_printf,u3_printf.

总结

  • 这部分内容实现了使用CubeMx配置中断优先级和串口,同时介绍了如何重定义printf到串口,这个之后能用到的地方很多,用于和各种模块通信。
  • 下一部分介绍如何使用CubeMx配置定时器相关功能,包括使用定时器输出PWM波,使用定时器中断,使用定时器完成输入捕获,敬请关注!

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2021 张竞豪的小岛 All Rights Reserved.

UV : | PV :