单片机开发——串口通信

基本概念

UART (Universal Asynchronous Receiver/Transmitter) 是一种通用异步收发传输器,其使用串行的方式在双机之间进行数据交换,实现全双工通信。数据引脚仅包含用于接收数据的RXD和用于发送数据的TXD, 数据在数据线上按位一位一位的串行传输,要正确解析这些数据,必须遵循UART协议,作为了解,这里仅简要讲述几个关键的概念:

波特率

波特率决定了数据传输速率,其表示每秒传送数据的位数,值越大,数据通信的速率越高,数据传输得越快。常见的波特率有4800、9600、14400、19200、38400 、115200等等,若波特率为115200, 则表示每秒钟可以传输115200位(注意:是bit,不是byte) 数据。

空闲位

数据线上没有数据传输时, 数据线处于空闲状态。空闲状态的电平逻辑为"l" 。

起始位

起始位表示一帧数据传输的开始, 起始位的电平逻辑是"O"。

数据位

紧接起始位后,即为实际通信传输的数据,数据的位数可以是5、6、7、8 等,数据传输时,从最低位开始依次传输。

奇偶校验位

奇偶校验位用于接收方对数据进行校验,及时发现由于通信故障等问题造成的错误数据。奇偶校验位是可选的,可以不使用奇偶校验位。奇偶校验有奇校验和偶校验两种形式,该位的逻辑电平与校验方法和所有数据位中逻辑"1"的个数相关。

奇校验:通过设置该位的值("1"或"O"),使该位和数据位中逻辑"1"的总个数为奇数。例如,数据位为8位,值为:1001 1001,1的个数为4个(偶数),则奇校验时,为了使1 的个数为奇数,就要设置奇偶校验位的值为1, 使1的总个数为5个(奇数)。

偶校验:通过设置该位的值("1"或"O"),使该位和数据位中逻辑"1"的总个数为偶数。例如,数据位为8 位,值为:1001 1001,1的个数为4个(偶数),则偶校验时,为了使1的个数为偶数,就要设置奇偶校验位的值为0,使1的个数保持不变,为4( 偶数)。

通信双方使用的校验方法应该一致,接收方通过判断"1"的个数是否为奇数(奇校验)或偶数(偶校验)来判定数据在通信过程中是否出错。

停止位

停止位表示一帧数据的结束,其电平逻辑为"1",其宽度可以是1位、1.5 位、2位。即其持续的时间为位数乘以传输一位的时间(由波特率决定),例如,波特率为115200,则传输一位的时间为1/115200 秒, 约为8.68us。若停止位的宽度为1.5位,则表示停止位持续的时间为:1.5 × 8.68us≈13us 。

常见的帧格式为:1位起始位,8 位数据位,无校验,1位停止位。由于起始位的宽度恒为1位,不会变化,而数据位,校验位和停止位都是可变的,因此,往往在描述串口通信协议时,都只是描述其波特率、数据位,校验位和停止位,不再单独说明起始位。帧格式下所示:

注意,通信双方必须使用完全相同的协议,包括波特率、起始位、数据位、停止位等。如果协议不一致,则通信数据会错乱,不能正常通信。在通信中,若出现乱码的情况,应该首先检查通信双方所使用的协议是否一致。

硬件连接

硬件连接主要有两种情况,一对一连接,一对多连接(多机通信)。

        单片机开发——串口通信_第1张图片

电平转换

单片机开发——串口通信_第2张图片

 

上图UART_TX与UART_RX端为3.3V,RXD1与TXD1端为5V。TXD1->UART_RX利用分压原理得到3.3V,UART_TX->RXD1利用S9013三极管开通与关闭实现3.3V转5V电平。

编程实现

由于各类单片机的UART外设功能存在差异,因此寄存器内容也存在较大差异,此处不做详细列举,给出一个通用配置过程与8051单片机示例代码。

配置过程

  1. 选择UART工作模式,分别是方式0、方式1、方式2、方式3
  2. 设置波特
  3. 使能中断(通常使用接收中断)
示例代码
void UartInit()
{
    PCON |= 0x80;   //
    SM0 = 0;
    SM1 = 1;
    SCON |= 0x40;    //使用方式1,8位波特率可变
    TMOD |= 0x20;
    TH1 = TL1 = 0xF3; //波特率19200
    TR1 = 1;
    REN = 1;        //使能UART
    ES = 1;         //使能中断
    EA = 1;
}

unsigned char Cmd[4];   //被接受数据的存储位置
unsigned char RecLen;   //已接收的数据长度
#define DefRecLen 4     //允许接收的最大长度
unsigned char TimeOut;   //接收超时
bit RecOver;

void RecDataHandle(unsigned char recdata)
{
    if(RecLen < DefRecLen)
    {
        Cmd[RecLen] = recdata;
        RecLen ++;
        TimeOut = 100;
        RecOver = 0;
    }
}

void Uart1_Interrupt() interrupt 4
{
    unsigned char RecData;
    if(RI)
    {
        RI = 0;
        RecData = SBUF;
        RecDataHandle(RecData);
    }
    
    if(TI)
    {
//        TI = 0;
    }
}

格式化输出

格式化输出,需调用标准C语言库函数printf(),需包含头文件“stdio.h”。以KEIL集成开发环境为例,printf()函数在编译时会自动调用putchar()函数,如果应用程序中未声明putchar()函数,则自动调用KEIL系统下的putchar.c中的putchar()函数。由于单片机的种类繁多,系统的putchar()函数无法适应全部硬件,因此,KEIL在\KEIL\C51\LIB路径下提供了PUTCHAR.C源文件,可对其修改来适应硬件环境。

建议:将\KEIL\C51\LIB路径下的PUTCHAR.C文件复制到应用程序目录下再添加到工程文件,避免修改系统PUTCHAR.C文件。

示例

#include  
void UserPrintf()
{
    printf(“This is a test example!”);
}

printf

摘要

#include

 

int printf (

  const char *fmtstr       /* format string */

  <[>, arguments ... <]>);   /* additional arguments */

描述

printf 函数格式化一系列字符串和数值创建一个字符串使用putchar函数写入输出流。fmtstr参数是一个格式字符串,可以由字符、转义序列和格式说明符组成。

普通字符和转义序列按他们被解释的顺序复制到流。格式说明符总是以百分号('%')并需要在调用的printf函数中包含附加的arguments

格式化的字符串从左向右读取。遇到的第一个格式说明符引用fmtstr后的第一个argument,使用格式说明符转换和输出。第二个格式说明符引用fmtstr后的第二个argument,以此类推。如果arguments比格式说明符多,多余的arguments被忽略。如果没有足够的arguments来匹配格式说明符或者参数类型不能与fmtstr说明匹配,结果是不可预知的。

格式说明符有以下常见格式:

% <[>flags<]> <[>width<]> <[>.precision<]> <[>{b|B|l|L}<]> type

格式说明符字段可以是单个字符或一个指定特殊格式选项的数字。

type字段是一个字符,指定参数解释为字符、字符串、数值或指针,详细如下表。

Type

参数类型

输入格式

d

int

有符号十进制数

u

unsigned int

无符号十进制数

o

unsigned int

无符号八进制数

x

unsigned int

无符号十六进制数,使用"0123456789abcedf"

X

unsigned int

无符号十六进制数,使用"0123456789ABCDEF"

f

float

浮点数格式化为
<[>-<]>dddd.dddd

e

float

浮点数格式化为
<[>-<]>d.dddde<[>-<]>dd

E

float

浮点数格式化为
<[>-<]>d.ddddE<[>-<]>dd

g

float

浮点数使用ef格式,为指定数值选择更紧凑精确的

G

float

浮点数使用Ef格式,为指定数值选择更紧凑精确的

c

char

一个字符

s

*

由空字符('\0')结尾的字符串

p

*

通用指针格式t:aaaat是内存类型aaaa是十六进制地址

注意

  • 可选的字符l或L可以在类型字符前分别指定d, i, u, o, x和X的long类型。
  • 可选的字符b或B可以在类型字符前分别指定d, i, u, o, x和X的char类型。

字符后跟随一个百分号将不会被认为是格式说明符而是被当作普通字符。例如, "%%" 向输出流写入一个百分号。

flags字段是单个字符用来对齐输出,打印一个+/-号、空白、小数点、八进制和十进制前缀,详细如下表。

Flag

描述

-

将指定字段宽度输出左对齐

+

如果输出是一个有符号类型,使用+-符号做输出的前缀。

blank (' ')

如果是一个有符号正值,使用空白做输出的前缀。否则,没有空白前缀。

#

当使用o, xX字段类型时,使用0, 0x0X作为非零输出的前缀

当使用e, E, f, gG字段类型,#flag强制使输出值包含小数点。

# flag 是在其他情况下被忽略。

width 字段是一个非负数值,指定被打印字符的最小数量。如果输出字符的数量少于width,默认在左边添加空白,或者使用-flag在右边添加空白来填补最小宽度。如果width使用'0'作为前缀,使用0替代空白。Width字段不会截断输出。如果输出的长度超过指定宽度,所有字符都输出。

width字段可以是个星号('*'),这种情况下,参数列表中的int argument提供width值。在星号前面指定一个'b'来说明参数是一个无符号char

precision 字段是一个非负数值,指定打印字符的数量、有效数字的位数或小数位的位数。precision 字段可以引起输出截断或凑整,浮点数的情况在下表中说明。

Type

Precision 字段意义

d,u,o,x,X

precision 字段指定输出值中包含的最小位数。如果参数中的位数超过precision字段指定的位数,数字不会被截断。如果参数中的位数比precision字段少,输出值在左边补0

f

precision 字段指定小数点右边的位数。最后一位数字四舍五入。

e,E

precision 字段指定小数点右边的位数。最后一位数字四舍五入。

g,G

precision 字段指定输出值的最大有效位数。

s

precision 字段指定输出值中字符的最大数量。超出的字符不输出。

c,p

precision 字段在这些字段类型中无效。

precision 字段可以是一个星号('*'),这种情况下,参数列表中的int argument提供这个值 。在星号前面指定'b'说明参数是一个无符号char

注意

  • 必须保证参数类型与格式说明符匹配。可以使用类型转换来保证将合适的类型传递给printf。
  • 这个函数基于_getkeyputchar函数的操作特殊的执行。这些函数在标准库中提供,使用微控制器的串行端口读写字符。可以自定义函数使用其他I/O设备。
  • 由于8051内存有限,可以传递到函数的字节总数受到限制。在SMALL或COMPACT模式中最多15字节可以被传递。LARGE模式中最多40字节可以被传递。

返回值

printf 函数返回实际写入输出流的字符数量。

参考

gets, printf517, puts, scanf, scanf517, sprintf, sprintf517, sscanf, sscanf517, vprintf, vsprintf

示例

#include

 

void tst_printf (void) {

  char a = 1;

  int b  = 12365;

  long c = 0x7FFFFFFF;

 

  unsigned char x = 'A';

  unsigned int y  = 54321;

  unsigned long z = 0x4A6F6E00;

 

  float f = 10.0;

  float g = 22.95;

 

  char buf [] = "Test String";

  char *p = buf;

 

  printf ("char %bd int %d long %ld\n",a,b,c);

  printf ("Uchar %bu Uint %u Ulong %lu\n",x,y,z);

  printf ("xchar %bx xint %x xlong %lx\n",x,y,z);

  printf ("String %s is at address %p\n",buf,p);

  printf ("%f != %g\n", f, g);

  printf ("%*f != %*g\n", (int)8, f, (int)8, g);

}

 

你可能感兴趣的:(单片机开发与应用,通信协议与应用)