[IMX] 05.串口 - UART

目录

1.通信格式

2.电平标准

[3.IMX UART 模块](#3.IMX UART 模块)

[4.时钟寄存器 - CCM_CSCDR1](#4.时钟寄存器 - CCM_CSCDR1)

5.控制寄存器

5.1.UART_UCR1

5.2.UART_UCR2

5.3.UART_UCR3

6.状态寄存器

6.1.UART_USR1

6.2.UART_USR2

[7.FIFO 控制寄存器 - UART_UFCR](#7.FIFO 控制寄存器 - UART_UFCR)

8.波特率寄存器

[8.1.分母 - UART_UBIR](#8.1.分母 - UART_UBIR)

[8.2.分子 - UART_UBMR](#8.2.分子 - UART_UBMR)

9.波特率计算

[10.接收数据寄存器 - UART_URXD](#10.接收数据寄存器 - UART_URXD)

[11.发送数据寄存器 - UART_UTXD](#11.发送数据寄存器 - UART_UTXD)

12.代码实现


串口全称串行接口,也称为 COM 接口,串行接口指的是数据一个接一个的按顺序传输

串口的通信线路很简单,使用两条线即可实现双向通信,一条用于发送,一条用于接收

串口是一种常用的工业接口,通信的距离较远,但是速度相对较低,I.MX6U 自带的 UART 外设就是串口的一种

UART 全称是 UniversalAsynchronous Receiver/Trasmitter,即异步串行收发器

USART 全称是 Universal Synchronous/Asynchronous Receiver/Transmitter,即同步/异步串行收发器,相比 UART 多了一个同步的功能,在硬件上体现出来的就是多了一条时钟线

一般 USART 可以作为 UART 使用的,即不使用其同步的功能

1.通信格式

UART 作为串口的一种,其工作原理是将数据按一位接一位的进行传输,发送和接收各用一 条线,因此通过 UART 接口与外界相连最少需要三条线:TXD (发送)、RXD (接收) 和 GND(地线)

串口的通信格式如下所示:

图中各个位的含义如下:

  • 空闲位:数据线在空闲状态时为逻辑 "1" 状态(高电平),表示没有数据线空闲,没有数据传输;

  • 起始位:当要传输数据时先传输一个逻辑 "0",也就是将数据线电平拉低,表示开始数据传输;

  • 数据位:数据位就是实际要传输的数据,数据位可选择 5~8 位,一般按照字节传输数据,一个字节占 8 位,因此数据位通常为 8 位,低位在前,先传输,高位最后传输;

  • 奇偶校验位:对数据中为 "1" 的位进行奇偶校验,可以不使用奇偶校验功能;

  • 停止位:数据传输完成标志位,停止位的位数可以选择 1 位、1.5 位或 2 位的高电平,一般选择 1 位停止位;

  • 波特率:波特率是 UART 数据传输的速率,即每秒传输的数据位数,一般选择 9600、19200、115200 等;

2.电平标准

UART 接口电平有 TTL 和 RS-232 两种标准

一般开发板上都有 TXD 和 RXD 引脚,引脚的低电平表示逻辑 0,高电平表示逻辑 1,这个就是 TTL 电平

RS-232 采用差分线,-3 ~ -15V 表示逻辑 1,+3 ~ +15V 表示逻辑 0

下图所示的就是 TTL 电平的接口:

上面图片中的模块为 USB 转 TTL 模块,TTL 接口部分有 VCC、GND、RXD、TXD、 RTS 和 CTS 引脚,使用时通过杜邦线和其他模块的 TTL 接口相连即可

RS-232 电平标准需要使用 DB9 接口,I.MX6U-ALPHA 开发板上的 COM3 (UART3) 口就是 RS-232 接口:

由于现在的电脑都没有 DB9 接口了,取而代之的是 USB 接口,所以就催生出了很多 USB 转串口 TTL 芯片,比如 CH340、PL2303 等,通过这些芯片可以实现串口 TTL 转 USB

I.MX6U-ALPHA 开发板使用 CH340 芯片完成 UART1 和电脑之间的连接,只需要一条USB 线即可:

3.IMX UART 模块

I.MX6U 一共有 8 个 UART 模块,主要特性如下:

  • 兼容 TIA/EIA-232F 标准,速度最高可到 5Mbit/s;

  • 支持串行 IR 接口,兼容 IrDA,最高可到 115.2Kbit/s;

  • 支持 9 位或者多节点模式 (RS-485);

  • 1 或 2 位停止位;

  • 可编程的奇偶校验 (奇校验和偶校验);

  • 自动波特率检测 (最高支持 115.2Kbit/s);

4.时钟寄存器 - CCM_CSCDR1

UART 的时钟源由寄存器 CCM_CSCDR1 的 UART_CLK_SEL[6] 位选择:

UART_CLK_SEL 为 0 时 UART 的时钟源为 pll3_80m (80MHz)

UART_CLK_SEL 为 1 时 UART 的时钟源为 osc_clk (24M)

一般选择 pll3_80m 作为 UART 的时钟源

寄存器 CCM_CSCDR1 的 UART_CLK_PODF[5:0] 位是 UART 的时钟分频值,可设置 0~63,分别对应 1~64 分频,一般设置为 1 分频,因此最终进入 UART 的时钟频率为 80MHz

5.控制寄存器

UART_UCR1 ~ UART_UCR4 这四个寄存器用于配置 UART 模块的相关功能,如自动波特率使能、奇偶校验模式等

5.1.UART_UCR1

其中,重点关注如下几个位域:

  • ADBR[14]:自动波特率检测使能,为 0 时关闭自动波特率检测,为 1 时使能自动波特率检测;

  • UARTEN[0]:UART 使能,为 0 时关闭 UART,为 1 时使能 UART;

5.2.UART_UCR2

其中,重点关注如下几个位域:

  • IRTS[14]:为 0 时使用 RTS 引脚功能,为 1 时忽略 RTS 引脚;

  • PREN[8]:奇偶校验使能,为 0 时关闭奇偶校验,为 1 时使能奇偶校验;

  • PROE[7]:奇偶校验模式选择,开启奇偶校验后,若该位为 0 则使用偶校验,为 1 使用奇校验;

  • STOP[6]:停止位数量,为 0 表示使用 1 位停止位,为 1 表示使用 2 位停止位;

  • WS[5]:数据位长度,为 0 时选择 7 位数据位,为 1 时选择 8 位数据位;

  • TXEN[2]:发送使能位,为 0 时关闭 UART 的发送功能,为 1 时打开 UART 的发送功能;

  • RXEN[1]:接收使能位,为 0 时关闭 UART 的接收功能,为 1 时打开 UART 的接收功能;

  • SRST[0]:软件复位,向该位写 0 复位 UART,为 1 时表示复位完成,复位完成后此位会自动置 1,表示复位完成,只能向该位写 0,写 1 会被忽略;

5.3.UART_UCR3

这里只关注 RXDMUXSEL[2],该位用于设置复用输入,但是 IMX UART 使用 MUXED 模式,因此该位始终为 1

6.状态寄存器

6.1.UART_USR1

例程未使用该寄存器

6.2.UART_USR2

其中,主要关注一下位域:

  • TXDC[3]:发送完成标志位,为 1 时表明发送缓冲 TxFIFO 和移位寄存器为空,即发送完成,向 TxFIFO 写入数据此位就会自动清零;

  • RDR[0]:数据接收标志位,为 1 时表明至少接收到一个数据,从寄存器 UART_URXD 接收到数据后此位会自动清零;

7.FIFO 控制寄存器 - UART_UFCR

其中,关注 RFDIV[9:7] 位域,其用于设置时钟源的分频系数:

  • 0b000:6 分频;

  • 0b001:5 分频;

  • 0b010:4 分频;

  • 0b011:3 分频;

  • 0b100:2 分频;

  • 0b101:1 分频;

  • 0b110:7 分频;

  • 0b111:保留

这里需要注意的是,分频系数和位域的值不是一一对应的关系

8.波特率寄存器

8.1.分母 - UART_UBIR

BRM(Binary Rate Multiplier)用于生成 16x 的波特率

UART_UBIR 寄存器的 INC[15:0] 位域存储计算波特率的分母:

8.2.分子 - UART_UBMR

UART_UBMR 寄存器的 MOD[15:0] 存储计算波特率的分子:

9.波特率计算

波特率的计算公式如下:

  • Ref Freq:经过分频进入 UART 的时钟频率;

  • UBMR:寄存器 UART_UBMR 中的值,分子;

  • UBIR:寄存器 UART_UBIR 中的值,分母;

通过 UART_UFCR 的 RFDIV 位、UART_UBMR 和 UART_UBIR 寄存器中的值即可计算波特率,例如,要设置 UART 的波特率为 115200,则可以设置 RFDIV 为 0x5 (0b101),即 1 分频,因此 RefFreq = 80MHz,设置 UBIR = 71,UBMR = 3124,根据公式可得:

10.接收数据寄存器 - UART_URXD

其中关注 RX_DATA[7:0] 位域,其中保存接收到的数据,读取该位域即可获得接收的数据,在 7-bit 模式下,最高位强制为 0,在 8-bit 模式下,全部的 8-bit 均为有效数据位

11.发送数据寄存器 - UART_UTXD

TX_DATA[7:0] 保存将要发送的数据,在 7-bit 模式下忽略最高位,在 8-bit 模式下,全部的 8-bit 均为有效位,数据发送时从最低位开始发送,仅当 UART_USR1.TRDY 为 1 时才允许向 TX_DATA[7:0] 写入数据,防止产生冲突

12.代码实现

cpp 复制代码
// uart.h

#ifndef _UART_H
#define _UART_H

#include "nxp.h"

// 函数声明
void uart_init(void);
void uart_io_init(void);
void uart_disable(UART_Type *base);
void uart_enable(UART_Type *base);
void uart_soft_reset(UART_Type *base);
void uart_set_baudrate(UART_Type *base, uint32_t baudrate, uint32_t clk);

// 注意,在 stdio.h SDK 中会使用下面这三个接口
// 因此两边的函数定义一定要保持一致
void putc(unsigned char c);
void puts(char *str);
unsigned char getc(void);

// 下面这个函数用于防止编译器报错
void raise(int sig_nr);

#endif /* _UART_H */
cpp 复制代码
// uart.c

#include "uart.h"

/* 初始化串口 UART1 使用的 IO 引脚 */
void uart_io_init(void)
{
    /* 初始化 IO 复用
     * UART1_RX_DATA 引脚功能选择为 UART1_RXD(配置 IOMUXC_SW_MUX_CTL_PAD_UART1_RX_DATA 寄存器)
     * UART1_TX_DATA 引脚功能选择为 UART1_TXD(配置 IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA 寄存器)
     */
    IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
    IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);

    /* 配置 UART1_RXD 和 UART1_TXD 引脚的电气特性
     * 配置 IOMUXC_SW_PAD_CTL_PAD_UART1_TX_DATA 寄存器
     * 配置 IOMUXC_SW_PAD_CTL_PAD_UART1_RX_DATA 寄存器
     * 这两个引脚的电气特性一样,相关位域的配置如下:
     * HYS[16]:使能迟滞比较器,0b0,禁用
     * PUS[15:14]:上拉/下拉电阻值,0b00,下拉
     * PUE[13]:引脚状态保持,0b0,不保持引脚电平值
     * PKE[12]:状态保持器使能,0b1,使能
     * ODE[11]:开漏输出使能,0b0,禁用
     * SPEED[7:6]:IO 速率,0b10,100MHz
     * DSE[5:3]:IO 驱动能力,0b110,R0/6
     * SRE[0]:压摆率,0b0,低压摆率
     * 因此,最终要写入寄存器的值为:0x000010B0
     */
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x000010B0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_RX, 0x000010B0);
}

/* 启用指定的 UART 模块 */
void uart_enable(UART_Type *base)
{
    /* 向 UART_UCR1 寄存器的 UARTEN[0] 位写 1 使能 UART 模块 */
    base->UCR1 |= (0b1 << 0);
}

/* 禁用指定的 UART 模块 */
void uart_disable(UART_Type *base)
{
    /* 向 UART_UCR1 寄存器的 UARTEN[0] 位写 0 禁用 UART 模块 */
    base->UCR1 &= ~(0b1 << 0);
}

/* UART 复位 */
void uart_soft_reset(UART_Type *base)
{
    /* 向 UART_UCR2 寄存器的 SRST[0] 位写 0 发送 UART 模块复位请求
     * 复位完成后,SRST[0] 会自动恢复为 1
     */
    base->UCR2 &= ~(0b1 << 0);

    /* 等待复位完成 */
    while (!(base->UCR2 & 0x00000001)) {}
}

/* 设置 UART 的波特率 */
void uart_set_baudrate(UART_Type *base, uint32_t baudrate, uint32_t clk)
{
    uint32_t numerator = 0; /* 分子 */
    uint32_t denominator = 0; /* 分母 */
    uint32_t divisor = 0;
    uint32_t refFreqDiv = 0;
    uint32_t divider = 0;
    uint64_t baudDiff = 0;
    uint64_t tempNumerator = 0;
    uint64_t tempDenominator = 0;

    /* 计算最大的除数 */
    numerator = clk;
    denominator = baudrate << 4;
    divisor = 1;

    while (denominator != 0)
    {
        divisor = denominator;
        denominator = numerator % denominator;
        numerator = divisor;
    }

    numerator = clk / divisor;
    denominator = (baudrate << 4) / divisor;

    /* numerator ranges from 1 ~ 7 * 64k */
    /* denominator ranges from 1 ~ 64k */
    if ((numerator > (UART_UBIR_INC_MASK * 7)) || (denominator > UART_UBIR_INC_MASK))
    {
        uint32_t m = (numerator - 1) / (UART_UBIR_INC_MASK * 7) + 1;
        uint32_t n = (denominator - 1) / UART_UBIR_INC_MASK + 1;
        uint32_t max = m > n ? m : n;
        numerator /= max;
        denominator /= max;
        if (0 == numerator)
        {
            numerator = 1;
        }
        if (0 == denominator)
        {
            denominator = 1;
        }
    }
    divider = (numerator - 1) / UART_UBIR_INC_MASK + 1;

    switch (divider)
    {
        case 1:
            refFreqDiv = 0x05;
            break;
        case 2:
            refFreqDiv = 0x04;
            break;
        case 3:
            refFreqDiv = 0x03;
            break;
        case 4:
            refFreqDiv = 0x02;
            break;
        case 5:
            refFreqDiv = 0x01;
            break;
        case 6:
            refFreqDiv = 0x00;
            break;
        case 7:
            refFreqDiv = 0x06;
            break;
        default:
            refFreqDiv = 0x05;
            break;
    }
    /* Compare the difference between baudRate_Bps and calculated baud rate.
     * Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1)).
     * baudDiff = (srcClock_Hz/divider)/( 16 * ((numerator / divider)/ denominator).
     */
    tempNumerator = clk;
    tempDenominator = (numerator << 4);
    divisor = 1;
    /* get the approximately maximum divisor */
    while (tempDenominator != 0)
    {
        divisor = tempDenominator;
        tempDenominator = tempNumerator % tempDenominator;
        tempNumerator = divisor;
    }
    tempNumerator = clk / divisor;
    tempDenominator = (numerator << 4) / divisor;
    baudDiff = (tempNumerator * denominator) / tempDenominator;
    baudDiff = (baudDiff >= baudrate) ? (baudDiff - baudrate) : (baudrate - baudDiff);

    if (baudDiff < (baudrate / 100) * 3)
    {
        base->UFCR &= ~UART_UFCR_RFDIV_MASK;
        base->UFCR |= UART_UFCR_RFDIV(refFreqDiv);
        base->UBIR = UART_UBIR_INC(denominator - 1); // 要先写 UBIR 寄存器,然后再写 UBMR 寄存器
        base->UBMR = UART_UBMR_MOD(numerator / divider - 1);
    }
}

/* UART1 初始化,波特率 115200 */
void uart_init(void)
{
    /* 初始化串口 IO 引脚 */
    uart_io_init();

    /* 初始化 UART1 */
    uart_disable(UART1); /* 先禁用 UART1 */
    uart_soft_reset(UART1); /* 复位 UART1 */

    /* 清除 UCR1 寄存器 */
    UART1->UCR1 = 0;

    /* 关闭自动波特率检测:UART1_UCR1.ADBR[14] 置 0 */
    UART1->UCR1 &= (0b1 << 14);

    /* 配置 UART:UART_UCR2 寄存器
     * IRTS[14]:1,忽略 RTS 引脚
     * PREN[8]: 0,关闭奇偶校验校验
     * STPB[6]: 0,1 位停止位
     * WS[5]:   1,8-bit 数据位
     * TXEN[2]: 1,使能发送
     * RXEN[1]: 1,使能接收
     */
    UART1->UCR2 |= (0b1 << 14) | (0b1 << 5) | (0b1 << 2) | (0b1 << 1);

    /* 配置 UART1_UCR3 寄存器,将 RXDMUXSEL[2] 设置为 1(该位必须始终为 1) */
    UART1->UCR3 |= (0b1 << 2);

    /* 设置波特率 */
#if 0
    /*
     * 波特率计算公式: BaudRate = RefFreq / (16 * (UBMR + 1) / (UBIR+1))
     * 如果要设置波特率为 115200,则使用如下参数:
     * RefFreq = 80MHz,寄存器 UART_UFCR 的 RFDIV[9:7] = 0b101, 表示 1 分频
     * UBMR = 3124
     * UBIR = 71
     * 因此,波特率 = 80000000 / (16 * (3124 + 1) / (71 + 1))
     *            = 80000000 / (16 * 3125 / 72)
     *            = (80000000 * 72) / (16 * 3125)
     *            = 115200
     */
    UART1->UFCR = (0b101 << 7); /* refFreq = ipg_clk / 1 = 80MHz */
    UART1->UBIR = 71;
    UART1->UBMR = 3124;
#endif

    /* 也可以通过以下函数计算并设置波特率 */
// #if 0
    uart_set_baudrate(UART1, 115200, 80000000); /* 设置波特率 */
// #endif

    /* 串口使能 */
    uart_enable(UART1);
}

/* 通过串口发送一个字符 */
void putc(unsigned char c)
{
    /* 等待上一次发送完成 */
    while (!(UART1->USR2 & 0x00000008)) {}

    /* 发送字符:将字符值(ASCII 码)写入 UTXD 寄存器 */
    UART1->UTXD = (c & 0xFF);
}

/* 发送字符串 */
void puts(char *str)
{
    char *p = str;

    while (*p)
    {
        putc(*p);

        p++;
    }
}

/* 接收一个字符 */
unsigned char getc(void)
{
    /* 等待接收完成 */
    while (!(UART1->USR2 & 0x00000001)) {}

    /* 返回接收到的字符 */
    return UART1->URXD;
}

/* 该函数仅用于防止编译器报错 */
void raise(int sig_nr) 
{

}
相关推荐
LeonDL1681 小时前
YOLOv8 在单片机上的几种部署方案
人工智能·python·单片机·嵌入式硬件·深度学习·yolo·yolov8 在单片机上的部署
LeonDL1681 小时前
YOLOv8 在单片机上部署的缺点和应对方案
python·单片机·嵌入式硬件·深度学习·yolo·yolov8在单片机上的缺点·yolov8 在单片机上的优化
尚久龙1 小时前
STM32实现RS485通讯
stm32·单片机·嵌入式硬件
嵌入式Linux,1 小时前
ESP32和STM32 就不应该放在一起比,
stm32·单片机·嵌入式硬件
硬件智障2 小时前
51单片机实现流水灯
单片机·嵌入式硬件·51单片机
暮雪倾风2 小时前
【STM32】ST-Link V2.1制作
stm32·单片机·嵌入式硬件
yuanpan2 小时前
支持python的单片机有哪些
开发语言·python·单片机
Camellia03114 小时前
嵌入式学习--江协51单片机day8
嵌入式硬件·学习·51单片机
搬砖的小码农_Sky5 小时前
FPGA:高速接口JESD204B以及FPGA实现
嵌入式硬件·fpga开发·硬件架构·硬件工程