更新了大量文档

This commit is contained in:
NeoZng 2023-06-03 21:58:21 +08:00
parent ff5028036a
commit 253f391cd5
17 changed files with 141 additions and 74 deletions

View File

@ -27,9 +27,9 @@
本框架主要代码分为**BSP、Module、APP**三层。三层的代码分别存放在同名的三个文件夹中这三个文件夹存放在根目录下。开发过程中主要编写APP层代码Module层与BSP层不建议修改。如需添加module如oled屏幕、其他传感器和外设等请按照规范编写并联系组长提交commit到dev分支或对应的功能名分支完善后合并至主分支。在配置git的时候将自己的`user.name`配置成英文缩写或易懂的nick name。
BSP层构建于ST的HAL硬件抽象层之上。为了方便使用已经将CMSIS相关、HAL库和实时系统、DSP支持等文件都放在`HAL_N_Middlewares`文件夹下包括Cube生成的外设初始化的Inc和Src文件夹
BSP层构建于ST的HAL硬件抽象层之上针对RoboMaster竞赛所用电控外设和模块的特点对其进行了进一步封装Module层是基于bsp的封装打造的各种模块旨在为app层提供**硬件无关的接口**,即应用层不应该出线任何与片上外设相关的代码
**main.c的位置在**`HAL_N_Middlewares/Src/main.c`
**main.c的位置在**`Src/main.c`
- **代码格式**
@ -119,23 +119,21 @@
**编写和使用指南**
- 补充与修改某款主控对应的BSP层应保持相同当认为该层可能缺少部分功能或有错误时请联系组长确认后解决并更新整个框架**请勿自行修改提交**。 请在你修改/增加的bsp_XXX.md中提供测试用例和使用示范以及任何其他需要注意的事项并在代码必要的地方添加注释。
- 代码移植BSP层也是在不同系列、型号的stm32间执行代码移植时主要需要关注的代码层。向功能更强系列移植一般只需要重配cube而向功能较少的系列移植还需要去掉其不支持的功能。如果仅是对同一型号的开发板进行HAL初始化配置的修改一般只需要给app层的应用重新分配外设和引脚或修改波特率和通信频率等。
- 代码移植BSP层也是在不同系列、型号的stm32间执行代码移植时主要需要关注的代码层。向功能更强系列移植一般只需要重配cube而向功能较少的系列移植还需要去掉其不支持的功能。如果仅是对同一型号的开发板进行CUBEMX初始化配置的修改一般只需要给app层的应用重新分配外设和引脚或修改波特率和通信频率等。
- 子文件与文件夹:
- bsp.c/h该层用于bsp基础功能初始化的文件其中.h被include至main.c中以实现整个代码层的初始化。include了该层所有模块的.h并调用各模块的初始化函数。目前需要初始化的bsp只有log和dwt**不同主频的MCU需要修改dwt初始化的参数**。**注意**有些外设如串口和CAN不需要在bsp.c中进行模块层的初始化他们会在module层生成实例即C语言中的结构体并注册到bsp层时自动进行初始化。以此达到提高运行速度避免未使用的模块被加载的问题。
- bsp.c/h该层用于bsp基础功能初始化的文件其中`bsp.h`被include至main.c中以实现必须的底层初始化目前需要初始化的bsp只有log和dwt**不同主频的MCU需要修改dwt初始化的参数**。**注意**有些外设如串口和CAN不需要在bsp.c中进行模块层的初始化他们会在module层生成实例即C语言中的结构体并注册到bsp层时自动进行初始化。以此达到提高运行速度避免未使用的模块被加载的问题。
- bsp_xxx.c/h每一个成对的.c/h对应一种外设当上面两个代码层需要使用某个外设时这里的文件就是对应的交互接口。
- 注册回调函数与接收:通信类外设模块有的定义了回调函数(函数指针类型)module层的模块需要自行处理接收回调函数在注册bsp的时候应传入对应参数格式的回调函数指针使得接收中断发生的时候bsp层可以自行找到对应的上层回调函数进行调用。这也是回调函数设计的初衷为底层代码调用上层代码提供接口当特定事件发生的时候完成触发自行搜索hook函数
## Module层
- 主要功能实现对设备的封装如将IMU、PC、电机等视为一个完整的功能模块让应用层不需要关心其底层的具体实现直接使用接口。
- 主要功能实现对设备的封装如将IMU、PC、电机等视为一个完整的功能模块让应用层app不需要关心其底层的具体实现,直接使用接口。
- 文件夹
- **注意module层没有也不需要进行统一初始化**。app层的应用会包含一些模块因此由app来调用各个模块的init或register函数只有当一个module被app实例化这个模块才会存在。
- **注意module层没有也不需要进行统一初始化**。app层的应用会包含一些模块因此由app来调用各个模块的init()或register()函数只有当一个module被app实例化这个模块才会存在。
> 命名为init()的初始化一般来说是开发板的独占资源即有且只有一个这样的模块无法拥有多个实例如板载陀螺仪、LED、按键等。命名为register()的模块则可以拥有多个,比如电机。
- monitor文件夹:实现看门狗功能。提供回调函数和count可选TODO
> ~~命名为init()的初始化一般来说是开发板的独占资源即有且只有一个这样的模块无法拥有多个实例如板载陀螺仪、LED、按键等。命名为register()的模块则可以拥有多个,比如电机。~~ legacy support为了保证代码风格统一所有接口统一命名为xxxRegister()。
- algorithm:该层软件库存放位置,这些功能与硬件无关,而是提供通用的数据结构和“算子”以供该层的其他部分调用,主要是算法、控制器、底盘和位姿解算等。
@ -147,7 +145,7 @@
- 结构体:
也就是所说的“实例”定义一个module结构体对于app层来说就是拥有某一个功能模块的实例比如一个特定的电机。在对电机进行操作的时候传入该结构体指针。
也就是所说的“实例”定义一个module结构体对于app层来说就是拥有某一个功能模块的实例比如一个特定的电机。在对电机进行操作的时候为实现面向对象的功能,需要在接口函数中传入该结构体指针。
- 函数:
@ -155,9 +153,9 @@
- 封装程度:
应尽可能使到上层使用时不考虑下层所需的操作。如在使用电机时这个电机的数据该和哪些电机的数据在一个数据包中发送can的过滤器设置均属于应该自动处理的功能接收类的driver应该封装到只有初始化用于初始化的`register`和发送控制命令`set_control`两个函数和一个实时更新的用于给app层提供该信息的数据结构体
app层使用时与底层实现无关。如在使用电机时这个电机的数据该和哪些电机的数据在一个数据包中发送can的过滤器设置均属于应该自动处理的功能通信类的模块应该封装到只有初始化、发送和读取。对于电机,则是用于初始化的`register`和发送控制命令`set_control`两个函数和一个实时更新的用于给app层提供该信息的数据结构体(电机反馈信息)。
Module层主要存放的是类型定义和实例指针数组在该层没有进行实例化定义或通过malloc分配空间若在APP层没有实例化则该模块的存在与否不会影响编译后的可执行文件只会占用初始化和代码区所需的少量内存。module只会保存每个实例对象的指针在没有初始化的时候仅仅占用一个指针数组的空间。因此基于本框架的其他工程没有必要删除APP层未使用的module文件。
Module层主要存放的是类型定义和实例指针数组在该层没有进行实例化定义或通过malloc分配空间若在APP层没有实例化则该模块的存在与否不会影响编译后的可执行文件只会占用.c文件中的static变量和代码区的少量内存有些module只会保存每个实例对象的指针在没有初始化的时候仅仅占用一个指针数组的空间。因此基于本框架的其他工程没有必要删除APP层未使用的module文件。
务必为模块添加说明文档和使用范例,以及其他需要注意的事项(如果有)。

View File

@ -160,7 +160,7 @@ void StartDefaultTask(void const *argument)
/* init code for USB_DEVICE */
MX_USB_DEVICE_Init();
/* USER CODE BEGIN StartDefaultTask */
vTaskDelete(NULL);
vTaskDelete(NULL); // 删除默认任务,防止占用CPU
/* USER CODE END StartDefaultTask */
}
@ -172,15 +172,15 @@ void StartINSTASK(void const *argument)
{
// 1kHz
INS_Task();
VisionSend(); // 解算完成后发送视觉数<EFBFBD>?
VisionSend(); // 解算完成后发送视觉数据,但是当前的实现不太优雅,后续若添加硬件触发需要重新考虑结构的组织
osDelay(1);
}
}
void StartMOTORTASK(void const *argument)
{
// 若使用HT电机则取消本行注释
HTMotorControlInit();
// 若使用HT电机则取消本行注释,该接口会为注册了的电机设备创建线程
// HTMotorControlInit();
while (1)
{
// 500Hz
@ -203,7 +203,7 @@ void StartROBOTTASK(void const *argument)
{
while (1)
{
// 200Hz
// 200Hz-500Hz,若有额外的控制任务如平衡步兵可能需要提升至1kHz
RobotTask();
osDelay(5);
}
@ -214,7 +214,8 @@ void StartUITASK(void const *argument)
My_UI_init();
while (1)
{
Referee_Interactive_task();
Referee_Interactive_task(); // 每次给裁判系统发送完一包数据后,挂起一次,防止卡在裁判系统发送中,详见Referee_Interactive_task函数的refereeSend();
osDelay(1); // 即使没有任何UI需要刷新,也挂起一次,防止卡在UITask中无法切换
}
}
/* USER CODE END Application */

View File

@ -6,7 +6,7 @@
**应用之间不应该有任何包含关系,它们必须是平行工作的。**而这通过pub-sub的机制实现。module层提供了`message_center`模块,支持发布订阅者的消息订阅机制。以传统的框架为例,负责整车控制的应用和其他应用(或任务)是从属的树状结构,或不同的任务和应用之间通过全局变量传递消息(**请不要使用全局变量!**),而此框架下的不同应用是并行的关系。
如果一个应用希望获取另一个应用的数据,那么他应该**订阅**由此此应用发布的话题(事件)。一个应用要把自己希望共享的数据,注册到消息中心,即**发布**。为了区别不同的消息来源(你希望订阅谁的消息?哪一个消息?),可以通过**话题名**进行订阅。也就是说,消息中心作为第三方,管理所有的消息发布者和订阅者,它像报刊亭一样对消息进行中转,使得不同的应用之间不需要包含彼此,更不用全局变量也能共享消息。
如果一个应用希望获取另一个应用的数据,那么他应该**订阅**由此此应用发布的话题。一个应用要把自己希望共享的数据,注册到消息中心,即**发布**。为了区别不同的消息来源(你希望订阅谁的消息?哪一个消息?),可以通过**话题名**进行订阅。也就是说,消息中心作为第三方,管理所有的消息发布者和订阅者,它像报刊亭一样对消息进行中转,使得不同的应用之间不需要包含彼此,更不用全局变量也能共享消息。
> 更多关于发布-订阅的实现,请参考`modules/message_center`下的文档。
@ -15,7 +15,7 @@
这是机器人的参数配置文件,必须要针对每个机器人进行修改。包括机器人的尺寸参数和性能参数等。你还需要在这里设定软硬件配置:云台板/底盘板/单板等。这里定义的宏会作为条件编译的决断。
app层共用的状态变量和结构体等也应该定义在这里例如用于应用之间通信的数据。记得通信变量要用:
app层共用的状态变量和结构体等也应该定义在这里例如用于应用之间通信的数据。记得用于通信变量要用:
```c
#pragma pack(1)
typedef struct
@ -25,7 +25,9 @@ typedef struct
} your_struct;
#pragma pack()
```
包裹起来取消字节对齐以防止出现访问8byte地址而出现错误。
包裹起来取消字节对齐以防止出现访问8-bit地址而出现错误。
如果你需要其他的通信数据类型或修改模块间通信数据的格式,直接在此处更改即可。
## robot_cmd
@ -35,6 +37,8 @@ typedef struct
robot_cmd工作起来就像一个遥控数据的兼容层不论数据的来源是视觉上位机/遥控器/键鼠/图传通信链路/ps手柄最后都会被转化成真实参考输入提供给其他的app。它的任务是将其他来源的数据映射到控制输入上。
## gimbal
以步兵为例云台应用应当包含两个电机分别用于驱动yaw和pitch轴除非你是一个三轴的云台还有一个imu开发板一般放在云台上。gimbal模块会接收robot_cmd发来的控制信息云台的角度、转速等并通过电机提供的接口完成电机的参考值设定。gimbal还要把imu的数据反馈给cmd用于和视觉的通信以及云台状态的判断。
@ -63,3 +67,4 @@ robot_cmd工作起来就像一个遥控数据的兼容层不论数据的来
此框架对单开发板/双开发板/多开发板的情况都提供了支持多板一般只在工程机器人上出现需要自己在robot_cmd和robot_def增加相应的条件编译选项robot.c中也不要忘记增加初始化和任务运行函数目前通过条件编译实现了对单双板的切换。使用双板时主控板在云台上连接遥控器和上位机副板在底盘上负责底盘的运动控制和与裁判系统的通信。
当然,你可以为每台不同的机器人进行特化,因为本框架是针对步兵/英雄定制的。

View File

@ -10,7 +10,7 @@
在main函数中包含`robot.h`头文件,这是对整车的抽象。将`INStask``motortask``ledtask``monitortask`这四个task加入`freertos.c`中,创建对应的任务,设置合适的任务运行间隔;然后将`robottask`放入freertos.c中同样以一定的频率运行。 在初始化实时系统之前,在`main()`中调用`RobotInit()`进行整车的初始化。
**关于运行的任务**INStask的运行频率必须为1kHzmotortask推荐的运行频率为200Hz\~500Hz详情见module/motor/motor_task.c在MotorTask内部对于高实时性要求的电机可以提升到1kHz不过要注意CAN总线的负载。ledtask的运行频率推荐为1kHzmonitortask的运行频率为1kHzrobottask的运行频率推荐为150Hz以上应当高于视觉发送的频率若后续使用插帧同样应该保证不低过motortask太多。
**关于运行的任务**INStask的运行频率必须为1kHzmotortask推荐的运行频率为200Hz\~1000Hz详情见module/motor/motor_task.c在MotorTask内部对于高实时性要求的电机可以提升到1kHz不过要注意CAN总线的负载。monitortask的运行频率为100Hzrobottask的运行频率推荐为150Hz以上应当高于视觉发送的频率若后续使用插帧同样应该保证不低过motortask太多。
若使用双板,则在`robot_def.h`中给对应的开发板设定宏定义,如底盘板使用`#define CHASSIS_BOARD`,云台板使用`#define GIMBAL_BOARD`;单个开发板控制整车,则定义`#define ONE_BOARD`。在每个应用中都已经使用编译预处理指令完成条件编译会自动根据设定的宏切换功能。使用双板的时候目前板间通信通过CAN完成因此两个开发板会挂载在一条总线上在两个开发板对这条总线的其他使用CAN的设备进行配置时注意**不要发生ID冲突**,还要注意**防止负载过大**。
@ -24,7 +24,7 @@ Robot.c是整个机器人的抽象其下有4个应用robot_cmdgimbal
为了进一步解耦应用之间的关系,这里并没有采用层级结构(或设计模式中所谓的**工厂模式**即robot_cmd包含其他三个模块而采用了应用并列的**发布-订阅**机制四个应用之间没有任何相互包含关系他们之间的通信通过module层提供的`message_center`实现。每个应用会通过该模块向一些话题事件发布一些消息同时从一些话题订阅消息。如robot_cmd应用会发布其他三个模块的控制信息同时订阅其他三个模块的反馈信息。其他三个模块会订阅robot_cmd发布的控制信息同时发布反馈给robot_cmd的信息他们不需要知道彼此的存在只是从`message_center`处获取其他应用发布的消息或向自己发布的话题推送消息。
application在初始化module的时候初始化参数会包含部分bsp的内容但仅仅是外设和引脚的选择以及id设置用于通信的外设需要id设置。实际上当前框架的app层和cubemx初始化部分耦合在配置的时候就必须确定每个外设的作用和归属权一旦cubemx完成设置app层必须按照对应参数设置引脚和并分配module的外设.后续考虑将cubemx和bsp耦合去除顶层代码和底层的关系
application在初始化module的时候初始化参数会包含部分bsp的内容但仅仅是外设和引脚的选择以及id设置用于通信的外设需要id设置。实际上当前框架的app层和cubemx初始化部分耦合在配置的时候就必须确定每个外设的作用和归属权一旦cubemx完成设置app层必须按照对应参数设置引脚和并分配module的外设后续考虑将cubemx和bsp耦合去除顶层代码和底层的关系

View File

@ -2,7 +2,9 @@
<p align='right'>neozng1@hnu.edu.cn</p>
# 请注意使用CAN设备的时候务必保证总线只接入了2个终端电阻开发板一般都有一个6020电机和HT、LK电机也都有终端电阻注意把多于2个的全部断开通过拨码
# 请注意使用CAN设备的时候务必保证总线只接入了2个终端电阻开发板一般都有一个6020电机、c620/c610电调、LK电机也都有终端电阻注意把多于2个的全部断开通过拨码
## 使用说明
@ -23,16 +25,18 @@
/* can instance typedef, every module registered to CAN should have this variable */
typedef struct _
{
CAN_HandleTypeDef* can_handle;
CAN_TxHeaderTypeDef txconf;
uint32_t tx_id;
uint32_t tx_mailbox;
uint8_t tx_buff[8];
uint8_t rx_buff[8];
uint32_t rx_id;
void (*can_module_callback)(struct _*);
void* id;
} can_instance;
CAN_HandleTypeDef *can_handle; // can句柄
CAN_TxHeaderTypeDef txconf; // CAN报文发送配置
uint32_t tx_id; // 发送id
uint32_t tx_mailbox; // CAN消息填入的邮箱号
uint8_t tx_buff[8]; // 发送缓存,发送消息长度可以通过CANSetDLC()设定,最大为8
uint8_t rx_buff[8]; // 接收缓存,最大消息长度为8
uint32_t rx_id; // 接收id
uint8_t rx_len; // 接收长度,可能为0-8
// 接收的回调函数,用于解析接收到的数据
void (*can_module_callback)(struct _ *); // callback needs an instance to tell among registered ones
void *id; // 使用can外设的模块指针(即id指向的模块拥有此can实例,是父子关系)
} CANInstance;
typedef struct
{
@ -62,6 +66,7 @@ typedef void (*can_callback)(can_instance*);
```c
void CANRegister(can_instance* instance, can_instance_config config);
void CANSetDLC(CANInstance *_instance, uint8_t length); // 设置发送帧的数据长度
uint8_t CANTransmit(can_instance* _instance, uint8_t timeout);
```

View File

@ -1,3 +1,30 @@
# bsp_dwt
DWT是stm32内部的一个"隐藏资源",他的用途是给下载器提供准确的定时,从而为调试信息加上时间戳.并在固定的时间间隔将调试数据发送到你的xxlink上.
## 常用功能
### 计算两次进入同一个函数的时间间隔
```c
static uint32_t cnt;
float deltaT;
deltaT=DWT_GetDeltaT(&cnt);
```
### 计算执行某部分代码的耗时
```c
float start,end;
start=DWT_DetTimeline_ms();
// some proc to go...
for(uint8_t i=0;i<10;i++)
foo();
end = DWT_DetTimeline_ms()-start;
```

View File

@ -11,6 +11,28 @@
bsp_log是基于segger RTT实现的日志打印模块。
推荐使用`bsp_log.h`中提供了三级日志:
```c
#define LOGINFO(format,...)
#define LOGWARNING(format,...)
#define LOGERROR(format,...)
```
分别用于输出不同等级的日志。
**若想启用RTT必须通过`launch.json`的`debug-jlink`启动调试(不论使用什么调试器)。**
注意若你使用的是cmsis-dap和daplink**请在调试任务启动之后再打开`log`任务。**(均在项目文件夹下的.vsocde/task.json中有注释自行查看
在ozone中查看log输出直接打开console调试任务台和terminal调试中断便可看到调试输出。
> 由于ozone版本的原因可能出现日志不换行或没有颜色。
## 自定义输出
你也可以自定义输出格式详见Segger RTT的文档。
```c
int printf_log(const char *fmt, ...);
void Float2Str(char *str, float va);
@ -33,3 +55,6 @@ printf_log("Motor %d met some problem, error code %d!\n",3,1);
```
或直接通过`%f`格式符直接使用`printf_log()`发送日志,可以设置小数点位数以降低带宽开销。

View File

@ -1,3 +1,5 @@
# bsp usb
简单写点,有待优化.
简单写点,有待优化. 目前仅支持虚拟串口通信,暂未开发其他内容.
注意为了增加发送完成和接收完成回调对Inc/usbd_xxxx.h四个文件做了修改对Src/usbxxx.c也进行了修改。

View File

@ -0,0 +1,3 @@
# tfminiplus
北醒激光单点激光雷达模块的简单实现。目前使用iic阻塞通信耗时和速度都太慢需修改为中断读取bsp_iic已经提供相应的接口。

View File

@ -25,6 +25,7 @@ CAN comm是用于CAN多机通信的模块。你不需要关心实现的协议
CANCommInstance *CANCommInit(CANComm_Init_Config_s* comm_config);
void CANCommSend(CANCommInstance *instance, uint8_t *data);
void *CANCommGet(CANCommInstance *instance);
uint8_t CANCommIsOnline(CANCommInstance *instance);
```
第一个函数将会初始化一个CANComm实例返回其指针。使用CANComm进行通信的应用应该保存返回的指针。初始化需要传入一个初始化结构体。请在应用初始化的时候调用该函数。推荐的结构体配置方式如下
@ -139,4 +140,3 @@ CAN comm的通信协议如下
流程图如下:![未命名文件](../../assets/CANcomm.png)

View File

@ -42,6 +42,7 @@ void DaemonTask()
else if (dins->callback) // 等于零说明超时了,调用回调函数(如果有的话)
{
dins->callback(dins->owner_id); // module内可以将owner_id强制类型转换成自身类型从而调用特定module的offline callback
// @todo 为蜂鸣器/led等增加离线报警的功能,非常关键!
}
}
}

View File

@ -5,3 +5,8 @@
注意LK电机在使用多电机发送的时候只支持一条总线上至多4个电机多电机模式下LK仅支持发送id 0x280为接收ID为0x140+id.
要设置为多电机模式请通过串口连接电机并使用该文件夹下的LK motor tool.exe进行配置。
## LK的其他电机
若使用其他LK电机唯一需要修改的是确定编码器的精度即LKMotorDecode()部分的速度反馈和编码器反馈解析。

View File

@ -4,10 +4,8 @@
请使用字库软件制作自己的图标和不同大小的ascii码.
> 后续尝试移植一些图形库使得功能更加丰富
> oled主要作调试和log/错误显示等使用
> 可以提供给视觉和机械的同学调试接口,方便他们通过显示屏进行简单的设置
*可以引入RoboMaster oled,或额外增加一个编码器用于控制oled界面并设定一些功能.*

View File

@ -1,6 +1,6 @@
# 标准命令
这是一个体力活也是一个艺术品。请把不同的控制命令module进行封装以转化成标准的消息类型包括云台角度速度底盘速度发射频率是否发射等信息供RobotCMD应用或其他应用使用。
这是一个体力活也是一个艺术品。请把不同的控制命令module进行封装以转化成标准的消息类型包括云台角度速度底盘速度发射频率是否发射等信息供RobotCMD应用或其他应用使用。通过这种方式开发者可以更专注于cmd命令的编写而不需要为每台机器人/不同的控制器编写命令转换。
是否将下面的模块都放到standard_cmd文件夹下似乎没有必要。
@ -13,5 +13,4 @@
## key and mouse
## 图传链路
这似乎和键鼠是同一套,不过走的是串口,可能需要额外添加支持模块
图传链路的数据解析似乎和键鼠是同一套协议,不过走的是串口,可能需要额外添加支持模块

View File

@ -1,7 +1,9 @@
# univsersal communication
@todo
unicomm旨在为通信提供一套标准的协议接口屏蔽底层的硬件差异使得上层应用可以定制通信协议包括包长度/可变帧长/帧头尾/校验方式等。
不论底层具体使用的是什么硬件接口,硬件的每一帧传输完将数据放在缓冲区里之后,就没有任何区别了。 此模块实际上就是对缓冲区的rawdata进行操作包括查找帧头计算包长度校验错误等。
不论底层具体使用的是什么硬件接口,实际上每一帧传输完并把数据放在缓冲区之后,就没有任何区别了。 此模块实际上就是对缓冲区的rawdata进行操作包括查找帧头计算包长度校验错误等。
完成之后可以将module/can_comm移除把原使用了cancomm的应用迁移到此模块。
完成之后可以将module/can_comm、视觉的通信协议seasky_protocol和master_process等移除把原使用了cancomm的应用迁移到此模块。

3
modules/vofa/vofa.md Normal file
View File

@ -0,0 +1,3 @@
# vofa
**除非迫不得已否则强烈不推荐使用vofa进行调试。应通过bsp_log输出日志或使用ozone可视化。**

View File

@ -1,6 +1,6 @@
# how to locate bug in your code
[TOC]
[toc]
只讨论运行中的bug指程序的运行结果不符合你的期望和异常直接异常终止。编译期出现的warning和error不在此范畴他们都可以通过直接阅读报错信息解决。
@ -85,8 +85,6 @@ long long的范围比float小。无符号和有符号数直接转换可能变成
**宏只在当前文件生效**,如果宏放在.c那么对其他的文件是不可见的这也一般称作私有宏。
## 典型debug案例一
这是一个结合了软件和硬件且有多模块耦合的异常。该bug发生在调试平衡步兵的底盘过程当中。
@ -137,8 +135,6 @@ void MotorControlTask()
这是motortask的内容此任务将以500hz的频率运行。在发生bug时我们将4个HT04电机和2个LK MF9025电机全部连接到CAN1上。注意HT04不支持多电机指令因此占用的带宽较大。在 `LKMotorControl()`完成参考值计算和CAN发送之后立刻会调用 `HTMotorControl()`后者需要连续发送4条报文。而HT和LK电机都会在接收到控制指令之后发送反馈信息报文。由于HT电机的控制在LK电机控制之后立刻执行导致总线被占据LK电机发送的反馈数据仲裁失败无法获得总线占有权使得主机收不到反馈数据。
### bug的发现和定位的尝试
程序的大体情况如下当时进行轮足式倒立摆机器人的测试启用了balance.c在其中注册了4个HT04电机can1和2个LK9025电机can2。控制报文的发送频率均为500Hz。
@ -159,8 +155,6 @@ void MotorControlTask()
均衡总线负载,调节任务运行时间。
# 典型debug案例二
这仍然是一个CAN总线引发的bug。使用的电机均为DJI电机。当多个电机接入时会产生反馈值跳变的情况。起初认为**总线负载过高**控制频率为500Hz反馈频率均为1kHz计算之后得出CAN的负载率接近90%),但将电机减少为一半甚至更少时仍然出现此问题。**单独使用CAN1且仅挂载一个电机则问题消失**同时使用CAN1和CAN2不论单个总线挂载几个电机则问题再次出现。
@ -170,4 +164,3 @@ void MotorControlTask()
将优先级统一设为5编译之后重新运行反馈值正常。
> “同时使用CAN1和CAN2不论几个电机则问题再次出现。” 导致此问题的原因是初始化CAN时按照rxid分配FIFO因此注册的电机会被交替分配到不同的FIFO故不论注册了几个电机只要多于2、注册到哪条总线都会出现FIFO1中断被FIFO0打断的情况。