UART/SIO 总线是一种通用异步串行总线, 它是各种设备之间进行通信的关键模块,当一个设备需要和另一个连接的设备进行通信时,通常采用数字信号。在发送端,这种并行的数字信号必须转换成串行信号后,才能通过有线或无线方式传输到另一台设备。在接收端,串行信号必须要被恢复成并行信号才能进行处理。UART就是用来处理这种数据总线和串行口之间串—并和并—串转换的。
8.2.1 UART/SIO 的结构及功能
UART/SIO主要由以下五个寄存器组成:
- 串行模式控制寄存器1(SMC10):设置串行数据传输方向(从低位还是高位开始传), 奇偶校验位,停止位的长度,数据长度,工作模式,串行时钟
- 串行模式控制寄存器2(SMC20):使能串行时钟输出,串行数据输出,发送/接受,中断和清除接受错误标志位
- 串行状态数据寄存器(SSR0):指示传输/接受的状态以及错误信息
- 串行输入数据寄存器(RDR0):存储接受到的数据,将串行数据通过一个移位寄存器后存储到RDR0
- 串行输出数据寄存器(TDR0):存储要发送出去的数据,通过一个移位寄存器后转换成串行数据输出
其功能模块如下图8-1所示:

当UART/SIO作为接受端时,它主要是用来接受其它设备发送过来的串行信号,通过串行数据输入引脚(UI0)送入到接受端移位寄存器,将串行数据转换为并行数据后储存在串行输入数据寄存器中(RDR0),最后送到内部总线上进行处理。
当UART/SIO作为发送端时,内部总线将并行数据发送到串行输出数据寄存器(TDR0)中,如果串行总线空闲,则将数据送入发送端移位寄存器,将并行数据转换为串行数据后通过串行数据输出引脚(UO0)输出。
8.2.2 UART/SIO 的工作模式
UART/SIO有两种工作模式,即
- 模式0:异步时钟模式(UART)
- 模式1:同步模式(SIO)
区别:模式0有奇偶校验位和停止位,其停止位长可为1或2bit,有校验位时数据长度可为6~9bit,时钟由专用的波特率发生器产生; 模式1没有奇偶校验位和停止位,数据长度为5~8bit,时钟由专用波特率发生器或外部时钟提供。下面详细介绍这两种工作模式:
(1) 模式0:异步时钟模式(UART)
当需要在模式0下工作时,首先将串行模式控制寄存器(SCM10)的MD位置“0”,选择异步时钟模式。
- 确定波特率
由于模式0使用的时钟是由专用波特率发生器提供的,因此在在进行异步数据传输前,必须确定时钟的波特率,由如下公式确定:

具体计算公式如下表8-1所示:

-
接受数据:
UART在接受数据时,通过SMC10来选择串行数据的传输方向,奇偶校验位,停止位的长度,数据长度及时钟。接受使能信号(RXE)为1时,当检测到串行数据的开始位就可以接受数据,当接受完一帧数据后,数据被储存到RDR0,并将接受数据寄存器满(RDRF)置1;只要RDRF=1,如果接受中断使能位(RIE)也为1,就会产生一个接受中断,当检测完各错误标志位无误后,将数据从RDR0中读出,RDRF=0,这时就可以接受下一帧数据。 -
发送数据:
UART在发送数据时,也是通过SMC10来选择串行数据的传输方向,奇偶校验位,停止位的长度,数据长度及时钟。以下两种方法都可以启动数据的发送:
a:将发送使能位(TXE)置1,然后将发送的数据写入到串行输出数据寄存器中。
b:先将发送的数据写入到串行输出数据寄存器中,然后将发送使能位(TXE)置1。
如果发送数据寄存器空(TDRE)这一位为1,则将发送数据写入到串行输出数据寄存器(TDR0)中,当数据写完后,将TDRE清零。在将数据送入发送移位寄存器中,并将TDRE置为1,如果此时发送数据中断使能位(TIE)为1,则产生一个中断,通过中断处理,允许下一组数据写入到串行数据输出寄存器。
(2)模式1(SIO):
-
确定时钟
当需要在模式1工作时,将串行模式控制寄存器(SMC10)的MD位置1 以选择同步时钟模式。其数据位长度为5~8bit,没有校验位和停止位。串行时钟由SMC10的CKS来选择是采用专用波特率发生器还是外部时钟,若输入外部时钟,则设置SCKE位为0,若将专用波特率发生器的输出作为移位时钟信号,则将SCKE置1。
当使用外部时钟作为时钟信号时,其波特率由如下的公式计算:

注:外部时钟的一个周期大于8个机器周期。
当使用专用波特率发生器时,其波特率的值通过如下公式计算:

-
接受数据
接受数据取决于采用的是外部时钟还是内部时钟。如果采用的是外部时钟,当接受使能位RXE为1时,在外部时钟的上升沿接受数据。 如果采用的时内部时钟,串行时钟信号随发送数据一起输出,因此即使在只有接受时,也必须要发送数据,可以采用如下两种方式:
a:将发送使能位(TXE)置1,然后将发送数据写入到串行输出数据寄存器产生串行时钟信号并开始接受。
b:将发送数据写入到串行输出数据寄存器,然后将发送使能位(TXE)置1 产生串行时钟信号并开始接受。
当移位接受寄存器接受到5 ~8bit的串行数据时,接受到的数据送到串行数据输入寄存器(RDR0)中,然后就可以接受下一组数据。当串行输入数据寄存器储存有数据时,接受数据满(RDRF)标志位置1,如果接受中断使能位(RIE)为1 ,则产生一个接受中断。在检查完串行状态和数据寄存器中的错误标志位(OVE)后,如果没有错误,则将接受到的数据从串行输入数据寄存器中读出,当数据读出后,将接受数据寄存器满标志位(RDRF)清零。 -
发送数据
如下两种方式都可以启动数据的发送:
a:将发送使能位(TXE)置1,然后将发送数据写入到串行输出数据寄存器。
b:先将发送的数据写入到串行输出数据寄存器中,然后将发送使能位(TXE)置1 。
当检查完发送数据寄存器空(TDRE)位为1后,将发送数据写入到串行输出数据寄存器(TDR0)中,并将TDRE清零。当数据从TDR0中送入发送移位寄存器后,将发送数据寄存器空(TDRE)位置1, 如果此时发送数据中断使能位(TIE)为1,则产生一个中断,通过中断处理,允许下一组数据写入到串行数据输出寄存器,当发送使能位为1时,串行数据的发送可以一直继续下去。
8.2.3 UART/SIO 的应用设计范例
在应用UART/SIO时,必须要对相关的寄存器进行设置,如下所示:
-
选择工作模式
工作模式的选择主要是过串行模式控制寄存器(SMC10)的模式选择位MOD来实现的:

-
选择工作时钟的类型(SMC10.CKS)

- 输入输出选择(UCK0,UI0,UO0)

- 如何启用/停用UART(接受使能位SMC20.RXE及发送使能位SMC20.TXE)

- 设置校验位
校验位的设置是通过模式控制寄存器(SMC10)的校验位控制位(PEN)及校验位极性位(TDP)来设置的,如下表8-6所示:

- 选择数据长度
数据长度是由模式控制寄存器的数据长度选择位(SMC10.CBL[1:0])决定的。


- 选择停止位的长度(SMC10.SBL)

- 清除错误标志位(SMC20.RERC)

- 设置数据传输方向(SMC10.BDS)

- 与中断相关的寄存器
通过中断级别设置寄存器来设置中断级别

通过对以上寄存器进行设置后,就可以对不同的工作模式进行配置,使其按预期的要求工作,不同工作模式的应用如下所示:
(1) 异步时钟模式:
下面一段程序是通过UART发送一个欢迎字符串,并响应所有接受到的字符串。如下所示:
/*--------------------MAIN.C-----------------------------------*/
#include "mb95100.h"
void UART_init (void) //初始化所有的寄存器
{
PSSR0 = 0x05; BRSR0 = 130; // Baudrate Generator: 9600 Baud
SMC10 = 0x0C; /*00001100, 从最低位开始传(0),无校验位(0),偶校验(0),停止位1bit(0) ,字符长度8bit(11) ,专用波特率发生器(0),UART(0)*/
SMC20 = 0x58; /*01011000, 串行时钟不输出(0),UO pin 为输出(1),清除SSR的所有标志位(0)PER,OVE,FER,允许接受数据(1),允许发送数据(1),没有中断(000)*/
SSR0 = 0x00; // 清除标志位
}
void UART_sendbyte (char ch) //发送一个字节
{
while (!SSR0_TDRE); TDR0 = ch;
}
char UART_readbyte_wait (void) //接受字节
{
while(!SSR0_RDRF); return (RDR0); // 返回接受到的字节
}
void UART_sendstring (const char *string) //发送字符串
{
unsigned int i;
for (i=0; i<strlen(string); i++)
{
if (string[i] == 10)
UART_sendbyte(13);
UART_sendbyte(string[i]);
}
}
/***************************************************************/
/* Main Routine */
/***************************************************************/
void main(void)
{
UART_init();
UART_sendstring("\nWelcome to UART0 of MB95100series (8FX)");
UART_sendstring("\nAny key will be echoed now...\n");
while(1)
{
UART_sendbyte( UART_readbyte_wait() );
}
}
(2) 同步时钟模式
下面一段程序是通过SPI和UART与NM93CS46-EEPROM通信的,程序代码将64个数据-字写入到EEPROM并读出,然后将接受到的数据与原来的数据比较,如果发生错误了,在CONCERTO-Kit board板上的LED就会显示“E”,如果没有错误,则LED的小数点会点亮。端口连接如下图8-2所示:

#include "mb95100.h"
#define DATASIZE 64 // EEPROM以字为单位
unsigned int data[DATASIZE]; // 发送到EEPROM的数据 unsigned int readbuffer[DATASIZE]; // 从EEPROM接受到的数据
void InitUART(void)
{
BGR = 9; // 1M Bit/s @ 10 MHz
ESCR = 0x01; // SCES = 1 => CPOL = 1
ECCR = 0x10; // SCDE = 1 => CPHA = 1
SCR = 0x03; // 启用发送和接受
SMR = 0x83; // 模式2, 启用SCLK, SOT
SSR = 0x04; // 从最高位开始传输,没有中断
}
void InitPorts(void)
{
// Bit#2: CS, Bit#1: PE
PDR6 = 0x00; // All Low
DDR6 = 0x1C; // CS, PE, PRE to output
PDR0 = 0xFF; // 与LED连接
DDR0 = 0xFF;
}
void makedata(void)
{
unsigned char i;
for (i = 0; i < DATASIZE; i++)
{
data[i] = (i * 2);
}
}
void wait(unsigned int j) //产生延时
{
volatile unsigned int i;
for (i = 0; i < j; i++)
{
#pragma asm
NOP
NOP
NOP
NOP
#pragma endasm
}
}
void read_eeprom(unsigned char adr)
{
unsigned char din, command;
PDR6_P64 = 1; // CS = 1
TDR = 0x01;
command = (adr & 0x3F) | 0x80; // 地址和读指令
while (SSR_RDRF == 0); //发送完成?
din = RDR; // 冲掉接受数据寄存器
TDR = command;
while (SSR_RDRF == 0);
din = RDR;
SCR_CRE = 1; /* 清除可能的错误,复位接受状态机 */
ECCR = 0x00;
TDR = 0xFF;
while (SSR_RDRF == 0); // 发送完成?
din = RDR; // 最高有效位
readbuffer[adr] = (din << 8);
while (SSR_TDRE == 0);
TDR = 0xFF; //
while (SSR_RDRF == 0);
din = RDR; // 最低有效位
readbuffer[adr] |= din;
ECCR = 0x10; // SCDE = 1 => CPHA = 1
PDR6_P64 = 0; // CS = 0 }
void write_eeprom(unsigned char adr)
{
unsigned char dout, command;
PDR6_P64 = 1; // CS = 1
while (SSR_TDRE == 0);
TDR = 0x01;
command = (adr & 0x3F) | 0x40; //地址和写指令
dout = command;
while (SSR_TDRE == 0);
TDR = dout;
dout = (data[adr] >> 8) & 0xFF; //最高有效位
while (SSR_TDRE == 0);
TDR = dout;
dout = data[adr] & 0xFF; // 最低有效位
while (SSR_TDRE == 0);
TDR = dout;
while (ECCR & 0x01); // 等待开始发送数据
while (!(ECCR & 0x01)); // 等待发送完成
PDR6_P64 = 0; // CS = 0
wait(1);
PDR6_P64 = 1; // CS = 1
while(ESCR_SIOP == 1); // 等待EEPROM空闲
while(ESCR_SIOP == 0);
PDR6_P64 = 0; // CS = 0
}
void write_enable(void)
{
PDR6_P64 = 1; // CS = 1
while (SSR_TDRE == 0);
TDR = 0x01;
while (SSR_TDRE == 0);
TDR = 0x30;
while (ECCR & 0x01); //等待开始发送数据
while (!(ECCR & 0x01)); //等待发送完成
PDR6_P64 = 0; // CS = 0
}
void write_disable(void)
{ PDR6_P64 = 1; // CS = 1
while (SSR_TDRE == 0);
TDR = 0x01;
while (SSR_TDRE == 0);
TDR = 0x00;
while (ECCR & 0x01); //等待开始发送数据
while (!(ECCR & 0x01)); //等待发送完成
PDR6_P64 = 0; // CS = 0
}
/***********************Main Routine */*************************/
void main(void)
{
unsigned char i, rerror, dummy;
InitIrqLevels(); // 初始化中断级别寄存器和中断向量表 __EI(); // 开启全局中断 __set_il(3); // 设置全局中断的级别为最低级别3,允许其它中断
InitPorts(); // 初始化 PRE, PE和 CS
makedata(); // 产生数据
InitUART(); // 初始化UART
// 写数据
PDR6_P63 = 1; // PE = 1
write_enable();
for (i = 0; i < DATASIZE; i++)
{
write_eeprom(i);
}
write_disable();
PDR6_P63 = 0; // PE = 0
wait (10);
// 读回数据 dummy = RDR; SCR_CRE = 1; // 清除可能的溢出
for (i = 0; i < DATASIZE; i++)
{
read_eeprom(i);
}
// 检查读出的数据是否正确
rerror = 0;
for (i = 0; i < DATASIZE; i++)
{
if (readbuffer[i] != data[i]) rerror = 1;
}
// 如果有错误产生,则LED显示“E”,否则小数点亮
if (rerror)
{
while(1)
{
PDR0 = 0xFF;
wait (60000);
PDR0 = 0x86;
wait (60000);
}
}
else
{
PDR0 = 0x7F;
while(1);
}
}


