The Art of Protocol | 通讯框架,含 IAP、曲线绘制等,步进电机示范工程
本帖最后由 dukelec 于 2020-10-26 12:39 编辑本框架代码量很少,非常简单。
核心是:程序中放一个结构体,前面的部分是可以保存到 flash/eeprom 的内容,上电从 flash/eeprom 中恢复,后面是运行中的数据。
通过此框架,用户可以直接修改和读取这个结构体,从而达到通讯的目的。具体说明如下文。
除了串口、RS485 可以使用此框架,其它通讯也可以,譬如 BLE 通讯,过一段时间我会开源一份使用此框架的 nRF52 的 Bootloader 工程(直接使用 CDNET 包格式,不使用 CDBUS 帧打包即可)。
USB CDC 版本的 IAP 升级则可以参考这个工程:https://github.com/dukelec/cdbus_bridge
CD-MDRV-STEP 简介
=======================================
下载项目:
git clone --recurse-submodules https://github.com/dukelec/stepper_motor_controller.git
## 协议
MDRV 是一个开源步进电机控制器,它使用 RS485 接口,默认波特率为 115200 bps, 最高 > 10 Mbps,默认地址为 0xfe.
最底层协议为 CDBUS,其帧格式为:
src, dst, len, , crc_l, crc_h
由 3 字节头、数据 和 最后 2 字节的 CRC 结尾(算法同 ModBus RTU 的 CRC)。
CDBUS 协议具体参见: https://github.com/dukelec/cdbus_ip
Payload 部分为 CDNET 协议,同时支持 CDNET L0 和 L1 两个版本。
CDNET 协议具体参见: https://github.com/dukelec/cdnet
CDNET 和 TCP/IP 中的 UDP 的概念比较类似,主要参考 UDP 的端口号的概念。
譬如最简单的 CDNET L0 协议,从主机默认端口发送一个字节数据 0x00 到 MDRV 的 1 号端口的格式:
01 00
第一个字节是目标端口号,然后跟数据即可。
以上示范,完整的 CDBUS 协议帧为(主机地址默认为 0,MDRV 为 0xfe):
00 fe 0201 00crc_l crc_h
以上示范,端口 1 是设备信息相关的,端口号可以看做 主命令号,首字节数据约定为 子命令号,
子命令号 0x00 是读设备信息。
MDRV 一共有 4 个端口接收指令,分别是:
### 端口 1: 设备信息
唯一一个子命令号 0 是用来查询信息。格式正是上面的例子。
返回 0x80 + 设备信息字符串,譬如 "M: mdrv-step; S: 23ff7660d405535353733034; SW: v1.1".
### 端口 5: 参数表读写
参数表在代码中是 csa_t 类型的全局结构体,csa 为 Config Status Area 的缩写,它的前半部分可以写入 Flash 中,以保存一些用户设置,
上电时从 flash 中恢复 csa 的前半部分数据。而后部分数据为运行中的一些变量,通过此接口把这些变量暴露给用户,让用户直接读写可以大大简化代码。
不过,我们在读写前后,可以通过预先定义的 hook 钩子函数,对写入数据进行安全检查,或是定义写入后需要执行的动作。
而对于读取命令,可以提前为用户准备其所需要的数据格式,譬如格式转换。
另外,为了方便用户整体同步 csa 信息,我们定义了 csa 的哪些区域可写,不可写的区域用户不用小心避开,写入的数据直接会被忽略。
譬如下面这个例子(非本工程代碼),我们可以直接把 pid 对象放在 csa 表中,pid 对象的后面是一些内部变量,不应该被用户修改,
有了可写区域限制,用户可以非常方便的整块写入数据,不用担心写到不该写的数据。
读写参数表的子命令为:
read:0x00, offset_16, len_8 | return
write: 0x20, offset_16 + | return on success
譬如设置电机锁舵并进入位置模式。电机状态 `state` 值为 0 不锁舵,为 1 锁舵。
`state` 本身的地址是 `0x0100`,长度 1 个字节,那么上电后需要锁舵,发送以下数据到端口 5 即可:
2000 0101
`20` 是子命令 `write`,`00 01` 是地址 0x0100 的小端格式(除非特别说明,否则都是用小端),最后一个 `01` 是写入数值。
具体参数表可以参考 `tools` 目录下的 `mdrv_reg.py` 这个文件。
修改 `csa_t` 的定义,可能会影响到很多寄存器的地址,如果每次都是手工计算各寄存器的地址,很麻烦且容易出错,所以,我们通过 `csa_list_show` 这个函数,
上电时自动打印出所有寄存器的地址、类型,以及说明信息,可以直接拷贝到文档或是 Python 源码中。
每次修改 `csa_t` 的定义后,请同时修改其中的 `conf_ver` 版本号。
### 端口 6: 快速读写
位置模式常用的参数表项有:
tc_pos, type: int32_t
tc_speed, type: uint32_t
tc_accel, type: uint32_t
我们可以通过端口 5 来写这些数据,但是,有的时候我们还想去写非连续的其它 表项,这样就要拆分多条命令,效率低,且容易导致数据不同步。
而且,我们还想设备同时回覆电机的错误码和一些运行数据,但不同模式下我们关心的运行数据不尽相同,如果写死会比较不方便。
参数表项内有 快速读写 通道的配置:(qxchg 是 quick exchange 的缩写)
regr_t qxchg_set;
regr_t qxchg_ret;
regr_t qxchg_ro;
以上数组,首个 size 值为 0 的元素表示它和后序的未使用,元素的定义:
typedef struct {
uint16_t offset;
uint16_t size;
} regr_t; // reg range
`qxchg_set` 和 `qxchg_ret` 是 快速读写 的 子命令 `20` 所用,
预先设置好写哪些数据,以及返回哪些数据,然后发送 `20` + 需要写的数据就可以了,
会返回 `80` + 返回的数据。
`qxchg_ro` 是指定只读的子命令 `00` 返回哪些数据。
默认的设定:
.qxchg_set = {
{ .offset = offsetof(csa_t, tc_pos), .size = 4 * 3 }
},
.qxchg_ret = {
{ .offset = offsetof(csa_t, cur_pos), .size = 4 * 2 }
},
`qxchg_set` 只用了一个元素,指向 `tc_pos`, `tc_speed` 和 `tc_accel` 这 3 个表项所在的区域,
如果需要修改目标位置参数 `tc_pos` 为 0,向 端口 6 写数据 `2000 00 00 00` 即可(`20` 是子命令号)。
如果需要同时修改目标位置和目标速度,譬如位置改为 0x00001000,速度改为 0x00000500,则写入 `2000 10 00 0000 05 00 00`.
### 端口 8: Flash 读写
可以通过此接口实现 IAP 升级。
子命令为:
erase: 0x2f, addr_32, len_32| return on success
write: 0x20, addr_32 + | return on success
read: 0x00, addr_32, len_8 | return
cal crc: 0x10, addr_32, len_32| return # modbus crc
先要把需要写的区域擦除,然后写入数据,最后调用 crc 计算所写数据是否正确即可。
也可以读回所有数据来判断,速度会稍慢一点。
Bootloader 上电后,先使用默认波特率 115200,如果收到主机设置 `keep_in_bl` 的命令就保持此波特率,如果一秒钟内没有收到该命令,则切换到 Flash 中保存的波特率继续等待命令,如果再过一秒依然没收到命令,则跳转执行 APP 固件。Bootloader 和 APP 共享 csa 配置的开头部分,其中包含了用户设置的波特率。
如果不知道 MDRV 当前的 ID 号,可以发送命令到广播地址 0xff,但需要确保总线没有其它设备接入。
当前 MCU Flash 总共 64K, 每页大小为 1K, 前 26K 存放 Bootloader,最后 1K 存放 csa 参数表,其余为 APP.
使用工具进行 IAP 操作的截图:
## 裸数据调试
和 快速读写 通道的 `qxchg_XXX` 数组一样,裸数据用同样的方式定义需要上报的数据的组成,可以自由配置想要观察的变量。
和 `qxchg_XXX` 不同的是,一个数据包会存放很多组数据,等数据包满到一定程度再发送给主机。
如果是固定周期的情况,譬如 FOC 电机,可以在电流环中为一个变量(譬如 `loop_cnt`)每次加 1,然后只需要把 `loop_cnt` 写在每个数据包的最前面即可。
但我们这个 stepper motor 控制,周期不固定,所以要把时间信息和其它被观察的变量,每次都一起记录。
使用裸数据调试时,建议把设备的波特率设置高一些。
如果数据量很大,MDRV 会把连续的信息保存在 buffer 中,buffer 满之后,会等待 buffer 完全清空再继续,能最大程度保证数据的连续性,避免细节丢失。
下一节将说明如何显示这些数据。
## MDRV 测试工具
除了使用 `cdbus_tools` 的 `cdbus_terminal.py` 发送 cdbus 帧数据,或者 `cdnet_terminal.py` 发送更上层的 cdnet 数据,
更方便的是使用 MDRV 的配套工具,除了基本控制以外,还可以抓取电机运行的数据,显示曲线图。
譬如运行工具后(如果电机已经上电,会打印电机的版本信息):
输入 m 跟状态 就可以进入对应状态,譬如 m1 是:锁舵并进入位置模式,m0 是停机。
d1 是开启裸数据调试功能,d0 是关闭。
进入位置模式后,输入 p 跟位置,可以指定运动到目标位置。
灰色信息是电机 printf 的调试打印,自动上报到电脑。这样可以很方便的远距离调试。
譬如正在运动中的机械手臂,用传统调试方式非常危险。还有些设备想调试需要拆开外壳,也非常的不方便。
(除了灰色,还可以增加更鲜艳的其它颜色,可以避免自己忽略重要的调试信息。)
用鼠标右键拖动可以很方便的放大细节:
这个曲线工具可以同时显示多个图表,譬如 FOC 的电流环、速度环、位置环、位置规划,而且老的版本还支持同时显示多个电机的曲线(为了代码简洁,新版本没有添加)。
## TODO
可以增加一个端口号,从广播包中取出自己节点的数据,然后进行处理。
另外是,实现一个推后执行命令的功能,方便主机先同步数据,再通知所有节点同步执行命令。
这两个功能可以放在一个端口号中,用不同的子命令来分别实现。
这两个功能实现很简单,代码量也非常少。
虽然看不懂,但还是要支持大神 本帖最后由 dukelec 于 2020-10-26 12:20 编辑
aleyn 发表于 2020-10-26 11:57
虽然看不懂,但还是要支持大神
可以看下源碼,代碼很少,先看下 bootloader 固件,再看 app 固件。代碼在 usr/ 目錄下。 棒!! 协议有没有计划支持一下 中继类型 的设备 支持大神
支持大神 同样看不懂,还是支持一下 本帖最后由 dukelec 于 2020-10-26 16:30 编辑
chendaon 发表于 2020-10-26 16:13
同样看不懂,还是支持一下
再补充一下吧,核心就是用一个结构体存放配置和其它变量,通讯接口直接让上位机读写这个结构体的数据即可。
譬如步进电机的结构体是下面这样的:
typedef struct {
uint16_t magic_code; // 0xcdcd
uint16_t conf_ver;
bool conf_from;// 0: default, 1: load from flash
bool do_reboot;
bool _reserved;// keep_in_bl for bootloader
bool save_conf;
uint8_t bus_net;
uint8_t bus_mac;
uint32_t bus_baud_low;
uint32_t bus_baud_high;
bool dbg_en;
cdn_sockaddr_tdbg_dst;
regr_t qxchg_set;
regr_t qxchg_ret;
regr_t qxchg_ro;
cdn_sockaddr_tdbg_raw_dst;
uint8_t dbg_raw_msk;
uint8_t dbg_raw_th; // len threshold (+ 1 samples < pkt size)
uint8_t dbg_raw_skip; // take samples every few times
regr_t dbg_raw;
int32_t tc_pos;
uint32_t tc_speed;
uint32_t tc_accel;
uint32_t tc_speed_min;
// end of flash (以上内容会保存到 flash,以下内容不保存)
uint8_t state; // 0: drv not enable, 1: drv enable
uint8_t tc_state; // t_curve: 0: stop, 1: run, 2: tailer
int cur_pos;
float tc_vc;
float tc_ac;
uint32_t time_cnt;
} csa_t; // config status area
譬如,你需要把 state 这个变量由 0 改成 1,就通过相关命令,把 state 的地址偏移当参数,再跟上需要改的目标值 0x01,就可以实现修改。
每个变量的地址、长度、类型 和 说明,我们通过以下函数,自动生成(效果见顶楼图片):
void csa_list_show(void)
{
d_debug("csa_list_show:\n");
d_debug("\n");
CSA_SHOW(conf_ver, "Magic Code: 0xcdcd");
CSA_SHOW(conf_from, "0: default config, 1: load from flash");
CSA_SHOW(do_reboot, "Write 1 to reboot");
CSA_SHOW(save_conf, "Write 1 to save current config to flash");
d_debug("\n");
CSA_SHOW(bus_mac, "RS-485 port id, range: 0~254");
CSA_SHOW(bus_baud_low, "RS-485 baud rate for first byte");
CSA_SHOW(bus_baud_high, "RS-485 baud rate for follow bytes");
CSA_SHOW(dbg_en, "1: Report debug message to host, 0: do not report");
CSA_SHOW_SUB(dbg_dst, cdn_sockaddr_t, addr, "Send debug message to this address");
CSA_SHOW_SUB(dbg_dst, cdn_sockaddr_t, port, "Send debug message to this port");
d_debug("\n");
CSA_SHOW(qxchg_set, "Config the write data components for quick-exchange channel");
CSA_SHOW(qxchg_ret, "Config the return data components for quick-exchange channel");
CSA_SHOW(qxchg_ro, "Config the return data components for the read only quick-exchange channel");
d_info("\n");
CSA_SHOW_SUB(dbg_raw_dst, cdn_sockaddr_t, addr, "Send raw debug data to this address");
CSA_SHOW_SUB(dbg_raw_dst, cdn_sockaddr_t, port, "Send raw debug data to this port");
CSA_SHOW(dbg_raw_msk, "Config which raw debug data to be send");
CSA_SHOW(dbg_raw_th, "Config raw debug data package size");
CSA_SHOW(dbg_raw_skip, "Reduce raw debug data");
CSA_SHOW(dbg_raw, "Config raw debug data components");
d_info("\n");
CSA_SHOW(tc_pos, "Set target position");
CSA_SHOW(tc_speed, "Set target speed");
CSA_SHOW(tc_accel, "Set target accel");
CSA_SHOW(tc_speed_min, "Set the minimum speed");
d_debug("\n");
CSA_SHOW(state, "0: disable drive, 1: enable drive");
d_debug("\n");
d_debug(" #--------------- Follows are not writable: -------------------\n");
CSA_SHOW(tc_state, "t_curve: 0: stop, 1: run, 2: tailer");
CSA_SHOW(cur_pos, "Motor current position");
CSA_SHOW(tc_vc, "Motor current speed");
CSA_SHOW(tc_ac, "Motor current accel");
d_debug("\n");
}
感谢分享 有点 Protobuf 的意思 支持大神!!!! zhangling520 发表于 2020-10-26 12:12
棒!! 协议有没有计划支持一下 中继类型 的设备
你說的中繼是指什麼?
如果想完美實現 CDBUS 協議的 RS485 中繼,需要自己做中繼,做起來也簡單。 支持这个通信协议 虽然看不懂,但是还得为楼主的无私共享精神顶一下! 占个座,顶一下。 牛。
开源的vesc好像也有上位机,是插usb的,不知道和cdbus有什么区别? 支持下开源 本帖最后由 dukelec 于 2020-10-28 10:40 编辑
motor_control 发表于 2020-10-28 09:49
牛。
开源的vesc好像也有上位机,是插usb的,不知道和cdbus有什么区别?
首先,工業上用 usb 就不太推薦,且因为它是有状态的通讯,線鬆動一下就很麻烦,也容易出錯,更不用說伺服器自身,干擾很大,usb 也不是全部都走差分信号,加隔離的話會好一點。
vesc 我看過它代碼,說實話,不太喜歡,主要是它大量工作偏離了 foc 控制本身,大量的代碼在周邊,光協議就同時支持了好多種類,除了 usb 還有 can 還有串口類的,串口類的除了 二進制 協議,還有類似 linux 終端的字符串命令交互。
這麼多软硬件接口,除了軟件繁瑣,硬件也很繁瑣,对外要留很多座子。而我的硬件對外一個 485 口就可以搞定包含 printf 調試在內的所有事情。(12 V 電源可以和 485 放一個座子,傳感器可以直接在板上。有的板子也可以單獨電源和單獨的傳感器接口。)
我喜歡簡約,我做的 foc 控制器,編碼器校準等事情,我都是盡量在上位機用 python 腳本做,簡化控制器自身的代碼和邏輯,代碼越少一是越不容易出錯,二是代碼運行效率更高,維護起來也更輕鬆。
就和 Linux 驅動編寫的準則一樣,驅動只實現必要的功能,各種策略在用戶空間完成。 支持大神
占个座,顶一下。{:lol:} 本帖最后由 motor_control 于 2020-10-29 17:47 编辑
dukelec 发表于 2020-10-28 10:18
首先,工業上用 usb 就不太推薦,且因为它是有状态的通讯,線鬆動一下就很麻烦,也容易出錯,更不用說伺 ...
回答非常专业,很赞同你的见解,必须顶下高手。
我也觉得VESC的代码整的太复杂,但是他搞的电机参数自动测量,还有实时波形显示真的不错,为什么我们就开发不出这样的东西来卖,至少目前为止,我觉得JSCOPE就是非常好的体验,除了有时候我不需要自动缩放功能,不知道有没有办法可以选择手动调节缩放,这样就完美了。
有伺服的话,可以放上来大家一起欣赏一下,现在直驱的力太小。 大神的帖子必须先收藏再细品 本帖最后由 dukelec 于 2020-10-30 15:56 编辑
motor_control 发表于 2020-10-29 15:25
回答非常专业,很赞同你的见解,必须顶下高手。
我也觉得VESC的代码整的太复杂,但是他搞的电机参数自动 ...
多谢支持。
我的 FOC 这次刚做的样品交给客户了,没板子了,新的板子要过一周才能回来。
我后面有时间,也会增加自整定和一些测量功能,但也是优先在 PC 端用 Python 脚本实现。
就譬如说 VESC 的字符串命令交互,其实和我上面在 Python 工具中敲命令,使用上没什么区别吧。
在 PC 端很容易实现的事情,如果放到 MCU 端就很麻烦,麻烦到一定程度就需要上 RTOS 操作系统,就要换更好性能和更多资源的 MCU,自问一下有这个必要吗?!
(我现在不用各种小系统了,简单的就裸奔,复杂的就上 Linux.)
JSCOPE 要收费的吧,而且还是有以下诸多问题:
- 安装好外壳就不方便调试了;
- 或者要单独增加对外的调试口,增加保护器件、占用电路板和外壳空间;
- 譬如运动中的 机器、手臂 或 执行机构,板子本身在高速运动,连接调试口不方便;
- 没法同时抓多个板子的数据;
- 不是差分,数据抗干扰差;
- 何时采集数据好像不能 MCU 软件控制吧?(我的方式可以每次电流环运行的时候,才上报电流环的数据,电流环没跑的时候,不用重复上报没有改变的数据,更不会漏掉某一个电流环周期的数据)
- 貌似芯片支持有限,不被官方支持的芯片没法使用;
- 二次开发应该也比较麻烦,譬如自己写软件分析波形数据;
- 费用应该不少,不是人人都有,不适合开源项目使用。
JTAG 我只用来下载代码,不用它调试。譬如单步调试,你今天调一个问题,下的断点没法和代码一起保存下来,下次想调试同样的问题,又要重新尝试增加很多断点。也没法分享你的调试方法给项目其他参与者。
而且断点会中断代码运行,很多时候会有副作用。
而且特别是系统复杂的时候,譬如用了 RTOS 操作系统,又譬如上了 Linux 系统,几乎没办法再用断点调试了。
(我曾经也喜欢 JTAG 调试,自从当年做了 Linux 内核开发后,就丢弃 JTAG 调试了。我曾经在芯片原厂,公司有 JTAG 工具,完全没人用。)
所以,综上可以得出结论,单步、断点等 JTAG 类型的调试方法,是初级的,终极调试手段反而是打印。
打印非常强大,这种调试的方法可以被保存到代码中,可以 动态的、部分的 开启和关闭,可以彩色打印,可以用挂在外部的黑盒子记录打印,可以无线打印……
JTAG 环境的配置也麻烦,譬如 Makefile + GCC 的环境。
弃用 JTAG 调试使我过的更舒畅。 dukelec 发表于 2020-10-30 15:53
多谢支持。
我的 FOC 这次刚做的样品交给客户了,没板子了,新的板子要过一周才能回来。
JSCOPE是SEGGER公司,也就是做JLINK的公司,搞的一个免费的软件,基于SWD,不用特别占用端口,可以显示8路还是16路实时波形,我忘了具体的路数了,在每个PWM中断周期里把那个变量发一下就可以了,把变量放在对应的变量位置那里就行,确实很方便。但有时电机失控或突发大电流时,确实会中断通信,如果用上你的总线,应该很好,但有个问题,我给你说下:
FREEMASTER和MICROCHIP,还有ucprobe,都有基于串口的实时波形显示,但显示时是一帧一帧的,看得不爽,不连续,但JSCOPE就可以显示连续的波形,时基可以很大,停止采集后可以展开时基,特别方便看波形的细节和变化过程,就像个数据记录仪一样,对我们搞电机控制来说,特别的重要。通道数倒不重要,有个4路足够。
现在的JSCOPE有几个不足之处:
1.基于ARM核,对于DSPIC什么的没法用。搞串口的通用性更好。
2.有时候我们不想要自动缩放功能,就是要手动设定缩放来观察波形。
3.容易受干扰而断开,界面也会死机。
4.不太容易加装无线传输,比如做无人机驱动时,下传遥测数据,并以波形的方式实时显示。
5.加上测量光标就好了,而且是2个点的测量。
目前我觉得MICROCHIP的RTDM就非常的不错,除了波形没法连续显示以外,都是满分好评。如果兄弟有兴趣,可以搞个类似的小盒子,用你的总线,我们用串口高速发送数据,配合少量的上传数据,这简直是完美的工具了。 标记一下 标志一下,有时间再认真学习 看起来的意思是可以在协议中混杂传输打印数据?这个可以有。 很多产品确实是有了外壳后没有log那么假设有个板子有485和串口以及Canbus,我们期望可以将Linux 内核的printk打印出来,如果走的是ModBus TCP的话 应该是没有办法的吧? 本帖最后由 dukelec 于 2020-11-5 19:20 编辑
QQ373466062 发表于 2020-11-5 14:47
看起来的意思是可以在协议中混杂传输打印数据?这个可以有。 很多产品确实是有了外壳后没有log那么假设 ...
是的。
對我而言,只留一個 RS485 (CDBUS) 就可以,對外的 TTL or RS232 串口 以及 CANBUS 都可以不再需要。
TTL 調試會保留一個在板子內部,做一些前期或者是底層的調試。
不過,要等便宜的 ASIC 芯片出來,才方便推廣這種做法。
對於 Linux,我覺得可以把 TTY 終端用 CDBUS 協議(不限物理層,TTL 也可以)包裝一下,這樣普通串口既可以打印 log、敲命令,也可以傳文件,比 AT 命令、X/Y/ZModem 這種需要來回切換模式的更方便。
或者,乾脆用 CDBUS 來實現 TCP/IP 通訊,這樣就可以用現有的工具實現 TTY 終端和文件傳輸,譬如 telnet 和 ssh 等等。(之前已經實現,使用 CDNET-TUN 這個小工具,在 linux 系統增加一個虛擬網卡。)
modbus tcp 只能雙方網絡連接並且 tcp 連接之後才能打印 log,萬一網絡故障外界就收不到打印了。
而用 rs485/cdbus,可以在非常底層的地方就把 printk / kmsg 主動發送出去。 学习来的,谢谢分享 顶一下牛人, ,
过年抽空学习 本帖最后由 dukelec 于 2021-2-4 16:25 编辑
一樓尾部的推後執行指令暫時不考慮,從廣播包取自己的數據已經加到 6 號快速讀寫端口了。另外 5 號端口增加了讀參數默認值的子命令。cdnet 協議棧也重構簡化了,現在默認打開 SEQ 支持,用于數據流控和完整性檢查。
新的硬件 v3.0 已經焊接好了,等待調試和更新,新的板子是用 stm32g0 系列,板子小很多。
順便在這邊也引用一下新的 GUI 工具:
對於新 gui 工具,mcu 端框架不變。
开源串口图形界面工具 CDBUS GUI 支持读写数据表、串口打印、IAP、波形显示…
https://www.amobbs.com/thread-5746055-1-1.html
原理圖已經上傳,pcb 有人需要也可以分享。
“如果不知道 MDRV 当前的 ID 号,可以发送命令到广播地址 0xff,但需要确保总线没有其它设备接入。”
一樓的這段話改一下,有其它設備也沒關係,只要 ID 沒有重複就好,一個廣播查詢命令,就可以返回總線所有設備列表。 多谢分享,标记 本帖最后由 Stm32Motor 于 2023-12-20 15:46 编辑
经过一天努力,终于可以在版言主的GUI界面显示CSA的信息,主要问题是bool定义为uint8_t时与t_name(expr)里的uint8_t,把t_name(expr)里的bool删除t就好。
以下为是发数据的关键代码:
void _dprintf(char* format, ...)
{
va_list arg;
va_start (arg, format);
uint8_t length=vsnprintf((char *)&infobuf, 250,format,arg);
va_end (arg);
infobuf=0x84;
infobuf=0x09;
infobuf=0x09;
infobuf=0x40;
CdBusSendData(0x0c,0x00,infobuf,length+4);
}
版言主的github里面各种找,终于打印出来了。
以下为打印CSA的相关代码:
#define t_name(expr)(_Generic((expr),int8_t: "b",uint8_t: "B",int16_t: "h",uint16_t: "H",int32_t: "i", uint32_t: "I",float: "f", char *: "",uint8_t *: "",regr_t: "H,H",regr_t *: "{H,H}",default: "-"))
#define CSA_SHOW(_p, _x, _desc) \
d_debug("[ 0x%04x, %d, \"%s\", " #_p ", \"" #_x "\", \"%s\" ],\n", \
offsetof(csa_t, _x), sizeof(csa._x), t_name(csa._x), _desc);
#define CSA_SHOW_SUB(_p, _x, _y_t, _y, _desc) \
d_debug("[ 0x%04x, %d, \"%s\", " #_p ", \"" #_x "_" #_y "\", \"%s\" ],\n", \
offsetof(csa_t, _x) + offsetof(_y_t, _y), sizeof(csa._x._y), t_name(csa._x._y), _desc);
void csa_list_show(void)
{
d_debug("csa_list_show:\n\n");
CSA_SHOW(1, magic_code, "Magic code: 0xcdcd");
CSA_SHOW(1, conf_ver, "Config version");
CSA_SHOW(1, conf_from, "0: default config, 1: all from flash, 2: partly from flash");
CSA_SHOW(0, do_reboot, "Write 1 to reboot");
CSA_SHOW(0, save_conf, "Write 1 to save current config to flash");
d_debug("\n");
CSA_SHOW_SUB(1, bus_cfg, cdctl_cfg_t, mac, "RS-485 port id, range: 0~254");
CSA_SHOW_SUB(0, bus_cfg, cdctl_cfg_t, baud_l, "RS-485 baud rate for first byte");
CSA_SHOW_SUB(0, bus_cfg, cdctl_cfg_t, baud_h, "RS-485 baud rate for follow bytes");
CSA_SHOW_SUB(1, bus_cfg, cdctl_cfg_t, filter_m, "Multicast address");
CSA_SHOW_SUB(0, bus_cfg, cdctl_cfg_t, mode, "0: Arbitration, 1: Break Sync");
CSA_SHOW_SUB(0, bus_cfg, cdctl_cfg_t, tx_permit_len, "Allow send wait time");
CSA_SHOW_SUB(0, bus_cfg, cdctl_cfg_t, max_idle_len, "Max idle wait time for BS mode");
CSA_SHOW_SUB(0, bus_cfg, cdctl_cfg_t, tx_pre_len, " Active TX_EN before TX");
d_debug("\n");
CSA_SHOW(0, dbg_en, "1: Report debug message to host, 0: do not report");
CSA_SHOW_SUB(2, dbg_dst, cdn_sockaddr_t, addr, "Send debug message to this address");
CSA_SHOW_SUB(1, dbg_dst, cdn_sockaddr_t, port, "Send debug message to this port");
d_debug("\n");
CSA_SHOW(1, qxchg_mcast, "Quick-exchange multicast data slice");
CSA_SHOW(1, qxchg_set, "Config the write data components for quick-exchange channel");
CSA_SHOW(1, qxchg_ret, "Config the return data components for quick-exchange channel");
CSA_SHOW(1, qxchg_ro, "Config the return data components for the read only quick-exchange channel");
d_debug("\n");
CSA_SHOW_SUB(2, dbg_raw_dst, cdn_sockaddr_t, addr, "Send raw debug data to this address");
CSA_SHOW_SUB(1, dbg_raw_dst, cdn_sockaddr_t, port, "Send raw debug data to this port");
CSA_SHOW(1, dbg_raw_msk, "Config which raw debug data to be send");
CSA_SHOW(0, dbg_raw_th, "Config raw debug data package size");
CSA_SHOW(1, dbg_raw, "Config raw debug for plot0");
CSA_SHOW(1, dbg_raw, "Config raw debug for plot1");
d_debug("\n");
CSA_SHOW(0, ref_volt, "Motor driver reference voltage, unit: mV");
CSA_SHOW(0, md_val, "Motor driver md pin value");
CSA_SHOW(0, set_home, "Write 1 set home position");
CSA_SHOW(0, drv_mo, "MO pin state of drv chip, for debug");
CSA_SHOW(0, lim_en, "Enable limit switch");
d_debug("\n");
CSA_SHOW(0, tc_pos, "Set target position");
CSA_SHOW(0, tc_speed, "Set target speed");
CSA_SHOW(0, tc_accel, "Set target accel");
CSA_SHOW(0, tc_accel_emg, "Set emergency accel");
d_debug("\n");
CSA_SHOW_SUB(0, pid_pos, pid_i_t, kp, "");
CSA_SHOW_SUB(0, pid_pos, pid_i_t, ki, "");
CSA_SHOW_SUB(0, pid_pos, pid_i_t, kd, "");
CSA_SHOW_SUB(0, pid_pos, pid_i_t, out_min, "");
CSA_SHOW_SUB(0, pid_pos, pid_i_t, out_max, "");
CSA_SHOW(0, cal_pos, "PID input position");
CSA_SHOW(0, cal_speed, "PID output speed");
d_debug("\n");
CSA_SHOW(0, state, "0: disable drive, 1: enable drive");
d_debug("\n");
d_debug(" // --------------- Follows are not writable: -------------------\n");
CSA_SHOW(0, tc_state, "t_curve: 0: stop, 1: run");
CSA_SHOW(0, cur_pos, "Motor current position");
CSA_SHOW(0, tc_vc, "Motor current speed");
CSA_SHOW(0, tc_ac, "Motor current accel");
d_debug("\n");
CSA_SHOW(0, loop_cnt, "Count for plot");
CSA_SHOW(0, string_test, "String test");
d_debug("\n");
}
//编辑原因,修改描述错误 1.泛型函数,C11新增了关键字 _Generic,可用于处理泛型函数
用法
_Generic(x, int: "int", double: "double", default: "other");
编译器会根据传入的x来推断类型,然后与后面的标签匹配。可结合宏定义来达到一定的泛型的目的
演示
#define get_type(x) _Generic(x, int: "int", double: "double", char: "char", default: "other")
offsetof的功能:用来确定结构体中各成员变量的偏移量。
#include<stddef.h>
typedef struct Student
{
int age;
char name;
char sex;
}Stu;
int main()
{
printf("%d %d\n", offsetof(Stu, age),offsetof(Stu,name));
return 0;
}
结果:
0,4
页:
[1]