I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向

  • 一、IMX6ULL 串口资源介绍
  • 1. 串口资源简介
  • 2. UART通讯格式
  • 3. UART结构框图
  • 4. 开发板原理图
  • 5. UART电平标准
  • 二、寄存器
  • 1. UARTx_URXD
  • 2. UARTx_UTXD
  • 3. UARTx_UCRn (1~4)
  • UARTx_UCR1
  • UARTx_UCR2
  • UARTx_UCR3
  • UARTx_UCR4
  • UARTx_UFCR
  • 4. UARTx_USRn
  • UARTx_USR2
  • 三、代码实现
  • 1. 引脚
  • 2. 串口IO初始化
  • 3. 串口初始化
  • 4. 其它一些函数
  • 5. 时钟设置
  • 6. 运行效果:
  • 四、设置波特率的函数
  • 五、printf 重定向
  • 1. 复制库文件
  • 2. Makefile 修改
  • (1)路径设置
  • (2)要使用 printf ,需要实现 putc 、 getc 函数:
  • (3)其它 Makefile 修改项
  • 3. main
  • 4. 运行效果

一、IMX6ULL 串口资源介绍

在《IMX6U参考手册》P3561

1. 串口资源简介

UART(Universal Asynchronous Receiver/Trasmitter),通用异步串行收发器。它可以通过电平转换器和 RS - 232 电缆或外部电路提供与外部设备的串行通信能力。
外部电路可以将红外信号转换为电信号(用于接收),或将电信号转换为驱动红外 LED 的信号(用于传输),以提供低速 IrDA 兼容性。

UART 支持以下几种编码格式:

  • NRZ(非归零)编码格式
  • RS485 兼容的 9 位数据格式
  • IrDA 兼容的红外低速数据率(SIR)格式

2. UART通讯格式

UART通讯一般使用三根线:TxD发送、RxD接收、GND接地。

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_嵌入式硬件


其中各位含义解释:

这篇文档主要介绍了UART(通用异步收发器)数据传输中的几个关键概念:

  1. 空闲位
  • 数据线在空闲状态时为逻辑“1”,即高电平,表示没有数据线空闲,没有数据传输。
  1. 起始位
  • 当要传输数据时,先传输一个逻辑“0”,即将数据线拉低,表示开始数据传输。
  1. 数据位
  • 数据位是实际要传输的数据,数据位数可选择5~8位,通常按照字节(8位)传输数据,低位在前,高位最后传输。
  1. 奇偶校验位
  • 这是对数据中“1”的位数进行奇偶校验用的,可以选择不使用奇偶校验功能。
  1. 停止位
  • 数据传输完成标志位,停止位的位数可以选择1位、1.5位或2位高电平,通常选择1位停止位。
  1. 波特率
  • 波特率是UART数据传输的速率,即每秒传输的数据位数,常见选择有9600、19200、115200等。

3. UART结构框图

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_嵌入式Linux_02

4. 开发板原理图

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_嵌入式Linux_03

5. UART电平标准

UART一般的接口电平有TTL和RS-232两种。

  1. TTL电平标准
  • 一般开发板上都有TXD和RXD这样的引脚。
  • 这些引脚低电平表示逻辑0,高电平表示逻辑1,这就是TTL电平。
  1. RS-232电平标准
  • RS-232采用差分线。
  • -3 ~ -15V表示逻辑1,+3 ~ +15V表示逻辑0。

二、寄存器

1. UARTx_URXD

低8位保存串口接收到的数据,其他位比较少用。

2. UARTx_UTXD

低8位放要发送的数据。

3. UARTx_UCRn (1~4)

串口控制寄存器。

UARTx_UCR1

  • bit0: UART的使能位;
  • bit14: 自动检测波特率,设置为0不使用;

UARTx_UCR2

  • bit0:软复位,0的时候复位,1时候不复位。
  • bit1:接收使能
  • bit2:发送使能
  • bit5:设置数据长度,0表示7位数据位,1表示8位数据位
  • bit6:停止位,0:1位停止位, 1:2位停止位
  • bit7:奇偶校验位,为0是偶校验,1是奇校验
  • bit8:校验使能

UARTx_UCR3

  • bit2:设置为1,工作在MUXED 模式。

UARTx_UCR4

忽略

UARTx_UFCR

  • bit7~9:设置分频值。

UART的时钟源由 CSCDR1的 UART_CLK_SEL 位设置,可以是OSC24MHz(UART_CLK_SEL=1),或 PLL3/6 分频(UART_CLK_SEL=0):

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_嵌入式Linux_04


即 480MHz/6 = 80MHz,根据这个值设置分频。

本文使用PLL/6作为时钟源。另外 CSCDR1[UART_CLK_PODF] 分频值设置1分频。

UARTx_UFCR设置的是80MHz时钟源的分频值,得到的值 RefFreq,再通过UART_UFCR、UART_UBIR、UART_UBMR三个寄存器确定串口波特率,计算公式如下:

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_嵌入式硬件_05

4. UARTx_USRn

UARTx_USR2

  • bit0:为1时,表示接收到数据。
  • bit3:1时表示数据发送完成。

三、代码实现

1. 引脚

UART1_TXD 使用的 IO 为UART1_TX_DATA, UART1_RXD 使用的IO为 UART1_RXD_DATA。

设置其复用功能:

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_串口_06


I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_嵌入式硬件_07


MINI 开发板通过 USB TTL 与电脑相连。

2. 串口IO初始化

/**
* @brief UART的IO初始化
*/
void uart_io_init(void){
    // 复用为UART1_TX
    IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
    // 设置电气属性
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);

    // 复用为UART1_RX
    IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
    // 设置电气属性
    IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
}

3. 串口初始化

/**
* @brief 初始化UART1
*/
void uart_init(UART_Type *base){
    // 1、初始化IO
    uart_io_init();

    // 2、初始化UART
    uart_disable(base);
    uart_reset(base);

    // 3、配置UART
    base->UCR1 = 0;

    // 配置数据位、停止位和校验位
    base->UCR2 = 0;
    // 1接收使能 | 2发送使能 | 5数据位8位 | 14忽略RTS脚
    base->UCR2 |= (1 << 1) | (1 << 2) | (1 << 5) | (1 << 14);

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

    // 配置波特率 115200
    base->UFCR = 5 << 7;
    base->UBIR = 71;
    base->UBMR = 3124;

    // 4、使能UART
    uart_enable(base);
}

4. 其它一些函数

/**
* @brief 关闭UART1
*/
void uart_disable(UART_Type *base) {
    base->UCR1 &= ~ (1 << 0);
}

/**
* @brief 打开UART1
*/
void uart_enable(UART_Type *base) {
    base->UCR1 |= 1 << 0;
}

/**
* @brief 复位
*/
void uart_reset(UART_Type *base) {
    base->UCR2 &= ~ (1 << 0);
    while((base->UCR2 & 0x1) == 0); /* 等待复位完成 					*/
}

/**
* @brief 发送一个字符
*/
void uart_write(UART_Type *base, unsigned char c){
    // 等待发送缓冲区为空
    while(((UART1->USR2 >> 3) &0X01) == 0);/* 等待上一次发送完成 */
    UART1->UTXD = c & 0XFF; 				/* 发送数据 */
}

/**
* @brief 发送字符串
*/
void uart_write_str(UART_Type *base, const char *str){
    while (*str) {
        uart_write(base, *str++);
    }
}

/**
* @brief 读取一个字符
*/
unsigned char uart_read(const UART_Type *base){
    // 等待接收缓冲区满
    while((UART1->USR2 & 0x1) == 0);/* 等待接收完成 */
    return base->URXD;
}

/**
* @brief 读取字符串
*/
void uart_read_str(const UART_Type *base, char *str, unsigned int len){
    unsigned int i = 0;
    while (i < len) {
        str[i] = uart_read(base);
        i++;
    }
}

5. 时钟设置

/**
* @brief 初始化系统时钟
*/
void imx6u_clkinit(void) {
    // 如果PLL1_SW_CLK_SEL 当前不是 step_clk
    if (((CCM->CCSR >> 2) & 0x1)== 0) {
        // 设置备用时钟step_sel=osc_clk(24MHz)
        CCM->CCSR &= ~(1 << 8);
        // 设置pll1_sw_clk为step_clk
        CCM->CCSR |= (1 << 2);
    }
    // 设置PLL1=1056MHz
    CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0x7f); // 528*2=1056MHz, 1056*2/24=88
    // 设置二分频 arm_clk=1056/2=528MHz
    CCM->CACRR = 1;
    // 将pll1_sw_clk切换为pll1_main_clk,即1056MHz
    CCM->CCSR &= ~(1 << 2);

    // 配置PLL2的PFD0~PFD3
    imx6u_clk_pll2_pfd_init();
    // 配置PLL3的PFD0~PFD3
    imx6u_clk_pll3_pfd_init();
    // 设置AHB时钟最大为132MHz
    imx6u_ahb_clk_init();
    // 设置IPG_CLK_ROOT=66MHz
    imx6u_ipg_clk_init();
    // 设置PERCLK_CLK_ROOT=66MHz
    imx6u_perclk_init();

    // PLL3时钟设置,串口使用
    CCM->CSCDR1 &= ~(1 << 6); // PERCLK=PLL3_SW_CLK=PLL3=480MHz, UART_CLK_ROOT=80MHz
    // 1分频
    CCM->CSCDR1 &= ~0x3F;
}

6. 运行效果:

在电脑打开串口调试工具,开发板上电后,串口工具连接相应的串口,按开发板的RESET键复位。 这时串口调试工具窗口打印字符: please input char: ,输入一个字符,开发板会回显输入的字符。

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_串口_08

四、设置波特率的函数

从 正点原子的例程里直接拷贝从NXP移植的 uart_setbaudrate 函数。
原型如下:

/*
 * @description 		: 波特率计算公式,
 *    			  	  	  可以用此函数计算出指定串口对应的UFCR,
 * 				          UBIR和UBMR这三个寄存器的值
 * @param - base		: 要计算的串口。
 * @param - baudrate	: 要使用的波特率。
 * @param - srcclock_hz	:串口时钟源频率,单位Hz
 * @return		: 无
 */
void uart_setbaudrate(UART_Type *base, unsigned int baudrate, unsigned int srcclock_hz);

使用方法:

// 配置波特率 115200
//    base->UFCR = 5 << 7;
//    base->UBIR = 71;
//    base->UBMR = 3124;
    uart_setbaudrate(base, 115200, 80000000);

为了使用这个函数,需要引用数学库,修改 Makefile

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

然后加到 $(LD) 指令:

# 生成二进制文件
$(TARGET).bin : $(OBJS) | obj
	$(LD) -T$(LINKER_SCRIPT) -o obj/$(TARGET).elf $^ $(LIBPATH)
	$(OBJCOPY) -O binary -S obj/$(TARGET).elf $@
	$(OBJDUMP) -D -m arm obj/$(TARGET).elf > obj/$(TARGET).dis

五、printf 重定向

1. 复制库文件

从 正点原子 教程源码中,复制 stdio/ 目录

2. Makefile 修改

(1)路径设置

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_嵌入式硬件_09

(2)要使用 printf ,需要实现 putc 、 getc 函数:

/*
 * @description : 发送一个字符
 * @param - c	: 要发送的字符
 * @return		: 无
 */
void putc(unsigned char c)
{
    while(((UART1->USR2 >> 3) &0X01) == 0);/* 等待上一次发送完成 */
    UART1->UTXD = c & 0XFF; 				/* 发送数据 */
}


/*
 * @description : 接收一个字符
 * @param 		: 无
 * @return		: 接收到的字符
 */
unsigned char getc(void)
{
    while((UART1->USR2 & 0x1) == 0);/* 等待接收完成 */
    return UART1->URXD;				/* 返回接收到的数据 */
}

(3)其它 Makefile 修改项

由于定义的函数与内置的putc会冲突, 在 Makefile 编译命令还需要修改:

$(SOBJS) : obj/%.o : %.S
	$(CC) -Wall -nostdlib -fno-builtin -c -O2  $(INCLUDE) -o $@ $<

$(COBJS) : obj/%.o : %.c
	$(CC) -Wall -nostdlib -fno-builtin -c -O2  $(INCLUDE) -o $@ $<

这时编译还会报:

/tmp/ccDDezEw.s: Assembler messages:
/tmp/ccDDezEw.s:42: Error: thumb conditional instruction should be in IT block -- `addcs r5,r5,#65536'

这个错误是因为在汇编代码中使用了 Thumb 条件指令 addcs,但它没有包含在 IT (If-Then) 块中。
处理方式是在Makefile 编译命令再加上 -Wa, -mimplicit-it=thumb,用于告诉汇编器在生成 Thumb 指令时自动插入 IT (If-Then) 块。

# 编译C文件
$(COBJS) : obj/%.o : %.c | obj
	$(CC) -Wall -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c -O2  $(INCLUDE) -o $@ $<

3. main

#include "inc/main.h"
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "bsp_int.h"
#include "bsp_epit.h"
#include "bsp_keyfilter.h"
#include "bsp_uart.h"
#include "stdio.h"

int main(void)
{
    unsigned char state = OFF;
    int a , b;
    bsp_int_init();  /* 初始化中断 */
    imx6u_clkinit();    /* 初始化系统时钟 */
    clk_enable();   /* 使能外设时钟 */
    led_init();     /* 初始化LED */
    beep_init();    /* 初始化蜂鸣器 */
    gpt1_delay_init();  /* 初始化GPT1高精度延时 */
    uart_init(UART1);     /* 初始化UART1的IO */

    while(1) {
        printf("input 2 numbers:");
        scanf("%d %d", &a, &b);					 		/* 输入两个整数 */
        printf("\r\n%d + %d = %d\r\n\r\n", a, b, a+b);	/* 输出两个数相加的和 */

        state = !state;
        led_switch(LED0,state);
    }
    return 0;
}

4. 运行效果

I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向_初始化_10