嵌入式Linux开发8——UART串口通讯

2023-05-16

1. 背景知识

1.1 UART通讯格式

  串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线路简单。使用两条线即可实现双向通信,一条用于发送,一条用于接收。UART为异步串行收发器。
  数据位:数据位就是实际要传输的数据,数据位数可选择 5~8 位,我们一般都是按照字节传输数据的,一个字节 8 位,因此数据位通常是 8 位的。低位在前,先传输,高位最后传输。
  具体通讯格式如下图所示:
在这里插入图片描述

1.2 UART电平标准

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

1.3 I.MX6U UART简介

  I.MX6U 一共有 8 个 UART,其主要特性如下:
① 兼容 TIA/EIA-232F 标准,速度最高可到 5Mbit/S。
② 支持串行 IR 接口,兼容 IrDA,最高可到 115.2Kbit/s。
③ 支持 9 位或者多节点模式(RS-485)。
④ 1或2位停止位。
⑥ 可编程的奇偶校验(奇校验和偶校验)。
⑦ 自动波特率检测(最高支持 115.2Kbit/S)
  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。
  通过设置寄存器UARTx_UFCR 、 UARTx_UBIR 和 UARTx_UBMR,可以得到我们想要的波特率。寄存器UARTx_UFCR 中我们要用到的是位 RFDIV(bit9:7),用来设置参考时钟分频,而UBMR、UBIR直接使用UARTx_UBIR 和 UARTx_UBMR寄存器中的值,计算公式如下:
在这里插入图片描述
  最后来看一下寄存器 UARTx_URXD 和 UARTx_UTXD,这两个寄存器分别为 UART的接收和发送数据寄存器,这两个寄存器的低八位为接收到的和要发送的数据。读取寄存器UARTx_URXD 即可获取到接收到的数据,如果要通过 UART 发送数据,直接将数据写入到寄存器 UARTx_UTXD 即可。
  综上所述,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 的数据收发操作。

2. 代码编写

  我们在UART的c文件中总共编写10个函数, uart_init用于初始化 UART1 相关的 IO、并且设置 UART1的波特率、字长、停止位和校验模式等,最后使能UART1。uart_io_init,用于初始化 UART1 所使用的 IO。uart_setbaudrate是从NXP官方的SDK包中移植过来的,用于设置波特率。uart_disable 和uart_enable,分别是使能和关闭 UART1。第 6 个函数是 uart_softreset,用于软件复位指定的 UART。第七个函数是putc,用于通过UART1发送一个字节的数据。第八个函数是puts,用于通过UART1发送一串数据。第九个函数是 getc,用于通过 UART1 获取一个字节的数据,最后一个函数是raise,这是一个空函数,防止编译器报错。


/*
 * @description : 初始化串口1,波特率为115200
 * @param		: 无
 * @return		: 无
 */
void uart_init(void)
{
	/* 1、初始化串口IO 			*/
	uart_io_init();

	/* 2、初始化UART1  			*/
	uart_disable(UART1);	/* 先关闭UART1 		*/
	uart_softreset(UART1);	/* 软件复位UART1 		*/

	UART1->
UCR1 = 0;		/* 先清除UCR1寄存器 */
	
	/*
     * 设置UART的UCR1寄存器,关闭自动波特率
     * bit14: 0 关闭自动波特率检测,我们自己设置波特率
	 */
	UART1->UCR1 &= ~(1<<14);
	
	/*
     * 设置UART的UCR2寄存器,设置内容包括字长,停止位,校验模式,关闭RTS硬件流控
     * 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寄存器
     * bit2: 1 必须设置为1!参考IMX6ULL参考手册3624页
	 */
	UART1->UCR3 |= 1<<2; 
	
	/*
	 * 设置波特率
	 * 波特率计算公式:Baud Rate = Ref Freq / (16 * (UBMR + 1)/(UBIR+1)) 
	 * 如果要设置波特率为115200,那么可以使用如下参数:
	 * Ref Freq = 80M 也就是寄存器UFCR的bit9:7=101, 表示1分频
	 * UBMR = 3124
 	 * UBIR =  71
 	 * 因此波特率= 80000000/(16 * (3124+1)/(71+1))=80000000/(16 * 3125/72) = (80000000*72) / (16*3125) = 115200
	 */
	UART1->UFCR = 5<<7; //ref freq等于ipg_clk/1=80Mhz
	UART1->UBIR = 71;
	UART1->UBMR = 3124;

#if 0
	 uart_setbaudrate(UART1, 115200, 80000000); /* 设置波特率 */
#endif

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

/*
 * @description : 初始化串口1所使用的IO引脚
 * @param		: 无
 * @return		: 无
 */
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);	/* 复用为UART1_TX */
	IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX,0);	/* 复用为UART1_RX */

	/* 2、配置UART1_TX_DATA、UART1_RX_DATA的IO属性 
 	*bit 16:0 HYS关闭
 	*bit [15:14]: 00 默认100K下拉
 	*bit [13]: 0 keeper功能
 	*bit [12]: 1 pull/keeper使能
 	*bit [11]: 0 关闭开路输出
 	*bit [7:6]: 10 速度100Mhz
 	*bit [5:3]: 110 驱动能力R0/6
 	*bit [0]: 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
 * @return		: 无
 */
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); //要先写UBIR寄存器,然后在写UBMR寄存器,3592页 
        base->UBMR = UART_UBMR_MOD(numerator / divider - 1);
    }
}

/*
 * @description : 关闭指定的UART
 * @param - base: 要关闭的UART
 * @return		: 无
 */
void uart_disable(UART_Type *base)
{
	base->UCR1 &= ~(1<<0);	
}

/*
 * @description : 打开指定的UART
 * @param - base: 要打开的UART
 * @return		: 无
 */
void uart_enable(UART_Type *base)
{
	base->UCR1 |= (1<<0);	
}

/*
 * @description : 复位指定的UART
 * @param - base: 要复位的UART
 * @return		: 无
 */
void uart_softreset(UART_Type *base)
{
	base->UCR2 &= ~(1<<0); 			/* UCR2的bit0为0,复位UART  	  	*/
	while((base->UCR2 & 0x1) == 0); /* 等待复位完成 					*/
}

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

/*
 * @description : 发送一个字符串
 * @param - str	: 要发送的字符串
 * @return		: 无
 */
void puts(char *str)
{
	char *p = str;

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

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

/*
 * @description : 防止编译器报错
 * @param 		: 无
 * @return		: 无
 */
void raise(int sig_nr) 
{

}

  我们只需要在main.c函数中调用uart_init,而后在while循环中接受PC机传来的字符并将其回发,在PC端的串口调试助手中显示出来。

/*
 * @description	: main函数
 * @param 		: 无
 * @return 		: 无
 */
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;
}

  最终,我们在串口调试程序SecureCRT中发现I.MX6U的串口1能够正常工作起来。
在这里插入图片描述

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

嵌入式Linux开发8——UART串口通讯 的相关文章

  • 【环境配置】Visual Studio opencv配置

    需求 在Visual Studio环境中编写C 43 43 代码 xff0c 同时可以调用OpenCV的相关代码 1 安装OpenCV 访问 opencv 官网下载对应平台的库文件 注意 xff1a Visual Studio和OpenCV
  • MySQL常见用法

    文章目录 一 时间类1 1 DATE SUB 函数1 2 NOW CURDATE CURTIME DATE 函数1 3 实战 二 统计类三 字符类3 1 LOCATE 函数3 2 concat 函数3 3 concat ws 函数3 4 g
  • 牢记公式,ardupilot EKF2就是纸老虎(四)!

    版权声明 xff1a 本文为博主原创文章 xff0c 转载请附上博文链接 xff01 四 一睹EKF2芳容 因为篇幅过长 xff0c 写的一些公式会乱码 xff0c 没办法只能把 牢记公式 xff0c ardupilot EKF2就是纸老虎
  • Java Optional使用

    文章目录 Optional一 Optional 简介二 创建 Optional 实例2 1 empty 方法2 2 of 方法2 3 ofNullable 方法 三 Optional的使用3 1 访问 Optional 对象的值3 1 1
  • 正则表达式:基础详解以及在Java中的使用

    文章目录 一 正则表达式1 1 正则表达式中的特殊字符1 2 正则表达式所支持的合法字符1 3 方括号表达式1 4 边界匹配符1 5 三种模式的数量表示符 二 应用2 1 String 类2 2 Pattern 类和 Matcher 类 一
  • Python学习:关键字global和nonlocal的用法说明

    一 global global关键字用来在函数或其他局部作用域中使用全局变量 1 1 如果局部要对全局变量修改 xff0c 而不使用global关键字 count 61 0 def global test count 43 61 1 pri
  • Python:flask框架下前后端的数据交互

    文章目录 前提 一 前端发送数据 xff0c 后端接受数据1 1 路由传参数 数据 1 2 表单提交 二 后端发送数据 xff0c 前端接受数据 前提 后端 xff1a python 的 flask 框架 前端 xff1a html css
  • Python关于None的报错:'NoneType' object is not iterable和cannot unpack non-iterable NoneType object

    文章目录 一 TypeError 39 NoneType 39 object is not iterable xff08 类型错误 xff1a 39 NoneType 39 对象不是可迭代的 xff09 二 TypeError cannot
  • Git:合并分支----git merge命令应用的三种情景

    文章目录 一 git merge 命令应用的三种情景1 1 快进 无冲突 1 2 非 快进 xff0c 修改不同文件 无冲突 1 3 非 快进 xff0c 修改相同文件 有冲突 一 git merge 命令应用的三种情景 1 1 快进 无冲
  • Git:远程分支----git fetch命令的使用

    git fetch 命令的使用 从远程主机克隆 Git 的 clone 命令会为你自动将远程主机命名为 origin xff0c 拉取它的所有数据 xff0c 创建一个指向它的 master 分支的指针 xff0c 并且在本地将其命名为 o
  • Git:移除文件----git rm命令的使用

    文章目录 一 git rm 命令使用1 1 rm 命令1 2 git rm 命令1 3 git rm f 命令1 4 git rm cached 命令 一 git rm 命令使用 Git 本地数据管理 xff0c 大概可以分为三个区 xff
  • 【OpenMv小车】OpenMv追小球的小车之pid调用

    pid py gt gt https github com wagnerc4 flight controller blob master pid py openmv 官网 xff1a http book openmv cc project
  • 【深入理解C++】函数模板作为成员函数

    文章目录 1 普通类的成员函数模板2 类模板的成员函数模板 1 普通类的成员函数模板 不管是普通类还是类模板 xff0c 它们的成员函数都可以是函数模板 xff0c 称为成员函数模板 xff0c 但不可以是虚函数 xff0c 否则编译器报错
  • QGroundControl开发之使用自定义mavlink

    工具 对QGC进行二次开发时 xff0c 常常会遇到想使用自定义mavlink的情况 xff0c 但不像APM那样编译命令会根据xml文件自动生成mavlink协议 QGC似乎不能自动生成mavlink协议 xff08 之前试过似乎不能自动
  • 字符串连接 (c语言)

    题目描述 将给定的字符串连接起来 书中的算法描述如下 xff1a 图 xff1a 字符串连接算法 输入描述 三对字符串 xff0c 每对字符串占一行 xff0c 用空格隔开 每个字符串只包含数字和英文字母大小写且长度不超过100 输出描述
  • STM32—UART中断收发 Day4

    软件 xff1a STM32CubeMX xff0c MDK ARM 硬件 xff1a 蓝桥杯物联网Lora开发板 xff0c 板载芯片STM32L071 一 STM32CubeMX配置 1 先在连接 xff08 Connectivity
  • 虚拟机出现command XXX is available in /bin/ls问题

    问题 xff1a 使用本地的shell命令时候 The command could not be located because 39 usr bin bin 39 is not included in the PATH environme
  • 全志lichee的pack命令

    全志lichee目录打包命令流程 pack 将打包命令传进去build sh脚本里面 查看buildsh里面的脚本命令 其实里面的脚本还是较为简单地的 xff0c 仅仅是作为一个过渡 xff0c 然后就跑进去buildroot script
  • Linux_kernel驱动之GPIO子系统

    前言 xff1a gpio子系统的内容在drivers gpio文件夹下 xff0c 主要文件有 xff1a devres c xff1a devres c是针对gpio api增加的devres机制的支持gpiolib c xff1a g
  • 转载:全志问题解决方法

    版权声明 xff1a 本文为博主原创文章 xff0c 遵循 CC 4 0 BY SA 版权协议 xff0c 转载请附上原文出处链接和本声明 本文链接 xff1a https blog csdn net yanzheng1113 articl

随机推荐