DAY61 IMX6ULL UART Serial Communication Practice

IMX6ULL UART Serial Communication Practice

I. Core Concepts of UART

Before writing code, let's clarify the core concepts of embedded communication, which form the foundation for understanding UART operation principles.

1. Communication

In embedded systems, communication essentially means data interaction between two or more hosts. UART is one type of asynchronous serial communication method that doesn't require a clock line for synchronization. It only needs TX (transmit) and RX (receive) lines to achieve bidirectional data transfer.

2. Asynchronous vs. Synchronous

  • Asynchronous communication: No dedicated clock line is needed. Both parties agree on parameters like baud rate, data bits, and stop bits, achieving synchronization through a frame format of "start bit + data bits + parity bit + stop bit" (UART belongs to this category).
  • Synchronous communication: Requires a clock line (e.g., SCK in SPI, SCL in I2C). Both parties strictly synchronize with the clock signal, offering higher transmission efficiency (e.g., SPI, I2C).

3. Serial vs. Parallel

  • Serial communication: Data is transmitted bit by bit over a single line (e.g., UART, RS485). Advantages include simple wiring and long transmission distance, while the disadvantage is relatively slower speed.
  • Parallel communication: Multiple bits of data are transmitted simultaneously over multiple lines (e.g., MCU parallel bus). Advantages include high speed, while disadvantages include complex wiring, poor anti-interference, and short transmission distance.

4. Simplex/Half-Duplex/Full-Duplex

  • Simplex: Supports one-way transmission only (e.g., remote control → TV).
  • Half-duplex: Supports bidirectional transmission but only one party can send at a time (e.g., walkie-talkie).
  • Full-duplex: Supports simultaneous bidirectional transmission (UART defaults to full-duplex with independent TX/RX).

5. TTL/RS232/RS485

These are different level standards designed for different transmission scenarios:

  • TTL: Native level for embedded chips (high level ≈ 3.3V/5V, low level ≈ 0V), with short transmission distance (<1 meter).
  • RS232: Negative level standard (high level -3~-15V, low level +3~+15V), with a transmission distance of ≈15 meters.
  • RS485: Differential level transmission with strong anti-interference, supporting transmission distances up to kilometers and multi-device communication.

6. Differential Transmission

Differential transmission uses voltage difference between two signal lines to represent logic levels (e.g., A-B > 0.2V for logic 1, A-B < -0.2V for logic 0). It effectively cancels common-mode interference (e.g., power line noise, electromagnetic interference) and is the core technology for long-distance communication like RS485 and CAN.

II. Hardware Schematic Analysis (IMX6ULL_MINI_V2.2)

This practice is based on the "USB USART & USB POWER" module in the schematic "IMX6ULL_MINI_V2.2(Mini Board Schematic).pdf". The core hardware components are as follows:

1. Core Module Breakdown

Module Description
USB_TTL Port Acts as the physical interface for UART, connecting the computer USB to the CH340 chip
CH340 (U8) USB-to-TTL serial chip, converting "computer USB bus" to "TTL-level UART" on the board
DCDC (U12/U13) Power regulation module, providing stable power with anti-jitter and anti-interference (⚠️ Not recommended to power via USB due to overheating risks)

2. Hardware Logic

Computer connects to the board's USB_TTL port via USB cable → CH340 converts USB signals to TTL-level UART signals → Connects to IMX6ULL's UART1_TX/RX pins → Enables "computer-board" UART communication.

III. UART Code Implementation (Based on IMX6ULL)

Code development refers to "IMX6ULL Reference Manual.pdf" and is divided into five steps: clock initialization , pin initialization , register configuration , transceiver function implementation , and stdio porting.

1. Preparations

First, define the base address of IMX6ULL's UART1 registers and pin multiplexing macros (example):

c 复制代码
// UART1 register base address
#define UART1_BASE        0x02020000
#define UART1             ((volatile unsigned int *)UART1_BASE)

// Register offset definitions (only core registers listed)
#define UART_URXD         0x00  // Receive register
#define UART_UTXD         0x40  // Transmit register
#define UART_UCR1         0x80  // Control register 1
#define UART_UCR2         0x84  // Control register 2
#define UART_UCR3         0x88  // Control register 3
#define UART_UFCR         0x90  // FIFO control register
#define UART_USR2         0xB8  // Status register 2
#define UART_UBIR         0xA4  // Baud rate increment register
#define UART_UBMR         0xA8  // Baud rate modulation register

// Pin multiplexing function declarations (IMX6ULL standard library)
void IOMUXC_SetPinMux(unsigned int muxRegister, unsigned int muxMode);
void IOMUXC_SetPinConfig(unsigned int configRegister, unsigned int configValue);

2. Step 1: Clock Initialization

IMX6ULL's UART reference clock is provided by the peripheral clock by default. Here, we configure the base clock as 80MHz with a 1:1 prescaler (i.e., reference clock = 80MHz):

c 复制代码
/**
 * @brief UART clock initialization: 80MHz base clock, 1:1 prescaler
 */
void uart_clk_init(void) {
    // Configure UART1's peripheral clock as 80MHz (specific registers refer to IMX6ULL clock tree)
    CCM->CSCDR1 &= ~(0x1F << 6);  // Clear existing prescaler value
    CCM->CSCDR1 |= (0x00 << 6);   // 1:1 prescaler, UART reference clock = 80MHz
}

3. Step 2: Pin Initialization

IMX6ULL pins are "multi-function multiplexed". Configure the specified pins as UART1_TX/RX and set electrical properties:

c 复制代码
/**
 * @brief UART1 pin initialization: TX->GPIO1_IO04, RX->GPIO1_IO05
 */
void uart_pin_init(void) {
    // 1. Configure TX pin (GPIO1_IO04 → UART1_TX)
    IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);  // Multiplex as UART1_TX
    // Configure electrical properties: pull-up, 100MHz speed, drive strength, etc. (0x10B0 is standard)
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);

    // 2. Configure RX pin (GPIO1_IO05 → UART1_RX)
    IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);  // Multiplex as UART1_RX
    IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
}

Parameter Explanation:

  • IOMUXC_SetPinMux second parameter 0: Use default multiplex mode with no additional configuration.
  • 0x10B0: Binary 0001 0000 1011 0000, core configurations:
    • Bits [15:12]: 0001 → Pin speed 100MHz.
    • Bits [11:8]: 1011 → 22KΩ pull-up.
    • Bits [7:0]: 0000 → No open-drain, no interrupts, etc.

4. Step 3: UART Register Configuration

Core configuration for UART operation mode (8 data bits, 1 stop bit, no parity, 115200 baud rate):

c 复制代码
/**
 * @brief UART1 register configuration: 8N1 (8 data bits + 1 stop bit + no parity), 115200 baud rate
 */
void uart_reg_init(void) {
    // 1. Software reset UART (UCR2[0] set to 1, hold for 4 clock cycles, then clear to 0)
    UART1[UART_UCR2/4] |= (1 << 0);
    while((UART1[UART_UCR2/4] & (1 << 0)));  // Wait for reset completion

    // 2. Configure UCR1: Enable UART main switch (UCR1[0] = 1)
    UART1[UART_UCR1/4] |= (1 << 0);

    // 3. Configure UCR2: 8 data bits, 1 stop bit, no parity, enable transceiver, ignore RTS flow control
    UART1[UART_UCR2/4] |= (1 << 1);  // RXEN=1, enable receive
    UART1[UART_UCR2/4] |= (1 << 2);  // TXEN=1, enable transmit
    UART1[UART_UCR2/4] &= ~(1 << 5); // WS=0, 8 data bits
    UART1[UART_UCR2/4] &= ~(1 << 6); // STPB=0, 1 stop bit
    UART1[UART_UCR2/4] &= ~(1 << 8); // PREN=0, disable parity
    UART1[UART_UCR2/4] |= (1 << 14); // IRTS=1, ignore RTS flow control

    // 4. Configure UCR3: RXDMUXSEL=1 (IMX6ULL must set this for multiplex mode)
    UART1[UART_UCR3/4] |= (1 << 2);

    // 5. Configure UFCR: RFDIV=0 (reference clock 1:1 prescaler)
    UART1[UART_UFCR/4] &= ~(0x7 << 7); // Clear existing value
    UART1[UART_UFCR/4] |= (0x0 << 7);  // 1:1 prescaler

    // 6. Configure baud rate: 115200 (reference clock 80MHz)
    // Formula: BaudRate = Ref Freq/(16 * ((UBMR + 1)/(UBIR + 1)))
    // Derivation: (UBMR+1)/(UBIR+1) = 80000000/(16*115200) ≈ 43.4028
    // Choose UBIR=15, UBMR=659 ((659+1)/(15+1)=660/16=41.25, close to target)
    UART1[UART_UBIR/4] = 15;
    UART1[UART_UBMR/4] = 659;
}

5. Step 4: Implementation of Transmit/Receive Functions

Based on register encapsulation, implement putc (send a single character), puts (send a string), and getc (receive a single character):

c 复制代码
/**  
 * @brief Send a single character  
 * @param d Character to send  
 */  
void putc(unsigned char d) {  
    // Poll TXDC bit (USR2[3]) until transmission is complete  
    while ((UART1[UART_USR2/4] & (1 << 3)) == 0);  
    UART1[UART_UTXD/4] = d;  // Write to transmit register, hardware sends automatically  
}  

/**  
 * @brief Send a string (automatically appends newline)  
 * @param pStr Pointer to the string  
 */  
void puts(const char *pStr) {  
    while (*pStr) {  // Traverse the string until '\0'  
        putc(*pStr++);  
    }  
    putc('\n');  // Append newline at the end  
}  

/**  
 * @brief Receive a single character (blocking)  
 * @return Received character  
 */  
unsigned char getc(void) {  
    // Poll RDR bit (USR2[0]) until data is ready  
    while ((UART1[UART_USR2/4] & (1 << 0)) == 0);  
    return (unsigned char)(UART1[UART_URXD/4] & 0xFF);  // Read 8-bit data  
}  

Core Logic:

  • Transmit : Check the TXDC bit to ensure the previous character has been sent, avoiding data overwrite.
  • Receive : Check the RDR bit to determine if new data is available, blocking until data is received.

6. Step 5: Porting the stdio Library (Supporting printf/scanf)

By default, bare-metal programs for IMX6ULL cannot use printf/scanf. The stdio library must be ported as follows:

(1) Add an Empty raise Function

Add the following function to uart.c (required by the stdio library; compilation will fail if missing):

c 复制代码
// For stdio library compatibility; function body can remain empty  
void raise(int n) {  
    (void)n;  // Avoid unused parameter warning  
}  
(2) Modify the Assembly File Extension

Rename the startup file project/start.s to project/start.S:

  • .s: Direct compilation, no preprocessing.
  • .S: Preprocessing first (supports #include, #define, etc.), then compilation, adapting to macro definitions in bare-metal projects.
(3) Modify the Makefile

Add stdio library header/source paths and link the GCC library:

makefile 复制代码
# Target file  
target = imx6ull_uart  
# Compiler paths (adjust based on your cross-compiler)  
cc = arm-linux-gnueabihf-gcc  
ld = arm-linux-gnueabihf-ld  
objcopy = arm-linux-gnueabihf-objcopy  
objdump = arm-linux-gnueabihf-objdump  

# 1. Add stdio-related paths  
incdirs = bsp imx6ull stdio/include  # Header directories  
srcdirs = bsp project stdio/lib      # Source directories  
# 2. Specify GCC library path (adjust based on your compiler path)  
libpath = -lgcc -L/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4  

# Compiler flags: Wall (enable warnings), thumb mode, no stdlib, disable built-ins  
CFLAGS = -Wall -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin  

# Collect all source files  
srcfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.c $(dir)/*.S))  
objfiles = $(patsubst %.c, %.o, $(patsubst %.S, %.o, $(srcfiles)))  

# Link to generate ELF file  
$(target).elf: $(objfiles)  
	$(ld) -Timx6ull.lds -o $@ $^ $(libpath)  

# Compile C files  
%.o: %.c  
	$(cc) $(CFLAGS) -I$(incdirs) -c -o $@ $<  

# Compile assembly files  
%.o: %.S  
	$(cc) $(CFLAGS) -I$(incdirs) -c -o $@ $<  

# Generate BIN file  
bin: $(target).elf  
	$(objcopy) -O binary $< $(target).bin  

# Clean  
clean:  
	rm -rf $(objfiles) $(target).elf $(target).bin  

7. Test Code

Call the encapsulated functions in main.c to test UART communication:

c 复制代码
#include "uart.h"  

int main(void) {  
    unsigned char recv_data;  

    // Initialize UART  
    uart_clk_init();  
    uart_pin_init();  
    uart_reg_init();  

    // Send test string  
    puts("IMX6ULL UART Test Start!");  
    puts("Please input a char:");  

    while (1) {  
        recv_data = getc();       // Receive character  
        putc(recv_data);          // Echo character  
        puts(" Recv Success!");   // Notify successful reception  
    }  

    return 0;  
}  

IV. Key Considerations

  1. Baud Rate Calculation: Must strictly match the reference clock to avoid garbled output.
  2. Pin Configuration: Refer to the manual for IMX6ULL UART pin multiplexing to avoid misconfiguration.
  3. Power Supply: Avoid powering the board via USB; use an external DC power supply to prevent overheating.
  4. Compilation: Ensure the compiler and library paths in the Makefile match your local environment.

V. Summary

This article provides a complete "concept → hardware → code" explanation of UART serial communication implementation for IMX6ULL. Key takeaways:

  • Understand the fundamentals of UART asynchronous serial communication, including frame format and baud rate.
  • Hardware-wise, focus on CH340's USB-to-TTL functionality and avoid incorrect power supply choices.
  • Code-wise, the core lies in register configuration (especially baud rate and transmit/receive enable) and stdio library porting.
  • Polling is the most basic bare-metal UART implementation; it can later be extended to interrupt/DMA modes.
相关推荐
郝学胜-神的一滴2 小时前
深入理解网络IP协议与TTL机制:从原理到实践
linux·服务器·开发语言·网络·网络协议·tcp/ip·程序人生
上海云盾商务经理杨杨8 小时前
2026游戏盾深度解析:从被动防御到智能作战,构建DDoS免疫堡垒
网络·游戏·ddos
杨靳言先8 小时前
✨【运维实战】内网服务器无法联网?巧用 SSH 隧道实现反向代理访问公网资源 (Docker/PortForwarding)
服务器·docker·ssh
二哈喇子!8 小时前
MySQL数据更新操作
数据库·sql
二哈喇子!8 小时前
MySQL命令行导入数据库
数据库·sql·mysql·vs code
心动啊1218 小时前
SQLAlchemy 的使用
数据库
强子感冒了8 小时前
Java网络编程学习笔记,从网络编程三要素到TCP/UDP协议
java·网络·学习
上海云盾商务经理杨杨9 小时前
付费网站的攻防战:2026年,如何破解并抵御爬虫攻击
网络·安全
emma羊羊9 小时前
【wordpress-wpdiscuz-rce】
网络·web安全·wordpress