基于MCU MM32G5330的FlexCAN实现CANopenNode协议栈移植
引言
在现代工业自动化和汽车电子领域,CAN总线以其高可靠性和实时性成为通信的主流选择。而CANopen协议,作为CAN总线上的一种上层通信协议,广泛应用于各种设备间的通信。本文将介绍如何基于灵动MM32G5330的FlexCAN实现CANopenNode协议栈的移植,并使用灵动官方提供的开发板Mini-G5333进行验证。
CANopen简介
CANopen是由CiA (CAN-in-Automation)组织开发的上层通信协议,它定义了一组用于工业自动化的通信对象,并在CAN总线之上实现了网络管理、设备配置和数据交换等功能。CANopen协议规范了设备如何通过CAN总线进行通信,使得不同厂商的设备能够无缝集成和协同工作。
CANopen从应用端到CAN总线的结构:
应用层(Application)
用于实现各种应用对象
对象字典(Object dictionary)
用于描述CANopen节点设备的参数
通信接口(Communication interface)
定义了CANopen协议通信规则以及CAN控制器驱动之间对应关系
CANopen网络中用到的三种通信模型:
主机/从机模型(Master/Salve)
一个节点(例如控制接口)充当应用程序主机控制器,从机(例如伺服电机)发送/请求数据,一般在诊断或状态管理中使用。
通信样例:NMT主机与NMT从机的通信
所有节点通信地位平等,运行时允许自行发送报文,但CANopen网络为了稳定可靠可控,都需要设置一个网络管理主机 NMT-Master。
NMT主机一般是CANopen网络中具备监控的PLC或者PC(当然也可以是一般的功能节点),所以也成为CANopen主站。相对应的其他CANopen节点就是NMT从机(NMT-slaves)。
客户端/服务端模型(Client/Server)
客户机向服务器发送数据请求,服务器进行响应。例如,当应用程序主机需要来自从机OD的数据时使用。
通信样例:SDO客户端与SDO服务端的通信
发送节点需要指定接收节点的地址(Node-ID)回应CAN报文来确认已经接收,如果超时没有确认,则发送节点将会重新发送原报文。
生产者/消费者模型(Producer/Consumer)
生产者节点向网络广播数据,而网络由使用者节点使用。生产者可以根据请求发送此数据,也可以不发送特定请求。
通信样例:心跳生产者与心跳消费者的通信
单向发送传输,无需接收节点回应CAN报文来确认。
CANopen的七种报文类型:
NMT(Network Management)
控制CANopen设备状态,用于网络管理。
SYNC(Synchronization)
SYNC 消息用于同步多个 CANopen 设备的输入感应和驱动——通常由应用程序 Master 触发。
EMCY(Emergency)
在设备发生错误(例如传感器故障)时使用的,发送设备内部错误代码。
TIME
用于分配网络时间,议采用广播方式,无需节点应答,CAN-ID 为 100h,数据长度为 6,数据为当前时刻与1984年1月1日0时的时间差。节点将此时间存储在对象字典1012h的索引中。
PDO(Process Object)
PDO服务用于在设备之间传输实时数据,例如测量数据(如位置数据)或命令数据(如扭矩请求)。
SDO(Sever D Object)
用于访问/更改CANopen设备的对象字典中的值——例如,当应用程序主机需要更改CANopen设备的某些配置时。
Heartbeat
Heartbeat服务有两个用途: 提供“活动”消息和确认NMT命令。
CANopenNode协议栈
CANopenNode是一款免费和开源的CANopen协议栈,使用ANSI C语言以面向对象的方式编写的。它可以在不同的微控制器上运行,作为独立的应用程序或与RTOS一起运行。变量(通信、设备、自定义)被收集在CANopen对象字典中,并且可以以两种方式修改:C源代码和CANopen网络。
CANopenNode主页位于:https://github.com/CANopenNode/CANopenNode
CANopenNode vs CAN Festival
表1
CANopenNode和CANFestival都是用于在嵌入式系统上实现CANopen协议通信的开源软件协议栈。需要注意的是它们使用了不同的开放程度的开源协议。CANFestival使用LGPLv2开源协议。这意味着CANFestival的源代码虽是免费提供的,任何人都可以使用、修改和分发,只要任何衍生作品使用相同的GPL许可证,但如果一个公司在产品中使用CANFestival,他们也必须按照同样的LGPLv2开源协议提供其产品的源代码。而CANopenNode使用 Apache v2.0开源协议,这是一个自由度比LGPLv2更为开发的一个开源协议,允许在使用软件方面有更大的灵活性。任何人都可以使用、修改和发布CANopenNode,甚至用于商业目的,而不需要发布其衍生作品的源代码。
移植前准备
获取CANopenNode源码
选择 CANopenNode v1.3,该版本为CANopenNode 官方发布版本,获取源码链接:https://github.com/CANopenNode/CANopenNode/releases/tag/v1.3。
获取 MiniBoard-OB (MM32G5333D6QV) 例程及开发板资料
开发板及LibSamples详情见灵动官网:https://www.MindMotion.com.cn/support/development_tools/evaluation_boards/miniboard/mm32g5330d6qv/。
编译工具和开发环境
使用基于 Keil MDK-ARM 创建工程。
基于FlexCAN移植CANopenNode
在CANopenNode移植中涉及到三个文件需要被复制引用和修改:
CANopenNode-1.3/example/main.c 文件。
CANopenNode-1.3/stack/drvTemplate/CO_driver.c 文件。
CANopenNode-1.3/stack/drvTemplate/CO_driver_target.h 文件。
其中:
在 mian.c 文件中实现 tmrTask_thread() 函数
通加载进入1ms 定时中断服务函数进行 1ms 定时的信息同步
在 CO_driver.c 文件中实现 CO_CANmodule_init() 函数
用于对 MCU 中的 CAN 模块进行初始,并配置CAN报文的收发参数以及开启 flexcan 中断。
在 CO_driver.C 文件中实现 CO_CANinterrupt() 函数
用于实现接收和发送CAN信息。该功能从高优先级的CAN中断中直接调用。
在 CO_driver.C 文件中实现 CO_CANverifyErrorst() 函数
用于对 CAN 总线进行错误检测和上报。
下面我们将以MM32G5330微控制器上集成的FlexCAN为例,完成对CANopenNode v1.3的移植,并实现一个 CANopen_Basic 样例进行基本功能验证。
首先在灵动官网下载基于Mini-G5330开发板的LibSamples_MM32G5330软件包,并在该软件包的根目录文件夹下创建 ~/3rdPartySoftwarePorting/CANopenNode 文件夹,如下图1所示,将获取的 CANopenNode-1.3 软件包解压后原封不动地复制到新建的 CANopenNode 文件夹中。
图 1
这里我们在 CANopenNode 文件夹下创建 Demos 文件夹用于按照LibSamples的样例结构创建关于 CANopenNode 相关的样例工程。接下来将CANopenNode源码中提供的example文件夹的结构如下图2所示,其中CO_OD.c/h是 CANopen中使用到的对象字典, 我们将这两个文件复制到 Demos/CANopen_Basic 文件夹下。main.c是 CANopenNode的主程序文件,我们将原有的main.c文件进行替换。
图 2
将如图3所示的位于CANopenNode-1.3/stack/drvTemplate文件夹下的CO_driver.c及CO_driver_target.h这两个文件复制到样例工程的文件夹下。
图 3
在CANopen_Basic文件夹下参照LibSample中的样例工程创建MDK-ARM样例工程并添加编译路径,CANopen_Basic样例完成移植后效果如下图所示:
图 4
由于本次移植是基于裸机移植,故按照CANopenNode的设计将Mainline线程放入while(1)中,CAN接收线程放入flexcan的中断服务程序中,定时线程放在一个1ms的定时中断服务程序中。
在 main.c 文件中配置定时器
这里初始化和配置了定时器 TIM1,并实现了与之相关的中断处理程序。
/* Setup the timer. */
void app_tim_init(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
RCC_APB2PeriphClockCmd(RCC_APB2ENR_TIM1, ENABLE);
TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
TIM_TimeBaseInitStruct.TIM_Prescaler = (RCC_Clocks.PCLK2_Frequency / APP_TIM_UPDATE_STEP - 1);
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_COUNTERMODE_UP;
TIM_TimeBaseInitStruct.TIM_Period = (APP_TIM_UPDATE_PERIOD - 1);
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);
TIM_ClearFlag(TIM1, TIM_IT_UPDATE);
TIM_ITConfig(TIM1, TIM_IT_UPDATE, ENABLE);
NVIC_InitStruct.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void TIM1_UP_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_UPDATE);
tmrTask_thread();
}
在 main.c 文件中实现定时线程任务处理
这里对 tmrTask_thread() 函数进行完善。
/* timer thread executes in constant intervals ********************************/
void tmrTask_thread(void){
INCREMENT_1MS(CO_timer1ms);
if (CO->CANmodule[0]->CANnormal) {
bool_t syncWas;
/* Process Sync */
syncWas = CO_process_SYNC(CO, TMR_TASK_INTERVAL);
/* Read inputs */
CO_process_RPDO(CO, syncWas);
/* Further I/O or nonblocking application code may go here. */
/* Write outputs */
CO_process_TPDO(CO, syncWas, TMR_TASK_INTERVAL);
/* verify timer overflow */
if((TIM_GetITStatus(TIM1, TIM_IT_UPDATE) & TIM_IT_UPDATE) != 0u) {
CO_errorReport(CO->em, CO_EM_ISR_TIMER_OVERFLOW, CO_EMC_SOFTWARE_INTERNAL, 0u);
TIM_ClearITPendingBit(TIM1, TIM_IT_UPDATE);
}
}
}
在 main.c 文件中实现 FlexCAN 的中断服务函数
/* CAN interrupt function *****************************************************/
void FLEXCAN_IRQHandler(void)
{
FLEXCAN_TransferHandleIRQ(FLEXCAN, &FlexCAN_Handle);
CO_CANinterrupt(CO->CANmodule[0]);
__DSB();
}
在 CO_driver.c 文件中实现FlexCAN模块配置
实现包括对 FlexCAN 相关的 GPIO引脚、时钟、CAN报文收发消息缓冲区的配置。
void FlexCAN_Configure(uint32_t can_bitrate)
{
GPIO_InitTypeDef GPIO_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
RCC_ClocksTypeDef RCC_Clocks;
flexcan_config_t FlexCAN_ConfigStruct;
flexcan_rx_mb_config_t FlexCAN_RxMB_ConfigStruct;
RCC_GetClocksFreq(&RCC_Clocks);
RCC_APB1PeriphClockCmd(RCC_APB1PERIPH_FLEXCAN, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_GPIOA, ENABLE);
GPIO_PinAFConfig(GPIOA, GPIO_PINSOURCE11, GPIO_AF_9);
GPIO_PinAFConfig(GPIOA, GPIO_PINSOURCE12, GPIO_AF_9);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_PIN_11;
GPIO_InitStruct.GPIO_Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.GPIO_Mode = GPIO_MODE_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_StructInit(&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = GPIO_PIN_12;
GPIO_InitStruct.GPIO_Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.GPIO_Mode = GPIO_MODE_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = FLEXCAN_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
FLEXCAN_GetDefaultConfig(&FlexCAN_ConfigStruct);
FlexCAN_ConfigStruct.baudRate = can_bitrate*1000;
FlexCAN_ConfigStruct.clkSrc = Enum_Flexcan_ClkSrc1;
FlexCAN_ConfigStruct.enableLoopBack = false;
FlexCAN_ConfigStruct.disableSelfReception = true;
FlexCAN_ConfigStruct.enableIndividMask = true;
#if 1 /* Baudrate calculate by automatically */
FLEXCAN_CalculateImprovedTimingValues(FlexCAN_ConfigStruct.baudRate, RCC_Clocks.PCLK1_Frequency, &FlexCAN_ConfigStruct.timingConfig);
#else /* You can modify the parameters yourself */
FlexCAN_ConfigStruct.timingConfig.preDivider = 23;
FlexCAN_ConfigStruct.timingConfig.propSeg = 6;
FlexCAN_ConfigStruct.timingConfig.phaseSeg1 = 3;
FlexCAN_ConfigStruct.timingConfig.phaseSeg2 = 3;
FlexCAN_ConfigStruct.timingConfig.rJumpwidth = 3;
#endif
FLEXCAN_Init(FLEXCAN, &FlexCAN_ConfigStruct);
/* Set Tx MB_2. */
FLEXCAN_TxMbConfig(FLEXCAN, BOARD_FLEXCAN_TX_MB_CH, ENABLE);
FLEXCAN_TransferCreateHandle(FLEXCAN, &FlexCAN_Handle, FlexCAN_Transfer_Callback, NULL);
/* Set Rx MB_0. */
FlexCAN_RxMB_ConfigStruct.id = FLEXCAN_ID_STD(0x222);
FlexCAN_RxMB_ConfigStruct.format = Enum_Flexcan_FrameFormatStandard;
FlexCAN_RxMB_ConfigStruct.type = Enum_Flexcan_FrameTypeData;
FLEXCAN_RxMbConfig(FLEXCAN, BOARD_FLEXCAN_RX_MB_CH, &FlexCAN_RxMB_ConfigStruct, ENABLE);
/* Set Rx Individual Mask. */
FLEXCAN_SetRxIndividualMask(FLEXCAN, BOARD_FLEXCAN_RX_MB_CH, FLEXCAN_RX_MB_STD_MASK(0x000, 0, 0));
FlexCAN_MB0_FrameStruct.length = (uint8_t)(8);
FlexCAN_MB0_FrameStruct.type = (uint8_t)Enum_Flexcan_FrameTypeData;
FlexCAN_MB0_FrameStruct.format = (uint8_t)Enum_Flexcan_FrameFormatStandard;
FlexCAN_MB0_FrameStruct.id = FLEXCAN_ID_STD(0x222);
FlexCAN_MB0_TransferStruct.mbIdx = BOARD_FLEXCAN_RX_MB_CH;
FlexCAN_MB0_TransferStruct.frame = &FlexCAN_MB0_FrameStruct;
FLEXCAN_TransferReceiveNonBlocking(FLEXCAN, &FlexCAN_Handle, &FlexCAN_MB0_TransferStruct);
}
/******************************************************************************/
CO_ReturnError_t CO_CANmodule_init(
CO_CANmodule_t *CANmodule,
void *CANdriverState,
CO_CANrx_t rxArray[],
uint16_t rxSize,
CO_CANtx_t txArray[],
uint16_t txSize,
uint16_t CANbitRate)
{
uint16_t i;
/* verify arguments */
if(CANmodule==NULL || rxArray==NULL || txArray==NULL){
return CO_ERROR_ILLEGAL_ARGUMENT;
}
/* Configure object variables */
CANmodule->CANdriverState = CANdriverState;
CANmodule->rxArray = rxArray;
CANmodule->rxSize = rxSize;
CANmodule->txArray = txArray;
CANmodule->txSize = txSize;
CANmodule->CANnormal = false;
CANmodule->useCANrxFilters = false;/* microcontroller dependent */
CANmodule->bufferInhibitFlag = false;
CANmodule->firstCANtxMessage = true;
CANmodule->CANtxCount = 0U;
CANmodule->errOld = 0U;
CANmodule->em = NULL;
for(i=0U; i<rxSize; i++){
rxArray[i].ident = 0U;
rxArray[i].mask = 0xFFFFU;
rxArray[i].object = NULL;
rxArray[i].pFunct = NULL;
}
for(i=0U; i<txSize; i++){
txArray[i].bufferFull = false;
}
FlexCAN_Configure(CANbitRate);
return CO_ERROR_NO;
}
在 CO_driver.c 文件中实现FlexCAN的报文收发
对 flexcan_tx() 函数及 CO_CANinterrupt()函数的实现。
/* Send a message frame. */
bool flexcan_tx(CO_CANtx_t *buffer)
{
bool status = false;
flexcan_frame_t FlexCAN_FrameStruct;
flexcan_mb_transfer_t FlexCAN_MB_TransferStruct;
if (!buffer->rtr)
{
FlexCAN_FrameStruct.type = (uint8_t)Enum_Flexcan_FrameTypeData; /* Data frame type. */
}
else
{
FlexCAN_FrameStruct.type = (uint8_t)Enum_Flexcan_FrameTypeRemote; /* Remote frame type. */
}
FlexCAN_FrameStruct.length = (uint8_t)buffer->DLC;
FlexCAN_FrameStruct.format = (uint8_t)Enum_Flexcan_FrameFormatStandard;
FlexCAN_FrameStruct.id = FLEXCAN_ID_STD(buffer->ident); /* Indicated ID number. */
FlexCAN_FrameStruct.dataByte0 = buffer->data[0];
FlexCAN_FrameStruct.dataByte1 = buffer->data[1];
FlexCAN_FrameStruct.dataByte2 = buffer->data[2];
FlexCAN_FrameStruct.dataByte3 = buffer->data[3];
FlexCAN_FrameStruct.dataByte4 = buffer->data[4];
FlexCAN_FrameStruct.dataByte5 = buffer->data[5];
FlexCAN_FrameStruct.dataByte6 = buffer->data[6];
FlexCAN_FrameStruct.dataByte7 = buffer->data[7];
FlexCAN_MB_TransferStruct.mbIdx = 2;
FlexCAN_MB_TransferStruct.frame = &FlexCAN_FrameStruct;
if (Status_Flexcan_Success == FLEXCAN_TransferSendNonBlocking(FLEXCAN, &FlexCAN_Handle, &FlexCAN_MB_TransferStruct))
{
status = true;
}
return status;
}
/******************************************************************************/
CO_ReturnError_t CO_CANsend(CO_CANmodule_t *CANmodule, CO_CANtx_t *buffer){
CO_ReturnError_t err = CO_ERROR_NO;
/* Verify overflow */
if(buffer->bufferFull){
if(!CANmodule->firstCANtxMessage){
/* don't set error, if bootup message is still on buffers */
CO_errorReport((CO_EM_t*)CANmodule->em, CO_EM_CAN_TX_OVERFLOW, CO_EMC_CAN_OVERRUN, buffer->ident);
}
err = CO_ERROR_TX_OVERFLOW;
}
CO_LOCK_CAN_SEND();
bool tx_mb_status = flexcan_tx(buffer);
if(tx_mb_status == true){
CANmodule->bufferInhibitFlag = buffer->syncFlag;
}
/* if no buffer is free, message will be sent by interrupt */
else{
buffer->bufferFull = true;
CANmodule->CANtxCount++;
}
CO_UNLOCK_CAN_SEND();
return err;
}
void CO_CANinterrupt(CO_CANmodule_t *CANmodule){
uint32_t status = FLEXCAN->IFLAG1;
if (0 != (status & (BOARD_FLEXCAN_RX_MB_STATUS)) || (FlexCAN_MB0_RxCompleteFlag))
{
/* receive interrupt */
CO_CANrxMsg_t *rcvMsg; /* pointer to received message in CAN module */
CO_CANrxMsg_t rcvMsgBuff;
uint16_t index; /* index of received message */
uint32_t rcvMsgIdent; /* identifier of the received message */
CO_CANrx_t *buffer = NULL; /* receive message buffer from CO_CANmodule_t object. */
bool_t msgMatched = false;
/* get message from module here */
rcvMsg = &rcvMsgBuff;
rcvMsg->ident = (FlexCAN_MBTemp_FrameStruct.id>> CAN_ID_STD_SHIFT)&0x7FF;
rcvMsg->DLC = FlexCAN_MBTemp_FrameStruct.length;
rcvMsg->data[0] = FlexCAN_MBTemp_FrameStruct.dataByte0;
rcvMsg->data[1] = FlexCAN_MBTemp_FrameStruct.dataByte1;
rcvMsg->data[2] = FlexCAN_MBTemp_FrameStruct.dataByte2;
rcvMsg->data[3] = FlexCAN_MBTemp_FrameStruct.dataByte3;
rcvMsg->data[4] = FlexCAN_MBTemp_FrameStruct.dataByte4;
rcvMsg->data[5] = FlexCAN_MBTemp_FrameStruct.dataByte5;
rcvMsg->data[6] = FlexCAN_MBTemp_FrameStruct.dataByte6;
rcvMsg->data[7] = FlexCAN_MBTemp_FrameStruct.dataByte7;
rcvMsgIdent = rcvMsg->ident;
FlexCAN_MB0_RxCompleteFlag = 0;
/* CAN module filters are not used, message with any standard 11-bit identifier */
/* has been received. Search rxArray form CANmodule for the same CAN-ID. */
buffer = &CANmodule->rxArray[0];
for(index = CANmodule->rxSize; index > 0U; index--){
if(((rcvMsgIdent ^ buffer->ident) & buffer->mask) == 0U){
msgMatched = true;
break;
}
buffer++;
}
/* Call specific function, which will process the message */
if(msgMatched && (buffer != NULL) && (buffer->pFunct != NULL)){
buffer->pFunct(buffer->object, rcvMsg);
}
/* Clear interrupt flag */
FLEXCAN_ClearMbStatusFlags(FLEXCAN, BOARD_FLEXCAN_RX_MB_STATUS);
}
else if (0 != (status & BOARD_FLEXCAN_TX_MB_STATUS))
{
/* Clear interrupt flag */
FLEXCAN_ClearMbStatusFlags(FLEXCAN, BOARD_FLEXCAN_TX_MB_STATUS);
/* First CAN message (bootup) was sent successfully */
CANmodule->firstCANtxMessage = false;
/* clear flag from previous message */
CANmodule->bufferInhibitFlag = false;
/* Are there any new messages waiting to be send */
if(CANmodule->CANtxCount > 0U){
uint16_t i; /* index of transmitting message */
/* first buffer */
CO_CANtx_t *buffer = &CANmodule->txArray[0];
/* search through whole array of pointers to transmit message buffers. */
for(i = CANmodule->txSize; i > 0U; i--){
/* if message buffer is full, send it. */
if(buffer->bufferFull){
buffer->bufferFull = false;
CANmodule->CANtxCount--;
/* Copy message to CAN buffer */
CANmodule->bufferInhibitFlag = buffer->syncFlag;
CO_CANsend(CANmodule, buffer);
break; /* exit for loop */
}
buffer++;
}/* end of for loop */
/* Clear counter if no more messages */
if(i == 0U){
CANmodule->CANtxCount = 0U;
}
}
}
else{
/* some other interrupt reason */
}
}
在 CO_driver.c 文件中实现CAN总线错误检测
关于 CO_CANverifyErrors() 函数的实现。
void CO_CANverifyErrors(CO_CANmodule_t *CANmodule){
uint16_t rxErrors, txErrors, overflow;
CO_EM_t* em = (CO_EM_t*)CANmodule->em;
uint32_t err;
/* get error counters from module. Id possible, function may use different way to
* determine errors. */
rxErrors = (uint16_t) ((FLEXCAN->ECR & CAN_ECR_RXERRCNT_MASK) >> CAN_ECR_RXERRCNT_SHIFT);
txErrors = (uint16_t) ((FLEXCAN->ECR & CAN_ECR_TXERRCNT_MASK) >> CAN_ECR_TXERRCNT_SHIFT);
overflow = (uint16_t) ((FLEXCAN->ESR1 & CAN_ESR1_ERROVR_MASK) >> CAN_ESR1_ERROVR_SHIFT);
err = ((uint32_t)txErrors << 16) | ((uint32_t)rxErrors << 8) | overflow;
if(CANmodule->errOld != err){
CANmodule->errOld = err;
if(txErrors >= 256U){ /* bus off */
CO_errorReport(em, CO_EM_CAN_TX_BUS_OFF, CO_EMC_BUS_OFF_RECOVERED, err);
}
else{ /* not bus off */
CO_errorReset(em, CO_EM_CAN_TX_BUS_OFF, err);
if((rxErrors >= 96U) || (txErrors >= 96U)){ /* bus warning */
CO_errorReport(em, CO_EM_CAN_BUS_WARNING, CO_EMC_NO_ERROR, err);
}
if(rxErrors >= 128U){ /* RX bus passive */
CO_errorReport(em, CO_EM_CAN_RX_BUS_PASSIVE, CO_EMC_CAN_PASSIVE, err);
}
else{
CO_errorReset(em, CO_EM_CAN_RX_BUS_PASSIVE, err);
}
if(txErrors >= 128U){ /* TX bus passive */
if(!CANmodule->firstCANtxMessage){
CO_errorReport(em, CO_EM_CAN_TX_BUS_PASSIVE, CO_EMC_CAN_PASSIVE, err);
}
}
else{
bool_t isError = CO_isError(em, CO_EM_CAN_TX_BUS_PASSIVE);
if(isError){
CO_errorReset(em, CO_EM_CAN_TX_BUS_PASSIVE, err);
CO_errorReset(em, CO_EM_CAN_TX_OVERFLOW, err);
}
}
if((rxErrors < 96U) && (txErrors < 96U)){ /* no error */
CO_errorReset(em, CO_EM_CAN_BUS_WARNING, err);
}
}
if(overflow != 0U){ /* CAN RX bus overflow */
CO_errorReport(em, CO_EM_CAN_RXB_OVERFLOW, CO_EMC_CAN_OVERRUN, err);
}
}
}
至此,驱动代码适配完成。
板载验证
验证环境
使用搭载了MM32G5330 MCU的开发板Mini-G5330 ,以CANopen_Basic样例工程为例,将开发板上的CAN收发器与PCAN相连接,并将PCAN与PC机通过USB相连接,在PC端(基于Win10操作系统)使用PCAN-View上位机模拟CANopen主站,来通过CANopen协议与CANopen从站(即 MM32 MCU)进行通信,如图5所示。
图 5 MCU与PC机交互示意图
注:这里我们使用了PCAN-USB,并使用了配套上位机PCAN-View。
验证过程
上述环境搭建好后,将上述工程代码编译后刷写固件进MCU,将MCU上电并复位通过PC端上位机PCAN-View测试如下指令,观察CANopen节点其对指令的响应,来判断该CANopen节点是否处于正常运行状态。
节点上线:
MCU上电后,CANopen节点应成功启动并向网络发送上线报文。
CANopen节点上线向CAN网络发送CANopen节点上线报文,PC上位机将收到一条如下报文:
表2
之后该CANopen节点以 1000ms 的时间间隔向CAN网络发送节点心跳报文,上位机以1000ms的时间间隔收到如下报文:
表 3
如图6所示。
图 6
至此,可验证该CANopen节点设备成功启动并开始正常运行。
模式切换:
通过上位机发送NMT命令,验证节点能够正确响应Start、Stop和Pre-operation等模式切换指令。
将NODE-ID为0x0A的节点设置为 Stop 模式,上位机PCAN-View发送如下指令:
表 4
如下图7所示,可接收到如下报文:
图 7
将NODE-ID为0x0A的节点设置为 Start 模式,上位机PCAN-View发送如下指令:
表 5
如下图8所示,可接收到如下报文:
图8
将NODE-ID为0x0A的节点设置为Pre-operation模式,上位机PCAN-View发送如下指令:
表6
如下图9所示,该节点进入Pre-operation模式,可接收到如下报文:
图 9
将NODE-ID为0x0A节点复位,上位机PCAN-View发送如下指令:
表 7
如下图10所示,该节点被复位:
图 10
将NODE-ID为0x0A节点的通信层复位,上位机PCAN-View发送如下指令:
表 8
如下图11所示,该节点通信层被复位,重新上线:
图 11
心跳检测:
节点应周期性发送心跳报文,以表明其处于活跃状态。
获取NODE-ID为0x0A节点的心跳发送间隔时间,上位机PCAN-View发送如下指令:
表 9
如下图12所示,返回该节点当前心跳发送间隔时间为1000(0x03E8)ms:
图 12
设置NODE-ID为0x0A节点的心跳发送间隔时间为500(0x01F4)ms,上位机PCAN-View发送如下指令:
表 10
如下图13所示,该节点当前心跳发送间隔时间变为500ms:
图 13
总结
通过本文的介绍,我们了解了CANopen协议的基本概念,并基于MM32G5330的FlexCAN完成了CANopenNode协议栈的移植工作。通过板载验证,我们确认了移植后的协议栈能够正常工作,为后续的设备集成和通信提供了进一步开发的基础。同样的开发者可以根据实际应用需求使用灵动其他带有FlexCAN的MCU,参考本文的方法进行相应的移植和验证工作,以实现高效可靠的CANopen通信。
- |
- +1 赞 0
- 收藏
- 评论 0
本文由ll转载自MindMotion(灵动微)微信公众号,原文标题为:灵动微课堂 (第281讲)|基于MM32G5330的FlexCAN实现CANopenNode协议栈移植,本站所有转载文章系出于传递更多信息之目的,且明确注明来源,不希望被转载的媒体或个人可与我们联系,我们将立即进行删除处理。
相关研发服务和供应服务
相关推荐
【技术】一文探讨MCU芯片在智能工业自动化中的应用前景,及其未来的发展方向展望
本文探讨MCU芯片在智能工业自动化中的应用前景,重点介绍超低功耗MCU的关键作用,并探讨它在工业自动化中的应用领域。同时,讨论MCU芯片的发展趋势和未来的发展方向。
技术探讨 发布时间 : 2023-10-07
中科芯(CETC)32位MCU选型指南
目录- 公司简介 MCU MCU型号定义&封装参考 MCU开发工具 LoRa/ NB-IoT LoRa/ NB-IoT产品简介 MCU应用案例
型号- CKS32F103V8T6,CKS32F031K6U6,CKS32F102C6T6,CKS32F417ZET6,CKS32F105VDT6,CKS32F107,SX1262,CKS32F105,CKS32F102R4T6,CKS32F103,CKS32F101,CKS32F102,CKS32F101R6T6,CKS32F103C4T6,CKS32F051K6U6,CKS32F101ZCT6,CKS32F030F6P6,CKS32F101VET6,CKS32F417VGT6,SX1280,CKS32L063C8T6,CKS32F031F4P6,CKS32F101C8T6,CKS32F107V8T6,SX1278,CKS32F103RCT6,CKS32F107RBT6,CKS32F051K6T6,CKS32F405ZGT6,TP20L607,CKS32F103ZET6,CKS32L052R6T6,CKS32L052C8T6,CKS32F105VCT6,CKS32F107系列,CKS32F105RET6,CKS32L051系列,CKS32L051R8T6,CKS32L053C6T6,CKS32F101ZDT6,CRF-62,CKS32F072R6T6,CKS32F051系列,CKS32F103RDT6,CKS32F072C8T6,CKS32F103VBT6,CKS32F030K6T6,CKS32F042K8T6,CKS32F407VET6,CKS32F103R4T6,CKS32F415系列,CKS32F103ZDT6,CKS32F102C8T6,CKS32F417ZGT6,CKS32F042系列,CKS32F105VBT6,CKS32F030R8T6,CKS32F102R6T6,CKS32F105RDT6,CRF62-LKWAN-CY,CKS32F103C6T6,CKS32L052系列,CKS32F030K6U6,CKS32F102系列,CKS32F101R8T6,CKS32F030F4P6,CKS32F101ZET6,CKS32L063R8T6,CKS32F101CBT6,CKS32F031系列,CKS32F107R8T6,CKS32F031G6U6,CKS32F103RET6,CKS32L051K6T6,CRF1278系列,CKS32F415RGT6,CKS32L063系列,CKS32F103VCT6,CKS32F051C8T6,CKS32F103ZCT6,CKS32F417IET6,CKS32L052R8T6,CKS32F105RCT6,CKS32L053系列,CRF1278,CKS32F051R8T6,CKS32F101系列,CKS32L053C8T6,CKS32F072CBT6,CKS32F105V8T6,CKS32F407ZET6,CKS32F101RBT6,CKS,CKS32F030系列,CKS32F072R8T6,CRF62-L2,CRF62-L5,CKS32F042C6T6,CKS32F103VDT6,CKS32L053R6T6,CKS32F107VET6,CKS32F407VGT6,CKS32F103R6T6,CKS32F417系列,CKS32F030C8T6,CKS32F102R8T6,CKS32F105RBT6,CKS32F031C6T6,CKS32F103C8T6,CKS32F102CBT6,CKS32F405系列,CKS32F101RCT6,CKS32L052K6T6,CKS32F415VGT6,CKS32F031G4U6,CKS32F103VET6,CKS32F107VDT6,CKS32F051,CKS32F051C6T6,CKS32L051K8T6,CKS32F417IGT6,CRF-62系列,CRF62-WAN,CKS32,CKS32F103系列,CKS32F072RBT6,CKS32F042,CKS32F407IET6,CKS32F405RGT6,CRF1280-12S,CKS32F107RET6,CKS32F407ZGT6,CKS32F101VBT6,CKS32F102RBT6,CKS32F101RDT6,CKS32L053R8T6,CKS32F031,CKS32F103R8T6,CKS32F042C8T6,CKS32F107VCT6,CKS32F030,CRF1278-L3,CRF1278-L1,CKS32F030C6T6,XY1100,CRF1278-L4,CKS32F103CBT6,CKS32F030K6,CKS32F102C4T6,ASR6601,CKS32F101R4T6,CKS32F105R8T6,CKS32F051K8U6,CKS32F407系列,CKS32F107RDT6,CKS32F101VCT6,CKS32F415ZGT6,CKS32F101RET6,CKS32L052K8T6,CKS32F417,CKS32F417VET6,CKS32F101C6T6,CKS32F415,CKS32F031F6P6,CKS32F107VBT6,CKS32L051C6T6,CKS32F051K8T6,TP20H607,CKS32L051C8T6,CKS32F042K6T6,CKS32L052C6T6,ASR6505,CKS32F105VET6,CKS32F407,CKS32F405,ASR6501,ASR6502,CKS32X...,CKS32F105系列,CKS32L051R6T6,CKS32L063,CKS32F407IGT6,CKS32F405VGT6,CKS32F072,CKS32F101VDT6,CKS32F072系列,CKS32F031K6T6,CKS32F103RBT6,CKS32F072C6T6,CRF1100-N1,CKS32L052,CKS32F107RCT6,CKS32L051,CKS32L053
复旦微电子(FMSH)MCU产品选型指南
描述- 从上世纪90年代初开始,深耕智能电表领域二十余年,成为智能电表专用MCU领域的领头羊。公司在不断推出满足市场需求的低功耗MCU产品系列的同时,也在不断完善产品的生态系统,致力于为客户提供可靠性高、平台化优、易用性强、资源丰富的MCU产品。目前复旦微MCU已广泛应用于智能电表、汽车电子、智能水气热表、工业控制、仪器仪表、电机驱动、传感检测、家用电器、消费电子、健康医疗、智能家居、物联网、新能源等多个领域。
型号- FM33M0XX系列,FM33LC045N,FM33LG023A,MG33M068ER,MG33M026ER,FM3316,FM3318,FM33FR045,MG33A045EV,FM33FT056A,FM33A0XXEV系列,FM33FR046,FM33FR043,FM33FR044,FM33FT028A,FM33LE0XXEVB系列,FM33A065EVB,FM33LE0XXA SERIES,FM33A0610EVB,FM33A0XXEV SERIES,FM33A0XX系列,FM33LC046N,MG33M0410ER,FM33A0410EV,FM33KF5XX,FM33LC022N,FM33LG0XXA,FM33FR056,FM33A0XXEVB系列,FM33FR054,FM33FR055,FM33FR053,FM33LG0XXEV系列,MG33M0XXER系列,FM33FR0XX SERIES,FM33A068EVB,FM33FR048,FM33LE0XX SERIES,MG33M0610ER,FM33LG048A,FM33LE0XXA系列,FM33LG0XX系列,MG33M046ER,FM33LG013A,FM33LC015N,FM33LG025A,FM33LC043N,FM33FR023,FM33FR024,FM33FT046A,FM33LC015M,FM33FT058A,FM33M0XX,FM3308,FM33A065EV,FM33LG0XX,FM33LC016N,FM33FR028,FM33FR026,FM33KT5XX,FM33LE015A,FM33A0XXEV,FM33LG026A,FM33LC0XXM SERIES,FM33LC0XXU SERIES,FM33FT0XXA SERIES,FM33L0XX,FM33LC013N,FM33LC025N,MG33M066ER,FM33G0XX系列,FM3316系列,FM33FT0XXA,FM33FT0XXA系列,FM33FR0XX,MG33M0XXER SERIES,FM33LG0XX SERIES,FM23XX,FM33FT048A,FM33LE015,FM33LE016,FM33LG0XXEV,FM33LE012,FM33LE013,MG33M028ER,FM33LG015A,FM33LC044NR,FM33LC0XXU,FM33LC026N,FM33LC042N,FM33A068EV,FM33FR0510,MG33M0XXER,FM33LE0XX系列,FM33FT0510A,MG33M0XX,FM33LE0XX,FM33LG025,FM33LG026,FM33G0XX,FM33LC0XXN SERIES,FM33LE013A,FM33LE025A,FM33LG016A,FM33LG0XXA SERIES,FM33LC023N,FM33LG045A,MG33M048ER,FM33FR0XX系列,FM33A048EVB,FM331X,FM33A0XXEVB,FM33A0XXEVB SERIES,FM33A0410EVB,FM33LE0XXA,FM33LF0XX,FM33FT026A,FM33LE0XXEVB,FM33FR058,FM33LC046U,FM33A0610EV,MG33A045EVB,FM33LC0XXN,FM33LC012N,FM33A048EV,FM33LC012M,FM33LG046A,FM33LC0XX系列,FM33LC0XXM,FM33LE023A,FM33LG045,FM23XX系列,FM33A0XX,FM33LE026,FM33LC0XX,FM33LG0XXA系列,FM33LE022,FM33LE023,FM33LG046,FM33L0XX系列,FM33LE025,FM33LG048,FM33LC023U
M0+产品行业新标杆诞生,武汉芯源CW32L010安全低功耗MCU集合多项技术性能优势
2024年9月26日,武汉芯源半导体带着最新产品CW32L010安全低功耗MCU作客电巢直播间,举行了一场璀璨的XR技术新品发布会,此次直播,武汉芯源半导体技术总监 张亚凡、北中国区销售总监 孙秀艳与大家分享了武汉芯源半导体的发展历程和重要时刻,全面介绍和讲解了新品CW32L010实现的技术优势。
原厂动态 发布时间 : 2024-11-05
Holychip(芯圣电子)MCU及MCU周边芯片选型指南
描述- 上海芯圣电子股份有限公司(Holychip)是一家专注于芯片设计研发和销售的集成电路设计企业,专业从事MCU及MCU周边芯片的研发和销售,致力于为客户提供高性能、高性价比的芯片产品、应用开发工具和系统解决方案。公司MCU及MCU周边芯片通用性强,性能优异,广泛应用于消费电子、智慧家电、智能照明、安防消防、工控医疗、汽车电子、IOT物联、通讯和PC等行业,为众多知名终端品牌提供专业的芯片产品和系统解决方案。
型号- HC20LO2030,SQ333,HC20OP0358,HC18P133L,HC32AT系列,HC20MD1225,HC89S105AC8T7M,HC89F3541B,AO3407,HC32F407VG,HC89F3531,HC20LO0012,HC89F0541,AO3401,HC20LO2033,AO3402,HC89S5840,HC89S003BF6P7M,YK2302A,AO3400,HC89F0411A,HC89F301C,HC18M303D,HC20LO1117-33,2N7002K,HC32F103BRB,HT66F0195,HC-ICD V4,YK3407,HC15P013A0,HC89F3541,HC89F3421,YK3401,HC32AT3781,HCP2019-5,HC89F0312,AO3415,SQL6970A2,HC89S105AK8T7CM,SQL6970A1,STM8S105K3,HC89S003AF4U7M,STM8S105S6,HC20LO0018,STM8S105S4,HC89F302C,HC18M302D,STM8S005C6,STM8S105K6,STM8S105K4,HC18M003,HC88T3681,HC89M7102,APM2306,HC89M7101,HC18M002,HC89M7103,STM8S003K3,HC60W2401,SQ3400,HC32F407系列,HC89F0421A,SQL6980A2,HC18P015B0,SQL6980A1,SQL6967,HC20CD4056,HC89S003BF6U7M,SQL6966,HC20LO1117-50,HC20CD4054,SQ2301A,HC89S001AJ4M7,HC20MD2012,HC32F030,HC20MD2011,HC32T3031,HC89S103K6T6,HC20LO1050,HC20MD2002,FDV301N,HC20BS6055,SQL4256,HC8M2401,HC20LO2025,HC89F0531,HT66F0185,BSS138K,HC88L051F4P7,HC8M603-SSOP20,YK2301A,SQL6973A2,SQL6973A1,HC8M603-SSOP24,HC16P100B1,HC20MD2001,HC18P018A0,N76E003AQ20,HC89S003AF4P7M,HC89S105AS8T7CM,HC20LO0050,HC18P110B0,HC88T3661,HC89F3521B,HCP2019-AD,HC20LO1025,HC-PM51 V5,HC15P121B1,HC32F407RG,HC32F103BVB,HC89F0431A,STM8L051F3,SQ2711L,HC8P2401L,SQL5811,SQL5810,HC16P122A1,HC20CD4156,HC20OP0324,HC20LO1150,S3F9454,HC88T3671,HC8M612-SOP16,HC32F030BK6,HC32T3051,SQ3407,HC18P235L,HC20LO1033,HC60W2401L,HC20LO1030,SQ3401,HC32F407,HC18P015A0,STM8S103K3,HC32F103系列,SQL6972A2,SQ2302A,SQL6972A1,HC16P122B1,SQL583,HC32F030BC8,HC-PM18 V5,STM8S005K6,HC8AT3541,HC20LO2050,XP152A,STM8S105C6,HC89F3531B,HC20LO1125,SQ7002,HC88L051F4U7,IRLML6402,HC20LO0033,IRLML6401,HC-LINK V4,AO3423,HC32F030BR8,HC89F0322,HC18M5830,HC20LO0030,HC89F303C,HC18M301D,AP2301,HC20LO1117-15,HC20LO1117-AD,AP2306,SI2307,STM8S003F3,SI2306,HC18P110A0,HC18P233L,HC20MD1115,HC32AT,SQL5820,HC20LO1133,STM8S001J3,HC89F0332,HC20LO1130,HC32F103BCB,SQL6971A1,WNM2306,SQL5822,SQL6990A1,HC32F103,HC32F407ZG,SQL6971A2,HC20LO1117-20,SI2301,HC32F030系列,HC20LO1117-25
【IC】灵动发布全新入门级32位MCU MM32G0001系列,内置时钟全温度范围内偏差不超过±2%
灵动股份推出全新超值型MM32G0001系列MCU。2023年初,灵动首次发布了其主打高性价比的MM32G系列,目前已陆续推出了G0140,G0160和G5330系列产品。为进一步丰富MM32G系列产品组合,灵动和上下游合作伙伴通力合作,打造出全新入门级超值型MM32G0001系列MCU。
新产品 发布时间 : 2023-07-01
雅特力携多款AT32 MCU新品与应用方案亮相2024慕尼黑上海电子展
7月8日,2024慕尼黑上海电子展electronica China在上海新国际博览中心拉开序幕,作为全球电子行业的盛会,汇聚了国内外众多优质电子企业。雅特力携高性能AT32 MCU与应用方案齐亮相,呈现了多款电机控制、工业控制、汽车电子、智能家居、消费、商务,及新能源等应用方案。现场人流攒动、氛围热烈!
原厂动态 发布时间 : 2024-07-17
灵动MM32SPIN⸺专注电机控制的MCU和SOC
型号- SPIN080G,MM32SPIN030C,SPIN360C,MM32SPIN06NT,SPIN033A,SPIN590G,MM32SPIN560C,MM32SPIN05PT,MM32SPIN580C,MM32SPIN05TW,SPIN0260,MM32SPIN27PF,MM32SPINEBK,MM32SPIN0230B3NV,MM32SPIN080GN,MM32SPIN06PF,SPIN02XX,MM32SPIN07,MM32SPIN0280,MM32SPIN160C,SPIN533A,MM32SPIN,MM32SPIN560CM,SPIN27,MM32SPIN023C,MM32SPIN06,MM32SPIN05,MM32SPIN0230B1NV,MM32SPIN422C,MM32SPIN0280D4PV,MM32SPIN0280D6PV,SPIN060G,SPIN0250,SPIN080C,SPIN222C,SPIN0290,SPIN040C,MM32SPIN080CN,MM32SPIN0230,MM32SPIN060G,MM32SPIN080C,SPIN0230,MM32SPIN040C,SPIN56XX,MM32SPIN37,MM32SPIN05NW,SPIN580C,SPIN160C,SPIN023C,MM32SPIN05NT,MM32SPIN06PT,MM32SPIN0280D6QV,MM32SPIN360C,SPIN0280,SPIN495C,MM32SPIN030CN,MM32SPIN27NF,SPIN560C,SPIN05XX,MM32SPIN07PF,SPIN07,SPIN06,MM32SPIN05PF,MM32SPIN27PQ,MM32SPIN0230B3TV,SPIN5630,MM32SPIN05PFOP,MM32SPIN27PS,MM32SPIN27PT,MM32SPIN0230B1TV,MM32SPIN37PSD,SPIN05,MM32SPIN533A,MM32SPIN033A,MM32SPIN27,MM32SPIN0280D7PV,MM32SPIN222C,SPIN080X,SPIN422C,MM32SPIN0280DAPV,SPIN030C,MM32SPIN080G
极海半导体(Geehy Semiconductor)汽车电子芯片 选型指南
描述- 极海汽车电子芯片产品布局通用微控制器/微处理器、传感器、接口、驱动等多条产品线,战略聚焦车身控制、信息娱乐系统、BMS电池管理系统及域控等领域,提供符合ISO 26262功能安全标准、安全可靠、质量稳定的芯片产品与应用方案,并为客户提供丰富的开发生态与本地化技术支持。通过优质的服务和多元化的产品组合,满足汽车电子行业日益增长的应用需求,协助客户实现快速量产,助力国产汽车产业向上发展。
型号- G32A1445UAT0MLL,G32A1465UAT0MLL,G32A1445UAT0MLH,G32A1465UAT0MLH,APM32F103RCT7,APM32A407VGT7,APM32A407,APM32A103,APM32A103CBT7,APM32F003F6U7,GURC01,APM32A407ZGT7,APM32A103系列,APM32A091RCT7,APM32A091,APM32,APM32F072RBT7,APM32A091系列,APM32A407系列,G32A1445系列,APM32A103RET7,APM32A103VET7,APM32F072CBT7,G32A1445,G32A
累计交付超4亿颗!世强硬创获低功耗32位MCU厂商灵动股份授权
MindMotion(灵动股份)MM32WB0510系列配套软件支持各种蓝牙Profile,提供小尺寸协议栈,最小仅占用12KB Flash和2KB RAM。
签约新闻 发布时间 : 2023-07-24
晟矽微电子两款车规级MCU入选《2023年度长三角汽车电子芯片产品手册》
近日,第三届(2023年)长三角汽车芯片对接交流会在上海张江举办,晟矽微电应邀出席。晟矽微电入选《长三角汽车电子芯片产品手册(2023年)》的两款车规MCU分别为MA60F9113CP48T以及MA51F8203A0Y。
产品 发布时间 : 2023-11-14
电子商城
现货市场
服务
可定制显示屏的尺寸0.96”~15.6”,分辨率80*160~3840*2160,TN/IPS视角,支持RGB、MCU、SPI、MIPI、LVDS、HDMI接口,配套定制玻璃、背光、FPCA/PCBA。
最小起订量: 1000 提交需求>
可烧录IC封装SOP/MSOP/SSOP/TSOP/TSSOP/PLCC/QFP/QFN/MLP/MLF/BGA/CSP/SOT/DFN;IC包装Tray/Tube/Tape;IC厂商不限,交期1-3天。支持IC测试(FT/SLT),管装、托盘装、卷带装包装转换,IC打印标记加工。
最小起订量: 1pcs 提交需求>
登录 | 立即注册
提交评论