本帖最后由 xld0932 于 2022-5-10 13:06 编辑
#申请原创# @21小跑堂
MM32F3270系列MCU带有一个CAN通讯接口,支持CAN协议的2.0A和2.0B协议标准,同时支持11位和29位识别码功能;支持BasicCAN和PeliCAN两种工作模式,具有64字节的先进先出接收缓冲区空间,其位速率最高达到1Mbps。
本文基于MM32F3270通讯板的CAN通讯接口来分享一下在实际项目中对于CAN接口通讯的初始化配置和应用以及在开发过程中遇到的一些注意事项等等。 1、CAN初始化配置配置、注意事项、及示例程序 2、CAN滤波器有何作用,该如何设置? 3、CAN采样点如果计算、设置和选择? 4、MM32F3270的CAN可以工作在5kbps的超低通讯速率下吗? 5、CAN总线通讯网络中为什么需要添加终端电阻呢? 6、单个节点测试数据收发正常,接入CAN网络后发数据就很少成功,这是怎么回事?
1、CAN初始化配置配置、注意事项、及示例程序 MM32F3270系列MCU对于CAN通讯接口的初始化包括收下几个方面,分别是复用功能的端口引脚配置、CAN中断配置、CAN通讯参数配置、CAN滤波器配置。如果开启了CAN中断,则需要在CAN中断函数中完成相应的中断请求处理动作。需要注意的是在配置CAN参数的时候,需要将CAN先配置进入到复位模式(复位模式和初始化模式等同),在配置完成后CAN通讯参数后,通过软件操作让硬件退出复位模式,进入到正常模式,以便正常的接收和发送报文。初始化示例程序如下:
复用功能的端口引脚配置 void CAN_InitGPIO(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHBPeriphClockCmd( RCC_AHBENR_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1ENR_CAN, ENABLE);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_9);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_9);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
CAN中断配置和处理函数 void CAN_InitNVIC(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = CAN_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void CAN1_RX_IRQHandler(void)
{
CanPeliRxMsg CanPeliRxMessage;
if(CAN_GetITStatus(CAN1, CAN_IT_RI) != RESET)
{
CAN_Peli_Receive(&CanPeliRxMessage);
CAN_RxHandler(CanPeliRxMessage);
}
}
CAN通讯参数配置 void CAN_BTR_AutoCALC(CAN_Peli_InitTypeDef *CAN_Peli_InitStructure, uint32_t ClockSource, uint32_t Baudrate)
{
/* CAN Baudrate = APB1 / ( 2 * (BRP + 1) * ((TSEG1 + 1) + (TEEG2 + 1) + 1))*/
uint8_t BRP = 0, SJW = 3, Flag = 0;
uint8_t TSEG1 = 0, TSEG2 = 0;
printf("\r\n");
printf("\r\n%s", __FUNCTION__);
printf("\r\nAPB1 : %dHz", ClockSource);
printf("\r\nBaud : %dHz", Baudrate);
printf("\r\n");
for(BRP = 0; BRP < 64; BRP++)
{
for(TSEG2 = 0; TSEG2 < 8; TSEG2++)
{
for(TSEG1 = 0; TSEG1 < 16; TSEG1++)
{
if(ClockSource / (2 * (BRP + 1) * ((TSEG1+ 1) + (TSEG2 + 1) + 1)) == Baudrate)
{
uint32_t A = 1 + (TSEG1 + 1);
uint32_t B = A + (TSEG2 + 1);
printf("\r\nBRP : 0x%02x, TSEG1 : 0x%02x, TESG2 : 0x%02x => %.02f%%", BRP, TSEG1, TSEG2, (float)(A) * 100.0 / (float)(B));
CAN_Peli_InitStructure->SJW = SJW;
CAN_Peli_InitStructure->BRP = BRP;
CAN_Peli_InitStructure->TESG2 = TSEG2;
CAN_Peli_InitStructure->TESG1 = TSEG1;
Flag = 1;
}
}
}
}
if(Flag == 0)
{
printf("\r\nError Parameters!!!");
}
printf("\r\n\r\n");
}
CAN滤波器的配置 void CAN_InitFilter(uint32_t Baudrate, CAN_Mode Mode, uint32_t IDCode1, uint32_t IDCode2, uint32_t IDMask1, uint32_t IDMask2)
{
CAN_Peli_InitTypeDef CAN_Peli_InitStructure;
CAN_Peli_FilterInitTypeDef CAN_Peli_FilterInitStructure;
uint32_t IDCodeTemp1 = 0, IDMaskTemp1 = 0;
uint32_t IDCodeTemp2 = 0, IDMaskTemp2 = 0;
RCC_ClocksTypeDef RCC_Clocks;
RCC_GetClocksFreq(&RCC_Clocks);
CAN_ResetMode_Cmd(CAN1, ENABLE);
CAN_Mode_Cmd(CAN1,CAN_PELIMode);
CAN_Peli_StructInit(&CAN_Peli_InitStructure);
CAN_AutoCfg_BaudParam(&CAN_Peli_InitStructure, RCC_Clocks.PCLK1_Frequency, Baudrate);
CAN_Peli_StructInit(&CAN_Peli_InitStructure);
CAN_BTR_AutoCALC(&CAN_Peli_InitStructure, RCC_Clocks.PCLK1_Frequency, Baudrate);
CAN_Peli_Init(&CAN_Peli_InitStructure);
switch(Mode)
{
case StandardFrame_SingleFilter:
IDCodeTemp1 = IDCode1 << (32 - 11);
IDMaskTemp1 = IDMask1 << (32 - 11);
IDMaskTemp1 |= 0x1FFFFF;
CAN_Peli_FilterInitStructure.AFM = CAN_FilterMode_Singal;
CAN_Peli_FilterInitStructure.CAN_FilterId0 = ((IDCodeTemp1 >> 0x18) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterId1 = ((IDCodeTemp1 >> 0x10) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterId2 = ((IDCodeTemp1 >> 0x08) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterId3 = ((IDCodeTemp1 >> 0x00) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId0 = ((IDMaskTemp1 >> 0x18) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId1 = ((IDMaskTemp1 >> 0x10) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId2 = ((IDMaskTemp1 >> 0x08) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId3 = ((IDMaskTemp1 >> 0x00) & 0xFF);
break;
case ExtendedFrame_SingleFilter:
IDCodeTemp1 = IDCode1 << (32 - 29);
IDMaskTemp1 = IDMask1 << (3);
IDMaskTemp1 |= 0x07;
CAN_Peli_FilterInitStructure.AFM = CAN_FilterMode_Singal;
CAN_Peli_FilterInitStructure.CAN_FilterId0 = (IDCodeTemp1 >> 0x18) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterId1 = (IDCodeTemp1 >> 0x10) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterId2 = (IDCodeTemp1 >> 0x08) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterId3 = (IDCodeTemp1 >> 0x00) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId0 = (IDMaskTemp1 >> 0x18) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId1 = (IDMaskTemp1 >> 0x10) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId2 = (IDMaskTemp1 >> 0x08) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId3 = (IDMaskTemp1 >> 0x00) & 0xFF;
break;
case StandardFrame_DoubleFilter:
IDCodeTemp1 = IDCode1 << (32 - 11);
IDCodeTemp2 = IDCode2 << (32 - 11);
IDMaskTemp1 = IDMask1 << (32 - 11);
IDMaskTemp2 = IDMask2 << (32 - 11);
IDMaskTemp1 |= 0x1FFFFF;
IDMaskTemp2 |= 0x1FFFFF;
CAN_Peli_FilterInitStructure.AFM = CAN_FilterMode_Double;
CAN_Peli_FilterInitStructure.CAN_FilterId0 = ((IDCodeTemp1 >> 0x18) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterId1 = ((IDCodeTemp1 >> 0x10) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterId2 = ((IDCodeTemp2 >> 0x18) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterId3 = ((IDCodeTemp2 >> 0x10) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId0 = ((IDMaskTemp1 >> 0x18) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId1 = ((IDMaskTemp1 >> 0x10) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId2 = ((IDMaskTemp2 >> 0x18) & 0xFF);
CAN_Peli_FilterInitStructure.CAN_FilterMaskId3 = ((IDMaskTemp2 >> 0x10) & 0xFF);
break;
case ExtendedFrame_DoubleFilter:
IDCodeTemp1 = IDCode1 << (32 - 29);
IDCodeTemp2 = IDCode2 << (32 - 29);
IDMaskTemp1 = IDMask1 << (32 - 29);
IDMaskTemp2 = IDMask2 << (32 - 29);
IDMaskTemp1 |= 0xFFFF;
IDMaskTemp2 |= 0xFFFF;
CAN_Peli_FilterInitStructure.AFM = CAN_FilterMode_Double;
CAN_Peli_FilterInitStructure.CAN_FilterId0 = (IDCodeTemp1 >> 0x18) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterId1 = (IDCodeTemp1 >> 0x10) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterId2 = (IDCodeTemp2 >> 0x18) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterId3 = (IDCodeTemp2 >> 0x10) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId0 = (IDMaskTemp1 >> 0x18) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId1 = (IDMaskTemp1 >> 0x10) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId2 = (IDMaskTemp2 >> 0x18) & 0xFF;
CAN_Peli_FilterInitStructure.CAN_FilterMaskId3 = (IDMaskTemp2 >> 0x10) & 0xFF;
break;
default: break;
}
CAN_Peli_FilterInit(&CAN_Peli_FilterInitStructure);
CAN_Peli_ITConfig(CAN_IT_ALL, ENABLE);
CAN_ResetMode_Cmd(CAN1, DISABLE);
}
2、CAN滤波器有何作用,该如何设置? 在CAN总线上,在接收方检测所有的数据报文时,只有在接收到的报文标识符与验收代码寄存器中设置的相关位相同时,报文数据才会被接收。在CAN通讯时使用滤波器的检测方法,可以防止对于某个节点来说是无效的报文数据被存储在接收缓冲区里,从而降低了MCU的处理负载。具体的设置细节描述参考MM32F3270的UM手册,在22.5.6标识符过滤章节中有详细的描述和举例说明。
3、CAN采样点如果计算、设置和选择? 在计算采样点之前我们先来复习一下CAN位时序:由发送单元在非同步的情况下发送的每秒钟的位数称为位速率,一个位可以分为4个段,别为为同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)、和相位缓冲段2(PBS2),另外再加一个再同步补尝宽度(SJW),这个段是用来解决因时钟频率偏差、传送延时等原因造成的各单元的同步误差的。以上这些段是由Time Quantum(Tq)为最小时间单位构成的,SS段固定为1Tq、PTS段为1~8Tq、PBS1段为1~8Tq、PBS2段为2~8Tq、SJW段为1~4Tq。
在MM32F3270系列MCU中,CAN位时序对于段的定义将其划分为3个段,分别是同步段(tSYNCSEG)、第一时间段(tTSEG1)和第二时间段(tTESG2),其中同步段对应了上面的SS段,第一时间段对应了上面的PTS段和PBS1段,第二时间段对应了上面的PBS2段。而CAN通讯接口的系统时钟是挂载在APB1总结上的,所以根据如下公式,我们可以通过配置参数计算出CAN当前的采样点百分比: CAN Baudrate = APB1 / ( 2 * (BRP + 1) * ((TSEG1 + 1) + (TESG2 + 1) + 1))
我们根据这一公式,CAN_BTR_AutoCALC函数通过穷举的方法,将符合条件的BRP、TSEG1和TESG2的值都一一计算出来,并计算出在这些配置参数下所对应的采样点百分比,如下图以125Kbps通讯波特率为例: 由上我们可以看出,符合125Kbps通信波特率的配置参数有很多,而且对应的采样点百分比也不尽相同,相同的百分比还会出现多组不同的配置参数值,这么多组的配置,我们到底要选择哪一下来写入到芯片CAN配置参数中呢?根据建议值,我们选择采样点在75%左右的,BRP、TSEG1和TESG2这3个值累加和最大的那一组配置参数作为最优的CAN配置参数。
3.1、采样点有建议参考百分比吗? 参考车辆的CAN通讯标准,建议CAN通讯的采样点设置在75%左右;而SJW的值建议设置为2或者3,这样可以加大位宽度和采样点的容忍度,提高波特率的适应性,当然SJW加大后允许的误差会加大,但整体的通讯速度就会有所下降。
3.2、CAN通讯的采样点为什么不设能置成50%以下或者90%以上呢? 这是因为在实际CAN通讯应用中,根据现场的实用环境,或多或少会存在些像环境因素、正弦频率、周期脉冲等诸多之类的干扰源,此时在发送一个比特位的初始阶段会有一段电平波动和稳定的时间,如果设置的采样点过低,就会出现波动电平被采样的情况,导致CAN数据比特位出现判断错误的情况;同样如果采样点设置过高,可能会产生采样点采样不到的有效比特位的情况;所以一般才有采样点设置在75%左右的参考建议值。
4、MM32F3270的CAN可以工作在5kbps的超低通讯速率下吗? 答案是可以的。但在使用官方库程序中的配置函数将CAN通讯波特率直接配置成5Kbps时,会发现CAN配置成功了,却无法通讯的问题,这是怎么回事呢? 一方面是官方库函数CAN_AutoCfg_BaudParam,另一个自编的穷举函数:CAN_BTR_AutoCALC,对于5Kbps这个通讯波特率却得出了不一样的计算结果,如下所示: 显然CAN_AutoCfg_BaudParam计算出来的参数值是不正确的,那又为什么CAN_BTR_AutoCALC会提示参数错误呢?CAN_BTR_AutoCALC通讯CAN波特率计算公式没有穷举出任一参数配置时,就会提示参数错误,怎么解决呢?这个函数调用时的形参就只有3个,这3个中只有ClockSource是可以再修改的,我们都知道CAN通讯接口是挂载在APB1总线上的,那我们适当把APB1总线频率降低一些是不是就可以得到正确的配置参数了呢?我们尝试在system_mm32f327x.c文件中,将SetSysClockToXX函数修改如下所示: static void SetSysClockToXX(void)
{
__IO u32 temp, tn, tm;//j,
__IO u32 StartUpCounter = 0, HSEStatus = 0;
u8 plln, pllm;
RCC->CR |= RCC_CR_HSION;
while(!(RCC->CR & RCC_CR_HSIRDY));
//PLL SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------
//Enable HSE
RCC->CR |= ((u32)RCC_CR_HSEON);
DELAY_xUs(5);
if(SystemCoreClock > 96000000) {
RCC->APB1ENR |= RCC_APB1ENR_PWR;
PWR->CR &= ~(3 << 14);
PWR->CR |= 3 << 14;
}
//Wait till HSE is ready and if Time out is reached exit
while(1) {
HSEStatus = RCC->CR & RCC_CR_HSERDY;
if(HSEStatus != 0)
break;
StartUpCounter++;
if(StartUpCounter >= (10 * HSE_STARTUP_TIMEOUT))
return;
}
if ((RCC->CR & RCC_CR_HSERDY) == RESET) {
//If HSE fails to start-up, the application will have wrong clock
//configuration. User can add here some code to deal with this error
HSEStatus = (u32)0x00;
return;
}
HSEStatus = (u32)0x01;
DELAY_xUs(5);
SystemCoreClock = SYSCLK_FREQ_XXMHz;
//Enable Prefetch Buffer
FLASH->ACR |= FLASH_ACR_PRFTBE;
//Flash 0 wait state ,bit0~2
FLASH->ACR &= ~FLASH_ACR_LATENCY;
temp = (SystemCoreClock - 1) / 24000000;
FLASH->ACR |= (temp & FLASH_ACR_LATENCY);
RCC->CFGR &= (~RCC_CFGR_HPRE) & ( ~RCC_CFGR_PPRE1) & (~RCC_CFGR_PPRE2);
//HCLK = AHB = FCLK = SYSCLK divided by 4
RCC->CFGR |= (u32)RCC_CFGR_HPRE_DIV4;
//PCLK2 = APB2 = HCLK divided by 1, APB2 is high APB CLK
RCC->CFGR |= (u32)RCC_CFGR_PPRE2_DIV1;
#if 0 //---For CAN Baud >= 10k
if(SystemCoreClock > 72000000) {
//PCLK1 = APB1 = HCLK divided by 4, APB1 is low APB CLK
RCC->CFGR |= (u32)RCC_CFGR_PPRE1_DIV4;
}
else if(SystemCoreClock > 36000000) {
//PCLK1 = APB1 = HCLK divided by 2, APB1 is low APB CLK
RCC->CFGR |= (u32)RCC_CFGR_PPRE1_DIV2;
}
#else //---For CAN Baud < 10k
if(SystemCoreClock > 72000000) {
//PCLK1 = APB1 = HCLK divided by 8, APB1 is low APB CLK
RCC->CFGR |= (u32)RCC_CFGR_PPRE1_DIV8;
}
else if(SystemCoreClock > 36000000) {
//PCLK1 = APB1 = HCLK divided by 4, APB1 is low APB CLK
RCC->CFGR |= (u32)RCC_CFGR_PPRE1_DIV4;
}
#endif
AutoCalPllFactor(HSE_VALUE, SystemCoreClock, &plln, &pllm);
RCC->PLLCFGR &= ~((u32 ) RCC_PLLCFGR_PLLSRC | RCC_PLLCFGR_PLLXTPRE) ;
RCC->PLLCFGR |= (u32 ) RCC_PLLCFGR_PLLSRC ;
tm = (((u32)pllm) & 0x07);
tn = (((u32)plln) & 0x7F);
RCC->APB1ENR |= RCC_APB1ENR_PWR;
RCC->PLLCFGR &= (u32)((~RCC_PLLCFGR_PLL_DN) & (~RCC_PLLCFGR_PLL_DP));
RCC->PLLCFGR |= ((tn << RCC_PLLCFGR_PLL_DN_Pos) | (tm << RCC_PLLCFGR_PLL_DP_Pos));
//Enable PLL
RCC->CR |= RCC_CR_PLLON;
//Wait till PLL is ready
while((RCC->CR & RCC_CR_PLLRDY) == 0) {
__ASM ("nop") ;//__NOP();
}
//Select PLL as system clock source
RCC->CFGR &= (u32)((u32)~(RCC_CFGR_SW));
RCC->CFGR |= (u32)RCC_CFGR_SW_PLL;
//Wait till PLL is used as system clock source
while ((RCC->CFGR & (u32)RCC_CFGR_SWS) != (u32)RCC_CFGR_SWS_PLL) {
__ASM ("nop") ;//__NOP();
}
DELAY_xUs(1);
// set HCLK = AHB = FCLK = SYSCLK divided by 2
RCC->CFGR &= (~(RCC_CFGR_PPRE_0));
DELAY_xUs(1);
// set HCLK = AHB = FCLK = SYSCLK divided by 1
RCC->CFGR &= (~(RCC_CFGR_PPRE_3));
DELAY_xUs(1);
}
此时我们发现,APB1总线时钟由原先的30MHz降低到了15MHz,此时对于计算配置参数的值也是有解的了,此时再进行CAN数据收发后,发现数据可以正常通讯了。 官方库程序中的CAN_AutoCfg_BaudParam函数计算实现方式一下没看太明白,但每次计算的采样点都在70%左右,但又不是最优配置,如果对采样点有要求的话,还是建议用我上面那个穷举函数的实现方式吧。
5、CAN总线通讯网络中为什么需要添加终端电阻呢? CAN总线上的信号电平幅值是接收节点能够正常识别判断有效逻辑信号的保证。一般来说差分电平(CAN_H & CAN_L)的电平幅值只有在大于0.9V及以上时才能被百分百的识别成显性电平,如果电平幅值低于0.9V就有可能被识别成隐性电平。为了保证通讯质量,再结合环境、干扰等因素的影响,我们需要将CAN通讯的电平幅值要求在1.2V~1.3V及以上,才能保证在实际使用过程中的信号质量,其中影响的除了线缆本身的阻抗外,通过调节终端电阻阻值是最有效的解决方法。终端电阻的阻值并不是固定的,这需要根据实际的通讯距离,以及使用的线缆做动态的有效调整,以保证有效的差分电平幅值,这样才具备抗良好的干扰能力和通讯质量。 从CAN收发器结构来看,从隐性电平变成显性电平是由晶体管驱动,所以边沿都是很陡峭的;但从显性电平再回到隐性电平,却是需要终端电阻放电来实现,如果没有终端电阻放电的过程,就会出现由于通讯线缆的分布电容,出现因缓慢放电而导致通讯数据位宽错误的情况。所以一般近距离或者是低波特率的CAN组网通讯中不加终端电阻的做法都是不对的。
6、单个节点测试数据收发正常,接入CAN网络后发数据就很少成功,这是怎么回事? CAN总线本质上还是半双工通讯,就是单行通讯通道,即一个节点发送数据的时候,其它节点无法向外发送数据。CAN报文ID是具有优先级的判断,如果高优先级一直占用总线,会导致低优先级的节点无法正常的往外发发送数据,这样就会出现堵塞的现象。遇到这样的问题,对于MM32F3270系列芯片来说,我们可以将CAN发送帧数据的函数由原先的CAN_Peli_Transmit修改为CAN_Peli_TransmitRepeat,这样在我们发送数据时总线仲裁被抢占后,可以使用芯片内的自动重发机制,直到数据帧被发送成功;当然也可以在软件层面采取控制流量的方式,来使CAN总线网络能够进行正常的通讯和数据传输。
附件
|