【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,不过引脚是兼容的。
GD32F4系列的CAN1引脚和USB ULPI是共用的,所以USB用外部phy的时候,不要初始化CAN1的引脚,只能用一组CAN。
初始化一些参数。这里要注意,F427的总线时钟频率是50M,所以这里的波特率计算方式是50M/((sjw+bs1+bs2)*prescaler),采样率建议选稍微大一点。
配置滤波器和中断,这里选择全部都收,滤波器设置一组不过滤的参数即可。
然后定义中断接收函数。
这里进了中断就必须要用can\_message\_receive把数据收掉,不然会重复进中断。
这里笔者用了一个兵乓缓冲实现RTOS里面中断接收数据。具体实现就不说了,网上大把。
然后写个demo验证一下。
另外硬件上面要接一个收发器。由于板上没有5V的引脚,因此要选个支持3V3的,建议用TI的SN65HVD230。
这里可以评估一下CAN的性能,找个周立功的CAN盒,对着它以最快速度发送。
然后可以在调试模式下看接收了多少条
可以看到周立功的CAN盒发送的条数和开发板接收到的条数一样,可以说明CAN性能足够。
发送就比较简单,因为多数情况下发送的频率不会很高,因此也不用评估它的性能了。
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的数据。
然后在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/下。编译的时候,打开下面的选项
然后可以得到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波特率,对应关系如下图
然后就可以使用Linux下面标准的socketcan来进行can数据收发了。cansend是数据发送,candump是数据接收,效果如下图。
7.试用总结
最后总结一下本次试用。本次试用在GD32的库基础上,首先移植了freertos,之后在freertos下调通了USB功能和CAN功能,并在官方的CDC-ACM例程基础上开发了双CDC-ACM复合设备,最后移植了slcan,实现USB-CAN的功能。不足之处有两个,一是开发板并没有把不加外部PHY的USBHS引出来,USB ULPI接口和CAN1冲突,最终只能实现一路CAN的收发。另外一个是Linux下的USB ACM驱动并不能支持复合设备,后面可能需要使用其他的驱动来支持复合设备,比如option驱动。