[活动专区] 【AT-START-F425测评】+基于python-can的USB-CAN监视设备

[复制链接]
 楼主| 发表于 2022-3-7 08:41 | 显示全部楼层 |阅读模式
本帖最后由 coslight 于 2022-3-7 08:49 编辑

基于python-can的USB-CAN监视设备
      本测试为AT-START-F425开发板的项目测试。测试想要达到的目的为,基于python环境完成一个USB-CAN监视设备的实现。试验主要完成如下几个关键内容:
l  USB虚拟串口的实现
l  CAN总线驱动及缓冲区管理
l  Python-can接口的协议解析
l  测试基于python的can总线数据首发
1、Python-can的介绍
python-can库为Python提供了控制器局域网的支持,为不同的硬件设备提供了通用的抽象,并提供了一套实用程序,用于在CAN总线上发送和接收消息。
python-可以在Python运行的任何地方运行;从具有商用CAN的高功率计算机到USB设备,再到运行Linux的低功率设备(例如BeagleBone或RaspberryPi)。
Python-can支持很多的接口,下表描述的他支持的接口类型。

  
接口应用
  
对应的接口
“socketcan”
SocketCAN
“kvaser”
Kvaser’s  CANLIB
“serial”
CAN  over Serial
“slcan”
CAN  over Serial / SLCAN
“ixxat”
IXXAT  Virtual CAN Interface
“pcan”
PCAN  Basic API
“usb2can”
USB2CAN  Interface
“nican”
NI-CAN
“iscan”
isCAN
“neovi”
NEOVI  Interface
“vector”
Vector
“virtual”
Virtual
“canalystii”
CANalyst-II
“systec”
SYSTEC  interface
其中“slcan”接口是我们本次研究的对象,我们将让AT32F425支持python-can的slcan接口类型。
Python-can的库API主要对象是BusABC和Message。其中包括如下的API:
l  Bus
l  Thread safe bus
l  Message
l  Listeners
l  Asyncio support
l  Broadcast Manager
l  Internal API

2、USB-CAN实现
2.1 USB虚拟串口
虚拟串口CDC在官方的标准库例程中已经提供了。由于采用USB传输,所以对于测试设备的使用非常友好,而且速度很快,完全可以匹配CAN总线的处理能力。
通过采用官方提供的例程,在系统中可以看到两个串口设备,其中COM6为仿真器提供的虚拟串口,COM8为程序产生的虚拟串口。
5036062254e4c2642f.png
实际测试,虚拟串口可以正确的工作。
2.2 CAN接口驱动及缓冲区管理
    CAN总线的使用,标准库中也已经提供了例程,但是例程中是采用1M固定波特率的一个测试,并不适合我们使用,我们需要重新调整,具体的调整要结合python-can中slcan接口中的要求,为了增加总线的处理能力,还需要为CAN总线的首发提供缓冲区。因此我们重新修改了基于slcan要求的CAN总线操作接口函数:
1)       CAN总线端口初始化
我们采用PB8和PB9两个引脚作为CAN总线的收发引脚,因此初始化如下。
  1. static void can_gpio_config(void)
  2. {
  3.   gpio_init_typegpio_init_struct;
  4.   /* enable the gpio clock */
  5. crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);

  6. gpio_default_para_init(&gpio_init_struct);

  7.   /* configure the can tx, rx pin */
  8.   gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;
  9.   gpio_init_struct.gpio_out_type = GPIO_OUTPUT_PUSH_PULL;
  10.   gpio_init_struct.gpio_mode = GPIO_MODE_MUX;
  11.   gpio_init_struct.gpio_pins = GPIO_PINS_9| GPIO_PINS_8;
  12.   gpio_init_struct.gpio_pull = GPIO_PULL_NONE;
  13.   gpio_init(GPIOB,&gpio_init_struct);

  14. gpio_pin_mux_config(GPIOB, GPIO_PINS_SOURCE9, GPIO_MUX_4);
  15.   gpio_pin_mux_config(GPIOB,GPIO_PINS_SOURCE8, GPIO_MUX_4);
  16. }

2)       CAN总线的配置和打开
CAN总线打开的时候,需要完成波特率的配置,首先定义CAN总线波特率配置所需的结构,并初始化结构,这个结构中波特率的设定是根据slcan中定义的要求来的。
  1. typedef struct
  2. {
  3.     uint16_t baudrate_div;
  4.     uint8_t rsaw_size;
  5.     uint8_t bts1_size;
  6.     uint8_t bts2_size;
  7. }_CAN_BAUD_CONFIG;
  8. const _CAN_BAUD_CONFIG can_baud_conf[] =
  9.     {
  10.      //baudrate_div  rsaw_size       bts1_size   bts2_size
  11.        {960,   CAN_RSAW_1TQ,     CAN_BTS1_7TQ,  CAN_BTS2_2TQ},  //10000: "S0",
  12.        {480,   CAN_RSAW_1TQ,     CAN_BTS1_7TQ,  CAN_BTS2_2TQ},  //20000: "S1",
  13.        {160,   CAN_RSAW_1TQ,     CAN_BTS1_8TQ,  CAN_BTS2_3TQ},  //50000: "S2",
  14.        {80,    CAN_RSAW_1TQ,     CAN_BTS1_8TQ,  CAN_BTS2_3TQ}, //100000:"S3",
  15.        {64,    CAN_RSAW_1TQ,     CAN_BTS1_8TQ,  CAN_BTS2_3TQ},  //125000: "S4",
  16.        {32,    CAN_RSAW_1TQ,     CAN_BTS1_8TQ,  CAN_BTS2_3TQ}, //250000:"S5",
  17.        {16,    CAN_RSAW_1TQ,     CAN_BTS1_8TQ,  CAN_BTS2_3TQ},  //500000: "S6",
  18.        {8,     CAN_RSAW_1TQ,     CAN_BTS1_12TQ, CAN_BTS2_3TQ},  //750000: "S7",
  19.        {8,     CAN_RSAW_1TQ,     CAN_BTS1_8TQ,  CAN_BTS2_3TQ}, //1000000:"S8",
  20.                 //83300:"S9",
  21.     };

定义CAN总线的打开函数,其中包含CAN接口配置和中断配置。
  1. int can_open(_SLCAN_CAN_STA slcan_sta)
  2. {
  3.   can_base_typecan_base_struct;
  4.   can_baudrate_type can_baudrate_struct;
  5.   can_filter_init_type can_filter_init_struct;
  6.   /* enable the can clock */
  7. crm_periph_clock_enable(CRM_CAN1_PERIPH_CLOCK, TRUE);

  8.   /* can base init */
  9. can_default_para_init(&can_base_struct);
  10.   can_base_struct.mode_selection = CAN_MODE_COMMUNICATE;
  11.   can_base_struct.ttc_enable = FALSE;
  12.   can_base_struct.aebo_enable = TRUE;
  13.   can_base_struct.aed_enable = TRUE;
  14.   can_base_struct.prsf_enable = FALSE;
  15.   can_base_struct.mdrsel_selection = CAN_DISCARDING_FIRST_RECEIVED;
  16.   can_base_struct.mmssr_selection = CAN_SENDING_BY_ID;
  17. can_base_init(CAN1, &can_base_struct);

  18.   /* can baudrate, set boudrate = pclk/(baudrate_div*(1 + bts1_size + bts2_size)) */
  19.   if(slcan_sta.baud <= 9)
  20.   {

  21.       can_baudrate_struct.baudrate_div =can_baud_conf[slcan_sta.baud].baudrate_div;
  22.       can_baudrate_struct.rsaw_size =can_baud_conf[slcan_sta.baud].rsaw_size;
  23.       can_baudrate_struct.bts1_size =can_baud_conf[slcan_sta.baud].bts1_size;
  24.       can_baudrate_struct.bts2_size =can_baud_conf[slcan_sta.baud].bts2_size;
  25.       can_baudrate_set(CAN1,&can_baudrate_struct);
  26.   }
  27.   else
  28.   {
  29.       /*disable the can clock */
  30.       crm_periph_clock_enable(CRM_CAN1_PERIPH_CLOCK, FALSE);
  31.       /*初始化错误*/
  32.       return -1;
  33.   }

  34.   /* can filter init */
  35. can_filter_init_struct.filter_activate_enable = TRUE;
  36. can_filter_init_struct.filter_mode = CAN_FILTER_MODE_ID_MASK;
  37.   can_filter_init_struct.filter_fifo = CAN_FILTER_FIFO0;
  38. can_filter_init_struct.filter_number = 0;
  39. can_filter_init_struct.filter_bit = CAN_FILTER_32BIT;
  40. can_filter_init_struct.filter_id_high = 0;
  41. can_filter_init_struct.filter_id_low = 0;
  42.   can_filter_init_struct.filter_mask_high = 0;
  43. can_filter_init_struct.filter_mask_low = 0;
  44. can_filter_init(CAN1, &can_filter_init_struct);

  45.   /* can interrupt config */
  46.   nvic_irq_enable(CAN1_IRQn, 0x01,0x00);
  47. can_interrupt_enable(CAN1, CAN_RF0MIEN_INT, TRUE);
  48.   return 0;
  49. }

3)       CAN总线的关闭
关闭CAN总线时钟,同时关闭中断。
  1. void can_close(void)
  2. {
  3.     can_interrupt_enable(CAN1,CAN_RF0MIEN_INT, FALSE);
  4.     /* disable the can clock */
  5.     crm_periph_clock_enable(CRM_CAN1_PERIPH_CLOCK, FALSE);
  6. }


4)       CAN总线的收发缓冲区定义
为了提高CAN总线的瞬时数据吞吐率,定义了CAN总线的收发缓冲区。
  1. #define_CAN_RX_MAX_FRAME   600
  2. typedef struct
  3. {
  4.     uint16_t head;
  5.     uint16_t tail;
  6.     can_rx_message_type buf[_CAN_RX_MAX_FRAME];
  7. }_CAN_RX_STRUCT;
  8. _CAN_RX_STRUCTcan_rx_buf;

  9. #define_CAN_TX_MAX_FRAME   100
  10. typedef struct
  11. {
  12.     uint16_t head;
  13.     uint16_t tail;
  14.     can_tx_message_type buf[_CAN_TX_MAX_FRAME];
  15. }_CAN_TX_STRUCT;
  16. _CAN_TX_STRUCTcan_tx_buf;

在CAN总线的接收中断中,完成CAN接收缓冲区的填充,然后再慢慢的把它转为slcan协议支持的数据帧发送到CDC口。
3、slcan接口的协议解析
      slcan接口时python-can库中一个基于串口的协议,我们采用的时USB虚拟串口,所以这个协议正好适合我们。Slcan接口协议采用ASCII码的方式进行数据交互,所以效率稍微低一点,不过再PC系统里面却比较好处理。
1)关闭can接口
      发送字符“C”。
2) 打开can接口
      发送字符“O”
3) can波特率设置
      发送字符”S”+一个ASCII码数字。具体含义如下:
  
序号
  
波特率(bps)
发送命令
1
10K
“S0”
2
20K
“S1”
3
50K
“S2”
4
100K
“S3”
5
125K
“S4”
6
250K
“S5”
7
500K
“S6”
8
750K
“S7”
9
1M
“S8”
10
83.3K
“S9”

4) 接收数据帧
    数据帧的构成包括:帧类型字符+CANID+数据长度+数据+“\r“
扩展帧: “T1234567890123456789ABCDEF”
帧类型:“T”
         CANid:12345678
             dlc:  9
            data:01 23 45 67 89 AB CD EF

     标准帧:“t12340123456789ABCDEF”
帧类型:“t”
         CANid:123
             dlc:  4
            data:01 23 45 67 89 AB CD EF

标准远程帧: “r1234”
帧类型:“r”
         CANid:123
             dlc:  4

扩展远程帧:“R123456789”
帧类型:“R”
         CANid:12345678
             dlc:  9

5)发送数据帧
      格式同接收

4、测试环境
4.1 PC端CAN测试节点
为了测试整体运行效果,我们还需要一个其它的CAN总线收发器。
4.2 基于python的USB-CAN编程
      当我们的程序可以正确的运行起来后,我们需要编写一个python的测试程序,前提是我们已经安装了python-can库。
      安装方法为: pip install python-can
      Python的测试程序为:
  1. # import the library
  2. import can

  3. # create a bus instance
  4. # many other interfaces are supported as well (see documentation)
  5. bus = can.Bus(interface='slcan',
  6.               channel='COM8',
  7.               bitrate=125000,
  8.               receive_own_messages=True)

  9. '''# send a message
  10. message = can.Message(arbitration_id=123, is_extended_id=True,
  11.                       data=[0x11, 0x22, 0x33])
  12. bus.send(message, timeout=0.2)

  13. # iterate over received messages
  14. for msg in bus:
  15.     print(f"{msg.arbitration_id:X}: {msg.data}")

  16. # or use an asynchronous notifier
  17. notifier = can.Notifier(bus, [can.Logger("recorded.log"), can.Printer()])'''

  18. '''   发送信息 '''
  19. def send_one():
  20.     #bus = can.interface.Bus();
  21.     msg = can.Message(arbitration_id=0x7f,
  22.            data=[11, 25, 11, 1, 1, 2, 23, 18],
  23.            is_extended_id=False)
  24.     try:
  25.         ''' 发送信息 '''
  26.         bus.send(msg)   
  27.         print("Message sent on {}".format(bus.channel_info))
  28.     except can.CanError:
  29.         print("Message NOT sent")

  30. def recv():
  31.     #bus = can.interface.Bus();
  32.     ''' 接收信息 '''
  33.     msg = bus.recv(100)   
  34.     try:
  35.         bus.send(msg)
  36.         print(msg)
  37. #        print(msg.data[0])              #  接收回来的第一个字节的数据
  38. #        print(msg.arbitration_id)    # 接收回来的ID
  39.         return msg
  40.     except can.CanError:
  41.         print("Message NOT sent")

  42. if __name__ == "__main__":
  43.     '''   can_setup("can1"); '''
  44. #    send_one()
  45.     while True:
  46.        recv()

  47.     ''' can_stop("can1"); '''


5、实际运行效果
搭建的基本环境,由于开发板没有CAN物理接口,所以外接了一个物理接口
8097562254f7fd57d1.png

PC端CAN节点工具的运行效果
7762462254fb3aea1d.png
Python在使用我们制作的USB-CAN监视设备进行数据收发测试数据,环境是基于VSCode搭建的。
7169662254fc32742a.png
数据收发及操作过程的演示视频
t2.gif

8271262254dbfd7aef.png
2253762254fbd3691a.png
发表于 2022-3-7 10:11 | 显示全部楼层
为大家积极的态度点个赞
发表于 2022-3-8 09:09 | 显示全部楼层
这个不错
发表于 2022-9-21 14:53 | 显示全部楼层
这个代码在哪里,学习一下
发表于 2022-10-1 18:10 | 显示全部楼层
这是写的监测软件么?
发表于 2022-10-4 21:11 | 显示全部楼层
楼主厉害啊
发表于 2022-10-5 16:34 | 显示全部楼层
其实就是USBCAN分  析仪吧   
发表于 2022-10-5 16:47 | 显示全部楼层
怎么自动识别波特率呢?                 
发表于 2022-10-5 17:10 | 显示全部楼层
这个方案做的不错,学习一下。   
发表于 2022-10-5 17:39 | 显示全部楼层
厂家一般不会提供PYTHON的例子吧   
发表于 2022-10-5 18:16 | 显示全部楼层
python-can库为Python提供了控制器局域网的支持,为不同的硬件设备提供了通用的抽象  
发表于 2022-10-5 18:59 | 显示全部楼层
如何用usb/can总线适配器解析CAN协议呢?
发表于 2022-10-5 20:38 | 显示全部楼层
应该不会给提供吧
您需要登录后才可以回帖 登录 | 注册

本版积分规则

61

主题

928

帖子

5

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