目录
1、CH32V307介绍:
CH32V307是南京沁恒出的RISC_V架构的MCU,这个网址为产品介绍互联型RISC-V单片机 CH32V307 - 南京沁恒微电子股份有限公司,选择它的主要原因是144Mhz主频,RISC_V架构比较便宜,自带480Mphy,支持USB2.0,480M的速度,相对于STM32F407不需要额外接PHY,带两个CAN,而且CAN和USBHS可以直接共同使用,无需额外设置。之前基于GD32VF103制作canable,发现GD32VF103的USBFS和CAN无法同时使用,然后就放弃了。
2、canable介绍
以下为canable官网An Open-Source USB to CAN Adapter - CANable,canable是开源的usb转CAN的工具,使用的MCU是STM32F0系列,由于STM32F0主频低,只支持USBFS,而且只有一路CAN无法满足客户需求。GitHub - normaldotcom/canable-fw: Firmware for the CANable USB to CAN adapter 这是CANABLE的开源源码,下面就基于该源码制作canable。
3、MounRiver Studio使用
在官网下载该IDE,和STM32CUBEIDE一样都是基于eclipse制作的,下载CH32V307例程,打开官方USBHS cdc虚拟串口程序。
打开wvproj文件自动导入到工作区,也可以直接在IDE里面新建工作区,然后把相关代码复制进去。
开发板使用的是官方开发板,开发板自带调试器,之前网上买了一个黑色PCB的WCHLink无法正常使用,弄了好几天结果发现调试器的问题,建议淘宝沁恒旗舰店买调试器,该开发板在淘宝旗舰店好像是70多。
4、代码调试
先调通USBHS,然后根据USBHS得到的字符串解析要发送的CAN报文,然后通过CAN发送出去,由于是调试阶段先使用一路CAN。
首先在ch32v30x_usbhs_device.h文件中定义结构体,NUM_RX_BUFS是缓存USB接收的数量,这里定义的是18,官方定义的是6,之前高速发送的时候出问题以为缓存数量太小了,结果发现是使用printf放慢了节奏影响了效率。buf是用于接收usb数据,msglen是接收数据的大小,head是接收数据的高度,tail是使用数据的高度。
#define NUM_RX_BUFS 18 // Number of RX buffers in FIFO
typedef struct _usbrx_buf_{// Receive buffering: circular buffer FIFO
__attribute__ ((aligned(4))) uint8_t buf[NUM_RX_BUFS][64];
uint32_t msglen[NUM_RX_BUFS];
uint8_t head;
uint8_t tail;
} usbrx_buf_t;
在ch32v30x_usbhs_device.c中定义static usbrx_buf_t rxbuf = {0};
void USBHS_Device_Endp_Init ( void )
{
uint8_t i;
USBHSD->ENDP_CONFIG = USBHS_UEP3_T_EN | USBHS_UEP3_R_EN |
USBHS_UEP2_T_EN | USBHS_UEP2_R_EN;
USBHSD->UEP0_MAX_LEN = DEF_USBD_UEP0_SIZE;
USBHSD->UEP2_MAX_LEN = DEF_USB_EP3_HS_SIZE;
USBHSD->UEP3_MAX_LEN = DEF_USB_EP3_HS_SIZE;
USBHSD->UEP0_DMA = (uint32_t)(uint8_t *)USBHS_EP0_Buf;
USBHSD->UEP3_RX_DMA = (uint32_t)(uint8_t *)USBHS_EP3_Tx_Buf;
// __attribute__ ((aligned(4))) uint8_t UART1_Tx_Buf[ 2048 ];
USBHSD->UEP2_RX_DMA = (uint32_t)(uint8_t *)&rxbuf.buf[rxbuf.head]; //初始化更改为rxbuf的地址
USBHSD->UEP2_TX_DMA = (uint32_t)(uint8_t *)USBHS_EP2_Tx_Buf;
在初始化UEP2_RX_DMA(UEP2端点接收地址)时把地址绑定到&rxbuf.buf[rxbuf.head],这一步可做可不做,在接收数据发生时会再进行赋值。
case USBHS_UIS_TOKEN_OUT | DEF_UEP2: //收集数据/* Endp download */
USBHSD->UEP2_RX_CTRL ^= USBHS_UEP_R_TOG_DATA1;
/* DMA address */
// Uart.Tx_PackLen[ Uart.Tx_LoadNum ] = USBHSD->RX_LEN;
int len=USBHSD->RX_LEN;
// printf("len is%d \r\n",len);
// Uart.Tx_LoadNum++;
if( ((rxbuf.head + 1) % NUM_RX_BUFS) == rxbuf.tail){//接收高度和使用高度一样,数据还没来得及使用,输出错误,但数据依然接收
printf("err\n");
USBHSD->UEP2_RX_DMA = (uint32_t)(uint8_t *)&rxbuf.buf[rxbuf.head];}
else{
rxbuf.msglen[rxbuf.head] =len;
rxbuf.head = (rxbuf.head + 1) % NUM_RX_BUFS; //接收数据高度加一
USBHSD->UEP2_RX_DMA = (uint32_t)(uint8_t *)&rxbuf.buf[rxbuf.head];}
下一步为数据使用:
void cdc_process(void)
{
if(rxbuf.tail != rxbuf.head) //高度不一致进行数据处理
{
// Process one whole buffer
for (uint32_t i = 0; i < rxbuf.msglen[rxbuf.tail]; i++)
{
if (rxbuf.buf[rxbuf.tail][i] == '\r') //\r为结束符
{
slcan_str[slcan_str_index]=0;
// printf("slcan_str is %s\n",slcan_str);
slcan_parse_str(slcan_str, slcan_str_index);//数据处理
// int8_t result = slcan_parse_str(slcan_str, slcan_str_index);
// Success
//if(result == 0)
// CDC_Transmit_FS("\n", 1);
// Failure
//else
// CDC_Transmit_FS("\a", 1);
slcan_str_index = 0;
}
else
{
// Check for overflow of buffer
if(slcan_str_index >= SLCAN_MTU)
{
// TODO: Return here and discard this CDC buffer?
slcan_str_index = 0;
}
slcan_str[slcan_str_index++] = rxbuf.buf[rxbuf.tail][i];
}
}
// Move on to next buffer
rxbuf.tail = (rxbuf.tail + 1) % NUM_RX_BUFS;//接收高度加一
}
}
在main函数中的while循环中一直调用cdc_process。cdc_process一直检测有没有新的数据可以使用,usb那边有新的数据会装到缓存里面去。
然后编辑slcan_parse_str函数,将接收的字符串进行处理然后发送CAN报文。
先创建can.c和can.h。在can.h中定义发送数据结构体
#define TXQUEUE_LEN 28 // Number of buffers allocated
#define TXQUEUE_DATALEN 8 // CAN DLC length of data buffers
typedef struct cantxbuf_
{
uint8_t data[TXQUEUE_LEN][TXQUEUE_DATALEN]; // Data buffer
CanTxMsg header[TXQUEUE_LEN]; // Header buffer
uint8_t head; // Head pointer
uint8_t tail; // Tail pointer
uint8_t full; // TODO: Set this when we are full, clear when the tail moves one.
} can_txbuf_t;
在can.c中添加两个函数
static can_txbuf_t txqueue = {0};
uint8_t can_tx(CanTxMsg *tx_msg_header, uint8_t* tx_msg_data)
{
// Check if space available in the buffer (FIXME: wastes 1 item)
if( ((txqueue.head + 1) % TXQUEUE_LEN) == txqueue.tail)
{
printf("full buff_can_tx\n");
return 0;
}
// Copy header struct into array
txqueue.header[txqueue.head] = *tx_msg_header;
// Copy data into array
for(uint8_t i=0; i<tx_msg_header->DLC; i++)
{
txqueue.data[txqueue.head][i] = tx_msg_data[i];
}
// Increment the head pointer
txqueue.head = (txqueue.head + 1) % TXQUEUE_LEN;
return 1;//将要发送的数据放到txqueue中
}
void can_process(void) //检查有没有需要发送的数据,将要发送的数据放到can_send
{
if( (txqueue.tail != txqueue.head)&&(can_getfree_txmail(CAN1)==1) )//&& (can_getfree_txmail(CAN1)==1)
{
// Transmit can frame
uint8_t status = can_send(&txqueue.header[txqueue.tail], txqueue.data[txqueue.tail]);
txqueue.tail = (txqueue.tail + 1) % TXQUEUE_LEN;
// led_green_on();
// This drops the packet if it fails (no retry). Failure is unlikely
// since we check if there is a TX mailbox free.
if(status == 1){
printf("err can_tx\n");}
}
}
//填下以下can网络初始化函数
void CAN_Mode_Init( u8 tsjw, u8 tbs2, u8 tbs1, u16 brp, u8 mode )
{
GPIO_InitTypeDef GPIO_InitSturcture={0};
CAN_InitTypeDef CAN_InitSturcture={0};
CAN_FilterInitTypeDef CAN_FilterInitSturcture={0};
RCC_APB2PeriphClockCmd( RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB, ENABLE );
RCC_APB1PeriphClockCmd( RCC_APB1Periph_CAN1, ENABLE );
GPIO_PinRemapConfig( GPIO_Remap1_CAN1, ENABLE);
GPIO_InitSturcture.GPIO_Pin = GPIO_Pin_9;
GPIO_InitSturcture.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitSturcture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOB, &GPIO_InitSturcture);
GPIO_InitSturcture.GPIO_Pin = GPIO_Pin_8;
GPIO_InitSturcture.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init( GPIOB, &GPIO_InitSturcture);
CAN_InitSturcture.CAN_TTCM = DISABLE;
CAN_InitSturcture.CAN_ABOM = DISABLE;
CAN_InitSturcture.CAN_AWUM = DISABLE;
CAN_InitSturcture.CAN_NART = ENABLE;
CAN_InitSturcture.CAN_RFLM = DISABLE;
CAN_InitSturcture.CAN_TXFP = DISABLE;
CAN_InitSturcture.CAN_Mode = mode;
CAN_InitSturcture.CAN_SJW = tsjw;
CAN_InitSturcture.CAN_BS1 = tbs1;
CAN_InitSturcture.CAN_BS2 = tbs2;
CAN_InitSturcture.CAN_Prescaler = brp;
CAN_Init( CAN1, &CAN_InitSturcture );
CAN_FilterInitSturcture.CAN_FilterNumber = 0;
CAN_FilterInitSturcture.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitSturcture.CAN_FilterScale = CAN_FilterScale_32bit;
CAN_FilterInitSturcture.CAN_FilterIdHigh = 0;
CAN_FilterInitSturcture.CAN_FilterIdLow = 0;
CAN_FilterInitSturcture.CAN_FilterMaskIdHigh = 0;//设置为不过滤
CAN_FilterInitSturcture.CAN_FilterMaskIdLow = 0;
CAN_FilterInitSturcture.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
CAN_FilterInitSturcture.CAN_FilterActivation = ENABLE;
CAN_FilterInit( &CAN_FilterInitSturcture );
}
void can1_init(void){
CAN_Mode_Init( CAN_SJW_1tq, CAN_BS2_5tq, CAN_BS1_6tq, 12, CAN_Mode_Normal ); //144MHZ系统时钟时 波特率为500k 144/(12*12)*2
}
u8 can_send(CanTxMsg *CanTxStructure, u8 *msg)
{
u8 mbox;
u16 i = 0;
// CanTxStructure.StdId = 0x317;
// CanTxStructure.IDE = CAN_Id_Standard;
// CanTxStructure.RTR = CAN_RTR_Data;
// CanTxStructure.DLC = 8;
for( i=0; i<8; i++ )
{
CanTxStructure->Data[i] = msg[i];
}
mbox = CAN_Transmit( CAN1, CanTxStructure);
i = 0;
while( ( CAN_TransmitStatus( CAN1, mbox ) != CAN_TxStatus_Ok ) && (i < 0xFFF) )
{
i++;
}
if( i == 0xFFF ){
return 1;}
else {
return 0;
}
}
然后编写can_getfree_txmail判断发送邮箱是否为空
uint8_t can_getfree_txmail(CAN_TypeDef *CANx){
if( (CANx->TSTATR&((uint32_t)0x1c000000)) > 0){ //有邮箱为空的
return 1;
}else{
printf("no mail box\r\n");
printf("tstatr is %#x\r\n",CANx->TSTATR);
return 0;
}
}
can的TSTATR为发送状态寄存器,第28和27 26位为邮箱123的时候为空的标致为,如果三个全为0表示邮箱全满无法再发送CAN报文。正常状态下寄存器的值为0x1c000000
然后再编辑字符串解析函数:
//slcan_parse_str(12380102030405060708)//123 ID 8 DLC
// Parse an incoming slcan command from the USB CDC port
int8_t slcan_parse_str(uint8_t *buf, uint8_t len)
{
CanTxMsg frame_header;
// Default to standard ID unless otherwise specified
frame_header.IDE = CAN_Id_Standard;
frame_header.StdId = 0;
frame_header.ExtId = 0;
// printf("BUF IS %s\n",buf);
// printf("chang du is %d\n",len);
// Convert from ASCII (2nd character to end)只针对buf[1]以后的进行转码
for (uint8_t i = 1; i < len; i++)
{
// Lowercase letters
if(buf[i] >= 'a')
buf[i] = buf[i] - 'a' + 10;
// Uppercase letters
else if(buf[i] >= 'A')
buf[i] = buf[i] - 'A' + 10;
// Numbers
else
buf[i] = buf[i] - '0';
}
// Process command
switch(buf[0]) //判断buf[0]的值
{
case 'O':
// Open channel command
printf("canable\n");
return 0;
case 'C':
// Close channel command
// can_disable();
printf("cana_close\n");
return 0;
case 'S':
// Set bitrate command
// Check for valid bitrate
// if(buf[1] >= CAN_BITRATE_INVALID)
// {
// return -1;
// }
// can_set_bitrate(buf[1]);
printf("cana_setbrate\n");
return 0;
case 'm':
case 'M':
// Set mode command
// if (buf[1] == 1) //没啥用的全注释掉
// {
// // Mode 1: silent
// can_set_silent(1);
// printf("canable\n");
// } else {
// // Default to normal mode
// can_set_silent(0);
printf("M\n");
// }
return 0;
case 'a':
case 'A':
// Set autoretry command
// if (buf[1] == 1)
// {
// // Mode 1: autoretry enabled (default)
// can_set_autoretransmit(1);
// } else {
// // Mode 0: autoretry disabled
// can_set_autoretransmit(0);
// }
printf("A\n");
return 0;
case 'V':
{
// Report firmware version and remote
// char* fw_id = GIT_VERSION " " GIT_REMOTE "\r";
// CDC_Transmit_FS((uint8_t*)fw_id, strlen(fw_id));
printf("Version\n");
return 0;
}
case 'T':
frame_header.IDE = CAN_Id_Extended;
break;
case 't':
// Transmit data frame command
frame_header.RTR = CAN_RTR_Data;
break;
case 'R':
frame_header.IDE = CAN_Id_Extended;
break;
case 'r':
// Transmit remote frame command
frame_header.RTR = CAN_RTR_Remote;
break;
default:
// Error, unknown command
return -1;
}
// Save CAN ID depending on ID type
uint8_t msg_position = 1; //当前位置为1
if (frame_header.IDE == CAN_ID_EXT) {
while (msg_position <= 8) {
frame_header.ExtId *= 16; //字节高位左移一位
frame_header.ExtId += buf[msg_position++];
}
}
else {
while (msg_position <= 3) {
frame_header.StdId *= 16;
frame_header.StdId += buf[msg_position++];
}
}
// Attempt to parse DLC and check sanity
frame_header.DLC = buf[msg_position++];
if (frame_header.DLC > 8) {
return -1;
}
// Copy frame data to buffer
uint8_t frame_data[8] = {0};
for (uint8_t j = 0; j < frame_header.DLC; j++) {
frame_data[j] = (buf[msg_position] << 4) + buf[msg_position+1];//这里要将两个数据转化成一个字节 高位数据左移4位当一个字节的高4位,下一个数据成为一个字节的低4位
msg_position += 2;
}
// Transmit the message
can_tx(&frame_header, frame_data); //将转换后的数据发送出去
return 0;
}
在主函数的while循环中调用cdc_process();can_process();就可以实现串口转CAN,后面添加CAN转串口功能,在主函数添加以下代码:
uint8_t rx_msg_data[8] = {0};
CanRxMsg CanRxStructure;
uint8_t msg_buf[SLCAN_MTU];
while(1)
{
cdc_process();
can_process();
if(CAN_MessagePending( CAN1, CAN_FIFO0 ) == 1)
{
// If message received from bus, parse the frame判断can网络是否有数据
CAN_Receive( CAN1, CAN_FIFO0, &CanRxStructure );
for( uint8_t i=0; i<CanRxStructure.DLC; i++ ){
rx_msg_data[i] = CanRxStructure.Data[i];
}
uint16_t msg_len = slcan_parse_frame((uint8_t *)&msg_buf,&CanRxStructure, rx_msg_data);
// Transmit message via USB-CDC
if(msg_len){//向串口发送数据
USBHS_Endp_DataUp( DEF_UEP2, &msg_buf[0], msg_len, DEF_UEP_DMA_LOAD );
}
}
}
//然后编辑将CAN数据转化成字符串的函数
uint8_t slcan_parse_frame(uint8_t *buf, CanRxMsg *frame_header, uint8_t* frame_data)
{
uint8_t msg_position = 0;
for (uint8_t j=0; j < SLCAN_MTU; j++){
buf[j] = '\0';}
// Add character for frame type
if (frame_header->RTR == CAN_RTR_DATA)
{
buf[msg_position] = 't';
} else if (frame_header->RTR == CAN_RTR_REMOTE) {
buf[msg_position] = 'r';
}
// Assume standard identifier
uint8_t id_len = 3;
uint32_t can_id = frame_header->StdId;
// Check if extended
if (frame_header->IDE == CAN_ID_EXT){// Convert first char to upper case for extended frame
buf[msg_position] -= 32;
id_len = 8;
can_id = frame_header->ExtId;
}
msg_position++;
// Add identifier to buffer
for(uint8_t j = id_len; j > 0; j--){// Add nybble to buffer
buf[j] = (can_id & 0xF);
can_id = can_id >> 4;
msg_position++;
}
// Add DLC to buffer
buf[msg_position++] = frame_header->DLC;
// Add data bytes
for (uint8_t j = 0; j < frame_header->DLC; j++){
buf[msg_position++] = (frame_data[j] >> 4);//获得第一个字节的前4位
buf[msg_position++] = (frame_data[j] & 0x0F);//获得第一个字节的低4位
}
// Convert to ASCII (2nd character to end)
for (uint8_t j = 1; j < msg_position; j++){
if (buf[j] < 0xA) {
buf[j] += 0x30;
} else {
buf[j] += 0x37;
}
}
buf[msg_position++] = '\r';
return msg_position;
}
编译并下载进去后然后可以测试了,测试中使用了大越创新的TTL转CAN,TJA1050也可以。使用CANprotocol,发送设置如下,间隔0秒发送100次。(代码调试结束后官方板子太大了换了一个小板)
由于是虚拟串口,串口助手波特率选择多少不影响,接收一次是数量为22 接收100次接收数量正好为2200,检查串口接收的数据和CAN报文能对的上,说明接收速度
然后测试发送数据,循环周期设置为最小,最小为1毫秒。
CANprotocol报文是从0开始计数的,所以接收了1783个报文,串口发送39226个数据,除以22正好是1783。所以串口间隔1ms发送不会丢帧,也没有错误帧,由于该串口工具发送最小单位为1ms
所以无法再进一步提高速度,CANprotocaol间隔0ms 需要19ms发送完成100帧,500K波特率的极限了,所以极限接收CAN报文问题不大。由于windows最小间隔很难搞到1ms以下,所以无法进行1ms以下的极限测试,所以直接不加延迟进行测试:
使用python无延迟发100个数据,CAN网络上能发出57帧数据,感觉速度表现还算可以吧,毕竟USB2.0的速度要比500K的CAN快一大截。
然后使用CANTACT(canable用的软件)也是没有问题的。
感谢大家的阅读~~
这个东西也是历经好几天弄出来的,有啥问题都是边查资料边学习,上手CH32V307后感觉官方例程结合寄存器手册有一定基础的话用起来还是比较方便,没有基础的话没有STM32的HAL库上手快,毕竟STM32全是教程。