NXP iMX8X M4核心SPI开发
- 关键词:NXP,iMX8X,FreeRTOS
- 作者:By Toradex胡珊逢
- 摘要:Colibri iMX8X 计算机模块上的处理器具有 Cortex-A35 和 Cortex-M4F。在 A35 上运行 Linux 操作系统, M4F 通常运行一个实时擦操作系统例如 FreeRTOS。NXP 的 MCUxpresso SDK 提供了 Cortex-M4F 例程,能够帮助用户进行开发。但在 MCUxpresso SDK 中只提供了少量的外设操作演示,本文将介绍如何修改配置文件,并调用 FreeRTOS API 创建一个 SPI 例程,驱动 SPI 接口的 OLED 屏幕。
Colibri iMX8X 计算机模块上的处理器具有 Cortex-A35 和 Cortex-M4F。在 A35 上运行 Linux 操作系统, M4F 通常运行一个实时擦操作系统例如 FreeRTOS。NXP 的 MCUxpresso SDK 提供了 Cortex-M4F 例程,能够帮助用户进行开发。但在 MCUxpresso SDK 中只提供了少量的外设操作演示,本文将介绍如何修改配置文件,并调用 FreeRTOS API 创建一个 SPI 例程,驱动 SPI 接口的 OLED 屏幕。
首先从 NXP 网站下载 MCUxpresso SDK。根据所使用的模块,分别选择 Select Development Board → Processors → I.MX → 8QuadXPlus → MIMX8QXx → MIMX8QX5xxxDZ/MIMX8QX6xxxDZ,最后点击 Build MCUXpresso SDK 即可下载。
在 SDK 安装目录的 boards/mekmimx8qx/rtos_examples/ 位置创建一个 freertos_lpspi 文件夹,里面工程文件可以从 freertos_lpuart 复制然后进行修改,我们也提供修改好的例程以便使用。主要修改的内容如下。
l pin_mux.h
定义使用的引脚,包括输出调试信息的串口,LPSPI 以及两个 GPIO 用于 OLED 的复位和命令/数据选择。
---------------------------------------
/* ADC_IN2 (coord V32), M40_UART0_RX */
#define BOARD_INITPINS_M40_UART0_RX_PIN_FUNCTION_ID SC_P_SCU_GPIO0_00 /*!< Pin function id */
/* ADC_IN3 (coord V30), M40_UART0_TX */
#define BOARD_INITPINS_M40_UART0_TX_PIN_FUNCTION_ID SC_P_SCU_GPIO0_01 /*!< Pin function id */
#define BOARD_INITPINS_SPI2_MOSI_PIN_FUNCTION_ID SC_P_SPI2_SDO
#define BOARD_INITPINS_SPI2_MISO_PIN_FUNCTION_ID SC_P_SPI2_SDI
#define BOARD_INITPINS_SPI2_CLK_PIN_FUNCTION_ID SC_P_SPI2_SCK
#define BOARD_INITPINS_SPI2_CS0_PIN_FUNCTION_ID SC_P_SPI2_CS0
#define BOARD_INITPINS_BB_UART2_TX_PIN_FUNCTION_ID SC_P_UART2_TX /* SODIMM21 GPIO1.IO23 OLED COMMAND/DATA SELECT*/
#define BOARD_INITPINS_BB_UART2_RX_PIN_FUNCTION_ID SC_P_UART2_RX /* SODIMM19 GPIO1.IO24 OLED RESET*/
---------------------------------------
l pin_mux.c
初始化上面定义的引脚,并配置复用关系。设置在BOARD_InitPins函数中完成。
---------------------------------------
void BOARD_InitPins(sc_ipc_t ipc) /*!< Function assigned for the core: Cortex-M4F[m4] */
{
sc_err_t err = SC_ERR_NONE;
err = sc_pad_set_all(ipc, BOARD_INITPINS_M40_UART0_RX_PIN_FUNCTION_ID, 2U, SC_PAD_CONFIG_NORMAL, SC_PAD_ISO_OFF, 0x0 ,SC_PAD_WAKEUP_OFF);/* IOMUXD_ADC_IN2 register modification value */
if (SC_ERR_NONE != err)
{
assert(false);
}
---------------------------------------
l freertos_lpspi.c
这里包括了对LPSPI 的设置,以及通过 SPI 发送数据。
---------------------------------------
sc_pm_set_resource_power_mode(ipc, SC_R_SPI_2, SC_PM_PW_MODE_ON)
---------------------------------------
配置 LPSPI 的供电。
---------------------------------------
sc_pm_clock_enable(ipc, SC_R_SPI_2, SC_PM_CLK_PER, true, 0);
if (CLOCK_SetIpFreq(kCLOCK_DMA_Lpspi2, SC_60MHZ) == 0)
---------------------------------------
设置 LPSPI 时钟源。
---------------------------------------
LPSPI_RTOS_Init(&handle, ADMA__LPSPI2, &lpspi_config, LPUART_CLK_FREQ)
---------------------------------------
完成对 LPSPI 工作状态配置,包括 SPI 时钟频率、相位、采样点、帧长等,这些包含在 lpspi_config 结构体中。
---------------------------------------
lpspi_master_config_t lpspi_config = {
.baudRate = 6000000,
.bitsPerFrame = 1024, /*!< Bits per frame, minimum 8, maximum 4096.*/
.cpol = kLPSPI_ClockPolarityActiveLow,
.cpha = kLPSPI_ClockPhaseSecondEdge,
.direction = kLPSPI_MsbFirst,
.pcsToSckDelayInNanoSec = 50,
.lastSckToPcsDelayInNanoSec = 50,
.betweenTransferDelayInNanoSec = 50,
.whichPcs = kLPSPI_Pcs0,
.pcsActiveHighOrLow = kLPSPI_PcsActiveLow,
.pinCfg = kLPSPI_SdiInSdoOut,
.dataOutConfig = kLpspiDataOutRetained,
};
---------------------------------------
其中bitsPerFrame 是指 SPI 的帧长,根据 SPI 设备实际数据输入要求需要做相应的更改,通常指令和数据的长度是不一样。例如在这个例程里多次调用 LPSPI_RTOS_Init 函数对其进行调整。
---------------------------------------
LPSPI_RTOS_TransferBlocking(&handle, &spi_data)
---------------------------------------
该函数实现 SPI 数据发送。由于采用了阻塞的方式发送,需要等待数据传输完毕才推出函数。数据存储在 lpspi_transfer_t 格式的结构体中。其中也包含了 SPI 一些配置,例如使用哪个 CS 片选,是否连续发送等。
---------------------------------------
lpspi_transfer_t spi_data = {
.txData = send_buffer,
.rxData = recv_buffer,
.dataSize = sizeof(send_buffer),
.configFlags = kLPSPI_MasterPcs0 | kLPSPI_MasterPcsContinuous | kLPSPI_MasterByteSwap,
};
---------------------------------------
上面 SPI 相关 API 主要来自 devices/MIMX8QX6/drivers/fsl_lpspi_freertos.c,fsl_lpspi.c。在默认的 fsl_lpspi_freertos.c 中只有非阻塞方式的 SPI 传输函数 LPSPI_RTOS_Transfer()。因此在这里我们新构建一个阻塞方式的函数 LPSPI_RTOS_TransferBlocking()。
---------------------------------------
status_t LPSPI_RTOS_TransferBlocking(lpspi_rtos_handle_t *handle, lpspi_transfer_t *transfer)
{
status_t status;
status = LPSPI_MasterTransferBlocking(handle->base, transfer);
if (status != kStatus_Success)
{
return status;
}
return status;
}
---------------------------------------
在 fsl_lpspi_freertos.h 头文件中申明该函数。
---------------------------------------
status_t LPSPI_RTOS_TransferBlocking(lpspi_rtos_handle_t *handle, lpspi_transfer_t *transfer);
---------------------------------------
另外为了支持编译,在 devices/MIMX8QX6/drivers 目录中创建 driver_lpspi_freertos_MIMX8QX6.cmake 和 driver_lpspi_MIMX8QX6.cmake 两个文件。相应地在上面项目工程目录中的boards/mekmimx8qx/rtos_examples/freertos_lpspi/armgcc/CMakeLists.txt 中将 LPSPI 的驱动添加进来。
---------------------------------------
# include modules
include(driver_clock_MIMX8QX6)
include(driver_lpspi_MIMX8QX6)
include(driver_lpspi_freertos_MIMX8QX6)
---------------------------------------
到此我们已经完成 LPSPI 在 FreeRTOS 的配置以及创建一个工程项目来使用 LPSPI 发送数据。上面的操作涉及 SDK 中多处修改,为了方便用户测试,我们也提供经修改的整个SDK。
编译好后,在 U-Boot 中通过 tftp 下载 M4 固件并运行。
---------------------------------------
Colibri iMX8X # print m4boot_test
m4boot_test=tftp ${loadaddr} m4_0.bin; dcache flush; bootaux ${loadaddr} 0
Colibri iMX8X # run m4boot_test
---------------------------------------
OLED 屏幕显示如下。
总结
通过上面的内容介绍了如何在 M4 上使用默认例程之外的外设,SDK 中还提供了诸多外设的 FreeRTOS API。用户可以使用类似的方法进行开发。