当前位置:   article > 正文

NXP S32K146 CAN通讯 TJA1043(一)

tja1043

今天来调一下nxp S32K146的CAN通讯,硬件部分使用的是NXP TJA1043 CAN通讯芯片先翻译一下数据手册。
一、TJA1043 有这么几个特点:
①几种保护和诊断功能,包括母线短路检测和电池连接检测
②CANFD快速阶段以高达5Mbit/s的数据速率进行可靠的通信。
③用于节点诊断和故障控制的仅监听模式
④传输数据(TXD)主要超时功能与诊断(如何实现?)
⑤TJA1043支持五种操作模式。控制引脚STB_N和EN用于选择操作模式。 在模式之间切换允许通过引脚ERR_N访问许多诊断标志。
其他都略过了,详细看一下7.1 Operating modes

正常模式Normal mode
在正常模式下,收发器可以通过总线CANH和CANL传输和接收数据。差分接收器将总线上的模拟数据转换为数字数据,然后输出到引脚RXD。引脚INH是运行的,因此由引脚INH控制的电压调节器也将是运行的。
只听模式 Listen-only mode
在只听模式下,收发器的发射器被禁用,有效地提供了收发器的只听”功能。接收器仍将将引脚CANH和CANL上的模拟总线信号转换为数字数据,可通过引脚RXD输出,引脚INH保持运行的。
待机模式Standby mode
待机模式是TJA1043的一级节电模式,提供减少电流消耗。在备用模式下,收发器无法传输或接收数据,并激活低功率接收机以监控总线活动。引脚INH仍然是运行的,所以由这个引脚控制的电压调节器也将是运行的。Pins RXD和ERR_N将反映任何活动的唤醒请求。
进入睡眠模式Go-to-Sleep mode
进入睡眠模式是进入睡眠模式的控制路径。在进入睡眠模式下,收发器表现为待机模式,并附加了一个向收发器发出进入睡眠的命令。在进入睡眠模式之前,收发器将保持最短保持时间。如果pin STB_N或pin EN的状态发生改变,或者在结束(分钟)之前设置了唤醒标志,收发器将不会进入休眠模式。
睡眠模式 Sleep mode
睡眠模式是TJA1043的二级节电模式。睡眠模式通过进入睡眠模式进入,当VCC或VIO的欠压检测时间在相关电压水平恢复时进入。在睡眠模式下,收发器按照待机模式的描述行为,除了引脚INH设置为浮动。由此引脚控制的电压调节器将关闭,进入引脚VBAT的电流将减少到最小。pin STB_N、EN和Wake标志可以用来从睡眠模式中唤醒。
:INH拉出控制供电芯片的使能脚,也就是通过该引脚控制电源芯片是否使能。

再翻译一下 7.2内部标志
TJA1043使用7个内部标志为其故障安全回退模式控制和系统诊断支持。控制器可以通过引脚ERR_N轮询其中五个标志。在任何时候,引脚ERR_N上可用的标志取决于活动操作模式和许多其他条件。

UVNOM标志
UVNOM是VCC和VIO欠压检测标志。当引脚VCC上的电压低于VCC欠压检测电压Vuvd(VCC),且持续时间超过欠压检测时间tdet(uv),或者当引脚VIO上的电压低于Vuvd(VIO)且持续时间超过tdet(uv)时,设置该标志。设置UVNOM标志后,收发器进入Sleep模式,节省电能,保证总线不受干扰。在睡眠模式下,连接到引脚INH的稳压器是禁用的,避免任何额外的功耗,可能产生的短路条件。任何唤醒请求,设置Pwon标志或STB_N上的LOW-to-HIGH转换将清除UVNOM和定时器,允许稳压器重新激活(至少直到UVNOM再次被设置)。如果VCC和VIO恢复时间超过欠压恢复时间trec(uv), UVNOM也将被清除。然后,收发器将切换到由引脚STB_N和EN上的逻辑电平指示的工作模式。
UVBAT 标志
UVBAT是VBAT欠压检测标志。当引脚VBAT上的电压低于vvd (VBAT)时设置该标志。当设置UVBAT时,收发器将尝试进入待机模式以节省电力,并将从总线断开(零负载)。当引脚VBAT电压恢复时,UVBAT被清除。然后,收发器将切换到由引脚STB_N和EN上的逻辑电平指示的工作模式。
Pwon 标志
Pwon是VBAT开机标志。当引脚VBAT上的电压在之前下降到vvd (VBAT)以下后恢复时,设置该标志(通常是因为电池断开)。设置Pwon标志可以清除UVNOM标志和定时器。唤醒和唤醒源标志的设置,以确保在所有供应条件下的一致系统上电。在Listen-only模式下,Pwon标志可以通过引脚ERR_N进行轮询。当收发器进入Normal模式时,该标志被清除。
wake 标志
当收发器检测到本地或远程唤醒请求时设置唤醒标志。当引脚WAKE上的逻辑电平发生变化时,检测到本地唤醒请求,并且新电平至少在唤醒后保持稳定。可在待机模式、转睡眠模式或睡眠模式下设置唤醒标志。设置唤醒标志可以清除UVNOM标志和定时器。一旦设置,唤醒标志状态立即在引脚ERR_N和RXD上可用(提供VIO和VBAT)。该标志也在上电时设置,当设置UVNOM标志或收发器进入正常模式时清除。
Remote wake-up (via the CAN bus)
当总线上检测到专用的唤醒模式(在ISO 11898- 2:16中指定)时,TJA1043从待机或睡眠模式中唤醒。这种过滤有助于避免虚假的唤醒事件。一个虚假的唤醒序列可以被触发,例如,一个主导箝位总线或主导相位由噪声或尖峰总线上。唤醒模式包括:
•至少唤醒(busdom)的主导阶段紧随其后
•随后是至少苏醒(busrec)的隐性阶段
•至少唤醒(busdom)的主导阶段
在上述阶段之间的显性或隐性位,分别比twake(busdom) twake(busrec)短被忽略。在tto(wake)总线内必须接收到完全的显性-隐性-显性模式,才能被识别为有效的唤醒模式(见图5)。否则,内部唤醒逻辑将被重置。然后需要重新传输完整的唤醒模式来触发唤醒事件。引脚RXD保持高,直到唤醒事件被触发。当收到一个有效的唤醒模式时,如果以下任何一个事件发生,RXD上的唤醒事件不会被标记:
•TJA1043切换到普通模式
•在to(wake)总线中没有接收到完整的唤醒模式
•检测到VCC或VIO欠压
剩下的标志也不翻译了也不大能用上。。。。

二、S32K146 FLEXCAN SDK解读:
FlexCAN 模块是一个通信控制器,该模块实现了 CAN 协议即CAN2.0B 协议规范。 FlexCAN 模块的字模块,包括了用来存储消息缓冲的相关联的内存区域,Rx 全局掩码寄存器、Rx 私有掩码寄存器、Rx 先进先出队列以及 Rx 队列标识过滤器。消息缓冲区存储在一个专用于 FlexCAN 模块的 RAM 区,请求存取 RAM 接收和传输消息帧,验证接收到的消息以及进行错误处理。控制器主机接口子模块用来选择接收和传输的消息缓冲区,使用仲裁与 ID 匹配算法,以建立同 CPU 或者其他模块的连接。
模块特征

  1. 0 到 8
    字节长度报文缓冲区,每个报文缓冲区都可以配置成发送缓冲区或者接受缓冲区,支持标准和扩展帧格式,每个消息缓冲区都有自己的接受掩码控制寄存器
  2. 全功能的接受队列,该队列可以存储最多 6 个帧,并且自动进行内部指针处理,传输中止能力;可编程的 CAN 协议接口的时钟源,可以是总线时钟也可以是外部晶振;没有使用的结构空间可以当成普通的 RAM 空间使用;等其他特征。

操作模式

  1. 正常模式(用户与管理员) 在正常模式下,CAN 模块收发数据帧、处理错误,CAN协议的所有功能全部开启。对于一些控制比较严格的寄存器,在用户模式和管理员模式下访问时又区别的。
  2. 冻结模式
    如果 MCR 寄存器的 FRZ 位被置位,那么将开启 CAN 模块的冻结模式。当 MCR 的 HALT 位置位时或者在 MCU 级请求调试模式(Debug Mode)并且 MCR 寄存器的FRZ_ACK 位被置位时,CAN
    模块将进入到冻结模式。
  3. 监听模式
    当控制 1 寄存器的 LOM 位置位时,模块将进入到监听模式。在该模式下,CAN 模块禁止数据收发,所有的错误计数器都被冻结,且该模块工作在 CAN 被动错误模式。只有被其他 CAN节点应答了的报文才可以被监听的节点接受。如果 FlexCAN 检测到一个还没有被应答的报文,则标记为一个 BIT0 错误(不会改变 REC),将如同它试图应答报文一些样。
  4. 监听模式
    如果控制 1 寄存器的 LPB 位被置位,模块将进入到回环模式。在该模式下,FlexCAN 工作在内部闭环模式用于自测。从发送器发送出的比特流输出回内部的接收器输入。输入引脚将会被忽略,并且输出引脚将处于逻辑 1 状态。发送报文时,FlexCAN 模块和正常模式一样,而接收器则认为接受它自己的报文与接受远程节点的报文相同。FlexCAN 为保证能正确接受到自己发送的报文,将忽略应答间隙内的应答字段。报文接受发送时,如果中断使能则 FlexCAN 将向CPU 产生中断。
  5. 模块禁止模式
    当 MCR 寄存器的 MDIS 位被 CPU 置位并且 LPM_ACK 位被FlexCAN 模块置位时,模块将会进入该低功耗模式。如果模块被禁止,那么模块将会请求停止 CAN 协议引擎的时钟并且请求禁止控制器主机接口子模块。通过忽略 MCR 寄存器 MDIS 位可以退出该模式。有关该模块的更多信息参考“模块禁止模式”节。
  6. 睡眠模式
    当 MCR 寄存器的 DOZE 位被置位、在 MCU 级请求睡眠模式并且由 FlexCAN 置位 MCR 寄存器的 LPM_ACK 位时模块将进入到该低功耗模式。当模块处于睡眠模式时,FlexCAN 将会请求禁止CAN 协议引擎的时钟并且请求禁止 CAN 控制器主机接口子系统。当 MCR 寄存器的 DOZE 位被忽略(negated)、当 MCU 离开睡眠模式时或者当检测到 CAN 总线上有活动并且自醒机制开启时模块将退出睡眠模式。
  7. 停止模式
    在 MCU 级请求模式并且由 FlexCAN 模块置位 MCR 寄存器的 LPM_ACK 位时,模块将会进入到该低功耗模式。在停止模式中,模块将会将自己置于不活动状态,然后通知 CPU 可以关闭所有的时钟。当请求离开停止模式或者当检测到 CAN 总线上有或者并且开启了自醒即使时,FlexCAN将会退出该模式。

SDK函数分析(一)

//这个函数将为经典帧设置所有的时间段值,
//或者为FD帧的仲裁阶段设置扩展的时间段。
//这些时间段值由用户传入,并基于所需的波特率。
void FLEXCAN_DRV_SetBitrate(uint8_t instance, const flexcan_time_segment_t *bitrate)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    DEV_ASSERT(bitrate != NULL);

    CAN_Type * base = g_flexcanBase[instance];
#if FEATURE_CAN_HAS_FD
    bool fdEnabled = FLEXCAN_IsFDEnabled(base);
#endif
//只有在冻结模式下才能被写入,在其他模式下该位被硬件锁定
    FLEXCAN_EnterFreezeMode(base);

#if FEATURE_CAN_HAS_FD
    if (fdEnabled)
    {
        /* Set extended time segments*/
        FLEXCAN_SetExtendedTimeSegments(base, bitrate);
    }
    else
#endif
    {
        /* Set time segments*/
        FLEXCAN_SetTimeSegments(base, bitrate);
    }

    FLEXCAN_ExitFreezeMode(base);
}
//
static inline void FLEXCAN_SetTimeSegments(CAN_Type * base, const flexcan_time_segment_t *timeSeg)
{
    DEV_ASSERT(timeSeg != NULL);
//0bit 传播时间段 16-18bit 相位段2位时间长度 19-21 相位段1位时间长度
//31-24 bit 预分频器分频系数
    (base->CTRL1) = ((base->CTRL1) & ~((CAN_CTRL1_PROPSEG_MASK | CAN_CTRL1_PSEG2_MASK |
                                        CAN_CTRL1_PSEG1_MASK | CAN_CTRL1_PRESDIV_MASK) |
                                        CAN_CTRL1_RJW_MASK));

    (base->CTRL1) = ((base->CTRL1) | (CAN_CTRL1_PROPSEG(timeSeg->propSeg) |
                                      CAN_CTRL1_PSEG2(timeSeg->phaseSeg2) |
                                      CAN_CTRL1_PSEG1(timeSeg->phaseSeg1) |
                                      CAN_CTRL1_PRESDIV(timeSeg->preDivider) |
                                      CAN_CTRL1_RJW(timeSeg->rJumpwidth)));
}
//FLEXCAN进入冻结模式
void FLEXCAN_EnterFreezeMode(CAN_Type * base)
{
	bool enabled = false;
    //使能进入冻结模式
    base->MCR = (base->MCR & ~CAN_MCR_FRZ_MASK) | CAN_MCR_FRZ(1U);
    //暂停FlexCAN模块
    base->MCR = (base->MCR & ~CAN_MCR_HALT_MASK) | CAN_MCR_HALT(1U);
//判断是否关FLEXCAN模块
    if (((base->MCR & CAN_MCR_MDIS_MASK) >> CAN_MCR_MDIS_SHIFT) == 0U)
	{
		enabled = true;
	}
	else
	{
		base->MCR &= ~CAN_MCR_MDIS_MASK;
	}
    //冻结模式确认位
    while (((base->MCR & CAN_MCR_FRZACK_MASK) >> CAN_MCR_FRZACK_SHIFT) == 0U) {}
    if (false == enabled)
    {
    	base->MCR |= CAN_MCR_MDIS_MASK;
    	//是否进入低功耗
        while (((base->MCR & CAN_MCR_LPMACK_MASK) >> CAN_MCR_LPMACK_SHIFT) == 0U) {}
    }
}
//这个函数将返回经典帧或FD帧仲裁阶段的当前比特率设置。
void  FLEXCAN_DRV_GetBitrate(uint8_t instance, flexcan_time_segment_t *bitrate)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    DEV_ASSERT(bitrate != NULL);

    const CAN_Type * base = g_flexcanBase[instance];
#if FEATURE_CAN_HAS_FD
    if (true == FLEXCAN_IsFDEnabled(base))
    {
    	/* Get the Extended time segments*/
    	FLEXCAN_GetExtendedTimeSegments(base, bitrate);
    }
    else
#endif
    {
    	/* Get the time segments*/
    	FLEXCAN_GetTimeSegments(base, bitrate);
    }
}
//这个函数将返回经典帧或FD帧仲裁阶段的当前比特率设置
static inline void FLEXCAN_GetTimeSegments(const CAN_Type * base, flexcan_time_segment_t *timeSeg)
{
    DEV_ASSERT(timeSeg != NULL);
//得到刚才寄存器里面配置的值
    timeSeg->preDivider = ((base->CTRL1) & CAN_CTRL1_PRESDIV_MASK) >> CAN_CTRL1_PRESDIV_SHIFT;
    timeSeg->propSeg = ((base->CTRL1) & CAN_CTRL1_PROPSEG_MASK) >> CAN_CTRL1_PROPSEG_SHIFT;
    timeSeg->phaseSeg1 = ((base->CTRL1) & CAN_CTRL1_PSEG1_MASK) >> CAN_CTRL1_PSEG1_SHIFT;
    timeSeg->phaseSeg2 = ((base->CTRL1) & CAN_CTRL1_PSEG2_MASK) >> CAN_CTRL1_PSEG2_SHIFT;
    timeSeg->rJumpwidth = ((base->CTRL1) & CAN_CTRL1_RJW_MASK) >> CAN_CTRL1_RJW_SHIFT;
}
//关于FLEXCAN的SDK分成两层 上层:flexcan_driver.c 底层flexcan_access.c
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104

SDK函数分析(二)


//Set RX masking type
//flexcan_rx_mask_type_t  分为全局/个人屏蔽
void  FLEXCAN_DRV_SetRxMaskType(uint8_t instance, flexcan_rx_mask_type_t type)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);

    CAN_Type * base = g_flexcanBase[instance];
    FLEXCAN_EnterFreezeMode(base);//上边分析了
    FLEXCAN_SetRxMaskType(base, type);
    FLEXCAN_ExitFreezeMode(base);//上边分析了
}
//判断是全局还是个人屏蔽
static inline void FLEXCAN_SetRxMaskType(CAN_Type * base, flexcan_rx_mask_type_t type)
{
    //关闭个别接收掩码和队列功能。
    if (type == FLEXCAN_RX_MASK_GLOBAL)
    {
        base->MCR = (base->MCR & ~CAN_MCR_IRMQ_MASK) | CAN_MCR_IRMQ(0U);
    }
    else//开启个别接收掩码和队列功能。
    {
        base->MCR = (base->MCR & ~CAN_MCR_IRMQ_MASK) | CAN_MCR_IRMQ(1U);
    }
}

//设置Rx FIFO全局掩码为11位标准掩码或29位扩展掩码
void FLEXCAN_DRV_SetRxFifoGlobalMask(
    uint8_t instance,
    flexcan_msgbuff_id_type_t id_type,
    uint32_t mask)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    //FLEXCAN_GetRxFifoIdFormat中两位用来定义接受队列过滤器表元素的格式
    // 格式A:每一个ID过滤表元素有一个全ID(标准的以及扩展的);
    //格式B:每一个ID过滤表元素都有两个全标准IDs或者两个14比特(标准的及扩展的)的IDs;
    //格式C:每一个过滤表元素有四个8比特标准IDs;
    //格式D:拒绝所有的帧
    flexcan_rx_fifo_id_element_format_t formatType;
    CAN_Type * base = g_flexcanBase[instance];
    uint32_t calcMask = 0U;

    FLEXCAN_EnterFreezeMode(base);
    //判断是否开启接收队列。该位用来控制是否开启接收队列
    if (true == FLEXCAN_IsRxFifoEnabled(base))
    {   //返回过滤器元素的格式
		formatType = FLEXCAN_GetRxFifoIdFormat(base);
		calcMask = FLEXCAN_GetRxFifoMask(id_type, formatType, mask);
//FLEXCAN_SetRxFifoGlobalMask设置接收队列 FIFO 全局掩码寄存器(CANx_RXFGMASK)该寄存器位于 RAM 区。
//如果接收 FIFO 队列被使能,RXFGMASK 被用来屏蔽接收队列 FIFO 过滤器表元素,
//这些表元素由于 CTRL2[RFEN]字段的设定并没有相应的 RXIMR。该寄存器只能在冻结模式下被写入,在其他模式下该字段被硬件锁定。
		switch (formatType)
		{
//FLEXCAN_SetRxFifoGlobalMask
//设置接收队列FIFO全局掩码位。这些比特位以完美对齐的方式用来屏蔽ID过滤器表元素
			case FLEXCAN_RX_FIFO_ID_FORMAT_A :
				FLEXCAN_SetRxFifoGlobalMask(base, calcMask);
				break;
			case FLEXCAN_RX_FIFO_ID_FORMAT_B :
				FLEXCAN_SetRxFifoGlobalMask(base, (calcMask | (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATB_EXT_SHIFT1)));
				break;
			case FLEXCAN_RX_FIFO_ID_FORMAT_C :
				FLEXCAN_SetRxFifoGlobalMask(base, (calcMask | (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATC_SHIFT1) |
												  	  	  	  (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATC_SHIFT2) |
															  (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATC_SHIFT3)));
				break;
			default :
				FLEXCAN_SetRxFifoGlobalMask(base, 0xFFFFFFFFU);
				break;
		}
    }
    FLEXCAN_ExitFreezeMode(base);
}
//设置邮箱全局掩码
void FLEXCAN_DRV_SetRxMbGlobalMask(
    uint8_t instance,
    flexcan_msgbuff_id_type_t id_type,
    uint32_t mask)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    CAN_Type * base = g_flexcanBase[instance];
    FLEXCAN_EnterFreezeMode(base);

    if (id_type == FLEXCAN_MSG_ID_STD)
    {
//设置接受邮箱全局掩码寄存器(CANx_RXMGMASK)
        FLEXCAN_SetRxMsgBuffGlobalStdMask(base, mask);
    }
    else if (id_type == FLEXCAN_MSG_ID_EXT)
    {
        FLEXCAN_SetRxMsgBuffGlobalExtMask(base, mask);
    }
    else {
    }
    FLEXCAN_ExitFreezeMode(base);
}
//缓冲区14过滤字段提供掩码
void FLEXCAN_DRV_SetRxMb14Mask(
    uint8_t instance,
    flexcan_msgbuff_id_type_t id_type,
    uint32_t mask)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    CAN_Type * base = g_flexcanBase[instance];
    FLEXCAN_EnterFreezeMode(base);
    if (id_type == FLEXCAN_MSG_ID_STD)
    {
//提供该寄存器是为了对传统的支持。当 MCR[IRMQ]位被置位时,RX14MASK 无效。
//RX14MASK 用来给报文缓冲区 14 的过滤字段提供掩码。
        FLEXCAN_SetRxMsgBuff14StdMask(base, mask);
    }
    else if (id_type == FLEXCAN_MSG_ID_EXT)
    {
        FLEXCAN_SetRxMsgBuff14ExtMask(base, mask);
    }
    else {
    }
    FLEXCAN_ExitFreezeMode(base);
}
//RX15MASK 用来给报文缓冲区 15 的过滤字段提供掩码。
void FLEXCAN_DRV_SetRxMb15Mask(
    uint8_t instance,
    flexcan_msgbuff_id_type_t id_type,
    uint32_t mask)
{
//略同上
}
//设置个别掩码
status_t FLEXCAN_DRV_SetRxIndividualMask(
    uint8_t instance,
    flexcan_msgbuff_id_type_t id_type,
    uint8_t mb_idx,
    uint32_t mask)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    CAN_Type * base = g_flexcanBase[instance];
    FLEXCAN_EnterFreezeMode(base);
//MAXMB最大报文缓冲区个数
    if ((mb_idx > FLEXCAN_GetMaxMsgBuffNum(base)) || (mb_idx >= CAN_RXIMR_COUNT))
    {
        FLEXCAN_ExitFreezeMode(base);
        return STATUS_CAN_BUFF_OUT_OF_RANGE;
    }
//开启接收队列
    if (false == FLEXCAN_IsRxFifoEnabled(base))
    {//标准帧
		if (id_type == FLEXCAN_MSG_ID_STD)
		{
//设置接收私有掩码寄存器
			FLEXCAN_SetRxIndividualStdMask(base, mb_idx, mask);
		}
		else if (id_type == FLEXCAN_MSG_ID_EXT)
		{
//设置接收私有掩码寄存器
			FLEXCAN_SetRxIndividualExtMask(base, mb_idx, mask);
		}
		else
		{
		}
    }
    else
    {//单个掩码配置的最大过滤器是(7 + RFFN * 2)
		if (mb_idx <= FLEXCAN_GetNoOfIndividualMBsRxFIFO(base))
		{
//返回过滤器元素的格式
			flexcan_rx_fifo_id_element_format_t formatType = FLEXCAN_GetRxFifoIdFormat(base);
//以FIFO模式的格式ID类型计算全局掩码
			uint32_t calcMask = FLEXCAN_GetRxFifoMask(id_type, formatType, mask);
			switch (formatType)
			{
				case FLEXCAN_RX_FIFO_ID_FORMAT_A :
					FLEXCAN_SetRxIndividualMask(base, mb_idx, calcMask);//设置接收私有掩码寄存器,以FIFO模式的格式ID类型计算全局掩码
					break;
				case FLEXCAN_RX_FIFO_ID_FORMAT_B :
					FLEXCAN_SetRxIndividualMask(base, mb_idx, (calcMask | (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATB_EXT_SHIFT1)));
					break;
				case FLEXCAN_RX_FIFO_ID_FORMAT_C :
					FLEXCAN_SetRxIndividualMask(base, mb_idx, (calcMask | (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATC_SHIFT1) |
																  	  	  (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATC_SHIFT2) |
																		  (calcMask >> FLEXCAN_RX_FIFO_ID_FILTER_FORMATC_SHIFT3)));
					break;
				default :
					FLEXCAN_SetRxIndividualMask(base, mb_idx, 0xFFFFFFFFU);
					break;
			}
		}
    	else
    	{
    		if (id_type == FLEXCAN_MSG_ID_STD)
    		{
    			FLEXCAN_SetRxIndividualStdMask(base, mb_idx, mask);
    		}
    		else if (id_type == FLEXCAN_MSG_ID_EXT)
    		{
    			FLEXCAN_SetRxIndividualExtMask(base, mb_idx, mask);
    		}
    		else
    		{
    		}
    	}
    }
    FLEXCAN_ExitFreezeMode(base);
    return STATUS_SUCCESS;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205

SDK函数分析(三)

//flexcan初始化
status_t FLEXCAN_DRV_Init(
   uint8_t instance,
   flexcan_state_t *state,
   const flexcan_user_config_t *data)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    DEV_ASSERT(state != NULL);
    DEV_ASSERT(g_flexcanStatePtr[instance] == NULL);

    status_t result;
    CAN_Type * base = g_flexcanBase[instance];
    flexcan_time_segment_t bitrate;
    status_t osifStat;
    uint32_t i, j;

    if(FLEXCAN_IsEnabled(base))
    {
        FLEXCAN_EnterFreezeMode(base);
        FLEXCAN_Disable(base);
    }
//CAN引擎时钟源。CTRL1 13BIT 该位用来为CAN协议引擎(PE)选择时钟源,
//可以是设备外设时钟(有PLL驱动)也可以是外部晶体振荡器时钟。
#if FEATURE_CAN_HAS_PE_CLKSRC_SELECT
    FLEXCAN_SelectClock(base, data->pe_clock);
#endif
    /* Enable the CAN clock */
    FLEXCAN_Enable(base);
    FLEXCAN_EnterFreezeMode(base);
    //初始化 1.复位 2.中止传输 3.清空RAM 4.寄存器重置
    //5.状态与错误寄存器init
    FLEXCAN_Init(base);
#if FEATURE_CAN_HAS_FD
    FLEXCAN_SetFDEnabled(base, data->fd_enable);
    if (FLEXCAN_IsFDEnabled(base) != data->fd_enable)
    {
        return STATUS_ERROR;
    }//flexcan_user_config_t配置FD是否使能
    FLEXCAN_SetStuffBitCount(base, data->fd_enable);
#endif
//不是回环模式
    if (data->flexcanMode != FLEXCAN_LOOPBACK_MODE)
    {//MCR->SRXDIS使能自接受
        FLEXCAN_SetSelfReception(base, false);
    }
    if (data->is_rx_fifo_needed)
    {//使能循环队列 1.mcr->rfen 2.CTRL->RFEN接收队列FIFO过滤器的数目
    //3.设置接受队列全局掩码寄存器(RXFGMASK)
    //4.接收私有掩码寄存器RXIMR
        result = FLEXCAN_EnableRxFifo(base, (uint32_t)data->num_id_filters);
        if (result != STATUS_SUCCESS)
        {
            return result;
        }
    }
#if FEATURE_CAN_HAS_DMA_ENABLE
    /* Enable DMA support for RxFIFO transfer, if requested. */
    if (data->transfer_type == FLEXCAN_RXFIFO_USING_DMA)
    {
        if (FLEXCAN_IsRxFifoEnabled(base))
        {
            FLEXCAN_SetRxFifoDMA(base, true);
        }
        else
        {
            return STATUS_ERROR;
        }
    }
    if (data->transfer_type == FLEXCAN_RXFIFO_USING_INTERRUPTS)
    {
    	FLEXCAN_SetRxFifoDMA(base, false);
    }
#endif

#if FEATURE_CAN_HAS_FD
//这个寄存器包含CAN FD操作的控制位。
// 它还定义了分配在RAM(内存块)的不同分区中的消息缓冲区的数据大小,如下表所示。
    FLEXCAN_SetPayloadSize(base, data->payload);
#endif
//设置缓冲区数量
    result = FLEXCAN_SetMaxMsgBuffNum(base, data->max_num_mb);
    if (result != STATUS_SUCCESS)
    {
        return result;
    }

#if FEATURE_CAN_HAS_FD
    if (FLEXCAN_IsFDEnabled(base))
    {
        bitrate = data->bitrate;
        FLEXCAN_SetExtendedTimeSegments(base, &bitrate);
        bitrate = data->bitrate_cbt;
        FLEXCAN_SetFDTimeSegments(base, &bitrate);
    }
    else
#endif
    {
        bitrate = data->bitrate;
        FLEXCAN_SetTimeSegments(base, &bitrate);
    }
    FLEXCAN_SetOperationMode(base, data->flexcanMode);
    if (data->flexcanMode != FLEXCAN_FREEZE_MODE)
    {
    	FLEXCAN_ExitFreezeMode(base);
    }
    FLEXCAN_EnableIRQs(instance);
    for (i = 0; i < FEATURE_CAN_MAX_MB_NUM; i++)
    {
        osifStat = OSIF_SemaCreate(&state->mbs[i].mbSema, 0U);
        if (osifStat != STATUS_SUCCESS)
        {
            for (j = 0; j < i; j++)
            {
                (void)OSIF_SemaDestroy(&state->mbs[j].mbSema);
            }
            return STATUS_ERROR;
        }
        state->mbs[i].isBlocking = false;
        state->mbs[i].mb_message = NULL;
        state->mbs[i].state = FLEXCAN_MB_IDLE;
    }
#if FEATURE_CAN_HAS_MEM_ERR_DET
    FLEXCAN_DisableMemErrorDetection(base);
#endif
    state->transferType = data->transfer_type;
#if FEATURE_CAN_HAS_DMA_ENABLE
    state->rxFifoDMAChannel = data->rxFifoDMAChannel;
#endif
    state->callback = NULL;
    state->callbackParam = NULL;
    state->error_callback = NULL;
    state->errorCallbackParam = NULL;
    g_flexcanStatePtr[instance] = state;
    return (STATUS_SUCCESS);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

SDK函数分析(四)

//发送mb cofig
status_t FLEXCAN_DRV_ConfigTxMb(
    uint8_t instance,
    uint8_t mb_idx,
    const flexcan_data_info_t *tx_info,
    uint32_t msg_id)
{
    DEV_ASSERT(instance < CAN_INSTANCE_COUNT);
    DEV_ASSERT(tx_info != NULL);

    flexcan_msgbuff_code_status_t cs;
    CAN_Type * base = g_flexcanBase[instance];
    //这个没啥说的就是赋值
    cs.dataLen = tx_info->data_length;
    cs.msgIdType = tx_info->msg_id_type;
#if FEATURE_CAN_HAS_FD
    cs.enable_brs = tx_info->enable_brs;
    cs.fd_enable = tx_info->fd_enable;
    cs.fd_padding = tx_info->fd_padding;
#endif
    cs.code = (uint32_t)FLEXCAN_TX_INACTIVE;
    //下边解释
    return FLEXCAN_SetTxMsgBuff(base, mb_idx, &cs, msg_id, NULL);
}
//设置flexcan 发送msg buff
status_t FLEXCAN_SetTxMsgBuff(
    CAN_Type * base,
    uint32_t msgBuffIdx,
    const flexcan_msgbuff_code_status_t *cs,
    uint32_t msgId,
    const uint8_t *msgData)
{
    DEV_ASSERT(cs != NULL);
    uint32_t val1, val2 = 1;
    uint32_t flexcan_mb_config = 0;
    uint32_t databyte;
    uint8_t dlc_value;
    status_t stat = STATUS_SUCCESS;
//这个函数多墨迹几句在四的结尾处
    volatile uint32_t *flexcan_mb = FLEXCAN_GetMsgBuffRegion(base, msgBuffIdx);
//拿到刚才给的地址 指针指向
    volatile uint32_t *flexcan_mb_id   = &flexcan_mb[1];
    volatile uint8_t  *flexcan_mb_data = (volatile uint8_t *)(&flexcan_mb[2]);
    volatile uint32_t *flexcan_mb_data_32 = &flexcan_mb[2];
    const uint32_t *msgData_32 = (const uint32_t *)msgData;
//最大缓冲区的个数
    if (msgBuffIdx > (((base->MCR) & CAN_MCR_MAXMB_MASK) >> CAN_MCR_MAXMB_SHIFT) )
    {
        stat = STATUS_CAN_BUFF_OUT_OF_RANGE;
    }
    if (((base->MCR & CAN_MCR_RFEN_MASK) >> CAN_MCR_RFEN_SHIFT) != 0U)
    {
        //这几句解释起来真烦
        //这个CTRL2寄存器的27-24(RFEN)
        //这4位用来定义了下表中所示的接收队列过滤器的数目
        //过滤器使用的越多,满足条件的邮箱越少
        //eg:RFEN=01 消息邮箱 MB8-63 val1=0x01
        val1 = (((base->CTRL2) & CAN_CTRL2_RFFN_MASK) >> CAN_CTRL2_RFFN_SHIFT);
        //val2 = 9
        val2 = RxFifoOcuppiedLastMsgBuff(val1);
        //为啥现在我也不知道。。。脑子不够用了,搞不动了
        if (msgBuffIdx <= val2) {
            stat =  STATUS_CAN_BUFF_OUT_OF_RANGE;
        }
    }
    if (stat == STATUS_SUCCESS)
    {
#if FEATURE_CAN_HAS_FD
        if (FLEXCAN_IsFDEnabled(base) && cs->enable_brs)
        {
            base->FDCTRL = (base->FDCTRL & ~CAN_FDCTRL_FDRATE_MASK) | CAN_FDCTRL_FDRATE(1U);
        }
        DEV_ASSERT((uint8_t)cs->dataLen <= FLEXCAN_GetPayloadSize(base));
#else
        DEV_ASSERT((uint8_t)cs->dataLen <= 8U);
#endif
        dlc_value = FLEXCAN_ComputeDLCValue((uint8_t)cs->dataLen);
        if (msgData != NULL)
        {
            uint8_t payload_size = FLEXCAN_ComputePayloadSize(dlc_value);

#if (defined(CPU_S32K116) || defined(CPU_S32K118))
            (void) msgData_32;
            databyte = FLEXCAN_DataTransferTxMsgBuff( flexcan_mb_data_32, cs, msgData);
#else
            for (databyte = 0; databyte < (cs->dataLen & ~3U); databyte += 4U)
			{
				FlexcanSwapBytesInWord(msgData_32[databyte >> 2U], flexcan_mb_data_32[databyte >> 2U]);
			}
#endif
            for ( ; databyte < cs->dataLen; databyte++)
            {
                flexcan_mb_data[FlexcanSwapBytesInWordIndex(databyte)] =  msgData[databyte];
            }
            /* Add padding, if needed */
            for (databyte = cs->dataLen; databyte < payload_size; databyte++)
            {
                flexcan_mb_data[FlexcanSwapBytesInWordIndex(databyte)] = cs->fd_padding;
            }
        }
        /* Clean up the arbitration field area */
        *flexcan_mb = 0;
        *flexcan_mb_id = 0;
        /* Set the ID according the format structure */
        if (cs->msgIdType == FLEXCAN_MSG_ID_EXT)
        {
            /* ID [28-0] */
            *flexcan_mb_id &= ~(CAN_ID_STD_MASK | CAN_ID_EXT_MASK);
            *flexcan_mb_id |= (msgId & (CAN_ID_STD_MASK | CAN_ID_EXT_MASK));
            /* Set IDE */
            flexcan_mb_config |= CAN_CS_IDE_MASK;
            /* Clear SRR bit */
            flexcan_mb_config &= ~CAN_CS_SRR_MASK;
        }
        if(cs->msgIdType == FLEXCAN_MSG_ID_STD)
        {
            /* ID[28-18] */
            *flexcan_mb_id &= ~CAN_ID_STD_MASK;
            *flexcan_mb_id |= (msgId << CAN_ID_STD_SHIFT) & CAN_ID_STD_MASK;
            /* make sure IDE and SRR are not set */
            flexcan_mb_config &= ~(CAN_CS_IDE_MASK | CAN_CS_SRR_MASK);
        }
        /* Set the length of data in bytes */
        flexcan_mb_config &= ~CAN_CS_DLC_MASK;
        flexcan_mb_config |= ((uint32_t)dlc_value << CAN_CS_DLC_SHIFT) & CAN_CS_DLC_MASK;
        /* Set MB CODE */
        if (cs->code != (uint32_t)FLEXCAN_TX_NOT_USED)
        {
            if (cs->code == (uint32_t)FLEXCAN_TX_REMOTE)
            {
                /* Set RTR bit */
                flexcan_mb_config |= CAN_CS_RTR_MASK;
            }
            /* Reset the code */
            flexcan_mb_config &= ~CAN_CS_CODE_MASK;
            /* Set the code */
            if (cs->fd_enable)
            {
                flexcan_mb_config |= ((cs->code << CAN_CS_CODE_SHIFT) & CAN_CS_CODE_MASK) | CAN_MB_EDL_MASK;
            }
            else
            {
                flexcan_mb_config |= (cs->code << CAN_CS_CODE_SHIFT) & CAN_CS_CODE_MASK;
            }
            if (cs->enable_brs)
            {
                flexcan_mb_config |= CAN_MB_BRS_MASK;
            }
            *flexcan_mb |= flexcan_mb_config;
        }
    }
    return stat;
}
//根据msgBuffIdx返回地址
volatile uint32_t* FLEXCAN_GetMsgBuffRegion(
        CAN_Type * base,
        uint32_t msgBuffIdx)
{
#if FEATURE_CAN_HAS_FD
    uint8_t payload_size = FLEXCAN_GetPayloadSize(base);
#else
    uint8_t payload_size = 8U;
#endif
    uint8_t arbitration_field_size = 8U;
//#define CAN_RAMn_COUNT                           128u    
//typedef struct 
//{
// __IO uint32_t RAMn[CAN_RAMn_COUNT]; array offset: 0x80, 
//} CAN_Type, *CAN_MemMapPtr;
//根据上边这几句所以uint32_t ramBlockSize = 512U;
    uint32_t ramBlockSize = 512U;
    uint32_t ramBlockOffset;
    //一个MB的大小
    uint8_t mb_size = (uint8_t)(payload_size + arbitration_field_size);
    //按这个算最大的mb个数
    uint8_t maxMbNum = (uint8_t)(ramBlockSize / mb_size);
    //偏移 eg msgBuffIdx =1 ramBlockOffset =0
    ramBlockOffset = 128U * (msgBuffIdx / (uint32_t)maxMbNum);
    //在这块ram中索引=0+1*16/4=4
    uint32_t mb_index = ramBlockOffset + ((msgBuffIdx % (uint32_t)maxMbNum) * ((uint32_t)mb_size >> 2U));
    return &(base->RAMn[mb_index]);
}
//最大512个字节 根据mb_size算一共有几个偏移多少返回地址 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183

初始化需要的SDK函数

static void Can0Init(void)
{
    //TJA1043 EN STB 全部使能高电平进入正常收发状态
    hal_mcu_can_modeset();
    FLEXCAN_DRV_Init(INST_CANCOM0, &canCom0_State, &canCom0_InitConfig0);
    FLEXCAN_DRV_ConfigRxMb(INST_CANCOM0, RX_MAILBOX_0, &data_std_info, 0x87);
	FLEXCAN_DRV_ConfigTxMb(INST_CANCOM0, 1, &data_std_info, 0x228);
    FLEXCAN_DRV_InstallEventCallback(INST_CANCOM0, canRxCallback, NULL);
    FLEXCAN_DRV_Receive(INST_CANCOM0, RX_MAILBOX_0, &recvMsg0);//CALLBACK 发什么回什么
void canRxCallback(uint8_t instance, flexcan_event_type_t eventType,
		uint32_t buffIdx, flexcan_state_t *flexcanState) {
	if(eventType == FLEXCAN_EVENT_RX_COMPLETE)
	{
			FLEXCAN_DRV_Receive(INST_CANCOM0, RX_MAILBOX_0, &recvMsg0);
			FLEXCAN_DRV_Send(INST_CANCOM0, TX_MAILBOX_0 , &data_std_info , recvMsg0.msgId, recvMsg0.data);
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

发送什么回复什么
接下来搞一下掩码FIFO
NXP S32K146 CAN通讯 TJA1043(二)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/144558
推荐阅读
相关标签
  

闽ICP备14008679号