搜索
bottom↓
回复: 34

The Art of Protocol | 通讯框架,含 IAP、曲线绘制等,步进电机示范工程

  [复制链接]

出615入1076汤圆

发表于 2020-10-26 11:55:33 | 显示全部楼层 |阅读模式
本帖最后由 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 简介
=======================================

下载项目:

  1. git clone --recurse-submodules https://github.com/dukelec/stepper_motor_controller.git
复制代码



## 协议

MDRV 是一个开源步进电机控制器,它使用 RS485 接口,默认波特率为 115200 bps, 最高 > 10 Mbps,默认地址为 0xfe.

最底层协议为 CDBUS,其帧格式为:  
  1. src, dst, len, [payload], 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 号端口的格式:

  1. 01 00
复制代码


第一个字节是目标端口号,然后跟数据即可。

以上示范,完整的 CDBUS 协议帧为(主机地址默认为 0,MDRV 为 0xfe):

  1. 00 fe 02  01 00  crc_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 对象的后面是一些内部变量,不应该被用户修改,
有了可写区域限制,用户可以非常方便的整块写入数据,不用担心写到不该写的数据。




读写参数表的子命令为:


  1. read:  0x00, offset_16, len_8   | return [0x80, data]
  2. write: 0x20, offset_16 + [data] | return [0x80] on success
复制代码


譬如设置电机锁舵并进入位置模式。电机状态 `state` 值为 0 不锁舵,为 1 锁舵。

`state` 本身的地址是 `0x0100`,长度 1 个字节,那么上电后需要锁舵,发送以下数据到端口 5 即可:

  1. 20  00 01  01
复制代码

`20` 是子命令 `write`,`00 01` 是地址 0x0100 的小端格式(除非特别说明,否则都是用小端),最后一个 `01` 是写入数值。

具体参数表可以参考 `tools` 目录下的 `mdrv_reg.py` 这个文件。

修改 `csa_t` 的定义,可能会影响到很多寄存器的地址,如果每次都是手工计算各寄存器的地址,很麻烦且容易出错,所以,我们通过 `csa_list_show` 这个函数,
上电时自动打印出所有寄存器的地址、类型,以及说明信息,可以直接拷贝到文档或是 Python 源码中。



每次修改 `csa_t` 的定义后,请同时修改其中的 `conf_ver` 版本号。


### 端口 6: 快速读写

位置模式常用的参数表项有:

  1. tc_pos, type: int32_t
  2. tc_speed, type: uint32_t
  3. tc_accel, type: uint32_t
复制代码


我们可以通过端口 5 来写这些数据,但是,有的时候我们还想去写非连续的其它 表项,这样就要拆分多条命令,效率低,且容易导致数据不同步。  
而且,我们还想设备同时回覆电机的错误码和一些运行数据,但不同模式下我们关心的运行数据不尽相同,如果写死会比较不方便。

参数表项内有 快速读写 通道的配置:(qxchg 是 quick exchange 的缩写)



  1. regr_t          qxchg_set[10];
  2. regr_t          qxchg_ret[10];
  3. regr_t          qxchg_ro[10];
复制代码


以上数组,首个 size 值为 0 的元素表示它和后序的未使用,元素的定义:


  1. typedef struct {
  2.     uint16_t        offset;
  3.     uint16_t        size;
  4. } regr_t; // reg range
复制代码


`qxchg_set` 和 `qxchg_ret` 是 快速读写 的 子命令 `20` 所用,
预先设置好写哪些数据,以及返回哪些数据,然后发送 `20` + 需要写的数据就可以了,
会返回 `80` + 返回的数据。

`qxchg_ro` 是指定只读的子命令 `00` 返回哪些数据。

默认的设定:


  1. .qxchg_set = {
  2.         { .offset = offsetof(csa_t, tc_pos), .size = 4 * 3 }
  3. },
  4. .qxchg_ret = {
  5.         { .offset = offsetof(csa_t, cur_pos), .size = 4 * 2 }
  6. },
复制代码



`qxchg_set` 只用了一个元素,指向 `tc_pos`, `tc_speed` 和 `tc_accel` 这 3 个表项所在的区域,  
如果需要修改目标位置参数 `tc_pos` 为 0,向 端口 6 写数据 `20  00 00 00 00` 即可(`20` 是子命令号)。  
如果需要同时修改目标位置和目标速度,譬如位置改为 0x00001000,速度改为 0x00000500,则写入 `20  00 10 00 00  00 05 00 00`.



### 端口 8: Flash 读写

可以通过此接口实现 IAP 升级。

子命令为:


  1. erase:   0x2f, addr_32, len_32  | return [0x80] on success
  2. write:   0x20, addr_32 + [data] | return [0x80] on success
  3. read:    0x00, addr_32, len_8   | return [0x80, data]
  4. cal crc: 0x10, addr_32, len_32  | return [0x80, crc_16] # 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

可以增加一个端口号,从广播包中取出自己节点的数据,然后进行处理。
另外是,实现一个推后执行命令的功能,方便主机先同步数据,再通知所有节点同步执行命令。
这两个功能可以放在一个端口号中,用不同的子命令来分别实现。
这两个功能实现很简单,代码量也非常少。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出0入4汤圆

发表于 2020-10-26 11:57:39 | 显示全部楼层
虽然看不懂,但还是要支持大神

出615入1076汤圆

 楼主| 发表于 2020-10-26 12:02:33 | 显示全部楼层
本帖最后由 dukelec 于 2020-10-26 12:20 编辑
aleyn 发表于 2020-10-26 11:57
虽然看不懂,但还是要支持大神


可以看下源碼,代碼很少,先看下 bootloader 固件,再看 app 固件。代碼在 usr/ 目錄下。

出0入0汤圆

发表于 2020-10-26 12:12:12 | 显示全部楼层
棒!! 协议有没有计划支持一下 中继类型 的设备

出0入0汤圆

发表于 2020-10-26 15:56:52 | 显示全部楼层
支持大神                  

出0入42汤圆

发表于 2020-10-26 16:13:46 | 显示全部楼层

支持大神                 

出0入4汤圆

发表于 2020-10-26 16:13:57 | 显示全部楼层
同样看不懂,还是支持一下

出615入1076汤圆

 楼主| 发表于 2020-10-26 16:27:35 | 显示全部楼层
本帖最后由 dukelec 于 2020-10-26 16:30 编辑
chendaon 发表于 2020-10-26 16:13
同样看不懂,还是支持一下


再补充一下吧,核心就是用一个结构体存放配置和其它变量,通讯接口直接让上位机读写这个结构体的数据即可。

譬如步进电机的结构体是下面这样的:

  1. typedef struct {
  2.     uint16_t        magic_code; // 0xcdcd
  3.     uint16_t        conf_ver;
  4.     bool            conf_from;  // 0: default, 1: load from flash
  5.     bool            do_reboot;
  6.     bool            _reserved;  // keep_in_bl for bootloader
  7.     bool            save_conf;

  8.     uint8_t         bus_net;
  9.     uint8_t         bus_mac;
  10.     uint32_t        bus_baud_low;
  11.     uint32_t        bus_baud_high;

  12.     bool            dbg_en;
  13.     cdn_sockaddr_t  dbg_dst;

  14.     regr_t          qxchg_set[10];
  15.     regr_t          qxchg_ret[10];
  16.     regr_t          qxchg_ro[10];

  17.     cdn_sockaddr_t  dbg_raw_dst;
  18.     uint8_t         dbg_raw_msk;
  19.     uint8_t         dbg_raw_th;      // len threshold (+ 1 samples < pkt size)
  20.     uint8_t         dbg_raw_skip[2]; // take samples every few times
  21.     regr_t          dbg_raw[2][10];

  22.     int32_t         tc_pos;
  23.     uint32_t        tc_speed;
  24.     uint32_t        tc_accel;
  25.     uint32_t        tc_speed_min;

  26.     // end of flash (以上内容会保存到 flash,以下内容不保存)

  27.     uint8_t         state;      // 0: drv not enable, 1: drv enable
  28.     uint8_t         tc_state;   // t_curve: 0: stop, 1: run, 2: tailer
  29.     int             cur_pos;
  30.     float           tc_vc;
  31.     float           tc_ac;

  32.     uint32_t        time_cnt;

  33. } csa_t; // config status area
复制代码


譬如,你需要把 state 这个变量由 0 改成 1,就通过相关命令,把 state 的地址偏移当参数,再跟上需要改的目标值 0x01,就可以实现修改。

每个变量的地址、长度、类型 和 说明,我们通过以下函数,自动生成(效果见顶楼图片):

  1. void csa_list_show(void)
  2. {
  3.     d_debug("csa_list_show:\n");
  4.     d_debug("\n");

  5.     CSA_SHOW(conf_ver, "Magic Code: 0xcdcd");
  6.     CSA_SHOW(conf_from, "0: default config, 1: load from flash");
  7.     CSA_SHOW(do_reboot, "Write 1 to reboot");
  8.     CSA_SHOW(save_conf, "Write 1 to save current config to flash");
  9.     d_debug("\n");

  10.     CSA_SHOW(bus_mac, "RS-485 port id, range: 0~254");
  11.     CSA_SHOW(bus_baud_low, "RS-485 baud rate for first byte");
  12.     CSA_SHOW(bus_baud_high, "RS-485 baud rate for follow bytes");
  13.     CSA_SHOW(dbg_en, "1: Report debug message to host, 0: do not report");
  14.     CSA_SHOW_SUB(dbg_dst, cdn_sockaddr_t, addr, "Send debug message to this address");
  15.     CSA_SHOW_SUB(dbg_dst, cdn_sockaddr_t, port, "Send debug message to this port");
  16.     d_debug("\n");

  17.     CSA_SHOW(qxchg_set, "Config the write data components for quick-exchange channel");
  18.     CSA_SHOW(qxchg_ret, "Config the return data components for quick-exchange channel");
  19.     CSA_SHOW(qxchg_ro, "Config the return data components for the read only quick-exchange channel");
  20.     d_info("\n");

  21.     CSA_SHOW_SUB(dbg_raw_dst, cdn_sockaddr_t, addr, "Send raw debug data to this address");
  22.     CSA_SHOW_SUB(dbg_raw_dst, cdn_sockaddr_t, port, "Send raw debug data to this port");
  23.     CSA_SHOW(dbg_raw_msk, "Config which raw debug data to be send");
  24.     CSA_SHOW(dbg_raw_th, "Config raw debug data package size");
  25.     CSA_SHOW(dbg_raw_skip, "Reduce raw debug data");
  26.     CSA_SHOW(dbg_raw, "Config raw debug data components");
  27.     d_info("\n");

  28.     CSA_SHOW(tc_pos, "Set target position");
  29.     CSA_SHOW(tc_speed, "Set target speed");
  30.     CSA_SHOW(tc_accel, "Set target accel");
  31.     CSA_SHOW(tc_speed_min, "Set the minimum speed");
  32.     d_debug("\n");

  33.     CSA_SHOW(state, "0: disable drive, 1: enable drive");
  34.     d_debug("\n");
  35.     d_debug("   #--------------- Follows are not writable: -------------------\n");
  36.     CSA_SHOW(tc_state, "t_curve: 0: stop, 1: run, 2: tailer");
  37.     CSA_SHOW(cur_pos, "Motor current position");
  38.     CSA_SHOW(tc_vc, "Motor current speed");
  39.     CSA_SHOW(tc_ac, "Motor current accel");
  40.     d_debug("\n");
  41. }
复制代码

出5入0汤圆

发表于 2020-10-26 16:32:02 来自手机 | 显示全部楼层
感谢分享

出0入147汤圆

发表于 2020-10-26 16:34:22 来自手机 | 显示全部楼层
有点 Protobuf 的意思

出0入0汤圆

发表于 2020-10-26 16:43:52 | 显示全部楼层
支持大神!!!!

出615入1076汤圆

 楼主| 发表于 2020-10-27 14:38:19 来自手机 | 显示全部楼层
zhangling520 发表于 2020-10-26 12:12
棒!! 协议有没有计划支持一下 中继类型 的设备

你說的中繼是指什麼?
如果想完美實現 CDBUS 協議的 RS485 中繼,需要自己做中繼,做起來也簡單。

出0入0汤圆

发表于 2020-10-27 17:06:18 | 显示全部楼层
支持这个通信协议

出0入0汤圆

发表于 2020-10-28 08:11:55 | 显示全部楼层
虽然看不懂,但是还得为楼主的无私共享精神顶一下!

出0入8汤圆

发表于 2020-10-28 09:06:34 | 显示全部楼层
占个座,顶一下。

出0入79汤圆

发表于 2020-10-28 09:49:31 来自手机 | 显示全部楼层
牛。
开源的vesc好像也有上位机,是插usb的,不知道和cdbus有什么区别?

出100入101汤圆

发表于 2020-10-28 10:10:30 | 显示全部楼层
支持下开源

出615入1076汤圆

 楼主| 发表于 2020-10-28 10:18:37 来自手机 | 显示全部楼层
本帖最后由 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 驅動編寫的準則一樣,驅動只實現必要的功能,各種策略在用戶空間完成。

出0入0汤圆

发表于 2020-10-28 10:36:35 | 显示全部楼层
支持大神

出0入0汤圆

发表于 2020-10-28 13:21:05 | 显示全部楼层

占个座,顶一下。

出0入79汤圆

发表于 2020-10-29 15:25:51 | 显示全部楼层
本帖最后由 motor_control 于 2020-10-29 17:47 编辑
dukelec 发表于 2020-10-28 10:18
首先,工業上用 usb 就不太推薦,且因为它是有状态的通讯,線鬆動一下就很麻烦,也容易出錯,更不用說伺 ...


回答非常专业,很赞同你的见解,必须顶下高手。

我也觉得VESC的代码整的太复杂,但是他搞的电机参数自动测量,还有实时波形显示真的不错,为什么我们就开发不出这样的东西来卖,至少目前为止,我觉得JSCOPE就是非常好的体验,除了有时候我不需要自动缩放功能,不知道有没有办法可以选择手动调节缩放,这样就完美了。

有伺服的话,可以放上来大家一起欣赏一下,现在直驱的力太小。

出0入8汤圆

发表于 2020-10-29 15:41:58 | 显示全部楼层
大神的帖子必须先收藏再细品

出615入1076汤圆

 楼主| 发表于 2020-10-30 15:53:02 | 显示全部楼层
本帖最后由 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 调试使我过的更舒畅。

出0入79汤圆

发表于 2020-10-30 18:05:51 | 显示全部楼层
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就非常的不错,除了波形没法连续显示以外,都是满分好评。如果兄弟有兴趣,可以搞个类似的小盒子,用你的总线,我们用串口高速发送数据,配合少量的上传数据,这简直是完美的工具了。

出0入0汤圆

发表于 2020-11-5 06:20:45 来自手机 | 显示全部楼层
标记一下

出0入0汤圆

发表于 2020-11-5 08:34:26 | 显示全部楼层
标志一下,有时间再认真学习

出0入0汤圆

发表于 2020-11-5 14:47:18 | 显示全部楼层
看起来的意思是可以在协议中混杂传输打印数据?  这个可以有。 很多产品确实是有了外壳后没有log  那么假设有个板子有485和串口以及Canbus,我们期望可以将Linux 内核的printk打印出来,如果走的是ModBus TCP的话 应该是没有办法的吧?

出615入1076汤圆

 楼主| 发表于 2020-11-5 19:05:31 | 显示全部楼层
本帖最后由 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 主動發送出去。

出0入0汤圆

发表于 2021-2-4 09:52:59 | 显示全部楼层
学习来的,谢谢分享

出0入0汤圆

发表于 2021-2-4 13:55:17 | 显示全部楼层
顶一下牛人, ,
过年抽空学习

出615入1076汤圆

 楼主| 发表于 2021-2-4 14:03:47 来自手机 | 显示全部楼层
本帖最后由 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 沒有重複就好,一個廣播查詢命令,就可以返回總線所有設備列表。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2021-6-28 12:06:35 来自手机 | 显示全部楼层
多谢分享,标记

出50入135汤圆

发表于 2023-12-20 15:38:11 | 显示全部楼层
本帖最后由 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[4], 250,format,arg);
        va_end (arg);
       
        infobuf[0]=0x84;
        infobuf[1]=0x09;
        infobuf[2]=0x09;
        infobuf[3]=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 *: "[c]",uint8_t *: "[B]",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[0], "Config raw debug for plot0");
    CSA_SHOW(1, dbg_raw[1], "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[2:0] 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");
}

//编辑原因,修改描述错误

出50入135汤圆

发表于 2023-12-20 15:43:16 | 显示全部楼层
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")

出50入135汤圆

发表于 2023-12-20 15:45:24 | 显示全部楼层
offsetof的功能:用来确定结构体中各成员变量的偏移量。
#include<stddef.h>

typedef struct Student
{
        int age;
        char name[10];
        char sex[10];
}Stu;

int main()
{
        printf("%d %d\n", offsetof(Stu, age),offsetof(Stu,name));

        return 0;
}

结果:
0,4
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 15:47

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表