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语言内存模型 -image-20221112160213066 +image-20221112160213066 以上是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)
安装路径可能不一样,这里要使用你自己的路径而不是直接抄
@@ -257,11 +263,11 @@ typedef struct 在project manager标签页工具链选择makefile - ![image-20221112173534670](assets\image-20221112173534670.png) + ![image-20221112173534670](assets/image-20221112173534670.png) 生成的目录结构如下: - ![image-20221112174211802](assets\image-20221112174211802.png) + ![image-20221112174211802](assets/image-20221112174211802.png) Makefile就是我们要使用的构建规则文件。 @@ -291,7 +297,7 @@ VSCode常用快捷键包括: 为了提供完整的代码高亮支持,需要配置Makefile tools插件的make程序路径,`ctrl+,`打开设置,搜索make path找到设置并填写: -![image-20221113152513343](assets\image-20221113152513343.png) +![image-20221113152513343](assets/image-20221113152513343.png) > mingw32-make就是下面介绍的make工具(配合makefile替代手动调用gcc)。这里之所以只要输入mingw32-make而不用完整路径,是因为我们将mingw的bin文件夹加入环境变量了,因此系统会在PATH下自动寻找对应项 @@ -317,7 +323,7 @@ mingw32-make -j24 # -j参数表示参与编译的线程数,一般使用-j12 > > 我对make的编译命令进行了静默处理,只输出error和warning以及最后的生成文件信息。如果想要解除静默(就是下面所说的“你可以看到大致如下的输出”),需要修改Makefile。**本仓库下的makefile中已经用注释标明。** -![image-20221112191712534](assets\image-20221112191712534.png) +![image-20221112191712534](assets/image-20221112191712534.png) 就会开始编译了。你可以看到大致如下的输出: @@ -363,7 +369,7 @@ arm-none-eabi-objcopy -O binary -S build/basic_framework.elf build/basic_framewo 这样,你就可以点击VSCode工具栏上方的Terminal->Run task选择你刚刚配置的任务开始编译了。**更方便的方法是使用快捷键:`ctrl+shift+B`。** 之后要配置下载任务和调试任务等,也可以利用这种方法,新建一个xxx_task,实现一键下载、一键调试等。 -![image-20221112192133103](assets\image-20221112192133103.png) +![image-20221112192133103](assets/image-20221112192133103.png) > 还没配置任务的时候,需要在Terminal标签页中选择Configure Tasks... 创建一个新的.json文件。 > @@ -373,13 +379,13 @@ arm-none-eabi-objcopy -O binary -S build/basic_framework.elf build/basic_framewo Makefile的大部分内容在CubeMX初始化的时候就会帮你生成。如果新增了.c的源文件,你需要在`C_SOURCES`中新增: -![image-20221112192509718](assets\image-20221112192509718.png) +![image-20221112192509718](assets/image-20221112192509718.png) 换行需要在行尾加反斜杠\\ 如果新增了头文件,在`C_INCLUDES`中新增头文件所在的文件夹: -![image-20221112192610543](assets\image-20221112192610543.png) +![image-20221112192610543](assets/image-20221112192610543.png) 换行需要在行尾加反斜杠\\ @@ -423,7 +429,7 @@ VSCode `ctrl+,`进入设置,通过`搜索`找到cortex-debug插件的设置。 然后选择run and debug标签页,在选项中选择你配置好的选项,开始调试。**或者使用快捷键:`F5`。** -![image-20221112180103750](assets\image-20221112180103750.png) +![image-20221112180103750](assets/image-20221112180103750.png) 我们的仓库中默认提供了两种下载器的支持,dap-link(无线调试器属于这一种)和j-link(包括小的j-link OB和黑色大盒子jlink)。 @@ -431,13 +437,13 @@ VSCode `ctrl+,`进入设置,通过`搜索`找到cortex-debug插件的设置。 开始调试后,显示的界面如下: -![](assets\vscodedebug.png) +![](assets/vscodedebug.png) 1. 变量查看窗口,包括当前调用栈(当前作用域或代码块)内的局部变量、当前文件的静态变量和全局变量。register选项卡可以查看cpu内核的寄存器数值。 2. 变量watch窗口。右键单击要查看的变量,选择watch加入查看。 - ![image-20221113131044191](assets\image-20221113131044191.png) + ![image-20221113131044191](assets/image-20221113131044191.png) 还支持直接运行到指针所选处(Run to Cursor)以及直接跳转到指针处执行(Jump to Cursor)。添加行内断点(若一个表达式由多个表达式组成)也是很方便的功能,可以帮助进一步定位bug。 @@ -445,7 +451,7 @@ VSCode `ctrl+,`进入设置,通过`搜索`找到cortex-debug插件的设置。 VSCode提供的一个最大的便利就是,你可以将鼠标悬停在需要查看的变量上,**不需要添加到watch就能观察变量值。**如果是指针还可以自动解析,获取解引用后的值。结构体也支持直接展开。 - ![image-20221113133624273](assets\image-20221113133624273.png) + ![image-20221113133624273](assets/image-20221113133624273.png) 3. 调用栈。表明在进入当前代码块之前调用了哪些函数,称之为栈也是因为调用的顺序从下至上。当前函数结束之后栈指针会减小,控制权会返还给上一级的调用者。通过调用栈可以确认程序是**如何**(按怎样的顺序)运行到当前位置的。 @@ -545,7 +551,7 @@ VSCode `ctrl+,`进入设置,通过`搜索`找到cortex-debug插件的设置。 安装好两个软件之后,打开ozone后会显示一个new project wizard,如果没有打开,在工具栏的File-> New -> New project wizard。 -![image-20221113133904084](assets\image-20221113133904084.png) +![image-20221113133904084](assets/image-20221113133904084.png) 选择M4内核,为了能够查看外设寄存器的值还需要svd文件。所有mcu的svd都在图中的文件夹里提供,当然你也可以使用我们仓库根目录下的文件。 @@ -553,11 +559,11 @@ VSCode `ctrl+,`进入设置,通过`搜索`找到cortex-debug插件的设置。 接口选择swd,接口速度不需要太高,如果调试的时候需要观察大量的变量并且使用日志功能,可以调高这个值。如果连接了jlikn,上面的窗口中会显示。如果链接了dap-link,比如无线调试器,会出现Unknown CMSIS-dap。选择你要使用的调试器,然后继续。 -![image-20221113134252407](assets\image-20221113134252407.png) +![image-20221113134252407](assets/image-20221113134252407.png) 选择构建之后生成的.elf文件(在项目文件夹下的build中)。这是调试器专用的文件格式,对其内容感兴趣可以自行搜索细节。此外ozone还支持.bin .hex .axf(最后一个是amr-cc,也就是keil的工具链会生成的)等格式。 -![image-20221113134605331](assets\image-20221113134605331.png) +![image-20221113134605331](assets/image-20221113134605331.png) 这页不要动。如果希望保存jlink的调试日志,最后一个选项选择一个文件或者新建一个日志文件。 @@ -583,7 +589,7 @@ Project.SetOSPlugin(“plugin_name”) 下图的配置是笔者常用的layout。每个窗口是否显示、放在什么位置等都是可以自己定义的。通过工具栏的view选项卡可以自行选择需要展示的窗口。 -![](assets\ozone.png) +![](assets/ozone.png) 1. 调试控制:和vscode类似 @@ -642,7 +648,7 @@ Project.SetOSPlugin(“plugin_name”) - 如果当前文件没有你要的变量,你想查看项目中的其他文件夹,在view-> source files中可以打开该项目所有的源文件,双击可以打开源文件。 - ![image-20221113142448939](assets\image-20221113142448939.png) + ![image-20221113142448939](assets/image-20221113142448939.png) - **日志打印** diff --git a/application/gimbal/gimbal.c b/application/gimbal/gimbal.c index 395f31b..5798ce5 100644 --- a/application/gimbal/gimbal.c +++ b/application/gimbal/gimbal.c @@ -19,8 +19,6 @@ static Gimbal_Ctrl_Cmd_s gimbal_cmd_recv; // 来自cmd的控制信息 BMI088Instance* imu; void GimbalInit() { - - BMI088_Init_Config_s imu_config = { .spi_acc_config={ .GPIOx=CS1_ACCEL_GPIO_Port, @@ -56,9 +54,8 @@ void GimbalInit() .cali_mode=BMI088_CALIBRATE_ONLINE_MODE, .work_mode=BMI088_BLOCK_PERIODIC_MODE, }; - - imu=BMI088Register(&imu_config); -// gimba_IMU_data = INS_Init(); // IMU先初始化,获取姿态数据指针赋给yaw电机的其他数据来源 + // imu=BMI088Register(&imu_config); + gimba_IMU_data = INS_Init(); // IMU先初始化,获取姿态数据指针赋给yaw电机的其他数据来源 // YAW Motor_Init_Config_s yaw_config = { .can_init_config = { diff --git a/application/shoot/shoot.c b/application/shoot/shoot.c index bea0812..f2ff607 100644 --- a/application/shoot/shoot.c +++ b/application/shoot/shoot.c @@ -22,7 +22,7 @@ static float hibernate_time = 0, dead_time = 0; void ShootInit() { // 左摩擦轮 - Motor_Init_Config_s left_friction_config = { + Motor_Init_Config_s friction_config = { .can_init_config = { .can_handle = &hcan2, .tx_id = 6, @@ -50,34 +50,12 @@ void ShootInit() .reverse_flag = MOTOR_DIRECTION_NORMAL, }, .motor_type = M3508}; - // 右摩擦轮 - Motor_Init_Config_s right_friction_config = { - .can_init_config = { - .can_handle = &hcan2, - .tx_id = 5, - }, - .controller_param_init_config = { - .speed_PID = { - .Kp = 10, - .Ki = 0, - .Kd = 0, - .MaxOut = 2000, - }, - .current_PID = { - .Kp = 1, - .Ki = 0, - .Kd = 0, - .MaxOut = 2000, - }, - }, - .controller_setting_init_config = { - .angle_feedback_source = MOTOR_FEED, - .speed_feedback_source = MOTOR_FEED, - .outer_loop_type = SPEED_LOOP, - .close_loop_type = SPEED_LOOP | CURRENT_LOOP, - .reverse_flag = MOTOR_DIRECTION_NORMAL, - }, - .motor_type = M3508}; + friction_l = DJIMotorInit(&friction_config); + + friction_config.can_init_config.tx_id = 5; // 右摩擦轮,改txid和方向就行 + friction_config.controller_setting_init_config.reverse_flag = MOTOR_DIRECTION_NORMAL; + friction_r = DJIMotorInit(&friction_config); + // 拨盘电机 Motor_Init_Config_s loader_config = { .can_init_config = { @@ -99,7 +77,7 @@ void ShootInit() .MaxOut = 2000, }, .current_PID = { - .Kp = 10, + .Kp = 1, .Ki = 0, .Kd = 0, .MaxOut = 3000, @@ -113,9 +91,6 @@ void ShootInit() }, .motor_type = M2006 // 英雄使用m3508 }; - - friction_l = DJIMotorInit(&left_friction_config); - friction_r = DJIMotorInit(&right_friction_config); loader = DJIMotorInit(&loader_config); shoot_pub = PubRegister("shoot_feed", sizeof(Shoot_Upload_Data_s)); @@ -206,8 +181,8 @@ void ShootTask() DJIMotorSetRef(friction_r, 0); break; default: // 当前为了调试设定的默认值4000,因为还没有加入裁判系统无法读取弹速. - DJIMotorSetRef(friction_l, 4000); - DJIMotorSetRef(friction_r, 4000); + DJIMotorSetRef(friction_l, 1000); + DJIMotorSetRef(friction_r, 1000); break; } } diff --git a/bsp/iic/bsp_iic.md b/bsp/iic/bsp_iic.md index 2cff522..d989d02 100644 --- a/bsp/iic/bsp_iic.md +++ b/bsp/iic/bsp_iic.md @@ -1,5 +1,7 @@ # bsp iic +> 预计增加模拟iic + 关于I2C的序列传输,Restart condition和总线仲裁,请看: https://blog.csdn.net/NeoZng/article/details/128496694 diff --git a/bsp/pwm/bsp_pwm.md b/bsp/pwm/bsp_pwm.md index e69de29..e89fbe4 100644 --- a/bsp/pwm/bsp_pwm.md +++ b/bsp/pwm/bsp_pwm.md @@ -0,0 +1,5 @@ +# bsp pwm + +当前模块的实现有较大问题,是否应该允许多个使用相同tim的channel修改预分频计数器和重装载计数器? + +以及,是否给用户权限修改脉宽? diff --git a/bsp/spi/bsp_spi.md b/bsp/spi/bsp_spi.md index 2724b9e..8bf1e59 100644 --- a/bsp/spi/bsp_spi.md +++ b/bsp/spi/bsp_spi.md @@ -1,3 +1,9 @@ +# bsp spi + +> 预计增加模拟spi + +初始化传入参数中的GPIOx(GPIOA,GPIOB,...)和cs_pin(GPIO_PIN_1,GPIO_PIN_2, ...)都是HAL库内建的宏,在CubeMX初始化的时候若有给gpio分配标签则填入对应名字即可,否则填入原本的宏。 + 注意,如果你没有在CubeMX中为spi分配dma通道,请不要使用dma模式 (后续添加安全检查,通过判断hspi的dma handler是否为空来选择模式,如果为空,则自动将DMA转为IT模式以继续传输,并通过log warning 提醒用户) \ No newline at end of file diff --git a/bsp/usart/bsp_usart.md b/bsp/usart/bsp_usart.md index 5923336..e42607e 100644 --- a/bsp/usart/bsp_usart.md +++ b/bsp/usart/bsp_usart.md @@ -2,7 +2,7 @@

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