一个嵌入式设备,串口基本上就是最常用到的外设了,通过串口可以将开发板和电脑连接,也有很多外设是通过串口来进行数据交互的。今天就来搞一下I.MX6UL的串口通讯,实现和电脑通讯的效果。

UART接口

I.MX6UL的串口外设叫做UART(Universal Asynchronous Receiver/Trasmitter),即异步串行收发器。UART作为串口的一种,其工作原理也是将数据位一帧一帧的进行传输,数据的发送和接收共用一条线缆,所以UART接口与外设相连的时候最少需要3根线:TXD、RXD和GND。下面的图是UART的通讯格式

十二.UART串口通讯

起始位(StartBit)是一个逻辑0

空闲位:在起始位前面的状态为逻辑1,表示没有数据,空闲中。

数据位:实际要传输的数据,一般是按照字节阐述,一个字节8位,低位在前先传输,高位在后面传输。

停止位(StopBit)逻辑1,位数可以是1、1.5或2个bit。

UART电平标准

UART一般接口电平有TTL和RS232电平,开发板上的TXD和RXD对应的就是TTL电平,使用低电平表示0,高电平表示1;而DB9接口就是对应的RS232接口,用-3~-15V表示逻辑1,+3~+15V表示逻辑0,采用差分线连接。在使用RS232时候一定要注意接口电平,不要烧毁外设。

由于现在电脑上基本都不带COM口,而在写单片机什么的需要串口,这就需要一个USB转TTL电平的芯片。最常用的就是CH340。比如Arduino(nano版)的的背后就有个CH340C。用过这个芯片和USB连接就可以实现串口功能(很多USB转232的设备就是用的这个芯片)。

I.MX6UL的UART接口

I.MX6UL提供了8组UART接口,结构体如下:

十二.UART串口通讯

 

 具备如下特点:

  • 兼容TIA/EIA-232-F标准,最高速率5.0M/s
  • 支持串行IR接口,兼容IrDA,最高速率115200bps
  • 支持9位或多点的RS-485模式
  • 232可以选择7或8位的字符格式,485模式9bit格式
  • 停止位1或2个bit
  • 可编程的奇偶校验
  • 最高到115200bit/s自动波特率检测
  • 等等等等,太多了

IMX.6UL的UART的功能有非常多,我们这里只用做最基础的串口通讯功能,具体的实际作用参考手册Chapter 55给了非常详细的介绍。

主要寄存器

UART相关寄存器也比较多,因为Soc一共有8组UART,这里截取;一组的寄存器映射

十二.UART串口通讯

 

 但是要注意的是,虽然8组UART里各个寄存器功能序列是一样的,但是这个内存映射不是从UART1开始的,而是从UART7开始的。

下面看看几个我们要用到的寄存器

UARTx_URXD

接收数据寄存器UART Receiver Register,寄存器结构如下:

十二.UART串口通讯

 

 

 寄存器全部为只读,我们主要用到就是最后的低8位,用来存储接收的数据。另外,bit[10]=1时可以在RS485模式下,数据结构为9bit时保存第九个bit的数据

UARTx_UTXD

发送数据寄存器UART Transmitter Register,用来存放待发送的数据。

十二.UART串口通讯

 

 

 寄存器低8位有效,在7bit数据结构下,bit[7]可以忽略,如果想要将数据写入该寄存器,需要确认TRDY(UARTx_UCR1[13])必须为高电平,即当前没有数据被发送。

UARTx_UCR1

控制寄存器1(UART Control Register 1)UART提供了4组控制寄存器用来对其进行功能设置,首先是UCR1,先看寄存器结构

十二.UART串口通讯

 

 

ADEN(bit[15])Automatic Baud Rate Detection Interrupt Enable,自动波特率侦测中断使能 ,允许ADET标志位(UARTx_USR2 bit[15])触发中断

ADBR(bit[14])Automatic Detection of Baud Rate,自动检测波特率使能,大概意思就是当该位值为1且ADET被清除时,接收器通过接收一个字符A或者a,对比其ASCII码为0x41或0x61,去确认合适的比特率。

TRDYEN(bit[13])Transmitter Ready Interrupt Enable,数据发送准备中断使能

IDEN(bit[12])Idle Condition Detected Interrupt Enable,一个什么中断使能,这个暂时没搞懂,暂时应该也用不上

中间几个中断使能就不说了,最后一个就是UART的使能UARTEN(bit[0]),整个寄存器我们暂时应该也就是能用到这一个bit。(自动获取波特率只能到115200,先关闭不用)

UARTx_UCR2
控制寄存器2,寄存器结构如下

十二.UART串口通讯

 

 

 手册上有很详细的解释,这里只说一下需要用到的几个

IRTS(bit[14])Ignore RTS Pin,1时忽略RTS引脚,我们在使用TTL电平串口信号时只用到RXD和TXD,RTS和CTS一般是不使用的,设置为1即可。

PREN(bit[8])Parity Enable,校验使能,1时使能校验功能

PROE(bit[7])Parity Odd/Even,校验方式:1为奇校验,0为偶校验

STPB(bit[6])停止位,0时停止位1bit,1时2bit

WS(bit[5])Word Size,数据位长度,0时7bit,1时8bit(该长度不包含起始、结束及校验位)

TXEN(bit[2])Transmitter Enable,发送数据使能,1时使能

RXEN(bit[1])Receiver Enable,接收数据使能

SRET(bit[0])Software Reset,软件复位,写0时对FIFO,USR1,USR2,UBIR,UBMR,UBRC,URXD,UTXD和UTS[6:3]进行复位,但复位前保留4个时钟周期用来进行其他的操作。复位后该位自动置1。

UARTx_UCR3

控制寄存器3

十二.UART串口通讯

 

 这个我们只用到了一个RXDMUXSEL,因为手册上说了这个应给被置1

十二.UART串口通讯

 

其他的位我们暂时也都用不到。

UARTx_UFCR

缓存控制寄存器UART FIFO Control Register,这里我们主要用来设置分频器

十二.UART串口通讯

 

RFDIV(bit[9:7])里定义了从CCM过来的时钟的分频

十二.UART串口通讯

 

 注意这个分频不是按照数值+1的模式进行分频的,看具体的值,这个分频器决定的UART的参考时钟

UARTx_USR2

状态寄存器1我们也用不到,这里要用到状态寄存器2

十二.UART串口通讯

 

ADET(bit[15])Automatic Baud Rate Detect Complete,波特率检测完毕,当1时接收到合适的A或者a字符,需要写1清除状态

TXFE(bit[14])Transmit Buffer FIFO Empty,发送缓存状态,1时表示缓存区为空

TXDC(bit[3])Transmitter Complete,发送完成标志位,1时表示发送数据完成,发送寄存器或发送缓存写入数据,该位自动清零

RDR(bit[0])Receive Data Ready,数据接收标志位,为1时表示至少还有1个数据要接收

UARTx_UBIR和UARTx_UBMR

用来凑波特率的两个寄存器,参考手册第55.5章节介绍了波特率的计算方法

十二.UART串口通讯

 

 RefFreq就是经过分频后的参考时钟,比如我们时钟为80MHz,分频为1分频,想要用115200的波特率,就要自己凑了,正点原子给出的数据是UBMR=3124,UBIR=71,那么

十二.UART串口通讯

 

其实NGP给了个函数,可以根据我们需要的波特率计算出对应的参数。

UART使用

使用UART的流程和其他的外设差不多也是先初始化、再使用

时钟源设置

有一点要注意:修改时钟树对应的时钟源,UART和其他的外设用到的不是一个时钟源,我们前面的用到的都是IPG_CLK,UART用到的的是UART_CLK_ROOT

十二.UART串口通讯

 

我们需要通过CSCDR1选择6分频的pll3(480MHz),也就是80MHz,后面分频器为1分频。根据手册可以查出,UART_CLK_SEL为bit[6],值应为1,分频器UART_CLK_PODR对应bit[5:0],对应2^6+1分频,1分频值为0。

所以要修改我们的clk初始化函数clk_init

/*--------------------------UART_CLK设置--------------------------*/
    /*UART_CLK_ROOT主频设置为80MHz*/
    CCM->CSCDR1 &= ~(1<<6);                //CSCDR1[UART_CLK_SEL](bit[6])=0,时钟源80MHz
    CCM->CSCDR1 &= ~(7<<0);                //CSCDR1[UART_CLK_PODF](bit[5:0])设置为0,对应1分频
/*-------------------------UART_CLK设置完毕------------------------*/

这步一定要记得!否则波特率就乱了!我在调试的时候就是忘了这一步!

UART初始化

UART的初始化包括IO的复用设置、UART参数设置、波特率设置。主要就是设置UCR1、UCR2、UCR3、UFCR、UBIR、UBMR几个寄存器。在设置寄存器值时,应该按照下面的顺序

  • 关闭串口功能(UARTEN=0)
  • 复位UART(SRET=0),复位时等待SRET为1,即复位完毕
  • 设置相关寄存器的值
  • 使能UART

配置寄存器的过程如下:

    /*配置UART1*/
    UART1->UCR1 = 0;
    // UART1->UCR1 &= ~(1<<14);

    /*配置UCR2*/
    UART1->UCR2 = 0;                                 //清除UCR0
    UART1->UCR2 |= (1<<1) |(1<<2) |(1<<5)|(1<<14);   //从左起:RXEN=1 TXEN=1 WS=1 IRTS=1
                                                     //接收、发送使能、数据长度为8bit 忽略RTS引脚
    /*配置UCR3*/
    UART1->UCR3 |= (1<<2);                           //RXDMUXSEL=1

    //波特率设置115200
    UART1->UFCR &= ~(7<<7);                          //RFDIV进行清零
    UART1->UFCR = 5<<7;                              //设置1分频,uart_clk=80MHz

    UART1->UBIR = 71;
    UART1->UBMR = 3124;

其实还是比较简单的。

其他的几个关闭、使能等函数放在最后。

数据接收、发送

数据的发送、接收就是对URXD、UTXD的低8位进行操作

/**
 * @brief           通过UART1发送1个字符
 * 
 * @param c         待发送的字符
 */
void putc(unsigned char c)
{
    while(((UART1->USR2 >>3) & 0x01) == 0);     //等待前一个发送流程完毕
    UART1->UTXD = (c & 0xFF);
}

/*通过UART1接收一个字符*/
unsigned char getc(void)
{
    while(((UART1->USR2)&0x01) == 0);          //等待前一个接收流程完毕
    return UART1->URXD;
}

/**
 * @brief           发送字符串
 * 
 * @param str       待发送的字符串 
 */
void puts(unsigned *str)
{
    char *p = str;
    while(*p){
        putc(*p++);
    }
}

这样就完成了所有的功能定义。

文件结构:

UART功能的文件结构和其他外设一样

十二.UART串口通讯

 

 两个文件如下:

/**
 * @file bsp_uart.c
 * @author your name (you@domain.com)
 * @brief uart功能定义
 * @version 0.1
 * @date 2022-01-17
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include "bsp_uart.h"

//初始化uart1,波特率固定为115200
void uart_init(void)
{
    uart_io_init();             //IO初始化
    uart_disable(UART1);        //关闭串口
    uart_softreset(UART1);      //复位UART1

    /*配置UART1*/
    UART1->UCR1 = 0;
    // UART1->UCR1 &= ~(1<<14);

    /*配置UCR2*/
    UART1->UCR2 = 0;                                 //清除UCR0
    UART1->UCR2 |= (1<<1) |(1<<2) |(1<<5)|(1<<14);   //从左起:RXEN=1 TXEN=1 WS=1 IRTS=1
                                                     //接收、发送使能、数据长度为8bit 忽略RTS引脚
    /*配置UCR3*/
    UART1->UCR3 |= (1<<2);                           //RXDMUXSEL=1

    //波特率设置115200
    UART1->UFCR &= ~(7<<7);                          //RFDIV进行清零
    UART1->UFCR = 5<<7;                              //设置1分频,uart_clk=80MHz

    UART1->UBIR = 71;
    UART1->UBMR = 3124;

    uart_enable(UART1);         //使能UART1
}

/**
 * @brief IO初始化为UART
 * 
 */
void uart_io_init(void)
{   
    IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX,0);//复用为UART1_TX 
    IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX,0x10b0);

    IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX,0);
    IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX,0x10b0);
}


/**
 * @brief           关闭UART串口
 * 
 * @param base      UART结构体
 */
void uart_disable(UART_Type *base)
{
    base->UCR1 &= (1<<0);
}


/**
 * @brief           使能UART串口
 * 
 * @param base      UART结构体
 */
void uart_enable(UART_Type *base)
{
    base->UCR1 |= (1<<0);
}


/**
 * @brief           UART软复位
 * 
 * @param base      UART结构体
 */
void uart_softreset(UART_Type *base)
{
    base->UCR2 &= ~(1<<0);                     //SRET=0
    while((base->UCR2 & 0x01) == 0 );          //复位完毕,SRET=1
}


/**
 * @brief           通过UART1发送1个字符
 * 
 * @param c         待发送的字符
 */
void putc(unsigned char c)
{
    while(((UART1->USR2 >>3) & 0x01) == 0);     //等待前一个发送流程完毕
    UART1->UTXD = (c & 0xFF);
}

/*通过UART1接收一个字符*/
unsigned char getc(void)
{
    while(((UART1->USR2)&0x01) == 0);          //等待前一个接收流程完毕
    return UART1->URXD;
}

/**
 * @brief           发送字符串
 * 
 * @param str       待发送的字符串 
 */
void puts(unsigned *str)
{
    char *p = str;
    while(*p){
        putc(*p++);
    }
}
bsp_uart.c

相关文章:

  • 2021-06-10
  • 2021-05-30
  • 2022-12-23
  • 2021-11-18
  • 2021-10-19
  • 2022-12-23
  • 2021-09-11
  • 2021-06-15
猜你喜欢
  • 2021-10-31
  • 2022-01-12
  • 2021-04-22
  • 2021-04-04
  • 2021-07-22
  • 2021-12-04
相关资源
相似解决方案