# LoraLed **Repository Path**: lv-liujun/lora-led ## Basic Information - **Project Name**: LoraLed - **Description**: STM32F405RGT6+Lora通信模块的设备开发 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2025-03-27 - **Last Updated**: 2025-06-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 一、目录结构讲解 | 文件目录 | 目录讲解 | | :------------ | ------------------------------------------------------------ | | `BSP` | 板级支持包,包含模块的驱动代码(`HAL`库开发移植性好) | | `Core` | 包含项目的核心代码,如 `main.c`、`startup` 、以及项目用到的协议文件等 | | `Drivers` | 包含外设驱动代码,如 `GPIO`、`UART`、`I2C` 等。 | | `MDK-ARM` | 包含 `Keil MDK-ARM` 开发工具相关的配置文件和项目文件(使用`keilkill.bat`释放空间) | | `Middlewares` | 包含中间件代码,如 `FreeRTOS`(本项目使用的操作系统) | | `Tasks` | 存放各个模块的任务代码(使用`cmsis_os2`,对`FreeRTOS`进一步封装的`API`,移植性较好) | | `.mxproject` | `STM32CubeMX` 生成的项目文件,包含硬件配置信息 | | `LoraLed.ioc` | `STM32CubeMX` 的配置文件,用于存储硬件配置和生成初始化代码 | | `README.md` | 项目的说明文件,通常包含项目概述、使用方法、注意事项等 | 本项目采用`stm32cubeMX`+`Keil uVision5`+`vscode`开发 - `stm32cubeMX`:用户配置项目,以及开发板的引脚信息 - `Keil uVision5`:用于编译,调试,下载代码 - `vscode`:用于编写代码 ## 二、开发遇到的问题 ### 1、无法使用`printf`输出语句 **解决方法**:在串口代码中添加如下代码 ```c #include int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF); // 使用 HAL 库发送字符 HAL_UART_Transmit(&huart4, (uint8_t *)&ch, 1, 0xFFFF); // 使用 HAL 库发送字符 return ch; } ``` 并在`Keil uVision5`集成开发环境中,找到`Options for Target -> Target -> Use MicroLIB(打勾)` ### 2、按键`LED`灯实际显示效果相反 ```c // 修改前(错误逻辑): if (HAL_GPIO_ReadPin(BUTTON_GPIO_Port, BUTTON_Pin)) { HAL_GPIO_TogglePin(...); } // 修改后(直接切换): HAL_GPIO_TogglePin(BTN_LED_GPIO_Port, BTN_LED_Pin); ``` ### 3、`gitee`上传问题 1. **行尾符格式差异**: - 在 Unix/Linux 系统中,行尾符通常使用 `LF`(`\n`)。 - 在 Windows 系统中,行尾符通常使用 `CRLF`(`\r\n`)。 - Git 默认会根据操作系统的不同进行行尾符的自动转换。 2. **文件的行尾符格式**: - 文件 `MDK-ARM/LoraLed.uvguix.25580` 当前使用的是 `LF` 格式。 - Git 提示在下次操作时会将其转换为 `CRLF` 格式,以适应 Windows 系统 ```bash 25580@DESKTOP-O8L1LM6 MINGW64 /d/ProgrammeProject/OpenHarmony/LoraLed (master) $ git add . warning: in the working copy of 'MDK-ARM/LoraLed.uvguix.25580', LF will be repla ced by CRLF the next time Git touches it ``` **解决方法**:全局配置 Git 行尾符处理: - 如果你希望 Git 在所有项目中都保持行尾符格式不变,可以在全局配置中设置: ```bash git config --global core.autocrlf false ``` - 这将禁用 Git 的自动行尾符转换。 ### 4、定义了多个`EXTI`中断事件,但是只有使用了多个中断函数,导致编译出错 **解决:**定义一个中间函数`sys.c`,用于创建中间函数 ```c /* 中断回调函数 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { switch (GPIO_Pin) { case BUTTON_Pin: // printf("[EXTI Callback] Trigger key interrupt function.\r\n"); osEventFlagsSet(buttonEvent, 0X0001); // 设置事件标志 break; case IR_SENSOR_Pin: // printf("[EXTI Callback] Trigger irSensor interrupt function.\r\n"); osEventFlagsSet(irSensorEvent, 0X0001); // 设立事件标志 break; default: printf("[EXTI Callback] There is no such interrupt event.\r\n"); break; } } ``` 并在头文件中声明外部变量,便于其它文件使用 ```c extern osEventFlagsId_t buttonEvent; // 按键事件标志 extern osEventFlagsId_t irSensorEvent; // 红外传感器事件标志 ``` 为了防止多次创建事件标志,将各个任务函数中创建任务事件标志的代码封装成一个函数,在freertos.c文件中调用一次即可。 ```c void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ SYS_Init(); // ... } ``` ### 5、当感应灯打开的时候,灯的亮度不会动态改变 ```c flags = osEventFlagsWait(lightSensorEvent, 0X0001, osFlagsWaitAny, osWaitForever); ``` - **问题**:`osWaitForever`参数导致任务**永久阻塞在此处**,直到按键事件发生。即使灯已开启(`isLedOn == true`),任务也无法执行后续的亮度更新逻辑,除非再次触发按键事件。 - **后果**:LED开启后,代码仅通过按键事件唤醒任务,无法持续更新亮度。 ### 6、`PWM`灯按键会影响感应灯的亮灭 > 直接复制`PWM`灯按键的消抖逻辑,忘记修改按键的引脚值了 ```c // 等待按键释放 do { osDelay(5); readPin = HAL_GPIO_ReadPin(LIGHT_BTN_GPIO_Port, LIGHT_BTN_Pin); } while (readPin == GPIO_PIN_RESET); ``` ## 三、`Lora`编码差异(和开源代码) **优先级分组问题** 下面表格中修改的代码是`STM32cubemx`自动配置好的,我并没有修改,只是对照源码,看能不能正常使用,结果是好的。 | 优先级分组差异 | 修改 | 源码 | | :--------------: | :------------------------------------------: | :------------------------------------------: | | 串口1优先级分组 | `HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);` | `HAL_NVIC_SetPriority(USART1_IRQn, 1, 1);` | | 串口4优先级分组 | `HAL_NVIC_SetPriority(UART4_IRQn, 5, 0);` | `HAL_NVIC_SetPriority(UART4_IRQn, 0, 0);` | | 定时器优先级分组 | `HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 5, 0);` | `HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 2, 2);` | ## 四、模块讲解 ### 4.1 按键`LED`灯 #### 4.1.1 模块接线 - 按键 - `VCC`接`3.3V` - `GND`接`0V` - `OUT`接`PA4` - `LED` - `VCC`接`3.3V` - `GND`接`0V` - `IN`接`PA5` #### 4.1.2 代码讲解 | 函数 | 作用 | 函数功能 | | :----------------------------------------------: | :------: | :----------------------------------------------------------: | | `void ButtonLedTaskEnter(void)` | 入口函数 | 创建任务和一个事件标志,该事件标志用于检测外部事件是否触发(在`gpio.c`文件中可查看引脚的配置参数),当按键触发时,调用设置事件标志。 | | `static void ButtonLedTask(void *argument)` | 任务函数 | 等待中断函数中的事件标志被触发,当事件触发后表示检测到按键按下,进行软件延时消抖后,切换`LED`灯状态,并打印输出信息 | | `void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)` | 中断函数 | 当检测到按键按下时,就设置事件标志 | | `void SetButtonLedState(bool state)` | 接口函数 | 设置LED灯状态 | | `bool GetButtonLedState(void)` | 接口函数 | 获取LED灯状态 | #### 4.1.3 `FreeRTOS`调度 该模块的任务文件为:`btnLEDTask.c`,在`freertos.c`文件中添加如下信息: **添加头文件** ```c /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "sys.h" #include "btnLedTask.h" /* USER CODE END Includes */ ``` **调用任务函数** ```c /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ ButtonLedTaskEnter(); /* USER CODE END RTOS_THREADS */ ``` ### 4.2 `PWM`灯 输出一个频率为`1KHZ`,占空比可任意调节,且分辨率为`1%`的`PWM`波形 - `PWM`频率:`Freq = CK_PSC / (PSC + 1) / (ARR + 1) ` - `CK_PSC`::定时器的时钟频率,通常由系统时钟或外部时钟提供 - `PSC`:预分频器(`Prescaler`)值,用于分频定时器的输入时钟 - `ARR`:自动重装载寄存器(`Auto Reload Register`)值,用于设置定时器的计数范围 - ``PWM`占空比:`Duty = CCR / (ARR + 1)` - `CCR`:捕获比较寄存器(`Capture Compare Register`)值,用于设置`PWM`信号的高电平持续时间 - `ARR`:自动重装载寄存器的值 - `PWM`分辨率:`Reso = 1 / (ARR + 1)` - `ARR`:自动重装载寄存器的值 参照:`STM32F4xx`中文参考手册可以`54/1284`可以知道,`TIM13`采用的是`APB1`总线的`APB1 Timer clocks(MHz)`,根据`STM32CubeMX`配置可以知道:改总线上的时钟频率为:`84MHz` > 综上可得: > > - `CK_PSC` :`84MHz` > - `PSC` :`840-1` > - `ARR `:`100-1` > - `CCR`:`0`到`100` #### 4.2.1 模块接线 - 按键 - `VCC`接`3.3V` - `GND`接`0V` - `OUT`接`PA7` - `LED` - `VCC`接`3.3V` - `GND`接`0V` - `IN`接`PA6` #### 4.2.2 代码讲解 | 函数 | 作用 | 函数功能 | | :--------------------------------------: | :------: | :----------------------------------------------------------: | | `void PwmLedTaskEnter(void)` | 入口函数 | 创建任务,配置任务名称和优先级 | | `static void PwmLedTask(void *argument)` | 任务函数 | 启用`PWM`信号,在任务循环中直接调用设置`CCR`值的函数,从而直接改变占空比 | | `void SetPwmDuty(uint8_t duty)` | 接口函数 | 设置占空比 | #### 4.2.3 `FreeRTOS`调度 该模块的任务文件为:`pwmLedTask.c`,在`freertos.c`文件中添加如下信息: **添加头文件** ```c /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "pwmLedTask.h" /* USER CODE END Includes */ ``` **调用任务函数** ```c /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ PwmLedTaskEnter(); /* USER CODE END RTOS_THREADS */ ``` **调试接口函数:**在`FreeRTOS`默认任务函数中调试接口函数 ```c void StartDefaultTask(void *argument) { SetPwmDuty(100); // 修改占空比的值 // ... } ``` #### 4.2.4 拓展按键功能 > 使用外部中断添加按键功能,来控制PWM灯的亮灭。和其它模块代码功能一致,这里不做过多讲解 ### 4.3 人体感应灯 #### 4.3.1 模块接线 - `HC-SR501` - `VCC`接`5V` - `GND`接`0V` - `OUT`接`PC2` - `LED` - `VCC`接`3.3V` - `GND`接`0V` - `IN`接`PC5` #### 4.3.2 模块功能讲解 1. **电气参数:** - 工作电压范围:`4.5 - 20V` - 电平输出:高`3.3V`/低`0v` - 触发方式:`L` 不可重复触发/ `H` 可重复触发 - 延时时间:`0.5 - 200S`(可调) - 感应角度:`<100` 度 - 工作温度:`-15 - 70℃` 2. **功能特点:** - 全自动感应:人进入其感应范围则输出高电平, 人离开感应范围则**自动延时关闭**高电平(限位器调节),输出低电平。 - 两种触发方式:(可跳线选择) - a、不可重复触发方式: > **感应输出高电平后,延时时间段一结束**,输出将自动从高电平变成低电平; - b、可重复触发方式: > 感应输出高电平后,**在延时时间段内,如果有人体在其感应范围活动,其输出将一直保持高电平,直到人离开后才延时将高电平变为低电平**(感应模块检测到人体的每一次活动后会自动顺延一个延时时间段,并且以最后一次活动的时间为延时时间的起始点)。 3. **使用说明:** - 感应模块通电后有一分钟左右的初始化时间,在此期间模块会间隔地输出 `0-3` 次,一分钟后进入待机状态 4. **电位器调节:** - 调节距离电位器**顺时针旋转**,感应距离增大(约 7 米),反之,感应距离减小(约 `3` 米)。 - 调节延时电位器**顺时针旋转**,感应延时加长(约`300S`),反之,感应延时减短(约 `0.5S`) #### 4.3.3 代码讲解 模块刚上电有一分钟的初始化时间,待机状态中模块默认输出低电平,当检测到有人时输出高电平,并延时关闭高电平输出低电平。在可触发模式下延时关闭期间,可重复检测活动范围内的人体移动。所以,代码既要检测高低电平的状态,还要控制动态变化的延时关闭时间。即不能直接写死延时阻塞时间,而是通过中断函数配置任务机制来规避动态改变的延时时间。 具体实现,由于模块默认是低电平,检测到有人时才输出高电平。所以启用下拉电阻,启用`EXTI`中断函数,并配置成上升沿触发。到模块检测到有人时触发中断,任务函数中检测到事件标志被设置的时候,读取传感器输出引脚状态,并使用循环语句等待模块输出低电平,之后禁用中断防止模块再次触发中断(目的是增加软件延时时间)。 | 函数 | 作用 | 函数功能 | | :----------------------------------------: | :------: | :----------------------------------------------------------: | | `void IrLedTaskEnter(void)` | 入口函数 | 创建任务,配置任务名称和优先级 | | `static void IrLedTask(void *argument)` | 任务函数 | 等待外部中断事件触发,处理传感器的输入信号,并设置软件延时时间 | | `void SetIrSensorDelayTime(uint32_t time)` | 接口函数 | 设置红外传感器的延时时间 | | `void SetIRLedState(bool state)` | 接口函数 | 设置红外LED灯状态 | | `bool GetIRLedState(void)` | 接口函数 | 获取红外LED灯状态 | #### 4.2.4 `FreeRTOS`调度 **添加头文件** ```c /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "sys.h" #include "irLedTask.h" /* USER CODE END Includes */ ``` **调用任务函数:** ```c /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ IrLedTaskEnter(); /* USER CODE END RTOS_THREADS */ ``` ### 4.4 光照感应灯 #### 4.4.1 模块接线 - 光强感应模块 - `VCC`接`3.3V` - `GND`接`0V` - `AO`接`PC4` - 按键 - `VCC`接`3.3V` - `GND`接`0V` - `OUT`接`PC0` - `LED` - `VCC`接`3.3V` - `GND`接`0V` - `IN`接`PB10` #### 4.4.2 代码讲解 | 函数 | 作用 | 函数功能 | | :----------------------------------------: | :------: | :----------------------------------------------------------: | | `void LightLedTaskEnter(void)` | 入口函数 | 创建任务,配置任务名称和优先级 | | `static void LightLedTask(void *argument)` | 任务函数 | 循环中非阻塞等待按键触发,当按键按下时切换灯的亮灭状态。当按键未按下且灯的状态为亮时,根据感光原件执行动态更新占空比 | | `static uint16_t LightLedADCRead(void)` | 操作函数 | 获取`ADC`数值 | | `static uint16_t LightLedGetData(void)` | 操作函数 | 处理一定数量的`ADC`的数值,返回`ADC`的处理后的平均值 | | `static uint16_t ADCChangeCCR(void)` | 操作函数 | `ADC`值等值转换成`CCR`值,`CCR`值:`0 - 1000` | #### 4.4.3 `FreeRTOS`调度 **添加头文件** ```c /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "sys.h" #include "lightLedTask.h" /* USER CODE END Includes */ ``` **调用任务函数** ```c /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ LightLedTaskEnter(); /* USER CODE END RTOS_THREADS */ ``` ## 五、如何使用 1. 在仓库中下载完代码后,打开`Keil uVision5`,先编译后下载、并使用`ST-LINK`等下载器,连接对应开发板的`SWD`口。 > 代码上传前先`kill`清理编译和可执行文件,确保整个项目较小,方便下载 2. 下载完成之后先点击开发板的复位按钮,等待`lora`模块连接,后进行模块操作。 > 因为`lora`模块的代码,在启动操作系统调度的前面