应用笔记 4465

MAXQ610微控制器的串口使用


摘要 : MAXQ610微控制器提供两路标准USART串口。异步模式1是MAXQ610串口支持的模式之一,可以用于实现与PC COM口及其它多种现有设备进行通信。本应用笔记介绍MAXQ610串口发送、接收操作的中断处理功能,当接收到一个新字符或发送完毕一个字符后,通过该功能,应用程序能够迅速响应串口。

简介

MAXQ610微控制器包括能够与外部器件或系统进行通信的外设。这些外设带有主机/从机串行外设接口(SPI™);红外(IR)通信载波发生器/调制系统;两路单独的通用同步/异步接收器/发送器(USART),即众所周知的串口。

本应用笔记演示在MAXQ610上以10位异步模式使用串口。该模式通常用于实现与PC标准COM口进行通信,发送调试输出。只需要有一致的公共位格式和波特率,不论通信通道两端的实际工作频率是多少,该模式就可以实现两个器件或者系统之间的通信。

本应用笔记使用基于汇编语言的MAX-IDE环境,为MAXQ610和MAXQ610评估套件(EV kit)编写了演示程序。可以下载本应用笔记涉及到的演示应用程序代码和工程文件。

需要下面的硬件和软件才能运行演示程序。
  • MAXQ610评估套件电路板
  • MAXQJTAG电路板(串口至JTAG接口)
  • JTAG编程电缆(2 x 5母头至母头0.100"插头,10连接器带状电缆)
  • DB9直通公头至母头串行电缆
  • 5VDC稳压电源,中心为正极
  • 支持COM (串行)端口或者USB至串口适配器的PC
  • MAX-IDE环境
  • 终端仿真器(例如,工作在Dumb Terminal模式下的MTK、HyperTerminal或TeraTerm)
可以免费下载最新的MAX-IDE环境安装包和文档。 必须安装IAR Embedded Workbench®才能运行C版本的演示程序,MAXQ610评估套件中的CD提供了该评估软件。

配置MAXQ610评估套件

本应用笔记涉及到的演示程序运行在MAXQ610评估套件上。为保证程序能够正常运行,实现正确的软件功能,必须正确配置评估套件电路板的跳线。在电路板上运行演示程序时所有的步骤(例如,装载程序、运行程序)都采用了相同的跳线配置。

配置MAXQ610评估套件上的跳线。
  • JH1:连接引脚2和引脚3。
  • JH2:连接引脚2和引脚3。
  • JH3:连接引脚2和引脚3。
  • 连接以下跳线(引脚1和引脚2):JH14、JH20、JH22、JH23、JH24、JH25和JH26。
正确配置MAXQ610评估套件电路板之后,对硬件进行设置,以便编译并装入演示程序。
  • 连接串口至JTAG电路板上的跳线JH1、JH2和JH3。
  • 将5V直流电源连接到串口至JTAG电路板上的插座J2。
  • 使用DB9电缆连接PC COM1端口和串口至JTAG电路板上的J1连接器。
  • 使用JTAG编程电缆,连接串口至JTAG电路板的P2和MAXQ610评估电路板上的P5。JTAG电缆上的红线应连接至两个连接器的引脚1 (TCK)。
  • 接通5V直流电源。

串口模式及其初始化

MAXQ610上的两个USART外设中的每一个都可以工作在同步或者异步模式下。当工作在同步模式(模式0)时,MAXQ610是所有操作的总线主机。它配置TXD线,使其成为所有发送和接收操作的移位时钟,而RXD用作双向数据线。采用这一配置时,只有在主机请求时,数据每次只能向一个方向传送(半双工)。

在异步工作模式(模式1、2和3)下,有两条单向数据线:TXD和RXD。TXD线传送MAXQ610串口至外部器件的异步数据,而RXD线传送从外部器件返回MAXQ610的异步数据。MAXQ610与外部器件必须在公共格式(数据位的数量、校验位和停止位)以及公共波特率(总线频率)上达成一致,才能实现通信。由于TXD和RXD线可以单独进行传送,不需要彼此同步,因此,这些模式支持数据以全双工方式传送。MAXQ610可以向TXD线上任一点的外部器件发送数据,不论外部器件当前是否在RXD上传输数据,反之亦然。

对于这一演示程序,我们将选择模式1,它具有以下特性。
  • 串行数据异步发送(在TXD上)和接收(在RXD上)
  • 专用波特时钟发生器提供的波特时钟(可以使用PR寄存器进行编程)
  • 8个数据位,1个起始位,1个停止位
  • 没有校验
串口在模式1下采用的波特率(每秒发送/接收的位数)由以下两个方程决定(正如MAXQ系列用户指南(English only)中所讨论的):

波特时钟频率(BAUD) = 系统时钟频率 × PR/217 (式1)

波特率 = BAUD × 2(SMOD × 2)/26 (式2)

式1确定了波特时钟发生器的输出。由16位PR (相位)寄存器中的内容控制所产生的输出频率。在PR寄存器中装入较大的数值会产生频率较高的波特时钟。

式2确定SMD或者串行端口模式寄存器中的控制位(SMOD)的作用。将该位设置为1,最终波特率提高4倍。这表明,根据SMOD值,式2也可以表达为以下形式:

当SMOD = 0时,波特率 = BAUD/64 (式3)

当SMOD = 1时,波特率 = BAUD/16 (式4)

对于大部分晶体速率和波特率组合,两种SMOD设置都可以采用。注意,SMOD设置影响装入到PR寄存器中的数值。在我们的应用中,选择SMOD = 1,意味着波特率公式简化为:

波特率 = (系统时钟频率 × PR/217)/16 (式5)

或者:

波特率 = (系统时钟频率 × PR)/221 (式6)

解出PR值得到我们最常用的公式。通常情况下,波特率和系统时钟频率是固定的,我们只需要知道能够产生目标波特率所需的PR寄存器设置。因此:

PR = 波特率 × 221/(系统时钟频率) (式7)

例如,使用MAXQ610评估板上的标准12MHz晶体,选择9600波特率。所需要的PR寄存器设置是(9600 × 221/12000000),大约为1677或者068Dh。

计算出PR和SMOD值之后,将USART设置为相应的模式,写入PR和SMOD值就很容易对串口进行初始化。由于我们使用了串口0,因此,针对该串口将代码写入到相应的一组寄存器中(SCON0、SMD0、PR0和SBUF0)。
;==============================================================================
;=
;=  InitSerial0
;=
;=  Set up serial port 0 to run in 10-bit asynchronous mode at
;=  9600 baud.
;=

InitSerial0:
   move    SCON0.6, #1       ; Set to mode 1 (10-bit asynchronous).
   move    SCON0.4, #1       ; Enable receiver. 
   move    SMD0.1,  #1       ; Baud rate = 1/16 of baud clock
   move    PR0, #0068Dh      ; P = 2^21 × 9600/12.000MHz
                             
   move    SCON0.0, #0       ; Clear received character flag.
   move    SCON0.1, #0       ; Clear transmit character flag.
   move    SMD0.2,  #1       ; Enable receive/transmit interrupt.
   ret
关于USART外设工作模式的详细信息,请参考MAXQ610用户指南(English only),以及MAXQ系列用户指南(English only)中的第10节(串行I/O模块)

发送和接收字符

正确配置好串口后,可以准备发送和接收字符。在模式1下,一个字符包括1个起始位,8个数据位和1个停止位。起始位和停止位用于同步目的,由USART硬件进行控制。其余的8位传送实际数据,意味着串口一次可以发送或者接收一个8位字节。

通过串口发送一个字符涉及到以下三个步骤。
  1. 把要发送的字节写入到SBUF寄存器中。
  2. 等待SCON寄存器中的TI (发送中断)位变为高电平(置为1),这表明硬件已经完成了通过串口进行的字符发送。
  3. 将TI位清零。
始终需要等待一次发送完成后才能开始另一次新的发送。相对于指令执行周期,串口发送所需要的时间较长。因此,在等待串口完成发送之前,可以在步骤2中执行其它操作。例如,当使用12MHz晶体以115200波特率进行发送时,发送所需要的总时间大约为10 × (1/115200),即87µs。在同一时间段内,MAXQ610可以执行多达1041条指令(87µs/(1/12MHz))。

以相似的方式通过串口接收一个字符。
  1. 等待RI (接收中断)位变为高电平,表明串口接收到了一个新字符。
  2. 读取SBUF寄存器,获得数据字节。
  3. 将RI位清零。
在任何时间,只有一个接收到的字符被存储(缓冲)在串口硬件中。这意味着硬件如果将RI位置为1 (表明已经接收到了一个字符),必须通过读取SBUF寄存器来获得该字符;串口接收下一字符前,必须清除RI位。如果串口接收到一个新字符,而RI位仍然为高电平,新字符会丢失。

处理串口中断

对于每一发送和接收操作,如果需要,串口操作的一个简单方法是轮询(不断检查) RI和TI位值。例如,当写入一个字符时,应用程序代码将数据字节写入SBUF,轮询TI位,直到硬件将其置为1。在发送完成之前,不会进行其它操作。类似的,接收一个新字节时,应用程序只需要轮询RI位,直到硬件将其置为高电平,然后,通过读取SBUF,取走接收到的字节。

轮询RI和TI位的方法虽然简单,但是等待发送或者接收字符的时间太长,因此牺牲了性能。此外,应用程序必须事先知道什么时候外部器件会发送新字符,应用程序不能同时发送和接收字符。

一种更灵活(但是稍有些复杂)的方法利用了TI和RI不仅仅是状态位而且还是同步中断源这一事实。MAXQ610不是连续轮询TI和RI位的状态来确定发送或者接收操作是否完成,而是当TI或者RI从0变为1时,使能中断。该方法使应用程序在需要时才响应串口,因此提高了时间效率。

这一过程的第一步是通过将IGE位(IC.0)置为1,使能所有中断。这样,MAXQ610可以从全局出发来处理中断。

然后,必须安装中断处理程序。MAXQ610使用多路固定中断矢量系统,每个中断源或者每组中断源分配不同的(不可编程)矢量地址。出于我们的目的,我们需要串行端口中断矢量,它被分配为字地址0040h。

下面的中断处理程序完成以下功能。
  1. 临时设置IC.0为0,防止其它中断(较高优先级)发生。
  2. 将PSF和Acc寄存器推入堆栈,保存这些寄存器的数值。由于随时都可以触发中断,因此,中断处理程序必须一直保存并恢复其它应用程序使用的任何寄存器。
  3. 由于TI (发送)或者RI (接收)标志都可以触发串行端口中断,因此,处理程序必须检查中断为什么被触发。
  4. 如果发送中断被触发,清除TI位,置位标志值(存储在A[15]中),向应用程序指示发送已经完成。出于演示目的,这非常简单,中断处理程序还可以针对这一状态,在SBUF中装入新值,发送下一字符。
  5. 如果触发了接收中断,清除RI位,读取来自SBUF的接收字符;根据所接收到的字符,采取相应的措施。
  6. 将Acc和PSF寄存器值从堆栈中弹出,恢复其数值。
  7. 将IC.0置为1,重新使能中断。
  8. 使用RETI指令,退出中断处理程序。
org 0040h

serialInt:
   move    IC.0, #0          ; Block any other interrupts from triggering.
   push    PSF
   push    Acc
   move    C, SCON0.0        ; Check for receive character interrupt.
   jump    C, serialInt_Rx

serialInt_Tx:
   move    SCON0.1, #0       ; Clear transmit complete interrupt flag.
   move    A[15], #1         ; Set flag to indicate transmit complete.
serialInt_done:
   move    IC.0, #1          ; Re-enable interrupts.
   pop     Acc
   pop     PSF
   reti

serialInt_Rx:
   move    SCON0.0, #0       ; Clear receive character interrupt flag.
   move    Acc, SBUF0        ; Get character from serial port.
   cmp     #'0'
   jump    E, serialInt_Rx0
   cmp     #'1'
   jump    E, serialInt_Rx1
   cmp     #'2'
   jump    E, serialInt_Rx2
   cmp     #'3'
   jump    E, serialInt_Rx3
   cmp     #'4'
   jump    E, serialInt_Rx4
   jump    serialInt_done

serialInt_Rx0:
   move    Acc, PO3
   or      #0Fh              ; Turn all LEDs off.
   move    PO3, Acc
   jump    serialInt_done


....


serialInt_Rx3:
   move    Acc, PO3
   xor     #04h              ; Toggle P3.2 state. 
   move    PO3, Acc
   jump    serialInt_done

serialInt_Rx4:
   move    Acc, PO3
   xor     #08h              ; Toggle P3.3 state.
   move    PO3, Acc
   jump    serialInt_done

开发演示应用程序

串行演示应用程序的整个架构如下。
  1. 初始化端口引脚和串口。
  2. 使能中断。
  3. 通过串口发送以下标志文本。
    	MAXQ610 Serial Port Demo
    	Type characters "1"-"4" to toggle LEDs or '0' to turn all LEDs off.
    
  4. 已经收到字符"1"、"2"、"3"、"4"或"0",相应改变LED状态。
如上面的中断处理程序代码所示,应用程序改变端口引脚P3.0、P3.1、P3.2和P3.3的输出状态,响应接收到的字符。在MAXQ610评估套件电路板(跳线JH22、JH23、JH24和JH25闭合)上,这些端口引脚驱动4个LED DS1、DS2、DS3和DS4。端口引脚上的低电平状态(0)导致LED接通(点亮),而端口引脚上的高电平状态使LED关断。
main:
   move    WDCN,  #0 
   move    PD3.0, #1         ; Set P3.0 to output mode.
   move    PO3.0, #1         ; Drive P3.0 high (LED off).

   move    PD3.1, #1         ; Set P3.1 to output mode.
   move    PO3.1, #0         ; Drive P3.1 low (LED on).

   move    PD3.2, #1         ; Set P3.2 to output mode.
   move    PO3.2, #1         ; Drive P3.2 high (LED off).

   move    PD3.3, #1         ; Set P3.3 to output mode.
   move    PO3.3, #0         ; Drive P3.3 low (LED on).

   move    PD0.2, #1         ; Set P0.2 (TXD) to output mode.
   move    PO0.2, #1         ; Idle high when not transmitting.

   call    InitSerial0
   move    IC.0, #1          ; Enable interrupts.

   move    LC[0], #12000     ; Give transceiver time to power on.
   djnz    LC[0], $      

   ; Print string to serial port.

   move    CP, #0800h
printLoop:
   move    Acc, @CP++
   nop
   jump    Z, printLoop_done
   move    GR, Acc
   move    Acc, GRL
   call    TxChar0
   move    Acc, GRH
   call    TxChar0
   jump    printLoop
printLoop_done:
   nop

mainLoop:
   nop
   jump    mainLoop


;==============================================================================
;=
;=  TxChar0
;=
;=  Outputs a character to serial port 0.
;=
;=  Inputs  : Acc - Character to send.
;=

TxChar0:
   push    Acc
   move    SBUF0, Acc        ; Send character.
   move    A[15], #0         ; Clear interrupt service routine flag.
TxChar0_Loop:
   nop
   nop
   nop
   move    Acc, A[15]        ; Check flag.
   jump    Z, TxChar0_Loop
   move    SCON0.1, #0       ; Clear the transmit flag.
   pop     Acc
   ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

org 800h

   db 0Dh, 0Ah, 0Dh, 0Ah
   db "MAXQ610 Serial Port Demo", 0Dh, 0Ah, 0Dh, 0Ah
   db "Type characters '1'-'4' to toggle LEDs or '0' to turn all LEDs off"
   db 0Dh, 0Ah, 0Dh, 0Ah
   dw 0000h
使用起始字地址0800h的DB声明来存储标志文本。这一常数和程序代码的其它部分一起被装入到MAXQ610的闪存中,可以使用上面所示的CP代码指针(printLoop)来进行恢复。由于使用CP从程序存储器中每次获取一个数据字,因此,每读取一个字,通过串口发送两个字符。

运行演示程序

使用MAX-IDE对演示程序进行编译,装入到MAXQ610评估套件中后,可以按以下步骤执行它。
  1. 关断电源,断开JTAG、电源和串行电缆。
  2. 使用串行电缆,连接PC上的COM1和MAXQ610评估电路板上的J1插座。
  3. MAXQ610评估电路板上的J3插座连接电源线。
  4. 打开电源。
  5. 打开PC上的终端仿真器程序。将其配置为通过COM1进行通信,9600波特率,8个数据位,1个停止位,没有校验。
  6. 按下并释放MAXQ610评估电路板上的RESET (SW1)。在您的终端仿真屏上应显示标志文本(MAXQ610 Serial Port Demo...)。如果没有显示,请检查您的连接和跳线设置。
  7. 输入以下字符,触发LED状态:输入"1",触发DS1;输入"2",触发DS2;输入"3",触发DS3;输入"4",触发DS4;或者输入"0",关断所有LED。字符不会回显。

C演示程序

下面的演示程序显示了相同的应用程序,在IAR™的嵌入式工作台IDE上通过C实现。为简单起见,去掉了中断处理程序。请注意,putchar函数用于通过串口0输出一个字符。这样,可以使用标准I/O库函数,例如puts()printf()
int putchar(int c)
{
   SBUF0 = c;
   while ((SCON0 & 0x02) == 0);
   SCON0 = (SCON0 ^ 0x02);
   return c;
}

void initUSART0(void)
{
   int i2;
   PD0_bit.bit2 = 1;   // Hold Tx0 line High.
   PO0_bit.bit2 = 1;   // Hold Tx0 line High.
   SMD0 = 2;           // Set baud rate select bit.
   SCON0 = 0x50;       // Set mode 1 and receive enable for UART 0.
   PR0 = 0x068D;       // 9600 baud: PR0 = 2^21 * 9600 / 12.000MHz
   for (i2 = 1; i2 < 10000; i2++);   // Give transceiver time to power on.
   SCON0 = 0x50;       
}

void main( void )
{
   int c;
   IC_bit.IGE = 0;
   WDCN       = 0;    
   PD3 = 0x0F;
   PO3 = 0x05;    // Default - DS2 and DS4 on, DS1 and DS3 off

   initUSART0();
   puts("MAXQ610 Serial Port Demo");
   puts("Type characters "1"-"4" to toggle LEDs or "0" to turn all LEDs off\n");

   while (1) {
      while ((SCON0 & 0x01) == 0);        // Wait for RI flag to go high.
      c = SBUF0;                          // Receive character.
      SCON0 = (SCON0 ^ 0x01);             // Clear RI flag.
            
      switch (c) {
         case '0' : PO3 = 0x0F;
                    break;
         case '1' : PO3 = (PO3 ^ 0x01);   // Toggle P3.0
                    break;
         case '2' : PO3 = (PO3 ^ 0x02);   // Toggle P3.1
                    break;
         case '3' : PO3 = (PO3 ^ 0x04);   // Toggle P3.2
                    break;
         case '4' : PO3 = (PO3 ^ 0x08);   // Toggle P3.3
                    break;
         default  : break;
      }
   }
}

结论

MAXQ610提供了两个在很多MAXQ微控制器上使用的标准USART串口。异步模式1是MAXQ610串口支持的模式之一,可以用于实现PC COM端口与其它多种类型的现有设备进行通信。MAXQ610串口发送和接收操作支持中断处理功能,当接收到一个新字符或者发送完毕一个字符后,通过该功能,应用程序能够迅速响应串口。

MAXQ610上的USART串口支持多种配置选择和通信模式,这已经超出了本应用笔记的讨论范围。如果需要了解详细信息,请参考上面提到的MAXQ610用户指南(English only)以及MAXQ系列用户指南(English only)第10节。