打印
[应用方案]

mbed UART通讯

[复制链接]
3881|12
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
598330983|  楼主 | 2015-12-19 21:42 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

Uart(Universal Asynchronous Receiver/Transmitter)异步串口通讯是各类单片机中最古老又是最常用的通讯方式,在UART通讯过程中,我们需要使用3条信号线,即发送(RX),接收(TX)和地(GND),对于收发双方来说,RX和TX要交叉连接。由于这三条线中并不带时钟信号,所有在使用时必须约定数据的发送速度,即波特率;另外为了让接收方能够准确地识别出1个字符,还还需要在发送时添加起始位(1位)和停止位(1,1.5,2位)。由于UART通讯以字符为传输单位(一般是8个bit,但也可以是其它,如7个,9个),且字符的发送时间是不确定的(异步的),所以我们称它为异步串口通讯。UART串口通讯具有结构简单,实现方便的优点,但也有传输速度低的缺点。


在具体的应用中,UART串口有TTL电平的串口和RS232电平的串口类中,TTL电平是高电平为3.3V,低电平为0;而RS232是负逻辑电平,它定义+5~+12V为低电平,而-12~-5V为高电平,对于不加外部电路的单片机而言,它的UART输出都是TTL电平,相对RS232电平来说,通讯距离较短,而且容易受到干扰,但功耗较低,适合1米以内的短距离数据传输。下图是UART数据通讯过程中的时序图:

Uart串口通讯其实在前面的代码中已经用到过,标准C语言的printf函数也被重定义到串口输出上,方便用户的调试,对于mbed来说,它使用Serial对象来完成串口的数据收发,它提供的主要方法有:

类名
方法
用途
Serial
Serial(PinName tx, PinName rx, const char *name=NULL);
构造函数,把tx,rx设成Uart的输出输入管脚
void baud(int baudrate);
设置Uart的波特率,默认为9600
void format(int bits=8, Parity parity=SerialBase::None, int stop_bits=1);
设置Uart传输的格式,包括一个字长的位数、奇偶检验的方法、停止位的位数,默认为一个字长为8位,无奇偶检验,1位停止位
int readable();
返回Uart是否有数据到达
int writeable();
返回Uart是否还有空间进行数据发送
void attach(void (*fptr)(void), IrqType type=RxIrq);
设置Uart中断是需要执行的用户自定义函数
void set_flow_control(Flow type, PinName flow1=NC, PinName flow2=NC);
设置Uart的流控方法,其目标是提高数据发送的可靠性,流控方法有无流控,RTS流控,CTS流控,RTSCTS流控,后面两个常数为流控的管脚设置
int getc()
从Uart读取一个字符
int putc(int c);
向Uart发送一个字符
int printf(const char* format, ...)
格式化Uart的输出,参数等同标准C的printf
int scanf(const char* format, ...);
格式化Uart的输入,参数等同标准C的scanf

在这里需要重点说明的是,并不是所有的管脚都能成为Uart管脚,只要被定义成Uart相关功能的GPIO管脚才行,具体来说,需要参考mbed每个平台实现的Serial_api.c文件,其中的相关代码入如下:

#define UART_NUM    4

static const PinMap PinMap_UART_TX[] = {

    {P0_0,  UART_3, 2},

    {P0_2,  UART_0, 1},

    {P0_10, UART_2, 1},

    {P0_15, UART_1, 1},

    {P0_25, UART_3, 3},

    {P2_0 , UART_1, 2},

    {P2_8 , UART_2, 2},

    {P4_28, UART_3, 3},

    {NC   , NC    , 0}

};

static const PinMap PinMap_UART_RX[] = {

    {P0_1 , UART_3, 2},

    {P0_3 , UART_0, 1},

    {P0_11, UART_2, 1},

    {P0_16, UART_1, 1},

    {P0_26, UART_3, 3},

    {P2_1 , UART_1, 2},

    {P2_9 , UART_2, 2},

    {P4_29, UART_3, 3},

    {NC   , NC    , 0}

};

static const PinMap PinMap_UART_RTS[] = {

    {P0_22, UART_1, 1},

    {P2_7,  UART_1, 2},

    {NC,    NC,     0}

};

static const PinMap PinMap_UART_CTS[] = {

    {P0_17, UART_1, 1},

    {P2_2,  UART_1, 2},

    {NC,    NC,     0}

};

这四个PinMap类型的定义就定义了可以用作Uart各类功能的管脚名称,其中的PinMap类型定义如下:

typedefstruct {

    PinNamepin;

    intperipheral;

    intfunction;


} PinMap;

其中的各个成员函数可以理解成管脚好、功能类型即UART、ADC、I2C等以及该功能对应GPIO的复用功能知识,如PinMap_UART_TX[]中的 {P0_0,  UART_3, 2}表示P0_0管脚可以用作UART3的TX管脚,它对应的功能序号为2;PinMap_UART_RX[]中的{P0_1 , UART_3, 2}表示P0_1管脚可以用作UART3的RX管脚,它对应的功能序号为2。如果你查找LPC1768的Datasheet,你可以发现下面的说明:


后面的I2C,SPI等的构造函数也是同样的原理。对于xbed LPC1768来说,它一共有4个UART口,分别是UART0,UART1,UART2,UART3,其中的UART0已经和CP2104相连,同时也用作printf的重定向输出,用户无法外接使用。

  mbed Uart双向串行通讯应用

为了理解UART的通讯方法,我们先来输入下面的代码:

Serial pc(USBRX,USBTX);

DigitalOut led(LED1);

int main()

{

    while (1)

    {

        pc.putc(pc.getc());

        led=0;

        wait(0.1);

        led=1;

        wait(0.1);

    }


}

我们编译上载后运行你会发现xbed LPC1768 LED乱闪,这是mbed出错的指示方式,表示用户的程序在运行过程中出现了问题,这是因为我们把不能设定为tx的USBRX管脚设成了tx管脚,我们把USBRX、USBTX换个顺序后程序就运行正常了。

现在我们来审视一下这段代码,它的目的是回显用户的输入,并保持LED的变换,但实际上我们发现该程序在运行过程中有以下问题:

l  当用户没有字符输入时,LED灯并不会变化,程序处于等待状态;

l  当用户一次输入字符过多时,返回的字符会有丢失。

这些问题的产生是由mbed Serial API实现的原理决定的,在mbed中,getc()这一读取函数会一直等待直到读取到输入,所以当没有字符输入时LED灯不会变化;至于字符丢失是因为LPC1768的UART有16个自己的发送和接收队列,对于超过16个字节的数据必须等待mbed读取完了以后再进行处理,如果读取处理速度不够快的会数据就丢失了,而本代码中0.1秒才读一次,显然是不行的,如果我们把wait(0.1)都去掉,那就没问题了。

当然,我们也可以换种方式来使用UART,前面的代码是用户不断地去读取串口数据,也就是我们所说的轮询方式,这种方式效率是很低的,与其对应的是中断方式,即当串口有数据的时候主动通知程序,下面是改进后的代码,此时你会发现数据丢失的问题也没了:

Serial pc(USBTX,USBRX);

DigitalOut led(LED1);

void echouart()

{

    pc.putc(pc.getc());

}

int main()

{

    pc.attach(&echouart,SerialBase::RxIrq);

    while (1)

    {

        led=0;

        wait(0.1);

        led=1;

        wait(0.1);

    }


}

在Uart串口的双向通讯中,经常涉及到一个用户交互的问题,如当用户执行完一段代码后,经常会说请按任意键或特定键继续,这是,就相当于让程序一直等待,直到程序期待的字符出现,这在mbed中是很容易的,如下面的代码:

Serial pc(USBTX,USBRX);

DigitalOut led(LED1);

char username[100];

int userkey;

int main()

{

    pc.printf("Hello World,please enter you name to continue\r\n");

    pc.scanf("%s",username);

    pc.printf("You name is %s \r\n",username);

    while (pc.readable())

        pc.getc();

    while (1)

    {

        pc.printf("Hello World,please enter return to continue\r\n");

        userkey=pc.getc();

        if (userkey=='\r')

            break;

        else

            pc.printf("Wrong key,please enter return key to continue \r\n");

    }

    pc.printf("Right key,good bye \r\n");


}

      


沙发
598330983|  楼主 | 2015-12-19 21:42 | 只看该作者
  这里需要注意的是,我们额外添加了while (pc.readable())       pc.getc()这部分代码,其目标是为了防止前面的输入有可能没有被scanf读取完全,所以先把它清空再读取。

         考虑到串口操作涉及的大量是字符串操作,所以我们在必要时还可以使用C++的string类来简化字符串的处理,在利用string之前,必须要添加string的引用,即#include<string>,注意不是string.h,string的具体用法可以参见C++的手册,下面是一个简单的例子,用户可以尝试一下,如果做复杂的字符串查找替换操作,建议使用string类:

#include<string>

char username[100];

string str("You name is ");

int main()

{

    pc.printf("Hello World,please enter you name to continue\r\n");

    pc.scanf("%s",username);

    str=str.append(username);

    str=str.append(". \n");

    pc.printf(str.data());



}

使用特权

评论回复
板凳
598330983|  楼主 | 2015-12-19 21:43 | 只看该作者

Uart最常见的应用就是系统和用户之间的串**互,下面的代码就可以实现用户通过发送串口命令控制led的效果,该代码还用到了对象数组,字符变换等处理,这些都是C/C++程序的常用操作,用户可以在理解的基础上进行进一步地语言学习。


#include"ctype.h"

DigitalOut led[4]={ DigitalOut(LED1), DigitalOut(LED2), DigitalOut(LED3), DigitalOut(LED4)};

Serial pc(USBTX,USBRX);

char cmd;

char lednumber;

int main() {

    while (1)

    {

        pc.printf("Please send you command as L(O,T)1(2,3,4) ,L1 means light led1 \r\n");

        while (1)

        {

            cmd=pc.getc();

            if (cmd!= ' ')

                break;

        }

        while (1)

        {

            lednumber=pc.getc();

            if (lednumber!= ' ')

                break;

        }

        while (pc.getc()!='\r')

            ;

        while (pc.readable())

            pc.getc();

        cmd=toupper(cmd);

        int ledindex=lednumber-'0';

        switch(cmd)

        {

        case'L':

            led[ledindex-1]=1;

            break;

        case'O':

            led[ledindex-1]=0;

            break;

        case'T':

            led[ledindex-1]=!led[ledindex-1];

            break;

        }

    }


}

   


使用特权

评论回复
地板
598330983|  楼主 | 2015-12-19 21:43 | 只看该作者
Uart串口的另外一个应用则和GPS数据的解读,因为GPS一般都是采用Uart进行通讯的,它在上电后就会不断输出符合标准的文本信息,其格式如下,该标准为NMEA-0183协议:

$GPGGA,033703.352,3958.1451,N,11622.6185,E,0,03,,57.8,M,,,,0000*13

$GPGSA,A,1,07,13,08,,,,,,,,,,,,*13

$GPGSV,3,1,12,07,66,288,45,08,29,316,34,13,21,227,44,30,01,103,*7A

$GPGSV,3,2,12,19,78,037,15,03,47,051,,11,42,177,,06,31,052,*7C

$GPGSV,3,3,12,16,26,087,,01,21,175,,28,04,285,15,23,04,199,*76

$GPRMC,033703.352,V,3958.1451,N,11622.6185,E,,,050912,,*11

$GPVTG,,T,,M,,N,,K*4E

         NMEA-0183数据都是以$开始,接着是信息类型,后面是数据,以逗号分隔开。在NMEA-0183标准中一共定义了以下数据类型,但并不是所有的GPS设备都会输出所有的信息类型:

GPGSV:可见卫星信息

GPGLL:地理定位信息

GPRMC:推荐最小定位信息

GPVTG:地面速度信息

GPGGA:GPS定位信息

GPGSA:当前卫星信息

其中最重要的就是GPGGA和GPRMC定位信息,其格式说明如下:

$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>

<1> UTC时间,hhmmss(时分秒)格式

<2> 纬度ddmm.mmmm(度分)格式(前面的0也将被传输)

<3> 纬度半球N(北半球)或S(南半球)

<4> 经度dddmm.mmmm(度分)格式(前面的0也将被传输)

<5> 经度半球E(东经)或W(西经)

<6> GPS状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算

<7> 正在使用解算位置的卫星数量(00~12)(前面的0也将被传输)

<8> HDOP水平精度因子(0.5~99.9)

<9> 海拔高度(-9999.9~99999.9)

<10> 地球椭球面相对大地水准面的高度

<11> 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)

<12> 差分站ID号0000~1023(前面的0也将被传输,如果不是差分定位将为空)

$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>

<1> UTC时间,hhmmss(时分秒)格式

<2> 定位状态,A=有效定位,V=无效定位

<3> 纬度ddmm.mmmm(度分)格式(前面的0也将被传输)

<4> 纬度半球N(北半球)或S(南半球)

<5> 经度dddmm.mmmm(度分)格式(前面的0也将被传输)

<6> 经度半球E(东经)或W(西经)

<7> 地面速率(000.0~999.9节,前面的0也将被传输)

<8> 地面航向(000.0~359.9度,以真北为参考基准,前面的0也将被传输)

<9> UTC日期,ddmmyy(日月年)格式

<10> 磁偏角(000.0~180.0度,前面的0也将被传输)

<11> 磁偏角方向,E(东)或W(西)

<12> 模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

使用特权

评论回复
5
598330983|  楼主 | 2015-12-19 21:43 | 只看该作者

为了从中得到我们可以利用的GPS定位信息,我们可以直接通过串口获取数据后进行大量的字符串运算搞定,当然,我们也可以利用现有的库函数搞定,我用的是mbed网站上http://mbed.org/users/Deurklink/code/GPS/这里的库,其具体方法如下:

l  下载此库的压缩包,然后解压到eclipsembedIDE的私有库文件中,默认为mbed/privatelibrary;

l  使用mbed——>Add a library to the selected project菜单启动下面的界面:


l  此时选择我们需要的mbed扩展库即可,这里是GPS,点击Finish按钮继续即可。

这样我们就可以在程序中使用GPS库了,测试代码如下:

Serial pc(USBTX,USBRX);

GPS mygps(USBTX,USBRX);

int main() {

    mygps.Init();

    while (1)

    {

        mygps.parseData();

        pc.printf("alt:%f lat:%f lon:%f \n",mygps.altitude,mygps.latitude,mygps.longitude);

        wait(1);

    }


}

考虑到用户并不一定有GPS设备,我们在测试时可以串口调试工具模拟发送GPS信息,同样也可以完成测试,效果如下。有兴趣的用户也可以去了解一下此库的具体实现,你会发现其中主要的就是C语言的字符串操作,这样说明要想学mbed,C/C++的语言基础很重要:


使用特权

评论回复
6
奥德赛| | 2015-12-20 21:23 | 只看该作者
用这个mbed uart的话还需要配置什么的吗

使用特权

评论回复
7
zhuotuzi| | 2015-12-20 21:47 | 只看该作者
Serial pc(USBTX,USBRX);
GPS mygps(USBTX,USBRX);
int main() {
    mygps.Init();
    while (1)
    {
        mygps.parseData();
        pc.printf("alt:%f lat:%f lon:%f \n",mygps.altitude,mygps.latitude,mygps.longitude);
        wait(1);
    }

}
就这么多代码就可以直接运行那个串口吗?没有看懂初始化的信息啊,是以什么格式都没有配置,怎么工作的?好神奇锕

使用特权

评论回复
8
稳稳の幸福| | 2015-12-21 11:32 | 只看该作者
貌似刚开始图片不显示啊,我点了一下竟然全显示了。大家有没有发现这个奇特现象?

使用特权

评论回复
9
598330983|  楼主 | 2015-12-22 16:36 | 只看该作者
Serial pc(USBTX,USBRX);

   pc.printf("Please send you command as L(O,T)1(2,3,4) ,L1 means light led1 \r\n");

这两句就行,第一句打开串口功能并初始化,第二句就可以直接发送了。

使用特权

评论回复
10
捉虫天师| | 2016-1-31 14:31 | 只看该作者

谢谢分享:lol

使用特权

评论回复
11
orangebanana| | 2016-1-31 15:30 | 只看该作者
这个mbed的应用方式和Arduino的方式差不多啊

使用特权

评论回复
12
quray1985| | 2016-2-1 16:20 | 只看该作者
这样弄的话感觉确实很简单学,专业人士的话还是免了吧

使用特权

评论回复
13
huangcunxiake| | 2016-2-4 18:05 | 只看该作者
官方提供具体的说明是最好的。主要是只学了C语言的,对类理解还不足,理解后,使用也需要段时间的磨合

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

249

主题

5397

帖子

22

粉丝