写在前面
- 这是一个新的系列, 我将会一步步将VESC电调固件代码框架梳理出来, 结合我过去和现在的理解来重新学习VESC的固件, 同时记录学习的过程.
- 目标是:
- 看懂电调从开机到关机/出错的代码执行流程, 尽量找出电调实际工作现象的代码依据;
- 看懂关键电机控制算法, FOC, BLDC, SVPWM, MTPA, HFI, SENSORLESS, 其中用到的观测器;
- 学习chibiOS操作系统的一些特性和使用技巧, 借此深入学习单片机操作系统;
- 会采取理论结合实践的学习方法.
固件文件列表
- 文件夹有这些
- 独立文件有这些
- 从上到下介绍
applications
重要的部分
- 红色框中是我认为重要的部分
app.c/.h
中定义了: 1) VESC的app设置的读写操作函数; 2) 一些预留接口的操作函数, 例如外部ADC
,UART
等; 3) 执行自定义功能相关的函数.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// Functions
const app_configuration* app_get_configuration(void);
void app_set_configuration(app_configuration *conf);
void app_disable_output(int time_ms);
bool app_is_output_disabled(void);
unsigned short app_calc_crc(app_configuration* conf);
// Standard apps
void app_adc_start(bool use_rx_tx);
void app_adc_stop(void);
void app_adc_configure(adc_config *conf);
//...
void app_uartcomm_initialize(void);
void app_uartcomm_start(UART_PORT port_number);
void app_uartcomm_stop(UART_PORT port_number);
void app_uartcomm_configure(uint32_t baudrate, bool permanent_enabled, UART_PORT port_number);
void app_uartcomm_send_packet(unsigned char *data, unsigned int len, UART_PORT port_number);
//...
// Custom apps
void app_custom_start(void);
void app_custom_stop(void);
void app_custom_configure(app_configuration *conf);appconf_default.h
中存放着默认app设置的宏定义, 恢复默认设置的时候, 各个参数从宏定义中获取. 所以有些app设置, 例如can的波特率, 是可以提前在这里改掉的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29//...
//...
//...
// The default app is UART in case the UART port is used for
// firmware updates.
理解但是没深究的部分
- 蓝色框内的文件我大概知道有什么用, 但是没有深究其原理.
app_adc.c/app_uartcomm.c
的内容主要是为外部adc输入和额外的uart口分配引脚和实现基本功能, 外部函数声明统一放在app.h
中, 如前文所示. 有一点需要注意的是, 外设驱动代码并不统一放在某个线程中运行, 而是每个外设都有独立的线程和线程函数, 随用随开.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46// adc
void app_adc_start(bool use_rx_tx) { //外部ADC的开启函数
// 配引脚
palSetPadMode(HW_ADC_EXT_GPIO, HW_ADC_EXT_PIN, PAL_MODE_INPUT_ANALOG);
palSetPadMode(HW_ADC_EXT2_GPIO, HW_ADC_EXT2_PIN, PAL_MODE_INPUT_ANALOG);
if (buttons_detached) {
use_rx_tx_as_buttons = false;
} else {
use_rx_tx_as_buttons = use_rx_tx;
}
stop_now = false;
chThdCreateStatic(adc_thread_wa, sizeof(adc_thread_wa), NORMALPRIO, adc_thread, NULL); // 开线程
}
// uart
void app_uartcomm_start(UART_PORT port_number) {
if(port_number >= UART_NUMBER){
return;
}
packet_init(write_functions[port_number], process_functions[port_number], &packet_state[port_number]);
if (!thread_is_running) {
chThdCreateStatic(packet_process_thread_wa, sizeof(packet_process_thread_wa),
NORMALPRIO, packet_process_thread, NULL); // 开线程
thread_is_running = true;
}
sdStart(serialPortDriverRx[port_number], &uart_cfg[port_number]);
sdStart(serialPortDriverTx[port_number], &uart_cfg[port_number]);
uart_is_running[port_number] = true;
palSetPadMode( // 配引脚
TxGpioPort[port_number],
TxGpioPin[port_number],
PAL_MODE_ALTERNATE(gpioAF[port_number]) |
PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUDR_PULLUP
);
palSetPadMode(
RxGpioPort[port_number],
RxGpioPin[port_number],
PAL_MODE_ALTERNATE(gpioAF[port_number]) |
PAL_STM32_OSPEED_HIGHEST | PAL_STM32_PUDR_PULLUP
);
pins_enabled[port_number] = true;
}app_custom_template.c
提供了一个模板, 包含一个空的线程和线程函数, 一个开启线程后在每个pwm周期结束后执行的空的函数, 还有演示如何在上位机上画图的代码(这部分没太注意看), 可以用来实现自定义功能.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48// Threads
static THD_FUNCTION(my_thread, arg);
static THD_WORKING_AREA(my_thread_wa, 1024);
//...
void app_custom_start(void) {
mc_interface_set_pwm_callback(pwm_callback); // 传入函数指针, 设置这个函数为pwm周期结束后的回调函数
stop_now = false;
chThdCreateStatic(my_thread_wa, sizeof(my_thread_wa),
NORMALPRIO, my_thread, NULL); // 开启线程
// Terminal commands for the VESC Tool terminal can be registered.
terminal_register_command_callback(
"custom_cmd",
"Print the number d",
"[d]",
terminal_test);
}
//...
static THD_FUNCTION(my_thread, arg) {
(void)arg;
chRegSetThreadName("App Custom");
is_running = true;
//...
for(;;) {
// Check if it is time to stop.
if (stop_now) {
is_running = false;
return;
}
//...
chThdSleepMilliseconds(10);
}
}
//...
static void pwm_callback(void) {
// 自定义内容
// Called for every control iteration in interrupt context.
}
//...
// Called when the custom application is stopped. Stop our threads
// and release callbacks.
void app_custom_stop(void) {
mc_interface_set_pwm_callback(0); // 清空函数指针
terminal_unregister_callback(terminal_test);
stop_now = true; // 即使线程在跑, 因为这个标志位, 实际上不会执行任何代码
while (is_running) {
chThdSleepMilliseconds(1);
}
}app_custom.c
用一系列宏定义指明了是否使用自定义app功能, 以及使用哪一个文件里的内容(但是我怀疑这个多选功能并没有实现)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
blackmagic
这个是真不懂, 说一下大概理解
- 关于
black magic
全称是black magic probe
, 这是一个开源的SWD调试器项目, 项目方有卖他们的板子配套固件使用, 也可以将固件中匹配的版本移植到芯片上直接跑, 使芯片具有通过SWD进行复杂调试和编程的功能.
出于节省资源的目的, 本杰明应该只移植了部分功能. 他还写了一些更高层的库, 调用bm的底层函数, 能够通过VESC tool 调试和编程电调. target
文件夹中的是bm固件的部分移植文件, 看不懂啦.bm_if.c/.h
中存放了本杰明自己写的高层函数, 包含bm的初始化, 与上位机的连接, flash擦除写入等功能.swdptap.c/.h
中存放的是比较底层的函数, 例如通过SWD发送和读取1个bit, 以及在此之上建立的发送和读取一个队列(seq).- 其它文件都是
bm_if.c/.h
调用的库函数, 内容并不多, 有些还被废弃了. - 想知道更多信息的可以去读一下
README.md
ChiBiOS_3.0.5
顾名思义, 这个是操作系统所在的文件夹
- 简单看一下, 蓝色框中的文件是外部文件, 不是
ChiBiOS
的一部分, 也就是说这一部分是可以替换的, 例如以后VESC主控芯片换成了G4系列, 这一部分的标准外设库就要改一改 - 绿色框中的是操作系统部分, 可以看到里面包含了一些比较熟悉的名字, hal(硬件抽象层), rt(实时), 其他的就不怎么认识了
- 想了解具体文件架构可以看红框的
readme.txt
, 里面有写, 应该是官方文档.
关于ChiBiOS, 以后有空会进行系统地学习
由于篇幅的关系, 接下来的内容, 如果以后还会重复出现, 则只作尽量简单的解释.
comm
- 这是VESC通过CAN和USB与外界或上位机通信的代码部分, 可以分为五个部分:
CAN
,USB
,commands
,log
,packet
.
CAN相关
消息的分类
- 两个文件,
comm_can.c/.h
, 总的功能分为CAN消息的读取
,发送
,编码
,解码
四部分. - 其中读取和发送底层直接通过
ChiBiOS
来实现. - 编码后的信息按用途可分为:
给主控发的
,给CAN上其它VESC发从而控制其它VESC的
, 以及给所有CAN设备发的电调状态反馈
. - 同理, 解码后的信息可以分为:
主控的命令
,其它VESC的命令
,其它VESC的状态反馈
.
CAN功能执行情况
读取
,解码
放在分别的两个线程中执行. 对于发送
环节, 一部分是紧接着解码的代码, 解码出需要发送的命令即发送; 另一部分在单独的状态反馈
的线程中发送; 还有极少一部分在代码各处被直接调用发送函数发送.编码
都在发送前完成.- 以上提到的三个线程(
读取
,解码
,状态反馈
)在main函数的初始化过程中即被开启.
CAN帧的id
- CAN帧采用扩展id,
extended id
, 其中包含的信息为VESC的编号(1~128)和这条帧的命令. 帧的内容是根据命令进行编码的, 在接收端需要有从eid中读取识别命令并采用正确解码方法的功能. - 从主控发出的控制VESC的CAN帧, 和从VESC发出控制VESC的CAN帧, 应该是一样的, 也就是说VESC不区分是谁发的CAN消息, 总线上有消息让它做什么它就做什么.
USB相关
- 四个文件,
comm_usb_serial.c/.h
和comm_usb.c/.h
, 其中comm_usb_serial.c/.h
被comm_usb.c/.h
调用, 直接被外界函数调用的只有comm_usb.c/.h
. - USB主要是用于与上位机通信, 主要编码和解码都在
commands.c/.h
中; 其发送和接收一份信息是以packet
为单位, 所以packet.c/.h
基本是为了USB而存在的. - USB的发送和接收底层直接通过
ChiBiOS
实现.
commands, packet相关
- 四个文件,
commands.c/.h
,packet.c/.h
. 这里的command
是一种框架, 依靠这个框架运行的部分除了USB以外还有潜在的nrf51
,BMS
,LISP
等. 这些通信方式发送信息的基本单位都是packet
, 所以packet.c/.h
提供一些通用的处理方法. BMS
是电池管理系统battery managing system
,LISP
像是一种指令集, 电调与上位机的互动大量依靠这个东西, 之后会研究.- 我很想研究清楚一个USB的
packet
是如何被产生和发送的, 但是这一坨文件写的太复杂了, 很多关键的函数为了通用性, 使用了大量函数指针, 按照我现有的功力还理不清楚. USB通信的实现方式不是重点, 只能就此作罢, 以后再说吧.
log相关
- 两个文件,
log.c/.h
. 功能主要是提供一套工具, 将任意采集到的数据转化为作图规则, 然后通过以上提到的任意一种通信方式发送出去. - 关键函数只提供转化方法, 似乎想要画图, 在这个函数之外还要计算好横轴和纵轴数据, 单次packet发送仅仅包含这一个点的数据.
- 整份代码中函数被调用的地方看起来只有与上位机互动的部分(用LISP实现).
- 由于packet可以通过CAN发送, 所以当上位机通过USB连接一个VESC时, 这个VESC可以通过CAN帮助其它VESC转发画图数据到上位机.// 真实性存疑, 先划掉- (板间通信这一块本杰明做的还是很周到的)
documentation
- 里面就一个CAN协议的说明文档, 想了解CAN是如何编码和解码的, 以及要写对应库函数的, 必看.
driver
- 一系列硬件外设的底层驱动. 其中
IIC
和SPI
均以软件方式实现,servo
指的是舵机(不知道为什么这东西需要无刷电机电调来控制). - 我觉得比较有意思的是
timer.c/.h
. 这个文件中定义的函数在全局都有调用, 核心函数的功能是获取当前系统tick, 或者理解为时间刻, 在之后可以用这个保存下来的时间刻与当前时间刻比较, 计算出流逝时间. 既然可以计算时间, 那么用处就很大了, 例如实现精准延时, 超时检测.
encoder
- 一系列编码器的底层驱动代码.
- 拿我熟悉的
as5047
来举例, 驱动代码包含了编码器初始化函数(也就是SPI
的初始化)和编码器角度读取函数(一次完整的SPI
通信). 每当我们在上位机中将编码器设置为as5047
时, 会跑一遍初始化函数, 之后角度读取函数会被放在一个线程中轮询执行.
hwconf
- 全是硬件配置, 分为自定义部分, 电机驱动芯片驱动部分, mcu配置部分, 硬件操作部分.
自定义部分
- 首先是自定义部分, 那一堆文件夹里装的是不同硬件方案的配置, 我用过的有
makerbase
,flipsky
, 以及我自己有一版和别人合作的硬件方案LIMITI
.example
是一个模板文件, 是根据自己的硬件方案自定义软件方案的开始.
- 以
makerbase
的为例, 一个硬件方案的固件版本的自定义内容通常包含两个core
, 一个全名.h
core
文件包含了独特的硬件信息:.h文件
中篇幅最大的部分是关键的GPIO口如何分配. 例如mcu有两组以上空闲的GPIO口可用作软件SPI
, 那么驱动芯片DRV8301
要用那一组?
这个问题有如上宏定义来说明, 电调启动后硬件进行初始化的时候才有依据..c文件
的主要是写硬件初始化的具体流程. 不同硬件方案上的硬件不只是型号有差别, 有些硬件是可以直接没有的, 例如非必要的nrf
或imu
. 此时在.c文件
中写清楚哪些硬件要初始化就很重要.
例如, 我有一版软件尝试禁用mcu
与drv8301
的SPI通信
, 以及去掉不需要的shutdown
口, 那么硬件初始化就这样写:
全名.h
文件的主要作用是在编译固件的时候, 标识不同版本的固件, 否则有这么多版固件, 不知道要编译那一版. 例如我想编译原版vesc6
的第5版, 则在终端中输入
make 60_mk5
想编译我自己固件的第二版, 则输入
make LIMITI_60_mk2
电机驱动芯片驱动部分
- 主要是绿色框内的一系列drv电机驱动芯片, 至于
si8900
, 我搜了一下没搜到相关信息, 不确定是不是电机驱动芯片.
MCU配置部分
- 大概是除了引脚之外所有关于
stm32f405
(MCU)的配置吧, 我看到了晶振频率的宏定义,ADC
和DMA
相关的宏定义, 至少这些挺重要的.
硬件操作部分
- 两个文件,
hw.c/.h
. 这个也是hardware, 点进去也有一堆硬件相关的宏定义, 我为什么要叫它硬件操作部分? 原因是:hw.c
的内容很少, 就是一个计算uuid
的函数, 一般来说不会去动它;hw.h
里绝大多数都是关于硬件的操作的宏定义, 而且很多都是空的. 我想本杰明在这里留空, 是他的原版固件不用这些宏定义, 但是如果你想用可以自己填上自己用.
- 例如这一堆
AUX
和PHASE_FILTER
是需要额外硬件支持才能使用的. 原版vesc6
没有AUX接口
, 直到mk6
之前没有PHASE_FILTER
, 但是如果你自己有合理的硬件方案, 那么可以使用他预留好的宏定义, 只需要填入相关操作(例如写GPIO)再编译即可.
其它
- 还剩一个
shutdown.c/.h
没讲, 这份文件像一个废案, 或者也是额外可以开启的功能, 总之原版没有使用. 文件中函数的功能简单概括, 是在你的电动滑板运行一段距离并停止后, 自动给当前配置和其它信息存个档(写flash
). 其实没啥用, 我又不是拿来做电动滑板的. (真的吗🤔)
imu
- 由于本人没用过在VESC上的
IMU
, 之前没有仔细研究过, 但是由于过往还是接触过IMU, 略微了解一点点IMU
的知识. 上图绿框是有意思的部分, 包括Madgwick的AHRS算法
, 以及封装好的IMU接口函数
; 蓝色框内是各种IMU的驱动库
, 目前VESC原生支持的IMU
有:mpu9x50
,icm20948
,bmi160(spi+iic)
,lsm6ds3
. - 图中的
Fusion文件夹
中的内容也属于AHRS算法
的一部分.
libcanard
UAVCAN
相关的, 从来没用过相关功能, 完全不懂
lispBM
- 存放
lispBM
相关的代码, 不是很懂还需学习
make
- 存放大多数
make
文件, 没具体学过看不懂捏
motor
- 最重要的一集, 分为以下几部分:
mc_interface.c/h
- 这两个文件里面全是接口函数, 包括控制电机速度, 位置, 占空比, 查询当前速度, 位置, 占空比这类基础操作函数, 也有在ADC采样周期结束后会执行的函数.
- 一般想要实现自定义操作, 调用其中的函数已经足够, 自定义的函数也可以写在里面.
foc部分
- 两组文件,
foc_math.c/h
和mcpwm_foc.c/h
foc_math
中主要是一系列PID
的运算,SVPWM
的运算,观测器
的更新等等, 倒是没有FOC主要的计算部分(park变换
,clark变换
).
mcpwm_foc
中有具体FOC运算
, 与读取ADC采样值的过程放在一个函数中, 紧接着采样结束后执行:- 这个
adc_int_handler
是最重要的一个函数, 包含采样以及FOC计算过程:
clark变换
park变换
和SVPWM
- 有一部分是foc控制的一些接口, 和
interface.c/h
中的基本一致
- 还有一部分是电机整定相关的几个函数, 其中关于有感位置整定(
encoder_detect
), 我已经详细分析过, 详见上篇文章: VESC学习之FOC有感整定代码. 其他的还需学习.
- 这个
非foc部分
- 剩下的有
mcpwm.c/h
, 推测这一组是用传统方法控制BLDC
的; 还有一组virtual_motor.c/h
, 不太清楚是干嘛的.
Project
Qt框架
的部分, 详情如下
tools
- 存放编译固件需要的环境和编译器
util
- 存放各种工具库函数的地方, 例如
buffer.c/h
, 包含各种将字节填充进数组的函数;
utils.c/h
, 包含各种数值映射和限幅等函数
- 这些工具函数在之后的
foc
,pid
,编码器读取
等环节被反复调用. 虽然很多工具函数很简单, 但是将一种操作统一为工具函数这种表达方式, 使代码可读性更高.
bms.c/h
- 电源管理系统(
battery managing system
)的代码, 仔细看了下感觉没啥用. 内容很少, 只有一个相关结构体的初始化, 设置数据和读取数据, 并没有直接执行某种功能的函数.
chconf.h
chibiOS
的一些配置参数, 比较常见的例如这个:
系统tick
, 计算时间可能会用到(是否也暗示了操作系统最高频率是10000Hz?)
conf_custom.c/h, conf_general.c/h
- 一些配置, 主要是涉及电调版本, 上位机UI显示, 还有一些主要用于与上位机交互的接口函数(详见下文
terminal.c/h
)
- 这些接口函数实现的功能在
interface.c/h
中全都有, 当然有些interface.c/h
的函数是靠调用这里对应的函数来实现. - VESC的版本号像是给电调固件开发者辨识版本用的. 例如上图中的
23
是小版本号, 对原版编译内容没有任何影响, 但是开发过程中, 开发人员可以用小版本号区分一些小改动, 编译出固件后, 也可以用小版本号区分具有微小差别的固件. conf_custom
是留给进一步定制用的, 原版对固件内容没有影响.
confgenerator.c/h
- *VESC中, 有两个重要的(同时也很巨大的)结构体, 一个用来存放所有的app设置(
appconf
), 一个用来存放所有的motor_control设置(mcconf
). * - 使用
VESC tool
控制VESC时, 经常要将两个conf
从上位机传到电调, 或者从电调读取到上位机, 数据传输的过程被VESC称为serialize
和deserialize
. serialize
本质上是将结构体的内容逐个塞入一个超大的buffer
, 然后发送出去, 而deserialize
是按照约定好的顺序将数据按顺序读取出来.- 如果
serialize
和deserialize
的规则对不上, 则会出现读取数据读的牛头不对马嘴😰, 例如将电机参数中的电感值读成了电阻值. - 有时用老的
VESC tool
读取烧录了新版本固件的电调的数据时, 会提示deserialize fail
, 就是因为新版本固件和老版本VESC tool
的规则对不上了,* 两边此时传递任何数据都可能是错的, 所以完全无法操作, 防止执行了包含错误参数的命令导致严重后果*. - 这一对文件, 包含了两个
conf
的serialize
和deserialize
规则.
datatype.h
- 祖宗级别的文件, 存放了绝大多数VESC在执行核心功能时会用到的结构体和枚举类型, 例如上文提到的
mcconf
和appconf
, 还有CAN通信的各种命令类型, 使用一个叫CAN_PACKET_ID
的枚举类型存放. - 我在自定义电机控制功能的时候, 修改过
mcconf
和CAN_PACKET_ID
. 修改mcconf
时加了一个结构体到最末尾, 结合上文的论述, 这种操作可能导致固件与上位机通信时deserialize
有问题, 毕竟单方面加入了内容而上位机不知道, 不过目前从没有出过问题(我推测在serialize
的时候代码不会把conf中多余的末尾加进去). 修改CAN_PACKET_ID
让我能利用现有CAN通信框架的同时, 保证代码的一致性和可读性. - 我还在里面写过自己的结构体, 并且将其成功存入内存池中, 开机即可从
flash
中读取上次的数据. 闲着没事可以在datatype.h
里找一个感兴趣的类型看看在哪里被调用过.
event.c/h
- 顾名思义, 事件管理的文件, 但是这里似乎与程序正常运行没有太大关系, 更像是为了调试方便, 记录各种重要事件并在终端打印出来.
- 可以看到, 原版固件中有如下事件类型:
firmware_metadata.h
- 意义不明捏~
flash_helper.c/.h
- 虽然能顾名思义认为是辅助读写mcu的
flash
的库函数, 真进去看也像那么回事情, 但是实际上这个库没有特别有用的接口, 这些接口在电调正常运行, 不与外界通信的时候基本不会被调用. - 之前我写过flash, 调用的是
conf_general
里定义的哪个. 包括原版固件储存和读取数据时, 最终还是使用driver
文件夹中eeprom.c/h
中的内容.
hal_conf
- 一堆宏定义, 看了一下有大多数没有被引用, 少部分被
chibiOS
的相关的文件引用, 想必对我这种简单开发者没有什么深究的必要啦~
irq_handlers.c / isr_vector_table.h
irq_handlers.c
将mcu产生的中断向量与要执行的代码相对应(这么表达足够清晰准确吗??). 文件内容明了, 某个中断向量(例如ADC1_2_3_IRQHandler
)对应一个需要在中断内执行的函数, 如下图:
isr_vector_table.h
是一张向量映射表, 经过我初步研究, 我推测这是将mcu裸机
的中断向量映射到chibiOS
的中断向量, 这样执行中断就不用回到裸机, 而是裸机中断->操作系统中断->操作系统中执行. 好处是避免潜在的代码执行优先级和资源调度的冲突.
ld_eeprom_emu.ld
- 代码在mcu内存空间中映射的规则, 编译时候会用到. 文件中提及的mcu型号是
STM32F407
, 虽然我用过的电调是STM32F405
, 但是内存空间的映射规则应该是一样的.
main.c/h
- 最最祖宗的一集, 程序入口
main
在这里, 还定义了一些初始化会用到的函数和线程.main
函数中有一大堆初始化, 操作系统初始化完成后, 就可以进行需要执行线程的初始化, 例如CAN收发
,FOC
,ADC采样
等.main
函数最后是死循环跑一个chThdSleepMilliseconds(10),
此时裸机不再执行任何步骤, 操作系统已经接管一切资源. main
函数具体内容之后单开文章来研究.
Makefile / package_firmware.py
Makefile
用于编译, 而package_firmware.py
似乎是用于生成编译路径的.- 过去我的自定义固件改了名字但是没有加入
package_firmware.py
的字典, 并且偶尔会出现改了代码但是编译出来的固件没有对应功能的情况, 或者直接编译时缺失关键文件, 会不会就是这里没有搞定?😵
两个cfg文件
- 没看懂是啥.
terminal.c/h
- 涉及到与上位机终端的信息交互和处理.
VESC tool
中有一个终端, 可以向VESC发送命令方便调试. 但是平常我们都用的图形化界面操作来代替手打指令, 所以不常用. - 即使是
VESC tool
中图形化界面的操作, 本质上还是发送这一组指令, 所以这对C文件的内容还是挺重要的. 文件包含接收终端指令处理, 向终端反馈错误信息, 自定义指令这些内容.
- 接收指令处理内容最多, 给所有类型的指令写了执行内容, 例子如下:
- 帮助指令
- 推测是下载某个电机参数到电调上时, 会发送的指令
- 推测是让电调控制电机旋转测磁链时, 会发送的指令
- 注意到终端命令的执行调用了很多
conf_general.c
里的函数. 同时大量调用了commands.c
中的commands_printf
函数.
- 帮助指令
- 反馈错误信息包含在接收指令处理中, 当执行一个指令产生
FAULT_CODE
后, 将FAULT_CODE
存在fault_vec
中, 时机合适时再打印到终端. (似乎这个数组并没有被太多调用, 多数报错是直接获取当前错误就上报了)
- 自定义指令是通过将某个指令与程序中某函数绑定在一起实现的, 要实现通过终端来操作, 可能需要一个函数名字符串向函数指针的映射(似乎在
lispBM
相关代码内见过).
timeout.c/h
- 主要内容是防止电机跑飞, 超时自动刹车, 以及喂看门狗.
- 我改过这方面的代码, 将看门狗设置为无条件喂(除非操作系统跑崩), 主要原因是之前改固件的时候, 改了一个不相关的部分, 但是老是报看门狗错误. 由于是很久之前改的了, 不太清楚原版是不是一直喂狗的, 我也懒得下载源码来看😄
timeout.c/h
中会跑一个线程, 随时检测last_update_time
距离现在是否超过了超时限制, 若超过则刹停电机. (线程最后还会喂狗)
- 固件的各个线程中, 有些体量比较大的函数在开头, 中间, 和结尾处可能会调用
timeout_reset()
这个函数, 重置last_update_time
. 若长时间不更新这个last_update_time
, 说明某个关键函数卡住了, 此时电机必须停止.
写在最后
- 终于写完了, 好累, 有一瞬间想骂人😡
- 前前后后耗时三个星期左右, 期间遇到困难一直拖延. 去上海度假回来终于振作起来, 一口气搞完了. 希望能对我接下来学习VESC有所帮助吧.
- 这项工程好像是蚍蜉撼树, 好像对我找工作没太大帮助, 好像是理想主义的一次冲锋. 我不清楚我做的是否”最优”和”正确”, 我已经疲于思考这些问题. 至少有开头有结尾, 写完后的内心是平静如水的, 这样就好, 无论做什么事情, 最终是追求精神的宁静和自洽.