ARM裸机开发:串口通信

ARM裸机开发:串口通信

一、硬件平台:

正点原子I.MX6U阿尔法开发板

_533488159_IMG_20210803_235508_1628006109000_xg_0

二、原理分析

2.1 UART 介绍

UART 全称是 UniversalAsynchronous Receiver/Trasmitter,异步串行收发器

USART 的全称是 Universal Synchronous/Asynchronous Receiver/Transmitter,同步/异步串行收发器。相比 UART 多了 一个同步的功能,在硬件上体现出来的就是多了一条时钟线

UART 作为串口的一种,其工作原理也是将数据一位一位的进行传输,发送和接收各用一条线,连接方式如下:

20211122203026

因为 UART 没有时钟线做同步,所以是异步收发器,必须有起止位,其通信位如下:

20211122203226

各位的含义如下:

功能
空闲位数据线在空闲状态的时候为逻辑“1”状态,也就是高电平,表示没有数据线空闲,没有数据传输
起始位当要传输数据的时候先传输一个逻辑“0”,也就是将数据线拉低,表示开始数据传输
数据位数据位就是实际要传输的数据,数据位数可选择 5~8 位,我们一般都是按照字节传输数据的,一个字节 8 位,因此数据位通常是 8 位的。低位在前,先传输,高位最后传输
奇偶校验位这是对数据中“1”的位数进行奇偶校验用的,可以不使用奇偶校验功能
停止位数据传输完成标志位,停止位的位数可以选择 1 位、1.5 位或 2 位高电平,一般都选择 1 位停止位
波特率波特率就是 UART 数据传输的速率,也就是每秒传输的数据位数,一般选择 9600、19200、115200 等

每一位的 0 和 1 的硬件层表达就是串口的硬件层协议,目前主流的有 TTL 、RS232、RS485等不同电平的协议

TTL电平:高电平为 1,低电平为 0(低电平要小于0.8V,高电平要大于2.4V

RS232电平:电平采用负逻辑,逻辑1的电平为-3~-15V,逻辑0的电平为+3~+15V

RS485电平:数据信号采用差分传输方式,接收端和发送端有不同的定义

  • 对于发送器,逻辑1(正)是A>B, AB之间电压差为+2+6V,而逻辑0(负)是A<B,AB之间的电压差为-2-6V.
  • 而对于接收器,则逻辑1(正)则是B>A,BA之间的电压不小于200mV,逻辑0则是A>B,BA之间的电压小于-200mv,即正负逻辑,电压绝对值都大于200mv

此处我们的串口物理层使用的是 TTL 电平

开发板上串口接口如下:

20211122204535

2.2 UART 寄存器

UART 控制寄存器 1

20211123170133

寄存器位功能
ADBR(bit14):自动波特率检测使能位,为 0 的时候关闭自动波特率检测,为 1 的时候使能自动波特率检测。
UARTEN(bit0):UART 使能位,为 0 的时候关闭 UART,为 1 的时候使能 UART。

UART 的控制寄存器 2

20211123170300

寄存器位功能
IRTS(bit14):为 0 的时候使用 RTS 引脚功能,为 1 的时候忽略 RTS 引脚
PREN(bit8):奇偶校验使能位,为 0 的时候关闭奇偶校验,为 1 的时候使能奇偶校验
PROE(bit7):奇偶校验模式选择位,开启奇偶校验以后此位如果为 0 的话就使用偶校验,此位为 1 的话就使能奇校验
STOP(bit6):停止位数量,为 0 的话 1 位停止位,为 1 的话 2 位停止位。
WS(bit5):数据位长度,为 0 的时候选择 7 位数据位,为 1 的时候选择 8 位数据位。
TXEN(bit2):发送使能位,为 0 的时候关闭 UART 的发送功能,为 1 的时候打开 UART的发送功能。
RXEN(bit1):接收使能位,为 0 的时候关闭 UART 的接收功能,为 1 的时候打开 UART 的接收功能。
SRST(bit0):软件复位,为 0 的是时候软件复位 UART,为 1 的时候表示复位完成。复位完成以后此位会自动置 1,表示复位完成。此位只能写 0,写 1 会被忽略掉

UARTx_UCR3 寄存器

20211123170334

就用到了寄存器 UARTx_UCR3 中的位 RXDMUXSEL(bit2),这个位应该始终为 1

UARTx_USR2 状态寄存器 2

20211123170516

寄存器位功能
TXDC(bit3):发送完成标志位,为 1 的时候表明发送缓冲(TxFIFO)和移位寄存器为空,也就是发送完成,向 TxFIFO 写入数据此位就会自动清零。
RDR(bit0):数据接收标志位,为 1 的时候表明至少接收到一个数据,从寄存器
UARTx_URXD读取数据接收到的数据以后此位会自动清零

寄存器 UARTx_UFCR 、 UARTx_UBIR 和 UARTx_UBMR

UARTx_UFCR 中我们要用到的是位 RFDIV(bit9:7),用来设置参考时钟分频:

20211123172017

UART 的波特率计算公式

20211123172119

公式含义如下:

Ref Freq: 经过分频以后进入 UART 的最终时钟频率

UBMR: 寄存器 UARTx_UBMR 中的值

**UBIR: ** 寄存器 UARTx_UBIR 中的值

2.3 UART 使用步骤

  • 设置 UART1 的时钟源
    设置 UART 的时钟源为 pll3_80m,设置寄存器 CCM_CSCDR1 的 UART_CLK_SEL 位为 0即可。
  • 初始化 UART1
    初始化 UART1 所使用 IO,设置 UART1 的寄存器 UART1_UCR1~UART1_UCR3,设置内容包括波特率,奇偶校验、停止位、数据位等等。
  • 使能 UART1
    UART1 初始化完成以后就可以使能 UART1 了,设置寄存器 UART1_UCR1 的位 UARTEN为 1。
  • 编写 UART1 数据收发函数
    编写两个函数用于 UART1 的数据收发操作

三、程序编写

工程还是在上一章工程基础上进行实现,创建 bsp_uart.c 和 .h 模块文件,这里直接使用正点原子代码

bsp_uart.c

#include "bsp_uart.h"

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) 
{

}

bsp_uart.h

#ifndef _BSP_UART_H
#define _BSP_UART_H
#include "imx6ul.h"

/* 函数声明 */
void uart_init(void);
void uart_io_init(void);
void uart_disable(UART_Type *base);
void uart_enable(UART_Type *base);
void uart_softreset(UART_Type *base);
void uart_setbaudrate(UART_Type *base, unsigned int baudrate, unsigned int srcclock_hz);
void putc(unsigned char c);
void puts(char *str);
unsigned char getc(void);
void raise(int sig_nr);

#endif

在主函数编写调用逻辑代码

#include "bsp_led.h"
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_exit.h"
#include "bsp_epit.h"
#include "bsp_uart.h"
int main(void)
{
    unsigned char a;
    int_init();
    LED_INIT();
    delay_init();
    uart_init();
    while (1)
    {
		puts("请输入1个字符:");
		a=getc();
		putc(a);	//回显功能
		puts("\r\n");

		//显示输入的字符
		puts("您输入的字符为:");
		putc(a);
		puts("\r\n\r\n");
        /* code */
    }
    return 0;
}

修改 Makefile 文件

CROSS_COMPILE 	?= arm-linux-gnueabihf-
TARGET		  	?= bsp_uart

CC 				:= $(CROSS_COMPILE)gcc
LD				:= $(CROSS_COMPILE)ld
OBJCOPY 		:= $(CROSS_COMPILE)objcopy
OBJDUMP 		:= $(CROSS_COMPILE)objdump

LIBPATH			:= -lgcc -L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4

INCDIRS 		:= imx6ul \
				   bsp/bsp_clk \
				   bsp/bsp_led \
				   bsp/bsp_delay\
				   bsp/bsp_beep\
				   bsp/bsp_key\
				   bsp/bsp_int\
				   bsp/bsp_exit\
				   bsp/bsp_gpio\
				   bsp/bsp_epit\
				   bsp/bsp_uart

SRCDIRS			:= project \
				   bsp/bsp_clk \
				   bsp/bsp_led \
				   bsp/bsp_delay\
				   bsp/bsp_beep\
				   bsp/bsp_key\
				   bsp/bsp_int\
				   bsp/bsp_exit\
				   bsp/bsp_gpio\
				   bsp/bsp_epit\
				   bsp/bsp_uart

INCLUDE			:= $(patsubst %, -I %, $(INCDIRS))

SFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES			:= $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))

SFILENDIR		:= $(notdir  $(SFILES))
CFILENDIR		:= $(notdir  $(CFILES))

SOBJS			:= $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS			:= $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS			:= $(SOBJS) $(COBJS)

VPATH			:= $(SRCDIRS)
.PHONY: clean

$(TARGET).bin : $(OBJS)
	$(LD) -Timx6ul.lds -o $(TARGET).elf $^ $(LIBPATH)
	$(OBJCOPY) -O binary -S $(TARGET).elf $@
	$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis

$(SOBJS) : obj/%.o : %.S
	$(CC) -Wall -nostdlib -fno-builtin -c -O2  $(INCLUDE) -o $@ $<

$(COBJS) : obj/%.o : %.c
	$(CC) -Wall -nostdlib -fno-builtin -c -O2  $(INCLUDE) -o $@ $<

clean:
	rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
  • Makefile 文件在链接的时候加入了数学库, 因为在 bsp_uart.c 中有个函数 uart_setbaudrate,在此函数中使用到了除法运算,因此在链接的时候需要将编译器的数学库也链接进来,的变量LIBPATH就是数学库的目录

  • 编译指令处加入了选项“-fno-builtin”,否则编译的时候提示“putc”、“puts” 这两个函数与内建函数冲突

编译通过,下载程序到SD卡

20211123203926

四、实验现象

连接串口,设置波特率 115200

20211123210618

  • 5
    点赞
  • 21
    收藏
  • 打赏
    打赏
  • 2
    评论
一、实验目的   1、了解实时时钟的硬件控制原理及设计方法。   2、掌握S3C44B0X 处理器的RTC 模块程序设计方法。     二、实验设备   1、硬件: Embest EduKit-III 实验平台, Embest ARM 标准/ 增强型仿真器套件,PC 机。   2、软件: Embest IDE Pro ARM 集成开发环境, Windows 98/2000/NT/XP。   三、实验内容   学习和掌握 Embest EduKit-III 实验平台中RTC 模块的使用,进行以下操作:   1、编写应用程序,修改时钟日期及时间的设置。   2、使用EMBEST ARM教 学系统的串口,在超级终端显示当前系统时间。     四、实验原理   1. 实时时钟( RTC )   实时时钟( RTC)器件是一种能提供日历/ 时钟、数据存储等功能的专用集成电路,常用作各种计算机系统的时钟信号源和参数设置存储电路。RTC 具有计时准确、耗电低和体积小等特点, 特别是在各种嵌入式系统中用于记录事件发生的时间和相关信息, 如通信工程、电力自动化、工业控制等自动化程度高的领域的无人值守环境。随着集成电路技术的不断发展, RTC 器件的新品也不断推出, 这些新品不仅具有准确的RTC,还有大容量的存储器、温度传感器和A/D 数据采集通道等,已成为集RTC、数据采集和存储于一体的综合功能器件,特别适用于以微控制器为核心的嵌入式系统。     RTC 器件与微控制器之间的接口大都采用连线简单的串行接口,诸如I2C、SPI、MICROWIR和ECAN 等串行总线接口。这些串口由 2~3 根线连接,分为同步和异步。     2. S3C44B0X 实时时钟( RTC )单元   S3C44B0X 实时时钟( RTC)单元是处理器集成的片内外设。由开发板上的后备电池供电,可以在系统电源关闭的情况下运行。RTC 发送8 位BCD 码数据到CPU。传送的数据包括秒、分、小时、星期、日期、月份和年份。RTC 单元时钟源由外部32.768KHz 晶振提供,可以实现闹钟(报警)功能。S3C44B0X 实时时钟( RTC)单元特性:BCD 数据:秒、分、小时、星期、日期、月份和年份   1、闹钟(报警)功能:产生定时中断或激活系统   2、自动计算闰年   3、无2000 年问题   4、独立的电源输入   5、支持毫秒级时间片中断,为RTOS 提供时间基准读/ 写寄存器   访问 RTC 模块的寄存器, 首先要设 RTCCON的 bit0 为1。CPU 通过读取 RTC 模块中寄存器 BCDSE、CBCDMIN、BCDHOU、RBCDDA、YBCDDAT、EBCDMON和 BCDYEAR的值,得到当前的相应时间值。然而,由于多个寄存器依次读出,所以有可能产生错误。比如:用户依次读取年(1989)、月(12)、日(31)、时(23)、分(59)、秒( 59)。当秒数为1 到59 时,没有任何问题,但是,当秒数为0 时,当前时间和日期就变成了1990 年1 月1 日0 时0 分。这种情况下(秒数为0),用户应该重新读取年份到分钟的值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:撸撸猫 设计师:马嘣嘣 返回首页
评论 2

打赏作者

Top嵌入式

投喂个鸡腿

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值