串口概述
串口是用于串口行传递数据的接口,在嵌入式设备中,串口还经常作为调试终端,用于打印各种调试信息。串口通信分为同步通讯和异步通信,通信时,双方需要先约定好数据帧的格式,即波特率,数据位,停止位,奇偶校验位等。
波特率用于衡量通信速度,它表示信号被调制以后在单位时间内的变化次数。波特率与比特率容易混淆,后者是对信息传输速率的度量,单位是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
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;
}
- 无标签