S3C2440A的UART

原文地址:S3C2440A的UART 作者:晨星

    UART(Universal Asynchronous Receiver/Transmitter),通用异步收发器,是常用于开发调试的RS-232C串口的核心器件。在百度百科(http://baike.baidu.com/view/245027.htm)上可以找到对它的详细介绍。

    虽然这段时间实验定时器、键盘、LED操作时都使用了UART串口输出功能,但那是用开发板提供的示例程序中已有的代码的,想要自己写代码正确实现串口读写操作,还不是那么容易的。昨天花了整整一天时间才稍微弄得明白了点,主要是涉及的寄存器和可选操作方式多,包括收发方式、边沿触发中断与电平触发中断、是否使用自动流控制、是否使用FIFO以及FIFO触发级别、换行符转换、退格键的处理等。特别是边沿触发中断和电平触发中断的处理方式,这个问题困扰了我很久,经过很多次失败的尝试,最后才稍微明白点。关于这两种中断触发方式的论述,是本文的重点。

 

    TTL与RS-232C

    mini2440开发板有3个串口,即TTL0、TTL1和TTL2,其中TTL0还有一个RS-232C形式的物理插座,和PC机上常见的COM口外形相同,用于程序调试。看到TTL,很多人可能会立刻想到Time To Live,即IP分组的存活时间,但这里它显然不是这个意思。用百度搜索了下,就是找不到对TTL串口的解释,不知道这里的TTL是不是Transistor-Transistor Logic(逻辑门电路)的意思,但感觉这个跟串口没有关系。

    RS-232C即通常所说的串口的通信标准,是1969年美国电子工业协会(Electronic Industries Association,EIA)建立的串口接口电气信号与电缆连接特性标准,命名为建议标准第232号版本C,即Recommend Standard 232C,简写为EIA-RS-232C,或者更常见的写法是RS-232C。当然,更常见的情况是,少了最后的字母C,写作RS-232,或者RS232。

    TTL与RS-232C是有所差别的,最大的差别是:TTL以正电平表示逻辑1,负电平表示逻辑0;而RS-232C是相反的,用-3~-15V的电平表示逻辑1,用+3~+15V的电平表示逻辑0。这样,RS-232C插座就不能直接连接到TTL串口上,因为需要进行电平转换,开发板是用MAX3232芯片进行这种转换的。在http://focus.ti.com.cn/cn/docs/prod/folders/print/max3232.html?DCMP=CN_HPA_BSE&HQS=Interf-494&247SEM可以下载到它的说明书,芯片的名字是多通道RS-232线路驱动器/接收器。

 

     2 相关寄存器

     S3C2440A的UART单元对每个串口使用10多个寄存器,3个串口共使用了30多个寄存器。我稍微总结了下(下面的n表示串口编号,取值为0,1,2;寄存器名称中第一个字母U应该是表示UART):

     ULCONn:线路控制寄存器,用于设定线路的字长度、停止位个数、奇偶校验方式、是否使用红外模式。(看的书中翻译成“线性控制寄存器”,我感觉是不正确的)

     UCONn: 控制寄存器,用于设定操作模式(中断或轮询/DMA)、环回模式、中断方式、时钟选择。

     UFCONn:FIFO控制寄存器,用于控制FIFO操作方式,如是否使用FIFO以及触发级别。

     UMCONn:Modem控制寄存器,用于设置是否使用AFC(自动流控制)和RTS。TTL2是不支持流控制的,所以没有UMCON2寄存器。

     UTRSTATn:收发状态寄存器,可从中读取收发保持寄存器的状态,即是否有数据,仅在非FIFO模式下使用。

     UFSTATn:FIFO状态寄存器,可从中读取FIFO状态信息,用于FIFO模式。

     UMSTATn:Modem状态寄存器,可从中读取Modem状态,即CTS信号状态。TTL2不支持流控制,所以没有UMSTAT2寄存器。

     UERSTATn:错误状态寄存器,可从中读取接收错误状态。

     UTXHn和URXHn:收发保持(对非FIFO模式)和缓冲(对FIFO模式)寄存器,用于收发数据。

     UBRDIV:波特率除数寄存器,用于设定串口通信波特率。

    

     关于Modem:

     串口通信跟Modem是怎样的关系?搜索到一篇好文章:http://blog.chinaunix.net/u/20526/showart.php?id=355252。个人理解应该是两台计算机以“计算机A--UART-A(串口A)--Modem-A-----Modem-B--UART-B(串口B)--计算机B”的方式进行连接,这里的Modem可以是电话线上使用的56K Modem或者xDSL Modem,Cable Modem,GPRS装置等。这也可以看出为什么UART名字中有U(通用)的了:它可以与各种Modem相连接(或者不使用Modem,两个串口直接相连也是可以的吧),实现并行数据的串行收发,让内部以并行方式传输数据的系统可以以串行方式进行远距离通信。因为并行通信的速度虽然快,但传输距离有限,一般只用于系统内部,如系统内的各种总线(IDE、ISA、PCI、PCIExpress等);而串行通信速度慢,但可以传输较远的距离,一般用于系统间的通信,如USB、蓝牙、网络、串口等。

   

   3 中断方式操作

   开发板提供的示例程序是使用轮询方式进行串口读写操作的,即程序采用同步或者阻塞方式进行串口操作。对于简单的示例程序,这当然没有问题,因为程序即使不等待串口操作完成,也没有其他事情可做。但在实际应用中,很可能是要求以非阻塞方式进行读写操作的,这时可能就应该使用中断方式进行串口读写操作了。

   通过UCONn可以设定UART以中断方式操作,还可以设定中断触发方式:边沿触发和电平触发。这个问题困扰了我好久才稍微弄明白点。下面是我咬文嚼字的理解,不知道对不对。引用一段S3C2440A用户手册的原文如下:

   [转载]S3C2440A的UART

 

    边沿(脉冲?)触发:一旦(as soon as)Tx缓冲区变为(becomes)空(非FIFO模式)或者达到(reaches)Tx FIFO触发级别(FIFO模式),则请求中断。注意,这里的“一旦”不仅限定前半句(非FIFO模式),也限定后半句(FIFO模式)。“一旦”与“变为”和“达到”连接起来,表示的是一个时间点,一个变化过程,是变为空或者达到触发级别的时间点或者说变化过程中请求中断。这个时间点过后,或者变化完成后,就不再请求中断了。只要处理中断后,清除源未决寄存器(SRCPND)和中断寄存器(INTPND)的相关位,就不会再有中断发生了。

    电平触发:当(while)Tx缓冲区为空(非FIFO模式)或者达到Tx FIFO触发级别,则请求中断。这里的“当”限定前半句和后半句。“当”表示的是一种状态,是处于缓冲区空或者达到Tx FIFO触发级别的状态的时候,就请求中断。处理中断后,即使清除了源未决寄存器(SRCPND)和中断寄存器(INTPND)的相关位,只要这种状态存在,就一直请求中断,即会再次发起中断请求。只有不存在这种状态了,才不会再发起中断请求。

    对两种中断触发方式的理解很重要,它直接影响到程序的编写方法。先看看边沿触发:

    串口初始化代码为: 

 void UART0_Init(void)
{
    // 8-N-1,50MHz,115200bps
    rULCON0 = 0x03;
    rUBRDIV0 = (int)(50 * 1000 * 1000 / (16.0 * 115200) + 0.5) - 1;
    rUMCON0 = 0x01;
    rUFCON0 = 0x00;
    rUCON0  = 0x05;

    pISR_UART0 = (unsigned int)UART0_ISR;
    rINTSUBMSK &= (~(BIT_SUB_RXD0 | BIT_SUB_TXD0 | BIT_SUB_ERR0));
    rINTMSK &= (~BIT_UART0);
}

       这里设定串口以边沿中断方式工作,不使用FIFO。中断处理代码为: 

 static void __irq UART0_ISR(void)
{
    UART0_do_transmit();

    rSUBSRCPND = BIT_SUB_RXD0;
    rSUBSRCPND = BIT_SUB_TXD0;
    rSUBSRCPND = BIT_SUB_ERR0;

    rSRCPND = BIT_UART0;
    rINTPND = BIT_UART0;
}

void UART0_do_transmit()
{
    int cur,fifo_size;
   
    if (!(rSUBSRCPND & BIT_SUB_TXD0)) return;

    if (data_to_send_len > 0)
    {
        // 使用FIFO
        if (rUFCON0 & 0x01)
        {
            fifo_size = UART_TX_FIFO_SIZE - ((rUFSTAT0 & 0x3F00) >> 8);
            if (rUFSTAT0 & 0x4000) fifo_size = 0;
        }
        // 不使用 FIFO
        else if (rUTRSTAT0 & 0x02)
        {
            fifo_size = 1;
        }
        else
        {
            fifo_size = 0;
        }

        if (fifo_size > 0)
        {
            if (fifo_size > data_to_send_len) fifo_size = data_to_send_len;
           
            for(cur = 0; cur < fifo_size; cur++)
            {
                rUTXH0 = uart_send_buff[cur];
            }
            data_to_send_len -= fifo_size;
            if (data_to_send_len > 0)
            {
                memcpy(&uart_send_buff[0],
                    &uart_send_buff[fifo_size],
                    data_to_send_len);
            }
        }
    }
}

     编译了,烧录到开发板中,在串口调试工具中看不到开发板发送的字符串。为什么呢?

     在设定UCON0之后,因为缓冲区变为空,所以会发起Tx中断,但在中断处理代码中,发现没有等待发送的数据,所以不进行发送操作。中断处理完成后,清除相关寄存器的相应位。因为是边沿触发,所以随后虽然缓冲区一直为空,但因为没有“变为空”这个边沿条件,所以不会再发生中断。是这样的吗?验证一下,在打开中断屏蔽前,在发送缓冲区里面放一些等待发送的数据试试:    

 void UART0_Init(void)
{
    // 8-N-1,50MHz,115200bps
    rULCON0 = 0x03;
    rUBRDIV0 = (int)(50 * 1000 * 1000 / (16.0 * 115200) + 0.5) - 1;
    rUMCON0 = 0x01;
    rUFCON0 = 0x00;
    rUCON0  = 0x05;

    UART0_send_string("Some data before open interrupt maskn");
    pISR_UART0 = (unsigned int)UART0_ISR;
    rINTSUBMSK &= (~(BIT_SUB_RXD0 | BIT_SUB_TXD0 | BIT_SUB_ERR0));
    rINTMSK &= (~BIT_UART0);
}

     这样修改程序后却发现,只输出了第一个字符“S”。原因何在?

     a 由于使用的是非FIFO模式,所以一次只能发送一个字节

     b 虽然第一个字节发送后,会存在缓冲区“变为空”的条件,会再次引发中断,但发送操作后的语句清除了相关标志位,导致中断丢失,具体过程为:

    (1)缓冲区变为空,发起Tx中断

    (2)进行中断处理,发送一个字节的数据

    (3)一个字节数据发送完成,缓冲区变为空,再次发起Tx中断

    (4)清除SUBSRCPND、SRCPND和INTPND中相关标志位,造成中断丢失

    (5)因为没有再向发送缓冲区写入数据,所以不会再发生缓冲区“变为空”的条件,不会再发生中断

     这里要注意中断的发起与处理方式:UART芯片进行数据收发和发起中断的操作,是与中央处理器同时进行的(不是软件模拟的并行,是硬件上的真正并行)。这个分析对不对呢?还是可以验证下:把清除SUBSRCPND的语句放换个位置,放在UART0_do_transmit()函数最开始看看:    

static void __irq UART0_ISR(void)
{
    UART0_do_transmit();

    rSUBSRCPND = BIT_SUB_RXD0;
    //rSUBSRCPND = BIT_SUB_TXD0;
    rSUBSRCPND = BIT_SUB_ERR0;

    rSRCPND = BIT_UART0;
    rINTPND = BIT_UART0;
}

void UART0_do_transmit()
{
    int cur,fifo_size;   
    if (!(rSUBSRCPND & BIT_SUB_TXD0)) return;
    rSUBSRCPND = BIT_SUB_TXD0;

         
}

     这样修改后可以正确输出第一次要求输出的字符串,但随后在看门狗中断中要求输出的字符串却不能输出。因为只要某次调用中断处理函数时,发现没有等待发送的数据,则最后一次缓冲区变为空引发的中断请求被处理完成后,由于再没有缓冲区变为空的条件,就不会再发生中断。随后即使有等待发送的数据了,那也与UART无关,不会再引发中断,无法再进行数据发送操作了。

    这种情况下,如果采用电平触发方式,则由于一直处于缓冲区空的状态,所以会再次发起中断请求,可以继续进行数据发送操作。只要在UART0_Init()函数中设置UCON0的值为0x0205就是采用电平触发方式了,测试结果与预期一致。

    在边沿触发方式下,为保证连续发送,必须不停地进行发送操作,以保证总有发送缓冲区“变为空”的条件发生;而电平触发方式则不需要这种不停的发送操作。但是,在所有数据发送完成,没有新的数据等待发送时,缓冲区一直处于空的状态,这时会不断地发生不必要的中断,让系统负载过重,严重影响效率,甚至让由定时器中断控制的LED累加器操作都不能进行了。为克服这种影响,可以采取的方式是:在中断处理中,当发现没有等待发送的数据时,屏蔽Tx中断,避免不必要的中断响应;但Tx中断未决请求仍然保留。这样,在主程序产生需要发送的数据后,只要打开对Tx中断的屏蔽,就可以继续进行发送了。 

 static void UART0_do_transmit()
{
    int cur,fifo_size;   
    if (!(rSUBSRCPND & BIT_SUB_TXD0)) return;

   

    if (0 == data_to_send_len)
    {
        rINTSUBMSK |= BIT_SUB_TXD0;
    }
}


static int UART0_send_string(const char* buff)
  

   
    if ((0 == old_len) && (send_len > 0))
    {
        rINTSUBMSK &= (~BIT_SUB_TXD0);
    }
    
    return send_len;
}

 

 

   4 FIFO

   S3C2440A的UART内部对于接收和发送各有64字节的缓冲区,当使用FIFO模式时,UART将使用这个缓冲区进行数据暂存操作,这样可以增加数据吞吐量,提高传输速率。其实,非FIFO模式也可以看作是特殊的FIFO模式,即只有一个字节缓冲区的FIFO模式。二者的主要不同在于读取缓冲区状态的方式:非FIFO模式下,通过UTRSTAT寄存器得知收发缓冲区状态;FIFO模式下,则从UFSTAT寄存器获得缓冲区状态。要注意的是,在FIFO模式下,只有达到触发级别后才会发起Rx或Tx中断。比如说,如果设置接收触发级别为16字节,则只有在接收缓冲区中有16个字节以上数据时,才会发起Rx中断请求。如果需要进行输入回显,则可能导致不能立即回显用户在串口工具中输入的字符。

 

   5 自动流控制(AFC)

   自动流控制涉及到RTS和CTS,我查了下相关缩写的含义:

   DTE:Data Terminal Equipment,数据终端设备,一般指计算机。

   DCE:Data Communication Equipment,数据通信设备,一般是调制解调器(Modem)。

   DTR:Data Terminal Ready,数据终端就绪,DTE向DCE发送这个信号表示已经准备就绪。

   DSR:Data Set Ready,数据设备就绪,DCE向DTE发送这个信号表示已经准备就绪。

   RTS:Request To Send,请求发送,DTE向DCE请求发送数据。

   CTS:Clear To Send,清除发送,DCE向DTE表示准备就绪,可以接收数据了。

   在百度百科上发现,RTS和CTS原来是用于半双工通信中,DTE从接收模式转换成发送模式,在全双工模式中两个信号线一直有效就可以了。后来,SmartModem的出现,使得DTR,DSR,RTS和CTS的作用都改变了,变成用于“硬件流控制”了。总之,太复杂了,上文提到的http://blog.chinaunix.net/u/20526/showart.php?id=355252处的文章说得很详细了,就不深入研究了。日常工作使用中,也很少见到使用硬件流控制的。

 

   6 其他问题

   DMA操作方式:等学习了DMA后再进行尝试。

   换行符问题:C语言中习惯用n表示换行,但在Windows世界里,却需要用rn表示换行,有的串口工具又进行了相互转换,所以为保证n真正起到回车换行的作用,需要仔细研究下串口工具,可能还需要在程序中进行一些转换。

   退格符问题:有时候b是不能起到删除前一个字符的作用的,这时可以用bx20b试试。

   中文显示问题:串口工具可能剥离了字节数据的最高位,导致不能正确显示中文。SecureCRT可以设置是否剥离最高位,在不剥离最高位的情况下,是可以正确显示中文的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值