CP AUTOSAR中Icu Driver的详细分析与使用指南

文章目录

  • 前言
  • 一、原理解析
  • 二、代码架构
  • 三、主要变量和类型描述
  • 四、主要代码描述
  • 五、EBTresos配置
  • 六、使用范例
  • 七、参考资料
  • 总结

  • 前言

    本文介绍CP AUTOSAR 架构下的Icu组件,基于S32K312芯片、NXP提供的MCAL包,使用EB Tresos工具进行配置的经验,不具体介绍芯片输入捕获外设等功能。
    Icu组件实现MCU的输入捕获功能。
    Icu组件位于I/O Drivers层里,为上层组件IoHwAb提供接口。


    一、原理解析

    Icu组件有以下几个功能和概念:
    (一)、MeasurementMode:
    Icu组件支持四种测量模式,可以将每个Channel配置成不同的模式,一个Channel只支持一种模式。
    1、ICU_MODE_SIGNAL_EDGE_DETECT:
    信号边沿检测。
    2、ICU_MODE_SIGNAL_MEASUREMENT:
    信号测量。
    3、ICU_MODE_TIMESTAMP:
    时间戳。
    4、ICU_MODE_EDGE_COUNTER:
    边沿计数。

    (二)、Icu channel:
    Icu的软件通道,每个通道可以选择不同的测量模式,每个软件通道需要映射到不同的硬件通道,像S32K3这芯片支持输入捕获的硬件有Emios、Siul2、LpCmp等,这些硬件的通道可以实现Icu的不同测量功能,每个硬件通道只能映射到一个软件通道。

    (三)、Icu module mode:
    Icu组件跟Gpt组件一样有ICU_MODE_NORMAL和ICU_MODE_SLEEP两种模式。正常工作时在NORMAL模式下,SLEEP模式下所有有用到输入捕获的硬件通道关闭,并且不能进行测量。
    在进入SLEEP模式下前如果使能了唤醒,那硬件通道不会关闭并且收到捕获信号后通知EcuM组件有唤醒信号。

    (四)、Signal edge notification:
    信号边沿检测通知,可以捕获到上升沿、下降沿、双边沿进入回调中断。
    Icu通道被设置成ICU_MODE_SIGNAL_EDGE_DETECT后,可以设置成ICU_RISING_EDGE或ICU_FALLING_EDGE或ICU_BOTH_EDGES捕获来触发通知。
    调用Icu_EnableEdgeDetection()进行开始检测。
    调用Icu_EnableNotification()使能通知,当检测到PWM波的上升沿或下降沿或双边沿其中之一时,Icu_EnableNotification()使能的回调函数被触发。
    调用Icu_GetInputState()可以知道当前是否触发了要捕获的状态。

    (五)、Periodic signal time measurement:
    周期信号测量,可以测量低脉冲、高脉冲、脉冲周期的时间和占空比。
    Icu通道被设置成ICU_MODE_SIGNAL_MEASUREMENT后,支持ICU_LOW_TIME、ICU_HIGH_TIME、ICU_PERIOD_TIME、ICU_DUTY_CYCLE其中之一的测量模式,支持ICU_RISING_EDGE或ICU_FALLING_EDGE或ICU_BOTH_EDGES捕获,ICU_LOW_TIME只能对应ICU_FALLING_EDGE,ICU_HIGH_TIME只能对应ICU_RISING_EDGE。
    调用Icu_StartSignalMeasurement()进行开始测量。
    调用Icu_GetInputState()可以知道当前是否触发了要捕获的状态。
    调用Icu_GetTimeElapsed()获取捕获到的时间,返回值为tick,通过通道对应的硬件时钟来知道该tick时间为多少。非ICU_DUTY_CYCLE测量模式下使用。
    调用Icu_GetDutyCycleValues()可以知道捕获后一个周期的占空比,输出值为ActiveTime和PeriodTime,两个相除就是占空比,返回值也是tick。ICU_DUTY_CYCLE测量模式下使用。

    (六)、Edge time stamping:
    时间戳,可以记录每次捕获完的tick,并且存储在外部Buffer里,可以用在测量非周期性的信号的场景。
    Icu通道被设置成ICU_MODE_TIMESTAMP后,支持ICU_RISING_EDGE或ICU_FALLING_EDGE或ICU_BOTH_EDGES捕获。
    ICU_MODE_TIMESTAMP测量模式下的存储Buffer能设置为ICU_LINEAR_BUFFER或ICU_CIRCULAR_BUFFER。ICU_LINEAR_BUFFER在外部Buffer存储完后,不在存储。ICU_CIRCULAR_BUFFER在在外部Buffer存储完后,下一次存储重新从第一个Buffer开始。
    调用Icu_StartTimestamp()进行开始测量。入参BufferPtr为外部Buffer的地址,捕获完后都会存储在该Buffer里。入参BufferSize为Buffer的大小。入参NotifyInterval为触发通知的捕获次数。
    调用Icu_EnableNotification()使能通知,NotifyInterval次数到达后触发通知。
    调用Icu_GetTimestampIndex()获取当前捕获次数,如果为2说明存储Buffer里有两个最新的数据。ICU_CIRCULAR_BUFFER下BUFFER满了后TimestampIndex也重新计数。

    (七)、Edge counting:
    边沿计数,记录每次捕获完的次数。
    Icu通道被设置成ICU_MODE_EDGE_COUNTER后,支持ICU_RISING_EDGE或ICU_FALLING_EDGE或ICU_BOTH_EDGES捕获。
    调用Icu_EnableEdgeCount()进行开始测量。
    调用Icu_GetEdgeNumbers()获取捕获的次数。
    调用Icu_ResetEdgeCount()清除之前记录的捕获次数。

    (八)、Controlling wakeup interrupts:
    跟Gpt组件一样可以设置休眠唤醒功能。
    调用Icu_SetMode()使Icu进入ICU_MODE_NORMAL或ICU_MODE_SLEEP,ICU_MODE_NORMAL下测量和捕获正常工作,ICU_MODE_SLEEP下硬件捕获停止不能测量。
    在进入ICU_MODE_SLEEP前如果调用了Icu_EnableWakeup(),那么Icu不会停止,继续检测是否有捕获信号,如果有则调用EcuM_CheckWakeup()通知EcuM组件调用Icu_CheckWakeup()来检测是否有Icu组件的唤醒事件,如果有则调用EcuM_SetWakeupEvent()通知EcuM组件有Icu组件的唤醒事件。

    二、代码架构

    无。

    三、主要变量和类型描述

    (一)、Icu_ModeType
    ICU_MODE_NORMAL和ICU_MODE_SLEEP设置Icu组件是否进入NORMAL和SLEEP。

    (二)、Icu_InputStateType
    ICU_IDLE:
    当前捕获未完成。
    ICU_ACTIVE:
    当前捕获完成。

    (三)、Icu_MeasurementModeType
    ICU_MODE_SIGNAL_EDGE_DETECT、ICU_MODE_SIGNAL_MEASUREMENT、ICU_MODE_TIMESTAMP、ICU_MODE_EDGE_COUNTER是Icu通道要设置的测量模式。

    (四)、Icu_SignalMeasurementPropertyType
    ICU_LOW_TIME、ICU_HIGH_TIME、ICU_PERIOD_TIME、ICU_DUTY_CYCLE是ICU_MODE_SIGNAL_MEASUREMENT模式下要选择的测量模式。

    (五)、Icu_TimestampBufferType
    ICU_LINEAR_BUFFER、ICU_CIRCULAR_BUFFER是ICU_MODE_TIMESTAMP模式下要选择的Buffer类型。

    (六)、Icu_ActivationType
    ICU_RISING_EDGE、ICU_FALLING_EDGE、ICU_BOTH_EDGES是要选择的捕获边沿。

    (七)、Icu_DutyCycleType
    ActiveTime是捕获到的电平的tick,PeriodTime是整个信号周期的tick。

    四、主要代码描述

    (一)、Icu_Init()
    Icu组件的初始化,包括涉及到输入捕获的硬件外设初始化,在测量前必须先执行Icu_Init()。

    (二)、Icu_DeInit()
    Icu组件的反初始化,通常用在要关闭Icu组件时调用。

    (三)、Icu_SetMode()
    设置Icu组件进入ICU_MODE_NORMAL还是ICU_MODE_SLEEP,Icu_Init()执行后进入ICU_MODE_NORMAL。进入ICU_MODE_SLEEP后不带有唤醒功能的通道将停止测量并且关闭硬件通道。

    (四)、Icu_EnableWakeup()
    使能Icu组件某个通道的唤醒功能,执行后如果调用Icu_SetMode()进入ICU_MODE_SLEEP,有唤醒功能的通道将不会关闭捕获外设,如果检测到捕获事件,通知EcuM组件。

    (五)、Icu_DisableWakeup()
    关闭Icu组件某个通道的唤醒功能。

    (六)、Icu_SetActivationCondition()
    只能在ICU_MODE_EDGE_COUNTER和ICU_MODE_SIGNAL_EDGE_DETECT和ICU_MODE_TIMESTAMP测量模式下使用,设置捕获边沿。

    (七)、Icu_EnableNotification()、Icu_DisableNotification()
    只能在ICU_MODE_SIGNAL_EDGE_DETECT和ICU_MODE_TIMESTAMP测量模式下使用,用来开启关闭通知。

    (八)、Icu_GetInputState()
    只能在ICU_MODE_SIGNAL_EDGE_DETECT和ICU_MODE_SIGNAL_MEASUREMENT测量模式下使用,用来获取当前是否捕获到有效边沿。

    (九)、Icu_StartTimestamp()、Icu_StopTimestamp()、Icu_GetTimestampIndex()
    只能在ICU_MODE_TIMESTAMP测量模式下使用。
    Icu_StartTimestamp()用来开启ICU_MODE_TIMESTAMP测量,并且设置外部Buffer地址、Buffer大小、进入通知的捕获次数。
    Icu_StopTimestamp()用来关闭ICU_MODE_TIMESTAMP测量。
    Icu_GetTimestampIndex()用来获取外部Buffer有效数据数量。

    (十)、Icu_EnableEdgeCount()、Icu_DisableEdgeCount()、Icu_ResetEdgeCount()、Icu_GetEdgeNumbers()
    只能在ICU_MODE_EDGE_COUNTER测量模式下使用。
    Icu_EnableEdgeCount()和Icu_DisableEdgeCount()用来开启关闭ICU_MODE_EDGE_COUNTER测量。
    Icu_ResetEdgeCount()用来重置捕获到的边沿数量。
    Icu_GetEdgeNumbers()用来获取捕获到的边沿数量。

    (十一)、Icu_EnableEdgeDetection()、Icu_DisableEdgeDetection()
    只能在ICU_MODE_SIGNAL_EDGE_DETECT测量模式下使用,用来开启关闭ICU_MODE_SIGNAL_EDGE_DETECT测量。

    (十二)、Icu_StartSignalMeasurement()、Icu_StopSignalMeasurement()、Icu_GetTimeElapsed()、Icu_GetDutyCycleValues()
    只能在ICU_MODE_SIGNAL_MEASUREMENT测量模式下使用。
    Icu_StartSignalMeasurement()、Icu_StopSignalMeasurement()用来开启关闭ICU_MODE_SIGNAL_MEASUREMENT测量。
    Icu_GetTimeElapsed()用来获取捕获完有效边沿后保存的tick。
    Icu_GetDutyCycleValues()用来获取有效电平和信号周期的tick。

    (十三)、Icu_CheckWakeup()
    用来给EcuM组件检测是否有唤醒事件的Callout。

    (十四)、
    ICU_MODE_SIGNAL_MEASUREMENT和ICU_MODE_TIMESTAMP模式下能获取到捕获到的波形的时间,所以有这两个模式下的Icu通道的硬件外设不仅要支持能捕获有效边沿,还要有计数器能计算捕获完的边沿,即捕获到有效边沿后,获取当前的计数器tick。
    像S32K的Siul2就不支持这两种模式了要用Emios来实现,Emios支持用两路emios通道实现测量功能,一路实现Counter,一路实现输入捕获。Emios的时钟如果为7.5MHZ,捕获完有效电平获取到的tick如果为32965,那么有效电平的时间为32965/7.5/1000000=4MS。
    NXP代码实现如下图所示

    static inline void Emios_Icu_Ip_SignalMeasurementWithSAICMode
    (
        const uint8 instance,
        const uint8 hwChannel,
        boolean bOverflow
    )
    {
        uint16 activePulseWidth;
        uint16 IcuPeriod;
        uint16 Bus_Period;
        eMios_Icu_Ip_MeasType nMeasurement_property = eMios_Icu_Ip_ChState[instance][hwChannel].measurement;
        uint16 IcuTempA = (uint16)Emios_Icu_Ip_GetCaptureRegA(instance, hwChannel);
    
    #ifdef EMIOS_ICU_IP_SIGNAL_MEASUREMENT_USES_SAIC_MODE
        uint16 Previous_Value;
        uint16 Pulse_Width;
    #endif /* EMIOS_ICU_IP_SIGNAL_MEASUREMENT_USES_SAIC_MODE */
     
        Emios_Icu_Ip_SetActivation (instance, hwChannel, EMIOS_OPPOSITE_EDGES);
    
        if (EMIOS_ICU_MEASUREMENT_PENDING == eMios_Icu_Ip_aeInt_Counter[instance][hwChannel])
        {
            /* store the first value */
            eMios_Icu_Ip_u16aTimeStart[instance][hwChannel] = IcuTempA;
            eMios_Icu_Ip_aeInt_Counter[instance][hwChannel] = EMIOS_ICU_MEASUREMENT_DUTY;
        }
        else
        {
            Previous_Value = eMios_Icu_Ip_u16aTimeStart[instance][hwChannel];
            /* if first value is greater than the second value */
            if (IcuTempA < Previous_Value)
            {
                Bus_Period = (uint16)Emios_Icu_Ip_ReadCounterBus(instance, hwChannel);
                Pulse_Width = (Bus_Period - Previous_Value) + IcuTempA + 1U;
            }
            else
            {
                Pulse_Width = IcuTempA - Previous_Value;
            }
    
            /* HIGH TIME or LOW TIME measurement */
            if ((EMIOS_ICU_HIGH_TIME == nMeasurement_property) ||   \
                (EMIOS_ICU_LOW_TIME == nMeasurement_property)
               )
            {
                activePulseWidth = Pulse_Width;
                /* clear to measure next LOW/HIGH pulse */
                eMios_Icu_Ip_aeInt_Counter[instance][hwChannel] = EMIOS_ICU_MEASUREMENT_PENDING;
                Emios_Icu_Ip_SignalMeasurementStore(instance, hwChannel, activePulseWidth, (uint16)0U, bOverflow);
            }
            /* Duty Cycle */
            else
            {
                /* DUTYCYCLE or PERIOD measurement */
                if (EMIOS_ICU_MEASUREMENT_DUTY == eMios_Icu_Ip_aeInt_Counter[instance][hwChannel])
                {
                    eMios_Icu_Ip_u16aCapturedActivePulseWidth[instance][hwChannel] = Pulse_Width;
                    eMios_Icu_Ip_aeInt_Counter[instance][hwChannel] = EMIOS_ICU_MEASUREMENT_PERIOD;
                    if(eMios_Icu_Ip_ChState[instance][hwChannel].callback != NULL_PTR)
                    {
                        eMios_Icu_Ip_ChState[instance][hwChannel].callback(eMios_Icu_Ip_ChState[instance][hwChannel].callbackParam, bOverflow);
                    }
                }
                else
                {
                    /* eMios_Icu_Ip_aeInt_Counter is for period */
                    IcuPeriod = eMios_Icu_Ip_u16aCapturedActivePulseWidth[instance][hwChannel] + Pulse_Width;
                    activePulseWidth = eMios_Icu_Ip_u16aCapturedActivePulseWidth[instance][hwChannel];
                    
                    /* set to Duty to find active pulse width next time */
                    eMios_Icu_Ip_aeInt_Counter[instance][hwChannel] = EMIOS_ICU_MEASUREMENT_DUTY;
                    if (EMIOS_ICU_DUTY_CYCLE == nMeasurement_property)
                    {
                        Emios_Icu_Ip_SignalMeasurementStore(instance, hwChannel, activePulseWidth, IcuPeriod, bOverflow);
                    }
                    else if (EMIOS_ICU_PERIOD_TIME == nMeasurement_property)
                    {
                        Emios_Icu_Ip_SignalMeasurementStore(instance, hwChannel, (uint16)0U, IcuPeriod, bOverflow);
                    }
                    else
                    {
                        /**/
                    }
                }
                /* store for next time */
                eMios_Icu_Ip_u16aTimeStart[instance][hwChannel] = IcuTempA;
            }
        }
    }
    

    如上图所示,该代码在捕获中断里调用,比如当前通道设置为上升沿捕获,ICU_MODE_SIGNAL_MEASUREMENT模式的CU_DUTY_CYCLE,这时候捕获引脚上出现上升沿,进入该代码,eMios_Icu_Ip_u16aTimeStart先保存IcuTempA的值,也就是Emios计数器运行到现在的计数值,该值就是低电平的tick,然后捕获引脚上出现下降沿,Pulse_Width保存高电平的tick赋值给activePulseWidth,Bus_Period是Emios最大的计数值也就是65535,最后有效电平和周期信号的时间tick都保存下来了。

    五、EBTresos配置

    本次用的是NXP的S32K3的MCAL,EB配置,主要配置如下:
    有依赖的组件有Mcu、Mcl、Port、Platform。本例使用Emios作为硬件通道。
    (一)、Mcu
    因为Emios用时钟源是Core_Clk,所以这里要配置Core_Clk时钟:
    添加McuClockReferencePoint:
    使能Emios外设时钟:
    (二)、Port
    使用某个有Emios0_0通道的引脚配置成Emios0_0:
    (三)、Platform
    因为用的是Emios0_0所以要使能通道中断:


    这里的中断函数名要跟Mcal里的名对的上:
    (四)、Mcl
    Emios作为输入捕获要用到两个通道一个作为计数器一个作为输入捕获,所以其他组件不能占用这两个通道,Mcl里要使能Emios的计数器:

    这里选择Emios0_22作为计数器,Bus Mode Type只能选择MCB_UP_COUNTER,Default period只能选择65535,Master Bus Prescaler选择16分频,因为Core_Clk频率是120MHZ,所以测量一个电平周期最大不能超过65535/120000000*16=0.008738S,不然就不准因为tick最大计到65535之后就溢出:

    (五)、Icu
    因为使用Emios0_0作为输入捕获通道,这里勾选:

    这里选择Icu硬件通道:

    这里选择SAIC作为Emios的输入捕获模式:
    1、如果配置Icu通道为ICU_MODE_EDGE_COUNTER:
    2、如果配置Icu通道为ICU_MODE_SIGNAL_EDGE_DETECT,下面那个绿框要勾上:
    输入通知回调函数名:

    3、如果配置Icu通道为ICU_MODE_SIGNAL_EDGE_DETECT,下面那个绿框要勾上:
    选择ICU_DUTY_CYCLE、ICU_HIGH_TIME、ICU_LOW_TIME、ICU_PERIOD_TIME之一,如果选择ICU_HIGH_TIME的话IcuDefaultStartEdge只能选择ICU_RISING_EDGE,如果选择ICU_LOW_TIME的话IcuDefaultStartEdge只能选择ICU_FALLING_EDGE:

    4、如果配置Icu通道为ICU_MODE_TIMESTAMP,下面那个绿框要勾上:
    选择ICU_CIRCULAR_BUFFER或ICU_LINEAR_BUFFER还有通知回调:

    (六)、
    因为有用到中断功能,使用的时候注意要调用

    	Platform_InstallIrqHandler(EMIOS0_5_IRQn, &EMIOS0_5_IRQ, NULL_PTR);
    	Platform_SetIrq(EMIOS0_5_IRQn,TRUE);
    

    使能捕获中断。

    六、使用范例

    (一)、使用边沿计数

    	/**忽略其他配置**/
    	Icu_EdgeNumberType num;
    	
    	Icu_Init(&Icu_Config_VS_0);
    	Icu_EnableEdgeCount(IcuChannel_0);
    	...
    	/**获取数量**/
    	num = Icu_GetEdgeNumbers(IcuChannel_0);
    
    

    (二)、使用边沿检测

    	/**忽略其他配置**/
    	Icu_InputStateType state;
    	
    	Icu_Init(&Icu_Config_VS_0);
    	Icu_EnableNotification(IcuChannel_0);
    	Icu_EnableEdgeDetection(IcuChannel_0);
    	...
    	/**进入到通知回调**/
    	state = Icu_GetInputState(IcuChannel_0);
    	if(ICU_ACTIVE == state )
    	{
    	}
    

    (三)、使用测量模式

    	/**忽略其他配置**/
    	Icu_ValueType value;
    	Icu_InputStateType state;
    	Icu_DutyCycleType duty;
    	
    	Icu_Init(&Icu_Config_VS_0);
    	Icu_StartSignalMeasurement(IcuChannel_0);
    	...
    	/**获取周期时间**/
    	if(ICU_ACTIVE == state )
    	{
    		value = Icu_GetTimeElapsed(IcuChannel_0);
    	}
    	...
    	/**或者获取占空比**/
    	if(ICU_ACTIVE == state )
    	{
    		Icu_GetDutyCycleValues(IcuChannel_0,&duty);
    	}
    

    (四)、使用时间戳

    	/**忽略其他配置**/
    	Icu_ValueType value[20u];
    	Icu_IndexType index;
    	
    	Icu_Init(&Icu_Config_VS_0);
    	Icu_EnableNotification(IcuChannel_0);
    	Icu_StartTimestamp(IcuChannel_0,&value,20u,2u);
    	...
    	/**进入到通知回调**/
    	index= Icu_GetTimestampIndex(IcuChannel_0);
    	if(2u== index)
    	{
    	}
    

    (五)、休眠下使用唤醒功能

    	/**忽略其他配置**/
    	Icu_InputStateType state;
    	
    	Icu_Init(&Icu_Config_VS_0);
    	Icu_EnableNotification(IcuChannel_0);
    	Icu_EnableEdgeDetection(IcuChannel_0);
    	Icu_EnableWakeup(IcuChannel_0);
    	Icu_SetMode(ICU_MODE_SLEEP);
    	...
    	
    

    七、参考资料

    https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SRS_ICUDriver.pdf
    https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_SWS_ICUDriver.pdf
    https://www.autosar.org/fileadmin/standards/R23-11/CP/AUTOSAR_CP_EXP_LayeredSoftwareArchitecture.pdf


    总结

    Icu组件要实现各种测量模式,需要注意对应的硬件通道支不支持该测量模式,比如周期测量要注意测量的周期是否超过硬件的计数器最大值,超过了那就返回的tick就不准了。
    在ICU_DUTY_CYCLE下上升沿或下降沿捕获时,对ActiveTime的计算NXP的代码实现和AUTOSAR文档里的计算是反的,比如上升沿捕获时,ActiveTimeNXP代码算的是低电平,这里还存疑。

    作者:前行。,

    物联沃分享整理
    物联沃-IOTWORD物联网 » CP AUTOSAR中Icu Driver的详细分析与使用指南

    发表回复