简介
本项目实现了Funpack第二季第2期的任务二,即“设计一个呼吸灯,通过旋转板卡上的电位计,改变呼吸灯闪烁速率,同时将ADC采集的数据通过串口/CAN,发送到另一台设备上显示”
硬件说明
本项目使用的开发板是Infineon的KIT_AURIX_TC275_LITE,其MCU为拥有三个独立的 32 位 TriCore CPU的TC275,具有非常强大的性能。不仅可用于汽车和工业应用的,对于业余玩家来说也具有非常高的可玩性。
本项目使用板载的LED1实现呼吸灯效果,通过调节电位器R32来控制呼吸灯的闪烁频率,通过板载的FT2232调试器的虚拟串口将数据通过UART传输至PC。
实现思路
首先是呼吸灯的实现。为了实现呼吸灯从暗到明,再由明到暗的亮度变化,我们需要控制LED灯的亮度。因为TC275没有内置的DAC,KIT_AURIX_TC275_LITE开发板上也没有集成外置DAC。所以我们选择通过PWM的方式来驱动LED,并通过调整PWM的占空比来控制LED的亮度。
TC275具有多个生成PWM的方式。在本项目中,我选择了使用GTM通用定时器模块的TOM单元来输出PWM。选择的LED是板载的LED1。需要注意的是,ED1是Low Active,所以占空比越高,亮度就越低。以下是TOM单元的初始化代码。
void initGtmTomPwm(void)
{
IfxGtm_enable(&MODULE_GTM); /* Enable GTM */
IfxGtm_Cmu_enableClocks(&MODULE_GTM, IFXGTM_CMU_CLKEN_FXCLK); /* Enable the FXU clock */
/* Initialize the configuration structure with default parameters */
IfxGtm_Tom_Pwm_initConfig(&g_tomConfig, &MODULE_GTM);
g_tomConfig.tom = LED.tom; /* Select the TOM depending on the LED */
g_tomConfig.tomChannel = LED.channel; /* Select the channel depending on the LED */
g_tomConfig.period = PWM_PERIOD; /* Set the timer period */
g_tomConfig.pin.outputPin = &LED; /* Set the LED port pin as output */
g_tomConfig.synchronousUpdateEnabled = TRUE; /* Enable synchronous update */
IfxGtm_Tom_Pwm_init(&g_tomDriver, &g_tomConfig); /* Initialize the GTM TOM */
IfxGtm_Tom_Pwm_start(&g_tomDriver, TRUE); /* Start the PWM */
}
为了实现亮度的周期变化,我们通过封装一个函数来实现对占空比的步进控制,然后在占空比到达最大或者最小的时候更改占空比的变化方向,就可以实现呼吸灯的效果。
以下为封装的函数实现:
void fadeLED(void)
{
if((g_fadeValue + FADE_STEP) >= PWM_PERIOD)
{
g_fadeDir = -1; /* Set the direction of the fade */
}
else if((g_fadeValue - FADE_STEP) <= 0)
{
g_fadeDir = 1; /* Set the direction of the fade */
}
g_fadeValue += g_fadeDir * FADE_STEP; /* Calculation of the new duty cycle */
setDutyCycle(g_fadeValue); /* Set the duty cycle of the PWM */
}
通过STEP的控制,我们可以设定一次亮度的单向变化需要的步数(本项目设定为100步)。然后每调用一次fadeLED函数,LED的亮度就会变化一小步。这样我们不仅实现了呼吸灯的效果,还可以通过改变调用fadeLED函数的频率来更改呼吸灯的闪烁速度。
接下来是对电位器电压的采样,此处对采样的精度和速度要求都不高,所以采用了TC275内置的VADC模块来实现电压的采样。以下是VADC的初始化代码:
void vadcBackgroundScanInit(void)
{
/* VADC module configuration */
/* Create VADC configuration */
IfxVadc_Adc_Config adcConfig;
/* Initialize the VADC configuration with default values */
IfxVadc_Adc_initModuleConfig(&adcConfig, &MODULE_VADC);
/* Initialize the VADC module using the VADC configuration */
IfxVadc_Adc_initModule(&g_vadcBackgroundScan.vadc, &adcConfig);
/* VADC group configuration */
/* Create group configuration */
IfxVadc_Adc_GroupConfig adcGroupConfig;
/* Initialize the group configuration with default values */
IfxVadc_Adc_initGroupConfig(&adcGroupConfig, &g_vadcBackgroundScan.vadc);
/* Define which ADC group is going to be used */
adcGroupConfig.groupId = VADC_GROUP;
adcGroupConfig.master = VADC_GROUP;
/* Enable background scan source */
adcGroupConfig.arbiter.requestSlotBackgroundScanEnabled = TRUE;
/* Enable background auto scan mode */
adcGroupConfig.backgroundScanRequest.autoBackgroundScanEnabled = TRUE;
/* Enable the gate in "always" mode (no edge detection) */
adcGroupConfig.backgroundScanRequest.triggerConfig.gatingMode = IfxVadc_GatingMode_always;
/* Initialize the group using the group configuration */
IfxVadc_Adc_initGroup(&g_vadcBackgroundScan.adcGroup, &adcGroupConfig);
}
在初始化完成后,我们通过以下函数启动VADC:
void vadcBackgroundScanRun(void)
{
/* Initialize the channel configuration of application handle g_vadcBackgroundScan with default values */
IfxVadc_Adc_initChannelConfig(&g_vadcBackgroundScan.adcChannelConfig, &g_vadcBackgroundScan.adcGroup);
g_vadcBackgroundScan.adcChannelConfig.channelId = (IfxVadc_ChannelId)CHANNEL_ID;
g_vadcBackgroundScan.adcChannelConfig.resultRegister = (IfxVadc_ChannelResult)CHANNEL_RESULT_REGISTER;
g_vadcBackgroundScan.adcChannelConfig.backgroundChannel = TRUE;
/* Initialize the channel of application handle g_VadcBackgroundScan using the channel configuration */
IfxVadc_Adc_initChannel(&g_vadcBackgroundScan.adcChannel, &g_vadcBackgroundScan.adcChannelConfig);
/* Enable background scan for the channel */
IfxVadc_Adc_setBackgroundScan(&g_vadcBackgroundScan.vadc,
&g_vadcBackgroundScan.adcGroup,
(1 << (IfxVadc_ChannelId)CHANNEL_ID),
(1 << (IfxVadc_ChannelId)CHANNEL_ID));
/* Start background scan conversion */
IfxVadc_Adc_startBackgroundScan(&g_vadcBackgroundScan.vadc);
}
然后我们就可以通过以下代码读取VADC的采样转换结果了
Ifx_VADC_RES conversionResult;
/* Retrieve the conversion value until valid flag of the result register is true */
do
{
conversionResult = IfxVadc_Adc_getResult(&g_vadcBackgroundScan.adcChannel);
}
while (!conversionResult.B.VF);
因为需要把ADC采样的数据通过串口发送出去,所以我们还初始化一个UART通道,在本项目中,这通过TC275的ASCLIN模块来实现,在初始化函数中,我们设定UART的基本参数为:115200,8数据位,1停止位,无校验。为了方便,本项目将UART的IO选择为板载的FT2232虚拟串口所在的IO,这样通过USB就可以在PC上接收数据,不用额外接线了。
以下为串口初始化代码:
void init_ASCLIN_UART(void)
{
/* Initialize an instance of IfxAsclin_Asc_Config with default values */
IfxAsclin_Asc_Config ascConfig;
IfxAsclin_Asc_initModuleConfig(&ascConfig, &MODULE_ASCLIN0);
/* Set the desired baud rate */
ascConfig.baudrate.baudrate = UART_BAUDRATE;
/* ISR priorities and interrupt target */
ascConfig.interrupt.txPriority = INTPRIO_ASCLIN0_TX;
ascConfig.interrupt.rxPriority = INTPRIO_ASCLIN0_RX;
ascConfig.interrupt.typeOfService = IfxCpu_Irq_getTos(IfxCpu_getCoreIndex());
/* FIFO configuration */
ascConfig.txBuffer = &g_ascTxBuffer;
ascConfig.txBufferSize = UART_TX_BUFFER_SIZE;
ascConfig.rxBuffer = &g_ascRxBuffer;
ascConfig.rxBufferSize = UART_RX_BUFFER_SIZE;
/* Pin configuration */
const IfxAsclin_Asc_Pins pins =
{
NULL_PTR, IfxPort_InputMode_pullUp, /* CTS pin not used */
&UART_PIN_RX, IfxPort_InputMode_pullUp, /* RX pin */
NULL_PTR, IfxPort_OutputMode_pushPull, /* RTS pin not used */
&UART_PIN_TX, IfxPort_OutputMode_pushPull, /* TX pin */
IfxPort_PadDriver_cmosAutomotiveSpeed1
};
ascConfig.pins = &pins;
IfxAsclin_Asc_initModule(&g_ascHandle, &ascConfig); /* Initialize module with above parameters */
}
串口初始化结束后,我们就可以通过一个简单的函数发送字符串:
void send_ASCLIN_UART_message(const char* str)
{
Ifx_SizeT len = (Ifx_SizeT)strlen(str);
IfxAsclin_Asc_write(&g_ascHandle, str, &len, TIME_INFINITE); /* Transmit data via TX */
}
然后我们将ADC采样和串口数据发送功能整合至一个函数中:
uint32_t indicateConversionValue(void)
{
static uint32_t loops = 0;
static char strbuf[256];
Ifx_VADC_RES conversionResult;
/* Retrieve the conversion value until valid flag of the result register is true */
do
{
conversionResult = IfxVadc_Adc_getResult(&g_vadcBackgroundScan.adcChannel);
}
while (!conversionResult.B.VF);
uint32_t value = conversionResult.B.RESULT;
if (((loops++) % 20) == 0)
{
sprintf(strbuf, "[+%012lld] ADC Value: %lu\r\n", IfxStm_now(), value);
send_ASCLIN_UART_message(strbuf);
}
return value;
}
因为ADC的采样频率是很高的,如果每次采样结束后都进行数据上传,那么数据就会滚动过快导致难以阅读,所以这里我创建了一个计数器,设定ADC每采样20次数据后,进行一次数据上传。
在所有的功能模块都实现了以后,我们就需要开始进行控制逻辑整合了。因为本项目对算力的要求不高,所以仅使用了CPU0。
在CPU0的入口main函数中,我创建了一个主循环,并设置循环的周期为1毫秒。同时,我添加了一个循环计数器。这样我们就可以方便通过判断循环次数是否可以被某个值整除来控制某个业务逻辑执行的频率(是多少个main循环周期)。
在本项目中,设定的ADC采样频率为每20个循环也就是20ms采样一次,因为ADC每采样20次上传一次数据,所以数据的上传周期就是20x20=400ms。然后用每次ADC采样的结果,控制呼吸灯单步变化需要的周期。这样就实现了通过电位器(ADC采样结果)来控制呼吸灯的闪烁频率。
以下为完整的main函数代码:
int core0_main(void)
{
IfxCpu_enableInterrupts();
/* !!WATCHDOG0 AND SAFETY WATCHDOG ARE DISABLED HERE!!
* Enable the watchdogs and service them periodically if it is required
*/
IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());
/* Wait for CPU sync event */
IfxCpu_emitEvent(&g_cpuSyncEvent);
IfxCpu_waitEvent(&g_cpuSyncEvent, 1);
/* Initialize a time variable */
Ifx_TickTime ticksForWait = IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, WAIT_TIME);
/* Initialize VADC */
vadcBackgroundScanInit();
/* Initialize GTM TOM module */
initGtmTomPwm();
/* Initialize UART module */
init_ASCLIN_UART();
/* Start the background scan */
vadcBackgroundScanRun();
uint32_t loops = 0;
while(1)
{
/* Fetch ADC Value per 20 loops(20ms) */
if (loops % 20 == 0)
{
adcValue = indicateConversionValue();
}
/* Fade the LEDs depending on the measured value */
if (loops % (adcValue / 200) == 0)
{
fadeLED();
}
/* Increase loops counter */
loops++;
wait(ticksForWait);
}
return (1);
}
至此,本项目已经实现了项目任务的全部要求(通过ADC采样电位器的电压,控制呼吸灯闪烁速率,并且同时将采样结果上传)。
结语
Funpack活动为我们这些喜欢折腾各类技术的电子爱好者提供了一个非常好的平台,希望这个活动能长期做下去,并且越办越好。