串口是用于串口行传递数据的接口,在嵌入式设备中,串口还经常作为调试终端,用于打印各种调试信息。串口通信分为同步通讯和异步通信,通信时,双方需要先约定好数据帧的格式,即波特率,数据位,停止位,奇偶校验位等。
波特率用于衡量通信速度,它表示信号被调制以后在单位时间内的变化次数。波特率与比特率容易混淆,后者是对信息传输速率的度量,单位是bps。波特率可以被理解为单位时间内传输符号的个数(传符号率),通过不同的调制方法可以在一个符号上负载多个比特信息。以RS232为例,常见的波特率有38400,115200,对应的比特率就是38400bps和115200bps。
除了波特率外,串口通讯还有其他的参数,包括以下几个:
起始位:当线路空闲时,电平为高。一旦检测到一个下降沿,则视为一个起始位,然后接收方按照约定好的格式,接收这一帧数据。
数据位:一帧中实际有效数据的位数。
停止位:表示这帧数据的结束。
校验位:用于检测数据传输是否正确的位。
串口有许多标准,以通常使用的RS232的9针串口为例,其中最为重要的是2,3,5脚:
2:RXD:数据接收端
3:TXD:数据发送端
5:GND:接地
首先查找OK6410的原理图,查看串口的硬件连接:
由硬件原理图可知,COM0使用的串口引脚是GPA0和GPA1,那么首先就需要找到GPA组的寄存器,把这两个引脚配置成串口功能来使用,寄存器描述如下:
接下来是对串口的数据格式的配置,比如配置串口的数据位,停止位,奇偶校验位等信息,相关的寄存器是ULCON0。需要按8位数据位,1位停止位,无奇偶校验方式进行配置:
接下来是设置串口的工作模式,串口收发可以工作在多种模式,比如DMA模式,中断模式,轮询模式,在有操作系统的情况下,串口一般会工作在中断模式或是DMA模式,但是此处我们将其设置为轮询模式。相关的配置寄存器是UCON0的bit0~3
接下来是设置波特率,通过寄存器UBRDIV0和UDIVSLOT0来控制,设置方法如下:
接下来实现字符的发送与接收,首先通过寄存器UTRSTAT0来判断收和发队列的状态,判断队列为空或为满,然后在条件满足的情况下,访问各自的数据缓冲寄存器即可。
综上,编写串口的测试代码:
#define GPACON (*((volatile unsigned short *)0x7F008000)) #define ULCON0 (*((volatile unsigned long *)0x7F005000)) #define UCON0 (*((volatile unsigned long *)0x7F005004)) #define UTRSTAT0 (*((volatile unsigned long *)0x7F005010)) #define UBRDIV0 (*((volatile unsigned short *)0x7F005028)) #define UTXH0 (*((volatile unsigned char *)0x7F005020)) #define URXH0 (*((volatile unsigned char *)0x7F005024)) #define UDIVSLOT0 (*((volatile unsigned short *)0x7F00502C)) #define PCLK 66500000 #define BAUD 115200 void uart_init() { //1.配置引脚功能 GPACON &= ~0xff; GPACON |= 0x22; //2.1 设置数据格式 ULCON0 = 0b11; //2.2 设置工作模式 UCON0 = 0b0101; //3. 设置波特率 UBRDIV0 =(int)(PCLK/(BAUD*16)-1); //UBRDIV0保存该公式计算后的整数部分 UDIVSLOT0 = 0x0; //UDISLOT0=保存该公式计算后的小数部分*16 } void putc(unsigned char ch) { while (!(UTRSTAT0 & (1<<2))); UTXH0 = ch; } unsigned char getc(void) { unsigned char ret; while (!(UTRSTAT0 & (1<<0))); // 取数据 ret = URXH0; if ( (ret == 0x0d) || (ret == 0x0a) ) { putc(0x0d); putc(0x0a); } else putc(ret); return ret; } |
下一步在OK6410上实现一个菜单式的控制台,用户可以根据菜单提示选择要进行何种操作,控制台的主程序代码如下所示:
while(1) { printf("********************************\n\r"); printf("*************GBOOT**************\n\r"); printf("1:Download kernel from tftp server\n\r"); printf("2:Boot linux from RAM\n\r"); printf("3:Boot linux from NAND\n\r"); printf("pleare enter your selection:"); scanf("%d", &num); switch(num) { case 1: //tftp_download(); break; case 2: //boot_linux_ram(); break; case 3: //boot_linux_nand(); break; default: printf("Error: wrong selection!\n\r"); break; } } |
为此,需要实现printf和scanf函数,才能方便程序的编写。
printf和scanf的实现需要借助标准C中的其他与硬件无关的库函数,比如vsprintf和vsscanf,有了这些函数,printf与scanf只需要实现与硬件相关的字符输入与输出即可,格式转化及变参处理部分交由库函数进行处理。最终printf和scanf的实现如下:
#include "vsprintf.h" unsigned char outbuf[1024]; unsigned char inbuf[1024]; int printf(const char *fmt, ...) { int i; //1.将变参转化为字符串 va_list args; va_start(args, fmt); vsprintf((char*)outbuf, fmt, args); va_end(args); //2.打印字符串到串口 for(i = 0; i < strlen(outbuf); i++) { putc(outbuf[i]); } return i; } int scanf(const char *fmt, ...) { va_list args; unsigned char c; int i = 0; //1.获取输入的字符串 while(1) { c = getc(); if(c == 0x0a || c == 0x0d) { inbuf[i] = '\n'; break; } else { inbuf[i] = c; ++i; } } //2.格式转化 va_start(args, fmt); vsscanf((char *)inbuf, fmt, args); va_end(args); return i; } |