Linux-ARM裸机(十一)-UART串口通信

news2024/11/15 11:29:34

        无论单片机开发还是嵌入式 Linux 开发,串口都是最常用到的外设。可通过串口将开发板与电脑相连,然后在电脑上通过串口调试助手来调试程序。还有很多的模块,比如蓝牙、GPS、 GPRS 等都使用的串口来与主控进行通信的,在嵌入式 Linux 中一般使用串口作为控制台。本篇记录了如何驱动 I.MX6U 的串口,并使用串口和电脑进行通信。

UART 简介

1、 UART 通信格式

        串口全称叫做串行接口,通常也叫 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。串口通信距离远,但是速度相对会低,串口是一种很常用的工业接口。 I.MX6U 自带的 UART 外设就是串口的一种,UART 全称是 Universal Asynchronous Receiver/Trasmitter,也就是异步串行收发器。既然有异步串行收发器,那肯定也有同步串行收发器,比如:STM32除了有 UART 外,还有另外一 个叫做 USART 的东西 。USART 全称是 UniversalSynchronous/Asynchronous Receiver/Transmitter,也就是同步/异步串行收发器。 相比 UART 多了一个同步的功能,在硬件上体现出来的就是多了一条时钟线。 一般 USART 是可以作为 UART使用的,也就是不使用其同步的功能。

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

上图中各位的含义如下:
空闲位:数据线在空闲状态的时候为逻辑“1”状态,也就是高电平,表示没有数据线空闲,无数据传输。
起始位:当要传输数据的时候先传输一个逻辑“0”,也就是将数据线拉低,表示开始数据传输。
数据位: 数据位就是实际要传输的数据,数据位数可选择 5~8 位,我们一般都是按照字节传输数据的,一个字节 8 位,因此数据位通常是 8 位的。低位在前,先传输,高位最后传输。
奇偶校验位: 这是对数据中“1”的位数进行奇偶校验用的,可以不使用奇偶校验功能。
停止位:数据传输完成标志位,停止位的位数可以选择 1 位、 1.5 位或 2 位高电平,一般选择 1 位停止位。
波特率:波特率就是 UART 数据传输的速率,也就是每秒传输的数据位数,一般选择 9600、19200、 115200 等。

2、UART 电平标准

之前学习STM32时候笔记有所记录,本处重复记录。

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。 RTS 和 CTS 基本不用,用的时候通过杜邦线和其他模块的 TTL 接口相连即可。

而RS-232 电平需要 DB9 接口, I.MX6U-ALPHA 开发板上的 COM3(UART3)口就是 RS-232 接
口的,如下图:

现在的电脑都没有 DB9 接口了,取而代之的是 USB 接口,所以催生出很多 USB转串口 TTL 芯片,比如 CH340、PL2303 等。通过这些芯片就可以实现串口 TTL 转 USB。I.MX6UALPHA 开发板就使用 CH340 芯片来完成 UART1 和电脑之间的连接,只需要一条USB 线即可,如下图:

I.MX6U UART 简介

UART 特性

前面介绍了 UART 接口,现在具体看一下 I.MX6U 的 UART 接口, 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)。

UART 几个重要寄存器

UART 的时钟源是由寄存器 CCM_CSCDR1 的 UART_CLK_SEL(bit6)位来选择的,当为 0时 UART 的时钟源为 pll3_80m(80MHz),为 1 时 UART 的时钟源为 osc_clk(24M),一般选择 pll3_80m 作为 UART 的时钟源。寄存器CCM_CSCDR1 的 UART_CLK_PODF(bit5:0)位是 UART 的时钟分频值,可设置 0~63,分别对应 1~64 分频,一般设置为 1 分频,则最终进入 UART 的时钟为 80MHz。

6ULL的UARTx_URXD寄存器保存着串口接收到的数据,要获取串口接收到的数据,读取此寄存器的低8位即可。UARTx_UTXD是串口发送数据寄存器,若需要通过串口发送数据,只需要将数据写入到此寄存器即可。

UART 的控制寄存器 1,即UARTx_UCR1(x=1~8),寄存器 UARTx_UCR1 中用到的重要位如下:
ADBR(bit14):自动波特率检测使能位,为 0 的时候关闭自动波特率检测,为 1 的时候使能自动波特率检测。
UARTEN(bit0): UART 使能位,为 0 时关闭 UART,为 1 时使能 UART。

此寄存器的结构如下图:

UART 的控制寄存器 2: UARTx_UCR2。寄存器 UARTx_UCR2 用到的重要位如下:

IRTS(bit14):为 0 的时候使用 RTS 引脚功能,为 1 的时候忽略 RTS 引脚。
PREN(bit8):奇偶校验使能位,为 0 的时候关闭奇偶校验,为 1 的时候使能奇偶校验。
PROE(bit7):奇偶校验模式选择位,开启奇偶校验以后此位如果为 0 的话就使用偶校验,此位为 1 的话就使能奇校验。
STOP(bit6):停止位数量,为 0 的话 1 位停止位,为 1 的话 2 位停止位。
WS(bit5):数据位长度,为 0 的时候选择 7 位数据位,为 1 的时候选择 8 位数据位。
TXEN(bit2):发送使能位,为 0 时关闭 UART 的发送功能,为 1 时打开 UART的发送功能。
RXEN(bit1):接收使能位,为 0 时关闭 UART 的接收功能,为 1 时打开 UART的接收功能。
SRST(bit0):软件复位,为 0 时软件复位 UART,为 1 时表示复位完成。复位完成以后此位会自动置 1,表示复位完成。此位只能写 0,写 1 会被忽略掉。

此寄存器结构如图下图:

UARTx_UCR3 寄存器,本篇的实验只用到了寄存器 UARTx_UCR3 中的位 RXDMUXSEL(bit2),这个位应该始终为 1,此寄存器结构如下图:

寄存器 UARTx_USR2,是 UART 的状态寄存器 2。寄存器 UARTx_USR2 用到的重要位如下:

TXDC(bit3):发送完成标志位,为 1 的时候表明发送缓冲(TxFIFO)和移位寄存器为空,也就是发送数据完成,向 TxFIFO 写入数据后此就会自动清零。
RDR(bit0):数据接收标志位,为 1 时表明至少接收到一个数据,可以从寄存器UARTx_URXD 寄存器中读取数据了,接收到数据以后此位会自动清零。

此寄存器结构如下图:

通过时钟树图可看出:UART的时钟源 = PLL3/6 = 480/6 = 80MHz。CSCDR1寄存器的UART_CLK_SEL位设置UART的时钟源,为0的时候,UART时钟源为80MHz,为1的时候UART时钟源为24MHz晶振。CSCDR1寄存器的UART_CLK_PODF位控制分频,一般设置为1分频。此处分频完之后,80MHz的时钟进入UART可再通过寄 存 器UARTx_UFCR的RFDIV(bit9:7)位来设置进一步分频。如下:

寄 存 器UARTx_UFCR 中要用到的是位 RFDIV(bit9:7),用来设置时钟分频值,设置如下表:

寄 存 器 UARTx_UFCR 、 UARTx_UBIR 和 UARTx_UBMR,通过这三个寄存器决定 UART 的波特率,波特率的计算公式如下:

  • Ref Freq:经过分频以后进入 UART 的最终时钟频率,分频值由寄存器UARTx_UFCR设置。
  • UBMR:寄存器 UARTx_UBMR 中的值。
  • UBIR:寄存器 UARTx_UBIR 中的值。

通过 UARTx_UFCR 的 RFDIV 位、 UARTx_UBMR 和 UARTx_UBIR 三者的配合即可得到想要的波特率。比如要设置 UART 波特率为 115200,那么可以设置 RFDIV 为5(0b101),也就是 1 分频,因此 Ref Freq=80MHz。设置 UBIR=71, UBMR=3124,根据上面的公式可以得到:

最后介绍一下寄存器 UARTx_URXD 和 UARTx_UTXD,这两个寄存器分别为 UART 的接收和发送数据寄存器,这两个寄存器的 低八位为接到的和要发送的数据。读取寄存器UARTx_URXD 即可获取接收到的数据,若要通过 UART 发送数据,直接将数据写入寄存器 UARTx_UTXD 即可。

UART1 基本数据收发实验

配置步骤如下:

本篇使用 I.MX6U 的 UART1 完成开发板与电脑串口调试助手间串口通信, UART1 具体配置步骤如下:

  1. 设置 UART1 的时钟源:设置 UART 的时钟源为 pll3_80m,设置寄存器 CCM_CSCDR1 的 UART_CLK_SEL 位为 0 即可。
  2. 初始化 UART1:初始化 UART1 所使用 IO,设置 UART1 的寄存器 UART1_UCR1 ~ UART1_UCR3,设置内容包括:波特率,奇偶校验、停止位、数据位等等。
  3. 使能 UART1:UART1 初始化完成以后就可以使能 UART1 了,设置寄存器 UART1_UCR1 的位 UARTEN为 1。
  4. 编写 UART1 数据收发函数:编写两个函数用于 UART1 的数据收发操作

硬件原理分析

I.MX6U-ALPHA 开发板串口 1 原理图:

实验之前需要用 USB 串口线将串口 1 和电脑连接起来,还需要设置 JP5 跳线帽,将串口 1 的 RXD、 TXD 两个引脚分别与 P116、 P117 连接一起,串口 1 硬件连接设置图如下:

实验程序编写

在 bsp 文件夹下创建“uart”文件夹,然后在 bsp/uart 中新建 bsp_uart.c 和 bsp_uart.h 两个文件。

bsp_uart.h:

全部为函数声明,具体的函数实现在bsp_uart.c文件中。

#ifndef _BSP_UART_H
#define _BSP_UART_H
#include "imx6ul.h"

/* 函数声明 */
void uart_init(void);
void uart_io_init(void);
void uart_disable(UART_Type *base);
void uart_enable(UART_Type *base);
void uart_softreset(UART_Type *base);
void uart_setbaudrate(UART_Type *base,unsigned int baudrate,unsigned int srcclock_hz);
void putc(unsigned char c);
void puts(char *str);
unsigned char getc(void);
void raise(int sig_nr);

#endif

bsp_uart.c:

文件 bsp_uart.c 中共有 10 个函数。

第一个函数是 uart_init:这个函数是 UART1 初始化函数,用于初始化 UART1 相关的 IO、并且设置 UART1的波特率、字长、停止位和校验模式等,初始化完成后就使能 UART1。

第二个函数是uart_io_init:用于初始化 UART1 所使用的 IO。

第三个函数是 uart_setbaudrate:这个函数是从NXP 官方的 SDK 包里面移植过来的,用于设置波特率。我们只需将要设置的波特率告诉此函数,此函数就会使用逐次逼近方式来计算出寄存器 UART1_UFCR 的 FRDIV 位、寄存器UART1_UBIR 和寄存器 UART1_UBMR 这三个的值。

第四和第五这两个函数为 uart_disable 和uart_enable,分别是使能和关闭 UART1。

第六个函数是 uart_softreset:用于软件复位指定的 UART。

第七个函数是putc:用于通过UART1发送一个字节的数据。

第八个函数是 puts:用于通过UART1发送一串数据。

第九个函数是 getc:用于通过 UART1 获取一个字节的数据。

最后一个函数是raise:这是一个空函数,防止编译器报错。

#include "bsp_uart.h"

//初始化串口 1,波特率为 115200
void uart_init(void)
{
    /* 1、初始化串口 IO */
    uart_io_init();

    /* 2、初始化 UART1 */
    uart_disable(UART1); /* 先关闭 UART1 */
    uart_softreset(UART1); /* 软件复位 UART1 */
    
    UART1->UCR1 = 0; /* 先清除 UCR1 寄存器 */
    UART1->UCR1 &= ~(1<<14); /* 关闭自动波特率检测 */
/*
 * 设置 UART 的 UCR2 寄存器,设置字长,停止位,校验模式,关闭硬件流控
 * bit14: 1 忽略 RTS 引脚
 * bit8: 0 关闭奇偶校验
 * bit6: 0 1 位停止位
 * bit5: 1 8 位数据位
 * bit2: 1 打开发送
 * bit1: 1 打开接收
 */
    UART1->UCR2 |= (1<<14) | (1<<5) | (1<<2) | (1<<1);
    UART1->UCR3 |= 1<<2; /* UCR3 的 bit2 必须为 1 */

/*
 * 设置波特率:根据上文介绍的波特率计算公式。
 * 如果要设置波特率为 115200,那么可以使用如下参数:
 * Ref Freq = 80M 也就是寄存器 UFCR 的 bit9:7=101, 表示 1 分频
 * UBMR = 3124
 * UBIR = 71
 */
    UART1->UFCR = 5<<7; /* ref freq 等于 ipg_clk/1=80Mhz */
    UART1->UBIR = 71;
    UART1->UBMR = 3124;

//此处屏蔽了NXP 官方的 SDK 包里面移植过来的设置波特率函数,波特率可自己配还可直接调此函数
#if 0
    uart_setbaudrate(UART1, 115200, 80000000); /* 设置波特率 */
#endif

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

//初始化串口 1 所使用的 IO 引脚
void uart_io_init(void)
{
   /* 1、初始化串口 IO
    * UART1_RXD -> UART1_TX_DATA
    * UART1_TXD -> UART1_RX_DATA
    */
    IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
    IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
}

/*
 * @description : 波特率计算公式,可用此函数计算出指定串口对应的
 *UFCR,UBIR 和 UBMR 这三个寄存器的值
 * @param - base : 要计算的串口。
 * @param - baudrate : 要使用的波特率。
 * @param - srcclock_hz : 串口时钟源频率,单位 Hz
 */
void uart_setbaudrate(UART_Type *base,unsigned int baudrate,unsigned int srcclock_hz)
{
    uint32_t numerator = 0u;
    uint32_t denominator = 0U;
    uint32_t divisor = 0U;
    uint32_t refFreqDiv = 0U;
    uint32_t divider = 1U;
    uint64_t baudDiff = 0U;
    uint64_t tempNumerator = 0U;
    uint32_t tempDenominator = 0u;

    /* get the approximately maximum divisor */
    numerator = srcclock_hz;
    denominator = baudrate << 4;
    divisor = 1;

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

    numerator = srcclock_hz / 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 = srcclock_hz;
    tempDenominator = (numerator << 4);
    divisor = 1;
/* get the approximately maximum divisor */
    while (tempDenominator != 0)
    {
        divisor = tempDenominator;
        tempDenominator = tempNumerator % tempDenominator;
        tempNumerator = divisor;
    }
    tempNumerator = srcclock_hz / 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);
        base->UBMR = UART_UBMR_MOD(numerator / divider - 1);
    }
}


void uart_disable(UART_Type *base)//关闭指定的UART
{
    base->UCR1 &= ~(1<<0);
}
void uart_enable(UART_Type *base)//打开指定的UART
{
    base->UCR1 |= (1<<0);
}
void uart_softreset(UART_Type *base)//复位指定的 UART
{
    base->UCR2 &= ~(1<<0); /* 复位 UART */
    while((base->UCR2 & 0x1) == 0); /* 等待复位完成 */
}
void putc(unsigned char c)//发送一个字符,传入的参数为要发送的字符
{
    while(((UART1->USR2 >> 3) &0X01) == 0);/* 等待上一次发送完成 */
    UART1->UTXD = c & 0XFF; /* 发送数据 */
}
void puts(char *str)//发送一个字符串,传入的参数为要发送的字符串
{
    char *p = str;

    while(*p)
        putc(*p++);
}

unsigned char getc(void)    //接收一个字符,返回值为接收到的字符
{
    while((UART1->USR2 & 0x1) == 0); /* 等待接收完成 */
    return UART1->URXD; /* 返回接收到的数据 */
}
void raise(int sig_nr)    //防止编译器报错
{

}

main.c:

调用函数 uart_init 初始化 UART1,然后在 while 循环里面获取串口接收到的数据,并且将获取到的数据通过串口打印出来。

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_uart.h"

int main(void)
{
    unsigned char a=0;
    unsigned char state = OFF;
    
    int_init(); /* 初始化中断(一定要最先调用! ) */
    imx6u_clkinit(); /* 初始化系统时钟 */
    delay_init(); /* 初始化延时 */
    clk_enable(); /* 使能所有的时钟 */
    led_init(); /* 初始化 led */
    beep_init(); /* 初始化 beep */
    uart_init(); /* 初始化串口,波特率 115200 */

    while(1)
    {
        puts("请输入 1 个字符:");
        a=getc();
        putc(a); /* 回显功能 */
        puts("\r\n");

        /* 显示输入的字符 */
        puts("您输入的字符为:");
        putc(a);
        puts("\r\n\r\n");

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

Makefile:

本章 Makefile 文件在链接时加入了数学库, 因为在 bsp_uart.c 中有个函数uart_setbaudrate,此函数中使用到了除法运算,因此在链接时需要将编译器的数学库也链接进来。第9行的变量LIBPATH就是数学库的目录,在第56行链接的时候使用了变量LIBPATH。

我们常要用到一些第三方库,那么在连接程序时就要指定这些第三方库所在的目录, Makefile 在链接的时候使用选项“-L”来指定库所在的目录,比如下面代码中第 9 行的变量 LIBPATH 就是指定了我们所使用的编译器库所在的目录。

②、 在第 61 行和 64 行中,加入了选项“-fno-builtin”,否则编译的时候提示“putc”、“puts”
这两个函数与内建函数冲突,错误信息如下所示:

warning: conflicting types for built-in function ‘putc’
warning: conflicting types for built-in function ‘puts’

编译时加入选项“-fno-builtin”表示不使用内建函数,我们就可自己实现 putc和 puts 这样的函数了。

1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= uart
3 
4 CC := $(CROSS_COMPILE)gcc
5 LD := $(CROSS_COMPILE)ld
6 OBJCOPY := $(CROSS_COMPILE)objcopy
7 OBJDUMP := $(CROSS_COMPILE)objdump
8
9 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
10
11
12 INCDIRS := imx6ul \
13            bsp/clk \
14            bsp/led \
15            bsp/delay \
16            bsp/beep \
17            bsp/gpio \
18            bsp/key \
19            bsp/exit \
20            bsp/int \
21            bsp/epittimer \
22            bsp/keyfilter \
23            bsp/uart
24
25 SRCDIRS := project \
26            bsp/clk \
27            bsp/led \
28            bsp/delay \
29            bsp/beep \
30            bsp/gpio \
31            bsp/key \
32            bsp/exit \
33            bsp/int \
34            bsp/epittimer \
35            bsp/keyfilter \
36            bsp/uart
37
38
39 INCLUDE     := $(patsubst %, -I %, $(INCDIRS))
40
41 SFILES      := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
42 CFILES      := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
43
44 SFILENDIR   := $(notdir $(SFILES))
45 CFILENDIR   := $(notdir $(CFILES))
46
47 SOBJS       := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
48 COBJS       := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
49 OBJS        := $(SOBJS) $(COBJS)
50
51 VPATH       := $(SRCDIRS)
52
53 .PHONY: clean
54
55 $(TARGET).bin : $(OBJS)
56     $(LD) -Timx6ul.lds -o $(TARGET).elf $^ $(LIBPATH)
57     $(OBJCOPY) -O binary -S $(TARGET).elf $@
58     $(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
59
60 $(SOBJS) : obj/%.o : %.S
61     $(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
62
63 $(COBJS) : obj/%.o : %.c
64     $(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
65
66 clean:
67     rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)

链接脚本保持不变。

串口格式化函数移植实验

上面实验实现了 UART1 基本的数据收发功能,虽然可以用来调试程序,但是功能单一,只能输出字符。若需要输出数字还需要我们自己先将数字转换为字符,非常不方便。我们可以将 printf 函数映射到串口上,这样就可使用printf 函数完成格式化输出了,使用非常方便。本实验即实现将 printf 这样的格式化函数移植到 I.MX6U-ALPHA 开发板上。

串口格式化函数简介

格式化函数说的是 printf、 sprintf 和 scanf 这样的函数,分为格式化输入和格式化输出两类函数。学习 C 语言的时常通过 printf 函数在屏幕上显示字符串,通过 scanf 函数从键盘获取输入。这样有了输入和输出,实现了最基本的人机交互。学习 STM32 时将 printf 映射到串口上,这样即使没有屏幕,也可通过串口来和开发板进行交互。在 I.MX6U-ALPHA 开发板上也可以使用此方法,将 printf 和 scanf 映射到串口上,这样就可使用串口调试助手的上位机作为开发板终端, 完成与开发板的交互。也可以使用 printf 和 sprintf 来实现各种各样的格式化字符串,方便我们开发调试程序。之前基本串口收发数据实验中,串口驱动已编写完成,本实验通过移植网上别人已经做好的文件来实现格式化函数。

实验程序编写

本实验所需要移植的源码在正点原子开发板资料中,路径为: 1、例程源码->5、模块驱动源码->2、格式化函数源码->stdio,文件夹 stdio 里面的文件就是我们要移植的源码文件。本实验在上一实验例程的基础上完成,将 stdio 文件夹复制到实验工程根目录中。

stdio 里面有两个文件夹: include 和 lib,这两个文件夹里面的内容如下:

stdio 里面的文件是从 uboot 里移植过来的。后面学习 uboot 后可以自行从 uboot 源码里面“扣”出相应的文件,完成格式化函数的移植。这里 stdio 中并没有实现完全版的格式化函数,比如 printf 函数并不支持浮点数,但是基本够使用。移植好以后就要测试相应的函数工作是否正常。如下代码,测试移植是否成功。使用 scanf 函数等待键盘输入两个整数,然后将两个整数进行相加并使用 printf 函数输出结果:

#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_uart.h"
#include "stdio.h"

int main(void)
{
    int c = 100;
    printf("%d的十六进制为:%#x\r\n",c,c);  //验证一下打印十六进制数
    
    unsigned char state = OFF;
    int a , b;

    int_init(); /* 初始化中断(一定要最先调用! ) */
    imx6u_clkinit(); /* 初始化系统时钟 */
    delay_init(); /* 初始化延时 */
    clk_enable(); /* 使能所有的时钟 */
    led_init(); /* 初始化 led */
    beep_init(); /* 初始化 beep */
    uart_init(); /* 初始化串口,波特率 115200 */

    while(1)
    {
        printf("输入两个整数,使用空格隔开:");
        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;
}

修改 Makefile 中的 TARGET 为 printf,在 INCDIRS 中加入“stdio/include”,在 SRCDIRS 中加入“stdio/lib”,

1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= printf
3 
4
/* 省略掉其它代码...... */
5 
6 INCDIRS := imx6ul \
7 stdio/include \
8 bsp/clk \
9 bsp/led \
10 bsp/delay \
11 bsp/beep \
12 bsp/gpio \
13 bsp/key \
14 bsp/exit \
15 bsp/int \
16 bsp/epittimer \
17 bsp/keyfilter \
18 bsp/uart
19
20 SRCDIRS := project \
21 stdio/lib \
22 bsp/clk \
23 bsp/led \
24 bsp/delay \
25 bsp/beep \
26 bsp/gpio \
27 bsp/key \
28 bsp/exit \
29 bsp/int \
30 bsp/epittimer \
31 bsp/keyfilter \
32 bsp/uart
33
34 /* 省略掉其它代码...... */
35
36 $(COBJS) : obj/%.o : %.c
37 $(CC) -Wall -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
38
39 clean:
40     rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)

第 2 行修改变量 TARGET 为“printf”,也就是目标名称为“printf”。
第 7 行在变量 INCDIRS 中添加 stdio 相关头文件(.h)路径。
第 28 行在变量 SRCDIRS 中添加 stdio 相关文件(.c)路径。
第 37 行在编译 C 文件的时候添加了选项“-Wa,-mimplicit-it=thumb”,否则的话会有如下错误提示:

thumb conditional instruction should be in IT block -- `addcs r5,r5,#65536'

链接脚本保持不变。

实验现象:I.MX6U开发板通过printf函数将 “输入两个整数,使用空格隔开” 打印到串口调试助手上位机SecureCRT界面。键盘键入两个整数,通过串口传给了I.MX6U开发板,然后通过scanf函数接收,接收到整数然后进行计算后,通过printf函数打印到SecureCRT的界面上。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1387257.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java处理16进制字符串的一些方法和基础知识

前言&#xff1a;本篇文章是对于基础数据的处理的一些简单经验总结里边包含了一些基础的数据储存和数据转化的一些知识&#xff0c;同样也包含有部分快捷的数据处理方法。主要用于个人知识的一个记录和方便进行对应的数据转换和处理。 1、bit,字节和字的关系 1.1 bit和字节的…

leetcode 24两两交换链表中的节点

题目 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 思想 对于操作链表节点的时候&#xff0c;首先需要就是创建一个虚拟的…

表单验证 ---- 在Vue2中使用ElementUI进行表单验证

目录 前言 给表单绑定对应属性 在data中定义数据对象和表单的定义规则 与数据对象双向绑定 对整个表单进行验证 前言 在做项目时&#xff0c;对于表单进行验证是我们必不可少的 例如 搭建一个基本的登录界面 <div class"form"><h1>登录</h1>&…

OPT(erlang)打造一套缓存系统(一)

缓存的设计 这个简易缓存存储的是键/值对&#xff0c;其中键与键之间不得重复&#xff0c;并且每个键只能映射到一个值。这个设计背后的核心思想是为写人缓存的每一个值都分配一个独立的存储进程再将对应的键映射至该进程。你可能会对这种为每个值分配一个进程的设计感到惊讶&…

2024年学鸿蒙开发就业前景怎么样?

随着科技的不断进步&#xff0c;鸿蒙系统作为华为自主研发的操作系统&#xff0c;逐渐引起了人们的关注。 2024年&#xff0c;鸿蒙开发就业前景如何&#xff1f; 对于那些对鸿蒙开发感兴趣并希望在这一领域寻找职业发展的人来说&#xff0c;这是一个非常重要的问题。 首先&a…

【电子取证篇】蘇小沐的电子取证工具合集在线文档

【电子取证篇】蘇小沐的电子取证工具合集在线文档 弄成了在线表格&#xff0c;记得及时保存&#xff1b;工具永远只是辅助&#xff0c;但不要过多依赖自动化&#xff0c;有难度说明可以提升&#xff0c;既要不断学习也要不停思考&#xff0c;知行合一—【蘇小沐】 【腾讯文档…

springboot项目启动时横幅修改

正常情况下&#xff0c;springboot启动时的横幅&#xff08;banner&#xff09;长这样 自定义banner 在resource下创建banner.txt&#xff0c;写入想要修改的内容即可 程序无bugSpring Boot Version: ${spring-boot.version}// _ooOoo_ …

力扣-刷MySQL(详细解析)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;重拾MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出现错误&am…

16 命令行模式

命令行模式 将行为的执行与与行为的调用通过命令分离&#xff0c;行为的的调用者不需要知道具体是哪个类执行的&#xff0c;他们之间通过命令连接。 demo的目录结构 命令的执行者&#xff08;接口&#xff09; package behavioralpattern.commandpattern.actuator;import ja…

2024年腾讯云服务器多少钱1年?超便宜62元一年

腾讯云服务器租用价格表&#xff1a;轻量应用服务器2核2G3M价格62元一年、2核2G4M价格118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;2核4G5M带宽756元三年、轻量4核8G12M服务器446元一年、646元15个月&#xff0c;云服务器CVM S5实例2核2G配置280.8元一年…

Github项目推荐-clone-voice

项目地址 GitHub - jianchang512/clone-voice 项目简述 一个声音ai工具。基于python编写。作用是音色复用。下面是官方说明&#xff1a;“这是一个声音克隆工具&#xff0c;可使用任何人类音色&#xff0c;将一段文字合成为使用该音色说话的声音&#xff0c;或者将一个声音使…

新火种AI|GPT-5前瞻!GPT-5将具备哪些新能力?

作者&#xff1a;小岩 编辑&#xff1a;彩云 Sam Altman在整个AI领域&#xff0c;乃至整个科技领域都被看作是极具影响力的存在&#xff0c;而2023年OpenAI无限反转的宫斗事件更是让Sam Altman刷足了存在感&#xff0c;他甚至被《时代》杂志评为“2023年度CEO”。 也正因此&…

Modbus协议学习第二篇之Modbus poll slave仿真软件初体验

软件准备 学习Modbus离不开硬件&#xff0c;好在我们可以通过仿真软件来模拟硬件&#xff0c;本篇博客就来简单介绍一下Modbus仿真软件的最基础使用方法&#xff0c;需要用到的3款仿真软件如下&#xff1a; Modbus Poll 64位 / Modbus Poll 32位&#xff08;根据自己机器位数选…

【Python学习】Python学习14-函数

目录 【Python学习】Python学习14-函数 前言自定义函数创建语法自定义函数与调用参数传递参考 文章所属专区 Python学习 前言 本章节主要说明Python的函数。函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。 函数能提高应…

自定义vector的实现

实现前需要思考的一个问题 为什么需要将空间的申请与对象的构建分开 查看vector的模板参数时可以看到其有第三个参数是空间适配器allocator&#xff0c;查找其对外提供的成员函数不难发现它的实现逻辑是将空间的申请与对象的构建分开的&#xff0c;为什么呢&#xff1f;不弄清…

云畅科技技术中心被认定为湖南省省级企业技术中心

近日&#xff0c;湖南省工业和信息化厅公布《2023年第二批湖南省省级企业技术中心(第29批)》&#xff0c;云畅科技技术中心作为研发设计型代表入选。 省级企业技术中心是强化企业技术创新主体地位&#xff0c;增强企业自主创新能力&#xff0c;推动工业企业高质量发展的一个重要…

搬运5款帮你优化电脑的小工具软件

​ 你想让你的电脑更好用吗&#xff1f;这里有五款电脑软件可以帮你&#xff0c;它们可以让你的电脑更高效、美观、安全&#xff0c;快来看看吧&#xff01; 1.窗口管理——MaxMax ​ MaxMax是一款窗口管理软件&#xff0c;可以让你自定义窗口的最大化行为&#xff0c;避免窗…

STC8H8K蓝牙智能巡线小车——2. 点亮左右转弯灯与危险报警灯

任务调用示例 RTX 51 TNY 可做多任务调度&#xff0c;API较为简单。 /* 接口API */// 创建任务 extern unsigned char os_create_task (unsigned char task_id); // 结束任务 extern unsigned char os_delete_task (unsigned char task_id);// 等待 extern unsig…

Ubuntu20.04下A-LOAM配置安装及测试教程(包含报错问题踩坑)

参考文章&#xff1a; ubuntu20.04下ros运行A-LOAM Ubuntu20.04下运行LOAM系列&#xff1a;A-LOAM、LeGO-LOAM、SC-LeGO-LOAM、LIO-SAM 和 LVI-SAM 需要学习源码的同学可以下载LOAM论文 LOAM论文链接 1.需要安装的库文件 1.1Eigen 3.3 可以直接使用apt命令安装&#xff0c;或…

C#,字符串匹配(模式搜索)AC(Aho Corasick)算法的源代码

Aho-Corasick算法简称AC算法&#xff0c;也称为AC自动机(Aho-Corasick)算法&#xff0c;1975年产生于贝尔实验室&#xff08;The Bell Labs&#xff09;&#xff0c;是一种用于解决多模式字符串匹配的经典算法之一。 the Bell Lab 本文的运行效果&#xff1a; AC算法以模式树…