一、概述

关于 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. SpringBoot 启动流程追踪(第二篇)

    上一篇文章分析了除 refresh 方法外的流程,并着重分析了 load 方法,这篇文章就主要分析 refresh 方法,可以说 refresh 方法是 springboot 启动流程最重要的一环,没 ...

  2. [ABC146F] Sugoroku

    2023-02-27 题目 题目传送门 翻译 翻译 难度&重要性(1~10):5 题目来源 AtCoder 题目算法 贪心 解题思路 对于第 ii 个点,只要到达 \(s_{i+1}\cdot ...

  3. 论文解读(IW-Fit)《Better Fine-Tuning via Instance Weighting for Text Classification》

    Note:[ wechat:Y466551 | 可加勿骚扰,付费咨询 ] 论文信息 论文标题:Better Fine-Tuning via Instance Weighting for Text Cl ...

  4. 《CTFshow-Web入门》05. Web 41~50

    @ 目录 web41 题解 原理 web42 题解 原理 web43 题解 原理 web44 题解 原理 web45 题解 原理 web46 题解 原理 web47 题解 web48 题解 web49 ...

  5. Spring Boot中自动装配机制的原理

    SpringBoot中自动装配机制的原理 1.自动装配,简单来说就是自动把第三方组件的Bean装载到Spring IOC容器里面,不需要开发人员再去写Bean的装配配置, 2.在Spring Boot ...

  6. Solution -「HNOI 2010」城市建设

    Description Link. 修改边权的动态 MST. Solution 讲清楚点. 修改边权的 MST,考虑对时间分治.设我们当前操作的操作区间是 \([l,r]\),直接暴力找 MST 是不 ...

  7. Teamcenter RAC开发 GoToHelper

    RAC开发,有时候会用到发送到我的Teamcenter 可以参考 com.teamcenter.rac.tcapps 包下 package com.teamcenter.rac.tracelinks; ...

  8. 各种SQL连接符Join

    一.连接符分类,内连接,外连接 1.内连接:Inner Join简写Join. 2.外连接:Left Outer Join 简写Left Join:Right Outer Join 简写Right J ...

  9. .Net核心级的性能优化(GC篇)

    1.前言 大部分人对于.Net性能优化,都停留在业务层面.或者简单的.Net框架配置层面.本篇来看下.Net核心部分GC垃圾回收配置:保留VM,大对象,独立GC,节省内存等.Net8里面有很多的各种G ...

  10. 别再吹捧什么区块链,元宇宙,Web3了,真正具有颠覆性的估计只有AI

    「感谢你阅读本文!」 别再吹捧什么区块链,元宇宙,Web3了,真正具有颠覆性的估计只有AI. 我们这个社会有这样一个特性,就是出现一个新事物,新概念,新技术,先不管是否真的现实,是否真的了解,第一件事 ...