利用异构多核处理器开发嵌入式应用入门 - 编译和部署应用到 在 Cortex-M 上其实很简单!

2017年4月4日星期二

每天都有新的异构多核处理器/片上系统 SoC 面市。在 SoC 上集成微控制器和外设控制核正变得越来越普遍,看看最新发布的 NXP®i.MX 6SoloXi.MX7 和即将面世的 i.MX 8前不久发布的 Toradex Apalis 新产品)。在我看来,这有点像曾经 ADC(模数转换器)开始集成微处理器上的外设功能,在应用处理器上集成微控制器,可以解决 Linux 系统中一些实时可控相关的问题。

如今,Toradex 已经有两款基于多核异构构架的系统模块 SoM / 计算机模块 CoM,即 Colibri iMX7Colibri VF61。另外还有两个即将发布的模块,Colibri iMX6ULL 和  Apalis iMX8 赋予用户在管脚兼容系列模块上的灵活性。

新技术的出现总是会引出许多问题,或许你会产生疑问,这是否需要很多工作量。本位旨在快速、明了地介绍一种使用异构多核方式开发应用的方法。这里我们将会涉及搭建开发环境以及创建一个双核通信的 ping pong 应用的基本步骤,最后演示一个用微控制器通过 SPI 读取 ADC 数据并把数据发送至运行 Linux 的处理器的实际应用。

这是揭示利用异构多核处理构架 SoC 开发嵌入式系统的系列文章。通过实际操作和一些案例演示,你可以快速地开始开发。

硬件

本文中将使用 Toradex 双核 Colibri iMX7 计算机模块:该模块采用 NXP i.MX7 SoC,一个双核 Arm Cortex-A7 和 一个 Arm Cortex-M4 核心,A7 主频为 1GHz,M4 主频为 200MHz,同时具备 512MB 存储和 512MB 内存。模块如下图所示:

Colibri iMX7D

Aster Carrier Board

载板采用 Aster。这是 Toradex 新发布的产品,使新项目开发更加容易。该载板具有标准的 Arduino 接口,使开发人员能够利用市面上丰富的 Arduino 模块,缩减研发时间。

除了 Arduino,还有一个兼容 Raspberry Pi 的接口,允许在开发的硬件上使用模块,不仅能够促进新产品的原型开发,也能够帮助从概念验证到可扩展、工业品质、保证生命周期硬件方案如 Toradex 的过渡。

搭建开发环境

本文中演示的案例是在 Linux 电脑上开发的。所有 Cortex-M 上的代码都基于 Makefile 和 Cmake。你只需要安装少量的软件并正确配置编译工具链,就可以编译示例代码。

我们建议使用 4.9 2015 Q3 版本 linaro toolchain。从这里下载好压缩包后,解压如下:

tar xjf ~/Downloads/gcc-arm-none-eabi-4_9-2015q3-20150921-linux.tar.bz2

因为编译工具生成 32位应用,所以需要安装 32位的 libc 和 libncurse。在 Ubuntu 上,命令如下:

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386

现在可以测试编译工具:

~/gcc-arm-none-eabi-4_9-2015q3/bin/arm-none-eabi-gcc --version
arm-none-eabi-gcc (GNU Tools for Arm Embedded Processors) 4.9.3 20150529 (release) [Arm/embedded-4_9-branch revision 227977]
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

最后,安装 cmake 和 make:

sudo apt-get install make cmake
下载示例

我们准备了一些示例,方便下载和测试,包括基本的 双核通信“Hello, World!”。下载源代码:

$ git clone -b colibri-imx7-m4-freertos-v8 git://git.toradex.com/freertos-toradex.git freertos-colibri-imx7/
$ cd freertos-colibri-imx7/

所有我们将会使用的源码都在这个文件夹里面。其中的文件已经能够支持 Colibri iMX7 和 FreeRTOS。在所有这些文件中,我们主要使用包含示例的的文件夹:

[raul@localhost freertos-colibri-imx7]$ tree -L 2 examples/imx7_colibri_m4/
examples/imx7_colibri_m4/
├── board.c
├── board.h
├── clock_freq.c
├── clock_freq.h
├── demo_apps
│   ├── blinking_imx_demo
│   ├── hello_world
│   ├── hello_world_ddr
│   ├── hello_world_ocram
│   ├── low_power_imx7d
│   ├── rpmsg
│   └── sema4_demo
├── driver_examples
│   ├── adc_imx7d
│   ├── ecspi
│   ├── flexcan
│   ├── gpio_imx
│   ├── gpt
│   ├── i2c_imx
│   ├── uart_imx
│   └── wdog_imx
├── gpio_pins.c
├── gpio_pins.h
├── pin_mux.c
└── pin_mux.h

17 directories, 8 files
[raul@localhost freertos-colibri-imx7]$
搭建硬件环境

本文中,我们将不涉及如何调试 Cortex-M 的内容,我们使用 UART 打印固件的输出信息。了解如何搭建产品开发环境是十分重要的。由于 Cortex-M 和  Cortex-A 共享外设接口,你需要知道 UART B 被 Cortex-M 上的固件输出打印信息,UART A 则由 Cortex-A (U-boot and Linux) 使用。

Cable for UART

所以我们将使用 UART A 和 UART B。对于 UART A,在 Aster 上已经有 FTDI 芯片,可以直接连接 USB X4。该接口不仅用于给载板供电,还可以访问 UART-A, 所以当连接到电脑后,/dev/ttyUSBX 设备将会被自动识别。

对于 UART B, Colibri iMX7  的 TX 和 RX 引脚在 X20 扩展口上。因为没有 FTDI 或者 RS-232 转换器,你需要使用 FTDI 串口线。连接 RX、TX 和 GND 到 X20 的 第8、10、9 引脚。

FTDI cable

最后,图下图所示连接:

UARTA and UARTB

现在都已经正确连接,在 Linux 使用 picocom 打开两个终端,打开串口:

终端 1:

[raul@localhost ~]$ picocom -b 115200 /dev/ttyUSB0

终端 2:

[raul@localhost ~]$ picocom -b 115200 /dev/ttyUSB1

你也许会看到下面的信息:

Compiler
编译第一个示例

进入 SPI 示例目录,编译第一个应用:

[raul@localhost freertos-colibri-imx7]$ cd examples/imx7_colibri_m4/driver_examples/ecspi/ecspi_interrupt/master/
[raul@localhost master]$ ls
armgcc  hardware_init.c  main.c

所有的示例都有 main.c 、hardware_init.c 和 armgcc 文件夹。我们先不解释源代码,只是进入目录,导出下载的 toolchain 路径然后编译:

[raul@localhost armgcc]$ cd ..
[raul@localhost master]$ cd armgcc/
[raul@localhost armgcc]$ export ArmGCC_DIR=~/gcc-arm-none-eabi-4_9-2015q3/
[raul@localhost armgcc]$ ./build_all.sh
-- TOOLCHAIN_DIR: /home/raul/gcc-arm-none-eabi-4_9-2015q3/
-- BUILD_TYPE: Debug
-- TOOLCHAIN_DIR: /home/raul/gcc-arm-none-eabi-4_9-2015q3/
-- BUILD_TYPE: Debug
-- Could not determine Eclipse version, assuming at least 3.6 (Helios). Adjust CMAKE_ECLIPSE_VERSION if this is wrong.
-- The ASM compiler identification is GNU
-- Found assembler: /home/raul/gcc-arm-none-eabi-4_9-2015q3//bin/arm-none-eabi-gcc
-- Configuring done
-- Generating done
-- Build files have been written to: /home/raul/freertos-colibri-imx7/examples/imx7_colibri_m4/driver_examples/ecspi/ecspi_interrupt/master/armgcc
Scanning dependencies of target ecspi_interrupt_master_example
[  5%] Building C object CMakeFiles/ecspi_interrupt_master_example.dir/home/raul/freertos-colibri-imx7/platform/utilities/src/debug_console_imx.c.obj
...
...
...
[ 94%] Building C object CMakeFiles/ecspi_interrupt_master_example.dir/home/raul/freertos-colibri-imx7/platform/drivers/src/uart_imx.c.obj
[100%] Linking C executable debug/ecspi_interrupt_master_example.elf
[100%] Built target ecspi_interrupt_master_example
-- TOOLCHAIN_DIR: /home/raul/gcc-arm-none-eabi-4_9-2015q3/
-- BUILD_TYPE: Release
-- Eclipse version is set to 3.6 (Helios). Adjust CMAKE_ECLIPSE_VERSION if this is wrong.
-- Configuring done
-- Generating done
CMake Warning:
  Manually-specified variables were not used by the project:

    CMAKE_TOOLCHAIN_FILE


-- Build files have been written to: /home/raul/freertos-colibri-imx7/examples/imx7_colibri_m4/driver_examples/ecspi/ecspi_interrupt/master/armgcc
[  5%] Building ASM object CMakeFiles/ecspi_interrupt_master_example.dir/home/raul/freertos-colibri-imx7/platform/devices/MCIMX7D/startup/gcc/startup_MCIMX7D_M4.S.obj
...
...
...
[ 94%] Building C object CMakeFiles/ecspi_interrupt_master_example.dir/home/raul/freertos-colibri-imx7/platform/drivers/src/uart_imx.c.obj
[100%] Linking C executable release/ecspi_interrupt_master_example.elf
[100%] Built target ecspi_interrupt_master_example
[raul@localhost armgcc]$

	The binaries are located in the "release" directory.
[raul@localhost armgcc]$ cd release/
[raul@localhost release]$ ls
ecspi_interrupt_master_example.bin  ecspi_interrupt_master_example.hex
ecspi_interrupt_master_example.elf  ecspi_interrupt_master_example.map
[raul@localhost release]$

在这里, bin 文件是最重要的。我们使用 U-boot 将其加载到 Cortex-M4。

运行固件程序

为了运行固件程序,U-boot 需要加载这个二进制文件,然后在 Cortex-M 上运行。也可以用另外的方法。我的建议是使用 SD 卡或者网络。我们将会演示如何使用这两种方法。一方面,需要知道的是使用网络,开发将以动态的方式进行,因为不需要在载板上拔插 SD 卡。另一方面,为了使用以太网加载文件,你需要配置 tftp 服务器,我这里配置为 "/srv/tftp/"。参考 Flashing Linux Over Ethernet 了解 tftp 配置。

SD 卡:
复制文件到 SD 卡,然后放到载板上:

[raul@localhost release]$ df
Filesystem              1K-blocks      Used Available Use% Mounted on
/dev/sdb1                 7780496    469540   7310956   7% /run/media/raul/DATA
[raul@localhost release]$ cp ecspi_interrupt_master_example.bin /run/media/raul/DATA
[raul@localhost release]$ umount /run/media/raul/DATA

以太网:
复制文件到 tftp 服务器目录,在载板上连接网线,配置好能够连接到电脑的网络。这里载板的 IP 是 192.168.0.170,电脑 IP 为 192.168.0.150。

[raul@localhost release]$ cp ecspi_interrupt_master_example.bin /srv/tftp/

开启载板电源,上电的时候,在 UART-A (U-boot and Linux)  终端上按下任意按键。进入 U-boot,加载可执行文件。

U-boot prompt

在 U-boot 中,执行命令加载文件:

SD 卡:

Colibri iMX7 # fatload mmc 0:1 0x7F8000 ecspi_interrupt_master_example.bin
reading ecspi_interrupt_master_example.bin
9956 bytes read in 20 ms (485.4 KiB/s)

以太网:

Colibri iMX7 # tftp 0x7F8000 ecspi_interrupt_master_example.bin
Using FEC0 device
TFTP from server 192.168.0.150; our IP address is 192.168.0.170
Filename 'ecspi_interrupt_master_example.bin'.
Load address: 0x7f8000
Loading: ##################################################  9.7 KiB
	 647.5 KiB/s
done
Bytes transferred = 9956 (26e4 hex)

加载完成后,无论是使用 SD 卡还是以太网,执行下面的命令运作已经加载到 Cortex-M 上的程序。

Command prompt

Colibri iMX7 # dcache flush
Colibri iMX7 # bootaux 0x7F8000
## Starting auxiliary core at 0x007F8000 ...
Colibri iMX7 #

接下来,你应该可以看到在 UART B 终端上打印出 Cortex-M 的调试信息。你的屏幕如下图所示。

Debug Messages

在 UART B 终端里按 “s”之前,试着将 SPI MISO 和 MOSI 连接起来。这样就可以看到在回环模式下的通信,不仅是发送数据,还可以接收 SPI 数据。

SPI MISO AND MOSI

-------------- ECSPI master driver example --------------

This example application demonstrates usage of SPI driver in master mode.
It transfers data to/from remote MCU in SPI slave mode.
Press "s" when spi slave is ready.
MASTER: Transmited data: 1
      : Received data: 1
MASTER: Transmited data: 2
      : Received data: 2
...
...
...
MASTER: Transmited data: 19
      : Received data: 19
MASTER: Transmited data: 20
      : Received data: 20
示例 - SPI

在之前的示例中,我们只编译和执行了代码。现在我们将修改源码,实现同 Microchip MCP3008 的 SPI 通信。这个一个10位 ADC,具有8个输入。按下图连接到 Aster 和面包板:

Aster Carrier Board - Connected

Aster Carrier Board - Connected 2

Aster Carrier Board - Connected 3

Aster Carrier Board - Connected 4

如果喜欢使用 Eclipse IDE,可以通过 CMake 生成 Eclipse 项目文件。 Cmake 的 -G 参数可以配置 “build system generator”。确保 build_all.sh 指定 “Eclipse CDT4 – Unix Makefiles”。

在 armgcc 示例目录中:

[raul@localhost armgcc]$ vi build_all.sh
#!/bin/sh
cmake -DCMAKE_TOOLCHAIN_FILE="../../../../../../../tools/cmake_toolchain_files/armgcc.cmake" -G "Eclipse CDT4 - Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug  .
make -j4
cmake -DCMAKE_TOOLCHAIN_FILE="../../../../../../../tools/cmake_toolchain_files/armgcc.cmake" -G "Eclipse CDT4 - Unix Makefiles" -DCMAKE_BUILD_TYPE=Release  .
make -j4

接下来运行 “build_all.sh”脚本:

[raul@localhost armgcc]$ ./build_all.sh
[raul@localhost armgcc]$ ls .cproject .project
.cproject  .project   

打开 Eclipse 并导入项目
File > Import…

Root Directory

在 “Select root directory”,输入 “armgcc”文件夹目录

/home/raul/freertos-colibri-imx7/examples/imx7_colibri_m4/driver_examples/ecspi/ecspi_interrupt/master/armgcc
Main.C Directory

打开目录中的 main.c”文件
[TARGET] → [exec]ecspi_interrupt_master_example → Source Files

ECSPI Interrupt Master

标准的示例是十分简单的。我们有必要介绍部分代码,从而在下面的示例中能够清楚地了解需要查看什么地方。

int main(void)
{
    uint8_t control_char;
    uint8_t i;

    ecspi_init_config_t ecspiMasterInitConfig = {
        .baudRate = 500000,
        .mode = ecspiMasterMode,
        .burstLength = ECSPI_MASTER_BURSTLENGTH,
        .channelSelect = BOARD_ECSPI_CHANNEL,
        .clockPhase = ecspiClockPhaseSecondEdge,
        .clockPolarity = ecspiClockPolarityActiveHigh,
        .ecspiAutoStart = ECSPI_MASTER_STARTMODE
    };

    /* Hardware initialize, include RDC, CLOCK, IOMUX, ENABLE MODULE */
    hardware_init();

    /* Update clock frequency of this module */
    ecspiMasterInitConfig.clockRate = get_ecspi_clock_freq(BOARD_ECSPI_BASEADDR);

    PRINTF("\n-------------- ECSPI master driver example --------------\n\n\r");
    PRINTF("This example application demonstrates usage of SPI driver in master mode.\n\r");
    PRINTF("It transfers data to/from remote MCU in SPI slave mode.\n\r");

    /* Ecspi module initialize, include configure parameters */
    ECSPI_MasterConfig(&ecspiMasterInitConfig);

    /* Wait slave ready, then press 's' to start communication. */
    while(true)
    {
        PRINTF("Press \"s\" when spi slave is ready.\n\r");
        control_char = GETCHAR();
        if((control_char == 's') || (control_char == 'S'))
            break;
    }
    /* Send 1~20 to slave and receive data from slave */
    for(i = 0; i < 20; i++)
    {
        txData[0]++;
        ECSPI_MasterTransfer((uint8_t*)txData, (uint8_t*)rxData, 1);
        while(ECSPI_MasterGetTransferStatus());
        PRINTF("MASTER: Transmited data: %d \n\r", txData[0]);
        PRINTF("      : Received data: %d \n\n\r", rxData[0]);
    }
    while(1);
}

第一个需要注意的配置引脚复用的地方。这里我们将使用标准的 SPI。右击“hardware_init();”函数,选择 “Open Declaration”

void hardware_init(void)
{
    /* Board specific RDC settings */
    BOARD_RdcInit();
    /* Board specific clock settings */
    BOARD_ClockInit();
    /* initialize debug uart */
    dbg_uart_init();

    /* RDC ECSPI */
    RDC_SetPdapAccess(RDC, BOARD_ECSPI_RDC_PDAP, 3 << (BOARD_DOMAIN_ID * 2), false, false);
    /* Select board ecspi clock derived from OSC clock(24M) */
    CCM_UpdateRoot(CCM, BOARD_ECSPI_CCM_ROOT, ccmRootmuxEcspiOsc24m, 0, 0);
    /* Enable ecspi clock gate */
    CCM_EnableRoot(CCM, BOARD_ECSPI_CCM_ROOT);
    CCM_ControlGate(CCM, BOARD_ECSPI_CCM_CCGR, ccmClockNeededAll);
    /* Configure ecspi pin IOMUX */
    configure_ecspi_pins(BOARD_ECSPI_BASEADDR);
}

主要的硬件初始化和配置都在这个函数中完成。SPI 引脚的配置在最后一个函数 “configure_ecspi_pins(BOARD_ECSPI_BASEADDR);”。

void configure_ecspi_pins(ECSPI_Type* base)
{
	// ECSPI1 iomux configuration
	/* daisy chain selection */
	IOMUXC_ECSPI3_MISO_SELECT_INPUT = 0;  //(I2C1_SCL  SODIM 90)
	IOMUXC_ECSPI3_MOSI_SELECT_INPUT = 0;  //(I2C1_SCL  SODIM 90)

	/* iomux */
	IOMUXC_SW_MUX_CTL_PAD_I2C2_SCL = IOMUXC_SW_MUX_CTL_PAD_I2C2_SCL_MUX_MODE(3);    /* ECSPI SLK  */
	IOMUXC_SW_MUX_CTL_PAD_I2C1_SDA = IOMUXC_SW_MUX_CTL_PAD_I2C1_SDA_MUX_MODE(3);    /* ECSPI MOSI */
	IOMUXC_SW_MUX_CTL_PAD_I2C1_SCL = IOMUXC_SW_MUX_CTL_PAD_I2C1_SCL_MUX_MODE(3);    /* ECSPI MISO  */
	IOMUXC_SW_MUX_CTL_PAD_I2C2_SDA  = IOMUXC_SW_MUX_CTL_PAD_I2C2_SDA_MUX_MODE(3);     /* ECSPI SS0 */

	/* pad control */
	IOMUXC_SW_PAD_CTL_PAD_I2C2_SCL =    IOMUXC_SW_PAD_CTL_PAD_I2C2_SCL_PE_MASK  |
			IOMUXC_SW_PAD_CTL_PAD_I2C2_SCL_PS(0)    |      /* pull down */
			IOMUXC_SW_PAD_CTL_PAD_I2C2_SCL_DSE(0)   |
			IOMUXC_SW_PAD_CTL_PAD_I2C2_SCL_HYS_MASK;

	IOMUXC_SW_PAD_CTL_PAD_I2C1_SDA = IOMUXC_SW_PAD_CTL_PAD_I2C1_SDA_DSE(0)   |
			IOMUXC_SW_PAD_CTL_PAD_I2C1_SDA_HYS_MASK;

	IOMUXC_SW_PAD_CTL_PAD_I2C1_SCL = IOMUXC_SW_PAD_CTL_PAD_I2C1_SCL_HYS_MASK;

	IOMUXC_SW_PAD_CTL_PAD_I2C2_SDA  =  IOMUXC_SW_PAD_CTL_PAD_I2C2_SDA_PE_MASK   |
			IOMUXC_SW_PAD_CTL_PAD_I2C2_SDA_PS(3)     |      /* pull up */
			IOMUXC_SW_PAD_CTL_PAD_I2C2_SDA_DSE(0)    |
			IOMUXC_SW_PAD_CTL_PAD_I2C2_SDA_HYS_MASK;
}

另外一个重要的文件是“board.h”。在同一个函数中,搜索 "configure_ecspi_pins (BOARD_ECSPI_BASEADDR);" 中的 "BOARD_ECSPI_BASEADDR",你将会发现部分“board.h”内容,这里配置除了 SPI 外的其他内容,例如中断向量表。

/* Colibri SPI is ECSPI3 */
#define BOARD_ECSPI_RDC_PDAP                rdcPdapEcspi3
#define BOARD_ECSPI_CCM_ROOT                ccmRootEcspi3
#define BOARD_ECSPI_CCM_CCGR                ccmCcgrGateEcspi3
#define BOARD_ECSPI_BASEADDR                ECSPI3
#define BOARD_ECSPI_CHANNEL                 ecspiSelectChannel0
#define BOARD_ECSPI_IRQ_NUM                 eCSPI3_IRQn
#define BOARD_ECSPI_HANDLER                 eCSPI3_Handler

回到“main.c”我将改变主函数,获取 MCP3008 的数据。具体地讲,我们将读取芯片 channel 0 的数据。

/* Wait slave ready, then press 's' to start communication. */
    while(true)
    {
        PRINTF("Press \"s\" when spi slave is ready.\n\r");
        control_char = GETCHAR();
        if((control_char == 's') || (control_char == 'S'))
            break;
    }

删除“break”,增加下面的代码。根据 MCP3008 白皮书,“00000001 10000000 00000000”序列分别表示起始位、通道选择和10位数据的信息。

/* Wait slave ready, then press 's' to start communication. */
	while(true)
	{
		PRINTF("Press \"s\" when spi slave is ready.\n\r");
		control_char = GETCHAR();
		if((control_char == 's') || (control_char == 'S'))
		{
			unsigned char datatx[3];
			unsigned char datarx[3];
			datatx[0] = 0b00000001;  //  first byte transmitted -> start bit
			datatx[1] = 0b10000000; // second byte transmitted -> (SGL/DIF = 1, D2=D1=D0=0)
			datatx[2] = 0b00000000; // third byte transmitted....don't care

			/* SPI Read */
			ECSPI_MasterTransfer((uint8_t*)&datatx[0], (uint8_t*)&datarx[0], 3);
			while(ECSPI_MasterGetTransferStatus());
			PRINTF("Transmited data: %d \n\r", datatx[0]);
			PRINTF("Transmited data: %d \n\r", datatx[1]);
			PRINTF("Transmited data: %d \n\r", datatx[2]);
			PRINTF("Received data: %d \n\n\r", datarx[0]);
			PRINTF("Received data: %d \n\n\r", datarx[1]);
			PRINTF("Received data: %d \n\n\r", datarx[2]);
			unsigned int a2dVal = 0;
			a2dVal = (datarx[1]<< 8) & 0b1100000000; //merge data[1] & data[2] to get result
			a2dVal |=  (datarx[2] & 0xff);

			PRINTF("data = %d \n\n\r", a2dVal);
		}
	}

修改完毕后,“int main (void)” 应该如下:

int main(void)
{
	uint8_t control_char;
	uint8_t i;

	ecspi_init_config_t ecspiMasterInitConfig = {
			.baudRate = 500000,
			.mode = ecspiMasterMode,
			.burstLength = ECSPI_MASTER_BURSTLENGTH,
			.channelSelect = BOARD_ECSPI_CHANNEL,
			.clockPhase = ecspiClockPhaseSecondEdge,
			.clockPolarity = ecspiClockPolarityActiveHigh,
			.ecspiAutoStart = ECSPI_MASTER_STARTMODE
	};

	/* Hardware initialize, include RDC, CLOCK, IOMUX, ENABLE MODULE */
	hardware_init();

	/* Update clock frequency of this module */
	ecspiMasterInitConfig.clockRate = get_ecspi_clock_freq(BOARD_ECSPI_BASEADDR);

	PRINTF("\n-------------- ECSPI master driver example --------------\n\n\r");
	PRINTF("This example application demonstrates usage of SPI driver in master mode.\n\r");
	PRINTF("It transfers data to/from remote MCU in SPI slave mode.\n\r");

	/* Ecspi module initialize, include configure parameters */
	ECSPI_MasterConfig(&ecspiMasterInitConfig);

	/* Wait slave ready, then press 's' to start communication. */
	while(true)
	{
		PRINTF("Press \"s\" when spi slave is ready.\n\r");
		control_char = GETCHAR();
		if((control_char == 's') || (control_char == 'S'))
		{
			unsigned char datatx[3];
			unsigned char datarx[3];
			datatx[0] = 0b00000001;  //  first byte transmitted -> start bit
			datatx[1] = 0b10000000; // second byte transmitted -> (SGL/DIF = 1, D2=D1=D0=0)
			datatx[2] = 0b00000000; // third byte transmitted....don't care

			/* SPI Read */
			ECSPI_MasterTransfer((uint8_t*)&datatx[0], (uint8_t*)&datarx[0], 3);
			while(ECSPI_MasterGetTransferStatus());
			PRINTF("Transmited data: %d \n\r", datatx[0]);
			PRINTF("Transmited data: %d \n\r", datatx[1]);
			PRINTF("Transmited data: %d \n\r", datatx[2]);
			PRINTF("Received data: %d \n\n\r", datarx[0]);
			PRINTF("Received data: %d \n\n\r", datarx[1]);
			PRINTF("Received data: %d \n\n\r", datarx[2]);

			unsigned int a2dVal = 0;
			a2dVal = (datarx[1]<< 8) & 0b1100000000; //merge data[1] & data[2] to get result
			a2dVal |=  (datarx[2] & 0xff);

			PRINTF("data = %d \n\n\r", a2dVal);
		}
	}
}

重新编译,根据前面的示例通过 SD 卡或者以太网复制,执行二进制程序。

SD 卡:

[raul@localhost release]$ df
Filesystem              1K-blocks      Used Available Use% Mounted on
/dev/sdb1                 7780496    469540   7310956   7% /run/media/raul/DATA
[raul@localhost release]$ cp ecspi_interrupt_master_example.bin /run/media/raul/DATA
[raul@localhost release]$ umount /run/media/raul/DATA

以太网:

[raul@localhost release]$ cp ecspi_interrupt_master_example.bin /srv/tftp/

Insert the SD card on the board or set up the network and execute the binary.

SD 卡:

Colibri iMX7 # fatload mmc 0:1 0x7F8000 ecspi_interrupt_master_example.bin
reading ecspi_interrupt_master_example.bin
9956 bytes read in 20 ms (485.4 KiB/s)

以太网:

Colibri iMX7 # tftp 0x7F8000 ecspi_interrupt_master_example.bin
Using FEC0 device
TFTP from server 192.168.0.150; our IP address is 192.168.0.170
Filename 'ecspi_interrupt_master_example.bin'.
Load address: 0x7f8000
Loading: ##################################################  9.7 KiB
	 647.5 KiB/s
done
Bytes transferred = 9956 (26e4 hex)

一旦固件加载完毕,使用哪种方法就不再重要,执行下面命令运行 Cortex-M 上加载的程序。

Colibri iMX7 # dcache flush
Colibri iMX7 # bootaux 0x7F8000
## Starting auxiliary core at 0x007F8000 ...
Colibri iMX7 #

现在使用修改后的代码,在 UART B 终端中按“s”将显示 channel 0 上模拟采集。

同 Linux 之间的冲突

在使用这些 U-boot 命令之后,你或许想要在启动 Linux 后运行“boot”命令。现在的问题是,我们的示例使用了 UART B 和 the SPI。想要正常启动 Linux,就需要修改 device tree,让 Linux 不去使用这些资源。

你可以使用下面的命令,暂时关闭  UART B 和 SPI,而无需修改 device tree:

Colibri iMX7 # setenv fdt_fixup 'fdt addr ${fdt_addr_r} && fdt rm /soc/aips-bus@30800000/spba-bus@30800000/serial@30890000  && fdt rm /soc/aips-bus@30800000/spba-bus@30800000/ecspi@30840000'
Colibri iMX7 # saveenv
Saving Environment to NAND...
Erasing NAND...
Erasing at 0x380000 -- 100% complete.
Writing to NAND... OK

更多关于修改 device tree 的内容,可以参考 Toradex 开发者中心网站上的这篇文章。

自动部署

在我的演示示例中,我通过以太网加载  Cortex-M 固件程序。一个节约时间的方法是自动复制文件到 “/dev/tftp/”目录中。在项目的根目录中,打开文件:

raul@localhost master]$ vi armgcc/CMakeLists.txt

在最后面添加下面几行内容:

[raul@localhost master]$ vi armgcc/CMakeLists.txt ADD_CUSTOM_COMMAND(TARGET ${Project_Name}_Main POST_BUILD COMMAND cp ${EXECUTABLE_OUTPUT_PATH}/ecspi_interrupt_master_example.bin /srv/tftp/m4.bin)

再次运行 “./build_all.sh”脚本,如果使用 Eclipse 编译,你可以在“console”中看到自动执行的命令:

cp /home/raul/freertos-colibri-imx7/examples/imx7_colibri_m4/driver_examples/ecspi/ecspi_interrupt/master/armgcc/release/ecspi_interrupt_master_example.bin /srv/tftp/m4.bin

另外一个对我有帮助的优化是,在 U-boot 中创建自动加载固件程序的规则:

Colibri iMX7 # setenv m4 'tftp 0x7F8000 m4.bin && dcache flush && bootaux 0x7F8000'
Colibri iMX7 # setenv bootcmd 'run m4; run ubiboot; setenv fdtfile ${soc}-colibri-${fdt_board}.dtb && run distro_bootcmd;'

现在,每一次开启模块,就会自动加载固件程序然后运行 Linux。

总结

在本文中,你可以掌握搭建异构多核处理器构架方案的基本步骤。通过两个演示示例,我们看到了如何在 Colibri iMX7 计算机模块的 HMP SoC Cortex-M4 核上编译和运行代码。我们也了解到 SoC 上的不同内核共享外设接口,所以你需要了解(以及规划)每个内核分配的外设。

下一篇文章将会介绍如何实现双核通信。

参考
#Asymmetric Multicore #Embedded Linux #FreeRTOS #Heterogeneous Processing #NXP® i.MX 8 #NXP® i.MX6 #NXP® i.MX7 #NXP® Vybrid™ #Yocto
Author Raul Rosetto Muñoz, Field Application Engineer, Toradex Brasil

1 comments

Beni3141 - 2 years 2 months | Reply

Great article! I retraced all your steps on windows and it was quite easy. I used a small tool called "OpenTFTPServer" to host the files on my workstation and the rest was more a less the same.

One thing you should add: in the end also save the changes with "saveenv", otherwhise all the typing was for nothing. :D

Toradex - 2 years 1 month | Reply

Hi Beni, we're glad you liked the article. And thank you very much for the feedback! We have already added "saveenv" after the command. Do stay tuned to the Toradex Blog.

Leave a comment

Your email ID will be kept confidential. Required fields are marked *


请填写上面所示的字符。不区分大小写。



* Your comment will be reviewed and then added. Thank you.