35. 串口实验

一、6ULL串口UART原理

6ULL的UART_URXD寄存器保存这串口接收到的数据。
UART_UTXD寄存器为发送数据寄存器,如果需要通过串口发送数据,只需要将数据写入到UART_UTXD寄存器里面。
UART_UCR1~UCR4都是串口的控制寄存器。UART_UCR1的bit0是UART的使能位,为1的时候使能UART。Bit14为自动检测波特率使能位,为1的时候使能波特率自动检测。
UART_UCR2的bit0为软件复位位。为0的时候复位UART。Bit1使能UART的接收,我们要配置为1。Bit2为发送使能,要设置为1。Bit5设置数据位,0的话表示7位数据位,1的话表示8位数据位。Bit6设置停止位,0的话表示1位停止位,1的话表示2位。Bit7奇偶校验位,为0的时候是偶校验,为1的时候是计校验。Bit8校验使能位,为0的时候关闭校验。
UART_UCR3的bit2必须为1!!!
UART_UFCR寄存器的bit9~7设置分频值,UART的时钟源=PLL3/6=480/6=80MHz。CSCDR1寄存器的UART_CLK_SEL位设置UART的时钟源,为0的时候UART时钟源为80MHz
,为1的时候UART时钟源为24M晶振。CSCDR1寄存器的UART_CLK_PODF位控制分频,一般设置为1分频,因此UART_CLK_ROOT=80MHZ
UART_UFCR、UART_UBIR和UART_UBMR这三个寄存器决定了串口波特率。
UART_USR2寄存器的bit0为1的时候表示有数据可以读取。Bit3为1的时候表示数据发送完成。

二、实验程序编写

UART1_TXD使用的IO为UART1_TX_DATA,UART1_RXD所使用的IO为UART1_RX_DATA。

Putc和puts编译的时候会提示吧报错,要在Makefile中添加-fno-builtin

SecuCRT打开以后串口接收到乱码,因为Linux默认用UTF-8编码,因此我们需要设置SecureCRT的编码模式为UTF-8
我们移植的printf不支持浮点计算和输出!!!!!

三、代码

//bsp_clk.c

#include "bsp_clk.h"

/*
 * @description : 使能I.MX6U所有外设时钟
 * @param       : 无
 * @return      : 无
 */
void clk_enable(void)
{
    CCM->CCGR0 = 0XFFFFFFFF;
    CCM->CCGR1 = 0XFFFFFFFF;
    CCM->CCGR2 = 0XFFFFFFFF;
    CCM->CCGR3 = 0XFFFFFFFF;
    CCM->CCGR4 = 0XFFFFFFFF;
    CCM->CCGR5 = 0XFFFFFFFF;
    CCM->CCGR6 = 0XFFFFFFFF;
}

/*
 * @description : 初始化系统时钟,设置系统时钟为528Mhz,并且设置PLL2和PLL3各个
                  PFD时钟,所有的时钟频率均按照I.MX6U官方手册推荐的值.
 * @param       : 无
 * @return      : 无
 */
void imx6u_clkinit(void)
{
    unsigned int reg = 0;
    /* 1、设置ARM内核时钟为528MHz */
    /* 1.1、判断当前ARM内核是使用的那个时钟源启动的,正常情况下ARM内核是由pll1_sw_clk驱动的,而
     *      pll1_sw_clk有两个来源:pll1_main_clk和tep_clk(参考手册648页)。
     *      如果我们要让ARM内核跑到528M的话那必须选择pll1_main_clk作为pll1的时钟源。
     *      如果我们要修改pll1_main_clk时钟的话就必须先将pll1_sw_clk从pll1_main_clk切换到step_clk,
     *      当修改完pll1_main_clk以后在将pll1_sw_clk切换回pll1_main_clk。而step_clk的时钟源可以选择
     *      板子上的24MHz晶振。
     */
    
    if((((CCM->CCSR) >> 2) & 0x1 ) == 0)    /* 当前pll1_sw_clk使用的pll1_main_clk*/
    {   
        CCM->CCSR &= ~(1 << 8);             /* 配置step_clk时钟源为24MH OSC */    
        CCM->CCSR |= (1 << 2);              /* 配置pll1_sw_clk时钟源为step_clk */
    }

    /* 1.2、设置pll1_main_clk为1056MHz,也就是528*2=1056MHZ,
     *      因为pll1_sw_clk进ARM内核的时候会被二分频!
     *      配置CCM_ANLOG->PLL_ARM寄存器
     *      bit13: 1 使能时钟输出
     *      bit[6:0]: 88, 由公式:Fout = Fin * div_select / 2.0,1056=24*div_select/2.0,
     *                      得出:div_select=    88  
     */
    CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0X7F);   /* 配置pll1_main_clk=1056MHz */
    CCM->CCSR &= ~(1 << 2);                                 /* 将pll_sw_clk时钟重新切换回pll1_main_clk */
    CCM->CACRR = 1;                                         /* ARM内核时钟为pll1_sw_clk/2=1056/2=528Mhz */

    /* 2、设置PLL2(SYS PLL)各个PFD */
    reg = CCM_ANALOG->PFD_528;
    reg &= ~(0X3F3F3F3F);       /* 清除原来的设置                      */
    reg |= 32<<24;              /* PLL2_PFD3=528*18/32=297Mhz   */
    reg |= 24<<16;              /* PLL2_PFD2=528*18/24=396Mhz(DDR使用的时钟,最大400Mhz) */
    reg |= 16<<8;               /* PLL2_PFD1=528*18/16=594Mhz   */
    reg |= 27<<0;               /* PLL2_PFD0=528*18/27=352Mhz   */
    CCM_ANALOG->PFD_528=reg;    /* 设置PLL2_PFD0~3                */

    /* 3、设置PLL3(USB1)各个PFD */
    reg = 0;                    /* 清零   */
    reg = CCM_ANALOG->PFD_480;
    reg &= ~(0X3F3F3F3F);       /* 清除原来的设置                          */
    reg |= 19<<24;              /* PLL3_PFD3=480*18/19=454.74Mhz    */
    reg |= 17<<16;              /* PLL3_PFD2=480*18/17=508.24Mhz    */
    reg |= 16<<8;               /* PLL3_PFD1=480*18/16=540Mhz       */
    reg |= 12<<0;               /* PLL3_PFD0=480*18/12=720Mhz       */
    CCM_ANALOG->PFD_480=reg;    /* 设置PLL3_PFD0~3                    */  

    /* 4、设置AHB时钟 最小6Mhz, 最大132Mhz (boot rom自动设置好了可以不用设置)*/
    CCM->CBCMR &= ~(3 << 18);   /* 清除设置*/ 
    CCM->CBCMR |= (1 << 18);    /* pre_periph_clk=PLL2_PFD2=396MHz */
    CCM->CBCDR &= ~(1 << 25);   /* periph_clk=pre_periph_clk=396MHz */
    while(CCM->CDHIPR & (1 << 5));/* 等待握手完成 */
        
    /* 修改AHB_PODF位的时候需要先禁止AHB_CLK_ROOT的输出,但是
     * 我没有找到关闭AHB_CLK_ROOT输出的的寄存器,所以就没法设置。
     * 下面设置AHB_PODF的代码仅供学习参考不能直接拿来使用!!
     * 内部boot rom将AHB_PODF设置为了3分频,即使我们不设置AHB_PODF,
     * AHB_ROOT_CLK也依旧等于396/3=132Mhz。
     */
#if 0
    /* 要先关闭AHB_ROOT_CLK输出,否则时钟设置会出错 */
    CCM->CBCDR &= ~(7 << 10);   /* CBCDR的AHB_PODF清零 */
    CCM->CBCDR |= 2 << 10;      /* AHB_PODF 3分频,AHB_CLK_ROOT=132MHz */
    while(CCM->CDHIPR & (1 << 1));/
* 等待握手完成 */
#endif
    
    /* 5、设置IPG_CLK_ROOT最小3Mhz,最大66Mhz (boot rom自动设置好了可以不用设置)*/
    CCM->CBCDR &= ~(3 << 8);    /* CBCDR的IPG_PODF清零 */
    CCM->CBCDR |= 1 << 8;       /* IPG_PODF 2分频,IPG_CLK_ROOT=66MHz */
    
    /* 6、设置PERCLK_CLK_ROOT时钟 */
    CCM->CSCMR1 &= ~(1 << 6);   /* PERCLK_CLK_ROOT时钟源为IPG */
    CCM->CSCMR1 &= ~(7 << 0);   /* PERCLK_PODF位清零,即1分频 */

    /* 设置UART时钟源频率为80M */
    CCM->CSCDR1 &= ~(1 << 6);   /* UART时钟源为pll3_80m */
    CCM->CSCDR1 &= ~0X3F;       /* UART时钟1分频            */
}

//uart.c

#include "bsp_uart.h"

/*
 * @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

#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"

/*
 * @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;
}

你可能感兴趣的:(Linux,单片机,Linux)