其实做这个话题是由于实验课程布置的一个综合性实验,记录一下也希望博客的各位能够批评指正,那么下面正文开始。
一、具体要求
该综合实验的具体要求如下:
基于正点原子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
|