STM32_SPI总线驱动OLED详细原理讲解

目录

这里写目录标题

  • 第13章 Cortex-M4-SPI总线
    • 13.1 SPI总线概述
      • 13.1.1 SPI总线介绍
      • 13.1.2 SPI总线接口与物理拓扑结构
      • 13.1.3 SPI总线通信原理
      • 13.1.4 SPI总线数据格式
    • 13.2 IO口模拟SPI操作OLED
      • 13.2.1 常见的显示设备
      • 13.2.2 OLED显示屏概述
      • 13.2.3 OLED特征
      • 13.2.4 显示原理
      • 13.2.5 管脚介绍
      • 13.2.6 OLED驱动
        • 13.2.6.1 驱动时序
      • 13.2.7 操作指令
        • 13.2.7.1 设置列地址(Y坐标)
        • 13.2.7.2 页地址
      • 13.2.8 OLED初始化
    • 13.3 OLED显示方式说明
    • 13.4 显示代码的编程流程
    • 13.5 显示文字
      • 13.5.1 程序设计流程
      • 13.5.2 取模方法
    • 13.6 显示图片
      • 13.6.1 程序设计流程
      • 13.6.2 取模方法
    • 13.7 STM32的SPI控制器操作OLED
      • 13.7.1 STM32的SPI总线介绍
        • 13.7.1.1 SPI控制器特征
      • 13.7.2 STM32的SPI控制器框架(重点)
      • 13.7.3 STM32的SPI相关寄存器
      • 13.7.4 SPI控制器使用
      • 13.7.3 STM32的SPI相关寄存器
      • 13.7.4 SPI控制器使用

第13章 Cortex-M4-SPI总线

13.1 SPI总线概述

13.1.1 SPI总线介绍

SPI(Serial Peripheral interface):是由Motorola公司开发的串行外围设备接口,是一种高速的,全双工,同步的通信总线。主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器等器件。

UART:异步串行全双工

SPI:同步串行全双工

13.1.2 SPI总线接口与物理拓扑结构

(1)五线制接口(4线SPI)—4-wire-spi

MOSI(单向数据线)、MISO(单向数据线)、CLK(时钟线)、NSS/CS(片选)

img

(2)四线制接口(3线SPI)

SDA(双向数据线)、CLK(时钟线)、NSS/CS(片选)

img

(3) 拓扑图

img

在SPI总线上,有主机(MCU)和从机(外围器件)之分,主机只有一个,从机可以有多个。

主机通过从机的片选信号线来选中从机与其进行通信。同一时间只能选择其中一个从机。随着SPI总线上挂接的从机增多,主机的片选IO也响应会增多。

M:master O:output S:slaver I:Input

MOSI(单向数据线):主出从入,主机通过这跟数据线发送数据给从机。

MISO(单向数据线):主入从出,从机通过这跟数据线发送数据给主机。

SCK(单向):时钟线,控制数据线什么时候才能传输数据。只有主机才能控制时钟线。

只有主机才能主动寻求从机与其通信,从机永远不可能主动跟主机通信。

CS:片选信号线,主机通过片选信号线来选中从机与其通信。

13.1.3 SPI总线通信原理

主机片选从机

主机通过时钟线决定什么时候发送数据给从机

主机通过时钟线决定什么时候接受从机的数据

主机取消片选

补充:

img

img

平行线:数据不允许发生改变,必须稳定发送

交叉线:数据允许发生改变,但不一定要变

13.1.4 SPI总线数据格式

SPI数据格式有4种:MODE0~3

决定数据格式的因素:时钟线哪一种跳边沿发送数据,前沿还是后沿接受数据,总线的空闲电平状态

img

img

CPHA:时钟相位。当CPHA=0,在第一个跳变沿(前沿)采集数据;当CPHA=1,在第二个跳边沿(后沿)采集数据。CPHA决定是前沿还是后沿采集数据和输出数据。

CPOL:时钟极性。当CPOL=0,总线空闲电平为低电平;当CPOL=1,总线的空闲电平为高电平。间接决定了哪一种跳边沿采集数据和发送数据。

img

如果一个设备支持MODE0,同时也会支持MODE3 (CPHA为0)

如果一个设备支持MODE1,同时也会支持MODE2

MODE0:

img

下降沿发送数据,上升沿采集数据

当SCK产生下降沿时,主机在MOSI上发送数据,同时从机在MISO上发送数据

等数据稳定在数据线上

当SCK产生上升沿时,主机在MISO上采集数据,同时从机在MOSI上采集数据

主机发送一位数据给从机:

SCK=0;//主机准备数据

MOSI=0/1;

SCK=1;//从机采集数据

主机接收一位数据给从机:

SCK=0;//从机准备数据

SCK=1;//主机采集数据.

读取MISO

主机和从机通信起始就是数据交换:

img

主机发送一个字节数据给从机//void SPI_Send_Byte(uint8_t data) //data = 1100 0000//{// uint8_t i;// for(i=0;i<8;i++)// {// SPI_SCK_L;//主机准备数据// if(data&0x80) // 1100 0000 & 1000 0000 // ((A=0)&&(B=1))// SPI_MOSI_H;// else// SPI_MOSI_L; // // data<<=1;//让次高位变成最高位 // data=data<<1;// // SPI_SCK_H;//从机采集数据// }//} 主机读取一个字节数据//uint8_t SPI_Revice_Byte(void)//{// uint8_t i;// uint8_t data=0; // for(i=0;i<8;i++)// {// SPI_SCK_L;//从机准备数据 // SPI_SCK_H;//主机采集数据// data <<=1;//空出最低位保存读取的数据// if(SPI_MISO)// data |=1;// }// // return data;//} //函数功能:SPI主机从机传输数据//参数说明:主机待发送的数据//返回值:主机接到到的数据uint8_t SPI_Exchange_Byte(uint8_t data){ uint8_t i; for(i=0;i<8;i++) { SPI_SCK_L;//主机准备数据/从机准备数据 if(data&0x80)SPI_MOSI_H; else SPI_MOSI_L; data<<=1;//让次高位变成最高位/空出最低位保存读取的数据 SPI_SCK_H;//从机采集数据/主机采集数据 if(SPI_MISO) data |=1; } return data;}只写:SPI_Exchange_Byte(data);只读:data=SPI_Exchange_Byte(0xFF);//发什么不重要

13.2 IO口模拟SPI操作OLED

OLED_CS(CS)—PB7----MCU发出----普通功能推挽输出

OLED_SCLK(SCK)—PB13–MCU发出----普通功能推挽输出

OLED_DIN(MOSI)—PB15—MCU发出----普通功能推挽输出

MISO–这里不需要

13.2.1 常见的显示设备

LED、数码管、点阵、LCD屏、OLED屏(消费电子)

13.2.2 OLED显示屏概述

OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。因为具备轻薄、省电等特性,因此从2003 年开始,这种显示设备在 MP3 播放器上得到了广泛应用,而对于同属数码类产品的 DC与手机,此前只是在一些展会上展示过采用 OLED 屏幕的工程样品。自 2007 年后,寿命得到很大提高,具备了许多 LCD 不可比拟的优势。

补充:

像素点:构成是一幅完整画面的最小单元

分辨率:一幅完整画面横向像素点的个数乘以纵向像素点的个数

帧:一幅完整画面就为一帧。

色深:表示一个像素点颜色数据的位数。16bpp,24bpp,32bpp

13.2.3 OLED特征

分辨率:128*64

尺寸:1.3寸

13.2.4 显示原理

主要目的:让OLED显示东西

显示内容从哪里来?MCU

MCU如何发显示数据给OLED?

\1. 相关管脚接线

\2. 遵循一定的通信协议—SPI

一般要让显示设备显示出内容,都需要显示屏的驱动芯片。

在STM32上,一般都是没有集成显示屏驱动芯片,那么显示模块本身就要具备自己的驱动芯片

img

13.2.5 管脚介绍

img

通信模式选择

img

img

img

OLED_CS:片选管脚,低电平有效

OLED_RES:复位管脚,低电平有效

OLED_D/C:数据命令选择管脚。当OLED_D/C=0,输入的数据是作为命令;当OLED_D/C=1,输入的数 据是作为显示数据

OLED_SDIN:串行数据输入管脚

OLED_SCLK:串行时钟线

OLED_CS、OLED_RES、OLED_D/C是控制管脚

OLED_SDIN、OLED_SCLK是数据管脚

13.2.6 OLED驱动

13.2.6.1 驱动时序

img

跟SPI的MODE0/3一样

13.2.7 操作指令

13.2.7.1 设置列地址(Y坐标)

img

设定列地址为column(A7~A0)

高4位列地址命令:0001A7A6A5A4—00010000| A7A6A5A4—0x10 | (column&0xf0)>>4

低4位列地址命令:0000A3A2A1A0—00000000|A3A2A1A0—0x00 | (column&0x0f)

OLED_DC=0;//发送命令

SPI_Exchange_Byte(0x10 | (column&0xf0)>>4);//发送高4位列地址(A7~A4)

SPI_Exchange_Byte(0x00 | (column&0x0f) );//发送低4位列地址(A3~A0)

13.2.7.2 页地址

OLED屏一共有64行,将这64行平均分成8份,每一份有8行,每一份就为一页

设定页地址是设定在本页的首行开始显示,而不能设定在本页的任意行开始显示

img

设定页地址为page(A3~A0)

页地址命令:1011A3A2A1A0—10110000 | A3A2A1A0—0xB0|page

OLED_DC=0;//发送命令

SPI_Exchange_Byte(0xB0|page);//设定页地址

13.2.8 OLED初始化

在前面知道OLED的管脚功能,也知道了MCU跟OLED是采用SPI通信。就算对OLED所有的管脚进行了初始化,并且完成SPI协议,但是仍然不能让OLED正常工作起来。我们还需要对OLED进行真正的初始化,就是对显示控制器(驱动芯片)进行初始化工作。

这个初始化官方已经提供了初始化序列给我们,直接调用就行 。

OELD_RES=1; delay_ms(100); OELD_RES=0; delay_ms(100); OELD_RES=1; delay_ms(100); Oled_Write_Cmd(0xAE); //关闭显示 Oled_Write_Cmd(0xD5); //设置时钟分频因子,震荡频率 Oled_Write_Cmd(80); //[3:0],分频因子;[7:4],震荡频率 Oled_Write_Cmd(0xA8); //设置驱动路数 Oled_Write_Cmd(0X3F); //默认0X3F(1/64) Oled_Write_Cmd(0xD3); //设置显示偏移 Oled_Write_Cmd(0X00); //默认为0 Oled_Write_Cmd(0x40); //设置显示开始行 [5:0],行数. Oled_Write_Cmd(0x8D); //电荷泵设置 Oled_Write_Cmd(0x14); //bit2,开启/关闭 Oled_Write_Cmd(0x20); //设置内存地址模式 Oled_Write_Cmd(0x02); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; Oled_Write_Cmd(0xA1); //段重定义设置,bit0:0,0->0;1,0->127; Oled_Write_Cmd(0xC8); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 Oled_Write_Cmd(0xDA); //设置COM硬件引脚配置 Oled_Write_Cmd(0x12); //[5:4]配置 Oled_Write_Cmd(0x81); //对比度设置 Oled_Write_Cmd(0xEF); //1~255;默认0X7F (亮度设置,越大越亮) Oled_Write_Cmd(0xD9); //设置预充电周期 Oled_Write_Cmd(0xf1); //[3:0],PHASE 1;[7:4],PHASE 2; Oled_Write_Cmd(0xDB); //设置VCOMH 电压倍率 Oled_Write_Cmd(0x30); //[6:4] 000,0.65vcc;001,0.77vcc;011,0.83*vcc; Oled_Write_Cmd(0xA4); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) Oled_Write_Cmd(0xA6); //设置显示方式;bit0:1,反相显示;0,正常显示 Oled_Write_Cmd(0xAF); //开启显示

当我调用OLED初始化函数后,OLED屏就有东西显示出来。但是我在本代码里并没有发送任何显示数据。这是因为我OLED显示控制器的显存里面已经存在着显示数据,我初始化成功了就能从显存把显示数据显示在OLED屏上。

但是正常情况并不希望在初始化OLED完成有任何东西显示出来。所以在OLED初始化完成后要有一个清屏动作。

如何清屏?–其实就是发送显示数据。

13.3 OLED显示方式说明

img

每发送完一个显示数据,列地址会自动加1.

13.4 显示代码的编程流程

得到显示数据

设定页地址

设定列地址

发送显示数据

13.5 显示文字

13.5.1 程序设计流程

得到带显示文字的点阵编码

设定页地址

设定列地址

发送显示数据

13.5.2 取模方法

img

img

img

13.6 显示图片

13.6.1 程序设计流程

13.6.2 取模方法

13.7 STM32的SPI控制器操作OLED

img

13.7.1 STM32的SPI总线介绍

串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。
它可用于多种用途,包括使用一条双向数据线的双线单工同步传输,还可使用CRC校验的可靠通信。

13.7.1.1 SPI控制器特征

● 3线全双工同步传输
● 带或不带第三根双向数据线的双线单工同步传输
● 8或16位传输帧格式选择
● 主或从操作(做为主设备还是从设备)
● 支持多主模式
● 8个主模式波特率预分频系数(最大为fPCLK/2)–设定SPI数据传输速度
● 从模式频率 (最大为fPCLK/2)
● 主模式和从模式的快速通信
● 主模式和从模式下均可以由软件或硬件进行NSS管理:主/从操作模式的动态改变
● 可编程的时钟极性和相位(决定MODE0~3)
● 可编程的数据顺序, MSB在前或LSB在前
● 可触发中断的专用发送和接收标志
● SPI总线忙状态标志
● 支持可靠通信的硬件CRC
─ 在发送模式下, CRC值可以被作为最后一个字节发送

─ 在全双工模式中对接收到的最后一个字节自动进行CRC校验
● 可触发中断的主模式故障、过载以及CRC错误标志
● 支持DMA功能的1字节发送和接收缓冲器:产生发送和接受请求

13.7.2 STM32的SPI控制器框架(重点)

img

要通过SPI控制器发送数据,就是要把数据写入到数据寄存器(DR)里,然后数据寄存器(DR)里的数据就会被送到移位寄存器里,然后再移位寄存器中的数据就会按照设定的帧格式(高位先出/低位先出)一位一位地通过MOSI发送出去,同时也会通过MISO一位一位地接收到新的数据。当移位寄存器把待发送的数据全部发送出去后,也就会接受一个完整的新数据,移位寄存器就会把这个新数据送到接收缓冲区里。然后通过读取数据寄存器(DR)就能得到该数据。

上述数据传输过程受到主控制电路的控制。

13.7.3 STM32的SPI相关寄存器

img

13.7.4 SPI控制器使用

img

速度限制:

img

[外链图片转存中…(img-bwDJFQxy-1700124605457)]

要通过SPI控制器发送数据,就是要把数据写入到数据寄存器(DR)里,然后数据寄存器(DR)里的数据就会被送到移位寄存器里,然后再移位寄存器中的数据就会按照设定的帧格式(高位先出/低位先出)一位一位地通过MOSI发送出去,同时也会通过MISO一位一位地接收到新的数据。当移位寄存器把待发送的数据全部发送出去后,也就会接受一个完整的新数据,移位寄存器就会把这个新数据送到接收缓冲区里。然后通过读取数据寄存器(DR)就能得到该数据。

上述数据传输过程受到主控制电路的控制。

13.7.3 STM32的SPI相关寄存器

[外链图片转存中…(img-dJL9Kwwf-1700124605457)]

13.7.4 SPI控制器使用

[外链图片转存中…(img-jod05jCk-1700124605463)]

速度限制:

[外链图片转存中…(img-IzVxDiRY-1700124605463)]

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/155133.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

模拟实现一个Linux中的简单版shell

exec系列接口中的环境变量 在之前我们学习了exec系类函数的功能就是将一个程序替换成另外一个程序。 然后就会出现下面的问题&#xff1a; 首先父进程对应的环境变量的信息是从bash中来的&#xff0c;因为我们自己写的父进程在运行的时候首先就要成为bash的子进程。这里我们将…

基于单片机的温度控制器系统设计

**单片机设计介绍&#xff0c; 基于单片机的温度控制器系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的温度控制器系统是一种利用单片机来检测环境温度并控制温度的系统。它通常由以下几个部分组成&#xff…

12v24v60v高校同步降压转换芯片推荐

12V/24V/60V 高校同步降压转换芯片推荐&#xff1a; 对于需要高效、稳定、低噪音的降压转换芯片&#xff0c;推荐使用WD5030E和WD5105。这两款芯片都是采用同步整流技术&#xff0c;具有高效率、低噪音、低功耗等优点&#xff0c;适用于各种电子设备。 WD5030E是一款高效率…

Web前端—小兔鲜儿电商网站底部设计及网站中间过渡部分设计

版本说明 当前版本号[20231116]。 版本修改说明20231116初版 目录 文章目录 版本说明目录底部&#xff08;footer&#xff09;服务帮助中心版权 banner侧边栏圆点 新鲜好物&#xff08;goods&#xff09;标题 底部&#xff08;footer&#xff09; 结构&#xff1a;通栏 >…

阿里云ESSD云盘、高效云盘和SSD云盘介绍和IOPS性能参数表

阿里云服务器系统盘或数据盘支持多种云盘类型&#xff0c;如高效云盘、ESSD Entry云盘、SSD云盘、ESSD云盘、ESSD PL-X云盘及ESSD AutoPL云盘等&#xff0c;阿里云服务器网aliyunfuwuqi.com详细介绍不同云盘说明及单盘容量、最大/最小IOPS、最大/最小吞吐量、单路随机写平均时延…

Accelerate 0.24.0文档 三:超大模型推理(内存估算、Sharded checkpoints、bitsandbytes量化、分布式推理)

文章目录 一、内存估算1.1 Gradio Demos1.2 The Command 二、使用Accelerate加载超大模型2.1 模型加载的常规流程2.2 加载空模型2.3 分片检查点&#xff08;Sharded checkpoints&#xff09;2.4 示例&#xff1a;使用Accelerate推理GPT2-1.5B2.5 device_map 三、bitsandbytes量…

shell脚本学习06(小滴课堂)

fi是结束循环的意思。 这里脚本1&#xff1a;代表着脚本和1.txt文件处于同一目录下。 脚本2为绝对路径的写法。 在使用./进行启动时&#xff0c;我们需要给文件赋予执行权限。 把文件名改为2.txt: 什么都没有返回&#xff0c;说明文件已经不存在。 可以使用脚本2 if else的方式…

基于单片机的智能家居安保系统(论文+源码)

1.系统设计 本次基于单片机的智能家居安保系统设计&#xff0c;在功能上如下&#xff1a; 1&#xff09;以51单片机为系统控制核心&#xff1b; 2&#xff09;温度传感器、人体红外静释电、烟雾传感器来实现检测目的&#xff1b; 3&#xff09;以GSM模块辅以按键来实现远/近程…

Jenkinsfile+Dockerfile前端vue自动化部署

前言 本篇主要介绍如何自动化部署前端vue项目 其中&#xff0c;有两种方案&#xff1a; 第一种是利用nginx进行静态资源转发&#xff1b;第二种方案是利用nodejs进行启动访问&#xff1b; 各个组件版本如下&#xff1a; Docker 最新版本&#xff1b;Jenkins 2.387.3nginx …

SQL注入学习--GTFHub(布尔盲注+时间盲注+MySQL结构)

目录 布尔盲注 手工注入 笔记 Boolean注入 # 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 时间盲注 手工注入 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 MySQL结构 手工注入 sqlmap注入 笔记 union 联合注入&#xff0c;手工注入的一般步骤 …

conan 入门指南

conan 新手入门 1 需要注意的事项2 使用 Poco 库的 MD5 哈希计算器2.1 创建源文件2.2 搜索poco conan 库2.3 获取poco/1.9.4的元数据2.4 创建conanfile.txt2.5 安装依赖2.6 创建编译文件2.7 构建和运行程序 3 安装依赖程序4 检查依赖关系5 搜索软件包6 与其他配置一起构建 该篇…

LeetCode(25)验证回文串【双指针】【简单】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 验证回文串 1.题目 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给你一个字符串 s&…

2.FastRunner定时任务Celery+RabbitMQ

注意&#xff1a;celery版本和Python冲突问题 不能用高版本Python 用3.5以下&#xff0c;因为项目的celery用的django-celery 3.2.2 python3.7 async关键字 冲突版本 celery3.x方案一&#xff1a; celery3.xpython3.6方案二 &#xff1a; celery4.xpython3.7 解决celery执…

【Linux网络】搭建内外网的网关服务器,实现DNS分离解析与DHCP自动分配

一、实验要求&#xff1a; 二、实验思路剖析&#xff1a; 网关服务器&#xff1a; 客户端准备&#xff1a; 实操&#xff1a; 第一步先安装dhcp服务和bind服务 第二步双网卡&#xff0c;配置网卡的ip地址 第三步&#xff1a;开始配置dhcp 第四步&#xff1a;做dns分离解析…

聊一聊前端面临的安全威胁与解决对策

前端是用户在使用您的网站或Web应用程序时首先体验到的东西。如果您的Web应用程序的前端受到侵害&#xff0c;它可能会影响整个布局&#xff0c;并造成糟糕的用户体验&#xff0c;可能难以恢复。集成前端安全变得越来越重要&#xff0c;本文将指导您通过可以应用于保护您的Web应…

开源与闭源:数字化时代的辩论与未来走向

在当今的数字化时代&#xff0c;关于开源和闭源软件的辩论一直是技术界的热门话题。 特斯拉CEO马斯克最近也加入了这场辩论&#xff0c;公开表示OpenAI不应该闭源&#xff0c;而他自己的首款聊天机器人将选择开源。 这引发了人们对开源与闭源软件的进一步思考&#xff1a;开源是…

让文字在盒子中水平居中与垂直居中

简单方法&#xff1a; 1.先用text-align: center;将文字垂直居中。 2.再用line-height: Xpx;将元素的行高设置为与父元素同样的高度。&#xff08;这里的X代表父元素的高度&#xff09; 举例&#xff1a; 对于该网页的代码如下&#xff1a; <!DOCTYPE html> <html&…

【Android】使用XML资源文件存储配置项:降低代码耦合性并提高可重用性

前言 在Android开发中&#xff0c;我们经常需要存储一些配置项。 例如在创建Retrofit实例时&#xff0c;需要指定baseUrl。如果需要修改替换整个项目中的baseUrl&#xff0c;那将会是一件很痛苦的事情。 为了方便管理和维护这些配置项&#xff0c;我们可以使用资源文件来存储…

如何在Docker部署Draw.io绘图工具并远程访问

文章目录 前言1. 使用Docker本地部署Drawio2. 安装cpolar内网穿透工具3. 配置Draw.io公网访问地址4. 公网远程访问Draw.io 前言 提到流程图&#xff0c;大家第一时间可能会想到Visio&#xff0c;不可否认&#xff0c;VIsio确实是功能强大&#xff0c;但是软件为收费&#xff0…

Golang 协程、主线程

Go协程、Go主线程 1)Go主线程(有程序员直接称为线程/也可以理解成进程):一个Go线程上&#xff0c;可以起多个协程&#xff0c;你可以这样理解&#xff0c;协程是轻量级的线程。 2)Go协程的特点 有独立的栈空间 共享程序堆空间 调度由用户控制 协程是轻量级的线程 go线程-…