i.MX Processors Knowledge Base

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

i.MX Processors Knowledge Base

Discussions

Sort by:
In the i.MX 8M Plus LPDDR4 EVK board there are two Type-C port design. For the port0 is used to power supply no usb function, for the port1 used for USB function but without PD function. But in customer’s design, customer only use one USB design on their board, how to make the one USB work with the PD and USB function, we need to make the hardware design and software modify. This article only give method to realized it and have tested and realized the port1 PD function. 1 Introduction of the USB interface on i.MX8MP         There are two USB 3.0 TypeC controllers with integrated PHY interface on the i.MX8MP: Backward compatibility with USB 2.0 Spread spectrum clock support   The USB on the i.MX8MP supports USB3.0 and is compatible with USB2.0 downward. We can see that the upper layer is the universal layer for USB 2.0 and USB 3.0 operations. This is a common interface, buffer management block, list processor, used to schedule and control the status register (CSR) function: USB 2.0 physical layer and MAC layer USB 3.0 physical layer, link layer and MAC layer   Features of USB 3.0: USB compliant version 3.0 (xHCI compatible) Supports operation as a stand-alone USB host controller USB dual role operation, configurable as host or device Ultra high speed (5Gbit/s), high speed (480Mbit/s), full speed (12Mbit/s) and low speed (1.5Mbit/s) operation. Support independent single port USB operation Support for four programmable bidirectional USB endpoints Support system memory interface with 40 bit addressing capability   2 Design of USB on Development Board         The i.MX 8M Plus processor includes two USB 2.0/3.0 controllers and two integrated USB PHYs. USB supports both running as an independent USB host controller and dual role USB operation, and can be configured as a host or device. Therefore, the design of these two functions is implemented on the development board of i.MX8MP.   We can see that on the development board, one USB1 is used for the USB Type-C port and the other USB2 is used for the USB 3.0 host port. USB Type-C port 0 (J5) is only used for power supply. It does not support USB data transfer. It is the only power port, so the system must always be powered.   On the CPU side of the schematic diagram, we can also see that USB1 is the port for USB Type-C, and USB2 is the host for USB3.0.    USB1 is designed as USB Type-C:   USB2 USB3.0 Host design:   Power design of the USB Type-C port:   3 Only one USB interface is used in the design (compatible with both USB PD function and USB dual roles function)         Two USB Type-C ports are used on our development board. One is used to power the board separately, and the other is used as the function of USB Type-C. However, due to the limited design cost and chip layout and space on the board, some customers will use a USB interface to realize the dual role function of power supply and USB. How to achieve this? USB Device(Download mode):     USB Host mode(power+device Need the hub support PD function):     The specific implementation and design are as follows: 3.1 Hardware realize PTN5110 To realize the USB Type-C support power supply function, PTN5110 (USB PD TCPC PHY IC) chip is required to realize Type-C data logic and power control and management. The selection of PTN5110 is critical and important.   PTN5110 is a single port USB PD (power supply) PHY IC that conforms to TCPC. It integrates Type-C configuration channel (CC) interface and USB PD physical layer functions into Type-C port manager (TCPM) that handles PD policy management. It complies with USB PD, Type-C and TCPC specifications.   The IC is mainly aimed at applications in system platforms (such as laptops, desktops, Chromebooks, tablets, flip notebooks, etc.). Other application cases may be feasible, depending on the application architecture, such as docking stations, displays, accessories, cable adapters, smartphones, etc.   It can support various Type-C applications: Sink, Source, Sink with accessory support or DRP. It executes Type-C CC simulation part (i.e. Rd/Rp/Ra detection, Rd/Rp indication) and PD Tx/Rx PHY and protocol state machine. PTN5110 supports TCPM in the system implementation of the following PD roles.   PTN5110 integrates VCONN load switch, programmable current limit, reverse leakage current blocking and over temperature protection (OTP). It is equipped with two enable control outputs to control the load switch/FET in the VBUS pull and/or sink path. It can also perform VBUS voltage monitoring/measurement, VBUS forced discharge and discharge discharge.   PTN5110 provides the main IO related functions for the main processor/TCPM, so that Type-C/PD interfaces can be easily controlled and managed through the TCPC interface.   PTN5110 supports a wide range of power input voltages, providing platform integrators with great flexibility. PTN5110 can run on VBUS to support specific system use cases that require no power operation.https://www.nxp.com/products/interfaces/usb-interfaces/usb-type-c/usb-pd-phy-and-cc-logic/usb-pd-tcpc-phy-ic:PTN5110   The design only use the USB1:   Here, it is required to weld R53 or R54. You can refer to this design completely. 2 Software modify Modify the BPS of the software: Take the newest released Linux 5.15.32_2.0.0​ as example: In the u-boot /board/freescale/imx8mp_evk/imx8mp_evk.c     It can be seen that the PD function of the port is turned off, so if you want to use USB1 for power supply, remove the following commands and turn on the PD function of USB1. “-   .disable_pd = true,” Use the above action to enable Port1 PD function. Kernel section modify: Kernel section modify towards to PTN5110. Type-C Configure channel (CC) interface: root/drivers/usb/typec/tcpm/tcpci.c @@ -524,6 +524,7 @@ static int tcpci_vbus_force_discharge(struct tcpc_dev *tcpc, bool enable)  static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)  {        struct tcpci *tcpci = tcpc_to_tcpci(tcpc); +      unsigned int reg;        int ret;          if (tcpci->data->set_vbus) { @@ -533,16 +534,20 @@ static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)                         return ret < 0 ? ret : 0;        }   +      ret = regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &reg); +      if (ret < 0) +              return ret; +        /* Disable both source and sink first before enabling anything */   -       if (!source) { +      if (!source && (reg & TCPC_POWER_STATUS_SOURCING_VBUS)) {                 ret = regmap_write(tcpci->regmap, TCPC_COMMAND,                                     TCPC_CMD_DISABLE_SRC_VBUS);                 if (ret < 0)                         return ret;        }   -       if (!sink) { +      if (!sink && (reg & TCPC_POWER_STATUS_SINKING_VBUS)) {                 ret = regmap_write(tcpci->regmap, TCPC_COMMAND,                                     TCPC_CMD_DISABLE_SINK_VBUS);                 if (ret < 0)   Type-C port manager managed by PD (TCPM): root/drivers/usb/typec/tcpm /tcpm.c @@ -340,6 +340,7 @@ struct tcpm_port {         */        bool vbus_vsafe0v;   +      bool vbus_keep;        bool vbus_never_low;        bool vbus_source;        bool vbus_charge; @@ -3662,7 +3663,8 @@ static void tcpm_reset_port(struct tcpm_port *port)        port->rx_msgid = -1;          port->tcpc->set_pd_rx(port->tcpc, false); -       tcpm_init_vbus(port);     /* also disables charging */ +      if (!port->vbus_keep) +              tcpm_init_vbus(port);  /* also disables charging */        tcpm_init_vconn(port);        tcpm_set_current_limit(port, 0, 0);        tcpm_set_polarity(port, TYPEC_POLARITY_CC1); @@ -5834,6 +5836,9 @@ static void tcpm_init(struct tcpm_port *port)          port->tcpc->init(port->tcpc);   +      port->vbus_present = port->tcpc->get_vbus(port->tcpc); +      if (port->vbus_present) +              port->vbus_keep = true;        tcpm_reset_port(port);          /* @@ -5872,7 +5877,10 @@ static void tcpm_init(struct tcpm_port *port)         * Some adapters need a clean slate at startup, and won't recover         * otherwise. So do not try to be fancy and force a clean disconnect.         */ -       tcpm_set_state(port, PORT_RESET, 0); +      if (!port->vbus_keep) +              tcpm_set_state(port, PORT_RESET, 0); + +      port->vbus_keep = false;  }    static int tcpm_port_type_set(struct typec_port *p, enum typec_port_type type) Note: The software just needs to modify these two parts. You also need to mention to the proper the I2C port use, if not proper the driver of the PTN5110 can not driver. 4 Test         In our i. MX8MP EVK development board show that R53 and R54 in the USB1 part of our development board are in DNP status, so VBUS_ IN is disconnected and no power comes in. Here, connect R53 or R54 with solder, so that VBUS_ IN, the power comes in again. After the power is connected. The board can be powered through USB1. 4.1 Download images to the emmc on the Board: Power from the USB1, set the boot mode to serial download mode, then go to download images finished. 4.2 Boot up the board from the EMMC Change the boot mode to boot up from EMMC,the board boot up, the log file is as following show:   It will stop at the TCPC for the section of PTN5110 driver. By default, the PD function of port1 in the u-boot is turned off, so if you want to use USB1 for power supply, remove the following commands and turn on the PD function of USB1. “-   .disable_pd = true,” After the PD function is turned on, the board can be started normally, but the whole part running to the kernel will be powered down, so the kernel part of PTN5110 still needs to be modified. After the patch modification of the above kernel part, the board can run normally.         I also did the same experiment on the i.MX8MM EVK development board. The same phenomenon occurs when the kernel starts. Therefore, similar modifications to the above i. MX8MP can work normally. Summary: In one word i.MX8MP and i.MX8M series can realize the role of using a USB for power supply and USB Dual. The hardware design refers to our development board, and we must use the logic chip PTN5110. For software, refer to the above code modification.  
View full article
Tips collected from zhaoyang-b49593 and dandouglass-b41520 while enabling redundant boot: Using i.MX 8MQ, same method can be applied for other i.MX devices that support redundant boot, see SoC Reference Manual. As described on the RM, if primary image authentication fails the ROM can reset and try booting a secondary image. This feature is only available on closed mode with properly signed binaries, otherwise the ROM boots the primary image despite the auth failure. For the i.MX 8MQ, the secondary image must start with spl, not HDMI firmware. Note, there is no ROM redundancy for the hdmi fw, if it is corrupt user can store a 2nd copy on a different memory address and update at run time. Steps to generate a dual spl image: 1. Build and Sign bootable binary (spl, u-boot, atf, fw, etc) Use the Yocto BSP or follow this post to build outside the Yocto environment. To sign the binary, follow the documentation on u-boot source: <u-boot>/doc/imx/habv4/guides/mx8m_secure_boot.txt Program image to the SD card: dd if=signed_flash.bin of=<sd path> bs=1024 seek=33 After boot you can use "hab_status" to verify that no events were generated: u-boot=> hab_status Secure boot disabled HAB Configuration: 0xf0, HAB State: 0x66 2. Corrupt spl on your boot image You can corrupt anywhere on the spl signed area. For easier visualization at boot time we can corrupt the SPL banner. First create a copy: cp signed_flash.bin signed_flash_corrupt.bin Find the banner: hexdump -C signed_flash.bin | grep 2019 00020190 26 1c 40 92 04 00 80 d2 05 01 80 52 c4 20 04 aa |&.@........R. ..| 0002eac0 32 30 31 39 2e 30 34 2d 30 30 30 32 39 2d 67 34 |2019.04-00029-g4| 000dde10 3a 20 20 00 55 2d 42 6f 6f 74 20 32 30 31 39 2e |: .U-Boot 2019.| 0002eac3 is on spl area, where "9" for 2019 is, replace by "X" printf "X" > X dd if=X of=signed_flash_corrupt.bin seek=$((0x2eac3)) bs=1 conv=notrunc Verify corrupt binary hexdump -C -s 0x2eac0 -n 64 signed_flash_corrupt.bin 0002eac0 32 30 31 58 2e 30 34 2d 30 30 30 32 39 2d 67 34 |201X.04-00029-g4| 0002ead0 37 63 31 39 32 32 20 28 41 70 72 20 32 37 20 32 |7c1922 (Apr 27 2| Transfer image to SD Card dd if=signed_flash_corrupt.bin of=<sd path> bs=1024 seek=33 Now, you should see hab events after running "hab_status" on u-boot 3. Create a secondary boot image This can be the same content as your primary image without the HDMI fw or it can be a different spl image. For easier visualization, we can change the SPL banner, on the code this time. Modify banner at ./common/spl/spl.c as: - puts("\nU-Boot " SPL_TPL_NAME " " PLAIN_VERSION " (" U_BOOT_DATE " - " + puts("\nSecondary U-Boot " SPL_TPL_NAME " " PLAIN_VERSION " (" U_BOOT_DATE " - " As mentioned above, we want our boot image without the HDMI fw, when running imx-mkimage use the flash_evk_no_hdmi target: make SOC=iMX8MQ flash_evk_no_hdmi Sign the image as in step 1. If you program the new image to the SD you should see the new banner. Make sure to run hab_status to confirm that no HAB events are generated. 4. Program SRK Hash and Close SoC Follow the documentation on u-boot source for SRK programming and closing the device: <u-boot>/doc/imx/habv4/guides/mx8m_secure_boot.txt Before closing the SoC, but after the SRK is programmed, try your images to confirm no HAB events are generated. Be careful with this step, errors could brick your board. This step is irreversible. After closing the SoC it will only boot signed images. 5. Create dual bootloader image We can concatenate our binaries to create a single file, let's use 2MB distance between primary and secondary images: For the working primary image: objcopy -I binary -O binary --pad-to 0x200000 --gap-fill=0x00 signed_flash.bin 1st-spl_pad.bin cat 1st-spl_pad.bin secondary2_nohdmifw_signed_flash.bin > 1st-spl_pad_2nd-spl.bin Or for the corrupt primary image experiment: objcopy -I binary -O binary --pad-to 0x200000 --gap-fill=0x00 signed_flash_corrupt.bin 1st-spl_pad.bin cat 1st-spl_pad.bin secondary2_nohdmifw_signed_flash.bin > 1st-spl_pad_2nd-spl.bin Program it to the SD card on 0x8400 offset (33k) dd if=1st-spl_pad_2nd-spl.bin of=<sd path> bs=1024 seek=33 && sync 6. Add Secondary image table Follow the format on the RM, this is only 20 bytes long. For a 2MB distance between the table and the secondary image we can use "0x1000" on the firstSectorNumber field. 2MB/512 = 4096 (0x1000) The perl script attached, genSecTable.pl, can be used to generate it. perl genSecTable.pl 0x1000 Program it to the SD card on 0x8200 offset dd if=secTable.bin of=<sd path> bs=1 seek=$((0x8200)) && sync 7. Verify secondary image is booting If using the corrupt primary image, you should see the spl with the "Secondary U-Boot SPL..." banner. You can also read the persist secondary boot bit. u-boot=> md.l 0x30390098 1 30390098: 40000000 ...@ The work can be extended patching spl for in case of u-boot authentication failure, spl can try to authenticate and jump to the secondary u-boot.
View full article
Hello everyone, In this document I'll explain on how to build the UUU (Universal Update Utility) using windows 10 PC. This may be useful in case of adding custom commands to run during the flash using built-in scripts, be it for debugging, fuse blowing, etc. First we need to download and install Visual Studio community, for this guide I'll use community 2019, version it is available here: https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=16 For workloads select Universal Windows Platform development.   When installing, make sure to select and install the Git for windows complement, at the top select Individual components, this will display a new list, scroll down to code tools and you will find Git for windows, check this box In case Visual Studio is already installed, you may open the installer again and chose modify, this will let you install the complement as well. After the installation is complete we may run the git commands on the power shell. Now open the windows power shell and type the following commands: git clone https://github.com/NXPmicro/mfgtools.git // clones the MFGTool (UUU) source code from the github cd mfgtools // enters the mfgtools folder we just cloned git submodule init // creates the local configuration file for the submodules git submodule update // set the submodules to the commit specified by the main repository. At this point we can edit the built in scripts to add our custom commands, for this guide I'll add the printenv uboot command at the end of the flashing process. For this I'll enter the folder mfgtools/uuu, and edit emmc_burn_all.lst with any text editor, i.e. Notepad ++, add the command FB: ucmd printenv.   Save and close the editor, it is possible to add most uboot commands like for example the fuse commands to burn eFuses. Then we can now build the tool, opening msvc/uuu-static-link.sln with Visual studio, select solution uuu-static-link.sln   And finally build the solution: The executable (uuu.exe) would be at the following path: mfgtools\msvc\x64\Debug   Finally we run the built in script we modified and check the results. Find attached both the powershell and uboot logs, I tested this using an i.MX8MN with L5.4.47_2.2.0, running the following command: ./uuu.exe -v -b emmc_all imx-boot-imx8mnevk-sd.bin-flash_evk imx-image-full-imx8mnevk.wic Hope this may found useful for anyone trying to achieve something similar.
View full article
Basic Linear Algebra Subprograms (BLAS) is a specification that prescribes a set of low-level routines for performing common linear algebra operations such as vector addition, scalar multiplication, dot products, linear combinations, and matrix multiplication. OpenBLAS is an optimized BLAS library which is uesd for deep learning accelerator in Caffe/Caffe2. I enable it in Yocto (Rocko) by adding bb file. And I build on i.MX6QP, i.MX7ULP and i.MX8MQ and also run its test example successfully. You can find test example(openblas_utest) under folder image/opt/openblas/bin of OpenBLAS work directory. Currently, version 0.3.0 is supported in the bb file. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ update to v 0.3.6 and enable mutli-thread by set USE_OPENMP=1 and USE_THREAD=4 when compiling this library.
View full article
  Introduction   Prior to 6.1.22_2.0.0 BSP release, Bluetooth interface are based on the tty line discipline framework, so we need to use hciattach tool to enable it in the user space. From 6.1.22_2.0.0 BSP, the nxp bluetooth driver no longer needs the help of the userspace hciattach tool, and the tty port bound by bluetooth also won't be exported to the user space, so you cannot find the corresponding tty device anymore. So, you won't see the (/dev/ttymxcX), for the Bluetooth interface. All jobs has been done in the new NXP Bluetooth driver. New Method   The new NXP Bluetooth UART Driver is based on a server driver for the NXP BT serial protocol, which can enable the built-in Bluetooth device inside an NXP BT chip. This driver has a Power Save feature that will put the chip into a sleep state whenever there is no activity for 2000ms and will be woken up when any activity is to be initiated over UART.  Device Tree support The new BT framework requires adding a "bluetooth" sub node with a device compatibility string to the attached UART node in the dts file &uart1 { bluetooth { compatibility = "nxp,88w8987-bt"; fw-init-baudrate = <3000000>; #Optional. Default is considered 115200 if this parameter not defined. }; };   Note: The parameter ‘compatibility = “nxp,88w8987-bt”’ will use for 88W8987, IW416, 88Q9098, IW612 chipsets and need to change for 88W8997 with parameter ‘compatibility = “nxp,88w8997-bt”’.   Note: ’fw-init-baudrate’ parameter depends on the module vendor. The Murata and Azuere wifi modules support in BSP release uses the default value -- 115200. We strongly recommend looking at the module vendor-specific baud rate parameter. Note: For the old 88Q9098 Murata 1XL module that uses the 3Mbps by default, please add the fw-init-baudrate = <3000000> property in dts files to make it work. Enable Guide   Use wifi interface to load combo (wifi & bt) firmware and enable BT Need to load wifi driver first, then load the BT driver, otherwise, BT driver suspend/resume test will fail. This is a HW limitation, since NXP wifi and BT module use the same power control pin(W_DISABLE1#), if we don't load the wifi driver, SDIO bus will power down the wifi chip during suspend resume, which may cause the BT chip also been powered down and cannot work after resume back. So we need to load the wifi driver to make sure SDIO bus won't power down the BT chip to make sure BT functions can work during suspend resume. modprobe moal mod_para=nxp/wifi_mod_para.conf modprobe btnxpuart or insmod mlan.ko insmod moal.ko mod_para=nxp/wifi_mod_para.conf insmod btnxpuart   Unload UART Driver modprobe moal Make sure run hciconfig hci0 up or hciconfig hci0 reset or bluetootctl power on before unload btnxpuart driver. If we don't open hci0 interface, the driver cannot send change to 115200 baud rate command to BT chip, which causes the host and BT chip baud rate mismatch, the host still uses 115200bps talk to the BT chip which now use 3Mbps, it cannot work anymore. So we need to make sure open the hci0 interface before unload btnxpuart driver.   mod_para=nxp/wifi_mod_para.conf modprobe btnxpuart sleep 3 hciconfig hci0 up #Note: Need to up hci interface before unload the BT module hcitool -i hci0 cmd 3F 23 02 00 00 modprobe -r btnxpuart modprobe -r moal sleep 3​ For better reference: Please find the I.MX 8MQ Linux getting started user guide, UM11483, Chapter "7.1 Bring-up using NXP Bluetooth UART driver"  Bluetooth Deep Sleep Feature App Note AN13920, Chapter 6 Load NXP UART driver module NOTE: Please do not run the power save feature for Murata IW612 2EL Module Regards, Mario
View full article
Purpose This is early communication to notify i.MX 8M Dual/8M QuadLite/8M Quad customers of a potential incorrect PCIe power supply configuration on certain NXP BSP Linux and Android versions. Description The PCIE_VPH power supply is selectable in software  between 1.8V and 3.3V. When the PCIE_VPH supply is configured to operate at 3.3V, the 1.8V internal regulator (disabled by default) must be enabled to prevent overstress conditions on the PCIe PHY. If the 1.8V internal regulator is left disabled when the PCIE_VPH supply is configured to operate at 3.3V, it could potentially impact the product lifetime of the device.   Impact •i.MX 8M Dual/8M QuadLite/8M Quad (other i.MX processors are not impacted) •Only Impacts Linux/Android kernel versions earlier than L5.4.70_2.3.2 or Linux 5.10.9_1.0.0 releases MITIGATION •When the PCIE_VPH supply is configured to operate at 3.3V users need to enable the internal regulator by setting the IOMUXC_GPR_GPR14 and IOMUXC_GPR_GPR16 registers - PCIE1_VREG_BYPASS and PCIE2_VREG_BYPASS bit to 0. •There are 3 software patches for each release. Software patch details in the Code Aurora Forum (CAF): •For L5.4.70_2.3.2 patch release, the git log references are: •MLK-25349-3 PCI: imx: clear vreg bypass when pcie vph voltage is 3v3 •MLK-25349-2 arm64: dts: imx8mq-evk: add one regulator used to power up pcie phy •MLK-25349-1 dt-bindings: imx6q-pcie: add one regulator used to power up pcie phy • •The L5.4.70_2.3.2, LF_5.10 Q2 and later BSP releases correctly configure and enable the internal regulator by setting the IOMUXC_GPR_GPR14 and IOMUXC_GPR_GPR16 registers The Patch MLK-25349 which correctly enables the internal regulator is already included in the L5.4.70_2.3.2 patch release and release versions after it. MITIGATION •The following branches of Linux/Android BSP releases contain the MLK-25349 patch. The patch is attached below for each respective release.   •Other branches which are not listed should try to apply the nearest Patch version patch. If a user encounters any conflicts in applying, they should back porting from below nearest patch release version below. imx_4.9.51_ga, imx_4.9.y_android_imx8m_ga_v2                           - Patch attached  imx_4.9.88_ga, imx_4.9.y_android_2.0.0_ga                                   - Patch attached  imx_4.14.y and imx_4.14.98_2.3.0, imx_4.14.98_2.3.0_android     - Patch attached  imx_4.19.y and imx_4.19.35_1.1.0, imx_4.19.35_1.1.0_android     - Patch attached  imx_5.4.y, imx_5.4.3_2.0.0, imx_5.4.3_2.0.0_android                     - Patch attached Documentation Change Description – 1 of 3 for Datasheet Updated Datasheets and Reference Manual will be published to nxp.com. Updated Hardware Design guide and Schematics have already been published on nxp.com.  Updated the descriptions of PCIE_VPH in the Datasheet Table 8, "Operating ranges"     Documentation Change Description – 2 of 3 for Reference Manual (RM) Updated the description of field 12 "PCIE1_VREG_BYPASS" in 8.2.4.15 GPR14 General Purpose Register (IOMUXC_GPR_GPR14)           Documentation Change Description – 3 of 3 for RM Updated the description of field 12 "PCIE2_VREG_BYPASS" in 8.2.4.17 GPR16 General Purpose Register (IOMUXC_GPR_GPR16)   REFERENCES •i.MX 8M Dual / 8M QuadLite / 8M Quad Product Lifetime Usage  •i.MX 8M Dual / 8M QuadLite / 8M Quad Applications Processors Data Sheet for Industrial Products •i.MX 8M Dual / 8M QuadLite / 8M Quad Applications Processors Data Sheet for Consumer Products •i.MX 8MDQLQ Hardware Developer’s Guide  •i.MX 8M Dual/8M QuadLite/8M Quad Applications Processors Reference Manual  
View full article
Before reading: only a personal works and sharing, not any form of "release". I didn't find any confidential information from the packages. So, I'm publishing it here. This is only for testing purpose. Do NOT use it for building a product. Use it at your own risk!! Yocto is flexible and powerful, and also, big and slow (when building). Sometimes we only need to build uboot or kernel or some piece of testing code. It's really a waste of time to build-up the whole Yocto environment which may cost over 50GB disk space and over 3 hours of building. I've made some scripts and sum them up to form a toolset for building uboot, kernel and some testing code out of Yocto environment. It's only a simple container and expect to use with uboot and kernel source code from formal Freescale release and a SDK built from Yocto project. GitHub source repo:       https://github.com/gopise/gopbuild What’s made off (a full package, not only the container): 1.    Some scripts and configurations files. 2.    SDK built from Yocto. 3.    Uboot/kernel from specific version. 4.    A hello-world to demonstrate how to build app in this environment. 5.    A slimmed rootfs binary from specific BSP pre-built as base. Will customize base on the source under “rootfs” folder. Only a placeholder in the container-only version. How to use it: Several common used board configurations have been included in the script: 6qsabresd/6qsabreai/6qpsabreai. You can add more into the “gopbuild” script easily. The “sabresd” has been set as default.      If you want to build all for sabresd (First of all, de-compress the package): cd <de-compressed-folder> source envsetup [It will prompt for selecting board configuration to be built. Choose one by input corresponding number or click <ENTER> for default board.] gmk ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍      If you want to build specific module for default board, such as uboot: gmk uboot ‍‍‍‍‍‍‍‍‍      Build kernel for sabreai board instead of default device: gmk kernel sabreai ‍‍‍‍‍‍‍‍‍      Clean everything? gmk all clean ‍‍‍‍‍‍‍‍‍ After a successfully full build, you will get everything under “output” folder, including a log folder contains full build log:      “u-boot.imx/zImage/rootfs.tar.bz2/*.dtb”, can be used with MFG or uuu.      “fsl-image.sdcard”, can be burn into SD card directly. "Ready-for-building" Package: The "gopbuild" itself is a "container-only" package which doesn't contain any source or SDK. I've also made some packages based on latest BSP release for i.MX6/i.MX7/i.MX8. These packages are "ready-for-build" package which you can de-compress and build it directly. -------------------------------------------------------------------------------------------------- URL:https://pan.baidu.com/s/1Xlh1OBGsTRXez_NQw-Rjxg Password: gdc9 -------------------------------------------------------------------------------------------------- Note: 1. To build for i.MX8 (8QM/8MQ/8QXP), you need L4.14.* or above. 2. To build for i.MX8, please download the SCFW from i.MX software page       i.MX Software and Development Tools | NXP      After download, decompress corresponding package for specific chip and put it under "/platform/scfw/". Take i.MX8QXP for example:             /platform/scfw/scfw_export_mx8qx/ All material (uboot/kernel/test code and SDK) are from official Yocto release. Thanks!
View full article
All below changes are done based on imx_android-10.0_5.4.y. mek_8qm enable uSD 8987 wifi            1. hardware rework                no hardware rework required            2. patches The patch is attached as 0001-8qm-usd-8987-wifi.patch mek_8qm enable M.2 8987 wifi            1. hardware rework                no hardware rework required            2. patches The patch is attached as 0001-8qm-m2-8987-wifi.patch evk_8mq enable uSD 8987 wifi            1. hardware rework                no hardware rework required            2. patches diff --git a/imx8m/evk_8mq/BoardConfig.mk b/imx8m/evk_8mq/BoardConfig.mk index db7c4991..4d130cc0 100644 --- a/imx8m/evk_8mq/BoardConfig.mk +++ b/imx8m/evk_8mq/BoardConfig.mk @@ -136,7 +136,8 @@ ifeq ($(TARGET_USE_DYNAMIC_PARTITIONS),true) TARGET_BOARD_DTS_CONFIG ?= imx8mq:imx8mq-evk-no-product.dtb else # imx8mq with HDMI display - TARGET_BOARD_DTS_CONFIG ?= imx8mq:imx8mq-evk-pcie1-m2.dtb + TARGET_BOARD_DTS_CONFIG ?= imx8mq:imx8mq-evk-usd-wifi.dtb + # imx8mq with MIPI-HDMI display TARGET_BOARD_DTS_CONFIG += imx8mq-mipi:imx8mq-evk-lcdif-adv7535.dtb # imx8mq with HDMI and MIPI-HDMI display diff --git a/imx8m/evk_8mq/SharedBoardConfig.mk b/imx8m/evk_8mq/SharedBoardConfig.mk index 330ab1c5..a6654bad 100644 --- a/imx8m/evk_8mq/SharedBoardConfig.mk +++ b/imx8m/evk_8mq/SharedBoardConfig.mk @@ -7,10 +7,10 @@ PRODUCT_IMX_TRUSTY := true #Enable this to disable product partition build. #IMX_NO_PRODUCT_PARTITION := true -#NXP 8997 wifi driver module +# NXP 8987 wifi driver module BOARD_VENDOR_KERNEL_MODULES += \ - $(KERNEL_OUT)/drivers/net/wireless/marvell/mrvl8997/wlan_src/mlan.ko \ - $(KERNEL_OUT)/drivers/net/wireless/marvell/mrvl8997/wlan_src/pcie8xxx.ko + $(KERNEL_OUT)/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan.ko \ + $(KERNEL_OUT)/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/moal.ko # mipi-panel touch driver module BOARD_VENDOR_KERNEL_MODULES += \ diff --git a/imx8m/evk_8mq/UbootKernelBoardConfig.mk b/imx8m/evk_8mq/UbootKernelBoardConfig.mk index 5aa1ce35..4c3378f0 100644 --- a/imx8m/evk_8mq/UbootKernelBoardConfig.mk +++ b/imx8m/evk_8mq/UbootKernelBoardConfig.mk @@ -14,7 +14,7 @@ endif TARGET_BOOTLOADER_CONFIG += imx8mq-evk-uuu:imx8mq_evk_android_uuu_defconfig TARGET_KERNEL_DEFCONFIG := imx_v8_android_defconfig -# TARGET_KERNEL_ADDITION_DEFCONF ?= android_addition_defconfig +TARGET_KERNEL_ADDITION_DEFCONF ?= android_addition_defconfig diff --git a/imx8m/evk_8mq/android_addition_defconfig b/imx8m/evk_8mq/android_addition_defconfig new file mode 100644 index 00000000..f51bd5ff --- /dev/null +++ b/imx8m/evk_8mq/android_addition_defconfig @@ -0,0 +1,2 @@ +CONFIG_WLAN_VENDOR_NXP=y +CONFIG_MXMWIFIEX=m diff --git a/imx8m/evk_8mq/early.init.cfg b/imx8m/evk_8mq/early.init.cfg index 9262d953..70097a1c 100644 --- a/imx8m/evk_8mq/early.init.cfg +++ b/imx8m/evk_8mq/early.init.cfg @@ -1,3 +1,3 @@ insmod vendor/lib/modules/mlan.ko -insmod vendor/lib/modules/pcie8xxx.ko sta_name=wlan uap_name=wlan wfd_name=p2p max_vir_bss=1 cfg80211_wext=0xf cal_data_cfg=none p2p_enh=1 fw_name=pcieuart8997_combo_v4.bin +insmod vendor/lib/modules/moal.ko sta_name=wlan uap_name=wlan wfd_name=p2p max_vir_bss=1 cfg80211_wext=0xf cal_data_cfg=none fw_name=sdiouart8987_combo_v0.bin insmod vendor/lib/modules/synaptics_dsx_i2c.ko diff --git a/imx8m/evk_8mq/evk_8mq.mk b/imx8m/evk_8mq/evk_8mq.mk index 7db1b212..210f8971 100644 --- a/imx8m/evk_8mq/evk_8mq.mk +++ b/imx8m/evk_8mq/evk_8mq.mk @@ -250,9 +250,9 @@ PRODUCT_PACKAGES += \ PRODUCT_PACKAGES += \ bt_vendor.conf -# NXP 8997 Wifi and Bluetooth Combo Firmware +# NXP 8987 Wifi and Bluetooth Combo Firmware PRODUCT_COPY_FILES += \ - vendor/nxp/imx-firmware/nxp/FwImage_8997/pcieuart8997_combo_v4.bin:vendor/firmware/pcieuart8997_combo_v4.bin + vendor/nxp/imx-firmware/nxp/FwImage_8987/sdiouart8987_combo_v0.bin:vendor/firmware/sdiouart8987_combo_v0.bin # Wifi regulatory PRODUCT_COPY_FILES += \ The patch is attached as 0001-8mq-usd-8987-wifi.patch evk_8mq enable M.2 8987 wifi            1. hardware rework                hardware rework required ( Be aware: after this rework, uSD is not working!)            2. patches diff --git a/imx8m/evk_8mq/BoardConfig.mk b/imx8m/evk_8mq/BoardConfig.mk index db7c4991..0cca9b8e 100644 --- a/imx8m/evk_8mq/BoardConfig.mk +++ b/imx8m/evk_8mq/BoardConfig.mk @@ -136,7 +136,8 @@ ifeq ($(TARGET_USE_DYNAMIC_PARTITIONS),true) TARGET_BOARD_DTS_CONFIG ?= imx8mq:imx8mq-evk-no-product.dtb else # imx8mq with HDMI display - TARGET_BOARD_DTS_CONFIG ?= imx8mq:imx8mq-evk-pcie1-m2.dtb + TARGET_BOARD_DTS_CONFIG ?= imx8mq:imx8mq-evk-usdhc2-m2.dtb + # imx8mq with MIPI-HDMI display TARGET_BOARD_DTS_CONFIG += imx8mq-mipi:imx8mq-evk-lcdif-adv7535.dtb # imx8mq with HDMI and MIPI-HDMI display diff --git a/imx8m/evk_8mq/SharedBoardConfig.mk b/imx8m/evk_8mq/SharedBoardConfig.mk index 330ab1c5..a6654bad 100644 --- a/imx8m/evk_8mq/SharedBoardConfig.mk +++ b/imx8m/evk_8mq/SharedBoardConfig.mk @@ -7,10 +7,10 @@ PRODUCT_IMX_TRUSTY := true #Enable this to disable product partition build. #IMX_NO_PRODUCT_PARTITION := true -#NXP 8997 wifi driver module +# NXP 8987 wifi driver module BOARD_VENDOR_KERNEL_MODULES += \ - $(KERNEL_OUT)/drivers/net/wireless/marvell/mrvl8997/wlan_src/mlan.ko \ - $(KERNEL_OUT)/drivers/net/wireless/marvell/mrvl8997/wlan_src/pcie8xxx.ko + $(KERNEL_OUT)/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/mlan.ko \ + $(KERNEL_OUT)/drivers/net/wireless/nxp/mxm_wifiex/wlan_src/moal.ko # mipi-panel touch driver module BOARD_VENDOR_KERNEL_MODULES += \ diff --git a/imx8m/evk_8mq/UbootKernelBoardConfig.mk b/imx8m/evk_8mq/UbootKernelBoardConfig.mk index 5aa1ce35..4c3378f0 100644 --- a/imx8m/evk_8mq/UbootKernelBoardConfig.mk +++ b/imx8m/evk_8mq/UbootKernelBoardConfig.mk @@ -14,7 +14,7 @@ endif TARGET_BOOTLOADER_CONFIG += imx8mq-evk-uuu:imx8mq_evk_android_uuu_defconfig TARGET_KERNEL_DEFCONFIG := imx_v8_android_defconfig -# TARGET_KERNEL_ADDITION_DEFCONF ?= android_addition_defconfig +TARGET_KERNEL_ADDITION_DEFCONF ?= android_addition_defconfig # absolute path is used, not the same as relative path used in AOSP make diff --git a/imx8m/evk_8mq/android_addition_defconfig b/imx8m/evk_8mq/android_addition_defconfig new file mode 100644 index 00000000..f51bd5ff --- /dev/null +++ b/imx8m/evk_8mq/android_addition_defconfig @@ -0,0 +1,2 @@ +CONFIG_WLAN_VENDOR_NXP=y +CONFIG_MXMWIFIEX=m diff --git a/imx8m/evk_8mq/early.init.cfg b/imx8m/evk_8mq/early.init.cfg index 9262d953..70097a1c 100644 --- a/imx8m/evk_8mq/early.init.cfg +++ b/imx8m/evk_8mq/early.init.cfg @@ -1,3 +1,3 @@ insmod vendor/lib/modules/mlan.ko -insmod vendor/lib/modules/pcie8xxx.ko sta_name=wlan uap_name=wlan wfd_name=p2p max_vir_bss=1 cfg80211_wext=0xf cal_data_cfg=none p2p_enh=1 fw_name=pcieuart8997_combo_v4.bin +insmod vendor/lib/modules/moal.ko sta_name=wlan uap_name=wlan wfd_name=p2p max_vir_bss=1 cfg80211_wext=0xf cal_data_cfg=none fw_name=sdiouart8987_combo_v0.bin insmod vendor/lib/modules/synaptics_dsx_i2c.ko diff --git a/imx8m/evk_8mq/evk_8mq.mk b/imx8m/evk_8mq/evk_8mq.mk index 7db1b212..210f8971 100644 --- a/imx8m/evk_8mq/evk_8mq.mk +++ b/imx8m/evk_8mq/evk_8mq.mk @@ -250,9 +250,9 @@ PRODUCT_PACKAGES += \ PRODUCT_PACKAGES += \ bt_vendor.conf -# NXP 8997 Wifi and Bluetooth Combo Firmware +# NXP 8987 Wifi and Bluetooth Combo Firmware PRODUCT_COPY_FILES += \ - vendor/nxp/imx-firmware/nxp/FwImage_8997/pcieuart8997_combo_v4.bin:vendor/firmware/pcieuart8997_combo_v4.bin + vendor/nxp/imx-firmware/nxp/FwImage_8987/sdiouart8987_combo_v0.bin:vendor/firmware/sdiouart8987_combo_v0.bin The patch is attached as 0001-8mq-m2-8987-wifi.patch
View full article
What is a device tree? The device tree is a data structure that is passed to the Linux kernel to describe the physical devices in a system. Before device trees came into use, the bootloader (for example, U-Boot) had to tell the kernel what machine type it was booting. Moreover, it had to pass other information such as memory size and location, kernel command line, etc. Sometimes, the device tree is confused with the Linux Kernel configuration, but the device tree specifies what devices are available and how they are accessed, not whether the hardware is used. The device tree is a structure composed of nodes and properties: Nodes: The node name is a label used to identify the node. Properties: A node may contain multiple properties arranged with a name and a value. Phandle: Property in one node that contains a pointer to another node. Aliases: The aliases node is an index of other nodes. A device tree is defined in a human-readable device tree syntax text file such as .dts or .dtsi. The machine has one or several .dts files that correspond to different hardware configurations. With these .dts files we can compile them into a device tree binary (.dtb) blobs that can either be attached to the kernel binary (for legacy compatibility) or, as is more commonly done, passed to the kernel by a bootloader like U-Boot. What is Devshell? The Devshell is a terminal shell that runs in the same context as the BitBake task engine. It is possible to run Devshell directly or it may spawn automatically. The advantage of this tool is that is automatically included when you configure and build a platform project so, you can start using it by installing the packages and following the setup of i.MX Yocto Project User's Guide on section 3 “Host Setup”. Steps: Now, let’s see how to compile your device tree files of i.MX devices using Devshell. On host machine. Modify or make your device tree on the next path: - 64 bits. ~/imx-yocto-bsp/<build directory>/tmp/work-shared/<machine>/kernel-source/arch/arm64/boot/dts/freescale - 32 bits. ~/imx-yocto-bsp/<build directory>/tmp/work-shared/<machine>/kernel-source/arch/arm/boot/dts To compile, it is needed to prepare the environment as is mentioned on i.MX Yocto Project User's Guide on section 5.1 “Build Configurations”. $ cd ~/imx-yocto-bsp $ DISTRO=fsl-imx-xwayland MACHINE=<machine> source imx-setup-release.sh -b <build directory> $ bitbake -c devshell virtual/kernel (it will open a new window) On Devshell window. $ make dtbs (after finished, close the Devshell window) On host machine. $ bitbake -c compile -f virtual/kernel $ bitbake -c deploy -f virtual/kernel This process will compile all the device tree files linked to the machine declared on setup environment and your device tree files will be deployed on the next path: ~/imx-yocto-bsp/<build directory>/tmp/deploy/images/<machine> I hope this article will be helpful. Best regards. Jorge.
View full article
On behalf of Gopise Yuan. This is an update for the DRM framebuffer capture tool I used to share with the team. Some enhancement added. Might be useful for debugging some display related issues.   Some special notes: Behavior of DRM subsystem is different between L4.x and L5.x. For L4.x, you can capture the RGB buffer without any problem. But, there’s no API for YUV (multi-plane) buffer. To capture YUV, need to apply “kernel_0001-drm-Add-getfb2-ioctl_L4.14.98.patch”. For L5.x, mapping/capturing the internal buffer is not allowed by default due to security reason. A simple change in “0001-drm-enable-mapping-of-internal-object-for-debugging_L5.x.patch” can disable this guard. Capture raw data only. RGB and YUV (packed/planar, 420/422) supported. Support de-tile on “Amphion tile” (VPU, NV12 only) and “Super tile” (Vivante GPU). Please use “-t” to enable this. Other tile might not be supported. This is a static linked binary. You can run it on any ARM64 based Linux/Android system in theory (prerequisites in item #1). If you need source code, come to me.   To get more details on how to use it, use “-?” option: DRM screen capture DRM based screen capture program Usage:     ./drmfbcap [OP] [ARG] [OP] OPeration (optional):     -v Show version.     -? Show help information.     -i Show information about target DRM device only (no capture).     -t Perform de-tile for tile format.     -d DRM device to open. [ARG] should contain the path to the device node. Default: '/dev/dri/card0'     -o Output folder. [ARG] should contain the path to the output folder. Default: '.'     -p Specific plane # to capture. [ARG] should contain the plane number. If no '-p' specified, capture all planes   Example:     ./drmfbcap   Capture all planes on default DRM device.     ./drmfbcap -d /dev/dri/controlD64   Capture all planes on '/dev/dri/controlD64' device.     ./drmfbcap -p 44 -t -o /sdcard   Capture plane 44, do de-tile after capture and then output to /sdcard/.   Raw buffer capture will be done for each enabled/target plane and one file for each. Captured file will be saved to './' if not specified. --- By Gopise, 2022/08   Updated_2023_10_16: continuous capture (repeat mode) support with this link: https://community.nxp.com/t5/i-MX-Processors-Knowledge-Base/DRM-screen-capture-tool/ta-p/1725363
View full article
    Test environment: MPU6050 module,i.MX8MP,Android11_2.4.0 This solution ported the MPU6050 module on Android to realize auto rotation of screen.      
View full article
One of the most popular use cases for embedded systems are projects destinated to show information and interact with users. These views are called GUI or Graphic User Interface which are designed to be intuitive, attractive, consistent, and clear. There are many tools that we can use to achieve great GUIs, mostly implemented for platforms such as Web, Android, and iOS. Here, we will need to introduce the concept of framework, basically, it is a set of tools and rules that provides a minimal structure to start with your development. Frameworks usually comes with configuration files, code snippets, files and folders organization helping us to save time and effort. Also, it is important to review the concept of SDK or Software Development Kit which is a set of tools that allows to build software for specific platforms. Usually supplies debugging tools, documentation, libraries, API’s, emulators, and sample code. Flutter is an open-source UI software development kit by Google that help us to create applications with great GUIs on different platforms from a single codebase. Depends on the reference, you can find Flutter defined as a framework or SDK and both are correct, however, an SDK could be a best definition thanks to Flutter supplies a wide and complete package to create an application in which framework is also included. This article is aimed at those that are in a prototyping stage looking for a different tool to develop projects. Also, this article pretends to be a theoretical introduction explaining the most important concepts. However, is a good practice to learn more about reviewing the official documentation from Flutter. (Flutter documentation | Flutter) Here is the structure used throughout this article: What is Flutter? Flutter details Platforms Programming language Official documentation Flutter for embedded systems What is Flutter? Flutter was officially released by Google in December 2018 with a main aim, to give developers a tool to create applications natively compiled for mobile (Android, iOS), web and desktop (Windows, Linux) from a single codebase. It means that as a developer, Flutter will create a structure with minimal code, configuration files, build files for each operating system, manifests, etc. in which we will add our custom code and finally build this code for our preferred OS. For example, we can create an application to review fruit and vegetable information and compile for Android and iOS with the same code. A basic Flutter development process based on my experience looks like the following diagram: Flutter has the following key features: Cross-platform development. Flutter allows the developer to create applications for different platforms using a single codebase. It means that you will not need to recreate the application for each platform you want to support.   Hot-reload. This feature allows the developer to see changes in real time without restarting the whole application, this results in time savings for your project.   High Performance Flutter apps achieve high performance due to the app code is compiled to native ARM code. With this tool no interpreters are involved.   UI Widgets Flutter supplies a set of widgets (UI components such as boxes, inputs text, buttons, etc.) predefined by UI systems guidelines Material on Android and Cupertino for iOS. Source: Material 3 Design Kit | Figma Community Source: Design - Apple Developer   Great community support. This feature could be subjective but, it is useful when we are developing our project find solutions to known issues or report new ones. Because of Flutter is an open source and is widely implemented in the industry this tool owns a big community, with events, forums, and documentation. Flutter Details Supported Platforms With Flutter you can create applications for: Android iOS Linux Debian Linux Ubuntu macOS web Chrome, Firefox, Safari, Edge Windows Supported deployment platforms | Flutter Programming Language Flutter use Dart, a programming language is an open-source language supported by Google optimized to use on the creation of user interfaces. Dart key features: Statically typed. This feature helps catching errors making the code robust ensuring that the variable’s value always match with the declared variable’s type. Null safety. All variables on Dart are non-nullable which means that every variable must have a non-null value avoiding errors at execution time. This feature also, make the code robust and secure. Async/Await. Dart is client-optimized which means that this language was specially created to ensure the best performance as a client application. Async/Await is a feature part of this optimization making easier to manage network requests and other asynchronous operations. Object oriented. Dart is an object-oriented language with classes and mixin. This is especially useful to use on Flutter with the usage of widgets. Compiler support of Just-In-Time (JIT) and Ahead-of-Time (AOT) JIT provides the support that enables the Hot Reload Flutter feature that I mentioned before. It is a complex mechanism, but Dart “detects” changes in your code and execute only these changes avoiding recompiling all the code. AOT compiler produces efficient ARM code improving start up time and performance. Official documentation Flutter has a rich community and documentation that goes from UI guidelines to an Architectural Overview. You can find the official documentation at the following links: Flutter Official Documentation: Flutter documentation | Flutter Flutter Community: Community (flutter.dev) Dart Official Documentation: Dart documentation | Dart Flutter for embedded systems So far, we know all the excellent features and platforms that Flutter can support. But, what about the embedded systems? On the official documentation we can find that Flutter may be used for embedded systems but in fact there is no an official supported platform. This SDK has been supported by their community, specially there is one repository on GitHub supported by Sony that provides documentation and Yocto recipes to support Flutter on embedded Linux. To understand the reason to differentiate between Flutter for Linux Desktop with official support and to create a specific Flutter support for embedded Linux is important to describe the basics of Flutter architecture. Based on the Flutter documentation the system is designed using layers that can be illustrated as follows:   Source: Flutter architectural overview | Flutter We can see as a top level “Framework” which is a high-level layer that includes widgets, tools and libraries that are in contact with developers. Below “Framework,” the layer “Engine” is responsible of drawing the widgets specified in the previous layer and provides the connection between high-level and low-level code. This layer is mostly written in C++ for this reason Flutter can achieve high performance running applications. Specifically for graphics rendering Flutter implements Impeller for iOS and Skia for the rest of platforms. The bottom layer is “Embedder” which is specific for each target and operating system this layer allows Flutter application to run as a native app providing the access to interact with different services managed by the operating systems such as input, rendering surfaces and accessibility. This layer for Linux Desktop uses GTK/GDK and X11 as backend that is highly dependent of unnecessary libraries and expensive for embedded systems which have constrained resources for computation and memory. The work around founded by Sony’s Flutter for Embedded Linux repository is to change this backend using a widely implemented backend for embedded systems Wayland. The following image illustrates the difference between Flutter for Linux Desktop and Flutter for Embedded Linux.   Source: What's the difference between Linux desktop and Embedded Linux · sony/flutter-embedded-linux Wiki · GitHub   Source: What's the difference between Linux desktop and Embedded Linux · sony/flutter-embedded-linux Wiki · GitHub Here is the link to the mentioned repository: GitHub - sony/flutter-elinux: Flutter tools for embedded Linux (eLinux) Finally, I would like to encourage you to read the official Flutter documentation and consider this tool as a great option compared to widely used tools on embedded devices such as Qt or Chromium. Also, please have a look to a great article written by Payam Zahedi delving into the implementation of Flutter for Embedded Linux measuring performance and giving conclusions about the usage of Flutter in embedded systems. (Flutter on Embedded Devices. Learn how to run Flutter on embedded… | by Payam Zahedi | Snapp Embedded | Medium).    
View full article
current bsp fixed the lvds pixel clock up to 74.25Mhz for single channel and 148.5Mhz for dual channel, if customer wants to know why and how to change it, maybe can refer to the enclosed file, hope helpful for you
View full article
BSP: L5.15.5_1.0.0   Platform: i.MX8MPlus EVK   1. Parameter preparation For more parameter calculation, please refer to: https://community.nxp.com/t5/i-MX-Processors-Knowledge-Base/iMX-8M- Mini-Register-Programming-Aid-DRAM-PLL-setting/ta-p/111209  For 1866MHz LPDDR4, we need a DRAM PLL size of 933MHz. The PLL dividing parameters are: m=622,p=16,s=0, k=0.   2. Calibration and stress test with DDR Tool 2.1 Creating a test script for 1866MHz Here we copy the script from another file (e.g. 2000MHz) and modify the contents of the script.   2.2 Modify the script to adapt to 1866MHz 2.3 Download the test script After selecting the ddr script we created, click on the download button   2.4 Calibrating the stress test Set the core clock of the chip's cpu to 1.2GHz, then click the Calibration button to calibrate, then click Gen Code to generate the lpddr4_timing.c file. Set the start frequency to 1866MHz for the stress test.   2.5 Modify lpddr4_timing.c We need to modify the generated lpddr4_timing.c file to change the maximum speed to 3732MTS.   3. SPL patch After getting the correct lpddr4_timing.c file, the SPL code also needs to be modified to add support for the 933MHz DRAM PLL. diff --git a/arch/arm/mach-imx/imx8m/clock_imx8mm.c b/arch/arm/mach-imx/imx8m/clock_imx8mm.c index e39f238fdf...5622a6334e 100644 --- a/arch/arm/mach-imx/imx8m/clock_imx8mm.c +++ b/arch/arm/mach-imx/imx8m/clock_imx8mm.c @@ -55,6 +55,7 @@ static struct imx_int_pll_rate_table imx8mm_fracpll_tbl[] = { PLL_1443X_RATE(650000000U, 325, 3, 2, 0), PLL_1443X_RATE(600000000U, 300, 3, 2, 0), PLL_1443X_RATE(594000000U, 99, 1, 2, 0), + PLL_1443X_RATE(933000000U, 622, 16, 0, 0), PLL_1443X_RATE(400000000U, 400, 3, 3, 0), PLL_1443X_RATE(2660000U, 266, 3, 3, 0), PLL_1443X_RATE(167000000U, 334, 3, 4, 0), diff --git a/drivers/ddr/imx/imx8m/ddrphy_utils.c b/drivers/ddr/imx/imx8m/ddrphy_utils.c index 326b92d784..ebd005bc2b 100644 --- a/drivers/ddr/imx/imx8m/ddrphy_utils.c +++ b/drivers/ddr/imx/imx8m/ddrphy_utils.c @@ -117,6 +117,10 @@ void ddrphy_init_set_dfi_clk(unsigned int drate) dram_pll_init(MHZ(1000)); dram_disable_bypass(); break; + case 3732: + dram_pll_init(MHZ(933)); + dram_disable_bypass(); + break; case 3200: dram_pll_init(MHZ(800)); dram_disable_bypass();   4. Test results   Reference blog. DDR Tool: https://community.nxp.com/t5/i-MX-Processors-Knowledge-Base/i-MX-8M-Family-DDR-Tool-Release/ta-p/1104467  RPA: https://community.nxp.com/t5/i-MX-Processors-Knowledge-Base/i-MX-8MPlus-m865S-DDR-Register-Programming-Aids-RPA/ta-p/1235352 
View full article
L5.4.3_1.0.0 release is now available on IMX_SW landing page: BSP Updates and Releases -> Linux ->Linux L5.4.3_1.0.0. Documentation -> Linux -> Linux 5.4.3_1.0.0 Documentation Files available: # Name Description 1 imx-yocto-LF_L5.4.3_1.0.0.zip i.MX L5.4.3_1.0.0 for Linux BSP Documentation. Includes Release Notes, User Guide. 2 LF_v5.4.y-1.0.0_images_MX6QPDLSOLOX.zip i.MX 6QuadPlus, i.MX 6Quad, i.MX 6DualLite, i.MX 6Solox Linux Binary Demo Files 3 LF_v5.4.y-1.0.0_images_MX6SLLEVK.zip i.MX 6SLL EVK Linux Binary Demo Files 4 LF_v5.4.y-1.0.0_images_MX6UL7D.zip i.MX 6UltraLite EVK, 7Dual SABRESD, 6ULL EVK Linux Binary Demo Files 5 LF_v5.4.y-1.0.0_images_MX7ULPEVK.zip i.MX 7ULP EVK Linux Binary Demo Files  6 LF_v5.4.y-1.0.0_images_MX8MMEVK.zip i.MX 8M Mini EVK Linux Binary Demo Files  7 LF_v5.4.y-1.0.0_images_MX8MNEVK.zip i.MX 8M Nano EVK Linux Binary Demo Files  8 LF_v5.4.y-1.0.0_images_MX8MQEVK.zip i.MX 8M Quad EVK Linux Binary Demo files 9 LF_v5.4.y-1.0.0_images_MX8QMMEK.zip i.MX 8QMax MEK Linux Binary Demo files 10 LF_v5.4.y-1.0.0_images_MX8QXPMEK.zip i.MX 8QXPlus MEK Linux Binary Demo files 11 imx-scfw-porting-kit-1.2.10.1.tar.gz System Controller Firmware (SCFW) porting kit v1.2.10.1 for L5.4.3_1.0.0   Target board: MX 8 Series MX 8QuadXPlus MEK Board MX 8QuadMax MEK Board MX 8M Quad EVK Board MX 8M Mini EVK Board MX 8M Nano EVK Board MX 7 Series MX 7Dual SABRE-SD Board MX 7ULP EVK Board MX 6 Series MX 6QuadPlus SABRE-SD and SABRE-AI Boards MX 6Quad SABRE-SD and SABRE-AI Boards MX 6DualLite SDP SABRE-SD and SABRE-AI Boards MX 6SoloX SABRE-SD MX 6UltraLite EVK Board MX 6ULL EVK Board MX 6ULZ EVK Board MX 6SLL EVK Board   What’s New/Features: Please consult the Release Notes.   Known Issues: For known issues and more details please consult the Release Notes.   More information on changes of Yocto, see: README: https://source.codeaurora.org/external/imx/imx-manifest/tree/README?h=imx-linux-zeus ChangeLog: https://source.codeaurora.org/external/imx/imx-manifest/tree/ChangeLog?h=imx-linux-zeus      
View full article
This document describes the i.MX 8MM EVK mini-SAS connectors features on Linux and Android use cases, covering the supported daughter cards, the process to change Device Tree (DTS) files or Boot images, and enable these different display options on the board.
View full article
目录 1 i.MX8Mmini 板级开发包镜像结构 ............................... 2 2 创建 i.MX8Mmini Linux 4.14.78_ga 板级开发包编译环境 3 2.1 下载板级开发包 ...................................................... 3 2.2 创建yocto编译环境: ................................................ 4 2.3 编译sdk及安装 ........................................................ 7 2.4 独立编译 ................................................................. 8 3 DDR配置,测试与输出 ............................................ 13 4 i.MX8Mmini ATF ...................................................... 15 5 FSL Uboot SPL 定制 ............................................... 17 5.1 SPL的编译............................................................ 17 5.2 SPL的启动流程 .................................................... 26 5.3 SPL的定制............................................................ 33 6 FSL Uboot 定制 ....................................................... 39 6.1 FDT支持 ............................................................... 40 6.2 DM(driver model)支持 .......................................... 45 6.3 Uboot目录 结构 .................................................... 59 6.4 Uboot编译 ............................................................ 61 6.5 Uboot初始化流程 .................................................. 62 6.6 uboot 定制 ............................................................ 72 6.7 uboot debug信息 .................................................. 78
View full article
NOTE: Always de-power the target board and the aggregator when plugging or unplugging smart sensors from the aggregator. NOTE: See this link to instrument a board with a Smart Sensor. This page documents the triple-range "smart" current sensor that's part of a larger system for profiling power on application boards. The smart sensor features a Kinetis KL05Z with three current sense amplifiers. It allows measurement currents in three ranges. Four assembly options allow measurement of rail voltages 0-3.3V (two overall current ranges), 0-6.6V, and 12V. It connects to an aggregator, which powers, controls and aggregates data from a number of smart sensor boards. One of the biggest improvements over the older dual-range measurement system is that the on-sensor microcontroller allows near-simultaneous measurement of all instrumented rails on a board. The dual range profiler can only make one measurement at a time.  These are intended to be used with a microncontroller board to act as a trigger and data aggregator. This aggregator could also be used to reprogram the sensors.  The series resistance added by the smart sensor when in run mode (highest current range) is under 11 milliOhms as measured with 4-point probes and a Keysight B2902B SMU.  A "power oscilloscope" can be made by triggering measurements at regular intervals and presenting the results graphically.... Schematic: Board Layout, Top: Board Layout, Bottom: Here's a photo of two with a nickel is included to show scale. The board measures about 0.5 by 1.3 inches. Connections: The smart sensor header connections are: 5V: powers the 3.3V regulator, which in turn powers everything else on the sensor board 12V: all the gates of all the switching FETs are pulled pulled up to 12V GND: ground connection SCL/TX: I2C clock line  SDA/RX: I2C data line  SWD_CLK:  line for triggering smart sensors to make measurements RESET_B:  line for resetting the smart sensor board SWD_IO: select line for the smart sensor Theory of operation: Three shunts and current sense amplifiers are used to measure current in three ranges. One shunt/sense amp pair has a 0.002Ω shunt integrated into the IC package (U1, INA250). The other two sense amps (U2 and U3, INA212) require an external shunt.  FETs Q1, Q2,  and Q3 are used to switch the two lower range shunt/sense amp pairs in and out of circuit. In normal run operation (highest current range), Q1 (FDMC012N03, with Rds(on) under 1.5mΩ) is turned on, which shorts leaves only U1 in circuit. FETs Q4, Q5 and Q6 translate the voltages to 3.3V so that GPIO on U4 (MCU KL05Z) can control them.  Rail voltage measurement is facilitated via resistors R3, R4, and R12 and Q7. Not all of these are populated in every assembly option. For measuring rail voltages 0-3.3V, R12 is populated. To measure 0-6.6V, R3, R4,and Q7 are populated. When turned on Q7 enables the voltage divider. All of the assembly option population info can be found in the schematic (attached). Regulator U5 (AP2210N) provides the 3.3V supply for all of the components on the board. This 1% tolerance regulator is used to provide a good reference for the ADC in U4.  Microcontroller U4 detects the assembly population option of the board via resistors R9, R10, and R11 so that the same application code can be used across all variations of the sensor boards. GPIO control the FETs and four ADC channels are used to measure the sense amplifier outputs and the rail voltage. Having a microcontroller on the sensor board allows the user to do extra credit things like count coulombs as well as allowing all similarly instrumented rails to measure at the same time via trigger line SWD_CLK. Data communication can be via I2C or UART, since these two pins can do both.  But if multiple sensor boards are to be used with an aggregator, communication needs to be over I2C. Application Code: The latest application code for the KL05Z on the smart sensor resides here: https://os.mbed.com/users/r14793/code/30847-SMRTSNSR-KL05Z/. The latest binary is attached below. In order to re-flash a smart sensor, the modification detailed in the aggregator page needs to be made. Once the modification is completed, leave the aggregator unpowered while pluging the SWD debugger into J5 and the smart sensor to be programmed into JP15. Very old UART-based application code for the KL05Z, built in the on-line MBED compiler (note that it requires the modified mbed library for internal oscillator). This code was used while testing the first smart sensor prototypes. It has since been abandoned. It's published here in the event that a user wants to use a single sensor plugged into JP15 with UART breakout connector J6. /****************************************************************************** * * MIT License (https://spdx.org/licenses/MIT.html) * Copyright 2017-2018 NXP * * MBED code for KL05Z-based "smart" current sensor board, basic testing * of functions via UART (connected via FRDM board and OpenSDA USB virtual * COM port). * * Eventual goal is to have each smart sensor communicate over I2C to an * aggregator board (FRDM board with a custom shield), allowing 1-10 power * supply rails to be instrumented. Extra credit effort is to support * sensors and aggregator with sigrok... * * Because there is no crystal on the board, need to edit source mbed-dev library * to use internal oscillator with pound-define: * change to "#define CLOCK_SETUP 0" in file: * mbed-dev/targets/TARGET_Freescale/TARGET_KLXX/TARGET_KL05Z/device/system_MKL05Z4.c * ******************************************************************************/ #include "mbed.h" // These will be GPIO for programming I2C address... // not yet implemented, using as test pins... DigitalOut addr0(PTA3); DigitalOut addr1(PTA4); DigitalOut addr2(PTA5); DigitalOut addr3(PTA6); // configure pins for measurements... // analog inputs from sense amps and rail voltage divider... AnalogIn HIGH_ADC(PTB10); AnalogIn VRAIL_ADC(PTB11); AnalogIn LOW1_ADC(PTA9); AnalogIn LOW2_ADC(PTA8); // outputs which control switching FETs... DigitalOut VRAIL_MEAS(PTA7); // turns on Q7, connecting voltage divider DigitalOut LOW_ENABLE(PTB0); // turns on Q4, turning off Q1, enabling low measurement DigitalOut LOW1(PTB2); // turns on Q5, turning off Q2, disconnecting shunt R1 DigitalOut LOW2(PTB1); // turns on Q6, turning off Q3, disconnecting shunt R2 // input used for triggering measurement... // will eventually need to be set up as an interrupt so it minimizes delay before measurement InterruptIn trigger(PTA0); // use as a trigger to make measurement... // PTB3/4 can be used as UART or I2C... // For easier development with one smart sensor, we are using UART here... Serial uart(PTB3, PTB4); // tx, rx long int count=0; int n=25; // global number of averages for each measurement int i, temp; bool repeat=true; // flag indicating whether measurements should repeat or not const float vref = 3.3; // set vref for use in calculations... float delay=0.25; // default delay between measurement bool gui = false; // flag for controlling human vs machine readable output bool statistics = false;// flag for outputting min and max along with average (GUI mode only) void enableHighRange(){ LOW_ENABLE = 0; // short both low current shunts, close Q1 wait_us(5); // delay for FET to settle... (make before break) LOW1 = 0; LOW2 = 0; // connect both shunts to make lower series resistance VRAIL_MEAS = 0; // disconnect rail voltage divider wait_us(250); // wait for B2902A settling... } void enableLow1Range(){ LOW1 = 0; LOW2 = 1; // disconnect LOW2 shunt so LOW1 can measure wait_us(5); // delay for FET to settle... (make before break) LOW_ENABLE = 1; // unshort low current shunts, open Q1 VRAIL_MEAS = 0; // disconnect rail voltage divider wait_us(250); // wait for B2902A settling... } void enableLow2Range(){ LOW1 = 1; LOW2 = 0; // disconnect LOW1 shunt so LOW2 can measure wait_us(5); // delay for FET to settle... (make before break) LOW_ENABLE = 1; // unshort low current shunts, open Q1 VRAIL_MEAS = 0; // disconnect rail voltage divider wait_us(500); // wait for B2902A settling... } void enableRailV(){ VRAIL_MEAS = 1; // turn on Q7, to enable R3-R4 voltage divider wait_us(125); // wait for divider to settle... // Compensation cap can be used to make // voltage at ADC a "square wave" but it is // rail voltage and FET dependent. Cap will // need tuning if this wait time is to be // removed/reduced. // // So, as it turns out, this settling time and // compensation capacitance are voltage dependent // because of the depletion region changes in the // FET. Reminiscent of grad school and DLTS. // Gotta love device physics... } void disableRailV(){ VRAIL_MEAS = 0; // turn off Q7, disabling R3-R4 voltage divider } // this function measures current, autoranging as necessary // to get the best measurement... void measureAuto(){ Timer t; float itemp; float tempI=0; float imin = 1.0; // used to keep track of the minimum... float imax = 0; // used to keep track of the maximum... t.start(); // use timer to see how long things take... enableHighRange(); // this should already be the case, but do it anyway... for (i = 0; i < n; i++){ itemp = HIGH_ADC; // read HIGH range sense amp output if (statistics && itemp>imax) imax = itemp; // update max if necessary if (statistics && itemp<imin) imin = itemp; // update min if necessary tempI += itemp; // add current sample to running sum } tempI = tempI/n *vref/0.8; // compute average we just took... if (gui) uart.printf("=> %5.3f ", tempI); if (statistics && gui) uart.printf("[%5.3f/%5.3f] ", imin*vref/0.8, imax*vref/0.8); // if current is below this threshold, use LOW1 to measure... if (tempI < 0.060) { if (!gui) uart.printf("... too Low: %f A, switching to low1 ==>\r\n", tempI); tempI=0; enableLow1Range(); // change FETs to enable LOW1 measurement... imin = 1.0; imax = 0; for (i = 0; i < n; i++){ itemp = LOW1_ADC; // read LOW1 sense amp output if (statistics && itemp>imax) imax = itemp; // update max if necessary if (statistics && itemp<imin) imin = itemp; // update min if necessary tempI += itemp; // add current sample to running sum } tempI = tempI/n *vref/0.05/1000; // compute average we just took... if (gui) uart.printf("%6.4f ", tempI); if (statistics && gui) uart.printf("[%6.4f/%6.4f] ", imin*vref/0.05/1000, imax*vref/0.05/1000); // if current is below this threshold, use LOW2 to measure... if (tempI < 0.0009){ if (!gui) uart.printf("... too Low: %f A, switching to low2 ==>\r\n", tempI); tempI=0; enableLow2Range(); // change FETs to enable LOW1 measurement... imin = 1.0; imax = 0; for (i = 0; i < n; i++){ itemp = LOW2_ADC; // read LOW2 sense amp output if (statistics && itemp>imax) imax = itemp; // update max if necessary if (statistics && itemp<imin) imin = itemp; // update min if necessary tempI += itemp; // add current sample to running sum } tempI = tempI/n *vref/2/1000; // compute average we just took... if (gui) uart.printf("%8.6f ", tempI); if (statistics && gui) uart.printf("[%8.6f/%8.6f] ", imin*vref/2/1000, imax*vref/2/1000); } } t.stop(); // stop the timer to see how long it took do do this... enableHighRange(); if (!gui) uart.printf("\r\nCurrent = %f A Current Measure Time = %f sec\r\n", tempI, t.read()); } // the autoranging should really be done with functions that return values, as should the // functions below... This would make for shorter and more elegant code, but the author // is a bit of a pasta programmer... void measureHigh(){ float highI=0; enableHighRange(); for (i = 0; i < n; i++){ highI += HIGH_ADC; } highI = highI/n; uart.printf("HIghI = %f A\r\n", vref*highI/0.8); } void measureLow1(){ float low1I=0; enableLow1Range(); for (i = 0; i < n; i++){ low1I += LOW1_ADC; } enableHighRange(); low1I = low1I/n; uart.printf("low1I = %f A\r\n", vref*low1I/0.05/1000); } void measureLow2(){ float low2I=0; enableLow2Range(); for (i = 0; i < n; i++){ low2I += LOW2_ADC; } enableHighRange(); low2I = low2I/n; uart.printf("low2I = %f A\r\n", vref*low2I/2/1000); } // measure the rail voltage, default being with // a divide by 2 resistor divider // It has to be switched out when not in use or it will // add to the measured current, at least in the low ranges... void measureRailV(){ float railv=0; float mult = vref*2; // since divide by 2, we can measure up to 6.6V... float vmin = 5; float vmax = 0; float vtemp; enableRailV(); // switch FETs so divider is connected... for (i = 0; i < n; i++){ vtemp = VRAIL_ADC; // read voltage at divider output... if (statistics && vtemp>vmax) vmax = vtemp; // update max if necessary if (statistics && vtemp<vmin) vmin = vtemp; // update min if necessary railv += vtemp; // add current sample to running sum } disableRailV(); // now disconnect the voltage divider railv = railv/n; // compute average (note this is in normalized ADC [0..1]) // Convert to voltage by multiplying by "mult" if (!gui) uart.printf("RailV = %5.3f V ", mult*railv); if (gui) uart.printf("%5.3f ", mult*railv); if (statistics && gui) uart.printf("[%5.3f/%5.3f] ", mult*vmin, mult*vmax); uart.printf("\r\n"); } // not sure how useful this function is... void measureAll(){ measureHigh(); measureLow1(); measureLow2(); measureRailV(); } // test function to see if trigger pin is being hit... // intended for use later to do timed triggering of measurements... void triggerIn(){ uart.printf("You're triggering me! \r\n"); measureAll(); } // main... int main() { // set up basic conditions... Timer m; uart.baud(115200); enableHighRange(); // default state - only HIGH sense amp in circuit, no divider // signal that we're alive... uart.printf("Hello World!\r\n"); // configure the trigger interrupt... trigger.rise(&triggerIn); while (true) { count++; wait(delay); if (repeat){ // if repeat flag is set, keep making measurements... m.reset(); // reset and start timer... m.start(); measureAuto(); // measuring current using auto-ranging... measureRailV(); // measure rail voltage... m.stop(); // stop the timer. if (!gui) uart.printf(" Total Measure Time = %f sec", m.read()); if (!gui) uart.printf("\r\n\r\n"); } // see if there are any characters in the receive buffer... // this is how we change things on the fly... // Commands (single keystroke... it's easier) // t = one shot automeasure // v = measure volt // h = one shot high measure // k = one shot LOW1 measure // l = one shot LOW2 measure (letter l) // r = toggle repeat // R = turn off repeat // + = faster repeat rate // - = slower repeat rate // = = set repeat rate to 0.25 sec // g = use human readable text output // G = use compressed text format for GUI // s = turn statistics output off // S = turn statistics output on (only in GUI mode) // n = decrease number of averages for each measurement // N = increase number of averages for each measurement // // these were for testing FET switching... // 1 = LOW_ENABLE = 0 (the number 1) // 2 = LOW1 = 0 // 3 = LOW2 = 0 // 4 = VRAIL_MEAS = 0 // ! = LOW_ENABLE = 1 // @ = LOW1 = 1 // # = LOW2 = 1 // $ = VRAIL_MEAS = 1 if (uart.readable()){ temp = uart.getc(); if (temp==(int) 't') { if (!gui) uart.printf("Keyboard trigger: "); measureAuto(); measureRailV(); //measureAll(); } if (temp==(int) 'v') { uart.printf("Keyboard trigger: "); measureRailV(); } if (temp==(int) 'h') { uart.printf("Keyboard trigger: "); measureHigh(); } if (temp==(int) 'k') { uart.printf("Keyboard trigger: "); measureLow1(); } if (temp==(int) 'l') { uart.printf("Keyboard trigger: "); measureLow2(); } if (temp==(int) '1') { LOW_ENABLE = 0; uart.printf("Keyboard trigger: LowEnable = %d\r\n", 0); } if (temp==(int) '2') { LOW1 = 0; uart.printf("Keyboard trigger: LOW1 = %d\r\n", 0); } if (temp==(int) '3') { LOW2 = 0; uart.printf("Keyboard trigger: LOW2 = %d\r\n", 0); } if (temp==(int) '4') { VRAIL_MEAS = 0; uart.printf("Keyboard trigger: VRAILMEAS = %d\r\n", 0); } if (temp==(int) '!') { LOW_ENABLE = 1; uart.printf("Keyboard trigger: LowEnable = %d\r\n", 1); } if (temp==(int) '@') { LOW1 = 1; uart.printf("Keyboard trigger: LOW1 = %d\r\n", 1); } if (temp==(int) '#') { LOW2 = 1; uart.printf("Keyboard trigger: LOW2 = %d\r\n", 1); } if (temp==(int) '$') { VRAIL_MEAS = 1; uart.printf("Keyboard trigger: VRAILMEAS = %d\r\n", 1); } if (temp==(int) 'r') { repeat = !repeat; uart.printf("Keyboard trigger: repeat toggle: %s \r\n", repeat ? "true" : "false"); } if (temp==(int) 'R') repeat = false; if (temp==(int) '+') { delay -= 0.05; if (delay<0.05) delay = 0.05; } if (temp==(int) '-') { delay += 0.05; if (delay>1) delay = 1; } if (temp==(int) '=') delay = 0.25; if (temp==(int) 'g') gui = false; if (temp==(int) 'G') gui = true; if (temp==(int) 's') statistics = false; if (temp==(int) 'S') statistics = true; if (temp==(int) 'n') { n -= 25; if (n<25) n = 25; } if (temp==(int) 'N') { n += 25; if (n>1000) n = 1000; } if (temp==(int) 'N' || temp==(int) 'n') uart.printf("/r/n/r/n Averages = %d \r\n\r\b", n); } } }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
View full article