前言

最近需要将之前的一些驱动接口转为安卓标准接口,方便上层应用或者第三方应用去适配。这篇文章先从简单的马达框架入手进行讲解。

正文

整个马达框架比较简单,安卓官方已经帮我们实现了framework到HAL层,我们需要实现的就只有驱动层。这篇文章我们梳理一下从上层到底层怎么流程。

1、APP层

import android.os.Vibrator;
import android.widget.ToggleButton;public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);private Vibrator vibrator=null;
vibrator=(Vibrator)this.getSystemService(VIBRATOR_SERVICE);
toggleButton1=(ToggleButton)findViewById(R.id.toggleButton1);
/*短震动*/
toggleButton1.setOnCheckedChangeListener(new ToggleButton.OnCheckedChangeListener() {@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(isChecked){Log.i(TAG,"toggleButton1 enter vibrator.vibrate");
//设置震动周期,第二个参数为 -1表示只震动一次
vibrator.vibrate(new long[]{1000, 10, 100, 1000},-1);
}else{
//取消震动Log.i(TAG,"toggleButton1 enter vibrator.cancel()");
vibrator.cancel();
}
}
});
}
}

上面展示了一个最简单的马达震动应用代码,获得服务后即可调用接口进行驱动。

2、framework层

代码路径:frameworks\base\services\core\java\com\android\server\VibratorService.java

    @Override // Binder callpublic void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint,IBinder token) {Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate");try {if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)!= PackageManager.PERMISSION_GRANTED) {throw new SecurityException("Requires VIBRATE permission");}if (token == null) {Slog.e(TAG, "token must not be null");return;}verifyIncomingUid(uid);if (!verifyVibrationEffect(effect)) {return;}// If our current vibration is longer than the new vibration and is the same amplitude,// then just let the current one finish.synchronized (mLock) {if (effect instanceof VibrationEffect.OneShot&& mCurrentVibration != null&& mCurrentVibration.effect instanceof VibrationEffect.OneShot) {VibrationEffect.OneShot newOneShot = (VibrationEffect.OneShot) effect;VibrationEffect.OneShot currentOneShot =(VibrationEffect.OneShot) mCurrentVibration.effect;if (mCurrentVibration.hasTimeoutLongerThan(newOneShot.getDuration())&& newOneShot.getAmplitude() == currentOneShot.getAmplitude()) {if (DEBUG) {Slog.d(TAG,"Ignoring incoming vibration in favor of current vibration");}return;}}// If the current vibration is repeating and the incoming one is non-repeating,// then ignore the non-repeating vibration. This is so that we don't cancel// vibrations that are meant to grab the attention of the user, like ringtones and// alarms, in favor of one-shot vibrations that are likely quite short.if (!isRepeatingVibration(effect)&& mCurrentVibration != null&& isRepeatingVibration(mCurrentVibration.effect)) {if (DEBUG) {Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");}return;}Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg);linkVibration(vib);long ident = Binder.clearCallingIdentity();try {doCancelVibrateLocked();startVibrationLocked(vib);addToPreviousVibrationsLocked(vib);} finally {Binder.restoreCallingIdentity(ident);}}} finally {Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);}}

接口里面会判断一下权限,根据应用层传递的不同effect值,有不同的震动效果。然后就调用到JNI层,调用顺序大概如下:

startVibrationLocked
startVibrationInnerLocked
doVibratorOn
vibratorOn

3、JNI层

代码路径:frameworks\base\services\core\jni\com_android_server_VibratorService.cpp

static void vibratorOn(JNIEnv* /* env */, jobject /* clazz */, jlong timeout_ms)
{Status retStatus = halCall(&V1_0::IVibrator::on, timeout_ms).withDefault(Status::UNKNOWN_ERROR);if (retStatus != Status::OK) {ALOGE("vibratorOn command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));}
}static void vibratorOff(JNIEnv* /* env */, jobject /* clazz */)
{Status retStatus = halCall(&V1_0::IVibrator::off).withDefault(Status::UNKNOWN_ERROR);if (retStatus != Status::OK) {ALOGE("vibratorOff command failed (%" PRIu32 ").", static_cast<uint32_t>(retStatus));}
}static const JNINativeMethod method_table[] = {{ "vibratorExists", "()Z", (void*)vibratorExists },{ "vibratorInit", "()V", (void*)vibratorInit },{ "vibratorOn", "(J)V", (void*)vibratorOn },{ "vibratorOff", "()V", (void*)vibratorOff },{ "vibratorSupportsAmplitudeControl", "()Z", (void*)vibratorSupportsAmplitudeControl},{ "vibratorSetAmplitude", "(I)V", (void*)vibratorSetAmplitude},{ "vibratorPerformEffect", "(JJ)J", (void*)vibratorPerformEffect}
};int register_android_server_VibratorService(JNIEnv *env)
{return jniRegisterNativeMethods(env, "com/android/server/VibratorService",method_table, NELEM(method_table));
}

以马达的On和off为例,会调用到HAL层的on和off方法。

4、HIDL层

代码路径:hardware\interfaces\vibrator\1.0\default\Vibrator.cpp

Return<Status> Vibrator::on(uint32_t timeout_ms) {int32_t ret = mDevice->vibrator_on(mDevice, timeout_ms);if (ret != 0) {ALOGE("on command failed : %s", strerror(-ret));return Status::UNKNOWN_ERROR;}return Status::OK;
}Return<Status> Vibrator::off()  {int32_t ret = mDevice->vibrator_off(mDevice);if (ret != 0) {ALOGE("off command failed : %s", strerror(-ret));return Status::UNKNOWN_ERROR;}return Status::OK;
}

HIDL层是较新的安卓版本才引入的,是连接HAL层和JNI层的桥梁。

5、HAL层

代码路径:hardware\libhardware\modules\vibrator\vibrator.c

static const char THE_DEVICE[] = "/sys/class/timed_output/vibrator/enable";static int sendit(unsigned int timeout_ms)
{char value[TIMEOUT_STR_LEN]; /* large enough for millions of years */snprintf(value, sizeof(value), "%u", timeout_ms);return write_value(THE_DEVICE, value);
}static int vibra_on(vibrator_device_t* vibradev __unused, unsigned int timeout_ms)
{/* constant on, up to maximum allowed time */return sendit(timeout_ms);
}static int vibra_off(vibrator_device_t* vibradev __unused)
{return sendit(0);
}static int vibra_open(const hw_module_t* module, const char* id __unused,hw_device_t** device __unused) {bool use_led;if (vibra_exists()) {ALOGD("Vibrator using timed_output");use_led = false;} else if (vibra_led_exists()) {ALOGD("Vibrator using LED trigger");use_led = true;} else {ALOGE("Vibrator device does not exist. Cannot start vibrator");return -ENODEV;}vibrator_device_t *vibradev = calloc(1, sizeof(vibrator_device_t));if (!vibradev) {ALOGE("Can not allocate memory for the vibrator device");return -ENOMEM;}vibradev->common.tag = HARDWARE_DEVICE_TAG;vibradev->common.module = (hw_module_t *) module;vibradev->common.version = HARDWARE_DEVICE_API_VERSION(1,0);vibradev->common.close = vibra_close;if (use_led) {vibradev->vibrator_on = vibra_led_on;vibradev->vibrator_off = vibra_led_off;} else {vibradev->vibrator_on = vibra_on;vibradev->vibrator_off = vibra_off;}*device = (hw_device_t *) vibradev;return 0;
}

其实开启和关闭马达的工作很简单,就是往节点"/sys/class/timed_output/vibrator/enable"写入震动时间,所以可以想得到驱动层只需要提供一个节点供上层操作就好。

6、驱动层

马达的驱动是基于kernel提供的timed_output框架完成的:

代码路径:kernel-4.4\drivers\staging\android\timed_output.c

代码比较简单,提供接口给驱动在"/sys/class/timed_output/"路径下面建立自己的节点,并提供节点的device attribute的操作接口,当我们写节点的时候就会调用到enable_store函数,并调用注册驱动的enable函数。

static struct class *timed_output_class;static ssize_t enable_store(struct device *dev, struct device_attribute *attr,const char *buf, size_t size)
{
struct timed_output_dev *tdev = dev_get_drvdata(dev);
int value;
int rc;rc = kstrtoint(buf, 0, &value);
if (rc != 0)
return -EINVAL;tdev->enable(tdev, value);return size;
}
static DEVICE_ATTR_RW(enable);static struct attribute *timed_output_attrs[] = {
&dev_attr_enable.attr,
NULL,
};
ATTRIBUTE_GROUPS(timed_output);static int create_timed_output_class(void)
{
if (!timed_output_class) {
timed_output_class = class_create(THIS_MODULE, "timed_output");
if (IS_ERR(timed_output_class))
return PTR_ERR(timed_output_class);
atomic_set(&device_count, 0);
timed_output_class->dev_groups = timed_output_groups;
}return 0;
}int timed_output_dev_register(struct timed_output_dev *tdev)
{
int ret;if (!tdev || !tdev->name || !tdev->enable || !tdev->get_time)
return -EINVAL;ret = create_timed_output_class();
if (ret < 0)
return ret;tdev->index = atomic_inc_return(&device_count);
tdev->dev = device_create(timed_output_class, NULL,
MKDEV(0, tdev->index), NULL, "%s", tdev->name);
if (IS_ERR(tdev->dev))
return PTR_ERR(tdev->dev);dev_set_drvdata(tdev->dev, tdev);
tdev->state = 0;
return 0;
}

现在我们看一下基于上面框架书写的马达驱动:

static void vibrator_off(void)
{
gpio_direction_output(gpio, !en_value);
wake_unlock(&vibdata.wklock); //震动关闭就可以释放 wake_lock锁
}void motor_enable(struct timed_output_dev *sdev,int value)
{
mutex_lock(&vibdata.lock); //关键代码段,同一时间只允许一个线程执行/* cancelprevious timer and set GPIO according to value */
hrtimer_cancel(&vibdata.timer); //当先前定时器完成后 关闭这个定时器
cancel_work_sync(&vibdata.work); //当上次震动完成后 关闭这次动作
if(value)
{
wake_lock(&vibdata.wklock); //开始震动打开wake lock锁不允许休眠
gpio_direction_output(gpio, en_value);if(value > 0)
{
if(value > MAX_TIMEOUT)
value= MAX_TIMEOUT;
hrtimer_start(&vibdata.timer,ktime_set(value / 1000, (value % 1000) * 1000000),HRTIMER_MODE_REL);
}
}
else
vibrator_off();mutex_unlock(&vibdata.lock);
}struct timed_output_dev motot_driver = {
.name ="vibrator", //注意这个名字,由于HAL层里面的设备为//"/sys/class/timed_output/vibrator/enable"//因此这个名字必须为"vibrator"
.enable= motor_enable,
.get_time= get_time,
};static enum hrtimer_restart vibrator_timer_func(struct hrtimer *timer) //定时器结束时候的回调函数
{
schedule_work(&vibdata.work); //定时器完成了 执行work队列回调函数来关闭电机
return HRTIMER_NORESTART;
}
static void vibrator_work(struct work_struct *work)
{
vibrator_off();
}static int motor_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
enum of_gpio_flags flags;
int ret =0;hrtimer_init(&vibdata.timer,CLOCK_MONOTONIC, HRTIMER_MODE_REL);
vibdata.timer.function= vibrator_timer_func;
INIT_WORK(&vibdata.work,vibrator_work);...ret=timed_output_dev_register(&motot_driver);
if (ret< 0)
goto err_to_dev_reg;
return 0;err_to_dev_reg:
mutex_destroy(&vibdata.lock);
wake_lock_destroy(&vibdata.wklock);printk("vibrator   err!:%d\n",ret);
return ret;}

1、 驱动接收上层传递过来的是震动时长,单位为毫秒。在驱动里注册一个定时器,定时器倒计时到期后会唤醒注册的工作队列,最终会执行vibrator_work()函数去关闭马达震动。

2、调用timed_output框架提供的timed_output_dev_register()接口将我们的马达驱动注册进系统,这里的关键就是我们需要自定义struct timed_output_dev结构体,填充enable和get_time函数。enable函数用来开启马达的震动:

void motor_enable(struct timed_output_dev *sdev,int value)
{
mutex_lock(&vibdata.lock); //关键代码段,同一时间只允许一个线程执行/* cancelprevious timer and set GPIO according to value */
hrtimer_cancel(&vibdata.timer); //当先前定时器完成后 关闭这个定时器
cancel_work_sync(&vibdata.work); //当上次震动完成后 关闭这次动作
if(value)
{
wake_lock(&vibdata.wklock); //开始震动打开wake lock锁不允许休眠
gpio_direction_output(gpio, en_value);if(value > 0)
{
if(value > MAX_TIMEOUT)
value= MAX_TIMEOUT;
hrtimer_start(&vibdata.timer,ktime_set(value / 1000, (value % 1000) * 1000000),HRTIMER_MODE_REL);
}
}
else
vibrator_off();mutex_unlock(&vibdata.lock);
}

开启震动的操作也很简单,只是写一下GPIO,然后重新开启定时器,倒计时的时间就是写入节点的值,到时间再把马达关闭就好。

参考链接

https://blog.csdn.net/qq_34211365/article/details/105556842

嵌入式Linux

微信扫描二维码,关注我的公众号

安卓9.0马达框架分析相关推荐

  1. Android 8.0 RIL框架分析

    原文地址:https://blog.csdn.net/qq_27540925/article/details/79356799 前言 Android O的版本对RIL的框架的通信功能进行了改动,不在使 ...

  2. android6.0分屏插件,xposed分屏模块安卓6.0下载

    安卓6.0系统分屏软件(xposed分屏插件)是一款支持分屏多任务软件,具有多窗口/双窗口功能,在众多智能分屏app中算是比较好用的啦,推荐给有需要的用户下载使用! 安卓6.0多窗口分屏软件简介 XH ...

  3. 安卓5.0以上设备最简单激活XPOSED框架的步骤

    对于喜欢研究手机的机友而言,经常会使用到Xposed框架及其各类功能强悍的模块,对于5.0以下的系统版本,只要手机能获得ROOT权限,安装和激活Xposed框架是非常简单的,但随着系统版本的持续更新, ...

  4. 【Codecs系列】SVAC1.0标准解读-----整体框架分析

    Date: 2017/9/11 目录 一.SVAC1.0标准介绍 二.SVAC1.0解码整体框架分析 三.SVAC1.0解码器架构分析 四.标准PDF下载地址 一.SVAC1.0标准介绍 SVAC(S ...

  5. 玩安卓从 0 到 1 之首页框架搭建

    前言 这篇文章是这个系列的第三篇文章了,前两篇文章分别是玩安卓从 0 到 1 之总体概览和玩安卓从 0 到 1 之项目首页. 一开始想的是一篇文章搞定,从项目的搭建到完成把所有的知识点写一遍,努力不做 ...

  6. JMAG.DESIGNER.16.0.WIN.LINUX.X64 马达电磁分析

    JMAG.DESIGNER.16.0.WIN.LINUX.X64 马达电磁分析  MicroSurvey.STARNET.v8.2.3.4253 MIDAS.NFX.2016.R1.20161018 ...

  7. 安卓6.0系统机器最简单激活XPOSED框架的流程

    对于喜欢研究手机的机友而言,常常会使用到XPOSED框架及各种功能强悍的模块,对于5.0以下的系统版本,只要手机能获得Root权限,安装和激活XPOSED框架是比较简便的,但随着系统版本的不断升级,5 ...

  8. 安卓8.0以上系统如何没root激活xposed框架的步骤

    在非常多企业的引流,或业务操作中,理论上需要使用安卓的黑高科技术XPOSED框架,这段时间,我们企业买来了一批新的安卓8.0以上系统,理论上都是基于7.0以上系统版本,理论上不能够获得root的su超 ...

  9. 安卓5.0以上机器一键激活Xposed框架的步骤

    对于喜欢搞机的伙伴而言,很多时候会使用上Xposed框架以及各种功能彪悍的模块,对于5.0以下的系统版本,只要手机能获得ROOT权限,安装和激活Xposed框架是比较简易的,但随着系统版本的不断迭代, ...

最新文章

  1. Ubuntu Mysql 数据库和表的操作   [2P]
  2. Java class.forname 功能介绍
  3. linux 故障监控必备五款软件
  4. JSTL 及 tablibs 的简单介绍和配置方法
  5. Java数组和Arrays类
  6. maven项目中测试代码
  7. Python之集合、解析式,生成器,函数
  8. 工作中应用计算机,浅谈计算机在我国计工作中的应用与发展.doc
  9. 机器不学习:初识迁移学习
  10. (22)Python-builtins-sorted()函数用法
  11. CDN架构原理、流量模型、网络调优
  12. ESP8266WiFi模块的使用以及arduino调试
  13. 如何用Camtasia制作简单动画?
  14. linux禁用ssh弱加密算法,ssh弱加密算法漏洞修复
  15. nginx 搭建静态网站
  16. 涉密计算机外送维修,涉密计算机送修
  17. 【二、八、十、十六】进制转换详解
  18. Excel切片器的使用
  19. 网页聊天气泡效果实现
  20. 如何划分用户生命周期?

热门文章

  1. OC之ARC环境中的循环strong问题
  2. ArrayList的使用方法【转载】
  3. notepad编译java_Notepad++直接编译运行java代码的具体步骤
  4. kafka 异常:ERROR Failed to clean up log for __consumer_offsets-30 in dir /tmp/kafka-logs due to IOExce
  5. Docker 精通之 Dockerfile
  6. 【机器学习】熵、决策树、随机森林 总结
  7. python中tkinter的使用-中
  8. 博客地址 RSS地址
  9. html标签api,html5新增标签+API介绍
  10. mysql和web文件夹_Linux使用记录---自动备份MySQL和web文件夹到windows共享路径