I.MX6U 裸机开发19.串口通讯实验和printf scanf 重定向
- 一、IMX6ULL 串口资源介绍
- 1. 串口资源简介
- 2. UART通讯格式
- 3. UART结构框图
- 4. 开发板原理图
- 5. UART电平标准
- 二、寄存器
- 1. UARTx_URXD
- 2. UARTx_UTXD
- 3. UARTx_UCRn (1~4)
- UARTx_UCR1
- UARTx_UCR2
- UARTx_UCR3
- UARTx_UCR4
- UARTx_UFCR
- 4. UARTx_USRn
- UARTx_USR2
- 三、代码实现
- 1. 引脚
- 2. 串口IO初始化
- 3. 串口初始化
- 4. 其它一些函数
- 5. 时钟设置
- 6. 运行效果:
- 四、设置波特率的函数
- 五、printf 重定向
- 1. 复制库文件
- 2. Makefile 修改
- (1)路径设置
- (2)要使用 printf ,需要实现 putc 、 getc 函数:
- (3)其它 Makefile 修改项
- 3. main
- 4. 运行效果
一、IMX6ULL 串口资源介绍
在《IMX6U参考手册》P3561
1. 串口资源简介
UART(Universal Asynchronous Receiver/Trasmitter),通用异步串行收发器。它可以通过电平转换器和 RS - 232 电缆或外部电路提供与外部设备的串行通信能力。
外部电路可以将红外信号转换为电信号(用于接收),或将电信号转换为驱动红外 LED 的信号(用于传输),以提供低速 IrDA 兼容性。
UART 支持以下几种编码格式:
- NRZ(非归零)编码格式
- RS485 兼容的 9 位数据格式
- IrDA 兼容的红外低速数据率(SIR)格式
2. UART通讯格式
UART通讯一般使用三根线:TxD发送、RxD接收、GND接地。
其中各位含义解释:
这篇文档主要介绍了UART(通用异步收发器)数据传输中的几个关键概念:
- 空闲位:
- 数据线在空闲状态时为逻辑“1”,即高电平,表示没有数据线空闲,没有数据传输。
- 起始位:
- 当要传输数据时,先传输一个逻辑“0”,即将数据线拉低,表示开始数据传输。
- 数据位:
- 数据位是实际要传输的数据,数据位数可选择5~8位,通常按照字节(8位)传输数据,低位在前,高位最后传输。
- 奇偶校验位:
- 这是对数据中“1”的位数进行奇偶校验用的,可以选择不使用奇偶校验功能。
- 停止位:
- 数据传输完成标志位,停止位的位数可以选择1位、1.5位或2位高电平,通常选择1位停止位。
- 波特率:
- 波特率是UART数据传输的速率,即每秒传输的数据位数,常见选择有9600、19200、115200等。
3. UART结构框图
4. 开发板原理图
5. UART电平标准
UART一般的接口电平有TTL和RS-232两种。
- TTL电平标准
- 一般开发板上都有TXD和RXD这样的引脚。
- 这些引脚低电平表示逻辑0,高电平表示逻辑1,这就是TTL电平。
- RS-232电平标准
- RS-232采用差分线。
- -3 ~ -15V表示逻辑1,+3 ~ +15V表示逻辑0。
二、寄存器
1. UARTx_URXD
低8位保存串口接收到的数据,其他位比较少用。
2. UARTx_UTXD
低8位放要发送的数据。
3. UARTx_UCRn (1~4)
串口控制寄存器。
UARTx_UCR1
- bit0: UART的使能位;
- bit14: 自动检测波特率,设置为0不使用;
UARTx_UCR2
- bit0:软复位,0的时候复位,1时候不复位。
- bit1:接收使能
- bit2:发送使能
- bit5:设置数据长度,0表示7位数据位,1表示8位数据位
- bit6:停止位,0:1位停止位, 1:2位停止位
- bit7:奇偶校验位,为0是偶校验,1是奇校验
- bit8:校验使能
UARTx_UCR3
- bit2:设置为1,工作在MUXED 模式。
UARTx_UCR4
忽略
UARTx_UFCR
- bit7~9:设置分频值。
UART的时钟源由 CSCDR1的 UART_CLK_SEL 位设置,可以是OSC24MHz(UART_CLK_SEL=1),或 PLL3/6 分频(UART_CLK_SEL=0):
即 480MHz/6 = 80MHz,根据这个值设置分频。
本文使用PLL/6作为时钟源。另外 CSCDR1[UART_CLK_PODF] 分频值设置1分频。
UARTx_UFCR设置的是80MHz时钟源的分频值,得到的值 RefFreq,再通过UART_UFCR、UART_UBIR、UART_UBMR三个寄存器确定串口波特率,计算公式如下:
4. UARTx_USRn
UARTx_USR2
- bit0:为1时,表示接收到数据。
- bit3:1时表示数据发送完成。
三、代码实现
1. 引脚
UART1_TXD 使用的 IO 为UART1_TX_DATA, UART1_RXD 使用的IO为 UART1_RXD_DATA。
设置其复用功能:
MINI 开发板通过 USB TTL 与电脑相连。
2. 串口IO初始化
/**
* @brief UART的IO初始化
*/
void uart_io_init(void){
// 复用为UART1_TX
IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
// 设置电气属性
IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);
// 复用为UART1_RX
IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
// 设置电气属性
IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
}
3. 串口初始化
/**
* @brief 初始化UART1
*/
void uart_init(UART_Type *base){
// 1、初始化IO
uart_io_init();
// 2、初始化UART
uart_disable(base);
uart_reset(base);
// 3、配置UART
base->UCR1 = 0;
// 配置数据位、停止位和校验位
base->UCR2 = 0;
// 1接收使能 | 2发送使能 | 5数据位8位 | 14忽略RTS脚
base->UCR2 |= (1 << 1) | (1 << 2) | (1 << 5) | (1 << 14);
// MUXED
base->UCR3 |= (1 << 2);
// 配置波特率 115200
base->UFCR = 5 << 7;
base->UBIR = 71;
base->UBMR = 3124;
// 4、使能UART
uart_enable(base);
}
4. 其它一些函数
/**
* @brief 关闭UART1
*/
void uart_disable(UART_Type *base) {
base->UCR1 &= ~ (1 << 0);
}
/**
* @brief 打开UART1
*/
void uart_enable(UART_Type *base) {
base->UCR1 |= 1 << 0;
}
/**
* @brief 复位
*/
void uart_reset(UART_Type *base) {
base->UCR2 &= ~ (1 << 0);
while((base->UCR2 & 0x1) == 0); /* 等待复位完成 */
}
/**
* @brief 发送一个字符
*/
void uart_write(UART_Type *base, unsigned char c){
// 等待发送缓冲区为空
while(((UART1->USR2 >> 3) &0X01) == 0);/* 等待上一次发送完成 */
UART1->UTXD = c & 0XFF; /* 发送数据 */
}
/**
* @brief 发送字符串
*/
void uart_write_str(UART_Type *base, const char *str){
while (*str) {
uart_write(base, *str++);
}
}
/**
* @brief 读取一个字符
*/
unsigned char uart_read(const UART_Type *base){
// 等待接收缓冲区满
while((UART1->USR2 & 0x1) == 0);/* 等待接收完成 */
return base->URXD;
}
/**
* @brief 读取字符串
*/
void uart_read_str(const UART_Type *base, char *str, unsigned int len){
unsigned int i = 0;
while (i < len) {
str[i] = uart_read(base);
i++;
}
}
5. 时钟设置
/**
* @brief 初始化系统时钟
*/
void imx6u_clkinit(void) {
// 如果PLL1_SW_CLK_SEL 当前不是 step_clk
if (((CCM->CCSR >> 2) & 0x1)== 0) {
// 设置备用时钟step_sel=osc_clk(24MHz)
CCM->CCSR &= ~(1 << 8);
// 设置pll1_sw_clk为step_clk
CCM->CCSR |= (1 << 2);
}
// 设置PLL1=1056MHz
CCM_ANALOG->PLL_ARM = (1 << 13) | ((88 << 0) & 0x7f); // 528*2=1056MHz, 1056*2/24=88
// 设置二分频 arm_clk=1056/2=528MHz
CCM->CACRR = 1;
// 将pll1_sw_clk切换为pll1_main_clk,即1056MHz
CCM->CCSR &= ~(1 << 2);
// 配置PLL2的PFD0~PFD3
imx6u_clk_pll2_pfd_init();
// 配置PLL3的PFD0~PFD3
imx6u_clk_pll3_pfd_init();
// 设置AHB时钟最大为132MHz
imx6u_ahb_clk_init();
// 设置IPG_CLK_ROOT=66MHz
imx6u_ipg_clk_init();
// 设置PERCLK_CLK_ROOT=66MHz
imx6u_perclk_init();
// PLL3时钟设置,串口使用
CCM->CSCDR1 &= ~(1 << 6); // PERCLK=PLL3_SW_CLK=PLL3=480MHz, UART_CLK_ROOT=80MHz
// 1分频
CCM->CSCDR1 &= ~0x3F;
}
6. 运行效果:
在电脑打开串口调试工具,开发板上电后,串口工具连接相应的串口,按开发板的RESET键复位。 这时串口调试工具窗口打印字符: please input char: ,输入一个字符,开发板会回显输入的字符。
四、设置波特率的函数
从 正点原子的例程里直接拷贝从NXP移植的 uart_setbaudrate 函数。
原型如下:
/*
* @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);
使用方法:
// 配置波特率 115200
// base->UFCR = 5 << 7;
// base->UBIR = 71;
// base->UBMR = 3124;
uart_setbaudrate(base, 115200, 80000000);
为了使用这个函数,需要引用数学库,修改 Makefile
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
然后加到 $(LD) 指令:
# 生成二进制文件
$(TARGET).bin : $(OBJS) | obj
$(LD) -T$(LINKER_SCRIPT) -o obj/$(TARGET).elf $^ $(LIBPATH)
$(OBJCOPY) -O binary -S obj/$(TARGET).elf $@
$(OBJDUMP) -D -m arm obj/$(TARGET).elf > obj/$(TARGET).dis
五、printf 重定向
1. 复制库文件
从 正点原子 教程源码中,复制 stdio/ 目录
2. Makefile 修改
(1)路径设置
(2)要使用 printf ,需要实现 putc 、 getc 函数:
/*
* @description : 发送一个字符
* @param - c : 要发送的字符
* @return : 无
*/
void putc(unsigned char c)
{
while(((UART1->USR2 >> 3) &0X01) == 0);/* 等待上一次发送完成 */
UART1->UTXD = c & 0XFF; /* 发送数据 */
}
/*
* @description : 接收一个字符
* @param : 无
* @return : 接收到的字符
*/
unsigned char getc(void)
{
while((UART1->USR2 & 0x1) == 0);/* 等待接收完成 */
return UART1->URXD; /* 返回接收到的数据 */
}
(3)其它 Makefile 修改项
由于定义的函数与内置的putc会冲突, 在 Makefile 编译命令还需要修改:
$(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 $@ $<
这时编译还会报:
/tmp/ccDDezEw.s: Assembler messages:
/tmp/ccDDezEw.s:42: Error: thumb conditional instruction should be in IT block -- `addcs r5,r5,#65536'
这个错误是因为在汇编代码中使用了 Thumb 条件指令 addcs,但它没有包含在 IT (If-Then) 块中。
处理方式是在Makefile 编译命令再加上 -Wa, -mimplicit-it=thumb
,用于告诉汇编器在生成 Thumb 指令时自动插入 IT (If-Then) 块。
# 编译C文件
$(COBJS) : obj/%.o : %.c | obj
$(CC) -Wall -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
3. main
#include "inc/main.h"
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
#include "bsp_int.h"
#include "bsp_epit.h"
#include "bsp_keyfilter.h"
#include "bsp_uart.h"
#include "stdio.h"
int main(void)
{
unsigned char state = OFF;
int a , b;
bsp_int_init(); /* 初始化中断 */
imx6u_clkinit(); /* 初始化系统时钟 */
clk_enable(); /* 使能外设时钟 */
led_init(); /* 初始化LED */
beep_init(); /* 初始化蜂鸣器 */
gpt1_delay_init(); /* 初始化GPT1高精度延时 */
uart_init(UART1); /* 初始化UART1的IO */
while(1) {
printf("input 2 numbers:");
scanf("%d %d", &a, &b); /* 输入两个整数 */
printf("\r\n%d + %d = %d\r\n\r\n", a, b, a+b); /* 输出两个数相加的和 */
state = !state;
led_switch(LED0,state);
}
return 0;
}
4. 运行效果