ARM嵌入式学习(十三)--- IMX6ULL串口

目录

一、串口介绍

二、配置串口寄存器

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

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

UARTx_UCR1:

UARTx_UCR2:

UARTx_UCR3:

3.波特率配置:

4.读写函数:

写函数:

读函数:

注意:

5.整体代码:

三、格式化输入输出

整体的makefile代码:

四、总结


一、串口介绍

不管是单片机开发还是嵌入式 Linux 开发,串口都是最常用到的外设。可以通过串口将开发板与电脑相连,然后在电脑上通过串口调试助手来调试程序。还有很多的模块,比如蓝牙、GPS、GPRS 等都使用的串口来与主控进行通信的,在嵌入式 Linux 中一般使用串口作为控制台,所以掌握串口是必备的技能。本节我们就来学习如何驱动 I.MX6U 上的串口,并使用串口和电脑进行通信。

I.MX6U 的 UART 接口,I.MX6U 一共有 8 个 UART,其主要特性如下:

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

②支持串行 IR 接口,兼容 IrDA(Infrared Data Association红外数据组织),最高可到 115.2Kbit/s;

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

④1 或 2 位停止位;

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

⑥自动波特率检测(最高支持 115.2Kbit/S)。

I.MX6U 的 UART 功能很多,但是我们本篇就只用到其最基本的串口功能 ,关于 UART 其它功能的介绍请参考《I.MX6ULL 参考手册》第 3561 页的"Chapter 55 Universal Asynchronous Receiver/Transmitter(UART)"章节。 本篇我们的目的是实现115200,8,N,1(这些数字看不懂的话先去学习51入门串口:ARM嵌入式学习(三) --- 入门51(串口)-CSDN博客)与电脑之间通信。

二、配置串口寄存器

UART 的时钟源是由寄存器 CCM_CSCDR1 的 UART_CLK_SEL(bit)位来选择的,当为 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。 (参考时钟树)

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 模块、蓝牙模块等通信时。

接下来看一下 UART 几个重要的寄存器

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

在手册第3618页,这里只写我们比较关心的:

UARTx_UCR1:

(1)ADBR(bit14):自动波特率检测使能位,为 0 的时候关闭自动波特率检测,为 1 的时候使能自动波特率检测;

(2)UARTEN(bit0):UART 使能位,为 0 的时候关闭 UART,为 1 的时候使能 UART。

这里我们关闭自动波特率检测,代码为:

复制代码
    UART1->UCR1 |= (1 << 0);

注意要最后配好了所有东西再使能。

UARTx_UCR2:

(1)IRTS(bit14):为 0 的时候使用 RTS 引脚功能,为 1 的时候忽略 RTS 引脚;

(2)PREN(bit8):奇偶校验使能位,为 0 的时候关闭奇偶校验,为 1 的时候使能奇偶校验;

(3)PROE(bit7):奇偶校验模式选择位,开启奇偶校验以后此位如果为 0 的话就使用偶校验,此位为 1 的话就使能奇校验。

(4)STOP(bit6):停止位数量,为 0 的话 1 位停止位,为 1 的话 2 位停止位;

(5)WS(bit5):数据位长度,为 0 的时候选择 7 位数据位,为 1 的时候选择 8 位数据位;

(6)TXEN(bit2):发送使能位,为 0 的时候关闭 UART 的发送功能,为 1 的时候打开 UART的发送功能;

(7)RXEN(bit1):接收使能位,为 0 的时候关闭 UART 的接收功能,为 1 的时候打开 UART的接收功能;

(8)SRST(bit0):软件复位,为 0 的是时候软件复位 UART,为 1 的时候表示复位完成。复位完成以后此位会自动置 1,表示复位完成。此位只能写 0,写 1 会被忽略掉。

复制代码
    UART1->UCR2 &= ~(1 << 0);
    delay_us(1);
    unsigned int tmp = UART1->UCR2;
    tmp |= (1 << 14);
    tmp &= ~(1 << 8);
    tmp &= ~(1 << 6);
    tmp |= (1 << 5);
    tmp |= (1 << 2);
    tmp |= (1 << 1);
    UART1->UCR2 = tmp;

UARTx_UCR3:

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

(1)TXDC(bit3):发送完成标志位,为 1 的时候表明发送缓冲(TxFIFO)和移位寄存器为空,也就是发送完成,向 TxFIFO 写入数据此位就会自动清零;

(2)RDR(bit0):数据接收标志位,为 1 的时候表明至少接收到一个数据,从寄存器UARTx_URXD 读取数据接收到的数据以后此位会自动清零。

复制代码
UART1->UCR3 |= (1 << 2);

第0位和第3位的使用我们写到读写函数中去

3.波特率配置:

首先需要把UART的工作频率确定下来,PLL3时钟为480MHz,经由一个静态6分频的分频器之后为80MHz。此后UART还有一个自己的分频器,由寄存器UARTx_UFCR[(bit9~bit7]决定分频值。我们需要1分频,即为b101。

接着波特率由寄存器UARTx_UBIR 和 UARTx_UBMR计算得出,计算公式为:(手册第3593页)

公式中Ref Freq为UART的实际工作频率(80MHz),RaudRate是波特率,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);
    UART1->UFCR = tmp;

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

4.读写函数:

先介绍一下UARTx_USR2(UART 的状态寄存器2 ):

(1)TXDC(bit3): 发送完成标志位,为 1 的时候表明发送缓冲(TxFIFO)和移位寄存器为空,也就是发送完成,向 TxFIFO 写入数据此位就会自动清零;

(2)RDR(bit0): 数据接收标志位,为 1 的时候表明至少接收到一个数据,从寄存器UARTx_URXD 读取数据接收到的数据以后此位会自动清零

写函数:

UTXD:写数据寄存器,

复制代码
void uart_send_byte(unsigned char data)
{
    while(!(UART1->USR2 & (1 << 3))); //当第3位不为0时,跳出循环
    UART1->UTXD = data;
}

读函数:

URXD:读数据寄存器,

复制代码
unsigned char uart_recv_byte
{
    unsigned char data = 0;
    while(!(UART1->USR2 & (1 << 0))); //当第3位不为0时,跳出循环
    data = UART1->URXD & 0xff;
    return data;
}

注意:

这里为什么要使用data = UART1->URXD & 0xff;这样的操作,是因为只有低八位才是有效数据,不&0xff会读出来一些我们不需要的东西

5.整体代码:

复制代码
#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);
    tmp &= ~(1 << 8);
    tmp &= ~(1 << 6);
    tmp |= (1 << 5);
    tmp |= (1 << 2);
    tmp |= (1 << 1);
    base->UCR2 = tmp;

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

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

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

    base->UCR1 |= (1 << 0);
}

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;
}

这里改了一下函数的参数,让我们可以选择打开任意一个串口

最后main.c:

下载随便一个串口调试助手,就可以看到我们发送出去的16进制数据会被加1再返回给我们。

三、格式化输入输出

虽然可以用来调试程序,但是功能太单一了,只能输出字符 。如果需要输出数字的时候就需要我们自己先将数字转换为字符,非常的不方便 。如果可以使用printf 函数来完成格式化输出了,那就非常方便了。本篇我们来继续学习如何将 printf 这样的格式化函数移植到 I.MX6U 开发板上。

移植的基本原理很简单,就是把printf和scanf函数映射到UART1上,之后用串口调试助手等软件就可以非常方便地和开发板之间通信了。

配套资料中的文件夹 stdio 里面的文件就是我们要移植的源码文件(需要资料请私信我),将该文件夹复制到工程目录下。stdio 里面的文件其实是从 uboot 里面移植过来的。后面学习 uboot 以后大家有兴趣的话可以自行从 uboot 源码里面"扣"出相应的文件,完成格式化函数的移植。这里要注意一点,stdio 中并没有实现完全版的格式化函数,比如 printf 函数并不支持浮点数,但是基本够我们使用了。

文件拷贝完成以后要做一下几点修改,否则编译或者链接时会出错:

1.需要在uart.c中添加一个函数:void raise(int n);函数体为空即可,否则会报错;
2.如果之前编写的project/start.s文件扩展名为小写s,则需改成大写。即start.S。对于汇编文件来说,扩展名S和s说不一样的,S会先做预处理,在进行编译而s则省略预处理步骤;
3.由于增加了新的源码和头文件,所以Makefile也需要修改。

4.之前所有引用.s的地方都需要改成.S
5.编译选项增加-Wa,-mimplicit-it=thumb --nostdlib,否则的话会有如下类似的错误提示thumb conditional instruction should be in IT block --`addcsr5,r5,#65536'

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

整体的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

如果报错缺少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;
}

移植好了以后,我们就可以实现一个电脑输入命令来控制开发板led和蜂鸣器的函数了。

main.c:

复制代码
#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"

void gpio1_io18_handler(void)
{
  GPIO1->DR ^= (1 << 3);
  GPIO5->DR ^= (1 << 1);
}

void epit1_irq_handler()
{
  GPIO1->DR ^= (1 << 3);
  GPIO5->DR ^= (1 << 1);
}

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

  clk_init();
  led_init();
  beep_init();
  // key_irq_init(gpio1_io18_handler);
  // epit1_init(epit1_irq_handler);
  gpt1_init();
  uart_init(UART1);
  int num = 0;
  while (1)
  {
    //  scanf("%d %d\r\n", &num1, &num2);
    //  num1++;
    //  printf("%d + %d = %d\n", num1, num2, num1 + num2);
    printf("input ur command:");
    scanf("%s", data);
    // printf("\n%s\n", data);
#if 1
    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");
    }
#endif
    // uart_send_byte(UART1, data + 1);
    // delay_ms(1000);
  }

  return 0;
}

四、总结

串口通信的原理一定要搞明白,明白之后就算换了一个开发板我们也能配置

相关推荐
hssfscv2 小时前
软件设计师 试题三 面向对象——UML事物、关系、图
笔记·学习·uml
wubba lubba dub dub7502 小时前
第四十周学习周报
学习
xuhaoyu_cpp_java2 小时前
XML学习
xml·java·笔记·学习
放下华子我只抽RuiKe52 小时前
深度学习-04-NLP项目实战
人工智能·深度学习·学习·自然语言处理·openclaw·development
鱼鳞_2 小时前
Java学习笔记_Day17(集合)
笔记·学习
鱼鳞_2 小时前
Java学习笔记_Day15
java·笔记·学习·排序算法
bolink52 小时前
大模型学习
学习
xw-busy-code2 小时前
npm私服搭建学习笔记
笔记·学习·npm·npm 私服
丝斯20112 小时前
AI学习笔记整理(77)——Python学习6
笔记·学习