第32章_瑞萨MCU零基础入门系列教程之DS18B20温度获取实验

2023-09-13 23:09:12

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写,需要的同学可以在这里获取: https://item.taobao.com/item.htm?id=728461040949

配套资料获取:https://renesas-docs.100ask.net

瑞萨MCU零基础入门系列教程汇总https://blog.csdn.net/qq_35181236/article/details/132779862


第32章 DS18B20温度获取实验

本章目标

  • 了解DS18B20通信协议;
  • 学会使用RA6M5驱动DS18B20以获取温度数据;

32.1 DS18B20简介

DS18B20温度传感器具有线路简单、体积小的特点,用来测量温度非常简单,在一根通信线上可以挂载多个DS18B20温度传感器。用户可以通过编程实现9~12位的温度读数,每个DS18B20有唯一的64位序列号,保存在rom中,因此一条总线上可以挂载多个DS18B20。

温度寄存器格式如下表所示:

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
LS Byte外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
MS ByteSSSSS外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

32.1.1 单总线连接

主控芯片和DS18B20之间,只需要连接两条线:数据线、GND。除去GND,只有一条数据线,这就是单总线。

要使用一条数据线传输双向的数据,要考虑最坏的情况:如果双方同时驱动这个数据线时,一个输出高电平,一个输出低电平,会不会烧坏?所以,一般来说,单总线的驱动电路都是漏极开路,并且使用上拉电阻。如下图所示:

A、 B的输出值与DATA信号的关系,如下表所示:

ABDATA
000
010
100
111

即:DATA = A & B,只要一方输出0,DATA就是0。

使用单总线可以连接很多DS18B20,它们平时处于高阻态(内部输出1,反相后无法驱动三极管),不影响其他设备。参与通信的DS18B20,想输出0时并不会损坏其他设备。

DS18B20接口如下:

32.1.2 内部存储器

DS18B20内部有个64位只读存储器(ROM)和64位配置存储器(SCRATCHP)。

64位只读存储器(ROM)包含序列号等,具体格式如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

低八位用于CRC校验,中间48位是DS18B20唯一序列号,高八位是该系列产品系列号(固定为28h)。因此,根据每个DS18B20唯一的序列号,可以实现一条总线上可以挂载多个DS18B20时,获取指定DS18B20的温度信息。

64位配置存储器(SCRATCHP)由9个Byte组成,包含温度数据、配置信息等,具体格式如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Byte[0:1]:温度值。也就是当我们发出一个测量温度的命令之后,还需要发送一个读内存的命令才能把温度值读取出来。

Byte[2:3]:TL是低温阈值设置,TH是高温阈值设置。当温度低于/超过阈值,就会报警。 TL、TH存储在EEPROM中,数据在掉电时不会丢失;

Byte4:配置寄存器。用于配置温度精度为9、10、11或12位。配置寄存器也存储在EEPROM中,数据在掉电时不会丢失;

Byte[5:7]:厂商预留;

Byte[8]:CRC校验码。

32.1.3 通信时序

① 初始化时序

主机要跟DS18B20通信,首先需要发出一个开始信号。下图中,深黑色线表示由主机驱动信号,浅灰色线表示由DS18B20驱动信号。最开始时引脚是高电平,想要开始传输信号,步骤如下:

a. 主机必须要拉低至少480us,这是复位信号;

b. 然后主机释放总线,等待15~60us之后,

c. 如果GPIO上连有DS18B20芯片,它会拉低60~240us。

如果主机在最后检查到60~240us的低脉冲,则表示DS18B20初始化成功。

② 写时序

如果写0,拉低至少60us(写周期为60-120us)即可;

如果写1,先拉低至少1us,然后拉高,整个写周期至少为60us即可。

③ 读时序

主机先拉低至少1us,随后读取电平,如果为0,即读到的数据是0,如果为1,即可读到的数据是1。

整个过程必须在15us内完成,15us后引脚都会被拉高。

32.1.4 常用命令

现在我们知道怎么发1位数据,收1位数据。发什么数据才能得到温度值,这需要用到“命令”。

DS18B20中有两类命令:ROM命令、功能命令,列表如下:

32.1.5 流程图

DS18B20芯片手册中有ROM命令、功能命令的流程图,先贴出来,下一小节再举例。

ROM命令流程图如下:

功能命令流程图如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

32.1.6 操作示例1:单个DS18B20温度转换

总线上只一个DS18B20设备时,根据下表发送命令、读取数据。因为只有一个DS18B20,所以不需要选择设备,发出“Skip ROM”命令。然后发户“Convert T”命令启动温度转换;等待温度转换成功后再读数据。读数据前,也要发出“Skip ROM”命令。

下表列得很清楚:

主机模式数据描述
发送复位主机发出复位脉冲
接收回应总线上可能有多个DS18B20,它们都可以拉低信号,回应
发送CCh主机发出“Skip ROM”命令(忽略ROM)
发送44h主机发出“Convert T”命令(启动温度转换)
发送保持高电平主机使用强上拉保存数据线为高电平,至少tCONV()
发送复位主机发出复位脉冲
接收回应总线上可能有多个DS18B20,它们都可以拉低信号,回应
发送CCh主机发出“Skip ROM”命令(忽略ROM)
发送BEh主机发出“Read Scratchpad”命令(读内存)
接收9字节数据主机读9字节数据

32.1.7 操作示例2:指定DS18B20温度转换

总线上有多个DS18B20设备时,根据下表发送命令、读取数据。首先是要选中指定设备:使用“Match ROM”命令发出ROM Code来选择中设备;然后发出“Convert T”命令启动温度转换;等待温度转换成功后读数据。读数据前,也要发出“Match ROM”命令、ROM Code。

下表列得很清楚:

主机模式数据描述
发送复位主机发出复位脉冲
接收回应总线上可能有多个DS18B20,它们都可以拉低信号,回应
发送55h主机发出“Match ROM”命令(匹配ROM)
发送64位ROM code主机发出想访问的DS18B20的“ROM Code”
发送44h主机发出“Convert T”命令(启动温度转换)
发送保持高电平主机使用强上拉保存数据线为高电平,至少tCONV()
发送复位主机发出复位脉冲
接收回应总线上可能有多个DS18B20,它们都可以拉低信号,回应
发送55h主机发出“Match ROM”命令(匹配ROM)
发送64位ROM code主机发出想访问的DS18B20的“ROM Code”
发送BEh主机发出“Read Scratchpad”命令(读内存)
接收9字节数据主机读9字节数据

32.2 模块配置

DS18B20所用引脚要配置为开漏输出,还要使用一个GPT定时器实现微妙的延时。

32.2.1 GPIO配置

本次实验使用的DS18B20为扩展模块,接插到开发板的扩展板上。使用引脚P003作为DS18B20的DQ功能引脚,原理图如下图所示:

根据DS18B20手册的描述,DQ引脚应该被设置为开漏输出,因而在RASC中如下配置:

32.2.2 GPT配置

本次实验需要比较精确的微妙级别的延时,因而使用了一个GPT定时器来实现延时函数,GPT配置如下图所示;

32.3 延时函数模块封装

为了满足更多的需求,将延时函数封装为一个独立的模块,实现秒级、毫秒级和微秒级的延时函数。这些延时函数对于不同的平台,不同的RTOS,内部实现的办法会有所不同。

基于瑞萨处理器RA6M5平台,这些延时函数使用定时器设备的Timeout函数实现,代码如下:

void delay(unsigned long secs)
{
    struct TimerDev *pTimer = TimerDeviceFind("Delay Timer");
    pTimer->Timeout(pTimer, secs*1000*1000);
}

void mdelay(unsigned long msecs)
{
    struct TimerDev *pTimer = TimerDeviceFind("Delay Timer");
    pTimer->Timeout(pTimer, msecs*1000);
}

void udelay(unsigned long usecs)
{
    struct TimerDev *pTimer = TimerDeviceFind("Delay Timer");
    pTimer->Timeout(pTimer, usecs);
}

要想使用上述函数,要在config.h中定义如下宏开关:

/* Libraries Enable/Disable */
#define LIBS_USE_DELAY      1

32.4 驱动程序

32.4.1 GPIO驱动

GPIO驱动程序就是对引脚进行初始化和读写。

  1. 初始化GPIO

    static int IODrvInit(struct IODev *ptdev)
    {
        if(ptdev == NULL)       return -EINVAL;
        if(ptdev->name == NULL) return -EINVAL;
        
        fsp_err_t err = g_ioport.p_api->open(g_ioport.p_ctrl, g_ioport.p_cfg);
        assert(FSP_SUCCESS == err);
    
        return ESUCCESS;
    }
    
  2. 输出电平

    static int IODrvWrite(struct IODev *ptdev, unsigned char level)
    {
        if(ptdev == NULL)       return -EINVAL;
        if(ptdev->name == NULL) return -EINVAL;
        
        fsp_err_t err = g_ioport.p_api->pinCfg(g_ioport.p_ctrl, ptdev->port, IOPORT_CFG_PORT_DIRECTION_OUTPUT);
        assert(FSP_SUCCESS == err);
        err = g_ioport.p_api->pinWrite(g_ioport.p_ctrl, ptdev->port, (bsp_io_level_t)level);
        assert(FSP_SUCCESS == err);
    
        return ESUCCESS;
    }
    
  • 第06行:将IO重新配置为输出模式;
  1. 读取电平

    static int IODrvRead(struct IODev *ptdev)
    {
        if(ptdev == NULL)       return -EINVAL;
        if(ptdev->name == NULL) return -EINVAL;
    
        fsp_err_t err = g_ioport.p_api->pinCfg(g_ioport.p_ctrl, ptdev->port, IOPORT_CFG_PORT_DIRECTION_INPUT);
        assert(FSP_SUCCESS == err);
        err = g_ioport.p_api->pinRead(g_ioport.p_ctrl, ptdev->port, (bsp_io_level_t*)&ptdev->value);
        assert(FSP_SUCCESS == err);
    
        return ESUCCESS;
    }
    
  • 第06行:将IO重新配置为输入模式;

32.4.2 定时器驱动

本节只展现GPT关键的代码,至于其它部分代码请读者自行参考前面章节。

  1. 初始化GPT

    static int GPTDrvInit(struct TimerDev *ptdev)
    {
        if(NULL==ptdev) return -EINVAL;
        switch(ptdev->channel)
        {
            case 0:
            {
                /* 打开GPT设备完成初始化 */
                fsp_err_t err = g_timer0.p_api->open(g_timer0.p_ctrl, g_timer0.p_cfg);
                assert(FSP_SUCCESS == err);
                break;
            }
            case 1:case 2:case 3:
            case 4:case 5:case 6:
            case 7:case 8:case 9:
                break;
            default:break;
        }
        
        return ESUCCESS;
    }
    
  2. GPT Timeout

static int GPTDrvTimeout(struct TimerDev *ptdev, unsigned int timeout)
{
    if(NULL == ptdev)   return -EINVAL;
    if(0 == timeout)    return -EINVAL;
    switch(ptdev->channel)
    {
        case 0:
        {
            fsp_err_t err = g_timer0.p_api->periodSet(g_timer0.p_ctrl, timeout*100);
            assert(FSP_SUCCESS == err);
            err = g_timer0.p_api->reset(g_timer0.p_ctrl);
            assert(FSP_SUCCESS == err);
            err = g_timer0.p_api->start(g_timer0.p_ctrl);
            assert(FSP_SUCCESS == err);
            GPTDrvWaitTimer0Overflow();
            break;
        }
        case 1:case 2:case 3:
        case 4:case 5:case 6:
        case 7:case 8:case 9:
            break;
        default:break;
    }
    return ESUCCESS;
}

32.5 DS18B20模块

32.5.1 DS18B20设备对象

要操作DS18B20,只需要对它进行初始化、然后读取数值(在读函数中封装了启动温度转换的操作)。抽象出如下结构体:

typedef struct DS18B20Dev{
    float value;
    int (*Init)(struct DS18B20Dev *ptdev);
    int (*Read)(struct DS18B20Dev *ptdev);
}DS18B20Device;

在Read函数中会发出各类指令,根据这些指令定义一个枚举类型:

typedef enum
{
    READ_ROM            = 0x33,
    MATCH_ROM           = 0x55,
    SEARCH_ROM          = 0xF0,
    ALARM_SEARCH        = 0xEC,
    SKIP_ROM            = 0xCC,
    
    WRITE_SCRATCHPAD    = 0x4E,
    READ_SCRATCHPAD     = 0xBE,
    COPY_SCRATCHPAD     = 0x48,
    CONVERT_T           = 0x44,
    RECALL_E2           = 0xB8,
    READ_POWER_SUPPLY   = 0xB4,
}DS18B20_CMD;

最后需要向上层应用提供获取DS18B20设备的接口:

struct DS18B20Dev *DS18B20GetDevice(void)
{
    return &gDevice;
}

32.5.2 初始化设备

DS18B20本身不需要进行什么初始化,只需要初始化使用到的IO即可:

static int DS18B20DevInit(struct DS18B20Dev *ptdev)
{
    if(NULL == ptdev)   return -EINVAL;
    gDQDevice = IODeviceFind("DS18B20 DQ");
    if(NULL == gDQDevice)
    {
        printf("Failed to find DS18B20 DQ!\r\n");
        return -ENXIO;
    }
    if(ESUCCESS != gDQDevice->Init(gDQDevice))
    {
        printf("Failed to init GPIO!\r\n");
        return -EIO;
    }
    return ESUCCESS;
}

32.5.3 DS18B20发送一个字节数据

根据DS18B20通信时序,实现发送一个字节的函数:

static void DS18B20DevWriteByte(unsigned char cmd)
{
    for(unsigned char i=0; i<8; i++)
    {
        if((cmd&0x01)==0x01)   // 写1
        {
            gDQDevice->Write(gDQDevice, 0);
            udelay(10);  // 低电平维持10us
            gDQDevice->Write(gDQDevice, 1);
            udelay(100); // 高电平维持100us, 总时长110us 
        }
        else    // 写0
        {
            gDQDevice->Write(gDQDevice, 0);
            udelay(100);  // 低电平维持50us
            gDQDevice->Write(gDQDevice, 1);
            udelay(10);  // 高电平维持50us, 总时长100us 
        }
        cmd = cmd>>1;
    }
}

32.5.4 DS18B20接收一个字节数据

基于开漏输出的特点,主控想放弃单总线的控制时,要让它输出1;然后才可以读取数据。代码如下:

static unsigned char DS18B20DevReadByte(void)
{
    unsigned char tmp = 0;
    unsigned char time_out = 100;

    for(unsigned char i=0; i<8; i++)
    {
        gDQDevice->Write(gDQDevice, 1);
        gDQDevice->Read(gDQDevice);
        if(gDQDevice->value==0)
        {
            tmp = (tmp>>1);
            gDQDevice->Read(gDQDevice);
            while((gDQDevice->value==0) && (time_out!=0))
            {
                udelay(1);
                gDQDevice->Read(gDQDevice);
                time_out--;
            }
            if(time_out==0) return 0xFF;
            udelay(10);
        }
        else
        {
            tmp = (tmp>>1)|0x80;
            udelay(100);
        }
    }
    return tmp;
}
  • 第08行:处理器将IO输出寄存器写1放弃总线控制权;

32.5.5 DS18B20复位

  1. 主控发出初始化时序

代码如下:

static void DS18B20DevResetPulse(void)
{   
    if(NULL == gDQDevice)   return;

    gDQDevice->Write(gDQDevice, 1);
    udelay(10);
    gDQDevice->Write(gDQDevice, 0); // 主机拉低480us~960us
    udelay(480);
    gDQDevice->Write(gDQDevice, 1); // 主机拉高10us
    udelay(10);
}

第05、09行的代码让GPIO输出1,但是基于开漏电路的特点,它是依靠上拉电阻将总线拉高。

  1. 主控等待DS18B20发出回应脉冲

当主机发出复位脉冲且放弃总线控制后,只需要去读取IO电平并判断时延即可:

static int DS18B20DevWaitPresencePulse(void)
{
    if(NULL == gDQDevice)   return -EINVAL;
    unsigned int time_out = 100;

    time_out = 100;
    gDQDevice->Read(gDQDevice);
    while((gDQDevice->value==1) && (time_out!=0))  
    {
        // 等待DS18B20将总线拉低
        gDQDevice->Read(gDQDevice);
        udelay(1);
        time_out--;
    }
    if(time_out==0) return -EIO;
    
    time_out = 100;
    gDQDevice->Read(gDQDevice);
    while((gDQDevice->value==0) && (time_out!=0))  
    {
        gDQDevice->Read(gDQDevice);
        udelay(1);
        time_out--;
    }
    if(time_out==0) return -EIO;
    
    return ESUCCESS;
}
  1. 复位函数

综合前两个函数即为复位函数:

static int DS18B20DevReset(void)
{
    DS18B20DevResetPulse();
    if(DS18B20DevWaitPresencePulse() == EIO)
    {
        return -EIO;  // 等待应答超时
    }
    
    return ESUCCESS;
}

32.5.6 读取DS18B20温度数据

根据前面的操作示例1,编写读取函数,代码如下:

static int DS18B20DevRead(struct DS18B20Dev *ptdev)
{
    if(NULL == ptdev)   return -EINVAL;

    unsigned char ret1 = 0, ret2 = 0;
    unsigned short ret = 0;
    
    if(ESUCCESS != DS18B20DevReset())
    {
        return -EIO;
    }
    DS18B20DevWriteByte(SKIP_ROM);    // 0xCC
    DS18B20DevWriteByte(CONVERT_T);   // 0x44
    
    if(ESUCCESS != DS18B20DevReset())
    {
        return -EIO;
    }
    DS18B20DevWriteByte(SKIP_ROM);    // 0xCC
    DS18B20DevWriteByte(READ_SCRATCHPAD); // 0xBE
    ret1 = DS18B20DevReadByte();
    ret2 = DS18B20DevReadByte();
    ret = (unsigned short)((ret2<<8) | ret1);

    float mTempture_inter = 0, mTempture_dec = 0, mTempture = 0;
    mTempture_dec = (float)((ret&0xFF)*0.0625);
    mTempture_inter = (ret>>4)&0x7F;
    mTempture = mTempture_inter + mTempture_dec;
    if(((ret>>12)&0xF)==0xF)
    {
        mTempture = -mTempture;
    }
    
    ptdev->value = mTempture;
    
    return ESUCCESS;
}

32.6 测试程序

测试程序比较简单,获取DS18B20设备并成功初始化之后,直接读取即可。本实验每隔2s读取一次数据并打印观察:

void DeviceTest(void)
{
    UartDevicesRegister();
    TimerDevicesRegister();
    IODevicesRegister();
    
    DS18B20Device *pDevice = DS18B20GetDevice();
    if(NULL == pDevice)
    {
        printf("Error. There is no DS18B20 device!\r\n");
        return;
    }
    pDevice->Init(pDevice);
    printf("\r\n");
    while(1)
    {
        if(pDevice->Read(pDevice) == ESUCCESS)
        {
            printf("环境温度:%.4f℃ \r", pDevice->value);
        }
        delay(2);
    }
}

32.7 测试结果

将程序编译烧录到开发板中运行可以观察到如下图所示的测试结果:


本章完
更多推荐

Linux:基础开发工具之yum,vim,gcc的使用

文章目录yumvimgcc本篇主要总结的是Linux下开发工具yumvimgcc/g++yum什么是yum?不管是在手机移动端还是pc端,不管是什么操作系统,当用户想要下载一些内容或者工具的时候,都需要到一个特定的位置进行下载,例如在手机上,要下载一些应用的时候就可以去对应的应用商店下载而在Linux系统中也有这样的概

虚拟机如何扩容麒麟操作系统的根文件系统

在工作中,经常会面临服务器磁盘空间不足的问题,特别是根文件系统(/)快满了。本文将介绍如何扩容Linux服务器的根文件系统,以解决这个常见的问题。步骤一:关机后扩容磁盘步骤二:打开磁盘实用工具(主要就是把空闲磁盘挂载命令行的话网上教程很多这里不赘述了我偷懒了hh)将空闲区域创建分区(效果如上图)步骤三:检查磁盘空间首先

jdk17新特性

JDK17新特性jdk17下载地址:https://download.oracle.com/java/17/latest/jdk-17_windows-x64_bin.exeJDK17文档-首页(oracle.com)垃圾回收器(ZGarbageCollector)概述JDK17引入名为ZGC(ZGarbageColl

【2023,学点儿新Java-02】计算机硬件与软件 | CPU、内存、硬盘概览 | 科学使用键盘——“指法” | 软件——计算机的灵魂 | 人机交互方式

前情回顾:【2023,学点儿新Java-01】从查看本机jdk版本开始|Java基础全程脉络图、Java工程师全程技术路线、Java职业晋升路线图我们见到的太阳是八分钟前的太阳,见到的月亮是一点三秒之前的月亮,见到一英里之外的建筑,是五微秒之前存在的,即使你在我一米之外,我见到的也是三纳米秒以前的你,我们所眼见的都是过

华为云云耀云服务器L实例评测|基于L实例使用Docker部署MySQL服务并连接MySQL—phpMyAdmin管理工具

文章目录一、云耀云服务器产品优势1、智能不卡顿2、价优随心用3、上手更简单4、管理更省心二、远程连接云耀云服务器L实例三、安装Docker、docker-compse1、docker安装2、docker-compose安装四、方法①使用Docker安装部署MySQL服务五、方法②使用docker-compse安装部署M

ELK 企业级日志分析系统

ELK概述:1、ELK简介ELK平台是一套完整的日志集中处理解决方案,将ElasticSearch、Logstash和Kiabana三个开源工具配合使用,完成更强大的用户对日志的查询、排序、统计需求。●ElasticSearch:是基于Lucene(一个全文检索引擎的架构)开发的分布式存储检索引擎,用来存储各类日志。E

免杀对抗-Python-混淆算法+反序列化-打包生成器-Pyinstall

Python-MSF/CS生成shellcode-上线cs上线1.生成shellcode-c或者python2.打开pycharm工具,创建一个py文件,将原生态执行代码复制进去shellcode执行代码:importctypesfromdjango.contrib.gisimportptr#cs#shellcode=

完整指南:使用JavaScript从零开始构建中国象棋游戏

引言中国象棋,又被称为国际象棋,是一款起源于中国的古老棋类游戏。本文旨在为大家提供一个简单明了的步骤,教你如何使用JavaScript从零开始构建这款经典的棋类游戏。1.游戏简介在中国象棋中,两方各有一军队,包括士、象、车、马、炮和卒等棋子,目标是将对方的“将”或“帅”给将死,达到胜利。2.准备工作首先,确保你的开发环

文举论金:黄金原油全面走势分析

市场没有绝对,涨跌没有定势,所以,对市场行情的涨跌平衡判断就是你的制胜法宝。欲望!有句意大利谚语:让金钱成为我们忠心耿耿的仆人,否则,它就会成为一个专横跋扈的主人。空头,多头都能赚钱,唯有贪心不能赚。是你掌控欲望还是欲望掌控你?古人云:不积硅步无以至千里,不积小流无以成江海。希望这句话成为我们之间的共勉。自知!人贵自知

SpringCloud:Feign实现微服务之间相互请求

文章目录🎉欢迎来到架构设计专栏~SpringCloud:Feign实现微服务之间相互请求☆*o(≧▽≦)o*☆嗨~我是IT·陈寒🍹✨博客主页:IT·陈寒的博客🎈该系列文章专栏:架构设计📜其他专栏:Java学习路线Java面试技巧Java实战项目AIGC人工智能数据结构学习🍹文章作者技术和水平有限,如果文中出现

Go的并发的退出

有时候我们需要通知goroutine停止它正在干的事情,比如一个正在执行计算的web服务,然而它的客户端已经断开了和服务端的连接。Go语言并没有提供在一个goroutine中终止另一个goroutine的方法,由于这样会导致goroutine之间的共享变量落在未定义的状态上。在8.7节中的rocketlaunch程序中

热文推荐