本文展示了STM32 USART串口的 识别操作
内容涉及 :
USART串口的识别
IO口输入输出
按键的外部中断处理
32位数据通讯,字符串通讯,单字符通讯
完整代码 GIT完整代码
文章目录
前言
STM32 的 USART 简介 通用同步异步收发器(Universal Synchronous Asynchronous Receiver and Transmitter)是一 个串行通信设备,可以灵活地与外部设备进行全双工数据交换。有别于 USART 还有一个 UART(Universal Asynchronous Receiver and Transmitter),它是在 USART 基础上裁剪掉了同 步通信功能,只有异步通信。简单区分同步和异步就是看通信时需不需要对外提供时钟输 出,我们平时用的串口通信基本都是 UART。: Git 代码
一、 编程要点
USART:1) 使能 RX 和 TX 引脚 GPIO 时钟和 USART 时钟;
2) 初始化 GPIO,并将 GPIO 复用到 USART 上;
3) 配置 USART 参数;
4) 配置中断控制器并使能 USART 接收中断;
5) 使能 USART;
6) 在 USART 接收中断服务函数实现数据接收和发送。
二、使用步骤
1.理解原理图
代码如下:
: STM32F103ZET6 串口引脚位PA9
: STM32F103ZET6 输出口为PB5低电平点有效
: STM32F103ZET6 Key检测脚为PA8
2.建立USART串口的 头文件 USART_book.h
代码如下(示例):
#ifndef __USART_BOOK_H_
#define __USART_BOOK_H_
#include "stm32f10x.h"
#include "stm32f10x_usart.h"
#include "stm32f10x_rcc.h"
//串口的宏定义 不同的串口挂在的总线和IO不一样
//串口1
#define _DEBUG_USARTx USART1
#define _DEBUG_USART_CLK RCC_APB2Periph_USART1
#define _DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
#define _DEBUG_USART_BAUDRATE 115200
// USART GPIO 引脚定义
#define _DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOA
#define _DEBUG_USART_GPIO_APBxCLKCmd RCC_APB2PeriphClockCmd
#define _DEBUG_USART_TX_GPIO_PORT GPIOA
#define _DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
#define _DEBUG_USART_TX_GPIO_MODE GPIO_Mode_AF_PP
#define _DEBUG_USART_RX_GPIO_PORT GPIOA
#define _DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
#define _DEBUG_USART_RX_GPIO_MODE GPIO_Mode_IN_FLOATING
#define _DEBUG_NVIC_USART_IRQ USART1_IRQn
#define _DRBUG_USART_IRQHandler USART1_IRQHandler
void fn_USART_IO_Config(void);
void fn_USART_Config(void);
void fn_Usart_Send_Byte(USART_TypeDef * pUSARTx , uint8_t ch );
void fn_Usart_SendString(USART_TypeDef *pUSARTx , char * str);
void Usart_SendHalf_32_Word( USART_TypeDef * pUSARTx, uint32_t ch);
void _DRBUG_USART_IRQHandler(void);
#endif
3.建立USART串口的 头文件 USART_book.c
代码如下(示例):
#include "USART_book.h"
/**************************************************************
* @brief
* void fn_LED_Corporate(GPIO_TypeDef* _GPIO_x , uint16_t _GPIO_Pin_x ,
* LED_Corporate_state_t _LED_Corporate_state_t );
* @param
* //串口1
* #define _DEBUG_NVIC_USART_IRQ USART1_IRQn
* #define _DRBUG_NVIC_USART_IRQHandler USART1_IRQHandler
* @retval
*************************************************************/
static void NVIC_Configuration(void){
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中断控制寄存器组选择*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置 USART 为中断源 */
NVIC_InitStructure.NVIC_IRQChannel = _DEBUG_NVIC_USART_IRQ;
/* 抢断优先级为 1 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子优先级为 1 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中断 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置 NVIC */
NVIC_Init(&NVIC_InitStructure);
}
/**************************************************************
* @brief
* void fn_LED_Corporate(GPIO_TypeDef* _GPIO_x , uint16_t _GPIO_Pin_x ,
* LED_Corporate_state_t _LED_Corporate_state_t );
* @param
* //串口1
* // USART GPIO 引脚定义
* #define _DEBUG_USART_GPIO_CLK RCC_APB2Periph_GPIOA
* #define _DEBUG_USART_GPIO_APBxCLKCmd RCC_APB2PeriphClockCmd
*
* #define _DEBUG_USART_TX_GPIO_PORT GPIOA
* #define _DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9
* #define _DEBUG_USART_TX_GPIO_MODE GPIO_Mode_AF_PP
* #define _DEBUG_USART_RX_GPIO_PORT GPIOA
* #define _DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10
* #define _DEBUG_USART_RX_GPIO_MODE GPIO_Mode_AF_FLOATING
* @retval
*************************************************************/
void fn_USART_IO_Config(void){
GPIO_InitTypeDef GPIO_InitStructure;
// 打开串口 GPIO 的时钟
_DEBUG_USART_GPIO_APBxCLKCmd(_DEBUG_USART_GPIO_CLK , ENABLE);
//将USART TX 的GPIO配置为推挽模式
GPIO_InitStructure.GPIO_Pin = _DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = _DEBUG_USART_TX_GPIO_MODE;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(_DEBUG_USART_TX_GPIO_PORT,&GPIO_InitStructure);
//将USART RX 的GPIO配置为浮空输入
GPIO_InitStructure.GPIO_Pin = _DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = _DEBUG_USART_RX_GPIO_MODE;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(_DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStructure);
}
/**************************************************************
* @brief
* void fn_LED_Corporate(GPIO_TypeDef* _GPIO_x , uint16_t _GPIO_Pin_x ,
* LED_Corporate_state_t _LED_Corporate_state_t );
* @param
* //串口1
* #define _DEBUG_USARTx USART1
* #define _DEBUG_USART_CLK RCC_APB2Periph_USART1
* #define _DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
* #define _DEBUG_USART_BAUDRATE 115200
* @retval
*************************************************************/
void fn_USART_Config(void){
USART_InitTypeDef USART_InitStructure;
// 打开串口外设的时钟
_DEBUG_USART_APBxClkCmd(_DEBUG_USART_CLK , ENABLE);
//配置串口的工作参数
USART_InitStructure.USART_BaudRate = _DEBUG_USART_BAUDRATE;
//配置波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置 针数据字长
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置停止位
USART_InitStructure.USART_Parity = USART_Parity_No;
// 配置校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
// 配置硬件流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx ;
// 配置工作模式,收发一起
USART_Init(_DEBUG_USARTx , &USART_InitStructure);// 完成串口的初始化配置
NVIC_Configuration();// 串口中断优先级配置
USART_ITConfig(_DEBUG_USARTx , USART_IT_RXNE , ENABLE);// 使能串口接收中断
USART_Cmd(_DEBUG_USARTx , ENABLE);// 使能串口
}
/**************************************************************
* @brief
* void fn_Usart_Send_Byte(USART_TypeDef * pUSARTx , uint8_t ch );
* @param
* //串口1
* #define _DEBUG_USARTx USART1
* #define _DEBUG_USART_CLK RCC_APB2Periph_USART1
* #define _DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
* #define _DEBUG_USART_BAUDRATE 115200
* @retval
*************************************************************/
void fn_Usart_Send_Byte(USART_TypeDef * pUSARTx , uint8_t ch ){
/*发送一个字节数据到USART*/
USART_SendData(pUSARTx , ch);
/*等待发送数据寄存器为空*/
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
}
/**************************************************************
* @brief
* void fn_Usart_SendString(USART_TypeDef *pUSARTx , char * str);
* @param
* //串口1
* #define _DEBUG_USARTx USART1
* #define _DEBUG_USART_CLK RCC_APB2Periph_USART1
* #define _DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd
* #define _DEBUG_USART_BAUDRATE 115200
* @retval
*************************************************************/
void fn_Usart_SendString(USART_TypeDef *pUSARTx , char * str){
unsigned int k = 0;
do{
fn_Usart_Send_Byte(pUSARTx,*(str + k++));
}while(*(str + k)!='\0');
/*等待发送完成*/
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC));
}
/**************************************************************
* @brief
* void Usart_SendHalf_32_Word( USART_TypeDef * pUSARTx, uint32_t ch);
* @param
* @retval
*************************************************************/
void Usart_SendHalf_32_Word( USART_TypeDef * pUSARTx, uint32_t ch){
uint32_t temp_Half32;
uint8_t temp_Half=0,i_Half=4;
temp_Half32 =ch;
while(i_Half-->0){
temp_Half=(temp_Half32 & 0xFF000000)>>24;
temp_Half32<<=8;
fn_Usart_Send_Byte(pUSARTx,temp_Half);
}
/*等待发送完成*/
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC));
}
/**************************************************************
* @brief
* void USART1_IRQHandler(void);
* @param
* @retval
*************************************************************/
void _DRBUG_USART_IRQHandler(void){
uint8_t ucTemp = 0;
if(USART_GetITStatus(_DEBUG_USARTx,USART_IT_RXNE)!=RESET){
ucTemp = USART_ReceiveData(_DEBUG_USARTx);
USART_SendData(_DEBUG_USARTx ,ucTemp );
}
}
4.利用之前的LED输出的 头文件 Led_book.h
代码如下(示例):
#ifndef __LED_BOOK_H_
#define __LED_BOOK_H_
#include "stm32f10x.h"
#define LED_OUT_GPIO_Port GPIOB //GPIO Point
#define LED_OUT_GPIO_Clock RCC_APB2Periph_GPIOB //GPIO clock
#define LED_OUT_GPIO_Pin GPIO_Pin_5
#define LED_OUT_GPIO_Pin_Bit 5
#define LED_OUT_GPIO_Modle GPIO_Mode_Out_PP
typedef enum {
LED_Corporate_On = 1,
LED_Corporate_OFF = 2,
LED_Corporate_Toggle = 3,
} LED_Corporate_state_t;
void fn_LED_GPIO_Config(GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock ,\
uint16_t _GPIO_Pin_x , GPIOMode_TypeDef _GPIOMode_TypeDef);
void fn_Led_Init(void);
void fn_LED_Corporate(GPIO_TypeDef* _GPIO_x , uint16_t _GPIO_Pin_x , \
LED_Corporate_state_t _LED_Corporate_state_t );
#define __LED_Change__ fn_LED_Corporate(LED_OUT_GPIO_Port,LED_OUT_GPIO_Pin,LED_Corporate_Toggle)
#endif
5.利用之前的LED输出的 头文件 Led_book.c
代码如下(示例):
#include "Led_book.h"
/**************************************************************
* @brief
* void fn_LED_GPIO_Config(GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock ,
* uint16_t _GPIO_Pin_x , GPIOMode_TypeDef _GPIOMode_TypeDef);
* @param
* @retval
*************************************************************/
#define LED_GPIO_Speed GPIO_Speed_10MHz
void fn_LED_GPIO_Config(GPIO_TypeDef* _GPIO_x , uint32_t _GPIO_Clock ,uint16_t _GPIO_Pin_x , GPIOMode_TypeDef _GPIOMode_TypeDef){
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = _GPIOMode_TypeDef;
GPIO_InitStruct.GPIO_Pin = _GPIO_Pin_x;
GPIO_InitStruct.GPIO_Speed = LED_GPIO_Speed;
RCC_APB2PeriphClockCmd(_GPIO_Clock ,ENABLE);
GPIO_Init(_GPIO_x , &GPIO_InitStruct) ;
GPIO_SetBits(_GPIO_x,_GPIO_Pin_x);
}
/**************************************************************
* @brief
* void fn_Led_Init(void);
* @param
* @retval
*************************************************************/
void fn_Led_Init(void){
fn_LED_GPIO_Config (LED_OUT_GPIO_Port,LED_OUT_GPIO_Clock,LED_OUT_GPIO_Pin,LED_OUT_GPIO_Modle);
}
/**************************************************************
* @brief
* void fn_LED_Corporate(GPIO_TypeDef* _GPIO_x , uint16_t _GPIO_Pin_x ,
* LED_Corporate_state_t _LED_Corporate_state_t );
* @param
* @retval
*************************************************************/
void fn_LED_Corporate(GPIO_TypeDef* _GPIO_x , uint16_t _GPIO_Pin_x , LED_Corporate_state_t _LED_Corporate_state_t ){
switch(_LED_Corporate_state_t){
case LED_Corporate_On :
GPIO_SetBits(_GPIO_x,_GPIO_Pin_x);
break;
case LED_Corporate_OFF:
GPIO_ResetBits(_GPIO_x,_GPIO_Pin_x);
break;
case LED_Corporate_Toggle:
GPIO_ReadOutputDataBit(_GPIO_x,_GPIO_Pin_x)?GPIO_ResetBits(_GPIO_x,_GPIO_Pin_x):GPIO_SetBits(_GPIO_x,_GPIO_Pin_x);
break;
}
}
//practice
//fn_LED_GPIO_Config (LED_OUT_GPIO_Port,LED_OUT_GPIO_Clock,LED_OUT_GPIO_Pin,LED_OUT_GPIO_Modle);
// while(1){
// delay(10000);
// fn_LED_Corporate(LED_OUT_GPIO_Port,LED_OUT_GPIO_Pin,LED_Corporate_Toggle);
// }
6.建立USART 输出的 主程序 main.c
代码如下(示例):
/**
******************************************************************************
* @file GPIO/JTAG_Remap/main.c
* @author MCD Application Team
* @version V3.5.0
* @date 08-April-2011
* @brief Main program body
******************************************************************************
* @attention
*
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "PROJ_book.h"
/* Private functions ---------------------------------------------------------*/
/**
* @brief Main program.
* @param None
* @retval None
*/
void delay(int x);
void fn_LED_Flash_Init(void);
void fn_usart_show_Init(void);
int main(void)
{
fn_Led_Init();
fn_LED_Flash_Init();
fn_usart_show_Init();
fn_EXTI_GPIO_Config();
while(1){
delay(10000);
fn_Usart_SendString(_DEBUG_USARTx," : 你瞅啥 \n");
}
}
void fn_LED_Flash_Init(void){
uint16_t count_Init = 2;
while(count_Init-->0){
__LED_Change__;
fn_Systick_Delay(500,_Systick_ms);
__LED_Change__;
fn_Systick_Delay(100,_Systick_ms);
__LED_Change__;
fn_Systick_Delay(100,_Systick_ms);
__LED_Change__;
fn_Systick_Delay(500,_Systick_ms);
}
}
void fn_usart_show_Init(void){
fn_USART_IO_Config();
fn_USART_Config();
fn_Usart_Send_Byte(_DEBUG_USARTx,'T');
fn_Usart_Send_Byte(_DEBUG_USARTx,'O');
Usart_SendHalf_32_Word(_DEBUG_USARTx,0xA69B4B7C);
fn_Usart_SendString(_DEBUG_USARTx," : 你瞅啥 \n");
}
void delay(int x){
int y = 0xFFFFF;
while((x--)>0){
while((y--)>0){
__NOP();
__NOP();
__NOP();
__NOP();
__NOP();
}
}
}
/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/
www.firebbs.cn。
参考笔记。
_STM32f103 中断 以及 EXT
总结
USART 初始化结构体
1 typedef struct {
2 uint32_t USART_BaudRate; // 波特率
3 uint16_t USART_WordLength; // 字长
4 uint16_t USART_StopBits; // 停止位
5 uint16_t USART_Parity; // 校验位
6 uint16_t USART_Mode; // USART 模式
7 uint16_t USART_HardwareFlowControl; // 硬件流控制
8 } USART_InitTypeDef;
1) USART_BaudRate:波特率设置。一般设置为 2400、9600、19200、115200。标准
库函数会根据设定值计算得到 USARTDIV 值,从而设置 USART_BRR 寄存器值。
2) USART_WordLength:数据帧字长,可选 8 位或 9 位。它设定 USART_CR1 寄存
器的 M 位的值。如果没有使能奇偶校验控制,一般使用 8 数据位;如果使能了奇
偶校验则一般设置为 9 数据位。
3) USART_StopBits:停止位设置,可选 0.5 个、1 个、1.5 个和 2 个停止位,它设定
USART_CR2 寄存器的 STOP[1:0]位的值,一般我们选择 1 个停止位。
4) USART_Parity : 奇 偶 校 验 控 制 选 择 , 可 选 USART_Parity_No( 无校验 ) 、
USART_Parity_Even( 偶校验 ) 以 及 USART_Parity_Odd( 奇 校 验 ) , 它 设 定
USART_CR1 寄存器的 PCE 位和 PS 位的值。
5) USART_Mode:USART 模式选择,有 USART_Mode_Rx 和 USART_Mode_Tx,
允许使用逻辑或运算选择两个,它设定 USART_CR1 寄存器的 RE 位和 TE 位。
6) USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效,
可选有⑴使能 RTS、⑵使能 CTS、⑶同时使能 RTS 和 CTS、⑷不使能硬件流。
当使用同步模式时需要配置 SCLK 引脚输出脉冲的属性,标准库使用一个时钟初始化
结构体 USART_ClockInitTypeDef 来设置,该结构体内容也只有在同步模式才需要设置。
USART 时钟初始化结构体
1 typedef struct {
2 uint16_t USART_Clock; // 时钟使能控制
3 uint16_t USART_CPOL; // 时钟极性
4 uint16_t USART_CPHA; // 时钟相位
5 uint16_t USART_LastBit; // 最尾位时钟脉冲
6 } USART_ClockInitTypeDef;
1) USART_Clock:同步模式下 SCLK 引脚上时钟输出使能控制,可选禁止时钟输出
(USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模
式发送,一般都需要开启时钟。它设定 USART_CR2 寄存器的 CLKEN 位的值。
2) USART_CPOL:同步模式下 SCLK 引脚上输出时钟极性设置,可设置在空闲时
SCLK 引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设
定 USART_CR2 寄存器的 CPOL 位的值。
3) USART_CPHA:同步模式下 SCLK 引脚上输出时钟相位设置,可设置在时钟第一
个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设
定 USART_CR2 寄存器的 CPHA 位的值。USART_CPHA 与 USART_CPOL 配合
使用可以获得多种模式时钟关系。
4) USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK 引脚
输 出 , 可 以 是 不 输 出 脉 冲 (USART_LastBit_Disable) 、 输 出 脉 冲
(USART_LastBit_Enable)。它设定 USART_CR2 寄存器的 LBCL 位的值。
21.5 USART1 接发通信实验
USART 只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留
USART 接口来实现与其他模块或者控制器进行数据传输,比如 GSM 模块,WIFI 模块、蓝
牙模块等等。在硬件设计时,注意还需要一根“共地线”。
我们经常使用 USART 来实现控制器与电脑之间的数据传输。这使得我们调试程序非
常方便,比如我们可以把一些变量的值、函数的返回值、寄存器标志位等等通过 USART
发送到串口调试助手,这样我们可以非常清楚程序的运行状态,当我们正式发布程序时再
把这些调试信息去除即可。
我们不仅仅可以将数据发送到串口调试助手,我们还可以在串口调试助手发送数据给
控制器,控制器程序根据接收到的数据进行下一步工作。
首先,我们来编写一个程序实现开发板与电脑通信,在开发板上电时通过 USART 发
送一串字符串给电脑,然后开发板进入中断接收等待状态,如果电脑有发送数据过来,开
发板就会产生中断,我们在中断服务函数接收数据,并马上把数据返回发送给电脑。
21.5.1 硬件设计
为利用 USART 实现开发板与电脑通信,需要用到一个 USB 转 USART 的 IC,我们选
择 CH340G 芯片来实现这个功能,CH340G 是一个 USB 总线的转接芯片,实现 USB 转
USART、USB 转 lrDA 红外或者 USB 转打印机接口,我们使用其 USB 转 USART 功能。具
体电路设计见图 21-9。
我们将 CH340G 的 TXD 引脚与 USART1 的 RX 引脚连接,CH340G 的 RXD 引脚与
USART1 的 TX 引脚连接。CH340G 芯片集成在开发板上,其地线(GND)已与控制器的
GND 连通。
使用 GPIO_InitTypeDef 和 USART_InitTypeDef 结构体定义一个 GPIO 初始化变量以及
一个 USART 初始化变量,这两个结构体内容我们之前已经有详细讲解。
调用 RCC_APB2PeriphClockCmd 函数开启 GPIO 端口时钟,使用 GPIO 之前必须开启
对应端口的时钟。使用 RCC_APB2PeriphClockCmd 函数开启 USART 时钟。
使用 GPIO 之前都需要初始化配置它,并且还要添加特殊设置,因为我们使用它作为
外设的引脚,一般都有特殊功能。我们在初始化时需要把它的模式设置为复用功能。这里
把串口的 Tx 引脚配置为复用推挽输出,Rx 引脚为浮空输入,数据完全由外部输入决定。
接下来,我们配置 USART1 通信参数为:波特率 115200,字长为 8,1 个停止位,没
有校验位,不使用硬件流控制,收发一体工作模式,然后调用 USART 初始化函数完成配
置。
程序用到 USART 接收中断,需要配置 NVIC,这里调用 NVIC_Configuration 函数完成
配置。配置完 NVIC 之后调用 USART_ITConfig 函数使能 USART 接收中断。
Usart_SendByte 函数用来在指定 USART 发送一个 ASCLL 码值字符,它有两个形参,
第一个为 USART,第二个为待发送的字符。它是通过调用库函数 USART_SendData 来实
现的,并且增加了等待发送完成功能。通过使用 USART_GetFlagStatus 函数来获取 USART
事件标志来实现发送完成功能等待,它接收两个参数,一个是 USART,一个是事件标志。
这里我们循环检测发送数据寄存器为空这个标志,当跳出 while 循环时说明发送数据寄存
器为空这个事实。
Usart_SendString 函数用来发送一个字符串,它实际是调用 Usart_SendByte 函数发送每
个字符,直到遇到空字符才停止发送。最后使用循环检测发送完成的事件标志 TC 来实现
保证数据发送完成后才退出函数。