打印
[STM32F1]

基于STM32F103RCT6利用双单片机串口通信实现按键控制PWM呼吸灯闪烁

[复制链接]
92|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wowu|  楼主 | 2025-6-15 22:23 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
其实做这个话题是由于实验课程布置的一个综合性实验,记录一下也希望博客的各位能够批评指正,那么下面正文开始。

一、具体要求
该综合实验的具体要求如下:

基于正点原子NANO STM32F103RCT6,通过串口实现数据发送与接收并实现现象

发送方:

1.key1按下实现全部led的亮

2.key2按下实现全部led的灭

3.key3按下通过串口发送开启消息

4.key4按下通过串口发送关闭消息

接收方:

1.接收通过串口发来的消息

2.读取消息并实现led呼吸灯开启

3.读取消息并实现led呼吸灯关闭

以上是具体的要求,大部分做的话会进行对于Keil来进行直接编写,这里我会使用STM32CubeMX来进行引脚等基础配置,也相应减少了一些基础配置方面的麻烦。

二、STM32CubeMX的基础配置
1、发送方基础配置
由于发送方涉及到按键、LED、串口,所以我们在配置CubeMX时应该调整如图所示的一些引脚配置:

(1)GPIO配置
要求中需要点亮全部的LED并用按键控制,且还需用按键发送消息,所以需要调用的GPIO略多。



其中有一点需要注意,PA0-WKUP引脚的GPIO模式需要更改为Pull-down时发送消息才能生效,原因是由于在查找芯片手册时发现其并不是接地,也就是原本的状态是高电平;而另外三个按键接地,原本状态为低电平。



(2)NVIC(中断)配置



这里对于NVIC进行一些相应的解释:

NVIC基本概念

作用:NVIC负责管理STM32的所有中断和异常,包括优先级管理、中断使能/禁用、中断挂起等。

特点:

支持嵌套中断(高优先级中断可以打断低优先级中断)

向量化中断处理(每个中断有固定入口地址)

低延迟中断处理

NVIC主要功能

中断优先级管理:

STM32使用4位优先级(可配置为抢占优先级和子优先级)

优先级数值越小,优先级越高

中断控制:

使能/禁用单个中断

设置挂起标志和清除挂起标志

检查中断活动状态

系统异常处理:

处理如HardFault、SysTick等系统异常

(3)RCC配置
高速时钟(HSE)选择晶振



(4)SYS配置
模式:Debug为Serial Wire



(5)Connectivity配置
这里选择USART1,为串口选择模式为异步并勾选USART1 global interrupt使能(Enabled)



(6)时钟源配置
在Clock Configuration中将HCLK(MHz)中的数值更改为72MHz。



(7)最终配置图示



这样,发送方的基本配置就完成了,接下来我们再进行接收方的基本配置。

2、接收方基础配置
和前面一样,这里接收方的SYS、RCC、Connectivity、时钟源就不再叙述,配置同上。

(1)NVIC配置
由于前面已经解释过其原理,这里就不再过多赘述,基本的配置如下图:



(2)Timers配置
这里我们选择TIM3,相应配置如下图:



这里还需要有一些修改,将Prescaler的值更改为72-1,将Counter Period的值更改为500,图及原因如下:



1. Prescaler(预分频器) = 72 - 1

作用:对定时器的时钟源进行分频,降低计数频率。

  STM32的定时器时钟通常由 APB总线 提供,默认情况下(如STM32F103系列),APB1的时钟频率是 72 MHz(如果使用外部8MHz晶振并经过PLL倍频)。

  72 - 1 表示预分频系数为72,即:

定时器时钟=APB时钟/(Prescaler+1)=72MHz/72=1MHz

这样,定时器的计数频率变为 1 MHz(即每个计数周期为 1 μs)。

为什么是72?因为STM32F103的APB1时钟通常是72MHz,而PWM呼吸灯一般不需要太高的频率(比如1kHz~10kHz就足够),所以先分频到1MHz,方便后续调整PWM周期。

2. Counter Period(自动重装载值,ARR) = 500

作用:决定PWM的周期(即一个完整PWM波形的时间)。

定时器工作在 向上计数模式(PWM模式1或模式2),计数器从0开始递增,直到达到 ARR(500),然后重新归零,形成周期。

由于前面分频后的计数频率是 1 MHz(1μs/计数),所以PWM的周期为:

PWM周期=(ARR+1)×计数周期=501×1μs=501μsPWM周期=(ARR+1)×计数周期=501×1μs=501μs

对应的PWM频率为:

PWM频率=1/PWM周期=1/501us=2KHz

为什么选择500?

2kHz的PWM频率对人眼来说足够高(>100Hz就不会闪烁),同时500的分辨率足够让LED亮度变化平滑(呼吸灯效果)。

如果ARR太小(如100),PWM频率会变高(~10kHz),但分辨率降低,LED亮度变化会有阶梯感。

如果ARR太大(如1000),PWM频率会降低(~1kHz),虽然分辨率提高,但可能影响LED的响应速度。

到此,接收方配置也已经完成,接下来进行相关代码的配置及解释。

三、Keil工程基本代码及解释
发送方代码

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
/* 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 */
uint8_t uart_tx_buffer[50];  // 串口发送缓冲区
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void MX_GPIO_Init(void);  //GPIO初始化函数
void MX_USART1_UART_Init(void);  //USART1初始化函数
void Send_UART_Message(char *message);  //串口发送消息函数
void Toggle_LEDs(uint8_t state);  //LED控制函数
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void Send_UART_Message(char *message)
{
    HAL_UART_Transmit(&huart1, (uint8_t *)message, strlen(message), HAL_MAX_DELAY);
          //使用HAL库的UART发送函数,最大等待时间
}

// 控制LED函数
void Toggle_LEDs(uint8_t state)
{
    if(state)
                {
        // 点亮所有LED (PC0-PC7)
        GPIOC->ODR |= 0x00FF;
    }
                else
                {
        // 熄灭所有LED (PC0-PC7)
        GPIOC->ODR &= ~0x00FF;
    }
}
/* 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();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  Toggle_LEDs(0);
  Send_UART_Message("System Started\r\n");  //通过串口发送启动消息
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    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;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
static uint32_t last_interrupt_time = 0;  //用于按键消抖的时间记录
#define DEBOUNCE_DELAY 200 // 消抖延时(ms)

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    // 消抖处理
    uint32_t current_time = HAL_GetTick();
    if (current_time - last_interrupt_time < DEBOUNCE_DELAY) {
        return;
    }
    last_interrupt_time = current_time;

    switch(GPIO_Pin)
    {
        case GPIO_PIN_8:  // KEY1 - 全部LED亮
            Toggle_LEDs(1);
            break;

        case GPIO_PIN_9:  // KEY2 - 全部LED灭
            Toggle_LEDs(0);
            break;

        case GPIO_PIN_2:  // KEY3 - 发送开启消息
            while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX); // 等待串口空闲
            Send_UART_Message("System is ON\r\n");
            break;

        case GPIO_PIN_0:  // KEY4 - 发送关闭消息
            while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX); // 等待串口空闲
            Send_UART_Message("System is OFF\r\n");
            break;

        default:
            break;
    }
}

/* 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 */
  __disable_irq();
  while (1)
  {
  }
  /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */



接收方代码

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include <stdlib.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define UART_RX_BUFFER_SIZE 64  //UART接收缓冲区大小
#define BREATHING_SPEED 5  // 呼吸速度,数值越小越快
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint8_t uart_rx_buffer[UART_RX_BUFFER_SIZE];  //UART接收缓冲区
uint8_t uart_rx_data;  //接收到的单个字节
uint16_t uart_rx_index = 0;  //接收缓冲区索引
uint8_t breathing_enabled = 0;  //呼吸灯使能标志
uint16_t pwm_val = 0;  //PWM当前值
uint8_t direction = 1;  // 1:增加亮度, 0:减小亮度
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void Process_UART_Command(char *command);  //处理UART命令
void Breathing_LED_Update(void);  //更新呼吸灯状态
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

//中断接收信息部分
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART1)  //判断是USART1中断
    {
        if (uart_rx_data == '\r' || uart_rx_data == '\n' || uart_rx_index >= UART_RX_BUFFER_SIZE - 1)  //检测到回车换行或缓冲区满时处理命令
        {
            if (uart_rx_index > 0)  //确保有数据
            {
                uart_rx_buffer[uart_rx_index] = '\0'; // 添加字符串结束符
                Process_UART_Command((char *)uart_rx_buffer);  //处理接收到的命令
                uart_rx_index = 0;  //重置索引
            }
        }
        else
        {
            uart_rx_buffer[uart_rx_index++] = uart_rx_data;
        }

        // 重新启动接收
        HAL_UART_Receive_IT(&huart1, &uart_rx_data, 1);
    }
}


//启停PWM呼吸灯部分
void Process_UART_Command(char *command)
{
    if (strstr(command, "System is ON") != NULL)
    {
        breathing_enabled = 1;  //使能呼吸灯
        HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); // 启动PWM
    }
    else if (strstr(command, "System is OFF") != NULL)
    {
        breathing_enabled = 0;  //禁用呼吸灯
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0); // 设置占空比为0
        HAL_TIM_PWM_Stop(&htim3, TIM_CHANNEL_1); // 停止PWM
    }
}


//PWM呼吸灯部分
void Breathing_LED_Update(void)
{
    static uint32_t last_update = 0;  //上次更新时间
    uint32_t current_time = HAL_GetTick();  //获取当前时间

    if (current_time - last_update >= BREATHING_SPEED && breathing_enabled)
    {
        last_update = current_time;  //更新最后更新时间

        if (direction)
        {
            pwm_val += 10;
            if (pwm_val >= 1000)
            {
                pwm_val = 1000;
                direction = 0;
            }
        }
        else
        {
            pwm_val -= 10;
            if (pwm_val <= 0)
            {
                pwm_val = 0;
                direction = 1;
            }
        }
        //更新PWM比较值
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pwm_val);
    }
}
/* 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();
  MX_USART1_UART_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  // 启动UART接收中断
  HAL_UART_Receive_IT(&huart1, &uart_rx_data, 1);

  // 初始化PWM
  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 0); // 初始占空比为0
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    Breathing_LED_Update();
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    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;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != 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 */
  __disable_irq();
  while (1)
  {
  }
  /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */



这两段代码分别实现相应的功能,但是只有代码,怎么实现两单片机之间的相互通信呢?应该这样操作:我们知道PA9、PA10引脚分别为TX、RX也就是传输、接收串口的相关引脚,两单片机通信应将该引脚上的扣帽拔出,用杜邦线将第一个单片机的TX、RX接到第二个单片机的RX、TX上,将两单片机供电后,再用杜邦线同时接入两单片机的GND使其共地,这样就能实现两单片机的通信。



————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/2401_83394251/article/details/148514870

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

119

主题

4265

帖子

1

粉丝