尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【i.MX6ULL 裸机开发】手把手搞定 UART 串口,移植 printf 调试输出

【i.MX6ULL 裸机开发】手把手搞定 UART 串口,移植 printf 调试输出 本文基于正点原子I.MX6ULL_MINI_V2.2开发板记录裸机开发UART串口从寄存器配置到printf移植的完整流程。一、为什么裸机开发一定要学UART做裸机开发的同学应该都有过这种体验程序写好了烧进去板子没反应一LED不亮啥也看不到只能对着代码干瞪眼。LED确实能用来指示程序是否在跑但它只能告诉你亮了或灭了信息量极其有限。你想知道某个变量当前的值是多少程序执行到哪一步了函数返回值对不对LED统统帮不了你。通过串口我们可以把开发板和电脑连起来在电脑上用串口调试助手实时查看程序打印的日志信息。不管是字符串、数字还是变量值都能通过串口输出出来。后面学定时器、ADC、LCD、RTC这些外设调试全靠它。而且很多模块一蓝牙、GPS、GPRS、WiFi一都是通过串口和主控通信的。在嵌入式LinUx中串口更是默认的控制台。所以掌握UART是嵌入式开发的必备技能。【开发板硬件小知识正点原子i.MX6U开发板把UART1的收发引I脚经过SGM3157模拟开关芯片接到了CH34O上所以我们只需要一根USBType-C线就能实现串口连接不需要额外的USB转TTL模块。这个Type-C口在开发板左下角。】二、UART基础概念快速过2.1什么是UARTUARTUniversal Asynchronous Receiver/Transmitter通用异步收发传输器是一种异步串行通信协议。“异步意味着不需要额外的时钟线收发双方靠约定好的波特率来同步数据。最基本的UART只需要两根线TXTransmit发送端和RXReceive接收端连接时要交叉对接A的TX接B的RXA的RX接B的TX。2.2常用参数115200-8-N-1这是串口调试中最常见的参数组合意思是| 参数 | 值 | 含义 ||------|-----|------|| 波特率 | 115200 | 每秒传输115200个bit || 数据位 | 8 | 每次传输8个数据位 || 校验位 | NNone | 无校验 || 停止位 | 1 | 1位停止位 |【重点】波特率指的是每秒传输码元符号 Symbol的个数UART 异步没有时钟线只靠起始沿做一次性同步剩下全靠本地时钟计数。波特率一旦不对采样点不断偏移直接读错 bit。所以乱码第一时间检查波特率。2.3UART的通信时序发完 8 位数据之后如果此刻 TX 引脚刚好已经是高电平那停止位不需要再 “拉高” 跳变直接保持高电平维持满 1 个 bit 时间就可以。2.4 i.MX6ULL的UART资源i.MX6ULL一共有8路UART主要特性包括- 兼容TIA/EIA-232F标准速度最高5Mbit/s- 支持红外IR接口兼容IrDA- 支持9位或多节点模式RS-485- 1或2位停止位- 可编程奇偶校验- 自动波特率检测本章实验我们使用UART1目标是实现115200, n, 8, 1与电脑通信。 ⚠️ 新手第一个坑点预警i.MX6ULL的引脚是复用的TXD/RXD引脚默认不是串口功能必须通过IOMUXC配置引脚复用为UART功能同时还要配置PAD电气属性。很多人第一步就栽在这里——寄存器配了半天串口完全没输出结果是引脚根本没复用成串口功能。三、UART寄存器配置全流程3.1 第一步配置UART时钟UART的时钟源由寄存器CCM_CSCDR1控制- UART_CLK_SEL位选择时钟源- 0 → pll3_80m80MHz- 1 → osc_clk24MHz- 一般选择pll3_80m- UART_CLK_PODFbit5:0时钟分频值0~63对应1~64分频一般设为1分频最终进入UART的时钟为80MHz。 PLL3本身是480MHz经过一个静态6分频后得到80MHz这就是UART的参考时钟来源。3.2 第二步配置引脚复用IOMUXC需要配置两个寄存器1. SW_MUX_CTL_PAD_UART1_TX_DATA设置UART1_TX引脚的复用功能2. SW_MUX_CTL_PAD_UART1_RX_DATA设置UART1_RX引脚的复用功能同时还要配置对应的PAD电气属性寄存器SW_PAD_CTL_PAD_xxx设置上下拉、驱动能力、速度等参数。3.3 第三步UART核心寄存器配置① UCR1控制寄存器1关键位- **UARTENbit0**UART使能位1使能0关闭- **ADBRbit14**自动波特率检测使能我们不用设为0② UCR2控制寄存器2这是最核心的控制寄存器| 位 | 名称 | 作用 | 我们的设置 ||----|------|------|-----------|| bit14 | IRTS | 为1时忽略RTS引脚 | 1不用硬件流控 || bit8 | PREN | 奇偶校验使能 | 0无校验 || bit7 | PROE | 校验模式选择 | 0偶校验反正没开 || bit6 | STOP | 停止位数量 | 01位停止位 || bit5 | WS | 数据位长度 | 18位数据位 || bit2 | TXEN | 发送使能 | 1打开发送 || bit1 | RXEN | 接收使能 | 1打开接收 || bit0 | SRST | 软件复位 | 写0复位复位完成后自动置1 |③ UCR3控制寄存器3 ⚠️ 第二个坑点RXDMUXSEL必须置1UCR3寄存器的**bit2RXDMUXSEL必须始终写1**这是手册明确规定的。很多人漏了这个配置导致串口完全收不到数据。这个位和UART的接收路径选择有关不置1的话接收功能不正常。④ USR2状态寄存器2- TXDCbit3发送完成标志位。为1表示发送FIFO和移位寄存器都为空即发送完成。向TxFIFO写入数据后此位自动清零。- RDRbit0数据接收标志位。为1表示至少接收到一个数据从URXD寄存器读取数据后自动清零。3.4 第四步波特率计算重点这是第三个高频踩坑点——波特率算错串口输出全是乱码。波特率由UBIR和UBMR两个寄存器决定公式如下BaudRate RefFreq / (16 × (UBMR 1) / (UBIR 1))其中- RefFreq UART实际工作频率 80MHz- UBMR UARTx_UBMR寄存器的值- UBIR UARTx_UBIR寄存器的值这个公式有两个未知数不好直接算。手册给出了变换后的形式(UBMR 1) / (UBIR 1) RefFreq / (16 × BaudRate)以115200波特率为例RefFreq / (16 × BaudRate) 80000000 / (16 × 115200) 43.402也就是说 (UBMR1)/(UBIR1) 43.402。我们可以取 UBIR1 1000那么 UBMR1 43402即- **UBIR 999**- **UBMR 43401** 注意UART还有一个自己的分频器由UFCR寄存器的bit9:7决定分频值。我们需要1分频设置为b101。这个分频会影响最终的RefFreq不要漏掉。四、实现收发函数4.1 单字节发送发送一个字节之前必须先等待上一个数据发送完成否则会覆盖FIFO中的数据导致数据丢 ⚠️发送时不判断TXDC标志直接往UTXD写数据会导致数据丢失、打印不全。一定要等上一个字节发完再写下一个。4.2 单字节接收五、重头戏移植printf裸机用上格式化输出有了基本的收发函数我们可以输出字符和字符串了。但如果要输出数字呢还得自己写数字转字符串的函数太麻烦了。如果能用上printf就好了——printf(count %d\r\n, count);一行搞定。裸机环境没有标准C库所以需要自己移植printf。原理很简单把printf的底层输出映射到我们写好的putc函数上。5.1 移植步骤教程配套资料里有一个stdio文件夹里面的文件是从U-Boot源码中提取出来的格式化函数实现。把这个文件夹复制到工程目录下即可。注意这个stdio不是完整版的比如printf不支持浮点数输出但基本的%d、%x、%s、%c都够用了。5.2 三个必须修改的地方坑①必须添加空的raise()函数在uart.c中添加void raise(int n){/* 函数体为空即可 */}否则链接时会报错因为stdio的某些代码引用了这个函数。坑②start.s必须改成start.S汇编文件的扩展名.s和.S是不一样的- .S大写会先做预处理再编译- .s小写省略预处理步骤移植的stdio代码中可能包含预处理指令所以必须把start.s改名为start.S同时Makefile中所有引用.s的地方都要改成.S。坑③Makefile编译参数修改在Makefile中需要做以下修改# 编译选项增加这两个参数CFLAGS -Wa,-mimplicit-itthumb --nostdlib其中 -Wa,-mimplicit-itthumb解决thumb条件指令报错类似thumb conditional instruction should be in IT block的错误- --nostdlib不使用标准库# 增加stdio的源文件路径和头文件路径INCDIRS stdio/includeSRCDIRS stdio/source# 增加链接目录LDFLAGS -L stdio/lib5.3 底层对接printf最终会调用一个底层输出函数我们需要把它和我们的putc关联起来。具体来说就是在stdio的实现中输出字符的函数会调用我们提供的putc。这样printf(Hello World!\r\n)就会通过我们的UART1发送出去了。5.4 测试main调用printf(count %d\r\n, count)printf来自工程内stdio/lib/printf.c不是系统 C 库。printf内部解析格式化字符串把数字转为 ASCII 字符生成一个字符就调用一次putc(c)。putc是我们在uart.c实现的钩子函数遇到\n会补\r做换行兼容接着调用uart_send_byte()。uart_send_byte等待硬件发送空闲将字符写入 UART 的UTXD寄存器。IMX6ULL 硬件自动把字节转为 UART 串行比特流经板载 CH340 转为 USB 信号传给电脑。串口助手解析字符屏幕打印count 0。while 循环中 count 不断自增重复上述流程持续打印计数。打开串口调试助手参数设置为115200-8-N-1烧录运行就能看到持续打印的计数信息了。
返回列表