diff --git a/.gitignore b/.gitignore index b1241dc..844cd0c 100644 --- a/.gitignore +++ b/.gitignore @@ -51,5 +51,5 @@ build ./idea .vscode/.cortex-debug.peripherals.state.json .vscode/.cortex-debug.registers.state.json -*.jdebug.user +*.jdebug* settings.json \ No newline at end of file diff --git a/Bug_Report.md b/Bug_Report.md new file mode 100644 index 0000000..4223b3f --- /dev/null +++ b/Bug_Report.md @@ -0,0 +1,47 @@ +# 异常报告 + +使用中遇到的bug和错误放在此处。参照下列格式: + +## 标题用简短的一句话描述 + +### 出现问题的application/module/bsp + +描述你的使用方法,应该贴上图片或代码块,以及硬件连线等 + +### 尝试解决的方案 + +你的尝试,以及猜测可能的错误 + +### 如何复现问题 + +问题能否稳定复现?描述复现方法等 + +### 紧急程度 + +这里用⭐表示。最大5颗⭐。 + +如果不修复,会有何种其他牵连情况发生? + +--- + +不同的问题用 --- 分隔开 + +你还可以使用Stepsize插件在代码出现问题(可能出现问题)的地方添加issues并详尽描述。或在gitee上增加issues。 + +当然,最快的方法是在群里提问。 + +## 使用LK电机并挂载在hcan2上时会出现HardFault + +使用MF9025v2电机,并将其配置在CAN2上。经过一次LKMotorControl,第二次进入时hcan->instance会在HAL_CAN_Add_Tx_Message()结束时被未知的语句修改成奇怪的值,造成HardFault + +### 尝试解决的方案 + +单步调试无果,在HAL_CAN_Add_Tx_Message()返回的那一步hcan->instance会莫名其妙变成0,hcan2也会被修改到一个0x8000xxx的地址上(hcan是HAL库自定的全局变量) + +### 如何复现问题 + +使用LK电机并将其挂载在CAN2上,连接电机后直接运行。在第二次进入MotorTAsk中的LKMotorControl时,于检查空闲CAN邮箱时,由于hcan2被修改,访问CAN2外设状态时会访问野指针导致HardFault。 + +### 紧急程度 + +⭐⭐⭐⭐⭐ \ No newline at end of file diff --git a/README.md b/README.md index 8b756f1..ac7dd04 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ > 每个bsp/module/application都有对应文档,建议阅读之后再看代码&进行开发。框架的搭建思路和讲解视频戳这里:[basic_framework讲解](https://www.bilibili.com/video/BV1Bd4y1E7CN)。 > 开发之前必看的文档:README.md & VSCode+Ozone使用方法.md + 修改HAL配置时文件目录的更改.md。开发app层请看application目录下的文档,若要开发module以及bsp务必把上层文档也浏览一遍以熟悉接口定义的方式。 -> 程序的运行流程和框架所有app/module/bsp的数据流图直接拉到本文档底部。 +> **程序的运行流程和框架所有app/module/bsp的数据流图直接拉到本文档底部。** [TOC] diff --git a/TODO.md b/TODO.md index 2b84ce6..da95fcc 100644 --- a/TODO.md +++ b/TODO.md @@ -64,6 +64,12 @@ ### 待完成 +- [ ] 给所有模块增加Daemon 模块以提供离线和异常检测 + +- [ ] 为键鼠/遥控器/ps手柄/视觉上位机等各种控制器提供一套统一的接口,把发来数据转化为标准的控制数据,包括底盘速度云台角度等等 + +- [ ] 给每个模块增加调试的条件编译,并增加bsp log的输出。或直接在运行时添加log等级,输出不同的信息。 + #### buzzer - [ ] 使用bsp_pwm添加buzzer模块 @@ -93,11 +99,6 @@ - [ ] 重构seasky protocol的接口 - [ ] 增加数据未更新的处理 -#### LQR - -- [ ] 通用的LQR控制器(矩阵运算) - - ### 待优化 @@ -199,6 +200,8 @@ ### 待完成 +- [ ] 增加调试的条件编译,使得在没有连接其他应用时也可以假装有那些应用而正常调试运行 + #### ==robot_cmd== - [ ] 键鼠控制 diff --git a/VSCode+Ozone使用方法.md b/VSCode+Ozone使用方法.md index 9d64467..a9f241c 100644 --- a/VSCode+Ozone使用方法.md +++ b/VSCode+Ozone使用方法.md @@ -53,6 +53,8 @@ CubeMX进行初始化 --> VSCode编写代/进行编译/简单调试 --> Ozone变 1. 软件安装(队伍NAS和资料硬盘内提供了所有必要的依赖,安装包和插件,目录是`/EC/VSCode+Ozone环境配置`),请以公共账号登陆网盘,ip地址为`49.123.113.2:5212`,账号`public@rm.cloud`,密码`public`。 + 所有安装包也可以在此百度网盘链接下获得:[archive.zip](https://pan.baidu.com/s/1sO_EI4cToyIAcScOQx-JSg?pwd=6666) + ```shell # 网盘中的文件: basic_framework.zip # 本仓库文件,注意,可能不为最新,建议从仓库clone并定时pull @@ -67,11 +69,15 @@ CubeMX进行初始化 --> VSCode编写代/进行编译/简单调试 --> Ozone变 VSCodeUserSetup-x64-1.73.1.exe # VSCode安装包 ``` -1. C语言从源代码到.bin和.hex等机器代码的编译和链接过程 -2. C语言的内存模型 -3. C语言标准,动态链接库和静态编译的区别,一些编译器的常用选项 -4. STM32F4系列的DBG外设工作原理 -5. GDB调试器、硬件调试器和DBG的关系 +2. C语言从源代码到.bin和.hex等机器代码的编译和链接过程 + +3. C语言的内存模型 + +4. C语言标准,动态链接库和静态编译的区别,一些编译器的常用选项 + +5. STM32F4系列的DBG外设工作原理 + +6. GDB调试器、硬件调试器和DBG的关系 ### 编译全过程 @@ -81,7 +87,7 @@ C语言代码由固定的词汇(关键字)按照固定的格式(语法) 对于单个.c文件,从C语言开始到单片机可识别的.bin文件,一般要经历以下几步: -![img](assets\v2-2797ea99d0d38eb9996993bb0ad77ab2_720w.webp) +![img](assets/v2-2797ea99d0d38eb9996993bb0ad77ab2_720w.webp) 首先是编译**预处理**Preprocessing,这一步会展开宏并删除注释,将多余的空格去除。预处理之后会生成.i文件。 @@ -97,7 +103,7 @@ C语言代码由固定的词汇(关键字)按照固定的格式(语法) ### C语言内存模型 - + 以上是C语言常见的内存模型,即C语言的代码块以及运行时使用的内存(包括函数、变量等)的组织方式。 @@ -127,7 +133,7 @@ RTOS创建任务的时候也会为每个任务分配一定的栈空间,它会 ### Debug外设工作原理 -![image-20221112145717063](assets\image-20221112145717063.png) +![image-20221112145717063](assets/image-20221112145717063.png) DBG支持模块(红框标注部分,也可以看作一个外设)通过一条专用的AHB-AP总线和调试接口相连(Jtag或swd),并且有与**数据**和**外设**总线直接相连的桥接器。它还同时连接了中断嵌套管理器(因此同样可以捕获中断并进行debug)和ITM、DWT、FPB这些调试支持模块。因此DBG可以直接获取内存或片上外设内的数据而不需要占用CPU的资源,并将这些数据通过专用外设总线发送给调试器,进而在上位机中读取。 @@ -153,7 +159,7 @@ ITM是instrument trace macrocell指令追踪宏单元的缩写,它用于提供 ### 字节对齐 -这是内存硬件设计和汇编语言设计的结果。在使用结构体的时候,如果你不做任何事情,编译器会自动帮助你完成字节对齐以提高内存访问的效率。stm32是4字节地址和数据总线的设计,单词可以传输32位数据,因此,访问4字节数据(也就是stm32的“字”,“字长”)效率最高。然而,历史的缘由导致i一个内存地址只存储8位的数据,如果你要访问float数据,则一次需要读取4个地址。当这四个地址是连续的时候,你只需要一次就可以将数据读出。然而,如果一个float数据被存放在0x03-0x07这四个地址,cpu首先要读出0x00-0x03这四个连续的地址,然后再取出最后一个字节;随后读取0x04-0x07这四个连续的字节,再取出前三个字节;最后将最后一个字节和前三个字节拼接在一起,形成我们需要的float数据。 +这是内存硬件设计和汇编语言设计的结果。在使用结构体的时候,如果你不做任何事情,编译器会自动帮助你完成字节对齐以提高内存访问的效率。stm32是4字节地址和数据总线的设计,单词可以传输32位数据,因此,访问4字节数据(也就是stm32的“字”,“字长”)效率最高。然而,历史的缘由导致一个内存地址只存储8位的数据,如果你要访问float数据,则一次需要读取4个地址。当这四个地址是连续的时候,你只需要一次就可以将数据读出。然而,如果一个float数据被存放在0x03-0x07这四个地址,cpu首先要读出0x00-0x03这四个连续的地址,然后再取出最后一个字节;随后读取0x04-0x07这四个连续的字节,再取出前三个字节;最后将最后一个字节和前三个字节拼接在一起,形成我们需要的float数据。 `#pragma`可能是最复杂的预编译指令,不同的编译器支持不同的`#pragma`指令,如常用的`#pragma once`可以替代header guard。arm gnu gcc编译器支持通过`#pragma pack()`来设置字节对齐,支持的对齐参数包括空/1/2/4/8,会启动对应长度的对齐方式。用于通信的结构体(串口/CAN/spi等外设接收数据的时候都是连续的,不会像结构体一样被编译器对齐)在声明时,采用如下的方式: @@ -201,15 +207,15 @@ typedef struct - **Cortex-Debug**,**Cortex-Debug: Device Support Pack - STM32F4**:提供调试支持。cortex debug还会自动帮助你安装一些调试相关的插件。 - **IntelliCode**,**Makfile Tools**:提供代码高亮支持 - ![image-20221112172157533](assets\image-20221112172157533.png) + ![image-20221112172157533](assets/image-20221112172157533.png) - ![image-20221112172208749](assets\image-20221112172208749.png) + ![image-20221112172208749](assets/image-20221112172208749.png) - ![image-20221112172221756](assets\image-20221112172221756.png) + ![image-20221112172221756](assets/image-20221112172221756.png) - ![image-20221112172239386](assets\image-20221112172239386.png) + ![image-20221112172239386](assets/image-20221112172239386.png) - ![image-20221112172254809](assets\image-20221112172254809.png) + ![image-20221112172254809](assets/image-20221112172254809.png) @@ -219,17 +225,17 @@ typedef struct - 安装MinGW,等待界面如下:(will be deprecated soon,请注意这种方法将会在主分支发布正式版的时候删除) - ![image-20221112172051589](assets\image-20221112172051589.png) + ![image-20221112172051589](assets/image-20221112172051589.png) 安装好后,打开MinGW后将所有的支持包勾选,然后安装: - ![image-20221112172348408](assets\image-20221112172348408.png) + ![image-20221112172348408](assets/image-20221112172348408.png) - ![image-20221112172420037](assets\image-20221112172420037.png) + ![image-20221112172420037](assets/image-20221112172420037.png) 安装完以后,将MinGW的bin文件夹添加到环境变量中的path下,按下菜单键搜索**编辑系统环境变量**打开之后: - ![image-20221112172716320](assets\image-20221112172716320.png) + ![image-20221112172716320](assets/image-20221112172716320.png) 图片看不清请打开原图。验证安装: @@ -239,7 +245,7 @@ typedef struct - 配置gcc-arm-none-eabi环境变量,**把压缩包解压以后放在某个地方**,然后同上,将工具链的bin添加到PATH:(will be deprecated soon,请注意这种方法将会在主分支发布正式版的时候删除) - ![image-20221112172858593](assets\image-20221112172858593.png) + ![image-20221112172858593](assets/image-20221112172858593.png)
neozng1@hnu.edu.cn
-> TODO: 增加发送队列以解决短时间内调用`USARTSend()`发生丢包的问题,并提供阻塞和IT模式以供选择 +> TODO: 增加发送队列以解决短时间内调用`USARTSend()`发生丢包的问题,目前仅支持DMA。还需要提供阻塞和IT模式以供选择,参考bspiic和spi进行实现。 > 可以直接在发送函数的参数列表添加发送模式选择,或增加instance成员变量,并提供设置模式接口,两者各有优劣 ## 使用说明 diff --git a/debug_ozone.jdebug b/debug_ozone.jdebug deleted file mode 100644 index f89d1d4..0000000 --- a/debug_ozone.jdebug +++ /dev/null @@ -1,338 +0,0 @@ -/********************************************************************* -* (c) SEGGER Microcontroller GmbH * -* The Embedded Experts * -* www.segger.com * -********************************************************************** - -File : D:/Desktop/basic_framework/debug_ozone.jdebug -Created : 9 Dec 2022 17:21 -Ozone Version : V3.24 -*/ - -/********************************************************************* -* -* OnProjectLoad -* -* Function description -* Project load routine. Required. -* -********************************************************************** -*/ -void OnProjectLoad (void) { - // - // Dialog-generated settings - // - Project.SetDevice ("STM32F407IG"); - Project.SetHostIF ("USB", "805251123"); - Project.SetTargetIF ("SWD"); - Project.SetTIFSpeed ("4 MHz"); - Project.AddPathSubstitute ("D:/Desktop/basic_framework", "$(ProjectDir)"); - Project.AddPathSubstitute ("d:/desktop/basic_framework", "$(ProjectDir)"); - Project.AddSvdFile ("$(InstallDir)/Config/CPU/Cortex-M4F.svd"); - Project.AddSvdFile ("$(InstallDir)/Config/Peripherals/STM32F407IG.svd"); - // - // User settings - // - Edit.SysVar (VAR_HSS_SPEED, FREQ_100_HZ); - File.Open ("$(ProjectDir)/build/basic_framework.elf"); -} - -/********************************************************************* -* -* OnStartupComplete -* -* Function description -* Called when program execution has reached/passed -* the startup completion point. Optional. -* -********************************************************************** -*/ -//void OnStartupComplete (void) { -//} - -/********************************************************************* -* -* TargetReset -* -* Function description -* Replaces the default target device reset routine. Optional. -* -* Notes -* This example demonstrates the usage when -* debugging an application in RAM on a Cortex-M target device. -* -********************************************************************** -*/ -//void TargetReset (void) { -// -// unsigned int SP; -// unsigned int PC; -// unsigned int VectorTableAddr; -// -// VectorTableAddr = Elf.GetBaseAddr(); -// // -// // Set up initial stack pointer -// // -// if (VectorTableAddr != 0xFFFFFFFF) { -// SP = Target.ReadU32(VectorTableAddr); -// Target.SetReg("SP", SP); -// } -// // -// // Set up entry point PC -// // -// PC = Elf.GetEntryPointPC(); -// -// if (PC != 0xFFFFFFFF) { -// Target.SetReg("PC", PC); -// } else if (VectorTableAddr != 0xFFFFFFFF) { -// PC = Target.ReadU32(VectorTableAddr + 4); -// Target.SetReg("PC", PC); -// } else { -// Util.Error("Project file error: failed to set entry point PC", 1); -// } -//} - -/********************************************************************* -* -* BeforeTargetReset -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void BeforeTargetReset (void) { -//} - -/********************************************************************* -* -* AfterTargetReset -* -* Function description -* Event handler routine. Optional. -* The default implementation initializes SP and PC to reset values. -** -********************************************************************** -*/ -void AfterTargetReset (void) { - _SetupTarget(); -} - -/********************************************************************* -* -* DebugStart -* -* Function description -* Replaces the default debug session startup routine. Optional. -* -********************************************************************** -*/ -//void DebugStart (void) { -//} - -/********************************************************************* -* -* TargetConnect -* -* Function description -* Replaces the default target IF connection routine. Optional. -* -********************************************************************** -*/ -//void TargetConnect (void) { -//} - -/********************************************************************* -* -* BeforeTargetConnect -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void BeforeTargetConnect (void) { -//} - -/********************************************************************* -* -* AfterTargetConnect -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void AfterTargetConnect (void) { -//} - -/********************************************************************* -* -* TargetDownload -* -* Function description -* Replaces the default program download routine. Optional. -* -********************************************************************** -*/ -//void TargetDownload (void) { -//} - -/********************************************************************* -* -* BeforeTargetDownload -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void BeforeTargetDownload (void) { -//} - -/********************************************************************* -* -* AfterTargetDownload -* -* Function description -* Event handler routine. Optional. -* The default implementation initializes SP and PC to reset values. -* -********************************************************************** -*/ -void AfterTargetDownload (void) { - _SetupTarget(); -} - -/********************************************************************* -* -* BeforeTargetDisconnect -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void BeforeTargetDisconnect (void) { -//} - -/********************************************************************* -* -* AfterTargetDisconnect -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void AfterTargetDisconnect (void) { -//} - -/********************************************************************* -* -* AfterTargetHalt -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void AfterTargetHalt (void) { -//} - -/********************************************************************* -* -* BeforeTargetResume -* -* Function description -* Event handler routine. Optional. -* -********************************************************************** -*/ -//void BeforeTargetResume (void) { -//} - -/********************************************************************* -* -* OnSnapshotLoad -* -* Function description -* Called upon loading a snapshot. Optional. -* -* Additional information -* This function is used to restore the target state in cases -* where values cannot simply be written to the target. -* Typical use: GPIO clock needs to be enabled, before -* GPIO is configured. -* -********************************************************************** -*/ -//void OnSnapshotLoad (void) { -//} - -/********************************************************************* -* -* OnSnapshotSave -* -* Function description -* Called upon saving a snapshot. Optional. -* -* Additional information -* This function is usually used to save values of the target -* state which can either not be trivially read, -* or need to be restored in a specific way or order. -* Typically use: Memory Mapped Registers, -* such as PLL and GPIO configuration. -* -********************************************************************** -*/ -//void OnSnapshotSave (void) { -//} - -/********************************************************************* -* -* OnError -* -* Function description -* Called when an error ocurred. Optional. -* -********************************************************************** -*/ -//void OnError (void) { -//} - -/********************************************************************* -* -* _SetupTarget -* -* Function description -* Setup the target. -* Called by AfterTargetReset() and AfterTargetDownload(). -* -* Auto-generated function. May be overridden by Ozone. -* -********************************************************************** -*/ -void _SetupTarget(void) { - unsigned int SP; - unsigned int PC; - unsigned int VectorTableAddr; - - VectorTableAddr = Elf.GetBaseAddr(); - // - // Set up initial stack pointer - // - SP = Target.ReadU32(VectorTableAddr); - if (SP != 0xFFFFFFFF) { - Target.SetReg("SP", SP); - } - // - // Set up entry point PC - // - PC = Elf.GetEntryPointPC(); - if (PC != 0xFFFFFFFF) { - Target.SetReg("PC", PC); - } else { - Util.Error("Project script error: failed to set up entry point PC", 1); - } -} \ No newline at end of file diff --git a/modules/remote/remote_control.c b/modules/remote/remote_control.c index 9537eae..5e9917b 100644 --- a/modules/remote/remote_control.c +++ b/modules/remote/remote_control.c @@ -7,7 +7,9 @@ #define REMOTE_CONTROL_FRAME_SIZE 18u // 遥控器接收的buffer大小 // 遥控器数据 -static RC_ctrl_t rc_ctrl[2]; //[0]:当前数据TEMP,[1]:上一次的数据LAST.用于按键持续按下和切换的判断 +static RC_ctrl_t rc_ctrl[2]; //[0]:当前数据TEMP,[1]:上一次的数据LAST.用于按键持续按下和切换的判断 +static uint8_t rc_init_flag = 0; // 遥控器初始化标志位 + // 遥控器拥有的串口实例,因为遥控器是单例,所以这里只有一个,就不封装了 static USARTInstance *rc_usart_instance; static DaemonInstance *rc_daemon_instance; @@ -81,17 +83,17 @@ static void sbus_to_rc(const uint8_t *sbus_buf) /** * @brief 对sbus_to_rc的简单封装,用于注册到bsp_usart的回调函数中 - * + * */ static void RemoteControlRxCallback() { - DaemonReload(rc_daemon_instance); // 先喂狗 + DaemonReload(rc_daemon_instance); // 先喂狗 sbus_to_rc(rc_usart_instance->recv_buff); // 进行协议解析 } /** - * @brief - * + * @brief + * */ static void RCLostCallback() { @@ -115,5 +117,14 @@ RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle) }; rc_daemon_instance = DaemonRegister(&daemon_conf); + rc_init_flag = 1; return (RC_ctrl_t *)&rc_ctrl; +} + +uint8_t RemotecontrolIsOnline() +{ + if (rc_init_flag) + return DaemonIsOnline(rc_daemon_instance); + return 0; + } \ No newline at end of file diff --git a/modules/remote/remote_control.h b/modules/remote/remote_control.h index be995db..d2c2187 100644 --- a/modules/remote/remote_control.h +++ b/modules/remote/remote_control.h @@ -122,4 +122,11 @@ typedef struct */ RC_ctrl_t *RemoteControlInit(UART_HandleTypeDef *rc_usart_handle); +/** + * @brief 检查遥控器是否在线,若尚未初始化也视为离线 + * + * @return uint8_t 1:在线 0:离线 + */ +uint8_t RemoteControlIsOnline(); + #endif diff --git a/modules/standard_cmd/standard_cmd.md b/modules/standard_cmd/standard_cmd.md new file mode 100644 index 0000000..a84669a --- /dev/null +++ b/modules/standard_cmd/standard_cmd.md @@ -0,0 +1,17 @@ +# 标准命令 + +这是一个体力活,也是一个艺术品。请把不同的控制命令module进行封装,以转化成标准的消息类型,包括云台角度速度,底盘速度,发射频率是否发射等信息,供RobotCMD应用或其他应用使用。 + +是否将下面的模块都放到standard_cmd文件夹下?似乎没有必要。 + +## remote control + +## master machine + +## ps handle + +## key and mouse + +## 图传链路 + +这似乎和键鼠是同一套,不过走的是串口,可能需要额外添加支持模块 \ No newline at end of file diff --git a/modules/standard_cmd/std_cmd.c b/modules/standard_cmd/std_cmd.c new file mode 100644 index 0000000..e69de29 diff --git a/modules/standard_cmd/std_cmd.h b/modules/standard_cmd/std_cmd.h new file mode 100644 index 0000000..e69de29 diff --git a/如何定位bug.md b/如何定位bug.md new file mode 100644 index 0000000..3138e9a --- /dev/null +++ b/如何定位bug.md @@ -0,0 +1,84 @@ +# how to locate bug in your code + +只讨论运行中的bug(指程序的运行结果不符合你的期望)和异常(直接异常终止)。编译期出现的warning和error不在此范畴,他们都可以通过直接阅读报错信息解决。 + +## Debug方法论 + +**首先**,请阅读module和bsp的标准使用示例,检查你的代码和示例是否有所不同。如果你正在编写一个新的模块,务必使用**增量式编程**的方法构建你的模块,每完成一部分的功能就进行单元测试,看看是否符合你的期望。**千万不要一口气写完**,这时候再来测试,你就无从下手了。 + +如果确认软件没有问题,先别急着单步调试,检查一下硬件连接是否正确,包括CAN的H和L,串口的TX RX以及电源和GND。 如果你的硬件有多个相同的接口但是使用不同的功能,你得好好看看是否插错接口了。 软件中对hdevice(hcan/hspi/htim等)的设定是否和CUBEMX中配置的一致? 模块的id和地址是否写错。 + +**修改之后要保存,编译,再调试/下载**。建议你把自动保存打开,并勤劳地commit。push时注意reset这些调试时产生的commit,合并成一次提交。你也可以新建一个分支用于解决bug,这是git的最佳实践。 + +同时注意条件编译的兼容,你是否测试一些应用或模块,却使用了错误的`#define`? + +完成上面的两步仍然无济于事,开始单步调试吧。在调试的同时,再仔细看看你的代码是否写错变量名和运算符,比如,把`==`写成了`=`,那么判断条件将永远是`true`。在值得怀疑的地方,打开汇编视图,查看你要访问的地址是否正确。一步一步往下的同时,关注调用栈是否符合你的期望,添加必要的变量到调试窗口,看看他们何时发生意想不到的变化。**条件断点**是一个杀手级功能,你可以设置一些条件,让条件满足时程序在此处停下。下面的一些常见问题可能对你的调试有所帮助。 + +如果一切正常(或者应该说你没有发现异常,虽然他确实在那里),试着查看你使用的外设寄存器值。这时候你要用到芯片的数据手册和功能描述手册。在运行的每一步中,对照datasheet的寄存器状态和改变,看看是否符合你期望的程序行为。寄存器分为控制寄存器/状态就寄存器/数据寄存器,参照datasheet就可以明白这些寄存器是控制哪些功能,描述什么状态以及内部是什么数据。 + +如果你的数据是连续或规则的,记得使用Ozone可视化工具配合条件断点。 + +> 也可以让copilot帮帮你,选中你认为可能出错的代码,选择copilot brush-》debug + +## 一些常见问题 + +### HardFault_Handler() + +99%是由于野指针和非法内存访问导致的。在HardFault函数内添加一句`asm("bx lr");`, 并在此处加上断点。当代码运行至此处时,选择跳出,程序会跳转回出错之前最后一句执行的语句。 + +查看你是否在出错前的最后一次操作中访问了非法地址或使用了已经被析构的指针。 `memcpy`的目标地址和源地址重合也可能引发硬件错误。另外,如果使用指针访问了一个非对齐地址(请参考__pack()相关的说明),这是CMSIS架构中不允许的(有些架构可以修改启动文件使其支持);例如你有四个uint8类型的数据被存储在0x03-0x07的地址内,这时候你通过强制类型转换,以float的方式读取这四个字节,就会发生非对齐访问。 虽然结构体可以通过`__pack(1)`来压缩,编译器会对结构体变量进行处理,在读取非对齐字段时分别读取拆分的两个部分再进行合并,从而支持非对齐访问;但前述的行为却是未定义的,编译器在编译代码的时候并不知道你会以分开的方式访问这段内存,即使知道,他也无法预测栈上分配的空间是否能对齐。 + +常见的错误还包括使用未初始化的指针(内部可能时垃圾值,指向未知的地址)和初始化为NULL的指针(指向0x00地址)。`free`一个指针两次也可能导致错误。 + +### 通信外设传递的数据没有进行压缩 + +经典的`__pack()`问题。这种问题大多出现在结构体的传输上。用于通信的结构体请在两端用`#pragma __pack(1)`和`#pragma __pack()`包裹。否则传输时会出现空字节,使得数据和你使用的协议对不上号。 + +### Delay或定时器卡死永远不跳出 + +Systick和HAL_Delay以及使用TIM来定时的方法,都需要通过**中断**来更新时间。如其重装载计数器上限为65535,当计数达到此值时会触发中断,在中断处理函数中,增加一次溢出的时间。如果此时有更高优先级的中断或同优先级的中断正在运行,且他们耗时很长 or 调用了这些依赖中断的Delay函数,那么将会形成**死锁**,永不见天日。 如果中断被关闭,这些计时也无法更新。 这里推荐使用DWT定时器(在`bsp_dwt`中实现),其重载计数器是64位的,按照stm32f407 168MHz的运行频率,需要两天多的时间才会发生溢出,因此仅依赖其重载计数器,计算两次tick的差值就可以实现高精度的定时(除非你delay超过两天,你在搞笑)。 + +### 静态变量的陷阱 + +注意,函数内的静态变量只会在程序启动的时候初始化一次,之后不论多少次重入,都会保存上一次修改的值。 + +然而,如果静态变量被放在头文件里,则每个包含该头文件的其他源文件,都会拥有一份自己的备份,这些备份之间是互不影响的(详见编译期的头文件展开)。 千万不要认为静态变量是全局的,它只是在当前文件内有效。 + +### 指针越界读写/内存泄漏 + +有时候,你发现你使用的变量值变成了奇怪的数,或者你的程序突然崩溃了,但是你并没有在代码中对这个变量进行过修改。这时候,你应该检查一下你的指针是否越界了。 你也许没有正确的将void\*指针cast成期望的类型,使其访问了不该访问的位置;例如,一个uint8类型长度为4数组,你希望将其转化为float进行读取。但你在声明数组时只分配了3个字节,使用float\*访问就会触及未知的第四个字节,第四个位置上存放的可能是其他变量的值,这时候访问其他变量就会出现奇怪的值。 或者,你在`memset()`和`memcpy()`的时候没有正确设置源地址和目标地址或长度。 + +### 实时系统 + +中断中使用实时系统的接口时记得使用有`ISR`后缀的版本,它们对中断的调用做了特殊处理。所有中断的优先级都是高于RTOS中任务的优先级的。 + +若期望某个任务以1KHz频率运行,你可能会在任务循环外加一个`OS_Delay(1)`,但是你的任务执行时间要是超过了1ms,系统的调度就会出现异常,倘若你还在此任务内认为每次进入的时间间隔是1ms并据此编写了一些依赖周期性的代码,那便是大错特错了。 + +对于实时性和周期性要求高的任务,使用`vTaskDelayUntil()`,这可以获得更高的定时精度。 + +不同的任务/中断调用了相同的函数,或使用了共享变量/全局变量/函数内的static变量会导致读\&写冲突,也被称作**数据竞争**。这一般只会在任务繁重且中断频率高时出现。要避免这种情况,访问共享变量时应进入临界区(关闭全局中断)或使用**互斥锁**。消息队列也是一个不错的选择,这些功能在FreeRTOS中都提供了支持。 + +### 中断 + +中断不要放入太复杂的逻辑和运算。使用标志位或将要处理的数据转移到队列中,于任务中检查标志位,判断是否要进行数据的处理。否则可能出现中断过于复杂/频繁使得通信出现overrun error + +强烈建议初始化时不要使用和中断相关的功能,若你在中断过程中访问了一些尚未初始化的变量,就很大概率会出现前述的野指针/越界等问题。 非要使用,请添加一个标志位,并让中断判断初始化是否完成。**当前框架在初始化机器人的时候关闭了全局中断,所以千万不要使用中断和与symstick/HALtick有关的延时!** + +### 强制类型转换 + +long long的范围比float小。无符号和有符号数直接转换可能变成负数。**应该在一切可能表达运算顺序错误的地方加上括号以明确操作的顺序,即使你知道不同运算符的优先级。** 使用移位操作要注意你的变量类型,请不要相信编译器的临时变量生成,务必加上类型转换。 + +例如:`#define MY_MACRO_NUMBER 3.0f`,使用宏定义一些带小数点的数据时记得加上.0或f后缀,干脆两个都加。无符号定义要加u后缀。 + +### 宏 + +换行用 `\` ,注意同一个代码块展开后用花括号`{}`包裹,特别注意宏展开之后是直接的文本替换!!! + +用已有的宏定义宏并且进行运算时,要将后面的字段用括号包围,因为: + +```c +#define YOUR_DEF SOMETHING +// 宏通过空格来解析替换字段,YOUR_DEF空格后的第一个字段当作替换文本 +``` + +**宏只在当前文件生效**,如果宏放在.c那么对其他的文件是不可见的,这也一般称作私有宏。 \ No newline at end of file