前言
本文介绍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组件的唤醒事件。
二、代码架构
该代码里可以选择MCU的EMIOS、CMP、SIUL2、WKPU外设来实现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代码算的是低电平,这里还存疑。