一、概述

关于 RN6752V1 这个芯片这里就不做介绍了,看到这篇笔记的小伙伴应该都明白,虽然说 RN6752V1 芯片是 AHD 信号的解码芯片,但是也可以把芯片当做是一个 YUV 信号的 MIPI 摄像头,所以驱动的编写和 MIPI 摄像头无太大的区别。这里主要是介绍具体的函数,关于 MIPI 驱动的框架程序看我之前的笔记:Linux MIPI 摄像头驱动框架编写(RN6752解码芯片)

二、RN6752 帧格式

RN6752 支持 DVP 和 MIPI 信号,这里我主要是对 MIPI 信号的使用,当然 DVP 通信的操作也可以做参考。

  1. 寄存地配置

    通过代理商提供的头文件中可以获取到相关寄存器的配置,如下所示:

    static const struct sensor_register rn6752_fhd_1080P25_video[] = {
    { 0x19, 0x0A }, // 视频格式检测滞后控制
    { 0x81, 0x01 }, // 打开视频解码器
    { 0xDF, 0xFE }, // 启用HD格式
    { 0xF0, 0xC0 }, // 使能 FIFO 和 144 MHz 解码器输出
    { 0xA3, 0x04 }, // 启用 HD 输出
    { 0x88, 0x40 }, // 禁用 SCLK1 输出
    { 0xF6, 0x40 }, // 禁用 SCLK3A 输出 /* 切换到ch0(默认;可选) */
    { 0xFF, 0x00 }, // 寄存器集选择
    { 0x33, 0x10 }, // 检测中的视频
    { 0x4A, 0xA8 }, // 检测中的视频
    { 0x00, 0x20 }, // internal use*
    { 0x06, 0x08 }, // internal use*
    { 0x07, 0x63 }, // 高清格式
    { 0x2A, 0x01 }, // 滤波器控制
    { 0x3A, 0x24 }, // 在SAV/EAV代码中插入通道ID
    { 0x3F, 0x10 }, // 通道ID
    { 0x4C, 0x37 }, // 均衡器
    { 0x4F, 0x03 }, // 同步控制
    { 0x50, 0x03 }, // 1080p分辨率
    { 0x56, 0x02 }, // 144M 和 BT656模式
    { 0x5F, 0x44 }, // 消隐电平
    { 0x63, 0xF8 }, // 滤波器控制
    { 0x59, 0x00 }, // 扩展寄存器存取
    { 0x5A, 0x48 }, // 扩展寄存器的数据
    { 0x58, 0x01 }, // 启用扩展寄存器写入
    { 0x59, 0x33 }, // 扩展寄存器存取
    { 0x5A, 0x23 }, // 扩展寄存器的数据
    { 0x58, 0x01 }, // 启用扩展寄存器写入
    { 0x51, 0xF4 }, // 比例因子1
    { 0x52, 0x29 }, // 比例因子2
    { 0x53, 0x15 }, // 比例因子3
    { 0x5B, 0x01 }, // H-标度控制
    { 0x5E, 0x08 }, // 启用H缩放控制
    { 0x6A, 0x87 }, // H-标度控制
    { 0x28, 0x92 }, // 剪裁
    { 0x03, 0x80 }, // 饱和
    { 0x04, 0x80 }, // 颜色
    { 0x05, 0x04 }, // 尖锐
    { 0x57, 0x23 }, // 黑色/白色拉伸
    { 0x68, 0x00 }, // coring
    { 0x37, 0x33 }, //
    { 0x61, 0x6C }, //
    #ifdef USE_BLUE_SCREEN
    { 0x3A, 0x24 }, // AHD 断开链接时,屏幕为蓝色
    #else
    { 0x3A, 0x2C }, // AHD 断开链接时,屏幕为黑色
    { 0x3B, 0x00 }, //
    { 0x3C, 0x80 }, //
    { 0x3D, 0x80 }, //
    #endif
    { 0x2E, 0x30 }, // 强制不播放视频
    { 0x2E, 0x00 }, // 回归平常 /* mipi 连接 */
    { 0xFF, 0x09 }, // 切换到 mipi tx1
    { 0x00, 0x03 }, // enable bias
    { 0xFF, 0x08 }, // 切换到 mipi csi1
    { 0x04, 0x03 }, // csi1 和 tx1 重置
    { 0x6C, 0x11 }, // 禁用 ch 输出,打开 ch0
    #ifdef USE_MIPI_4LANES
    { 0x06, 0x7C }, // mipi 4 线
    #else
    { 0x06, 0x4C }, // mipi 2 线
    #endif
    { 0x21, 0x01 }, // 启用 hs 时钟
    { 0x34, 0x06 }, //
    { 0x35, 0x0B }, //
    { 0x78, 0xC0 }, // ch0 的 Y/C 计数
    { 0x79, 0x03 }, // ch0 的 Y/C 计数
    { 0x6C, 0x01 }, // 启用 ch 输出
    { 0x04, 0x00 }, // csi1 和 tx1 重置完成
    { 0x20, 0xAA }, //
    #ifdef USE_MIPI_NON_CONTINUOUS_CLOCK
    { 0x07, 0x05 }, // 启用非连续时钟
    #else
    { 0x07, 0x04 }, // 启用连续时钟
    #endif
    { 0xFF, 0x0A }, // 切换到 mipi csi3
    { 0x6C, 0x10 }, // 禁用 ch 输出;关闭 ch0~3
    {REG_NULL, 0x00},
    };

    注意: 其他格式的寄存器我这里就不附上了,可以参考代理商提供的头文件

  2. 将配置信息存入帧列表中

    static const struct rn6752_framesize rn6752_mipi_framesizes[] = {
    {
    .width = 1280,
    .height = 720,
    .max_fps = {
    .numerator = 10000,
    .denominator = 250000,
    },
    .regs = rn6752_fhd_720P25_video,
    },
    {
    .width = 1280,
    .height = 720,
    .max_fps = {
    .numerator = 10000,
    .denominator = 300000,
    },
    .regs = rn6752_fhd_720P30_video,
    },
    {
    .width = 1920,
    .height = 1080,
    .max_fps = {
    .numerator = 10000,
    .denominator = 250000,
    },
    .regs = rn6752_fhd_1080P25_video,
    },
    {
    .width = 1920,
    .height = 1080,
    .max_fps = {
    .numerator = 10000,
    .denominator = 300000,
    },
    .regs = rn6752_fhd_1080P30_video,
    },
    {
    .width = 1280,
    .height = 960,
    .max_fps = {
    .numerator = 10000,
    .denominator = 250000,
    },
    .regs = rn6752_fhd_960P25_video,
    },
    {
    .width = 1280,
    .height = 960,
    .max_fps = {
    .numerator = 10000,
    .denominator = 300000,
    },
    .regs = rn6752_fhd_960P30_video,
    }
    };
  3. 配置默认帧

    在 rn6752_probe 函数中存入默认支持的帧列表,如下所示

    static void rn6752_get_default_format(struct rn6752 *rn6752,
    struct v4l2_mbus_framefmt *format)
    {
    format->width = rn6752->framesize_cfg[2].width; /* 设置默认宽度 */
    format->height = rn6752->framesize_cfg[2].height; /* 设置默认高度 */
    format->colorspace = V4L2_COLORSPACE_SRGB; /* 设置默认色彩空间为标准的 sRGB 色彩空间 */
    format->code = rn6752_formats[0].code; /* 设置默认编码格式 */
    format->field = V4L2_FIELD_NONE; /* 设置默认场模式 */
    } /* rn6752_mipi_framesizes 是 rn6752 mipi 通信支持的所有帧格式 */
    rn6752->framesize_cfg = rn6752_mipi_framesizes;
    rn6752->cfg_num = ARRAY_SIZE(rn6752_mipi_framesizes);
    /* 获取摄像头传感器支持的图像帧格式 */
    rn6752_get_default_format(rn6752, &rn6752->format);
    rn6752->frame_size = &rn6752->framesize_cfg[2]; /* 设置帧大小 */
    rn6752->format.width = rn6752->framesize_cfg[2].width; /* 设置宽度 */
    rn6752->format.height = rn6752->framesize_cfg[2].height; /* 设置高度 */
    rn6752->fps = DIV_ROUND_CLOSEST(
    rn6752->framesize_cfg[2].max_fps.denominator,
    rn6752->framesize_cfg[2].max_fps.numerator); /* 设置最大帧速率 */

    注意:

    • 首先将所有支持的帧列表存入了 rn6752->framesize_cfg 中
    • 将支持的列表数量存入 rn6752->cfg_num 中
    • 将默认支持的帧格式和大小存入 rn6752->format 中,这个在用户空间可以查看
    • 将默认支持的帧大小存入 rn6752->frame_size 中
    • 将默认支持的帧率存入 rn6752->fps 中
    • 以上这些默认变量将在后面的函数中经常用到,所以需要特别注意一下,不然很难理解数据从哪里来的

三、Media 设备节点

之前在 Media 子系统中提到过模块之间的关系查看命令media-ctl -p -d /dev/mediaX ,通过命令可以得到驱动中的一些信息,如下图所示

  1. Media 帧大小

    Media 帧大小是在驱动初始化时,通过 rn6752_get_fmt 函数获取的,程序如下

    static int rn6752_get_fmt(struct v4l2_subdev *sd,
    struct v4l2_subdev_pad_config *cfg,
    struct v4l2_subdev_format *fmt)
    {
    struct i2c_client *client = v4l2_get_subdevdata(sd); /* 获取i2c_client指针 */
    struct rn6752 *rn6752 = to_rn6752(sd); /* 使用dev_dbg打印日志,显示当前函数进入 */
    // dev_info(&client->dev, "%s enter\n", __func__); /* 条件成立时,表示要获取正在尝试的格式 */
    if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
    #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
    struct v4l2_mbus_framefmt *mf; /* 获取正在尝试的格式 */
    mf = v4l2_subdev_get_try_format(sd, cfg, 0);
    mutex_lock(&rn6752->lock);
    fmt->format = *mf;
    mutex_unlock(&rn6752->lock);
    return 0;
    #else
    return -ENOTTY;
    #endif
    } /* 条件不成立时,表示要获取当前的格式 */
    mutex_lock(&rn6752->lock);
    fmt->format = rn6752->format;
    mutex_unlock(&rn6752->lock); /* 使用dev_dbg打印日志,显示当前格式的代码值、宽度和高度 */
    dev_dbg(&client->dev, "%s: %x %dx%d\n", __func__, rn6752->format.code,
    rn6752->format.width, rn6752->format.height); return 0;
    }
  2. 帧格式判断

    Media 设备是通过 rn6752_enum_frame_sizes 和 rn6752_enum_frame_interval 函数枚举了帧大小和帧率,这两个函数主要起到判断的作用,确实当前帧率是否是驱动支持的,程序如下

    static int rn6752_enum_frame_sizes(struct v4l2_subdev *sd,
    struct v4l2_subdev_pad_config *cfg,
    struct v4l2_subdev_frame_size_enum *fse)
    {
    struct rn6752 *rn6752 = to_rn6752(sd);
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    int i = ARRAY_SIZE(rn6752_formats);
    printk(KERN_INFO
    "rn6752_enum_frame_sizes................................................\n"); dev_dbg(&client->dev, "%s:\n", __func__); if (fse->index >= rn6752->cfg_num)
    return -EINVAL; while (--i)
    if (fse->code == rn6752_formats[i].code)
    break; fse->code = rn6752_formats[i].code; fse->min_width = rn6752->framesize_cfg[fse->index].width;
    fse->max_width = fse->min_width;
    fse->max_height = rn6752->framesize_cfg[fse->index].height;
    fse->min_height = fse->max_height;
    return 0;
    } static int rn6752_enum_frame_interval(struct v4l2_subdev *sd,
    struct v4l2_subdev_pad_config *cfg,
    struct v4l2_subdev_frame_interval_enum *fie)
    {
    struct rn6752 *rn6752 = to_rn6752(sd);
    printk(KERN_INFO
    "rn6752_enum_frame_interval index: %d....................\n", fie->index ); /* 检查传入的 fie 结构体中的 index 字段是否超出了 rn6752 所支持的帧间隔配置数量(cfg_num) */
    if (fie->index >= rn6752->cfg_num)
    return -EINVAL; /* 检查传入的 fie 结构体中的 code 字段是否与期望的媒体总线格式(MEDIA_BUS_FMT_UYVY8_2X8)匹配 */
    if (fie->code != MEDIA_BUS_FMT_UYVY8_2X8)
    return -EINVAL; fie->width = rn6752->framesize_cfg[fie->index].width; /* 宽 */
    fie->height = rn6752->framesize_cfg[fie->index].height; /* 高 */
    fie->interval = rn6752->framesize_cfg[fie->index].max_fps; /* 最大帧率 */ return 0;
    }
  3. 帧大小设置

    可以通过 rn6752_set_fmt 函数设置帧的大小,程序如下

    tatic int rn6752_set_fmt(struct v4l2_subdev *sd,
    struct v4l2_subdev_pad_config *cfg,
    struct v4l2_subdev_format *fmt)
    {
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    int index = ARRAY_SIZE(rn6752_formats);
    struct v4l2_mbus_framefmt *mf = &fmt->format;
    const struct rn6752_framesize *size = NULL;
    struct rn6752 *rn6752 = to_rn6752(sd);
    printk(KERN_INFO
    "rn6752_set_fmt................................................\n"); dev_info(&client->dev, "%s enter\n", __func__); /* 根据传入的参数调整帧大小和帧速率,并返回适合的帧大小和帧速率 */
    __rn6752_try_frame_size_fps(rn6752, mf, &size, rn6752->fps); /* 遍历rn6752_formats数组 */
    while (--index >= 0)
    if (rn6752_formats[index].code == mf->code)
    break; if (index < 0)
    return -EINVAL; /* 色彩空间为sRGB,场为无 */
    mf->colorspace = V4L2_COLORSPACE_SRGB;
    mf->code = rn6752_formats[index].code;
    mf->field = V4L2_FIELD_NONE; mutex_lock(&rn6752->lock); if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
    #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
    mf = v4l2_subdev_get_try_format(
    sd, cfg,
    fmt->pad); /* 使用v4l2_subdev_get_try_format函数获取正在尝试的格式 */
    *mf = fmt->format;
    #else
    return -ENOTTY;
    #endif
    } else {
    if (rn6752->streaming) {
    mutex_unlock(&rn6752->lock);
    return -EBUSY;
    } /* 分别设置为获取到的帧大小和传入的格式 */
    rn6752->frame_size = size;
    rn6752->format = fmt->format;
    } mutex_unlock(&rn6752->lock); return 0;
    }
  4. 帧间隔获取

    static int rn6752_g_frame_interval(struct v4l2_subdev *sd,
    struct v4l2_subdev_frame_interval *fi)
    {
    struct rn6752 *rn6752 = to_rn6752(sd);
    printk(KERN_INFO
    "rn6752_g_frame_interval................................................\n"); mutex_lock(&rn6752->lock);
    fi->interval = rn6752->frame_size->max_fps;
    mutex_unlock(&rn6752->lock);
    return 0;
    }

四、总线编码格式

之前有提到过,RN6752 支持 DVP 和 MIPI 总线格式,所以可以在一个驱动中实现两个功能,这里我就是写了 MIPI 的通信方式,我目前对 DVP 也不了解,以后在补上。

刚好驱动中提供了两个函数可以获取驱动总线的格式,如下所示

  1. 获取当前媒体总线配置的函数

    static int rn6752_g_mbus_config(struct v4l2_subdev *sd,
    struct v4l2_mbus_config *config)
    {
    printk(KERN_INFO
    "rn6752_g_mbus_config................................................\n"); /* 总线类型是CSI-2 */
    config->type = V4L2_MBUS_CSI2;
    config->flags = V4L2_MBUS_CSI2_4_LANE | V4L2_MBUS_CSI2_CHANNEL_0 |
    V4L2_MBUS_CSI2_CHANNEL_1 |
    V4L2_MBUS_CSI2_CONTINUOUS_CLOCK; return 0;
    }
  2. 枚举所有支持的媒体总线编码和格式

    static int rn6752_enum_mbus_code(struct v4l2_subdev *sd,
    struct v4l2_subdev_pad_config *cfg,
    struct v4l2_subdev_mbus_code_enum *code)
    {
    struct i2c_client *client = v4l2_get_subdevdata(sd);
    printk(KERN_INFO
    "rn6752_enum_mbus_code................................................\n"); dev_dbg(&client->dev, "%s:\n", __func__); if (code->index >= ARRAY_SIZE(rn6752_formats))
    return -EINVAL; code->code = rn6752_formats[code->index].code;
    return 0;
    }

五、电源管理

摄像头每次开启和关闭时,都需要通过电源管理函数配置摄像头电源

static int rn6752_power(struct v4l2_subdev *sd, int on)
{
struct rn6752 *rn6752 = to_rn6752(sd);
struct i2c_client *client = rn6752->client;
int ret = 0; /* 使用dev_info打印日志,显示当前函数和行号,并打印on参数的值 */
dev_dbg(&client->dev, "%s(%d) on(%d)\n", __func__, __LINE__, on); mutex_lock(&rn6752->lock); if (rn6752->power_on == !! on)
goto unlock_and_return; if (on) {
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0) {
pm_runtime_put_noidle(&client->dev);
goto unlock_and_return;
}
rn6752->power_on = true;
} else {
pm_runtime_put(&client->dev);
rn6752->power_on = false;
} unlock_and_return:
mutex_unlock(&rn6752->lock); return ret;
}

六、摄像头控制

由于这里我没有实现太多的控制功能,所以只实现了必要的两个控制,最主要的是复位时执行的 RKMODULE_SET_QUICK_STREAM 功能

static long rn6752_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg)
{
struct rn6752 *rn6752 = to_rn6752(sd);
// struct rkmodule_hdr_cfg *hdr;
long ret = 0;
u32 stream = 0; // dev_dbg(KERN_INFO "rn6752_ioctl 0x%x..........\n", cmd); switch (cmd) {
case RKMODULE_GET_MODULE_INFO:
rn6752_get_module_info(rn6752, (struct rkmodule_inf *)arg);
break;
case RKMODULE_SET_QUICK_STREAM:
stream = *((u32 *)arg); rn6752_set_streaming(rn6752, !!stream);
break; default:
ret = -ENOIOCTLCMD;
break;
}
return ret;
} #ifdef CONFIG_COMPAT
static long rn6752_compat_ioctl32(struct v4l2_subdev *sd, unsigned int cmd,
unsigned long arg)
{
void __user *up = compat_ptr(arg);
struct rkmodule_inf *inf;
struct rkmodule_awb_cfg *cfg;
long ret;
u32 stream = 0; // dev_dbg(KERN_INFO "rn6752_compat_ioctl32..........\n"); switch (cmd) {
case RKMODULE_GET_MODULE_INFO:
inf = kzalloc(sizeof(*inf), GFP_KERNEL);
if (!inf) {
ret = -ENOMEM;
return ret;
} ret = rn6752_ioctl(sd, cmd, inf);
if (!ret)
ret = copy_to_user(up, inf, sizeof(*inf));
kfree(inf);
break;
case RKMODULE_AWB_CFG:
cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
if (!cfg) {
ret = -ENOMEM;
return ret;
} ret = copy_from_user(cfg, up, sizeof(*cfg));
if (!ret)
ret = rn6752_ioctl(sd, cmd, cfg);
kfree(cfg);
break;
case RKMODULE_SET_QUICK_STREAM:
ret = copy_from_user(&stream, up, sizeof(u32));
if (!ret)
ret = rn6752_ioctl(sd, cmd, &stream);
break;
default:
ret = -ENOIOCTLCMD;
break;
}
return 0;
}
#endif

七、数据流控制

整个驱动最重要的便是流控制函数,通过此函数完成了摄像头的启动和停止

static int rn6752_set_streaming(struct rn6752 *rn6752, int on)
{
struct i2c_client *client = rn6752->client;
int ret = 0; dev_info(&client->dev, "%s: on: %d\n", __func__, on); if (on)
{
ret = rn6752_write(client, 0x80, 0x31);
usleep_range(200, 500);
ret |= rn6752_write(client, 0x80, 0x30);
if (ret)
{
dev_err(&client->dev, "rn6752 soft reset failed\n");
return ret;
} ret = rn6752_write_array(client, rn6752->frame_size->regs);
if (ret)
dev_err(&client->dev, "rn6752 start initialization failed\n");
}
else
{
ret = rn6752_write(client, 0x80, 0x00);
if (ret)
dev_err(&client->dev, "rn6752 soft standby failed\n"); }
return ret;
} static int rn6752_s_stream(struct v4l2_subdev *sd, int on)
{
struct i2c_client *client = v4l2_get_subdevdata(sd);
struct rn6752 *rn6752 = to_rn6752(sd);
int ret = 0;
unsigned int fps; /* 计算帧率和延迟时间 */
fps = DIV_ROUND_CLOSEST(rn6752->frame_size->max_fps.denominator,
rn6752->frame_size->max_fps.numerator); dev_info(&client->dev, "%s: on: %d, %dx%d@%d\n", __func__, on,
rn6752->frame_size->width, rn6752->frame_size->height,
DIV_ROUND_CLOSEST(rn6752->frame_size->max_fps.denominator,
rn6752->frame_size->max_fps.numerator)); mutex_lock(&rn6752->lock); on = !!on; if (rn6752->streaming == on)
goto unlock; if (on) {
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0)
{
pm_runtime_put_noidle(&client->dev);
goto unlock;
}
} rn6752->streaming = on;
ret = rn6752_set_streaming(rn6752, on);
if (ret)
rn6752->streaming = !on; pm_runtime_put(&client->dev); unlock:
mutex_unlock(&rn6752->lock);
return ret;
}

注意: 摄像头驱动中并没有图像接收之类的关系,而数据流操作函数主要的作用是对芯片进行初始化,使摄像头进入工作模式。从上面的驱动程序可以看出,整个驱动并没有其他特别的功能,就是一个 I2C 控制功能,所以摄像头的驱动其实就是一个 I2C 驱动程序。

由于笔记内容有点多,这里我就不附上完成的驱动程序了,其次是驱动程序也比较简单,看完的小伙伴应该都能明白。主要的难度都在调试摄像头驱动上面,我也折腾了很久,有需要的小伙变可以看我后面的笔记

参考资料

gc2145.c 和 imx335.c 驱动程序

Linux RN6752 驱动编写的更多相关文章

  1. linux设备驱动编写入门

    linux设备驱动是什么,我个人的理解是liunx有用户态和内核态,用户空间中是不能直接对设备的外设进行使用而内核态中却可以,这时我们需要在内核空间中将需要的外设驱动起来供用户空间使用.linux的驱 ...

  2. linux设备驱动编写_tasklet机制

    在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成. 为了最大程度的避免中断处理时间过长而导致中断丢失,有时 ...

  3. linux设备驱动编写_tasklet机制(转)

    在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成. 为了最大程度的避免中断处理时间过长而导致中断丢失,有时 ...

  4. v4l2驱动编写篇【转】

    转自:http://blog.csdn.net/michaelcao1980/article/details/53008418 大部分所需的信息都在这里.作为一个驱动作者,当挖掘头文件的时候,你可能也 ...

  5. Linux驱动之按键驱动编写(中断方式)

    在Linux驱动之按键驱动编写(查询方式)已经写了一个查询方式的按键驱动,但是查询方式太占用CPU,接下来利用中断方式编写一个驱动程序,使得CPU占有率降低,在按键空闲时调用read系统调用的进程可以 ...

  6. linux驱动编写之中断处理

    一.中断 1.概念 学过单片机的应该非常清楚中断的概念,也就是CPU在正常执行程序过程中,出现了突发事件(中断事件),于是CPU暂停当前程序的执行,转去处理突发事件.处理完毕后,CPU又返回被中断的程 ...

  7. Linux驱动之按键驱动编写(查询方式)

    在Linux驱动之LED驱动编写已经详细介绍了一个驱动的编写过程,接着来写一个按键驱动程序,主要是在file_operations结构中添加了一个read函数.还是分以下几步说明 1.查看原理图,确定 ...

  8. Linux驱动:I2C驱动编写要点

    继续上一篇博文没讲完的内容“针对 RepStart 型i2c设备的驱动模型”,其中涉及的内容有:i2c_client 的注册.i2c_driver 的注册.驱动程序的编写. 一.i2c 设备的注册分析 ...

  9. Linux驱动:SPI驱动编写要点

    题外话:面对成功和失败,一个人有没有“冠军之心”,直接影响他的表现. 几周前剖析了Linux SPI 驱动框架,算是明白个所以然,对于这么一个庞大的框架,并不是每一行代码都要自己去敲,因为前人已经把这 ...

  10. ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程

    ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程 原文地址:http://www.cnblogs.com/NickQ/p/9026545.html 一.开发板与ds18b20的入门 ...

随机推荐

  1. [docker]安装常见数据库

    前言 本文使用docker安装常见数据库大部分没配置什么参数,只是基本的安装. 不只是数据库,还有elasticsearch.rabbitmq等和数据相关的服务. docker 版本: 18.06.3 ...

  2. elasticsearch中的数据类型search_as_you_type及查看底层Lucene索引

    search_as_you_type字段类型用于自动补全,当用户输入搜索关键词的时候,还没输完就可以提示用户相关内容.as_you_type应该是说当你打字的时候.它会给索引里的这个类型的字段添加一些 ...

  3. 10、Mybatis之缓存

    10.1.环境搭建 10.1.1.创建新module 创建名为mybatis_cache的新module,过程参考5.1节 10.1.2.创建Mapper接口和映射文件 package org.rai ...

  4. 2023-09-03:用go编写。给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1 给你整数 n 和一个长度为 n - 1 的二维整数数组 edges , 其中 edges[i] =

    2023-09-03:用go语言编写.给你一个 n 个节点的无向无根树,节点编号从 0 到 n - 1 给你整数 n 和一个长度为 n - 1 的二维整数数组 edges , 其中 edges[i] ...

  5. shell、python时间函数小结

    有时需要写一些定时任务脚本,简单总结一下,备忘. 1. 获取当前时间 python 在windows下精确到0.001秒,linux下时间精度为0.000001秒 >>> impor ...

  6. MD5&MD5盐值加密到BCryptPasswordEncoder

    MD5&MD5盐值加密 Message Digest algorithm5,信息摘要算法: 压缩性:任意长度的数据,算出的MD5值长度都是固定的 容易计算:从原数据计算出MD5值很容易 抗修改 ...

  7. 吴恩达人工智能-python实现逻辑回归

    吴恩达人工智能 逻辑回归python代码实现 逐行注释 import numpy as np import pandas as pd from matplotlib import pyplot as ...

  8. PostgreSQL学习笔记-6.基础知识:ALTER、TRUNCATE 、View(视图)、TRANSACTION 事务、LOCK 锁

    ALTER TABLE 命令 在 PostgreSQL 中,ALTER TABLE 命令用于添加,修改,删除一张已经存在表的列. 另外你也可以用 ALTER TABLE 命令添加和删除约束. 语法 用 ...

  9. hash code

    值相同却可能有不同的hashcode //对象值到底指什么?(x.equals(y) == true)应该并不代表对象值相同 class A { A(){} public boolean equals ...

  10. 使用playwright爬取魔笔小说网站并下载轻小说资源

    一.安装python 官网 下载python3.9及以上版本 二.安装playwright playwright是微软公司2020年初发布的新一代自动化测试工具,相较于目前最常用的Selenium,它 ...