# 2.串口介绍


# 串口操作流程

1. 配置串口工作方式
2. 配置用于串口的定时器T1为工作方式2
3. 设置波特率
4. 使能总中断、串口中断
5. 设置接收中断函数
6. 设置stdio库

# 串口寄存器

与串口有关的寄存器有以下几个：

- SUBF：数据缓冲器
- SCON：串行口控制寄存器
- PCON寄存器

## SUBF：数据缓冲器

有两个，用于接收/发送数据。
需要注意的是，该两个缓冲器地址一致，可以当作普通寄存器看待。

## SCON寄存器

串行口控制寄存器
用于设置串行口的工作方式、监视串行口的工作状态、控制发送与接收的状态等。

| 位序 | D7  | D6  | D5  | D4  | D3  | D2  | D1 | D0 |
| ---- | --- | --- | --- | --- | --- | --- | -- | -- |
| 位名 | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |

### SM0和SM1位

用于选择串行工作方式

| SM0 SM1 | 工作方式 | 功能                        | 波特率                       |
| ------- | -------- | --------------------------- | ---------------------------- |
| 00      | 0        | 8位同步移位寄存器（输出）   | 波特率固定为fosc/12          |
| 01      | 1        | 10位异步收发方式（8位数据） | 波特率可变                   |
| 10      | 2        | 11位异步收发（9位数据）     | 波特率固定为fosc/64或fosc/32 |
| 11      | 3        | 11位异步收发方式（9位数据） | 波特率可变                   |

### SM2位

多机通信控制位，主要用于方式2和方式3。

- 在工作方式0中，SM2必须设成0。
- 在工作方式1中，当处于接收状态时，若SM2=1，则只有接收到的停止位“1”时，RI才能被激活成“1”（产生中断请求）。
- 在工作方式2和3中，当SM2=0时，串口以单机发送或接收方式工作，TI和RI以正常方式被激活并产生中断请求；
- 在工作方式2和3中，当SM2=1时，若RB8=1，则RI被激活并产生中断请求；若RB8=0，不激活RI，也不引起接收中断请求。

### REN位

使能串行接收位。

- 为1时，启动串行口接收数据。
- 为0时，禁止接收。

### TB8位

- 在方式2或方式3中，是发送数据的第九位。可以用作数据的奇偶校验位，或在多机通信中，作为地址帧/数据帧的标志位。
- 在方式0和方式1中，该位未使用。

### RB8位

- 在方式2或方式3中，是接收到数据的第九位。作为奇偶校验位或地址帧/数据帧的标志位。
- 在方式1时，若SM2=0，则RB8是接收到的停止位。

### TI位

发送中断标志位

- 在方式0中，当串行发送第8位数据结束时，硬件自动将改位置1，并向CPU发送中断请求。
- 在其它方式中，串行发送停止位的开始时，硬件自动将改位置1，并向CPU发送中断请求。
- 必须用软件方式清除该位。

### RI位

接收中断标志位

- 在方式0中，当串行接收第8位数据结束时，硬件自动将改位置1，并向CPU发送中断请求。
- 在其它方式中，串行接收停止位的中间时，硬件自动将改位置1，并向CPU发送中断请求。
- 必须用软件方式清除该位。

> 注意
> TI和RI位在任何工作方式下都必须由软件清零。这点和外部中断及定时中断的标志位的清除都不同。

## PCON寄存器

| 位序 | D7   | D6 | D5 | D4 | D3  | D2  | D1 | D0  |
| ---- | ---- | -- | -- | -- | --- | --- | -- | --- |
| 位名 | SMOD | -  | -  | -  | GF1 | GF0 | PD | IDL |

### SMOD位

波特率加倍位

- 只能工作于方式1~3
- 为1的时候，串口波特率增加1倍。
- 为0的时候，串口波特率不加倍。

## 波特率计算

使用本站的 [51 波特率计算器](/tools/51%E6%B3%A2%E7%89%B9%E7%8E%87%E8%AE%A1%E7%AE%97%E5%99%A8/)，计算 Timer 1 重装值、实际波特率与误差，支持 SMOD 和时钟分频选择。

### 工作方式0

$$
波特率=\frac{fosc}{12}
$$

### 工作方式1

$$
波特率=\frac {2^{SMOD}\times {f_{osc}}}{32 \times 12 \times(定时器最大值-定时器初值)}
$$

### 工作方式2

$$
波特率=\frac{2^{SMOD} \times f_{osc}}{64}
$$

### 工作方式3

$$
波特率=\frac {2^{SMOD}\times {f_{osc}}}{32 \times 12 \times(定时器最大值-定时器初值)}
$$

# 使用stdio库

C51单片机可以调用C语言的stdio库，只需要重新实现 `putchar`函数和 `_getkey`函数：

```c
void putchar(char buf)
{
	SBUF = buf;
	while(TI==0);
	TI = 0;
}

char _getkey (void)
{
	while(RI == 0);
	RI = 0;
	return SBUF;
}
```

将上述代码复制到主代码中，然后导入 `stdio.h`，右键打开该头文件，将以下两行代码注释即可：

```c
extern char putchar (char);

extern char _getkey (void);
```

# 使用SailLib库

SailLib库提供了串口相关的函数：

| 函数名              | 描述               |
| ------------------- | ------------------ |
| Serial_init         | 串口初始化函数     |
| Serial_set_callback | 设置读串口回调函数 |

## Serial_init：串口初始化函数

参数：

- 是否启用读串口中断。设置为1时，当串口接收到数据的时候会产生一个中断，这个中断会调用一个回调函数，该回调函数需要通过 `Serial_set_callback`函数设置。

## Serial_set_callback：设置读串口回调函数

参数：

- 回调函数。需要传入一个无返回值、参数为char的函数，用于串口接收到数据产生中断时调用。

## 库代码

声明：

```C
static void (*_cb)(char buf);
void Serial_init(uint8_t ES_en);
void Serial_set_callback(void (*cb)(char buf));
```

定义：

```C
void Serial_init(uint8_t ES_en)
{
	//设置串口为工作方式1
	SCON = 0x50;

	//设置波特率
	// T1工作于方式2(8位自动重载)
	TMOD |= 0x20;
	//波特率不倍增
	PCON &= 0x7f; //关闭最高位

	//设置波特率9600
	TL1 = 0xfd;
	TH1 = 0xfd;
	//使能定时器1
	TR1 = 1;

	EA = 1; //打开全局中断
	ES = ES_en; //打开串口中断(如果不使用中断，将使用阻塞函数)
}
void Serial_set_callback(void (*cb)(char buf))
{
	_cb = cb;
}
void Serial_receive() interrupt 4
{
	if (RI == 0) //如果没有接收到串口中断标志
		return;

	ES = 0;		  //关闭串口中断
	RI = 0;		  //复位串口中断标志
	(*_cb)(SBUF); //调用回调函数，返回串口的数据
	ES = 1;		  //重启串口中断
}
```

