android HAL层代码

AudioFlinger往hal层写数据时会调用到Hal层的out_write函数

在打开pcm stream设备的时候会调用到select_device和pcm_open函数

enable_snd_device和 enable_audio_route函数
打开设备通路的流程:

int select_devices(struct audio_device *adev, audio_usecase_t uc_id)
{
    usecase = get_usecase_from_list(adev, uc_id);//根据uc_id获取当前的usecase  
    if ((usecase->type == VOICE_CALL) || (usecase->type == VOIP_CALL)  ||  
   。。。。
    out_snd_device = platform_get_output_snd_device(adev->platform, usecase->stream.out->devices);

    in_snd_device = platform_get_input_snd_device
if (usecase->type == PCM_PLAYBACK) {  
            usecase->devices = usecase->stream.out->devices;  
            in_snd_device = SND_DEVICE_NONE;  
            out_snd_device = platform_get_output_snd_device(adev->platform,  
                                            usecase->stream.out->devices);  
...//如果当前是音乐播放的话只选择输出设备  
} else if (usecase->type == PCM_CAPTURE) {  
            usecase->devices = usecase->stream.in->device;  
            out_snd_device = SND_DEVICE_NONE;  
            in_snd_device = platform_get_input_snd_device(adev->platform,                                                                 AUDIO_DEVICE_NONE);  
             ...//如果当时是录音的话只选择输入设备  
}  
}     
...  
if (out_snd_device != SND_DEVICE_NONE) {  
...  
        enable_snd_device(adev, out_snd_device, false);//打开输出设备通路  
}  
if (in_snd_device != SND_DEVICE_NONE) {  
    ....  
    enable_snd_device(adev, in_snd_device, false);//打开输入设备通路  
}   
...  
usecase->in_snd_device = in_snd_device;  
usecase->out_snd_device = out_snd_device;//更新当前的输入和输出设备  


enable_audio_route(adev, usecase, true);//使能audio route 

int enable_snd_device(struct audio_device *adev,
snd_device_t snd_device,
bool __unused update_mixer)
{
….
if(platform_get_snd_device_name_extn(adev->platform, snd_device, device_name) < 0 ) {
ALOGE(“%s: Invalid sound device returned”, func);
return -EINVAL;
}//根据snd_device名字在device_table上查找device_name,如[SND_DEVICE_OUT_HANDSET] = “handset”等

audio_route_apply_and_update_path(adev->audio_route, device_name);
//跟当前的device_name在 mixer_paths.xml上找到对应的设备通路并打开

}

Audio_route.c (\system\media\audio_route)

int audio_route_apply_and_update_path(struct audio_route *ar, const charchar *name)  
{  
if (audio_route_apply_path(ar, name) < 0) {  
//更新声卡中所有控件的值  
        return -1;  
    }  
return audio_route_update_path(ar, name, false /*reverse*/);  
//将更新后声卡控件的值写入到“/dev/snd/controlC0”中  
}  
int audio_route_apply_path(struct audio_route *ar, const charchar *name)  
{  
    struct mixer_path *path;  
...  
    path = path_get_by_name(ar, name);//根据当前设备名字找到该设备通路  
...  
    path_apply(ar, path);//根据当前使能通路控件值更新声卡中控件的状态,即mixer_state  


    return 0;  
}  


static int path_apply(struct audio_route *ar, struct mixer_path *path)  
{  
...  
    for (i = 0; i < path->length; i++) {  
        ctl_index = path->setting[i].ctl_index;//通路控件的id  
        ctl = index_to_ctl(ar, ctl_index);  
        type = mixer_ctl_get_type(ctl);  
        if (!is_supported_ctl_type(type))  
            continue;  
...  
        memcpy(ar->mixer_state[ctl_index].new_value, path->setting[i].value,  
               path->setting[i].num_values * sizeof(int));//更新声卡中该id对应的空间的值  
    }  


    return 0;  
}  


static int audio_route_update_path(struct audio_route *ar, const charchar *name, bool reverse)  
{  
...  
path = path_get_by_name(ar, name);//找出当前设备通路  
...  
i = reverse ? (path->length - 1) : 0;  
end = reverse ? -1 : (int32_t)path->length;  


while (i != end) {  
...  
ctl_index = path->setting[i].ctl_index;//找出设备通路中控件id  
struct mixer_state * ms = &ar->mixer_state[ctl_index];//根据控件id找到当前控件的值  
 ...  
 /* if any value has changed, update the mixer */  
 for (j = 0; j < ms->num_values; j++) {  
    if (ms->old_value[j] != ms->new_value[j]) {  
        if (type == MIXER_CTL_TYPE_ENUM)  
            mixer_ctl_set_value(ms->ctl, 0, ms->new_value[0]);  
//将值写入到底层控件中  
        else  
            mixer_ctl_set_array(ms->ctl, ms->new_value, ms->num_values);  
            memcpy(ms->old_value, ms->new_value, ms->num_values * sizeof(int));  
            break;  
    }  
  }  
  i = reverse ? (i - 1) : (i + 1);  
}  
return 0;  
}  
int mixer_ctl_set_value(struct mixer_ctl *ctl, unsigned int id, int value)  
{  
struct snd_ctl_elem_value ev;  
...  
memset(&ev, 0, sizeof(ev));  
ev.id.numid = ctl->info->id.numid;  
ret = ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_READ, &ev);//读当前所有音频控件的值  
...  
return ioctl(ctl->mixer->fd, SNDRV_CTL_IOCTL_ELEM_WRITE, &ev);//将新的控件的值写入到/dev/snd/controlC0中去  
}  

audio route的流程:

int enable_audio_route(struct audio_device *adev,  
                              struct audio_usecase *usecase,  
                              bool __unused update_mixer)  
{  
...  
platform_add_backend_name(mixer_path, snd_device);//在通路名上添加设备名,用于定制不同的audio route  
...  
audio_route_apply_and_update_path(adev->audio_route, mixer_path);//打开audio route,其流程与之前的打开设备通路的一样  
...  
}  

以上设备通路和audio route打开的过程中,最终都走到了mixer_ctl_set_value函数,这个函数里面利用底层提供给用户的空间的设备节点来控制底层的音频控件
首先,我们在使用 snd_card_create函数创建声卡的时候,会调用snd_ctl_create函数创建control逻辑设备,

int snd_ctl_create(struct snd_card *card)  
{  
    static struct snd_device_ops ops = {  
        .dev_free = snd_ctl_dev_free,  
        .dev_register = snd_ctl_dev_register,  
        .dev_disconnect = snd_ctl_dev_disconnect,  
    };//这个ops很关键  


    if (snd_BUG_ON(!card))  
        return -ENXIO;  
    return snd_device_new(card, SNDRV_DEV_CONTROL, card, &ops);//把control设备放入到card->devices中去  
}  

上面创建control逻辑设备的时候,ops中的snd_ctl_dev_register接口,会调用snd_register_device传递snd_ctl_f_ops,这个osp就是实际使用到的control设备的操作函数。

static const struct file_operations snd_ctl_f_ops =
{
.owner =THIS_MODULE,
.read =snd_ctl_read,
.open =snd_ctl_open,
.release =snd_ctl_release,
.llseek =no_llseek,
.poll =snd_ctl_poll,
.unlocked_ioctl =snd_ctl_ioctl,
.compat_ioctl =snd_ctl_ioctl_compat,
.fasync =snd_ctl_fasync,
};

在用户空间中 mixer_ctl_set_value函数读写底层control节点,其实就是通过snd_ctl_ioctl函数来实现的:

static long snd_ctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
...
case SNDRV_CTL_IOCTL_ELEM_READ:
return snd_ctl_elem_read_user(card, argp);
case SNDRV_CTL_IOCTL_ELEM_WRITE:
return snd_ctl_elem_write_user(ctl, argp);
...
}

最终,snd_ctl_elem_read_user和snd_ctl_elem_write_user函数都分别调用了snd_kcontrol的get和put函数,实现了对dapm kcontrol控件的控制。

pcm_open

struct pcm_config pcm_config_deep_buffer = {
    .channels = 2,
    .rate = DEFAULT_OUTPUT_SAMPLING_RATE,//48000
    .period_size = DEEP_BUFFER_OUTPUT_PERIOD_SIZE,//960,硬件一次消耗的数据
    .period_count = DEEP_BUFFER_OUTPUT_PERIOD_COUNT,//8
    .format = PCM_FORMAT_S16_LE,//16 bits = 2bytes(即2字节)
    .start_threshold = DEEP_BUFFER_OUTPUT_PERIOD_SIZE / 4,
    .stop_threshold = INT_MAX,
    .avail_min = DEEP_BUFFER_OUTPUT_PERIOD_SIZE / 4,
};

1 frame = channels * format = 2*2字节 = 4字节 (32bits)
Period_size = DEEP_BUFFER_OUTPUT_PERIOD_SIZE 即 960frames = 960*32字节

struct pcm *pcm_open(unsigned int card, unsigned int device,
                     unsigned int flags, struct pcm_config *config)
{
struct snd_pcm_hw_params params;//硬件参数
struct snd_pcm_sw_params sparams;//软件参数
...
pcm = calloc(1, sizeof(struct pcm));
pcm->config = *config;
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
             flags & PCM_IN ? 'c' : 'p');//P即为底层对应的dai_link
pcm->flags = flags;
pcm->fd = open(fn, O_RDWR);//打开底层设备节点


param_init(&params);
param_set_mask(&params, SNDRV_PCM_HW_PARAM_FORMAT,
                   pcm_format_to_alsa(config->format));
param_set_mask(&params, SNDRV_PCM_HW_PARAM_SUBFORMAT,
                   SNDRV_PCM_SUBFORMAT_STD);
param_set_min(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
param_set_int(&params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
                  pcm_format_to_bits(config->format));
param_set_int(&params, SNDRV_PCM_HW_PARAM_FRAME_BITS,
                  pcm_format_to_bits(config->format) * config->channels);
param_set_int(&params, SNDRV_PCM_HW_PARAM_CHANNELS,
                  config->channels);
param_set_int(&params, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
param_set_int(&params, SNDRV_PCM_HW_PARAM_RATE, config->rate);


if (flags & PCM_MMAP)
param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
                   SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
else
param_set_mask(&params, SNDRV_PCM_HW_PARAM_ACCESS,
                   SNDRV_PCM_ACCESS_RW_INTERLEAVED);
//设置硬件参数


if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, &params)) {
        oops(pcm, errno, "cannot set hw params");
        goto fail_close;
}//将硬件参数参数写到设备节点中。


/* get our refined hw_params */
config->period_size = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
//硬件一次性消耗数据量
config->period_count = param_get_int(&params, SNDRV_PCM_HW_PARAM_PERIODS);
pcm->buffer_size = config->period_count * config->period_size;
//硬件整体缓冲大小


 if (flags & PCM_MMAP) {
     pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
                                PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
  //将pcm->buffer_size映射进内存
     ...
  }


  memset(&sparams, 0, sizeof(sparams));
  sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
  sparams.period_step = 1;
  sparams.start_threshold = config->start_threshold;
  sparams.stop_threshold = config->stop_threshold;
  sparams.avail_min = config->avail_min;
  sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
  sparams.silence_size = 0;
  sparams.silence_threshold = config->silence_threshold;
  pcm->boundary = sparams.boundary = pcm->buffer_size;


  if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
        oops(pcm, errno, "cannot set sw params");
        goto fail;
  }//将软件参数写入到底层设备节点中


  rc = pcm_hw_mmap_status(pcm);
  ..
}

/dev/snd/pcmC%uD%u%c 节点底层相关的操作函数在:Pcm_native.c (\linux\android\kernel\sound\core):

const struct file_operations snd_pcm_f_ops[2] = {
{
.owner =THIS_MODULE,
.write =snd_pcm_write,
.aio_write =snd_pcm_aio_write,
.open =snd_pcm_playback_open,
.release =snd_pcm_release,
.llseek =no_llseek,
.poll =snd_pcm_playback_poll,
.unlocked_ioctl =snd_pcm_playback_ioctl,
.compat_ioctl =snd_pcm_ioctl_compat,
.mmap =snd_pcm_mmap,
.fasync =snd_pcm_fasync,
.get_unmapped_area =snd_pcm_get_unmapped_area,
},
{
.owner =THIS_MODULE,
.read =snd_pcm_read,
.aio_read =snd_pcm_aio_read,
.open =snd_pcm_capture_open,
.release =snd_pcm_release,
.llseek =no_llseek,
.poll =snd_pcm_capture_poll,
.unlocked_ioctl =snd_pcm_capture_ioctl,
.compat_ioctl =snd_pcm_ioctl_compat,
.mmap =snd_pcm_mmap,
.fasync =snd_pcm_fasync,
.get_unmapped_area =snd_pcm_get_unmapped_area,
}
};

硬件参数写入:
snd_pcm_playback_ioctl -> snd_pcm_playback_ioctl1 -> snd_pcm_common_ioctl1-> snd_pcm_hw_params_user(substream, arg);
其中 arg 对应的是 params
static int snd_pcm_hw_params_user(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params __user * _params)
{
struct snd_pcm_hw_params *params;
int err;

params = memdup_user(_params, sizeof(*params)); //从用户向内核拷贝参数

err = snd_pcm_hw_params(substream, params);//pcm硬件设备参数设置

kfree(params);
return err;
}

软件参数写入,流程上硬件的类似,最终走到snd_pcm_sw_params函数。

pcm_write函数

int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
{
struct snd_xferi x;
...
x.buf = (void*)data;
x.frames = count / (pcm->config.channels *
                        pcm_format_to_bits(pcm->config.format) / 8);
//计算出帧数

。。。。
for (;;) {
   if (!pcm->running) {
      if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE))
         return oops(pcm, errno, "cannot prepare channel");
         if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
            return oops(pcm, errno, "cannot write initial data");
         pcm->running = 1;
         return 0;
   }
   if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
         pcm->running = 0return oops(pcm, errno, "cannot write stream data");
   }
   return 0;
}
}
底层相关操作函数:
snd_pcm_playback_ioctl1() -> SNDRV_PCM_IOCTL_WRITEI_FRAMES -> snd_pcm_lib_write() ->snd_pcm_lib_write1() -> snd_pcm_lib_write_transfer()

具体流程如下:
在snd_pcm_playback_ioctl1函数中,根据SNDRV_PCM_IOCTL_WRITEI_FRAMES参数,将用户空间传下来的数据传递给snd_pcm_lib_write()函数:

if (copy_from_user(&xferi, _xferi, sizeof(xferi)))
return -EFAULT;
result = snd_pcm_lib_write(substream, xferi.buf, xferi.frames);
snd_pcm_lib_write()函数由通过snd_pcm_lib_write1()函数调用snd_pcm_lib_write_transfer()将size大小的数据buf传送给DSP:
return snd_pcm_lib_write1(substream, (unsigned long)buf, size, nonblock,
 snd_pcm_lib_write_transfer);


static snd_pcm_sframes_t snd_pcm_lib_write1(struct snd_pcm_substream *substream, 
   unsigned long data,
   snd_pcm_uframes_t size,
   int nonblock,
   transfer_f transfer)
{
...
while (size > 0) {
...
err = transfer(substream, appl_ofs, data, offset, frames);
//这个transfer即为snd_pcm_lib_write_transfer,在其中会调用substream->ops->copy(substream, -1, hwoff, buf, frames) 拷贝数据
...
if (runtime->status->state == SNDRV_PCM_STATE_PREPARED &&
snd_pcm_playback_hw_avail(runtime) >= (snd_pcm_sframes_t)runtime->start_threshold) {
err = snd_pcm_start(substream);
...
}
}
...
}

下面看一下snd_pcm_lib_write_transfer和snd_pcm_start两个函数。

static int snd_pcm_lib_write_transfer(struct snd_pcm_substream *substream,
     unsigned int hwoff,
     unsigned long data, unsigned int off,
     snd_pcm_uframes_t frames)
{
struct snd_pcm_runtime *runtime = substream->runtime;
...
char __user *buf = (char __user *) data + frames_to_bytes(runtime, off);
if (substream->ops->copy) {
if ((err = substream->ops->copy(substream, -1, hwoff, buf, frames)) < 0)
//调用substeam的copy函数拷贝数据到dai上去
return err;
} else {
char *hwbuf = runtime->dma_area + frames_to_bytes(runtime, hwoff);
if (copy_from_user(hwbuf, buf, frames_to_bytes(runtime, frames)))
    //直接将数据拷贝到硬件去
return -EFAULT;
}
return 0;
}


int snd_pcm_start(struct snd_pcm_substream *substream)
{
return snd_pcm_action(&snd_pcm_action_start, substream,
     SNDRV_PCM_STATE_RUNNING);
}
其中,
static struct action_ops snd_pcm_action_start = {
.pre_action = snd_pcm_pre_start,
.do_action = snd_pcm_do_start,
.undo_action = snd_pcm_undo_start,
.post_action = snd_pcm_post_start
};

在snd_pcm_start函数中有以下流程:
snd_pcm_start-> snd_pcm_action -> snd_pcm_action_single

在snd_pcm_action_single中会分别执行:
ops->pre_action(substream, state);
ops->do_action(substream, state);
即snd_pcm_pre_start() 和snd_pcm_do_start() 函数,在snd_pcm_do_start函数中会执行:
substream->ops->trigger(substream, SNDRV_PCM_TRIGGER_START); 即触发数据拷贝。

Substream的ops都是在platform中实现的,如在snd_soc_dai_link中,我们设置了platform_name = “msm-pcm-dsp.0”
则可以找到其对应的ops函数在:

Msm-pcm-q6-v2.c (\kernel\sound\soc\msm\qdsp6v2)
static struct snd_pcm_ops msm_pcm_ops = {
.open           = msm_pcm_open,
.copy= msm_pcm_copy,
.hw_params= msm_pcm_hw_params,
.close          = msm_pcm_close,
.ioctl          = snd_pcm_lib_ioctl,
.prepare        = msm_pcm_prepare,
.trigger        = msm_pcm_trigger,
.pointer        = msm_pcm_pointer,
.mmap= msm_pcm_mmap,
};

用户空间pcm_write函数分别调用了底层的msm_pcm_copy和msm_pcm_trigger函数,在msm_pcm_copy函数中使用了q6asm_write函数向DSP中写入数据,q6asm_write和msm_pcm_trigger中最终都是通过apr_send_pkt来发送数据的。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的Hall传感器在Android HAL代码示例: ```c #include <hardware/hardware.h> #include <hardware/sensors.h> #include <fcntl.h> #include <errno.h> #include <math.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define SENSOR_NAME "hall-sensor" #define SENSOR_VENDOR "ACME" #define SENSOR_VERSION 1 #define SENSOR_HANDLE 0 struct hall_sensor_context_t { struct sensors_poll_device_t device; sensors_event_t sensor_event; int fd; }; static int hall_sensor_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device); static int hall_sensor_close(struct hw_device_t* device); static int hall_sensor_activate(struct sensors_poll_device_t *dev, int handle, int enabled); static int hall_sensor_set_delay(struct sensors_poll_device_t *dev, int handle, int64_t ns); static int hall_sensor_poll(struct sensors_poll_device_t *dev, sensors_event_t* data, int count); static struct hw_module_methods_t hall_sensor_module_methods = { .open = hall_sensor_open }; struct sensors_poll_device_t HAL_MODULE_INFO_SYM = { .common = { .tag = HARDWARE_DEVICE_TAG, .version = 0, .module = &HAL_MODULE_INFO_SYM.common, .close = hall_sensor_close, }, .poll = hall_sensor_poll, .activate = hall_sensor_activate, .setDelay = hall_sensor_set_delay, }; static int hall_sensor_open(const struct hw_module_t* module, const char* name, struct hw_device_t** device) { if (strcmp(name, SENSORS_POLL_DEVICE_NAME)) { return -EINVAL; } struct hall_sensor_context_t* hall_dev = (struct hall_sensor_context_t*) malloc(sizeof(struct hall_sensor_context_t)); memset(hall_dev, 0, sizeof(*hall_dev)); hall_dev->device.common.tag = HARDWARE_DEVICE_TAG; hall_dev->device.common.version = 0; hall_dev->device.common.module = (struct hw_module_t*) module; hall_dev->device.common.close = hall_sensor_close; hall_dev->device.poll = hall_sensor_poll; hall_dev->device.activate = hall_sensor_activate; hall_dev->device.setDelay = hall_sensor_set_delay; hall_dev->sensor_event.version = sizeof(sensors_event_t); hall_dev->sensor_event.sensor = SENSOR_HANDLE; hall_dev->sensor_event.type = SENSOR_TYPE_MAGNETIC_FIELD; hall_dev->sensor_event.data[0] = 0.0f; hall_dev->sensor_event.data[1] = 0.0f; hall_dev->sensor_event.data[2] = 0.0f; hall_dev->fd = open("/dev/hall-sensor", O_RDONLY); if (hall_dev->fd < 0) { ALOGE("Failed to open hall sensor device: %s", strerror(errno)); free(hall_dev); return -errno; } *device = &hall_dev->device.common; return 0; } static int hall_sensor_close(struct hw_device_t* device) { struct hall_sensor_context_t* hall_dev = (struct hall_sensor_context_t*) device; close(hall_dev->fd); free(hall_dev); return 0; } static int hall_sensor_activate(struct sensors_poll_device_t *dev, int handle, int enabled) { struct hall_sensor_context_t* hall_dev = (struct hall_sensor_context_t*) dev; if (handle != SENSOR_HANDLE || (enabled != 0 && enabled != 1)) { return -EINVAL; } return 0; } static int hall_sensor_set_delay(struct sensors_poll_device_t *dev, int handle, int64_t ns) { struct hall_sensor_context_t* hall_dev = (struct hall_sensor_context_t*) dev; if (handle != SENSOR_HANDLE) { return -EINVAL; } return 0; } static int hall_sensor_poll(struct sensors_poll_device_t *dev, sensors_event_t* data, int count) { struct hall_sensor_context_t* hall_dev = (struct hall_sensor_context_t*) dev; ssize_t n = read(hall_dev->fd, &hall_dev->sensor_event.data[0], sizeof(float)); if (n < 0) { ALOGE("Failed to read hall sensor data: %s", strerror(errno)); return -errno; } hall_dev->sensor_event.timestamp = getTimestamp(); *data = hall_dev->sensor_event; return 1; } ``` 这个代码示例定义了一个名为`hall-sensor`的传感器,它返回磁场强度值。在`hall_sensor_open()`函数中,我们打开了`/dev/hall-sensor`设备文件,并初始化了一个`sensors_event_t`结构体来存储传感器事件。在`sensors_poll_device_t`结构体中,我们定义了传感器的`poll()`、`activate()`和`setDelay()`函数。在`hall_sensor_activate()`和`hall_sensor_set_delay()`函数中,我们简单地检查传入的`handle`参数是否是我们定义的传感器句柄,并返回0或-EINVAL。在`hall_sensor_poll()`函数中,我们从设备文件中读取传感器数据,并将其存储在先前初始化的`sensors_event_t`结构体中,最后返回1表示有一个新的传感器事件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值