1. 串口原理
- UART_URXD寄存器保存着串口接收到的數據
- UART_UTXD寄存器為發送數據寄存器,如果需要串口發送數據,只需要寫入到這個結存器
- UARTx_UCR1到UARTx_UCR4都是控制寄存器
- UCR1的bit0是使能位,bit是發送完了產生中斷,14是自動監測波特率使能位,為1的時候使能
- UCR2的bit0位軟件復位位,為0的時候復位UART bit1是使能接受,bit2位發送使能,都設置為1,bit4請求發送中斷使能(可以不用設置) bit5設置數據位,0是7位,1表示8位,bit6是停止位,0是1個停止位,1表示兩位,bit7是奇偶校驗位,為0是偶校驗,bit8是開不開校驗
- UCR3 bit2必須一直是1
- UCR4沒用到
- USR2狀態寄存器,bit0表示已接收到數據,有數據可以讀取,bit0 = 1,要判斷;bit3是發送完成,也要判斷
- UART_UFCR的bit7-9設置分頻值,在時鍾樹上,是80Mhz, CSCDRde UART_CLK_SEL設置時鍾源,為0就是PLL3的80M,否則是OSC晶振24M;CSCDR1的UART_CLK_PODF用來控制分頻,一般為1
- UART_UFCR、UBIR和UBMR這三個寄存器決定了串口波特率
如果是115200的話,自己算吧
2. 步驟
3. 代碼
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 // !__BSP_UART_H
bsp_uart.c
#include "bsp_uart.h"
/*
* @description : 初始化串口 1,波特率為 115200
* @param : 無
* @return : 無
* */
void uart_init()
{
/* 初始化UART1的IO */
uart_io_init();
/* 初始化UART1 */
/* 關閉串口,前面的也可以先關閉 */
uart_disable(UART1); /* 先關閉 UART1 */
uart_softreset(UART1); /* 軟件復位 UART1 ,復位波特率*/
UART1->UCR1 = 0; /* 先清除 UCR1 寄存器 */
UART1->UCR1 &= ~(1<<14); /* 關閉自動波特率檢測 */
/* 配置數據位,奇偶校驗 */
/*
* 設置 UART 的 UCR2 寄存器,設置字長,停止位,校驗模式,關閉硬件流控
* 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 |= 1<<2; /* UCR3 的 bit2 必須為 1 */
/*
* 設置波特率
* 波特率計算公式: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 &= ~(7 << 7);
UART1->UFCR = 5<<7; /* ref freq 等於 ipg_clk/1=80Mhz z注意是反着來的*/
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()
{
/* 初始化串口IO */
/* 首先是復用為0,就是發送接受 */
IOMUXC_SetPinMux(IOMUXC_UART1_TX_DATA_UART1_TX, 0);
IOMUXC_SetPinMux(IOMUXC_UART1_RX_DATA_UART1_RX, 0);
/* 電氣屬性,和key一樣 */
IOMUXC_SetPinConfig(IOMUXC_UART1_TX_DATA_UART1_TX, 0x10B0);
IOMUXC_SetPinConfig(IOMUXC_UART1_RX_DATA_UART1_RX, 0x10B0);
}
#if 0
/*
* @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 /
der)/ 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);
base->UBMR = UART_UBMR_MOD(numerator / divider - 1);
}
}
#endif
/* 關閉指定uart */
void uart_disable(UART_Type *base)
{
base->UCR1 &= ~(1 << 0);
}
/* 打開指定uart */
void uart_enable(UART_Type *base)
{
base->UCR1 |= (1 << 0);
}
/* 復位指定uart */
void uart_softreset(UART_Type *base)
{
/* 首先將SRST_B清零,UCR2[0] */
/* 指導SOFTRST為0 */
base->UCR2 &= ~(1<<0); /* 復位 UART */
while((base->UCR2 & 0x1) == 0); /* 等待復位完成 */
/* 重寫UBIR和UBMR */
}
/* 發送一個字符 */
void putc(unsigned char c)
{
while(((UART1->USR2 >> 3) &0X01) == 0);/* 等待上一次發送完成 */
UART1->UTXD = c & 0XFF; /* 發送數據 */
}
/* 發送一個字符串 */
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
#include "imx6ul.h"
#include "bsp_led.h"
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_exit.h"
#include "bsp_epittimer.h"
#include "bsp_keyfilter.h"
#include "bsp_uart.h"
/*
* @description : mian函數
* @param : 無
* @return : 無
*/
int main(void)
{
char a;
/* 對於中斷的初始化,一定要放在代碼的最前面 */
int_init();
clk_init(); /* 系統時鍾初始化 */
clk_enable(); /* 使能所有的時鍾 */
delay_init(); /* 延時初始化 */
uart_init(); /* 初始化串口 */
led_init(); /* 初始化led */
beep_init(); /* 初始化beep */
filterkey_init(); /* 帶有消抖功能的按鍵 */
while(1) /* 死循環 */
{
puts("請輸入一個字符:");
a = getc();
putc(a); //回顯
puts("\r\n");
puts("您輸入的字符為:");
putc(a);
puts("\r\n");
}
return 0;
}
4. 問題
putc puts的警告,因為C庫函數有這個函數所以編譯的時候需要加
-fno-builtin
串口linux是UTF-8
有可能串口是亂碼,如果設置好了串口還是亂碼,說明波特率有問題
uart_setbaudrate報錯:
需要在MakeFile中引入數學庫
LIBPATH := -lgcc -L /usr/local/arm/gcc-linaro-7.3.1-2018.05-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/7.3.1
解決方法:
thumb conditional instruction should be in IT block -- `addcs r5,r5,#65536'
$(SOBJS) : obj/%.o : %.S
$(GCC) -Wall -Wa,-mimplicit-it=thumb -fno-builtin -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(GCC) -Wall -Wa,-mimplicit-it=thumb -fno-builtin -nostdlib -c -O2 $(INCLUDE) -o $@ $<
5.printf
移植stdio
main
#include "imx6ul.h"
#include "bsp_led.h"
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_beep.h"
#include "bsp_key.h"
#include "bsp_key.h"
#include "bsp_int.h"
#include "bsp_exit.h"
#include "bsp_epittimer.h"
#include "bsp_keyfilter.h"
#include "bsp_uart.h"
#include "stdio.h"
/*
* @description : mian函數
* @param : 無
* @return : 無
*/
int main(void)
{
int a,b;
/* 對於中斷的初始化,一定要放在代碼的最前面 */
int_init();
clk_init(); /* 系統時鍾初始化 */
clk_enable(); /* 使能所有的時鍾 */
delay_init(); /* 延時初始化 */
uart_init(); /* 初始化串口 */
led_init(); /* 初始化led */
beep_init(); /* 初始化beep */
filterkey_init(); /* 帶有消抖功能的按鍵 */
while(1) /* 死循環 */
{
printf("請輸入兩個數字:");
scanf("%d %d", &a, &b);
printf("\r\n");
printf("和為:%d", a + b);
printf("\r\n");
}
return 0;
}
Makefile
# ?= 沒有賦值的話就賦值
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= bsp
# :=覆蓋之前的值
GCC := $(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 包含整個工程的.h 頭文件目錄 \是換行符
INCDIRS := imx6ull \
bsp/clk \
bsp/gpio \
bsp/led \
bsp/delay \
bsp/beep \
bsp/key \
bsp/int \
bsp/exit \
bsp/epittimer \
bsp/keyfilter \
bsp/uart \
stdio/include
# SRCDIRS 包含整個工程的.s .c文件目錄
SRCDIRS := project \
bsp/clk \
bsp/gpio \
bsp/led \
bsp/delay \
bsp/beep \
bsp/key \
bsp/int \
bsp/exit \
bsp/epittimer \
bsp/keyfilter \
bsp/uart \
stdio/lib
# patsubst的作用是給INCDIRS中的每個目錄前面加一個 -I,指明頭文件目錄時必須加
# -I imx6ull -I bsp/clk -I bsp/led -I bsp/delay
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
# foreach dir對每個dir都執行一次,SRCDIRS中dir, 即上面的文件夾
# wildcard 取出文件夾內所有文件
# 比如bsp/clk/bsp_clk.c
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
# notdir 上面取出來之后去掉目錄
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
# 把所有的.s和.c都變成點o文件,並在前面加上obj
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
# -Wall 編譯后顯示所有警告
# -nostdlib 不連接系統標准啟動文件和標准庫文件,只把指定的文件傳遞給連接器。
# 這個選項常用於編譯內核、bootloader等程序,它們不需要啟動文件、標准庫文件
# -O2 是比O1更高級的選項,進行更多的優化。
# Gcc將執行幾乎所有的不包含時間和空間折中的優化。
# 當設置O2選項時,編譯器並不進行循環打開()loop unrolling以及函數內聯。
# 與O1比較而言,O2優化增加了編譯時間的基礎上,提高了生成代碼的執行效率。
# 這樣還能帶着.h文件一起編譯
$(SOBJS) : obj/%.o : %.S
$(GCC) -Wall -Wa,-mimplicit-it=thumb -fno-builtin -nostdlib -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(GCC) -Wall -Wa,-mimplicit-it=thumb -fno-builtin -nostdlib -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)