【S32K3 RTD LLD篇7】K344中心对齐PWM中心点触发ADC BCTU采样
- 一,文档简介
- 二,中心对齐PWM中心点触发ADC原理
- 2.1 如何生成中心对齐的PWM
- 2.2 如何生成PWM中心点触发标志
- 三, 软件配置与实现
- 3.1 Demo CT 模块配置
- 3.1.1 引脚配置
- 3.1.2 时钟配置
- 3.1.3 外设配置
- 3.2 主程序调用情况
- 3.3 测试结果
- 四,总结与坑位描述
一,文档简介
手里还有好几个典型的案子没来得及写,春节前就先上一个吧。前不久,有客户实际项目需要在S32K3XX 上实现中心对齐PWM的高电平中心点去触发ADC BCTU多通道采样。这个功能需要使用EMIOS,ADC,BCTU的相互配合,开始的时候客户总是无法实现在PWM中心点位置的触发,触发位置不对,后续给与客户K3相关模块原理的讲解分析,以及实际代码配置测试的引导,客户也实现了需要的功能。这里为了测试方便,还额外添加了PIT以及UART printf功能。所以在此总结下该功能的具体情况,便于后续有其他客户遇到类似功能需求,有参考且能够快速构建起来。
需求如下图,在向上向下计数的模式下,输出中心对齐的PWM,并且在高电平的中心点实现ADC BTCU LIST的触发采样。本文将在官方S32K344-EVB板子上实现1KHZ的中心对齐PWM,50%占空比,高电平中心点实现ADC BTCU LIST采样,基于RTD400 LLD版本。
二,中心对齐PWM中心点触发ADC原理
关于原理,ADC这块是没有什么悬念,可以直接参考之前的ADC软硬件触发的文章即可:
【S32K3 RTD LLD篇5】K344 ADC SW+HW trigger
就是图2这种结构,配置ADC之后,再配置BCTU,选择触发的EMIOS通道,当EMIOS的相关标志产生时候,就会触发BCTU,在watermark达到之后,产生通知去存储所有的LIST ADC值到buffer,然后再根据情况可以打印出ADC值便于查看。
当前工况,需要关心的逻辑主要是:如何生成中心对齐的PWM,如何实现在高电平中心点去产生一个标志,并用这个标志去触发BTCU采样。
2.1 如何生成中心对齐的PWM
首先来看看EMIOS的通道情况,支持中心对齐PWM的通道情况。
中心对齐的PWM模式是:OPWMCB
正如图1所示的情况,可以看到,这种模式下的计数器计数模式是向上向下的。下面再看看支持OPWMCB的通道类型有哪些,可以看到,只有type G。
那么,Type G支持的通道又有哪些呢?请看下图:
谜团揭开,只有eMIOS的CH1-CH7支持中心对齐的PWM。
好,那么又引出新的问题,该模式是否能够直接产生PWM高电平中心点的触发信号去触发ADC BCTU采样?回答:不能!
原因可以从图1看出,这种PWM模式下,产生的触发标志有两个,分别是向上向下计数对比完成的时候。所以,如果用这种模式通道直接去触发ADC采样,一个周期里面会产生两次触发,并且都在PWM的边沿上。
既然OPWMCB无法直接产生PWM中心点的触发,那么如何实现呢?
另外再开一个专门的通道,并且时钟源和OPWMCB同时钟源,只是那个通道只做计数产生触发标志,并不输出任何的波形。
2.2 如何生成PWM中心点触发标志
关键信息:OPWMCB同时钟源,只有计数模式,还是向上向下MCB模式,满足的情况只有一个通道:ADC CH0,counter bus类型还需要选择为counter_bus_B
来看看MCB的触发标志情况:
可以看到,非常完美,触发标志就在向上向下计数的顶点,该点正好就是同源PWM高电平中心点。
所以,到这里,原理上已经确定了具体的通道安排,两个通道:EMIOS0_CH0 MCB模式纯计数并触产生标志触发ADC采样; EMIOS0_CH1-CH7为OPWMCB模式,单纯输出中心对齐PWM。
三, 软件配置与实现
软件使用的是S32K3 RTD400版本,其他的版本配置类同!
3.1 Demo CT 模块配置
首先需要准备一个K344的demo,然后再去配置引脚,时钟,和外设模块。下面分别从几种配置来讲解。对于使用的模块主要有:2个GPIO, ADC,BCTU, EMIOS时钟,EMIOS PWM, LPUART用于打印log,PIT用来做定时打印, Trgmux配置了但是本文并没用,不讲!
3.1.1 引脚配置
用到的引脚有如下情况:
两个GPIO的用处是分别在BCTU触发通知和BCTU watermark通知中翻转,用来测试波形定位情况。ADC1_s10引脚用于ADC1,但是本文主要是BCTU用来结合ADC0,ADC0并没有添加特定外部引脚,用的是内部信号。LPUART是用来做打印用,EMIOS0_CH1是用来产生中心对其的PWM波形。
3.1.2 时钟配置
时钟这里需要关注的是:ADC时钟,EMIOS时钟,UART时钟,PIT时钟。
这些时钟后续在配置具体的定时周期,波特率的时候需要用到。
3.1.3 外设配置
用到的外设主要有如下模块:
(1)Adc_Sar_Ip
ADC0模块的配置使用是one shot 模式,也就是一次触发就产生一次转换,这样就可以使用定时器EMIOS通道产生触发条件去触发产生一次转换,使能预采样是为了防止上一个通道的采样值电荷的残存导致对当前通道结果的影响,引起ADC值的偏差,触发模式使用BCTU,对于Adc prescaler value和calibration prescale的配置,需要满足RM中分频要求,最终的配置如下:
(2)Bctu_Ip
Bctu_Ip这边配置需要选对使用的EMIOS通道,从上述原理中可以知道,使用的是EMIOS0_CH0, 然后采用LIST模式,针对ADC0,LIST里面,当前添加了三个内部通道:BANDGAP,VREFL,VREFH. 具体配置如下:
(3)Emios_Mcl_Ip
这里就需要考虑需要产生的PWM周期的问题了,目标是产生1Khz的中心对其PWM,所以EMIOS模式是MCB向上向下计数模式。
对于MCB的时钟周期公式是:(2 x AS1) – 2.
那么对于EMIOS0时钟源是160Mhz,对应的1KHZ counter情况是如何的呢?先做160分配,得到1Mhz,然后再去按照(2 x AS1) – 2计算:
(2 x AS1) – 2=1Mhz/1KHZ=>AS1=1000/2 +1= 501.
配置如下:
(3)Emios_Pwm
前面原理分析是PWM采用的是OPWMCB中心对齐模式的PWM,时钟是来自EMIOS CH0的counter bus是B,然后周期是1khz,占空比是50%,具体配置如下:
(4)Lpuart_Uart
配置UART6时钟波特率为115200.
(5)Pit
(6)IntCtrl_Ip
中断配置如下,实际上也可以通过代码方式去配置。
3.2 主程序调用情况
配置完成之后,可以通过在main中调用如下代码实现中心PWM的输出以及ADC BCTU的采样。
#include "Clock_Ip.h"
#include "IntCtrl_Ip.h"
#include "Adc_Sar_Ip.h"
#include "Bctu_Ip.h"
#include "Siul2_Port_Ip.h"
#include "Pit_Ip.h"
#include "Siul2_Dio_Ip.h"
#include "Trgmux_Ip.h"
#include "Lpuart_Uart_Ip.h"
#include "Lpuart_Uart_Ip_Irq.h"
#include "string.h"
#include "stdio.h"
#include "retarget.h"
#include "Emios_Mcl_Ip.h"
#include "Emios_Pwm_Ip.h"
/* PIT instance used - 0 */
#define PIT_INST_0 0U
/* PIT Channel used - 0 */
#define CH_0 0U
/* PIT time-out period - equivalent to 1s */
#define PIT_PERIOD 40000000
static volatile uint8 toggleLed = 0U;
#ifdef ADC_3V3_VREF_SELECTED
#define ADC_BANDGAP 5980U /* Vbandgap ~ 1.2V on 14 bits resolution, 3.3V VrefH */
#else
#define ADC_BANDGAP 3932U /* Vbandgap ~ 1.2V on 14 bits resolution, 5V VrefH */
#endif
#define ADC_SAR_USED_CH 48U /* Internal Bandgap Channel */
#define BCTU_USED_SINGLE_TRIG_IDX 0U
#define BCTU_USED_FIFO_IDX 0U
#define BCTU_FIFO_WATERMARK 3U
#define ADC_TOLERANCE(x,y) (((x > y) ? (x - y) : (y - x)) > 200U) /* Check that the data is within tolerated range */
#define LED_Q172_PIN 13u
#define LED_Q172_PORT PTA_H_HALF
#define GPIO_PTA2_PIN 2u
#define GPIO_PTA2_PORT PTA_L_HALF
#define EMIOS_INST0 0U
volatile int exit_code = 0;
volatile boolean notif_triggered = FALSE;
volatile boolean notif_triggered1 = FALSE;
volatile uint16 data;
volatile uint16 data1;
volatile uint16 data_bctu[3];
#define UART_LPUART_INTERNAL_CHANNEL 6
#define WELCOME_MSG_1 "Hello, This message is sent via Uart!\r\n"
volatile Lpuart_Uart_Ip_StatusType lpuartStatus = LPUART_UART_IP_STATUS_ERROR;
uint32 remainingBytes;
uint32 T_timeout = 0xFFFFFF;
uint8* pBuffer;
extern void Adc_Sar_0_Isr(void);
extern void Bctu_0_Isr(void);
extern void Adc_Sar_1_Isr(void);
void AdcEndOfChainNotif(void)
{
notif_triggered = TRUE;
data = Adc_Sar_Ip_GetConvData(ADCHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE, ADC_SAR_USED_CH);
/* Checks the measured ADC data conversion */
}
void AdcEndOfChainNotif1(void)
{
notif_triggered1 = TRUE;
data1 = Adc_Sar_Ip_GetConvData(ADCHWUNIT_1_BOARD_INITPERIPHERALS_INSTANCE, 34);
/* Checks the measured ADC data conversion */
}
void Pit0ch0Notification(void)
{
toggleLed = 1U;
}
void BctuWatermarkNotif(void)
{
uint8 idx;
Siul2_Dio_Ip_WritePin(GPIO_PTA2_PORT, GPIO_PTA2_PIN, 1U);
notif_triggered = TRUE;
for (idx = 0u; idx < BCTU_FIFO_WATERMARK; idx++)
{
data_bctu[idx] = Bctu_Ip_GetFifoData(BCTUHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE, BCTU_USED_FIFO_IDX);
}
Siul2_Dio_Ip_WritePin(GPIO_PTA2_PORT, GPIO_PTA2_PIN, 0U);
}
void BcutTriggerNotif()
{
Siul2_Dio_Ip_WritePin(LED_Q172_PORT, LED_Q172_PIN, 1U);
Siul2_Dio_Ip_WritePin(LED_Q172_PORT, LED_Q172_PIN, 0U);
}
void TestDelay(uint32 delay);
void TestDelay(uint32 delay)
{
static volatile uint32 DelayTimer = 0;
while(DelayTimer < delay)
{
DelayTimer++;
}
DelayTimer = 0;
}
int main(void)
{
StatusType status;
uint8 Index;
Clock_Ip_StatusType clockStatus;
/* Initialize and configure drivers */
clockStatus = Clock_Ip_Init(&Clock_Ip_aClockConfig[0]);
while (clockStatus != CLOCK_IP_SUCCESS)
{
clockStatus = Clock_Ip_Init(&Clock_Ip_aClockConfig[0]);
}
Siul2_Port_Ip_Init(NUM_OF_CONFIGURED_PINS_PortContainer_0_BOARD_InitPeripherals, g_pin_mux_InitConfigArr_PortContainer_0_BOARD_InitPeripherals);
#if 1
Bctu_Ip_Init(BCTUHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE, &BctuHwUnit_0_BOARD_INITPERIPHERALS);
status = (StatusType) Adc_Sar_Ip_Init(ADCHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE, &AdcHwUnit_0_BOARD_InitPeripherals);
while (status != E_OK);
status = (StatusType) Adc_Sar_Ip_Init(ADCHWUNIT_1_BOARD_INITPERIPHERALS_INSTANCE, &AdcHwUnit_1_BOARD_InitPeripherals);
while (status != E_OK);
/* set PIT 0 interrupt */
IntCtrl_Ip_Init(&IntCtrlConfig_0);
IntCtrl_Ip_EnableIrq(PIT0_IRQn);
/* Install and enable interrupt handlers */
IntCtrl_Ip_InstallHandler(ADC0_IRQn, Adc_Sar_0_Isr, NULL_PTR);
IntCtrl_Ip_InstallHandler(BCTU_IRQn, Bctu_0_Isr, NULL_PTR);
IntCtrl_Ip_InstallHandler(ADC1_IRQn, Adc_Sar_1_Isr, NULL_PTR);
IntCtrl_Ip_EnableIrq(ADC0_IRQn);
IntCtrl_Ip_EnableIrq(BCTU_IRQn);
IntCtrl_Ip_EnableIrq(ADC1_IRQn);
// IntCtrl_Ip_EnableIrq(EMIOS0_5_IRQn);
/* Call Calibration function multiple times, to mitigate instability of board source */
for(Index = 0; Index <= 5; Index++)
{
status = (StatusType) Adc_Sar_Ip_DoCalibration(ADCHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE);
if(status == E_OK)
{
break;
}
}
for(Index = 0; Index <= 5; Index++)
{
status = (StatusType) Adc_Sar_Ip_DoCalibration(ADCHWUNIT_1_BOARD_INITPERIPHERALS_INSTANCE);
if(status == E_OK)
{
break;
}
}
Adc_Sar_Ip_EnableNotifications(ADCHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE, ADC_SAR_IP_NOTIF_FLAG_NORMAL_ENDCHAIN | ADC_SAR_IP_NOTIF_FLAG_INJECTED_ENDCHAIN);
Adc_Sar_Ip_EnableNotifications(ADCHWUNIT_1_BOARD_INITPERIPHERALS_INSTANCE, ADC_SAR_IP_NOTIF_FLAG_NORMAL_ENDCHAIN | ADC_SAR_IP_NOTIF_FLAG_INJECTED_ENDCHAIN);
/* Start a SW triggered normal conversion on ADC_SAR */
Adc_Sar_Ip_StartConversion(ADCHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE, ADC_SAR_IP_CONV_CHAIN_NORMAL);
/* Wait for the notification to be triggered and read the data */
while (notif_triggered != TRUE);
notif_triggered = FALSE;
/* Start a SW triggered injected conversion on ADC_SAR */
Adc_Sar_Ip_StartConversion(ADCHWUNIT_0_BOARD_INITPERIPHERALS_INSTANCE, ADC_SAR_IP_CONV_CHAIN_INJECTED);
/* Wait for the notification to be triggered and read the data */
while (notif_triggered != TRUE);
notif_triggered = FALSE;
#endif
/* Initialize PIT instance 0 - Channel 0 */
Pit_Ip_Init(PIT_INST_0, &PIT_0_InitConfig_PB_BOARD_InitPeripherals);
/* Initialize channel 0 */
Pit_Ip_InitChannel(PIT_INST_0, PIT_0_CH_0);
/* Enable channel interrupt PIT_0 - CH_0 */
Pit_Ip_EnableChannelInterrupt(PIT_INST_0, CH_0);
/* Start channel CH_0 */
Pit_Ip_StartChannel(PIT_INST_0, CH_0, PIT_PERIOD);
// Trgmux_Ip_Init(&Trgmux_Ip_xTrgmuxInitPB);//
Lpuart_Uart_Ip_Init(UART_LPUART_INTERNAL_CHANNEL, &Lpuart_Uart_Ip_xHwConfigPB_6_BOARD_INITPERIPHERALS);
Emios_Mcl_Ip_Init(EMIOS_INST0, &Emios_Mcl_Ip_0_Config_BOARD_INITPERIPHERALS);
Emios_Pwm_Ip_InitChannel(EMIOS_PWM_IP_BOARD_INITPERIPHERALS_I0_CH1_CFG, &Emios_Pwm_Ip_BOARD_InitPeripherals_I0_Ch1);
printf("S32K344 PIT TRIGMUX ADC demo RTD400.\r\n");
/* Uart_AsyncSend transmit data */
lpuartStatus = Lpuart_Uart_Ip_AsyncSend(UART_LPUART_INTERNAL_CHANNEL, (const uint8 *) WELCOME_MSG_1, strlen(WELCOME_MSG_1));
/* Check for no on-going transmission */
do
{
lpuartStatus = Lpuart_Uart_Ip_GetTransmitStatus(UART_LPUART_INTERNAL_CHANNEL, &remainingBytes);
} while (LPUART_UART_IP_STATUS_BUSY == lpuartStatus && 0 < T_timeout--);
Siul2_Dio_Ip_WritePin(GPIO_PTA2_PORT, GPIO_PTA2_PIN, 0U);
while(1)
{
#if 1
if( toggleLed == 1)
{
toggleLed = 0;
printf("ADC0_bandgap ch48 data_bctu = %d .\r\n", data_bctu[0]);
printf("ADC0_vrefl ch54 data_bctu = %d .\r\n", data_bctu[1]);
printf("ADC0_vrefh ch55 data_bctu = %d .\r\n", data_bctu[2]);
}
#endif
}
return exit_code;
}
3.3 测试结果
测试结果包括两部分:打印结果看ADC采样值情况,以及PWM输出和BCTU触发位置的关系情况。打印结果如下,可以看到内部三个不同ADC通道的值是正确的:
PWM波形测试情况如下:
Ch1:PTB13 PWM
Ch2:PTA29 BCTU trigger notification
Ch3: PTA2 BCTU watermark notification
从图中,可以看到BCTU第一次触发是在PWM高电平的中心点位置,BCTU watermark notification是在三次触发ADC采样之后用来存储数据的。
下面再来一张查看PWM的周期的波形:
四,总结与坑位描述
通过上述的配置,最终在S32K344-EVB上使用RTD400 LLD方式实现了1Khz,50%占空比的中心对齐PWM,高脉冲中心位置触发ADC BCTU LIST采集。
遇到的坑位属于自己配置的问题,开始的时候误认为watermark配置的值和LIST通道数保持一致,实际上是watermark触发是在 超过watermark值才会触发。所以,如果是3个通道的LIST,那么watermark需要配置的是2,而不是3,如果是3的情况,测试波形如下:
可以看到,实际上是触发了4次之后,才会产生watermark通知。
所以,切记要配对watermark的FIFO数。
文章源码链接:
https://community.nxp.com/t5/S32K-Knowledge-Base/RTD400-K344-Center-Aligned-PWM-Trigger-ADC-BCTU/ta-p/2034211