ARM裸机开发:串口通信
Posted JeckXu666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARM裸机开发:串口通信相关的知识,希望对你有一定的参考价值。
ARM裸机开发:串口通信
一、硬件平台:
正点原子I.MX6U阿尔法开发板
二、原理分析
2.1 UART 介绍
UART 全称是 UniversalAsynchronous Receiver/Trasmitter,异步串行收发器
USART 的全称是 Universal Synchronous/Asynchronous Receiver/Transmitter,同步/异步串行收发器。相比 UART 多了 一个同步的功能,在硬件上体现出来的就是多了一条时钟线
UART 作为串口的一种,其工作原理也是将数据一位一位的进行传输,发送和接收各用一条线,连接方式如下:
因为 UART 没有时钟线做同步,所以是异步收发器,必须有起止位,其通信位如下:
各位的含义如下:
位 | 功能 |
---|---|
空闲位 | 数据线在空闲状态的时候为逻辑“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 电平
开发板上串口接口如下:
2.2 UART 寄存器
UART 控制寄存器 1
寄存器位 | 功能 |
---|---|
ADBR(bit14): | 自动波特率检测使能位,为 0 的时候关闭自动波特率检测,为 1 的时候使能自动波特率检测。 |
UARTEN(bit0): | UART 使能位,为 0 的时候关闭 UART,为 1 的时候使能 UART。 |
UART 的控制寄存器 2
寄存器位 | 功能 |
---|---|
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 寄存器
就用到了寄存器 UARTx_UCR3 中的位 RXDMUXSEL(bit2),这个位应该始终为 1
UARTx_USR2 状态寄存器 2
寄存器位 | 功能 |
---|---|
TXDC(bit3): | 发送完成标志位,为 1 的时候表明发送缓冲(TxFIFO)和移位寄存器为空,也就是发送完成,向 TxFIFO 写入数据此位就会自动清零。 |
RDR(bit0): | 数据接收标志位,为 1 的时候表明至少接收到一个数据,从寄存器 |
UARTx_URXD | 读取数据接收到的数据以后此位会自动清零 |
寄存器 UARTx_UFCR 、 UARTx_UBIR 和 UARTx_UBMR
UARTx_UFCR 中我们要用到的是位 RFDIV(bit9:7),用来设置参考时钟分频:
UART 的波特率计算公式
公式含义如下:
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卡
四、实验现象
连接串口,设置波特率 115200
以上是关于ARM裸机开发:串口通信的主要内容,如果未能解决你的问题,请参考以下文章