By Toradex秦海
1). 简介
NXP i.MX8 系列处理器均为异构多核架构 SoC,除了可以运行 Linux 等复杂操作系统的 Cortax-A 核心,还包含了可以运行实时操作系统比如 FreeRTOS 的 Cortex-M 核心,本文就演示通过 NXP i.MX8MM 处理器集成的 Cortex-M4 核心来运行 GPT (General Purpose Timer) 输入采集功能模块的测试。
I.MX8M Mini 处理器 GPT 模块硬件比较简单,如下框图,可以实现 Capture 捕获输入功能和 Compare 定时输出功能。
本文所演示的ARM平台来自于Toradex 基于NXP i.MX8M Mini ARM处理器的Verdin iMX8MM ARM嵌入式平台。
2. 准备
a). Verdin i.MX8MM ARM核心版配合Verdin Development Board,连接调试串口(载板X66)到开发主机方便调试,X66 连接了4个串口,其中第三个是 Cortex-M4 核心的默认调试串口,第四个是 Cortex-A53 核心的默认调试串口。
b). 为了测试 GPT 输入捕获, 相应的需要一个PWM 波发生设备,这里使用Toradex 基于NXP i.MX8M Plus ARM处理器的Verdin i.MX8MP 核心板配合 Dahlia Board 作为PWM output使用。同样连接调试串口(载板X18)到开发主机方便调试。
c). Verdin i.MX8MP Cortex-A53 核心系统使用Toradex Yocto Linux BSP6, 更多说明请参考这里。
d). 参考如下将 Verdin i.MX8MP PWM1 连接到 Verdin i.MX8MM GPT1 Capture 管脚,同时为了阻断载板其他电路干扰,将 Verdin Development Board X6 Pin_24 的跳线帽去掉。
Dahlia Board X20 Pin_9 -> Verdin Development Board X5Pin_24 SODIMM_252
3). Verdin i.MX8MM M4核心FreeRTOS基本资料
a). Verdin i.MX8MM HMP(Heterogeneous Multi-core Processing) 架构基本说明请参考如下:
https://developer.toradex.cn/software/cortex-m/hmp-memory-areas-on-toradex-soms/
b). 参考如下说明下载配置 NXP 用于开发 Cortex-M 核心的 MCUXpresso SDK
https://developer.toradex.cn/software/cortex-m/setting-up-sdk-toolchain/
c). Verdin i.MX8MM 编译运行 M4 firmware 操作流程请参考如下文章
d). MCUXpresso SDK 包含的 sample 示例应用可以参考如下 SDK 源位置
$cd <SDK_root>/boards/evkmimx8mm/
$ tree -L 2
.
├── cmsis_driver_examples
│ ├── ecspi
│ ├── enet
│ ├── i2c
│ └── uart
├── demo_apps
│ ├── hello_world
│ └── sai_low_power_audio
├── driver_examples
│ ├── ecspi
│ ├── enet
│ ├── gpio
│ ├── gpt
│ ├── i2c
│ ├── pdm
│ ├── pwm
│ ├── rdc
│ ├── sai
│ ├── sdma
│ ├── sema4
│ ├── tmu
│ ├── uart
│ └── wdog
├── evkmimx8mm.png
├── freertos_examples
│ ├── freertos_event
│ ├── freertos_generic
│ ├── freertos_hello
│ ├── freertos_mutex
│ ├── freertos_queue
│ ├── freertos_sem
│ ├── freertos_swtimer
│ └── freertos_tickless
├── multicore_examples
│ ├── rpmsg_lite_pingpong_rtos
│ └── rpmsg_lite_str_echo_rtos
└── project_template
├── board.c
├── board.h
├── BOARD_Project_Template_evkmimx8mm.cmake
├── clock_config.c
├── clock_config.h
├── peripherals.c
├── peripherals.h
├── pin_mux.c
└── pin_mux.h
4). Verdin i.MX8MM Cortex- M4核心FreeRTOS GPT Capture 示例驱动开发
a). Verdin i.MX8MM MCUXpresso SDK 已经包含一个简单的 GPT Capture sample驱动,本文基于此 sample 进行修改测试。
$cd <SDK_root>/boards/evkmimx8mm/driver_examples/gpt/capture
$ tree -L 1
.
├── armgcc
├── board.c
├── board.h
├── clock_config.c
├── clock_config.h
├── empty_rsc_table.c
├── fsl_iomuxc.h
├── gpt_capture.c
├── gpt_capture_v3_14.xml
├── pin_mux.c
├── pin_mux.h
└── readme.md
b). 首先先确认 pin_mux 定义以及其他 i.MX8MM 初始化基本配置,如果需要可以进行修改
./ pin_mux.h/pin_mux.c 用于确定项目中使用的管脚定义,本文中使用的正好就是示例默认的 GPT1 Capture1 管脚,因此无需修改。如果用到其他管脚,就需要进行修改,支持的所有管脚定义可以参考 fsl_iomuxc.h 文件。
/* FUNCTION ************************************************************************************************************
*
* Function Name : BOARD_InitPins
* Description : Configures pin routing and optionally pin electrical features.
*
* END ****************************************************************************************************************/
void BOARD_InitPins(void) { /*!< Function assigned for the core: Cortex-M4[m4] */
IOMUXC_SetPinMux(IOMUXC_SAI3_RXFS_GPT1_CAPTURE1, 0U);
IOMUXC_SetPinMux(IOMUXC_UART4_RXD_UART4_RX, 0U);
...
./ board.h/board.c 用于 i.MX8MM M4 核心基本初始化配置,本文不做修改。
./ clock_config.h/clock_config.c 用于 i.MX8MM M4 核心基本时钟配置,本文不做修改。
c). GPT Capture 功能实现
./ 本文 GPT Capture 功能定义
GPT1 capture1 管脚输入一个给定频率(如 1k Hz )和占空比(如 50% ) 的PWM 信号,通过捕获输入上升/下降沿中断,分别获得相邻两次中断的 GPT Counter 计数器的计数,并以此来计算输入 PWM 信号的半波周期。
./ GPT Capture 功能基本都是通过 gpt_capture.c 文件代码来实现,默认 sample 是捕获上升沿中断后,打印中断当时的 GPT Counter 计数数值。
./ 为了实现本文定义的捕获功能,首先增加如下全局变量定义
/*******************************************************************************
* Variables
******************************************************************************/
volatile bool gptIsrFlag_Start = false;
volatile bool gptIsrFlag_Finish = false;
volatile uint8_t gptIsrFlag_Overflow = 0;
volatile uint32_t captureVal = 0;
volatile uint32_t captureVal_Last = 0;
// gptIsrFlag_Start 定义为第一次捕获中断开始标志
// gptIsrFlag_Finish 定义为第二次捕获中断结束标志
// gptIsrFlag_Overflow 定义为 GPT Counter 溢出标志计数
// captureVal 定义为第二次中断 GPT Counter 数值
// captureVal_Last 定义为第一次中断 GPT Counter 数值
./ GPT Interrupt 函数修改如下:
首先处理计数器溢出情况,如果中断发生时候已经发生溢出,则增加 gptIsrFlag_Overflow 溢出标志计数的数值;然后通过 gptIsrFlag_Start / gptIsrFlag_Finish 标志位来分别处理第一次和第二次中断,获取第一次和第二次中断时候的 GPT Counter 数值,同时分别翻转 GPT Capture Interrupt 模式。
void EXAMPLE_GPT_CAPTURE_IRQHandler(void)
{
/* GPT Counter Overflow processing */
if (GPT_GetStatusFlags(DEMO_GPT_BASE, kGPT_RollOverFlag) != false)
{
if (gptIsrFlag_Start == true)
{
gptIsrFlag_Overflow ++;
}
GPT_ClearStatusFlags(DEMO_GPT_BASE, kGPT_RollOverFlag);
}
if (GPT_GetStatusFlags(DEMO_GPT_BASE, kGPT_InputCapture1Flag) != false)
{
if(gptIsrFlag_Finish != true)
{
/* First time IRQ */
if (gptIsrFlag_Start == false)
{
captureVal_Last = GPT_GetInputCaptureValue(DEMO_GPT_BASE, BOARD_GPT_INPUT_CAPTURE_CHANNEL);
/* Switch Interrupt mode to falling edge */
GPT_SetInputOperationMode(DEMO_GPT_BASE, BOARD_GPT_INPUT_CAPTURE_CHANNEL, kGPT_InputOperation_FallEdge);
gptIsrFlag_Start = true;
}
/* Second time IRQ */
else
{
captureVal = GPT_GetInputCaptureValue(DEMO_GPT_BASE, BOARD_GPT_INPUT_CAPTURE_CHANNEL);
/* Switch Interrupt mode to rising edge */
GPT_SetInputOperationMode(DEMO_GPT_BASE, BOARD_GPT_INPUT_CAPTURE_CHANNEL, kGPT_InputOperation_RiseEdge);
gptIsrFlag_Start = false;
gptIsrFlag_Finish = true;
}
}
GPT_ClearStatusFlags(DEMO_GPT_BASE, BOARD_GPT_CHANNEL_FLAG);
}
SDK_ISR_EXIT_BARRIER;
}
./ Main 主函数修改如下:
int main(void)
{
uint64_t int_Peroid = 0;
uint32_t time_Ms = 0;
uint32_t time_Us = 0;
gpt_config_t gptConfig;
...
GPT_GetDefaultConfig(&gptConfig);
/* Initialize GPT module */
GPT_Init(DEMO_GPT_BASE, &gptConfig);
...
/* Setup input capture on a gpt channel */
GPT_SetInputOperationMode(DEMO_GPT_BASE, BOARD_GPT_INPUT_CAPTURE_CHANNEL, kGPT_InputOperation_RiseEdge);
...
/* Enable GPT Overflow interrupt */
GPT_EnableInterrupts(DEMO_GPT_BASE, kGPT_RollOverFlagInterruptEnable);
...
while (true)
{
/* Check whether occur 2nd interupt */
if (true == gptIsrFlag_Finish)
{
/* GPT counter diff value between 2 IRQs */
int_Peroid = gptIsrFlag_Overflow * (uint64_t) 0xffffffff;
int_Peroid = int_Peroid + captureVal;
int_Peroid = int_Peroid - captureVal_Last;
/* transfer counter value to peroid */
time_Us = (uint32_t) ((int_Peroid / 24) % 1000);
time_Ms = (uint32_t) ((int_Peroid / 24) / 1000);
PRINTF("\r\n interval between 2 rising edge =%u ms and %u us\r\n", time_Ms, time_Us);
gptIsrFlag_Overflow = 0;
gptIsrFlag_Finish = false;
}
else
{
__WFI();
}
}
}
// 通过 GPT_GetDefaultConfig 函数获取默认的 GPT Capture 配置,参考 docs 目录下的 MCUXpresso SDK API Reference Manual_MIMX8MM6.pdf 文档,可以查到默认配置如下,如果需要也可以修改这个配置
config->clockSource = kGPT_ClockSource_Periph;
config->divider = 1U;
config->enableRunInStop = true;
config->enableRunInWait = true;
config->enableRunInDoze = false;
config->enableRunInDbg = false;
config->enableFreeRun = false;
config->enableMode = true;
// 通过 GPT_SetInputOperationMode 函数将 GPT Capture 模式初始配置为上升沿触发
// 为了处理 GPT Counter Overflow,使能对应中断
// while 函数循环执行当 gptIsrFlag_Finish 第二次中断采集结束标志位声明后,打印捕获的输入 PWM 波的半波周期。如果有溢出发生,则需要考虑增加相应的 0xffffffff 溢出计数次数。这里需要说明下,由于 NXP iMX8MM SoC 也受到如下 Errata 影响,因此 GPT Clock Source 只能使用内部 24M Hz 时钟源,所以这里直接使用 24M 来算出半波周期是多少 ms 和 us 。
https://www.nxp.com.cn/docs/en/errata/IMX8X_C0_0N99Z_ER.pdf
5). Verdin i.MX8MM Cortex-M4核心FreeRTOS GPT Capture示例部署测试
a). 将上述修改后的项目参考章节 3 的相关资料编译后,复制 gpt_capture.bin 可执行文件到 Verdin i.MX8MM 核心板 Linux /home/root 目录下保存。
b). 对Verdin i.MX8MM 模块进入 U-boot 命令行,通过如下命令配置 Cortex-M4 核心 Firmware 下载和运行
setenv load_cmd "ext4load mmc 0:2"
setenv m4image "/home/root/gpt_capture.bin"
> setenv m4image_size 17000
> setenv loadm4image "${load_cmd} ${loadaddr} ${m4image}"
> setenv m4boot "${loadm4image}; cp.b ${loadaddr} 0x7e0000 ${m4image_size}; dcache flush; bootaux 0x7e0000"
> saveenv
> run m4boot
c). Verdin i.MX8MM Cortex-M4 核心运行后其调试串口打印信息
GPT input capture example
Once the input signal is received the input capture half peroid is printed
d). 此时在 Verdin i.MX8MP 平台通过如下脚本使能 1kHz 50% 占空比 PWM 输出 10s 时间
#!/bin/sh
cd /sys/class/pwm/pwmchip0/
echo 0 > export
echo 1000000 > pwm0/period
echo 500000 > pwm0/duty_cycle
echo "normal" > pwm0/polarity
echo 1 > pwm0/enable
sleep 10
echo 0 > pwm0/enable
e). 这时 Verdin i.MX8MM Cortex-M4 调试串口就会打印出对应的半波周期
...
Input Capture Half Period Value = 0ms and 500us
Input Capture Half Period Value = 0ms and 500us
Input Capture Half Period Value = 0ms and 500us
Input Capture Half Period Value = 0ms and 500us
...
f). 尝试将 Verdin i.MX8MP PWM 修改为 10kHz 80%/20% 占空比
...
echo 100000 > pwm0/period
echo 80000 > pwm0/duty_cycle
...
g). Verdin i.MX8MM Cortex-M4 输出周期会对应变化
Input Capture Half Period Value = 0ms and 80us
Input Capture Half Period Value = 0ms and 80us
Input Capture Half Period Value = 0ms and 80us
Input Capture Half Period Value = 0ms and 80us
h). 最后,由于 Verdin i.MX8MM GPT1 CAPTURE1 管脚在 Cortex-A53 核心 Linux 下默认是用于 WAKEUP GPIO 使用,如果需要同时运行 Verdin i.MX8MM Cortex-A53 核心和 Cortex-M4 核心,就需要在 Linux device-tree 文件中将 WAKEUP gpio-key 功能替换为其他 GPIO 管脚资源。
6). 总结
本文简单示例了基于i.MX8MM Cortex-M4 核心 GPT Capture 功能供参考。