ARM-08-I.MX6U UART 串口

一、串口介绍

串口是单片机和嵌入式 Linux 开发中最常用的外设,可用于:

  • 将开发板与电脑相连,通过串口调试助手调试程序
  • 蓝牙、GPS、GPRS 等模块与主控通信
  • 嵌入式 Linux 中作为控制台使用

I.MX6U 共有 8 个 UART,主要特性:

  1. 兼容 TIA/EIA-232F 标准,速度最高可到 5Mbit/S
  2. 支持串行 IR 接口,兼容 IrDA,最高可到 115.2Kbit/s
  3. 支持 9 位或者多节点模式(RS-485)
  4. 1 或 2 位停止位
  5. 可编程的奇偶校验(奇校验和偶校验)
  6. 自动波特率检测(最高支持 115.2Kbit/S)

本篇目标:实现 115200,8,N,1 与电脑之间通信。


二、配置串口寄存器

时钟配置

  • 时钟源由寄存器 CCM_CSCDR1UART_CLK_SEL(bit) 位选择:
    • 为 0:UART 时钟源为 pll3_80m(80MHz)← 一般选择此项
    • 为 1:UART 时钟源为 osc_clk(24MHz)
  • UART_CLK_PODF(bit5:0):时钟分频值,可设置 0~63(对应 1~64 分频)← 一般设置为 1 分频
  • 最终进入 UART 的时钟为 80MHz

1. 选择串口引脚功能和电气属性

这里选择 UART1:

复制代码
IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10b0);
IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10b0);

注意:不配置 UART1 的 CTS_B 和 RTS_B,因为:

  • CTS/RTS 引脚用于硬件流控,不是 UART 通信的必要引脚
  • 只有在需要防止数据溢出的高速或大数据量传输场景(如 GSM 模块、GPS 模块、蓝牙模块等)才需要配置

2. 配置三个 UART 控制寄存器

UARTx_UCR1
名称 说明
bit14 ADBR 自动波特率检测使能位:0=关闭,1=使能
bit0 UARTEN UART 使能位:0=关闭,1=使能

⚠️ 注意:要最后配好所有东西再使能

复制代码
UART1->UCR1 |= (1 << 0);  // 使能 UART(最后执行)
UARTx_UCR2
名称 说明
bit14 IRTS 0=使用 RTS 引脚功能,1=忽略 RTS 引脚
bit8 PREN 奇偶校验使能位:0=关闭,1=使能
bit7 PROE 奇偶校验模式选择:0=偶校验,1=奇校验
bit6 STOP 停止位数量:0=1位,1=2位
bit5 WS 数据位长度:0=7位,1=8位
bit2 TXEN 发送使能位:0=关闭,1=打开
bit1 RXEN 接收使能位:0=关闭,1=打开
bit0 SRST 软件复位:0=软件复位 UART,1=复位完成(自动置1)
复制代码
UART1->UCR2 &= ~(1 << 0);   // 软件复位
delay_us(1);
unsigned int tmp = UART1->UCR2;
tmp |= (1 << 14);             // 忽略 RTS
tmp &= ~(1 << 8);             // 关闭奇偶校验
tmp &= ~(1 << 6);             // 1 位停止位
tmp |= (1 << 5);              // 8 位数据位
tmp |= (1 << 2);              // 使能发送
tmp |= (1 << 1);              // 使能接收
UART1->UCR2 = tmp;
UARTx_UCR3

该寄存器的第二位 RXDMUXSEL 必须始终为 1(手册规定)

名称 说明
bit3 TXDC 发送完成标志位:为1时发送缓冲(TxFIFO)和移位寄存器为空(发送完成),写入TxFIFO后自动清零
bit0 RDR 数据接收标志位:为1时表明至少接收到一个数据,读取 UARTx_URXD 后自动清零
复制代码
UART1->UCR3 |= (1 << 2);  // RXDMUXSEL 置 1

3. 波特率配置

工作频率计算:

  • PLL3 时钟 480MHz → 静态 6 分频 → 80MHz
  • UART 自身分频器由 UARTx_UFCR[(bit9~bit7)] 决定 → 需要 1分频 ,即 b101

波特率计算公式:

复制代码
(UBMR + 1) / (UBIR + 1) = RefFreq / (16 × BaudRate)
  • RefFreq:UART 实际工作频率(80MHz)
  • BaudRate:波特率
  • UBMR:寄存器 UARTx_UBMR 中的值
  • UBIR:寄存器 UARTx_UBIR 中的值

例:波特率 115200,工作频率 80MHz:

  • 右边 = 80000000 / (16 × 115200) = 43.402

  • 设 (UBIR+1) = 1000,则 (UBMR+1) = 43402

  • 所以 UBIR = 999,UBMR = 43401

    tmp = UART1->UFCR;
    tmp &= ~(0x7 << 7); // 清除分频位
    tmp |= (0x5 << 7); // 设置 1 分频(b101)
    UART1->UFCR = tmp;

    UART1->UBIR = 999;
    UART1->UBMR = 43404; // 实际代码中用 43404


4. 读写函数

UARTx_USR2(UART 状态寄存器2)
名称 说明
bit3 TXDC 发送完成标志位,为1时发送缓冲和移位寄存器为空
bit0 RDR 数据接收标志位,为1时至少接收到一个数据
写函数(JTXD:写数据寄存器)
复制代码
void uart_send_byte(UART_Type * base, unsigned char data)
{
    while(!(base->USR2 & (1 << 3)));  // 等待发送完成(第3位为1则跳出)
    base->UTXD = data;
}
读函数(URXD:读数据寄存器)
复制代码
unsigned char uart_recv_byte(UART_Type * base)
{
    unsigned char data = 0;
    while(!(base->USR2 & (1 << 0)));  // 等待接收到数据(第0位为1则跳出)
    data = base->URXD & 0xff;         // 只取低8位(有效数据)
    return data;
}

注意data = UART1->URXD & 0xff ,因为只有低八位才是有效数据。


三、完整 uart.c 代码

复制代码
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "uart.h"
#include "gpt.h"

void uart_init(UART_Type * base)
{
    if (base == UART1)
    {
        IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
        IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
        IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10b0);
        IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10b0);
    }
    else if(base == UART2)
    {
        IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_UART2_RX, 0);
        IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_UART2_TX, 0);
        IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_UART2_RX, 0x10b0);
        IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_UART2_TX, 0x10b0);
    }

    base->UCR2 &= ~(1 << 0);    // 软件复位
    delay_us(1);
    unsigned int tmp = base->UCR2;
    tmp |= (1 << 14);            // 忽略 RTS
    tmp &= ~(1 << 8);            // 关闭奇偶校验
    tmp &= ~(1 << 6);            // 1 位停止位
    tmp |= (1 << 5);             // 8 位数据位
    tmp |= (1 << 2);             // 使能发送
    tmp |= (1 << 1);             // 使能接收
    base->UCR2 = tmp;

    base->UCR3 |= (1 << 2);     // RXDMUXSEL 置 1

    tmp = base->UFCR;
    tmp &= ~(0x7 << 7);
    tmp |= (0x5 << 7);           // 1 分频
    base->UFCR = tmp;

    base->UBIR = 999;
    base->UBMR = 43404;

    base->UCR1 |= (1 << 0);     // 使能 UART
}

void uart_send_byte(UART_Type * base, unsigned char data)
{
    while(!(base->USR2 & (1 << 3)));
    base->UTXD = data;
}

unsigned char uart_recv_byte(UART_Type * base)
{
    unsigned char data = 0;
    while(!(base->USR2 & (1 << 0)));
    data = base->URXD & 0xff;
    return data;
}

四、格式化输入输出(printf/scanf 移植)

移植原理

printfscanf 函数映射到 UART1 上,之后用串口调试助手等软件就可以非常方便地和开发板通信。

  • 配套资料中的 stdio 文件夹里的文件是要移植的源码(从 uboot 移植)
  • stdio 中并没有实现完整版的格式化函数,比如 printf 不支持浮点数,但基本够用

移植步骤(修改项)

  1. 在 uart.c 中添加函数void raise(int n); 函数体为空即可,否则会报错

  2. 汇编文件扩展名改大写start.sstart.S

    • S 会先做预处理,再进行编译;s 则省略预处理步骤
  3. Makefile 修改:由于增加了新的源码和头文件,Makefile 需要更新

  4. 所有引用 .s 的地方改成 .S

  5. 编译选项增加-Wa,-mimplicit-it=thumb --nostdlib,否则报错 thumb conditional instruction should be in IT block

  6. 增加源程序路径和头文件路径,并添加链接目录

如果报错缺少 putc 和 getc,自己写:

复制代码
void putc(unsigned char data)
{
    while(!(UART1->USR2 & (1 << 3)));
    UART1->UTXD = data;
}

unsigned char getc(void)
{
    unsigned char data = 0;
    while(!(UART1->USR2 & (1 << 0)));
    data = UART1->URXD & 0xff;
    return data;
}

五、Makefile

复制代码
target = uart

cross_compiler = arm-linux-gnueabihf-

cc = $(cross_compiler)gcc
ld = $(cross_compiler)ld
objcopy = $(cross_compiler)objcopy
objdump = $(cross_compiler)objdump

incdirs = bsp sdk stdio/include
srcdirs = bsp project stdio/lib

include = $(patsubst %, -I%, $(incdirs))

cfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.c))
sfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.S))

cfilenodir = $(notdir $(cfiles))
sfilenodir = $(notdir $(sfiles))

cobjs = $(patsubst %, obj/%, $(cfilenodir:.c=.o))
sobjs = $(patsubst %, obj/%, $(sfilenodir:.S=.o))

objs = $(cobjs) $(sobjs)
VPATH = $(srcdirs)

gcc_lib = -lgcc -L /home/linux/tools/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4

$(target).bin : $(objs)
	$(ld) -Timx6ull.lds -o$(target).elf $^ $(gcc_lib)
	$(objcopy) -O binary -S -g $(target).elf $@
	$(objdump) -D $(target).elf > $(target).dis

$(sobjs) : obj/%.o : %.S
	@mkdir -p obj
	$(cc) -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c $(include) -o $@ $<

$(cobjs) : obj/%.o : %.c
	@mkdir -p obj
	$(cc) -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c $(include) -o $@ $<

.PHONY : clean
clean:
	rm -rf $(objs) $(target).elf $(target).bin $(target).dis

load:
	./imxdownload ./$(target).bin /dev/sdb

六、main.c 实战:串口命令控制 LED 和蜂鸣器

复制代码
#include "MCIMX6Y2.h"
#include "beep.h"
#include "clk.h"
#include "core_ca7.h"
#include "epit.h"
#include "fsl_iomuxc.h"
#include "gpt.h"
#include "irq.h"
#include "key.h"
#include "led.h"
#include "stdio.h"
#include "uart.h"

int main(void)
{
    char data[50] = {0};

    clk_init();
    led_init();
    beep_init();
    gpt1_init();
    uart_init(UART1);

    while (1)
    {
        printf("input ur command:");
        scanf("%s", data);

        if (strncmp(data, "ledon", 5) == 0)
        {
            led_on();
            printf("open led already\r\n");
        }
        else if (strncmp(data, "ledoff", 6) == 0)
        {
            led_off();
            printf("close led already\r\n");
        }
        else if (strncmp(data, "beepon", 6) == 0)
        {
            beep_on();
            printf("open beep already\r\n");
        }
        else if (strncmp(data, "beepoff", 7) == 0)
        {
            beep_off();
            printf("close beep already\r\n");
        }
        else
        {
            printf("invalid command\r\n");
        }
    }

    return 0;
}

效果 :下载后通过串口调试助手输入命令(ledon/ledoff/beepon/beepoff),开发板即可响应控制。


七、关键寄存器速查表

寄存器 功能
CCM_CSCDR1 UART_CLK_SEL 时钟源选择(0=pll3_80m,1=osc_clk)
CCM_CSCDR1 UART_CLK_PODF 时钟分频(0~63 → 1~64分频)
UCR1 bit0 UARTEN UART 使能
UCR1 bit14 ADBR 自动波特率检测使能
UCR2 bit0 SRST 软件复位
UCR2 bit1 RXEN 接收使能
UCR2 bit2 TXEN 发送使能
UCR2 bit5 WS 数据位(0=7位,1=8位)
UCR2 bit6 STOP 停止位(0=1位,1=2位)
UCR2 bit7 PROE 奇偶校验模式(0=偶,1=奇)
UCR2 bit8 PREN 奇偶校验使能
UCR2 bit14 IRTS RTS忽略位
UCR3 bit2 RXDMUXSEL 必须始终为1
UCR3 bit3 TXDC 发送完成标志
UCR3 bit0 RDR 数据接收标志
UFCR bit9~bit7 UART 自身分频器
UBIR - 波特率计算分子
UBMR - 波特率计算分母
USR2 bit3 TXDC 发送完成标志(写函数轮询)
USR2 bit0 RDR 数据接收标志(读函数轮询)
UTXD - 发送数据寄存器
URXD - 接收数据寄存器(只取低8位)
相关推荐
observe1012 小时前
ARM学习之时钟,EPIT,GPT
arm开发·学习
誰能久伴不乏2 小时前
从数字世界到物理引擎:用 PWM 撕开 0 和 1 的结界
linux·arm开发·c++·qt
mcupro2 小时前
TQTT_KU5P开发板教程---在Windows下XCKU5P+AD9361测试
嵌入式硬件·fpga开发·模块测试
果果燕2 小时前
ARM嵌入式学习(一)---ARM基础概念学习
arm开发·学习
青桔柠薯片2 小时前
IMX6ULL 时钟、定时器与中断系统:从晶体振荡器到GIC的硬件机制分析
嵌入式硬件·imx6ull
Zevalin爱灰灰2 小时前
零基础入门学用物联网(ESP8266) 第二部分 MQTT基础篇(五)
单片机·物联网·mqtt·嵌入式·esp8266
欢乐熊嵌入式编程2 小时前
做一个智能温湿度监控系统(含显示与数据上传)
单片机·温湿度·嵌入式学习·智能温湿度监控系统
辰哥单片机设计2 小时前
STM32智能家用垃圾桶(升级版)
stm32·单片机·嵌入式硬件
qq_150841993 小时前
浅析光模块固件之PC-MCU-Driver构架下的二级I2C从机的透传编程(再续)
单片机·嵌入式硬件