Full system development tutorial based on imx8m plus development board 5: Cortex-M7 development

Foreword:

The i.MX8M Plus development board has 4 Cortex-A53 cores, running at 1.8GHz; 1 Cortex-M7 core, running at 800MHz; in addition, it also integrates a 2.3 TOPS NPU, which greatly accelerates machine learning reasoning.

The development platform used in the full text is the FS-IMX8MPCA development board (Huaqing Yuanjian imx8mp development board) that cooperates with NXP official, supports Weston, ubuntu20.04, Android11 ​​and other operating systems; also supports Xenomai hard real-time kernel, EtherCAT bus, TSN Time-sensitive network, ROS1.0, ROS2.0 and other industrial and robot applications; can be used in industrial Internet, artificial intelligence, edge computing, multi-screen display and other application directions. Huaqing Foresight R&D Center has compiled a large number of development tutorials and recorded rich video teaching resources for free!

For more information on the development board, please pay attention to Huaqing Vision Online Lab (WeChat ID: hqyjlab) to receive`` 

 Cortex-M7  development

The first program  Helloworld

This chapter takes the simplest Helloworld program as an example, run the program on the Cortex-M7 core of the development board, to

Debugging, compiling and downloading of demo programs.

Compile and run Cortex-M7  program

Before starting the Cortex-M7 core, it is necessary to connect the debugging serial port and the power supply of the development board to the development board.

⚫ Import source code

First, you need to open the Helloworld program based on Cortex-M7, which is located in [Huaqing Vision-I.MX8M Plus

SDK_2_10_0_EVK-MIMX8MP.zip folder under Development Materials\Program Source Code\Cortex-M7].

Open the "IAR EW for Arm 9.10.1" application, click the "File" -> "Open workspace..." button

 

In the pop-up dialog box, select the hello_world.eww file in the directory SDK_2_10_0_EVKMIMX8MP/boards/evkmimx8mp/demo_apps/helloworld\iar

Open the hello_world project.

 

After that, you can see the hello_world project in the upper left corner of the window

 

⚫ Compile the source code

The structure of the hello_world project is divided into "debug", "release", "ddr_debug", "ddr_release",

“flash_debug”,“flash_release”。

debug/release: The program is used to execute in the TCM inside the CPU.

ddr_debug/ddr_release: Mainly used for execution in DRAM.

flash_debug/ flash_release: Mainly used to execute in QSPI/XIP internal memory.

Here we simulate the program through the emulator, so the debug/release code is used for compilation.

Find the debug menu in the Workspace area in the upper left corner of the window

 

After switching successfully, find the "Make" button in the toolbar to compile the program image.

 

The compilation is successful as shown in the figure below

 

Startup of the Cortex-M7  coprocessor

Before starting the Cortex-M7 core, it is necessary to connect the debugging serial port and the power supply of the development board to the development board.

⚫ Execute program in TCM

First import the Helloworld program mentioned above, and then switch to "release" successfully, after the programming is successful, it will be in

The hello_world.bin image is generated under the [iar\release] directory.

We can copy the image to the root directory of the SDcard, and the development board reads the image file in the SDcard directory

The file is loaded into the TCM to execute the program.

We power up the development board so that the program stays in the u-boot control terminal.

 

To load the image file through SDcard, execute the following command

The default TF card has no partition

u-boot=> mmc dev 1

u-boot=> fatload mmc 1 0x48000000 hello_world.bin

u-boot=> cp.b 0x48000000 0x7e0000 0x20000

u-boot=> bootaux 0x7e0000

 

After successful execution, you will see "hello world." character output on the serial port terminal of M7

 

Cortex-M7  coprocessor self-booting

In the previous section, we implemented the manual startup of the Cortex-M7 coprocessor. This section mainly implements the Cortex-M7 coprocessor

The power-on of the device starts automatically.

The Cortex-M7 coprocessor is not the same as the traditional M7 processor. The processor cannot be powered on by itself and requires

Boot the Cortex-M7 through the u-boot loader.

The program in the external SDcard memory can be automatically loaded into the TCM for execution through the following instructions.

The Helloworld program image in the previous chapter is taken as an example for demonstration.

First put the image generated by the release program into the SDcard root directory, and then set the m7_run environment variable to "mmc

dev 1; fatload mmc 1:1 0x48000000 hello_world.bin; cp.b 0x48000000 0x7e0000 0x20000; bootaux 0x7e0000" The environment variable here is the environment variable mentioned in the previous section.

We power up the development board so that the program stays in the u-boot control terminal.

 

Then enter the following command to set the environment variable

u-boot=> setenv m7_run 'mmc dev 1; fatload mmc 1:1 0x48000000 hello_world.bin; cp.b

0x48000000 0x7e0000 0x20000; bootaux 0x7e0000'

u-boot=> setenv boot_mmc 'mmc dev ${mmcdev}; if mmc rescan; then if run loadbootscript;

then run bootscript; else if run loadimage; then run mmcboot; else run netboot; fi; fi; fi;'

u-boot=> setenv bootcmd 'run m7_run; run boot_mmc'

u-boot=> saveenv

 

After restarting the development board, you can see that the program will run automatically on the M7 serial port terminal.

In this example, the TCM operation mode under SDcard is used as an example, and all the startup methods provided in the previous section are supported by this method.

You only need to modify the corresponding m7_run environment variable.

LED  light control

Hardware principle analysis

As long as you are operating on the hardware, you must first check the schematic diagram. See which pin the peripheral is connected to on the SoC. development board

The on and off state of the LED on the chip is related to the pin I/O output level on the chip.

For LED I/O expansion on the development platform, find the LED schematic diagram in the baseboard schematic diagram as follows:

 

As shown in the figure, it can be seen that the pin number of the control LED light is GPIO_LED, and then continue to search for this number.

 

Under this schematic diagram, it can be found that the network number of the corresponding pin of the socket connected to it is SD1_DATA5, and then in the core

Look for this net number under the schematic of the board.

 

As shown in the figure, the pin of the chip connected to it can be finally obtained as AA29, and we set the high and low levels of this pin to finally

You can control the on and off of the LED light.

 

The commonly used registers are mainly as follows:

 

 

 

 

 

 

 

 

 

 

 

Programming

First introduce the more important structures used in the program

⚫ gpio_pin_config_t : Mainly used to configure GPIO pins

typedef struct _gpio_pin_config

{

 /*!< Specifies the pin direction. */

gpio_pin_direction_t direction;

/*!< Set a default output logic, which has no use in input */

uint8_t outputLogic;

/*!< Specifies the pin interrupt mode, a value of @ref gpio_interrupt_mode_t. */

gpio_interrupt_mode_t interruptMode;

 

} gpio_pin_config_t;

 

 

Only some important codes are provided here. If you want to view the detailed implementation of some functions, please open the tool corresponding to the CD-ROM.

[ Data CD\Huaqing Vision-I.MX8M Plus Development Documentation\Program Source Code\Coertx-M7\igpio_led_output_task] .

#define EXAMPLE_LED_GPIO GPIO2

#define EXAMPLE_LED_GPIO_PIN 7U

volatile bool g_pinSet = false;

int main(void)

{

 /* Define the init structure for the output LED pin*/

gpio_pin_config_t led_config = {kGPIO_DigitalOutput, 0, kGPIO_NoIntmode};

/* Board pin, clock, debug console init */

/* M7 has its local cache and enabled by default,

 * need to set smart subsystems (0x28000000 ~ 0x3FFFFFFF)

 * non-cacheable before accessing this address region */

BOARD_InitMemory();

/* Board specific RDC settings */

BOARD_RdcInit();

BOARD_InitBootPins();

 BOARD_BootClockRUN();

 BOARD_InitDebugConsole();

/* Print a note to terminal. */

/* Init output LED GPIO. */

GPIO_PinInit(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, &led_config);

while (1)

 {

 SDK_DelayAtLeastUs(100000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);

if (g_pinSet)

 {

 GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 0U);

 g_pinSet = false;

 }

 else

{

 GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 1U);

 g_pinSet = true;

 }

 }

}

 

In this program, line 7 defines a pin configuration structure, and then from line 13 to line 20 are the

And system initialization, initialize GPIO on line 25, the first parameter determines which GPIO to operate, the second parameter determines which pin to operate, and the third parameter is the pin configuration. Then in the while loop, lines 29 to 39 control the cyclic flashing of the LED light.

Note: To run the program, refer to the section "  Starting the Cortex-M7 Coprocessor".

Serial transceiver

Hardware principle analysis

Basic knowledge of serial communication:

Serial port communication refers to the bit-by-bit transmission of data between peripherals and computers through data signal lines, ground lines, control lines, etc.

a means of communication. This communication method uses fewer data lines, which can save communication costs in long-distance communication, but its

The transfer speed is lower than parallel transfer.

The serial port is a very common device communication protocol on the computer. Most computer (excluding laptops) bags

Contains two serial ports based on RS-232. The serial port is also a common communication protocol for instrumentation equipment (the serial port communication protocol can also be used to obtain data from remote acquisition devices).

The communication method in which the data of each bit of a message is transmitted bit by bit in sequence is called serial communication.

Evaluating whether a communication is of high quality is mainly reflected in the speed of transmission, the correctness of data, whether the power consumption is low, and whether the wiring cost is low (for example, if one line can be sent and received, it is more cost-effective than parallel transmission and reception of eight lines); use Whether it is popular (it seems that everyone learns English, can most people in the world use English independently? If there are many people who can speak English, it is very popular, and the communication surface is very wide; if you learn bird language, then only Can communicate with birds, no one can understand). The characteristics of serial communication are: data bit transmission, the transmission is carried out in bit order, at least one transmission line is needed to complete, the cost is low but the transmission speed is slow. The distance of serial communication can be from a few meters to several thousand meters.

RS-232 (English synonym for serial port) adopts unbalanced transmission mode, which is the so-called single-ended communication, and its common mode suppression ability

Poor, the longest transmission distance is about 15 meters, and the highest rate is 20kb/s. RS-232 is for point-to-point (that is, only one pair of receiving,

Sending equipment) communication design. So RS-232 is suitable for communication between local devices.

The traditional serial interface standard has 22 wires, using a standard 25-core D-type socket (DB25), which was later simplified

It is a 9-pin D-type socket (DB9), and 25-pin plug sockets are rarely used in current applications.

The few-wire serial port mentioned now generally refers to the use of several wires. The original RS-232 serial port is 25-pin, so

Some pin definitions are used, and later became 9-pin. The so-called full-featured serial port means that all pin definitions are used, such as flow control, handshake signals, etc. Generally speaking, foreign products are used for product comparison. According to the rules, all the serial port signals are uploaded. However, domestic technicians found that the RS-232 serial port mainly uses 2 and 3 wires. If the other interfaces are not used, there will be no major problems. Therefore, the 9-pin interface is simplified , so there are so-called serial ports with 2, 3, 4, 5, 6, and 8 wires. .

The 2-wire serial port only has two basic sending and receiving signal lines, RXD and TXD; the 3-wire serial port has RXD and TXD in addition to

GND; the so-called 4~9 wires are only the corresponding control signal wires added on the basis of TXD and RXD, and are adjusted according to actual needs.

line design. Generally speaking, the use of 5-wire 232 communication adds hardware flow control, that is, RTS and CTS signals, mainly to ensure the reliability of high-speed communication. If your communication speed is not very high, you can completely ignore it. According to the transmission direction of information, serial communication can be further divided into three types: simplex, half-duplex and full-duplex. Information can only be transmitted in one direction as simplex; information that can be transmitted in both directions but not at the same time is called half-duplex; information that can be transmitted in both directions at the same time is called full-duplex.

• If at any moment in the communication process, information can only be transmitted from one party A to the other party B, it is called simplex.

• If at any time, information can be transmitted from A to B, and from B to A, but can only be stored by transmission in one direction

Now, it is called half-duplex transmission.

• If there is bidirectional signal transmission from A to B and B to A on the line at any time, it is called full duplex.

Parallel communication and serial communication:

There are two basic ways for the microcontroller to communicate with the outside world: parallel communication and serial communication, and the serial port belongs to serial communication. parallel

Communication refers to using multiple data transmission lines to send or receive each bit of a data at the same time. Serial communication refers to the use of a transmission

The transmission line sends or receives data bit by bit sequentially.

The schematic diagram of parallel communication and serial communication is as follows:

 

When the transmission rate of each transmission line is the same, the transmission speed of parallel communication is faster than that of serial communication. However, when the transmission distance

When the distance becomes longer, the disadvantages of parallel communication will be highlighted. First, compared with serial communication, the signal is susceptible to external interference, and the signal

The mutual interference between the lines also increases, and the second is that after the rate is increased, the data of each data line cannot be guaranteed to reach the receiver at the same time

However, reception errors are generated, and the longer the distance, the higher the wiring cost.

Therefore, parallel communication is currently mainly used in short-distance communication, such as the processor and external flash and external RAM.

And the communication between various functional modules inside the chip. Serial communication has become a long-distance communication due to its advantages of fast communication speed and low cost

The first choice for off-site communication. RS232 serial port, and differential serial bus like RS485 serial port, USB interface, CAN interface,

IEEE-1394 interface, Ethernet interface, SATA interface and PCIE interface all belong to the category of serial communication.

The left side of the figure below shows the ideal situation where the data of each data line arrives at the receiver at the same time and is correctly sampled; the picture on the right side is

The data of each data line cannot arrive at the receiver at the same time, resulting in a reception error situation.

 

The principle and characteristics of the serial port:

The serial port is a very common device communication protocol on the computer (not to be confused with the Universal Serial Bus

or USB confusion). Most computers contain two RS232-based serial ports. The serial port is also an instrumentation device

Common communication protocol; many GPIB (General Purpose Interface Bus) compatible devices also have RS-232 ports. At the same time, the serial communication

The protocol can also be used to obtain data from remote collection devices.

The concept of serial communication is very simple, the serial port sends and receives bytes bit by bit. Although compared to the byte (byte) and

Serial communication is slow, but a serial port can send data on one wire while receiving data on the other. it's simple and

Capable of long-distance communication. Typically, the serial port is used for the transmission of ASCII characters. Communication is done using 3 wires: (1)

Ground wire, (2) transmit, (3) receive. Since serial communication is asynchronous, the port can send data on one line at the same time

Receive data on the other line. Other lines are used for handshaking, but are not required. The most important parameters of serial communication are baud rate,

data bits, stop bits, and parity.

For two ports to pass through, these parameters must match:

Baud rate:

This is a parameter to measure the communication speed. It represents the number of bits transmitted per second. eg 300 baud

It means sending 300 bits per second. When we refer to the clock period, we mean the baud rate eg if the protocol requires 4800 baud rate then the clock is 4800Hz. This means that the serial communication has a sampling rate of 4800Hz on the data line. Common phone lines have baud rates of 14400, 28800 and 36600. The baud rate can be much higher than these values, but the baud rate is inversely proportional to the distance. High baud rates are often used for communication between instruments that are placed very close together, a typical example is the communication of GPIB devices.

Data bits:

This is a measure of the actual data bits in the communication. When a computer sends a packet of information, the actual data is not

Will be 8 bits, the standard values ​​are 5, 7 and 8 bits. How to set it up depends on the information you want to send. For example, the standard ASCII code is 0~127 (7 bits). The extended ASCII code is 0~255 (8 bits). If the data is plain text (standard ASCII), then each packet uses 7 bits of data. Each packet refers to a byte, including start/stop bits, data bits, and parity bits. Since the actual data bits depend on the choice of communication protocol, the term "packet" refers to any communication.

stop bit:

Used to represent the last digit of a single packet. Typical values ​​are 1, 1.5 and 2 bits. The 1.5-bit data width here is 1.5 baud rate. Since the data is timed on the transmission line, and each device has its own clock, it is likely that there is a small out-of-sync between the two devices during communication. . So the stop bit not only signals the end of the transfer, but also gives the computer an opportunity to correct clock synchronization. The more bits available for stop bits, the greater the tolerance for different clock synchronizations, but the slower the data transfer rate.

Parity bits:

A simple error detection method in serial communication. There are four error detection modes: Even, Odd, High and Low.

Of course, no check digit is also possible. For the case of even and odd parity, the serial port will set the parity bit (one after the data bit)

bits), use a value to ensure that the transmitted data has even or odd logic high bits. For example, if the data is 011, then for even parity, the parity bit is 0, and the number of bits that guarantee logic high is an even number. If it is odd parity, the parity bit is 1, so there are 3 logic high bits. The high and low bits do not really check the data, simply set the logic high or logic low to check. This allows the receiving device to know the state of a bit, giving it the opportunity to determine if there is noise interfering with the communication or if the transmitted and received data are out of sync.

Hardware flow control: 

Hardware flow control is commonly used with RTS/CTS flow control and DTR/R (data terminal ready/data set

ready) flow control. For hardware flow control, the corresponding cables must be connected. When using RTS/CTS (request to send/clear to send) flow control, the RTS and CTS lines at both ends of the communication should be connected correspondingly. The data terminal equipment (such as a computer) uses RTS to start A data communication device (such as a modem) uses CTS to start and pause data flow from a computer. The process of this hardware handshake method is as follows: we set a high flag (which can be 75% of the buffer size) and a low flag (which can be 25% of the buffer size) according to the buffer size of the receiving end when programming. When the amount of data in the area reaches a high level, we set the CTS line low at the receiving end (send logic 0). When the program at the sending end detects that CTS is low, it stops sending data until the amount of data in the buffer at the receiving end is low. Set CTS high when low. RTS is used to indicate whether the receiving device is ready to receive data.

Commonly used flow control is also DTR/R (data terminal ready/data set ready). We will not go into details here.

Open our baseboard schematic and find the serial port of M7 for serial communication, as shown in the figure:

 

As shown in the figure, it can be seen that the serial port used by the M7 core is UART4, and then continue to search for this number.

 

In this schematic diagram, it can be found that the network numbers of the corresponding pins of the socket connected to it are UART4_TXD and

UART4_RXD, then find this network number under the schematic diagram of the core board

 

As shown in the figure, you can get the RXD and TXD of the serial port used to connect the pins of the chip to AJ5 and AH3.

⚫ Connect the debugging serial port

 

According to the M7 debugging serial port circled in the above figure (down and up are 5V, M4_TX, M4_RX, A53_TX, A53_RX,

GND). Here we only connect GND, M7_RX, M7_TX three lines.

Main Controller Configuration

The main registers used are as follows, because not all bits of each register will be used, here is just an introduction to each

Certain bits used in the register.

 

 

 

 

 

 

 

 

 

 

 

 

Programming

First introduce the more important structures used in the program.

⚫ struct _uart_config: This structure is used to configure the UART serial port

typedef struct _uart_config

{

 /*!< UART baud rate. */

uint32_t baudRate_Bps;

 /*!< Parity error check mode of this module. */

uart_parity_mode_t parityMode;

 /*!< Data bits count, eight (default), seven */

uart_data_bits_t dataBitsCount;

 /*!< Number of stop bits in one frame. */

uart_stop_bit_count_t stopBitCount;

 /*!< TX FIFO watermark */

uint8_t txFifoWatermark;

/*!< RX FIFO watermark */

uint8_t rxFifoWatermark;

 /*!< RX RTS watermark, RX FIFO data count being larger than \

 this triggers RTS deassertion */

uint8_t rxRTSWatermark;

 /*!< Enable automatic baud rate detection */

bool enableAutoBaudRate;

 /*!< Enable TX */

bool enableTx;

 /*!< Enable RX */

bool enableRx;

 /*!< RX RTS enable */

bool enableRxRTS;

 /*!< TX CTS enable */

bool enableTxC
其成员变量各作用如下
struct _uart_transfer:UART 传输结构体,一般传输数据时会用到此结构体
typedef struct _uart_transfer
{

 union

{

uint8_t *data; /*!< The buffer of data to be transfer.*/

uint8_t *rxData; /*!< The buffer to receive data. */

const uint8_t *txData; /*!< The buffer of data to be sent. */

};

 size_t dataSize; /*!< The byte count to be transfer. */

} uart_transfer_t;

 

⚫ struct _uart_transfer: UART transfer structure, which is generally used when transferring data

typedef struct _uart_transfer
{
 union
{
uint8_t *data; /*!< The buffer of data to be transfer.*/
uint8_t *rxData; /*!< The buffer to receive data. */
const uint8_t *txData; /*!< The buffer of data to be sent. */
};
 size_t dataSize; /*!< The byte count to be transfer. */
} uart_transfer_t;

 

Then introduce the functions used.

⚫ status_t UART_Init function

 

⚫ status_t UART_TransferSendNonBlocking function

 

⚫ status_t UART_TransferReceiveNonBlocking 函数

 

⚫ void UART_TransferCreateHandle function

 

Only some important codes are provided here. If you want to view the detailed implementation of some functions, please open the tool corresponding to the CD-ROM.

[Data CD\Huaqing Vision-I.MX8M Plus Development Documentation\Program Source Code\CoertxM7\iuart_interrupt_transfer_task].

int main(void)

{

 status_t status;

 uart_config_t config;

 uart_transfer_t xfer;

 uart_transfer_t sendXfer;

 uart_transfer_t receiveXfer;

/* M7 has its local cache and enabled by default,

 * need to set smart subsystems (0x28000000 ~ 0x3FFFFFFF)

 * non-cacheable before accessing this address region */

BOARD_InitMemory();

/* Board specific RDC settings */

BOARD_RdcInit();

BOARD_InitBootPins();

 BOARD_BootClockRUN();

 /*

 * config.baudRate_Bps = 115200U;

 * config.parityMode = kUART_ParityDisabled;

 * config.dataBitsCount = kUART_EightDataBits;

 * config.stopBitCount = kUART_OneStopBit;

 * config.txFifoWatermark = 2;

 * config.rxFifoWatermark = 16;

 * config.enableTx = false;

 * config.enableRx = false;

 */

UART_GetDefaultConfig(&config);

 config.baudRate_Bps = BOARD_DEBUG_UART_BAUDRATE;

 config.rxFifoWatermark = 16;

 config.enableTx = true;

 config.enableRx = true;

status = UART_Init(DEMO_UART, &config, DEMO_UART_CLK_FREQ);

 if (kStatus_Success != status)

 {

 return kStatus_Fail;

 }

UART_TransferCreateHandle(DEMO_UART, &g_uartHandle, UART_UserCallback, NULL);

/* Start to echo. */

sendXfer.data = g_txBuffer;

 sendXfer.dataSize = ECHO_BUFFER_LENGTH;

 receiveXfer.data = g_rxBuffer;

 receiveXfer.dataSize = ECHO_BUFFER_LENGTH;

while (1)

 {

 /* If RX is idle and g_rxBuffer is empty, start to read data to g_rxBuffer. */

if ((!rxOnGoing) && rxBufferEmpty)

 {

 rxOnGoing = true;

 UART_TransferReceiveNonBlocking(DEMO_UART, &g_uartHandle, &receiveXfer, NULL);

 }

/* If TX is idle and g_txBuffer is full, start to send data. */

if ((!txOnGoing) && txBufferFull)

 {

 txOnGoing = true;

 UART_TransferSendNonBlocking(DEMO_UART, &g_uartHandle, &sendXfer);

 }

/* If g_txBuffer is empty and g_rxBuffer is full, copy g_rxBuffer to g_txBuffer. */

if ((!rxBufferEmpty) && (!txBufferFull))

 {

 memcpy(g_txBuffer, g_rxBuffer, ECHO_BUFFER_LENGTH);

 rxBufferEmpty = true;

 txBufferFull = true;

 }

 }

}

 In this program, lines 12 to 18 complete the initialization of the development board and the kernel, and line 29 obtains the default value of the serial port.

Confirm the configuration, and then modify the configuration of the serial port in lines 30 to 33, including setting the serial port baud rate and receiving FIFO size, enabling TX and RX. After completion, initialize the serial port in line 35. The UART handle is initialized in line 41, where the user needs to customize a callback function UART_UserCallback, which is used to judge the status flag.

In while, line 54 judges whether the data is being received and whether the receiving buffer is empty, and starts receiving when the conditions are met

Data, put the received data into g_rxBuffer. In line 68, judge whether the sending buffer is empty and whether the receiving buffer is full, and copy the content of the receiving buffer to the sending buffer when the conditions are met. Line 61 judges whether data is being sent and whether the sending buffer is full, and when the conditions are met, the data in g_txBuffer is sent out. Finally, the receiving and sending of strings is realized. In addition, in this program, the buffer size ECHO_BUFFER_LENGTH is defined as 8, so each sending and receiving is eight characters. To run the program, refer to the section "Starting the Cortex-M7 Coprocessor". The experimental results are as follows:

 

FreeRTOS  real-time operating system application

FreeRTOS  basic interface introduction

The full name of RTOS is Real Time Operation System, that is, real-time operating system. RTOS emphasizes real-time performance and

Divided into hard real-time and soft real-time. Hard real-time requires that the operation must be completed within the specified time, and timeout is not allowed; while in soft real-time

The requirements for processing timeouts are not very strict. The core of RTOS is task scheduling

FreeRTOS is a type of RTOS that is very small and runs on microcontrollers. Microcontrollers are small in size, capital

A source-constrained processor that contains the processor itself, a read-only memory for programs to be executed, on a single chip

(ROM or Flash), the random access memory (RAM) required by the program to be executed, generally the program directly reads from only

Read-memory implementation FreeRTOS is an open source real-time operating system for embedded systems that also supports many different hardware architectures as well as cross-compilers.

The following introduces the functions commonly used in the FreeRTOS operating system, mainly as follows:

⚫ xTaskCreate function

 

⚫ xTaskCreate function

⚫ UART_RTOS_Receive function

 

⚫  UART_RTOS_Send function

 

⚫ xSemaphoreCreateBinary(void) function

⚫ xSemaphoreGive(xSemaphore) function

⚫ SemaphoreTake(SemaphoreHandle_t xSemaphore,TickType_t xBlockTime) 函数

 

Button lighting control system based on  FreeRTOS 

In this experiment, a button is used to control the lighting and extinguishing of the LED light. The control flow chart of the whole program is as follows:

 

The main code is as follows, only some important codes are provided here, if you want to view the detailed implementation of some functions, please open the light

The project corresponding to the disk data [Data CD\Huaqing Vision-I.MX8M Plus Development Data\Program Source Code\CoertxM7\freertos_sem_test].

int main(void)
{
gpio_pin_config_t led_config = {kGPIO_DigitalOutput, 0, kGPIO_NoIntmode};
 GPIO_PinInit(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, &led_config);
xSemaphore_producer = xSemaphoreCreateBinary();
 if (xSemaphore_producer == NULL)
 {
 PRINTF("xSemaphore_producer creation failed.\r\n");
 vTaskSuspend(NULL);
 }
xSemaphore_consumer = xSemaphoreCreateBinary();
 if (xSemaphore_consumer == NULL)
 {
 PRINTF("xSemaphore_consumer creation failed.\r\n");
 vTaskSuspend(NULL);
 }
if (xTaskCreate(producer_task, "PRODUCER_TASK", configMINIMAL_STACK_SIZE + 128, \
 NULL, TASK_PRIO, NULL) != pdPASS)
 {
- 171 -
PRINTF("Task creation failed!.\r\n");
 while (1)
 ;
 }
 if (xTaskCreate(consumer_task, "CONSUMER_TASK", configMINIMAL_STACK_SIZE + 128, \
 NULL, TASK_PRIO, NULL) != pdPASS)
 {
 PRINTF("Task creation failed!.\r\n");
 vTaskSuspend(NULL);
 }
/* Start scheduling. */
vTaskStartScheduler();
 for (;;)
 ;
}
static void producer_task(void *pvParameters)
{
 PRINTF("Producer_task created.\r\n");
 while (1)
 {
 xSemaphoreGive(xSemaphore_consumer);
 while(GPIO_PinRead(EXAMPLE_KEY_GPIO, EXAMPLE_KEY_GPIO_PIN) == 1);
if (xSemaphoreTake(xSemaphore_producer, portMAX_DELAY) == pdTRUE)
 {
 GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 1U);
 PRINTF("LED OFF \n");
 SDK_DelayAtLeastUs(100000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
 }
 }
}
static void consumer_task(void *pvParameters)
{
 while (1)
 {
 if (xSemaphoreTake(xSemaphore_consumer, portMAX_DELAY) == pdTRUE)
 {
 GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 0U);
 PRINTF("LED ON \n");
 SDK_DelayAtLeastUs(100000, SDK_DEVICE_MAXIMUM_CPU_CLOCK_FREQUENCY);
 }
 xSemaphoreGive(xSemaphore_producer);
 

In the main function, two semaphores are defined on lines 4 and 11, two tasks are created on lines 18 and 25, and then tasks are started to run on line 33.

In the producer_task task, line 43 is blocked waiting for the button to be pressed, and line 45 is released when the condition is met

xSemaphore_consumer Semaphore. In the consumer_task task, after obtaining the xSemaphore_consumer semaphore, perform the lighting operation, and then release the xSemaphore_producer semaphore. At this time, after the producer_task task obtains the semaphore, perform the light-off operation.

To run the program, refer to the section "Starting the Cortex-M7 Coprocessor".

Implementation of multi-core communication on Cortex-M7 

This development board supports two cores of Cortex-M7 and Cortex-A53. The so-called multi-core communication refers to the Cortex-M7 core and Cortex-A53

Data exchange between Cortex-A53 cores.

When implementing multi-core communication, some protocol libraries need to be used, mainly including:

(1) Embedded Remote Procedure Call (eRPC): This component is a combination of libraries and code generator tools that implement remote

Transparent function call interface for services (running on different cores).

(2) Multi-Core Manager (MCMGR): This library maintains information about all cores and starts auxiliary cores.

 Its main functions are as follows:

⚫ Maintain information about all cores in the system

⚫ Secondary/secondary core startup and shutdown

⚫ Remote core monitoring and event handling

(3) (RPMsg-Lite): Inter-processor communication library.

The RPMsg protocol needs to be mentioned here. The full name of RPMSG is Remote Processor Messaging, which defines a standard

A standard binary interface for communication between multiple cores in a heterogeneous multi-core system. RPMsg acts as a message transfer bus between processors, where each processor is a device on the bus. It allows kernel drivers to communicate with remote processors on the system, and at the same time, drivers can expose appropriate user-space interfaces as needed. RPMsg-Lite offers code size reduction, API simplification, and improved modularity compared to the legacy OpenAMP implementation.

Key features of the RPMsg protocol:

⚫ Shared memory inter-processor communication

⚫ virtio-based messaging bus

⚫ Application-defined messages sent between endpoints

⚫ Portable to different environments/platforms

⚫ Available on upstream Linux OS

Based on this, this experiment mainly realizes the data transmission between Cortex-M7 and Cortex-A53.

Cortex-M7  terminal program implementation

First introduce the library functions mainly used in the Cortex-M7 part:

⚫ *rpmsg_lite_remote_init function

 

 

The main programs of the M7 terminal are as follows, only some important codes are provided here, if you want to view the detailed implementation of some functions, please type

Open the project corresponding to the CD-ROM data [Data CD\Huaqing Vision-I.MX8M Plus Development Data\Program Source Code\CoertxM7\multicore-communication\rpmsg_lite_str_echo_rtos_imxcm7_test].

int main(void)

{

 BOARD_InitMemory();

/* Board specific RDC settings */

BOARD_RdcInit();

BOARD_InitBootPins();

 BOARD_BootClockRUN();

 BOARD_InitDebugConsole();

copyResourceTable();

#ifdef MCMGR_USED

 /* Initialize MCMGR before calling its API */

(void)MCMGR_Init();

#endif /* MCMGR_USED */

gpio_pin_config_t led_config = {kGPIO_DigitalOutput, 0, kGPIO_NoIntmode};

 GPIO_PinInit(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, &led_config);

if (xTaskCreate(app_task, "APP_TASK", APP_TASK_STACK_SIZE, NULL, \

 tskIDLE_PRIORITY + 1, &app_task_handle) != pdPASS)

 {

 PRINTF("\r\nFailed to create application task\r\n");

 for (;;)

 ;

 }

vTaskStartScheduler();

PRINTF("Failed to start FreeRTOS on core0.\n");

 for (;;)

 ;

}

static void app_task(void *param)

{

 volatile uint32_t remote_addr;

 struct rpmsg_lite_endpoint *volatile my_ept;

 volatile rpmsg_queue_handle my_queue;

 struct rpmsg_lite_instance *volatile my_rpmsg;

 void *rx_buf;

 uint32_t len;

 int32_t result;

 void *tx_buf;

 uint32_t size;

/* Print the initial banner */

PRINTF("\r\nRPMSG String Echo FreeRTOS RTOS API Demo...\r\n");

my_rpmsg = rpmsg_lite_remote_init((void *)RPMSG_LITE_SHMEM_BASE, \

 RPMSG_LITE_LINK_ID, RL_NO_FLAGS);

 while (0 == rpmsg_lite_is_link_up(my_rpmsg))

 ;

my_queue = rpmsg_queue_create(my_rpmsg);

 my_ept = rpmsg_lite_create_ept(my_rpmsg, LOCAL_EPT_ADDR, rpmsg_queue_rx_cb, my_queue);

 (void)rpmsg_ns_announce(my_rpmsg, my_ept, RPMSG_LITE_NS_ANNOUNCE_STRING, RL_NS_CREATE);

PRINTF("\r\nNameservice sent, ready for incoming messages...\r\n");

for (;;)

 {

 /* Get RPMsg rx buffer with message */

result = rpmsg_queue_recv_nocopy(my_rpmsg, my_queue, (uint32_t *)&remote_addr, \

 (char **)&rx_buf, &len, RL_BLOCK);

 if (result != 0)

{

 assert(false);

 }

/* Copy string from RPMsg rx buffer */

assert(len < sizeof(app_buf));

 memcpy(app_buf, rx_buf, len);

 app_buf[len] = 0; /* End string by '\0' */

if ((len == 2) && (app_buf[0] == 0xd) && (app_buf[1] == 0xa))

 PRINTF("Get New Line From Master Side\r\n");

 else

PRINTF("Get Message From Master Side : \"%s\" [len : %d]\r\n", app_buf, len);

/* Get tx buffer from RPMsg */

tx_buf = rpmsg_lite_alloc_tx_buffer(my_rpmsg, &size, RL_BLOCK);

 assert(tx_buf);

if(strncmp("LED ON", app_buf, len) == 0)

 {

 GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 1U);

 /* Copy string to RPMsg tx buffer */

memcpy(tx_buf, "led on", len);

 }

else if(strncmp("LED OFF", app_buf, len) == 0)

 {

 GPIO_PinWrite(EXAMPLE_LED_GPIO, EXAMPLE_LED_GPIO_PIN, 0U);

 /* Copy string to RPMsg tx buffer */

memcpy(tx_buf, "led off", len);

 }

/* Echo back received message with nocopy send */

result = rpmsg_lite_send_nocopy(my_rpmsg, my_ept, remote_addr, \

 tx_buf, len);

 if (result != 0)

 {

assert(false);

 }

 /* Release held RPMsg rx buffer */

result = rpmsg_queue_nocopy_free(my_rpmsg, rx_buf);

 if (result != 0)

 {

 assert(false);

 }

 }

}

In this multi-core communication between A53 and M7, the main effects are as follows: A53 core sends "LED

ON” or “LED OFF” command, after M7 receives the command, it controls the LED lights to turn on and off respectively, and sends a message to A53 at the same time.

Send the string of "led on" or "led off".

First, in the main function, the initialization of the development board and the kernel is completed on lines 6 to 12, and then passed on line 19

FreeRTOS creates a task. After running into this task, first define some variables needed in the program, in 50

Line initialization stack will get an instance pointer, line 52 blocks and waits for whether M7 and A53 have been linked, when the link is successful, continue to run down, and then create the required queue and RPMSG endpoint in lines 55 and 56, which are used for data Send and receive.

After entering the for loop, block receiving data at line 64, judge whether it is an instruction to control the LED light at line 85 and line 92, and then perform the operation of turning on or off the light, and then reply the corresponding string at line 100.

To run the program, refer to the section "Starting the Cortex-M7 Coprocessor".

Cortex-A53  terminal program implementation

The main program of A53 terminal is as follows, only some important codes are provided here, if you want to view the detailed implementation of some functions, please

Open the project corresponding to the CD-ROM data [Data CD\Huaqing Vision-I.MX8M Plus Development Data\Program Source Code\CoertxM7\multicore-communication\multicore-A53].

Multicore-a53.c

int main(int argc, char *argv[])

{

 int i = 0, count = 0;

 char tem1[32] = {0};

 struct termios opt;

 char buf[512] = {0};

 int fd;

// strcpy(tem1, "/dev/ttyRPMSG30");

if((fd = open(argv[1], O_RDWR | O_NOCTTY )) < 0)

 {

printf("open is error.\n");

 return -1;

 }

 printf("open is ok!\n");

printf("uart init start.\n");

 tcgetattr(fd, &opt);

 cfsetispeed(&opt, B115200);

 cfsetospeed(&opt, B115200);

//tcflush(fd, TCIOFLUSH);

opt.c_cflag |= (CLOCAL | CREAD);

opt.c_cflag &= ~CSIZE;

 opt.c_cflag &= ~CRTSCTS;

 opt.c_cflag |= CS8;

opt.c_iflag |= IGNPAR;

 opt.c_cflag &= ~CSTOPB;

opt.c_oflag = 0;

 opt.c_lflag = 0;

tcsetattr(fd, TCSANOW, &opt);

 printf("uart init end.\n");

while(1)

 {

 count = write(fd, "LED ON", 6);

sleep(1);

 count = read(fd, buf, sizeof(buf));

 printf("read buf count: %d\n", count);

 printf("read buf : %s\n", buf);

memset(buf, 0, sizeof(buf));

 sleep(1);

count = write(fd, "LED OFF", 7);

count = read(fd, buf, sizeof(buf));

 printf("read buf count: %d\n", count);

 printf("read buf : %s\n", buf);

memset(buf, 0, sizeof(buf));

sleep(1);

 }

 return 0;

}

In the A53 program, the 3rd to 7th lines define the variables needed in the program, and the structure defined in the 5th line is used to provide a sound set of line settings, mainly to configure the serial port channel for the dual-core communication between M7 and A53. The channel we use is opened on line 11, and the configuration of the structure members is completed on lines 20 to 37.

In the while loop, call the write function to send the string of "LED ON" or "LED OFF", each

After sending each time, use read to receive the data returned by the M7 core, and perform cyclic operation.

For part A53, after the program is written, the next step is to compile it. Before compiling, you need to install the imxrobot-xwayland cross-compilation toolchain. For the installation process, please refer to the "Cross-compilation Toolchain Installation" section.

⚫ import SDK

linux@ubuntu: $ source /opt/fsl-imx-xwayland/5.4-zeus/environment-setup-aarch64-poky-li

Nux

Verify that the development tools are installed correctly, and the version information is displayed as shown in the figure below.

linux@ubuntu: $ $CC --version

 

⚫ compile and execute

Please make sure that the SDK has been imported before compiling, as we have already imported the SDK. Also need to add a Makefile

file, we provide the prepared Makefile under the source code path, which can be used directly, and the test executable program can be generated by executing make to compile.

linux@ubuntu: $ few

 

run the program

Import the test executable program generated in the previous step to the target board. The scp method is used here.

linux@ubuntu: $ scp test [email protected]:~

Here root is the user name of the target board, 192.168.100.100 is the IP address of the target board, and ~ is the home directory of the target board.

Program Execution Steps

After the program is written, open the serial port used to connect to the A53 core, boot to the u-boot state, and then open the serial port connected to the M7 core, and then start the Cortex-M7 coprocessor, as shown below:

 

In addition, for the A53 core, in the u-boot state, we need to set the environment variable

u-boot=> setenv fdt_file imx8mp-ai-robot-rpmsg.dtb

u-boot=> save

Saving Environment to MMC... Writing to MMC(1)... OK

Then you can execute boot to boot into the kernel

u-boot=> boot

Before entering the kernel, you need to enter the login password, the password is root

 

At this point, the M7 core program has judged that the connection is successful, and it will print another statement, as shown below:

 

After A53 enters the kernel, it first needs to load a driver file. This file is used to generate the dual-core communication channel between M7 and A53. At the same time, after executing the A53 core program, it will also send the "hello world!" string to M7.

root@imx-robot-kinetic:~# modprobe imx_rpmsg_tty

 

At this time, check the tty device in the dev directory, and you can see that there is an extra RPMSG serial port, which is used for

A53 communicates with M7 dual-core.

 

Send the executable file generated by the A53 core program to the development board and execute it under the development board terminal.

root@imx-robot-kinetic:~# ./test /dev/ttyRPMSG30

The running results are as follows

 

 

Guess you like

Origin blog.csdn.net/u014170843/article/details/130949587