IMX6ULL学习笔记(20)——UART串口使用

2023-05-16

一、UART简介

i.MX6U 芯片具有多达 8 个 UART 外设用于串口通讯,UART 是在 USART 基础上裁剪掉了同步通信功能,只支持异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用的串口通信基本都是 UART。

UART 满足外部设备对工业标准 NRZ 异步串行数据格式的要求,并且使用了小数波特率发生器,可以提供多种波特率,使得它的应用更加广泛。UART 支持异步单向通信和半双工单线通信;还支持局域互连网络 LIN、智能卡(SmartCard)协议与 lrDA(红外线数据协会)SIR ENDEC 规范。

i.MX6U 的 UART 主要特性如下:

  • 兼容高速 TIA/EIA-232-F,最高 5.0 Mbit/s。
  • 兼容 IrDA 串行低速红外接口,最高 115.2 Kbit/s。
  • 支持 9 位或多点(Multidrop mode)模式(RS-485)(自动从地址检测)。
  • 支持 7 位或 8 位 RS-232 格式或 9 位的 RS-485 格式。
  • 支持 1 或 2 位停止位。
  • 可编程奇校验或偶校验。
  • 支持硬件流控(CTS/RTS)。
  • RTS支持边缘中断检测。
  • 自动波特率检测(最高为115.2kbit/s)。
  • rx_data 输入和 tx_data 输出可分别在 rs-232/rs-485 模式下进行交换(软件可交换 TX、RX 引脚)。
  • 两个 DMA 请求(TxFIFODMA 请求和 RxFIFODMA 请求)。
  • 支持软件复位 (Srst_B)。
  • 两个独立的 32 输入 FIFO,用于发送和接收。

二、引脚分布


相比 STM32,i.MX6U 的串口增加了引脚交换功能。通过配置 UFCR[DCEDTE] 寄存器可以交换 TX、RX 引脚以及 CTS、RTS 引脚,如下图所示。

三、波特率设置

3.1 串口时钟

串口模块共有两个时钟输入 Peripheral ClockModule Clock

  • Peripheral Clock
    外部时钟。这个时钟主要用于读、写接收和发送的数据,例如读接收FIFO、写发送FIFO。 这个时钟与波特率设置无关,如果没有特殊需求我们将这个时钟保持默认即可,在初始代码中并没有特意设置这个时钟。

  • Module Clock
    模块时钟,它既可用于接收、发送数据也用于设置波特率,这个时钟决定了串口最高支持的波特率。Module Clock 时钟来自根时钟 UART_CLK_ROOT

从图中可以看出,从PLL时钟到UART时钟共用用到了两个时钟选择寄存器(标号①和③),两个时钟分频寄存器(标号②和标号④)。 我们最终目的是将PLL3时钟作为UART根时钟(UART_CLK_ROOT)的根时钟。按照标号顺序讲解如下:

  1. 标号①选择 PLL3 时钟还是 CCM_PLL3_BYP。我们选择 PLL3 输出时钟,寄存器 CCSR[PLL3_SW_CLK_SEL] = 0, 则表示选择 PLL3 时钟。默认情况下是这样设置的。所以我们代码中并没有设置该寄存器。
  2. 标号②设置时钟分频,根据之前的设置,PLL3 的输出频率为 480MHz ,这里的时钟分频是固定的 6 分频, 经过分频后的时钟为 480MHz / 6 = 80MHz
  3. 标号③ 再次选择时钟源。一个是 PLL3 分频得到的 80MHz 时钟,另外一个是 OSC 时钟即 24MHz 的系统参考时钟。 设置 CSCDR1[UART_CLK_SEL] = 0,选择第一个(80MHz)时钟。
  4. 标号④再次进行时钟分频。这是一个 6 位的时钟分频寄存器。分频值为 CSCDR1[UART_CLK_PODF] 寄存器值加一。 程序中将其设置为 1,则分频系数为 2,UART_CLK_ROOT 时钟频率实际为 80MHz / 2 = 40MHz

3.2 波特率计算公式

UART的发送器和接收器使用相同的波特率。计算公式如下:

  • BaudRate:要设置的波特率。
  • Ref Freq:参考时钟,这个时钟是 Module Clock 经过 RFDIV 寄存器分频后得到的频率。
  • UBMR:UBMR寄存器值。
  • UBIR:UBIR 寄存器值。

3.3 配置UARTx_UFCR寄存器

假设目标波特率为 115200

3.3.1 设置时钟分频寄存器RFDIV

通过设置时钟分频寄存器 RFDIV,得到参考时钟 Ref Freq。

Ref Freq 时钟应该被设置为多少,没有一个准确的数字,遵守以下几条:

  1. Ref Freq 时钟要大于波特率的 16 倍

依据是 module_clock 必须大于等于 16 倍波特率【参考《IMX6ULRM》(参考手册)53.4.2.1 Clock requirements,而 module_clock 经过 Clock Gating & Divider 之后变为 ref_clk,从功能框图看 ref_clk 最终作为 uart 模块参考时钟】。

  1. UBMR 和 UBIR 的值必须小于 0xFFFF

module_clock 时钟经过 UARTx_UFCR[RFDIV] 寄存器分频后得到 Ref Freq 时钟,如下图所示。

在程序中 Module Clock 被设置为 40MHz 而波特率只有 115200,所以这里将分频值设置为最大值 7,即UARTx_UFCR[RFDIV] = 110B。得到参考时钟 Ref Freq = 40MHz / 7 = 5.71MHz

3.3.2 计算UBMR和UBIR的值

已知波特率 115200,参考时钟 Ref Freq = 5.71MHz,可以计算得到 (UBMR+1) / (UBIR+1) 约为 3.10。 我们设置 (UBIR+1) = 10,(UBMR+1) = 31

四、接收和发送FIFO

Tx Block 与 Rx Block 包括三部分:

  • 控制单元Control
    控制整个串口的工作, 我们编写软件不必过多关心。

  • 电源管理单元 Power Saving

  • TxFIFO (和RxFIFO)
    TxFIFO 与 RxFIFO 大小均为 32 字节,以发送为例,数据通过 UTXD 寄存器自动写入 TxFIFO,如果 TxFIFO没有满,则可以不断将数据写入 UTXD 寄存器。UTS[4] 寄存器是 TxFIFO 满的标志位。如果关闭了发送,仍然可以向 TxFIFO 写入数据,但这样做将会产生传输错误信息。当 TxFIFO 中的数据发送完成后将会自动设置发送缓冲区空中断标志位,向 TxFIFO 写入数据将自动清除发送缓冲区空标志位。

五、DMA和中断请求

每个串口有两个 DMA 请求,txfifo dma 请求和 rxfifo dma 请求,有多个中断请求。

这里只介绍几个常用的中断:

中断事件使能位标志位
空闲中断UARTx_UCR1[TRDYEN]UARTx_USR2[TXFE]
接收溢出中断UARTx_UCR4[OREN]UARTx_USR2[ORE]
接收缓冲区非空中断UARTx_UCR4[DREN]UARTx_USR2[RDR]

完整内容请参考《IMX6ULRM》(参考手册)53.4.1 Interrupts and DMA Requests。

六、编程流程

1. 创建工程文件夹
2. 移植官方SDK寄存器定义文件
3. 移植野火PAD属性配置文件
4. 编写启动文件
5. 编写链接文件
6. 编写makefile文件
7. 编写C语言代码
(1) 设置UART时钟源
(2) 初始化UART
(3) 使能UART
(4) UART数据接收
(5) UART数据发送

七、创建工程文件夹

  1. 创建一个文件夹 uart
  2. 创建一个用于存放头文件的文件夹 include
  3. 创建一个用于存放驱动源码的文件 device
  4. 创建一个启动文件 start.S
  5. 创建一个源文件 main.c
  6. 创建一个链接脚本 base.lds

八、移植官方SDK寄存器定义文件

/uart/include 目录下添加官方SDK寄存器定义文件 MCIMX6Y2.h,位于 SDK_2.2_MCIM6ULL_EBF6ULL/devices/MCIMX6Y2 目录下。

在官方SDK的头文件 MCIMX6Y2.h 文件多达4万多行,包含了i.MX6U芯片几乎所有的寄存器定义以及中断编号的定义。

这里只列 GPIO1相关寄存器 的部分代码。其他寄存器定义与此类似。 添加这些定义之后我们就可以 直接使用 “GPIO1->DR” 语句操作GPIO1的DR寄存器。操作方法与STM32非常相似。

typedef struct {
   __IO uint32_t DR;     /**< GPIO data register, offset: 0x0 */
   __IO uint32_t GDIR;   /**< GPIO direction register, offset: 0x4 */
   __I  uint32_t PSR;    /**< GPIO pad status register, offset: 0x8 */
   __IO uint32_t ICR1;   /**< GPIO interrupt configuration register1,*/
   __IO uint32_t ICR2;   /**< GPIO interrupt configuration register2, */
   __IO uint32_t IMR;   /**< GPIO interrupt mask register, offset: 0x14 */
   __IO uint32_t ISR; /**< GPIO interrupt status register, offset: 0x18 */
   __IO uint32_t EDGE_SEL;/**< GPIO edge select register, offset: 0x1C */
} GPIO_Type;

/*********************以下代码省略***************************8*/
/** Peripheral GPIO1 base address */
#define GPIO1_BASE                               (0x209C000u)
/** Peripheral GPIO1 base pointer */
#define GPIO1                                    ((GPIO_Type *)GPIO1_BASE)

九、移植野火PAD属性配置文件

/uart/include 目录下添加 pad_config.h

通常情况下一个引脚要设置8种PAD属性,而这些属性只能通过数字指定。为简化PAD属性设置野火编写了一个PAD属性配置文件 pad_config.h (embed_linux_driver_tutorial_imx6_code/bare_metal/led_rgb_c/pad_config.h)【源码下载:https://gitee.com/Embedfire/embed_linux_driver_tutorial_imx6_code.git】,这里使用宏定义了引脚可选的PAD属性值,并且通过宏定义的名字很容易知道宏代表的属性值:

/* SPEED 带宽配置 */
#define SPEED_0_LOW_50MHz       IOMUXC_SW_PAD_CTL_PAD_SPEED(0)
#define SPEED_1_MEDIUM_100MHz   IOMUXC_SW_PAD_CTL_PAD_SPEED(1)
#define SPEED_2_MEDIUM_100MHz   IOMUXC_SW_PAD_CTL_PAD_SPEED(2)
#define SPEED_3_MAX_200MHz      IOMUXC_SW_PAD_CTL_PAD_SPEED(3)


/* PUE 选择使用保持器还是上下拉 */
#define PUE_0_KEEPER_SELECTED       IOMUXC_SW_PAD_CTL_PAD_PUE(0)
#define PUE_1_PULL_SELECTED         IOMUXC_SW_PAD_CTL_PAD_PUE(1)


/* PUS 上下拉配置 */
#define PUS_0_100K_OHM_PULL_DOWN  IOMUXC_SW_PAD_CTL_PAD_PUS(0)
#define PUS_1_47K_OHM_PULL_UP     IOMUXC_SW_PAD_CTL_PAD_PUS(1)
#define PUS_2_100K_OHM_PULL_UP    IOMUXC_SW_PAD_CTL_PAD_PUS(2)
#define PUS_3_22K_OHM_PULL_UP     IOMUXC_SW_PAD_CTL_PAD_PUS(3)

完整的代码请阅读源文件,这里只列出了文件“pad_config.h”部分代码(embed_linux_driver_tutorial_imx6_code/bare_metal/led_rgb_c/pad_config.h)【源码下载:https://gitee.com/Embedfire/embed_linux_driver_tutorial_imx6_code.git】。

十、编写启动文件

/uart 下创建 start.S 文件用于编写启动文件。
在汇编文件中设置“栈地址”并执行跳转命令跳转到main函数执行C代码。

10.1 完整代码

/***********************第一部分*********************/
  .text            //代码段
  .align 2         //设置2字节对齐
  .global _start   //定义一个全局标号

/*************************第二部分*************************/
  _start:          //程序的开始
    b reset      //跳转到reset标号处

/*************************第三部分*************************/
reset:
   mrc     p15, 0, r0, c1, c0, 0     /*  将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中   */
   bic     r0,  r0, #(0x1 << 12)     /*  清除第12位(I位)禁用 I Cache  */
   bic     r0,  r0, #(0x1 <<  2)     /*  清除第 2位(C位)禁用 D Cache  */
   bic     r0,  r0, #0x2             /*  清除第 1位(A位)禁止严格对齐   */
   bic     r0,  r0, #(0x1 << 11)     /*  清除第11位(Z位)分支预测   */
   bic     r0,  r0, #0x1             /*  清除第 0位(M位)禁用 MMU   */
   mcr     p15, 0, r0, c1, c0, 0     /*  将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中   */

/***********************第四部分*********************/
      ldr sp, =0x84000000   //设置栈地址64M
      b main                //跳转到main函数

/***********************第五部分*******************/
    /*进入死循环*/
  loop:
      b loop

10.2 分析代码

  • 第一部分
    .text 定义代码段。
    .align 2 设置字节对齐。
    .global _start 生命全局标号_start。
/*************************第一部分*************************/
.text            //代码段
.align 2         //设置2字节对齐
.global _start   //定义一个全局标号
  • 第二部分
    _start: 定义标号_start: ,它位于汇编的最前面,说以会首先被执行。
    b reset 使用b指令将程序跳转到reset标号处。
/*************************第二部分*************************/
_start:          //程序的开始
   b reset      //跳转到reset标号处
  • 第三部分
    通过修改CP15寄存器(系统控制寄存器) 关闭 I Cache 、D Cache、MMU 等等。
    我们暂时用不到的功能,如果开启可能会影响我们裸机运行,为避免不必要的麻烦暂时关闭这些功能。
/*************************第三部分*************************/
reset:
   mrc     p15, 0, r0, c1, c0, 0     /*  将 CP15 协处理器中的寄存器数据读到 ARM 寄存器中   */
   bic     r0,  r0, #(0x1 << 12)     /*  清除第12位(I位)禁用 I Cache  */
   bic     r0,  r0, #(0x1 <<  2)     /*  清除第 2位(C位)禁用 D Cache  */
   bic     r0,  r0, #0x2             /*  清除第 1位(A位)禁止严格对齐   */
   bic     r0,  r0, #(0x1 << 11)     /*  清除第11位(Z位)分支预测   */
   bic     r0,  r0, #0x1             /*  清除第 0位(M位)禁用 MMU   */
   mcr     p15, 0, r0, c1, c0, 0     /*  将 ARM 寄存器的数据写入到 CP15 协处理器寄存器中   */
  • 第四部分
    ldr sp, =0x84000000 用于设置栈指针。野火i.MX6ULL开发板标配512M的DDR内存,裸机开发用不了这么多。程序中我们将栈地址设置到DDR的64M地址处。 这个值也可以根据需要自行定义。
    b main 只用跳转指令跳转到main函数中执行。
/***********************第四部分*********************/
      ldr sp, =0x84000000   //设置栈地址64M
      b main                //跳转到main函数
  • 第五部分
    b loop 是“无返回”的跳转指令。正常情况下,不会执行第五部分代码。
/***********************第五部分*******************/
  /*进入死循环*/
  loop:
      b loop

十一、编写链接脚本

写好的代码(无论是汇编还是C语言)都要经过编译、汇编、链接等步骤生成二进制文件或者可供下载的文件。在编译阶编译器会对每个源文件进行语法检查并生成对应的汇编语言,汇编是将汇编文件转化为机器码。

使用 arm-none-eabi-gcc -g -c led.S -o led.o 命令完成源码的编译、汇编工作,生成了 .o文件。编译和汇编是针对单个源文件,也就编译完成后一个源文件(.c.S.s)对应一个 .o 文件。程序链接阶段就会将这些 .o 链接成一个文件。

链接脚本的作用就是告诉编译器怎么链接这些文件,比如那个文件放在最前面,程序的代码段、数据段、bss段分别放在什么位置等等。

/uart 下创建 base.lds 链接脚本。

11.1 完整代码

 ENTRY(_start)
 SECTIONS {
   . = 0x80000000;

   . = ALIGN(4);
   .text :
   {
   start.o (.text)
   *(.text)
   }

   . = ALIGN(4);
   .data :
   {
   *(.data)
   }

   . = ALIGN(4);
   .bss :
   {
   *(.bss)
   }
 }

11.2 分析代码

  • 指定程序的入口
    ENTRY(_start) 用于指定程序的入口,ENTRY() 是设置入口地址的命令, “_start” 是程序的入口,led程序的入口地址位于 start.S“_start” 标号处。
 ENTRY(_start)
  • 定义SECTIONS
    SECTIONS 可以理解为是一块区域,我们在这块区域排布我们的代码,链接时链接器就会按照这里的指示链接我们的代码。
 SECTIONS {
···
···
}
  • 定义链接起始地址
    “.” 运算符代表当前位置。 我们在SECTION的最开始使用 “.= 0x80000000” 就是将链接起始地址设置为0x80000000。
. = 0x80000000;
  • 设置字节对齐
    “. = ALIGN(4);” 它表示从当前位置开始执行四字节对齐。假设当前位置为0x80000001,执行该命令后当前地址将会空出三个字节转到0x80000004地址处。

  • 设置代码段
    “.text :” 用于定义代码段,固定的语法要求,我们按照要求写即可。在“{}”中指定那些内容放在代码段。
    start.o 中的代码放到代码段的最前面。start.S是启动代码应当首先被执行,所以通常情况下要把它放到代码段的最前面,其他源文件的代码按照系统默认的排放顺序即可,通配符 “*” 在这里表示其他剩余所有的 .o文件。

   . = ALIGN(4);
   .text :
   {
   start.o (.text)
   *(.text)
   }
  • 设置数据段
    同设置代码段类似,首先设置字节对齐,然后定义代码段。在数据段里使用 “*” 通配符, 将所有源文件中的代码添加到这个数据段中。
   . = ALIGN(4);
   .data :
   {
   *(.data)
   }
  • 设置BSS段
    设置方法与设置数据段完全相同。
. = ALIGN(4);
   .bss :
   {
   *(.bss)
   }

十二、编写makefile文件

程序编写完成后需要依次输入编译、链接、格式转换命令才能最终生成二进制文件。这种编译方式效率低、容易出错。

使用makefile只需要在所在文件夹下执行make命令,makefile工具便会自动完成程序的编译、链接、格式转换等工作。正常情况下我们可以在当前目录看到生成的一些中间文件以及我们期待的.bin文件。

修改makefile主要包括两部分

  • 第一部分,在“device”文件夹下添加并编写子makefile。
  • 第二部分,修改主makefile。

12.1 编写子makefile

/uart/device 下创建 makefile

子makefile: 用于将“device”文件夹下的驱动源文件编译为一个“.o”文件

all : led.o system_MCIMX6Y2.o uart.o
	arm-none-eabi-ld -r $^  -o device.o
	
%.o : %.c
	arm-none-eabi-gcc ${header_file} -c $^
	
%.o : %.S
	arm-none-eabi-gcc ${header_file} -c $^

clean:
	-rm -f *.o *.bak
  • 添加最终目标以及依赖文件
    生成最终目标“device.o”。如果程序中新增了某个外设驱动程序,只需要将对应的“.o”文件填入“依赖”处即可。
    “$^” 代表所有的依赖文件。
    “-o” 指定输出文件名。
all : button.o  led.o system_MCIMX6Y2.o
    arm-none-eabi-ld -r $^  -o device.o
  • 添加编译C文件的命令
    编译“device”文件夹下的所有“.c”文件并生成对应的“.o”文件,其中“header_file”是头文件路径,它是定义在主makefile的变量。
    “$^” 替代要编译的源文件。
%.o : %.c
  arm-none-eabi-gcc ${header_file} -c $^
  • 添加汇编文件编译命令
    编译“device”文件夹下的所有“.S”文件并生成对应的“.o”文件,其中“header_file”是头文件路径,它是定义在主makefile的变量。
    “$^” 替代要编译的源文件。
%.o : %.S
  arm-none-eabi-gcc ${header_file} -c $^
  • 添加清理命令
    “clean” 为目标用于删除make生成的文件。
clean:
  -rm -f *.o *.bak

12.2 修改主makefile

主makefile的改动主要有两点:

  1. 在编译命令中指明头文件位置。
  2. 使用命令调用子makefile,生成依赖文件。
#定义变量,用于保存编译选项和头文件保存路径
header_file := -fno-builtin -I$(shell pwd)/include
export header_file

/*arm-none-eabi 安装位置*/
libgcc_address := /usr/lib/gcc/arm-none-eabi/6.3.1

all : start.o main.o device/device.o 
	arm-none-eabi-ld -Tbase.lds $^ -o base.elf -static -L $(libgcc_address) -lgcc
	arm-none-eabi-objcopy -O binary -S -g base.elf base.bin


%.o : %.S
	arm-none-eabi-gcc -g -c $^ 
%.o : %.c
	arm-none-eabi-gcc $(header_file) -c $^ 	

#调用其他文件的makefile
device/device.o :
	make -C device all


.PHONY: copy
copy:
	cp ./base.bin  /home/pan/download/embedfire

#定义清理伪目标
.PHONY: clean
clean:
	make -C device clean
	-rm -f *.o *.elf *.bin 
  • 添加编译选项和头文件保存路径
    定义变量 “header_file”。在makefile中“变量”更像C原因中的宏定义。
    “-fno-builtin” 是一个编译选项,用于解决库函数与自己编写函数同名问题。
    “-I$(shell pwd)/include” 用于指定头文件路径。
    “export header_file” 声明后可以在其他makefile中调用。
header_file := -fno-builtin -I$(shell pwd)/include
export header_file
  • 添加链接库路径
/*arm-none-eabi 安装位置*/
libgcc_address := /usr/lib/gcc/arm-none-eabi/6.3.1

“libgcc_address” 用于保存”arm-none-eabi”编译工具的安装位置,我们需要的libgcc.a位于该目录下。如果使用”sudo apt-get install gcc-arm-none-eabi”安装则默认位于程序中所指目录,如果使用其他方式安装,找到对应的路径填入该变量即可。

  • 添加最终目标以及依赖文件
all : start.o main.o device/device.o
  • 添加链接命令
    “-Tbase.lds” 表示使用base.lds链接脚本链接程序。
    “$^” 代表所有的依赖文件。
    “-o” 指定输出文件名。
    “-static -L $(libgcc_address) -lgcc” 添加静态链接libgcc.a库文件。
arm-none-eabi-ld -Tbase.lds $^ -o base.elf -static -L $(libgcc_address) -lgcc
  • 添加格式转换命令
    “-O binary” 指定输出二进制文件。
    “-S” 不从源文件中复制重定位信息和符号信息。
    “-g” 不从源文件中复制可调试信息。
arm-none-eabi-objcopy -O binary -S -g base.elf base.bin
  • 添加汇编文件编译命令
    “$^” 替代要编译的源文件。
%.o : %.S
  arm-none-eabi-gcc -g -c $^
  • 添加编译C文件的命令
    “$^” 替代要编译的源文件。
%.o : %.c
  arm-none-eabi-gcc $(header_file) -c $^
  • 添加调用其他文件的makefile
    定义生成“device/device.o”的命令,“device.o”文件由子makefile生成,所以这里只需要调用子makefile即可。
device/device.o :
  make -C device all
  • 添加清理命令
    在清理命令中不但要清理主makefile所在文件夹的内容还要调用子makefile的清理命令以清理子makefile所在文件夹的内容。
    “.PHONY” 定义了伪目标“clean”。伪目标一般没有依赖,并且 “clean” 伪目标一般放在Makefile文件的末尾。
    “clean” 为目标用于删除make生成的文件。
.PHONY: clean
clean:
  make -C device clean
  -rm -f *.o *.elf *.bin

十三、编写C语言代码

13.1 添加串口初始化和接收发送代码

13.1.1 uart.h

/uart/include 下创建 uart.h

#ifndef uart_h
#define uart_h


#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "pad_config.h"

#define  uint32_t  unsigned int
#define  uint64_t  unsigned long int



/*定义 UART1 RX 引脚*/
#define UART1_RX_GPIO                GPIO1
#define UART1_RX_GPIO_PIN            (17U)
#define UART1_RX_IOMUXC              IOMUXC_UART1_RX_DATA_UART1_RX

/*定义 UART1 TX 引脚*/
#define UART1_TX_GPIO              GPIO1
#define UART1_TX_GPIO_PIN          (16U)
#define UART1_TX_IOMUXC            IOMUXC_UART1_TX_DATA_UART1_TX


/*******************************************************************************
 * uart引脚配置
 ******************************************************************************/
#define UART_RX_PAD_CONFIG_DATA            (SRE_0_SLOW_SLEW_RATE| \
                                        DSE_6_R0_6| \
                                        SPEED_1_MEDIUM_100MHz| \
                                        ODE_0_OPEN_DRAIN_DISABLED| \
                                        PKE_1_PULL_KEEPER_ENABLED| \
                                        PUE_1_PULL_SELECTED| \
                                        PUS_3_22K_OHM_PULL_UP| \
                                        HYS_0_HYSTERESIS_DISABLED) 
    /* 配置说明 : */
    /* 转换速率: 转换速率慢
        驱动强度: R0/6 
        带宽配置 : medium(100MHz)
        开漏配置: 关闭 
        拉/保持器配置: 使能
        拉/保持器选择: 上下拉
        上拉/下拉选择: 22K欧姆上拉(选择了保持器此配置无效)
        滞回器配置: 禁止 */ 

#define UART_TX_PAD_CONFIG_DATA            (SRE_0_SLOW_SLEW_RATE| \
                                        DSE_6_R0_6| \
                                        SPEED_1_MEDIUM_100MHz| \
                                        ODE_0_OPEN_DRAIN_DISABLED| \
                                        PKE_1_PULL_KEEPER_ENABLED| \
                                        PUE_0_KEEPER_SELECTED| \
                                        PUS_3_22K_OHM_PULL_UP| \
                                        HYS_0_HYSTERESIS_DISABLED)
    /* 配置说明 : */
    /* 转换速率: 转换速率慢
        驱动强度: R0/6 
        带宽配置 : medium(100MHz)
        开漏配置: 关闭 
        拉/保持器配置: 使能
        拉/保持器选择: 保持器
        上拉/下拉选择: 22K欧姆上拉(选择了保持器此配置无效)
        滞回器配置: 禁止 */ 



void uart_init(void);
int32_t UART_SetBaudRate(UART_Type *base, uint32_t baudRate_Bps, uint32_t srcClock_Hz);

void UART_WriteBlocking(UART_Type *base, const uint8_t *data, uint8_t length);
void UART_ReadBlocking(UART_Type *base, uint8_t *data, uint8_t length);

#endif

13.1.2 uart.c

/uart/device 下创建 uart.c

#include "uart.h"

void uart_init(void)
{
    /*时钟初始化,设置 UART 根时钟,并设置为40MHz*/
    CCM->CSCDR1 &= ~(0x01 << 6); //设置UART选择 PLL3 / 6 = 80MHz
    CCM->CSCDR1 &= ~(0x3F);      //清零
    CCM->CSCDR1 |= (0x01 << 0);  //设置串口根时钟分频值为1,UART根时钟频率为:80M / (dev + 1) = 40MHz

    /*开启 UART1 的时钟*/
    CCM_CCGR5_CG12(0x3); //开启UART1的时钟

    UART1->UCR1 &= ~UART_UCR1_UARTEN_MASK; //禁用 UART1

    /*软件复位*/
    UART1->UCR2 &= ~UART_UCR2_SRST_MASK;
    while ((UART1->UCR2 & UART_UCR2_SRST_MASK) == 0)
    {
    }

    UART1->UCR1 = 0x0;
    UART1->UCR2 = UART_UCR2_SRST_MASK;
    UART1->UCR3 = UART_UCR3_DSR_MASK | UART_UCR3_DCD_MASK | UART_UCR3_RI_MASK;
    UART1->UCR4 = UART_UCR4_CTSTL(32);
    UART1->UFCR = UART_UFCR_TXTL(2) | UART_UFCR_RXTL(1);
    UART1->UESC = UART_UESC_ESC_CHAR(0x2B);
    UART1->UTIM = 0x0;
    UART1->ONEMS = 0x0;
    UART1->UTS = UART_UTS_TXEMPTY_MASK | UART_UTS_RXEMPTY_MASK;
    UART1->UMCR = 0x0;

    /*引脚初始化*/
    IOMUXC_SetPinMux(UART1_RX_IOMUXC, 0);
    IOMUXC_SetPinConfig(UART1_RX_IOMUXC, UART_RX_PAD_CONFIG_DATA);

    IOMUXC_SetPinMux(UART1_TX_IOMUXC, 0);
    IOMUXC_SetPinConfig(UART1_TX_IOMUXC, UART_TX_PAD_CONFIG_DATA);

    /*******uart初始化******/
    /*设置控制寄存器到默认值*/
    UART1->UCR2 |= (1 << 5);  //8位数宽度
    UART1->UCR2 &= ~(1 << 6); //一位停止位
    UART1->UCR2 &= ~(1 << 8); //禁用奇偶校验位

    UART1->UCR2 |= (1 << 2);  //使能发送
    UART1->UCR2 |= (1 << 1);  //使能接收
    UART1->UCR2 |= (1 << 14); //忽略流控

    /* For imx family device, UARTs are used in  mode, so that this bit should always be set.*/
    UART1->UCR3 |= UART_UCR3_RXDMUXSEL_MASK;

    //只有FIFO的数据超过阈值才会产生相应的中断,由于没有使用中断,所以这里将阈值设置为1即可。
    UART1->UFCR = (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置发送FIFO 阀值
    UART1->UFCR = (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置接收FIFO 阀值

    UART1->UCR1 &= ~UART_UCR1_ADBR_MASK; //禁用可变波特率
    // UART1->UCR1 |= UART_UCR1_ADBR_MASK;

    /*波特率设置方式 1 。 使用官方SDK设置波特率函数*/
    UART_SetBaudRate(UART1, 115200, 40000000);

    /*波特率设置方式 2 。 手动计算,填入寄存器*/
    /*设置串口波特率
    * Ref Freq时钟 40MHz
    * UFCR RFDIV   110  0x06 7分频    5.714MHz
    * BaudRate     115200bps
    * UBMR         31-1 = 0x09
    * UBIR         10-1 = 0x1E
    */
    UART1->UFCR &= ~(0x07 << 7); //清零分频值
    UART1->UFCR |= (0x06 << 7);  //设置分频值,40MHz /7 =  5.714MHz

    UART1->UBIR = 0x09;
    UART1->UBMR = 0x1E;

    /*开启串口*/
    UART1->UCR1 |= UART_UCR1_UARTEN_MASK;
}


/*!
 * 功能:官方SDK 串口字符串读取函数
 * @brief Reads the receiver register.
 *
 * This function is used to read data from receiver register.
 * The upper layer must ensure that the receiver register is full or that
 * the RX FIFO has data before calling this function.
 *
 * @param base UART peripheral base address.
 * @return Data read from data register.
 */
static inline uint8_t UART_ReadByte(UART_Type *base)
{
    return (uint8_t)((base->URXD & UART_URXD_RX_DATA_MASK) >> UART_URXD_RX_DATA_SHIFT);
}


/*函数功能:串口接收函数
 *参数: base,指定串口。data,保存接收到的数据。 length,要接收的数据长度
 *
*/
void UART_ReadBlocking(UART_Type *base, uint8_t *data, uint8_t length)
{
    while (length--)
    {
        /* 等待接收完成 */
        while (!(base->USR2 & UART_USR2_RDR_MASK))
        {
        }
        /*读取接收到的数据 */
        *(data++) = UART_ReadByte(base);
    }
}


/*!
 * 功能:官方SDK 串口发送函数
 * 参数:base,指定串口。data,指定要发送的字节
 * This function is used to write data to transmitter register.
 * The upper layer must ensure that the TX register is empty or that
 * the TX FIFO has room before calling this function.
 */
static inline void UART_WriteByte(UART_Type *base, uint8_t data)
{
    base->UTXD = data & UART_UTXD_TX_DATA_MASK;
}

/*
 *功能:官方SDK 串口字符串发送函数
 *参数说明:
*/
void UART_WriteBlocking(UART_Type *base, const uint8_t *data, uint8_t length)
{

    while (length--)
    {
        /* Wait for TX fifo valid.
         * This API can only ensure that the data is written into the data buffer but can't
         * ensure all data in the data buffer are sent into the transmit shift buffer.
         */
        while (!(base->USR2 & UART_USR2_TXDC_MASK))
        {
        }
        UART_WriteByte(base, *(data++));
    }
}

/* 官方SDK 波特率设置函数,
 * 修改内容:修改了函数的返回值,波特率设置成功,返回1 。波特率设置失败返回 0
 *This UART instantiation uses a slightly different baud rate calculation.
 * Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1)).
 * To get a baud rate, three register need to be writen, UFCR,UBMR and UBIR
 * At first, find the approximately maximum divisor of src_Clock and baudRate_Bps.
 * If the numerator and denominator are larger then register maximum value(0xFFFF),
 * both of numerator and denominator will be divided by the same value, which
 * will ensure numerator and denominator range from 0~maximum value(0xFFFF).
 * Then calculate UFCR and UBIR value from numerator, and get UBMR value from denominator.
 */
int32_t UART_SetBaudRate(UART_Type *base, uint32_t baudRate_Bps, uint32_t 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_Bps << 4;
    divisor = 1;

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

    numerator = srcClock_Hz / divisor;
    denominator = (baudRate_Bps << 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_Bps) ? (baudDiff - baudRate_Bps) : (baudRate_Bps - baudDiff);

    if (baudDiff < (baudRate_Bps / 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);
        base->ONEMS = UART_ONEMS_ONEMS(srcClock_Hz / (1000 * divider));

        return 1;
    }
    else
    {
        return 0;
    }
}
  • 第一部分:设置 UART 时钟源
    设置 UART 的时钟源为 pll3_80m,设置寄存器 CCM_CSCDR1 的 UART_CLK_SEL 位为 0 即可。
/*时钟初始化,设置 UART 根时钟,并设置为40MHz*/
CCM->CSCDR1 &= ~(0x01 << 6); //设置UART选择 PLL3 / 6 = 80MHz
CCM->CSCDR1 &= ~(0x3F);      //清零
CCM->CSCDR1 |= (0x01 << 0);  //设置串口根时钟分频值为1,UART根时钟频率为:80M / (dev + 1) = 40MHz

/*开启 UART1 的时钟*/
CCM_CCGR5_CG12(0x3); //开启UART1的时钟

详细查看 IMX6ULL学习笔记(19)——时钟系统

  • 第二部分:初始化 UART
    初始化 UART 所使用 IO,设置 UART1 的寄存器 UARTx_UCR1~UARTx_UCR3,设置内容包括波特率,奇偶校验、停止位、数据位等等。
UART1->UCR1 &= ~UART_UCR1_UARTEN_MASK; //禁用 UART1

/*软件复位*/
UART1->UCR2 &= ~UART_UCR2_SRST_MASK;
while ((UART1->UCR2 & UART_UCR2_SRST_MASK) == 0)
{
}

UART1->UCR1 = 0x0;
UART1->UCR2 = UART_UCR2_SRST_MASK;
UART1->UCR3 = UART_UCR3_DSR_MASK | UART_UCR3_DCD_MASK | UART_UCR3_RI_MASK;
UART1->UCR4 = UART_UCR4_CTSTL(32);
UART1->UFCR = UART_UFCR_TXTL(2) | UART_UFCR_RXTL(1);
UART1->UESC = UART_UESC_ESC_CHAR(0x2B);
UART1->UTIM = 0x0;
UART1->ONEMS = 0x0;
UART1->UTS = UART_UTS_TXEMPTY_MASK | UART_UTS_RXEMPTY_MASK;
UART1->UMCR = 0x0;

/*引脚初始化*/
IOMUXC_SetPinMux(UART1_RX_IOMUXC, 0);
IOMUXC_SetPinConfig(UART1_RX_IOMUXC, UART_RX_PAD_CONFIG_DATA);

IOMUXC_SetPinMux(UART1_TX_IOMUXC, 0);
IOMUXC_SetPinConfig(UART1_TX_IOMUXC, UART_TX_PAD_CONFIG_DATA);

/*******uart初始化******/
/*设置控制寄存器到默认值*/
UART1->UCR2 |= (1 << 5);  //8位数宽度
UART1->UCR2 &= ~(1 << 6); //一位停止位
UART1->UCR2 &= ~(1 << 8); //禁用奇偶校验位

UART1->UCR2 |= (1 << 2);  //使能发送
UART1->UCR2 |= (1 << 1);  //使能接收
UART1->UCR2 |= (1 << 14); //忽略流控

/* For imx family device, UARTs are used in  mode, so that this bit should always be set.*/
UART1->UCR3 |= UART_UCR3_RXDMUXSEL_MASK;

//只有FIFO的数据超过阈值才会产生相应的中断,由于没有使用中断,所以这里将阈值设置为1即可。
UART1->UFCR = (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置发送FIFO 阀值
UART1->UFCR = (UART1->UFCR & ~UART_UFCR_TXTL_MASK) | UART_UFCR_TXTL(1); //设置接收FIFO 阀值

UART1->UCR1 &= ~UART_UCR1_ADBR_MASK; //禁用可变波特率
// UART1->UCR1 |= UART_UCR1_ADBR_MASK;

/*波特率设置方式 1 。 使用官方SDK设置波特率函数*/
UART_SetBaudRate(UART1, 115200, 40000000);

/*波特率设置方式 2 。 手动计算,填入寄存器*/
/*设置串口波特率
* Ref Freq时钟 40MHz
* UFCR RFDIV   110  0x06 7分频    5.714MHz
* BaudRate     115200bps
* UBMR         31-1 = 0x09
* UBIR         10-1 = 0x1E
*/
UART1->UFCR &= ~(0x07 << 7); //清零分频值
UART1->UFCR |= (0x06 << 7);  //设置分频值,40MHz /7 =  5.714MHz

UART1->UBIR = 0x09;
UART1->UBMR = 0x1E;

串口初始化配置了多个寄存器,结合代码和《IMX6ULRM》(参考手册)53.15 UART Memory Map/Register Definition 章节可以查看寄存器的详细介绍。不必花太多时间在这些寄存器,在需要时能够找到即可。

  • 第三部分:使能 UART
    UART 初始化完成以后就可以使能 UART 了,设置寄存器 UARTx_UCR1 的位 UARTEN 为 1。
/*开启串口*/
UART1->UCR1 |= UART_UCR1_UARTEN_MASK;
  • 第四部分:UART 数据接收
    串口接收函数仅实现简单的接收字符串功能,没有使用中断。
/*!
 * 功能:官方SDK 串口字符串读取函数
 * @brief Reads the receiver register.
 *
 * This function is used to read data from receiver register.
 * The upper layer must ensure that the receiver register is full or that
 * the RX FIFO has data before calling this function.
 *
 * @param base UART peripheral base address.
 * @return Data read from data register.
 */
static inline uint8_t UART_ReadByte(UART_Type *base)
{
    return (uint8_t)((base->URXD & UART_URXD_RX_DATA_MASK) >> UART_URXD_RX_DATA_SHIFT);
}


/*函数功能:串口接收函数
 *参数: base,指定串口。data,保存接收到的数据。 length,要接收的数据长度
 *
*/
void UART_ReadBlocking(UART_Type *base, uint8_t *data, uint8_t length)
{
    while (length--)
    {
        /* 等待接收完成 */
        while (!(base->USR2 & UART_USR2_RDR_MASK))
        {
        }
        /*读取接收到的数据 */
        *(data++) = UART_ReadByte(base);
    }
}
  • 第五部分:UART 数据发送
/*!
 * 功能:官方SDK 串口发送函数
 * 参数:base,指定串口。data,指定要发送的字节
 * This function is used to write data to transmitter register.
 * The upper layer must ensure that the TX register is empty or that
 * the TX FIFO has room before calling this function.
 */
static inline void UART_WriteByte(UART_Type *base, uint8_t data)
{
    base->UTXD = data & UART_UTXD_TX_DATA_MASK;
}

/*
 *功能:官方SDK 串口字符串发送函数
 *参数说明:
*/
void UART_WriteBlocking(UART_Type *base, const uint8_t *data, uint8_t length)
{

    while (length--)
    {
        /* Wait for TX fifo valid.
         * This API can only ensure that the data is written into the data buffer but can't
         * ensure all data in the data buffer are sent into the transmit shift buffer.
         */
        while (!(base->USR2 & UART_USR2_TXDC_MASK))
        {
        }
        UART_WriteByte(base, *(data++));
    }
}

13.2 main.c

/uart 下创建 main.c

main函数中实现“发送接收到的数据”功能。

#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
#include "pad_config.h"

#include "uart.h"

/*简单延时函数*/
void delay(uint32_t count)
{
    volatile uint32_t i = 0;
    for (i = 0; i < count; ++i)
    {
        __asm("NOP"); /* 调用nop空指令 */
    }
}

/*提示字符串*/
uint8_t txbuff[] = "Uart polling example\r\nBoard will send back received characters\r\n";
int main()
{
    uint8_t ch; //用于暂存串口收到的字符

    uart_init();
    UART_WriteBlocking(UART1, txbuff, sizeof(txbuff) - 1);

    while (1)
    {
        UART_ReadBlocking(UART1, &ch, 1);
        if (ch == '\r') /*添加回车换行\n\r*/
        {
        UART_WriteBlocking(UART1, '\n', 1);
        }
        if (ch == '\n')
        {
        UART_WriteBlocking(UART1, '\r', 1);
        }
        UART_WriteBlocking(UART1, &ch, 1);
    }

    return 0;
}

十四、编译下载验证

14.1 编译代码

make

执行make命令,生成base.bin文件。

14.2 代码烧写

编译成功后会在当前文件夹下生成.bin文件,这个.bin文件也不能直接放到开发板上运行, 这次是因为需要在.bin文件缺少启动相关信息。

为二进制文件添加头部信息并烧写到SD卡。查看 IMX6ULL学习笔记(12)——通过SD卡启动官方SDK程序

进入烧写工具目录,执行 ./mkimage.sh <烧写文件路径> 命令,例如要烧写的 base.bin 位于 home 目录下,则烧写命令为 ./mkimage.sh /home/button.bin

执行上一步后会列出linux下可烧写的磁盘,选择你插入的SD卡即可。这一步 非常危险!!!一定要确定选择的是你插入的SD卡!!,如果选错很可能破坏你电脑磁盘内容,造成数据损坏!!! 确定磁盘后SD卡以“sd”开头,选择“sd”后面的字符即可。例如要烧写的sd卡是“sdb”则输入“b”即可。

14.3 实验现象

使用USB数据线连接电脑和开发板的USB转串口接口,接通电源,打开串口调试助手,正常情况下可以串口调试助手可以收到来自开发板的提示信息,通过串口调试助手发送字符会立即收到发送的字符。


• 由 Leung 写于 2023 年 3 月 29 日

• 参考:12. UART—串口通讯

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

IMX6ULL学习笔记(20)——UART串口使用 的相关文章

  • 没有与这些操作数匹配的运算符

    没有与这些操作数匹配的 lt lt 运算符 include与 include lt string h gt 的区别 lt string h gt 的区别 是C 43 43 特化的字符容器 xff0c 内含string类 lt string
  • gazebo模型下载以及配置

    最近在学习ROS xff0c 主要是为了结合SLAM仿真使用 启动gazebo命令 roscore 在另一个终端执行 gazebo 就可以进入清爽的gazebo界面 xff08 如果屏幕出现黑屏并不是安装错误可以稍微等待一会 xff09 x
  • SLAM中常用数据集下载链接(TUM KITTI DSO Mono EuRoC)

    TUM 链接 xff1a https pan baidu com s 1nwXtGqH 密码 xff1a lsgr KITTI 链接 xff1a https pan baidu com s 1htFmXDE 密码 xff1a uu20 KI
  •  windows docker 更改镜像安装目录

    目录 1 问题 1 1 版本信息 2 修改Docker盘位操作 2 1 停止docker 2 2 备份已有的数据 2 3 删除旧数据 数据未备份前请谨慎操作 2 4 导入数据到新盘 2 5 启动Docker START 1 问题 Windo
  • gnssins代码阅读

    这个代码是GNSS和INS紧组合的 xff1a https github com marcoamm gnssins xff0c 实现了ppp和ins紧组合 改变数据需要改代码的地方 xff1a imu tactical 61 fopen 3
  • 深度解析FUTABA的SBUS协议(/天地飞遥控器的WBUS协议/Robomaster接收机的DBUS协议)到底是啥?

    写在前面 xff1a 无论是SBUS xff08 日本FUTABA xff0c 所以航模 xff0c 车模爱好者都知道的公司 xff0c 一个好点遥控器近万了 xff09 xff0c 还是WBUS xff08 天地飞遥控器接收机用 xff0
  • 贝塞尔曲线动画C++简单实践

    目录 贝塞尔曲线简介一阶贝塞尔二阶贝塞尔三阶贝塞尔N阶贝塞尔曲线 贝塞尔曲线在动画中的应用实践求曲线散点坐标将曲线应用到动画动画框架cmd动画窗口动画 完整代码示例代码核心类代码BezierCurve Animator Console 参考
  • package.xml文件介绍

    package xml文件介绍 在ROS中创建功能包时 xff0c 会自动生成package xml文件 xff0c pacakge xml 包含了package的名称 版本号 内容描述 维护人员 软件许可 编译构建工具 编译依赖 运行依赖
  • ubuntu误修改了bashrc文件的解决办法

    在安装Pycharm的过程中配置JAVA的JDK环境变量时 xff0c 将bashrc内的内容不小心修改了 xff0c 导致命令窗口中的很多命令不能执行 xff0c 并且su及sudo这些权限的命令也用不了 xff0c 问题信息如下图所示
  • docker安装canal1.1.5监控mysql的binlog日志并配置rocketmq进行数据同步到elasticsearch(超级大干货)

    直接来 xff0c 不逼逼 xff08 canal官网说的很明白 xff0c 伪从节点请求dump 然后这个那个的 xff0c 自行查阅资料 xff09 1 直接拉取canal镜像 docker pull canal canal serve
  • 相机定位、相机重定位和视觉里程计的概念定义

    相机定位 相机重定位和视觉里程计的概念定义 什么是相机定位 xff1f 什么是相机重定位 xff1f 什么是视觉里程计 xff1f 相机定位 相机定位 xff08 Camera Localization xff09 是求解基于基本坐标系下的
  • vscode使用restClient实现各种http请求

    vscode使用restClient实现各种http请求 一 xff0c 安装插件 首先 xff0c 我们要在vscode的扩展中 xff0c 搜索rest Client xff0c 然后安装它 xff0c 这里我已经安装过了 安装后 xf
  • akka-2 利用模式匹配,实现worker节点向master报告本机配置信息

    1 创建本机配置信息类 WorkInfo Worker的本机信息类 id 主机名称 momery 内存大小 cores CPU核数 直接使用默认构造创建消息类 Worker的本机信息类 id 主机名称 momery 内存大小 cores C
  • 多路归并排序-Python实现大文件排序,合并排序

    使用python实现多 K 路归并外部排序 xff0c 解决小内存排序大文件问题 上一篇中 xff0c 我们实现了一般的归并排序 归并排序递归与非递归 Python实现 在实际工作中 xff0c 多个有序数列合并成一个 xff0c 大文件或
  • GINAV使用

    在配置文件中 xff0c 绝对不能 psd gyro 61 8 46e 14 等号后面必须要有空格才能区分开 psd gyro 61 8 46e 14 运行谷歌数据直接是空的图像
  • ZED双目相机(c++程序实现)

    1 前提条件 SDK最新版 xff08 从官网直接下载安装 xff0c 默认安装路径 xff09 CUDA xff08 安装对应版本 xff0c 此步需要电脑GPU支持 xff08 此步劝退一部人 xff09 xff09 下载zed示例包
  • fork()使用详解

    其他参考 xff1a linux中fork xff08 xff09 函数详解 一 fork入门知识 进程的定义 xff1a 进程是一个执行中的程序的实例 xff0c 是系统进行资源分配和调度的一个独立单位 PCB是进程存在的唯一标识 PCB
  • conda 安装包出现问题 :Collecting package metadata (current_repodata.json): failed

    如果有重装过anaconda的经历的同学 xff0c 应该注意到会有一个 condarc文件会自动生成 xff0c 所以当使用conda install和conda create命令会出现问题 删除 condarc文件之后 xff0c 这个
  • DockerFile+Python2安装和python3和Pip2安装request

    dockerfile 的使用步骤 sudo docker build t cve 11890 cve 11890是文件名 docker images 查看文件是否成功创建 sudo docker run d p 8089 80 cve 11
  • Linux关闭启用图形界面

    Linux关闭启用图形界面 关闭图形界面 sudo su root systemctl set default multi user target sudo service lightdm stop shutdown r 重启命令 xff0

随机推荐