chenjie · 2022年12月21日 · 广东

【GD32F427开发板试用】三、USB转CAN功能开发与试用总结

【GD32F427开发板试用】一、环境搭建与freertos移植
【GD32F427开发板试用】二、USB库移植与双USB CDC-ACM功能开发
【GD32F427开发板试用】三、USB转CAN功能开发与试用总结

5.CAN功能开发

GD32库的CAN部分函数跟以前ST标准库和现在ST LL库的类似,所以用ST标准库的人应该会比较熟悉。

它的demo程序在GD32F4xx\_Firmware\_Library\Examples\CAN里面,有三个demo,都差不多,直接借鉴它初始化的部分。虽然它example里面用的是F450,不过引脚是兼容的。
image.png

GD32F4系列的CAN1引脚和USB ULPI是共用的,所以USB用外部phy的时候,不要初始化CAN1的引脚,只能用一组CAN。
image.png

初始化一些参数。这里要注意,F427的总线时钟频率是50M,所以这里的波特率计算方式是50M/((sjw+bs1+bs2)*prescaler),采样率建议选稍微大一点。
image.png

配置滤波器和中断,这里选择全部都收,滤波器设置一组不过滤的参数即可。

然后定义中断接收函数。

image.png
image.png

这里进了中断就必须要用can\_message\_receive把数据收掉,不然会重复进中断。

这里笔者用了一个兵乓缓冲实现RTOS里面中断接收数据。具体实现就不说了,网上大把。
 
然后写个demo验证一下。
image.png

另外硬件上面要接一个收发器。由于板上没有5V的引脚,因此要选个支持3V3的,建议用TI的SN65HVD230。

这里可以评估一下CAN的性能,找个周立功的CAN盒,对着它以最快速度发送。
image.png

然后可以在调试模式下看接收了多少条
image.png

可以看到周立功的CAN盒发送的条数和开发板接收到的条数一样,可以说明CAN性能足够。

发送就比较简单,因为多数情况下发送的频率不会很高,因此也不用评估它的性能了。
image.png

 

6.USB-CAN 功能开发

最后一篇文章就是对本次试用的最终目标进行开发。USB-CAN,这种设备目前市面上很多,但是开源的不多,能在linux下使用的就更少了。这里笔者使用的是slcan,这个在Linux是有现成驱动的。笔者的目标是用一个USB CDC-ACM复合设备的两个ACM口,对应两路CAN,这样就可以配合上位机,实现类似周立功CAN盒两路CAN的效果。不过有一个点,如果用了USBHS的ULPI接口,那么CAN1就不可用了,而USBFS的endpoint不够,不足以支持两个USB CDC-ACM。因此要想真正实现这种效果,唯一的方法是用USBHS,不加外部PHY,开发板上面不支持这种接口,后续如果自己打板可以这样干。另外Linux下的CDC-ACM驱动不能支持复合设备。

 

首先要找一个工程模板来移植。canable是一个开源项目,里面就有slcan的支持,他们用的芯片是STM32F072,ST的USB库和GD的差不多,因此移植起来也简单。此工程源码在https://github.com/normaldotcom/canable-fw下。这里只需要用到它的slcan.c 和 slcan.h两个文件。把这两个文件复制到工程目录下,并添加到工程里面。

然后要修改一下头文件和函数的定义。其中slcan\_parse\_frame函数是把can接收到的数据组包为符合slcan协议的数据包,要把原本st库里面的CAN结构体定义改为GD的,另外要添加一个变量,用于标识是哪一路USB的数据/是哪一路CAN的数据。
image.png

然后在freertos下面添加两个线程,内容如下

#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"

can_receive_message_struct can0_rxmsg[50] = {0};
can_receive_message_struct can1_rxmsg[50] = {0};
int can0_rxlen = 0;
int can1_rxlen = 0;
unsigned char usb_tx_string_buf[SLCAN_MTU];
unsigned char usb_tx_string_len;

unsigned char cdc_acm_0_rx_data[64 + 1];
int cdc_acm_0_rx_len;
unsigned char cdc_acm_1_rx_data[64 + 1];
int cdc_acm_1_rx_len;

extern int get_usb_cur_state(void);

void can2usb_func(void *pvParameters)
{
    int i = 0;
    vTaskDelay(50);
    while (1) {
//        vTaskDelay(1);
        if(i++ >= 50) {
            vTaskDelay(1);
            i = 0;
        }
        
        can0_rxlen = can_recv_frames(0, can0_rxmsg, 50);
        if (can0_rxlen > 0) {
            // 处理
            for (int i = 0; i < can0_rxlen; i++) {
                usb_tx_string_len += slcan_parse_frame(0, usb_tx_string_buf + usb_tx_string_len, can0_rxmsg + i);
                if(i % 2 == 1 || i == can0_rxlen - 1) {
                    if (get_usb_cur_state() == 0) {
                        dual_cdc_acm_data_send(0, usb_tx_string_buf, usb_tx_string_len);
                    }
                    memset(usb_tx_string_buf, 0, sizeof(usb_tx_string_buf));
                    usb_tx_string_len = 0;
                }
            }
        }

        can1_rxlen = can_recv_frames(1, can1_rxmsg, 50);
        if (can1_rxlen > 0) {
            // 处理
            for (int i = 0; i < can1_rxlen; i++) {
                memset(usb_tx_string_buf, 0, sizeof(usb_tx_string_buf));
                usb_tx_string_len = slcan_parse_frame(1, usb_tx_string_buf, can1_rxmsg + i);
                if (get_usb_cur_state() == 0) {
                    dual_cdc_acm_data_send(1, usb_tx_string_buf, usb_tx_string_len);
                }
            }
        }
    }
}

void usb2can_func(void *pvParameters)
{
    vTaskDelay(20);
    while (1) {
        vTaskDelay(1);
        if (get_usb_cur_state() == 0) {
            memset(cdc_acm_0_rx_data,0,sizeof(cdc_acm_0_rx_data));
            cdc_acm_0_rx_len = dual_cdc_acm_data_receive(0, cdc_acm_0_rx_data, sizeof(cdc_acm_0_rx_data));
            if (cdc_acm_0_rx_len > 0) {
                slcan_parse_str(0, cdc_acm_0_rx_data, cdc_acm_0_rx_len);
            }
            
            memset(cdc_acm_1_rx_data,0,sizeof(cdc_acm_1_rx_data));
            cdc_acm_1_rx_len = dual_cdc_acm_data_receive(1, cdc_acm_1_rx_data, sizeof(cdc_acm_1_rx_data));
            if (cdc_acm_1_rx_len > 0) {
                slcan_parse_str(1, cdc_acm_1_rx_data, cdc_acm_1_rx_len);
            }
        }
    }
}

然后编译,下载到开发板。

 

Linux端,其源码在drivers/net/can/下。编译的时候,打开下面的选项
image.png

然后可以得到slcan.ko。另外还需要can-utils工具,编译之后,得到slcand,cansend,candump几个工具。

将开发板的USBHS插到Linux设备上,会出现/dev/ttyACM0设备节点。此时使用以下命令来开启Linux socketcan功能

insmod slcan.ko  
  
./slcand -o -c -s5 /dev/ttyACM0 slcan0  
  
ifconfig slcan up  
  
ifconfig slcan txqueuelen 1000 

其中第一个命令中,-s5意思是指定250k波特率,对应关系如下图
image.png

然后就可以使用Linux下面标准的socketcan来进行can数据收发了。cansend是数据发送,candump是数据接收,效果如下图。
image.png

image.png

 

7.试用总结

最后总结一下本次试用。本次试用在GD32的库基础上,首先移植了freertos,之后在freertos下调通了USB功能和CAN功能,并在官方的CDC-ACM例程基础上开发了双CDC-ACM复合设备,最后移植了slcan,实现USB-CAN的功能。不足之处有两个,一是开发板并没有把不加外部PHY的USBHS引出来,USB ULPI接口和CAN1冲突,最终只能实现一路CAN的收发。另外一个是Linux下的USB ACM驱动并不能支持复合设备,后面可能需要使用其他的驱动来支持复合设备,比如option驱动。

推荐阅读
关注数
10697
内容数
187
中国高性能通用微控制器领域的领跑者兆易创新GD系列芯片技术专栏。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息