当前位置: 首页 > news >正文

Linux驱动开发-①I2C驱动②spi驱动③uart驱动

Linux驱动开发-①I2C驱动②spi驱动③uart驱动

  • 一,I2C驱动
    • 1.AP3216C
    • 2.I2C设备不需要内存映射
  • 二,SPI驱动
    • 2.1 icm20608c
      • 2.1.1 实现原理
      • 2.1.2 工作流程
      • 2.1.3驱动实现
      • 2.1.4 相关问题
      • ① MMIO设备和I2C/SPI设备
      • ② read函数和write函数中,为什么采用kmalloc函数
      • ③DMA技术
      • ④cs-gpios
      • ⑤同步串行和异步串行
  • 三,UART驱动
    • 3.1 UART驱动
    • 3.2 关于 I2C、SPI、TTL、RS232、RS485和RS422
      • 3.2.1 TTL UART和RS232、RS485和RS422的关系
      • 3.2.2 RS485能一主多从,但是RS232只能点对点
      • 3.2.3 帧

一,I2C驱动

1.AP3216C

  类似于裸机驱动,将I2C总线具体处理分成一部分总线驱动(已经写好的),针对具体设备再分成一部分(使用时候,针对读写建立特定结构体,使用i2c_transfer函数进行设备和总线的读写工作)。
  总体思想:i2c_driver类似于platform结构,也需要prob函数和remove函数,需要建立of表匹配设备树中compatible属性,特点:①i2c_client描述设备信息结构体,包含i2c_adapter总线控制器,总线控制器结构体里面又有i2c_transfer函数,具体实现设备和总线之间的读写。②在probe函数中,将i2c_client传递到ap3216c_dev结构体中(全局变量中),从而能够在其他地方调用i2c_client的数据和i2c_transfer函数。
  驱动程序:①关键是i2c_driver框架的建立。②ap3216c_read_reg和ap3216c_write_reg函数的实现,即i2c_msg结构体变量内容的设置。

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>  // 包含 register_chrdev_region 的定义
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#include<linux/platform_device.h>
#include<linux/i2c.h>
#include<linux/delay.h>
#define AP3216C_NAME "ap3216c"
#define AP3216C_SYSTEMCONG 0x00 /* 配置寄存器 */
#define AP3216C_INTSTATUS 0X01 /* 中断状态寄存器 */
#define AP3216C_INTCLEAR 0X02 /* 中断清除寄存器 */
#define AP3216C_IRDATALOW 0x0A /* IR 数据低字节 */
#define AP3216C_IRDATAHIGH 0x0B /* IR 数据高字节 */
#define AP3216C_ALSDATALOW 0x0C /* ALS 数据低字节 */
#define AP3216C_ALSDATAHIGH 0X0D /* ALS 数据高字节 */
#define AP3216C_PSDATALOW 0X0E /* PS 数据低字节 */
#define AP3216C_PSDATAHIGH 0X0F /* PS 数据高字节 */
struct ap3216c_dev{
    struct class *class;
    struct device *device;
    struct cdev cdev;
    dev_t ap3216c_hao;
    int major;//主设备号
    int minor;//次设备号
    struct i2c_client *private_date;
    unsigned short ir,ps,als;

};
struct ap3216c_dev ap3216c;
static u8 ap3216c_read_reg(struct ap3216c_dev *ap3216c_read, u8 reg)
{
	int ret;
    struct i2c_client *client = ap3216c.private_date;//传递数据
	struct i2c_msg msg[2];
    u8 buf;
	msg[0].addr  = client->addr;//设备地址
	msg[0].flags = 0;//写操作
	msg[0].len   = 1;
	msg[0].buf   = &reg;//寄存器

	msg[1].addr  = client->addr;//设备地址
	msg[1].flags = I2C_M_RD;//读
	msg[1].len   = 1;
	msg[1].buf   = &buf;//把最终读到的数据放到buff中

	ret = i2c_transfer(client->adapter, msg, 2);
	if (ret < 0) {
		printk(KERN_ERR "error %d\n", ret);
		buf = 0xff;
	}
	return buf;
}
static void ap3216c_write_reg(struct ap3216c_dev *ap3216c_write, 
                            u8 reg,u8 data)
{
    u8 b[3];
    struct i2c_client *client = ap3216c.private_date;//传递数据
	struct i2c_msg msg[1];
    b[0]=reg;//寄存器地址放在首位
    memcpy(&b[1],&data,1);//每次写入一个数据

	msg[0].addr  = client->addr;//设备地址
	msg[0].flags = 0;//写操作
	msg[0].len   = 2;//长度为2
	msg[0].buf   = b;//b中放的先是寄存器地址,后面是数据

	i2c_transfer(client->adapter, msg, 1);
}
/*读写数据操作*/
void ap3216c_read_data(struct ap3216c_dev *dev)
{
    unsigned char buf[6]={0};
    int i =0;
    for(i=0;i<6;i++)
    {
        buf[i] = ap3216c_read_reg(dev,AP3216C_IRDATALOW+i);
    }
    if(buf[0] & 0X80) /* IR_OF 位为 1,则数据无效 */
    dev->ir = 0;
    else /* 读取 IR 传感器的数据 */
    dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
    dev->als = ((unsigned short)buf[3] << 8) | buf[2];/* ALS 数据 */

    if(buf[4] & 0x40) /* IR_OF 位为 1,则数据无效 */
    dev->ps = 0;
    else /* 读取 PS 传感器的数据 */
    dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) |(buf[4] & 0X0F);
}
static int ap3216c_open(struct inode *innode,struct file *filp)
{
    unsigned char result = 0;
    filp->private_data = &ap3216c;//将ap3216c结构体数据设为私有数据
    ap3216c_write_reg(&ap3216c,AP3216C_SYSTEMCONG,0x04);//写入寄存器数据
    mdelay(50);
    ap3216c_write_reg(&ap3216c,AP3216C_SYSTEMCONG,0x03);//写入寄存器数据
    result =ap3216c_read_reg(&ap3216c, AP3216C_SYSTEMCONG);
    printk("AP3216C_SYSTEMCONG = %d \r\n",result);
    return 0;
}
static int ap3216c_release(struct inode *innode,struct file *filp)
{  

    return 0;
}
static ssize_t ap3216c_read(struct file *filp, char __user *buf,size_t cnt,loff_t *offt)
{
    int ret = 0;
    unsigned short buff[3];
    ap3216c_read_data(&ap3216c);
    buff[0]=ap3216c.ir;
    buff[1]=ap3216c.als;
    buff[2]=ap3216c.ps;
    ret = __copy_to_user(buf,buff,sizeof(buff));

    return ret;
}
static ssize_t ap3216c_write(struct file *filp, const char __user *buf,size_t cnt,loff_t *offt)
{
    
    return 0;
}
static struct file_operations ap3216c_fops={
    .owner=THIS_MODULE,
    .read=ap3216c_read,
    .write=ap3216c_write,
    .open=ap3216c_open,
    .release=ap3216c_release,
};
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    /*注册*/
    /*1.设备号*/
    if(ap3216c.major)
    {
        ap3216c.ap3216c_hao = MKDEV(ap3216c.major,0);
        register_chrdev_region(ap3216c.ap3216c_hao, 1, AP3216C_NAME);//主动注册
    }else{
        alloc_chrdev_region(&ap3216c.ap3216c_hao, 0, 1, AP3216C_NAME);//自动注册
    }
    printk("major = %d,minor = %d",MAJOR(ap3216c.ap3216c_hao),MINOR(ap3216c.ap3216c_hao));

    /*2.注册函数*/
    ap3216c.cdev.owner = THIS_MODULE;
    cdev_init(&ap3216c.cdev,&ap3216c_fops);
    cdev_add(&ap3216c.cdev,ap3216c.ap3216c_hao,1);

    /*3.节点申请*/ 
    ap3216c.class = class_create(THIS_MODULE,AP3216C_NAME);
    ap3216c.device = device_create(ap3216c.class, NULL,ap3216c.ap3216c_hao, NULL,AP3216C_NAME);
    
    ap3216c.private_date = client;//传递数据
    return 0;
}
static int ap3216c_remove(struct i2c_client *client)
{

    printk("exit in linux\r\n");
    cdev_del(&ap3216c.cdev);//先删除设备
    unregister_chrdev_region(ap3216c.ap3216c_hao,1);//删除设备号
    device_destroy(ap3216c.class,ap3216c.ap3216c_hao);//先删除和设备关系
    class_destroy(ap3216c.class);//再删除类
    return 0;
}
static const struct i2c_device_id ap3216c_id[] = {
	{ "ap3216c", 0 },
	{ }
};
const struct of_device_id	ap3216c_of_match[]={
    {.compatible = "wyt-ap3216c"},
    { /*sentinel*/ },
};
static struct i2c_driver ap3216c_driver = {
	.probe		= ap3216c_probe,
	.remove		= ap3216c_remove,
	.driver		= {
        .owner = THIS_MODULE,
		.name	= "ap3216c666",//1.无设备树,匹配名字
        .of_match_table = ap3216c_of_match,//2.有设备树,直接利用设备树中compatible属性
	},
    .id_table = ap3216c_id,
};
static int __init ap3216c_init(void)
{ 
    int ret = 0;
    ret = i2c_add_driver(&ap3216c_driver);
    return ret;
}
static void  __exit ap3216c_exit(void)
{
    i2c_del_driver(&ap3216c_driver);  
}
/*驱动入口和出口*/
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyt");



2.I2C设备不需要内存映射

  简单来说,I2C设备就不在内存中,它接的是具体硬件,那还怎么会需要地址映射呢,直接对设备地址和寄存器地址进行操作就行了(访问通过数据包传输,而非直接读写内存地址)。
在这里插入图片描述

二,SPI驱动

2.1 icm20608c

2.1.1 实现原理

  流程:spi驱动包含spi控制器驱动和spi设备驱动,控制器部分驱动已经做好,也就是说在设备驱动中可以调用其中的读写函数,实现数据的具体读写。同样和i2c设备驱动差别不大,spi设备驱动也类似于platform框架,具体操作包括:①在init和exit函数中进行注册和注销。②对spi_driver结构体变量的内容进行设置,主要包括of_match_table对compatible属性设置,probe和remove函数,name等设置。③在probe函数中实现设备号和节点的注册,以及设置spi工作模式设置,然后进行spi_setup注册。④读和写函数设置,spi_transfer结构体内数据的设置(想要写出的数据或者是准备接收数据要放的位置,用指针接收),最终的信息以spi_message形式,因此将spi_transfer添加到spi_message中。
  注意的是spi是全双工,因此发送数据的同时也在一直接收数据,比如想要读取某个寄存器的值,先发送读标志位加寄存器值(读标志位在最高位,为1,和j寄存器reg进行或运算),发送这个1|reg的同时,也接收了一个数据,如果把接收数据放到指针data位置,那么data[0]这个数据其实并不是寄存器中的数据,data[1]才是寄存器中的数据。

2.1.2 工作流程

①主机拉低 CS 选择从机。
②主机生成时钟 SCLK,同时在 MOSI 上逐位发送数据。
③从机在 MISO 上同步返回数据(每个时钟周期同时收发一位)。
④传输完成后,主机拉高 CS。

2.1.3驱动实现

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>  // 包含 register_chrdev_region 的定义
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#include<linux/platform_device.h>
#include <linux/spi/spi.h>          // SPI 核心头文件(必须)
#include<linux/delay.h>
#include"spi_my.h"
#define IMC20608C_NAME  "icm20608c"
 struct icm20608c_dev {
    int major;
    int minor;
    dev_t ID;
    struct class *class;
    struct cdev cdev;
    struct device *device;
    struct device_node *nd;
    int gpio;//gpio
    struct spi_device *icm20608c_spi_private_data;
    signed short accel_x_adc,accel_y_adc,accel_z_adc,
    temp_adc,gyro_x_adc, gyro_y_adc,gyro_z_adc ;
};
static struct icm20608c_dev  icm20608c;
static void  icm20608c_read_regs(struct icm20608c_dev *dev,u8 reg,
                                    void *buf,int len)
{
    int ret = 0;
    unsigned char txdata[1];//发送的数据,即读标志位+reg寄存器
	unsigned char *rxdata;//设备中的数据,读到的
    struct spi_message m;
	struct spi_device *spi = dev->icm20608c_spi_private_data;
	struct spi_transfer *t ;
    // gpio_set_value(icm20608c.gpio,0);

    t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
    rxdata= kzalloc(len+1,GFP_KERNEL);
    if(!rxdata)
    {
        goto out_rxdata;
    }
    /*spi为全双工,放入读标志,并且放入寄存器值,说明要读取多少个数据,读到后放入rxdata中*/
    txdata[0]=reg|0x80;//读最高位为1 
    t->tx_buf =txdata;
    t->rx_buf = rxdata; //让两个指针=,指向同一个数据
    t->len = len+1;
	spi_message_init(&m);
	spi_message_add_tail(t, &m);
	ret = spi_sync(spi, &m);
    if(ret)
    {
        goto out_spi_sync;
    }
    // printk(" read len  =%x\r\n",t->len);
    // printk(" read t->tx_buf  reg=%x\r\n",((uint8_t*)t->tx_buf)[0]);    
    // printk(" read t->rx_buf[0]=%x\r\n",((uint8_t*)t->rx_buf)[0]);
    // printk(" read t->rx_buf[1]=%x\r\n",((uint8_t*)t->rx_buf)[1]);
    // printk(" read t->rx_buf[2]=%x\r\n",((uint8_t*)t->rx_buf)[2]);
	memcpy(buf, rxdata+1, len);

out_spi_sync:
    kfree(rxdata);
out_rxdata:
    kfree(t);
    // gpio_set_value(icm20608c.gpio,1);
}
static void  icm20608c_write_regs(struct icm20608c_dev *dev,u8 reg,
                                    void *buf,int len)
{
    unsigned char *txdata;//发送的数据,第一个是标志位+reg寄存器,其于为写入第数据
	struct spi_device *spi = dev->icm20608c_spi_private_data;
    // gpio_set_value(icm20608c.gpio,0);
    txdata = kzalloc(len+1,GFP_KERNEL);
    txdata[0]=reg &~0x80;
    memcpy(&txdata[1],buf,len);
    spi_write(spi, txdata, len+1);
    kfree(txdata);
    // gpio_set_value(icm20608c.gpio,1);
}

static  unsigned  char icm20608c_read_one_reg(struct icm20608c_dev *dev,u8 reg)
{
    unsigned char result_one_reg;
    icm20608c_read_regs(&icm20608c,reg,&result_one_reg,1);
    return result_one_reg;
}
void icm20608c_write_one_reg66(struct icm20608c_dev *dev,u8 reg,unsigned char data)
{
    icm20608c_write_regs(&icm20608c,reg,&data,1);
}

static int icm20608c_release (struct inode *node , struct file *filp)
{
    return 0;
}
static int icm20608c_open (struct inode *node, struct file *filp)
{
    filp->private_data = &icm20608c;
    return 0;
}
/*没填写函数内部分*/
static 	ssize_t icm20608c_read (struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    int ret = 0;
    unsigned char data[14] = { 0 };
    signed int result[7] = {0};
    struct icm20608c_dev *dev = (struct icm20608c_dev*)filp->private_data;
    icm20608c_read_regs(dev,ICM20_ACCEL_XOUT_H,data,14);
    result[3] = (signed int)((data[0] << 8) | data[1]);
    result[4] = (signed int)((data[2] << 8) | data[3]);
    result[5] = (signed int)((data[4] << 8) | data[5]);

    result[6] = (signed int)((data[6] << 8) | data[7]); 

    result[1] = (signed int)((data[8] << 8) | data[9]);
    result[2] = (signed int)((data[10] << 8) | data[11]);   
    result[3] = (signed int)((data[12] << 8) | data[13]);
    ret = __copy_to_user(buf,result,sizeof(result));
    if(ret<0)
    {
        ret = -1;
    }
    return ret; 
}

/*文件操作函数*/
static struct file_operations icm20608c_fops={
    .owner = THIS_MODULE,
    .open = icm20608c_open,
    .release = icm20608c_release,
    .read = icm20608c_read,
};

/*寄存器初始化*/
static void icm20608c_reg_init(void)
{
    u8 value = 0,ret = 0;
    icm20608c_write_one_reg66(&icm20608c, ICM20_PWR_MGMT_1,0x80);
    mdelay(50);
    icm20608c_write_one_reg66(&icm20608c, ICM20_PWR_MGMT_1,0x01);
    mdelay(50);
    ret = icm20608c_read_one_reg(&icm20608c, ICM20_PWR_MGMT_1);
    printk("ICM20_PWR_MGMT_1 = %X\r\n", ret);
    value = icm20608c_read_one_reg(&icm20608c, ICM20_WHO_AM_I);
    printk("ICM20608 ID = %X\r\n", value);
    icm20608c_write_one_reg66(&icm20608c, ICM20_SMPLRT_DIV, 0x00); 
    icm20608c_write_one_reg66(&icm20608c, ICM20_GYRO_CONFIG, 0x18); 
    icm20608c_write_one_reg66(&icm20608c, ICM20_ACCEL_CONFIG, 0x18); 
    icm20608c_write_one_reg66(&icm20608c, ICM20_CONFIG, 0x04); 
    icm20608c_write_one_reg66(&icm20608c, ICM20_ACCEL_CONFIG2, 0x04);
    icm20608c_write_one_reg66(&icm20608c, ICM20_PWR_MGMT_2, 0x00); 
    icm20608c_write_one_reg66(&icm20608c, ICM20_LP_MODE_CFG, 0x00); 
    icm20608c_write_one_reg66(&icm20608c, ICM20_FIFO_EN, 0x00); 
};




const struct of_device_id of_icm20608c_table[]={
    {.compatible = "wyt,icm20608"},
    {}
};
const struct spi_device_id icm20608c_table[]={
    {"icm20608c",0},
    { }
};
/*完成设备号注册 节点注册 spi设备初始化*/
static int icm20608c_probe(struct spi_device *spi)
{
    int ret = 0;
    /*1.设备号*/
    if(icm20608c.major)
    {
        icm20608c.ID = MKDEV(icm20608c.major, 0);
        ret = register_chrdev_region(icm20608c.ID, 1, IMC20608C_NAME);
    }else {
        ret = alloc_chrdev_region(&icm20608c.ID, 0, 1, IMC20608C_NAME);
        icm20608c.major = MAJOR(icm20608c.ID);        
    }
    printk("major = %x\r\n",icm20608c.major);
    /*2.设备注册操作函数*/
    cdev_init(&icm20608c.cdev, &icm20608c_fops);
    icm20608c.cdev.owner = THIS_MODULE;
    ret = cdev_add(&icm20608c.cdev,icm20608c.ID, 1);
    if (ret < 0)
        goto err_unreg;
    /*3.类*/
    icm20608c.class = class_create(THIS_MODULE, IMC20608C_NAME);
    if (IS_ERR(icm20608c.class)) {
        ret = PTR_ERR(icm20608c.class);
        goto err_cdev;
    }
    /*4.节点*/
    icm20608c.device = device_create(icm20608c.class, NULL, icm20608c.ID, NULL, IMC20608C_NAME);
    if (IS_ERR(icm20608c.device)) {
        ret = PTR_ERR(icm20608c.device);
        goto err_class;
    }
    // icm20608c.nd=of_get_parent(spi->dev.of_node);
    // icm20608c.gpio = of_get_named_gpio(icm20608c.nd,"cs-gpio",0);
    // ret = gpio_request(icm20608c.gpio,"icm20608c");
    // ret = gpio_direction_output(icm20608c.gpio,1);
    // if(ret<0)
    // {
    //     printk("gpio error\r\n");
    // }
    /*5.初始化SPI设备*/
    spi->mode = SPI_MODE_0;//时钟模式
    spi_setup(spi);
    icm20608c.icm20608c_spi_private_data = spi;
    /*寄存器初始化内容*/
    icm20608c_reg_init();
    printk("probe success \r\n");
    return ret;
err_class:
    class_destroy(icm20608c.class);
err_cdev:
    cdev_del(&icm20608c.cdev);
err_unreg:
    unregister_chrdev_region(icm20608c.ID, 1);
    return ret;
}

static int icm20608c_remove(struct spi_device *spi)
{
    cdev_del(&icm20608c.cdev);
    unregister_chrdev_region(icm20608c.ID, 1);
    device_destroy(icm20608c.class, icm20608c.ID);
    class_destroy(icm20608c.class);
    printk("remove success \r\n");
    return 0;
}


static struct spi_driver icm20608c_driver={
    .probe = icm20608c_probe,
    .remove = icm20608c_remove,
    .driver = {
        .name = "icm20608c_spi_driver",
        .of_match_table = of_icm20608c_table,
    },
    .id_table = icm20608c_table,

};


static int __init  icm20608c_init(void)
{
    return spi_register_driver(&icm20608c_driver);
}
static void __exit icm20608c_exit(void)
{
    spi_unregister_driver(&icm20608c_driver);
}
/*驱动入口*/
module_init(icm20608c_init);
module_exit(icm20608c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("WYT");

2.1.4 相关问题

① MMIO设备和I2C/SPI设备

在这里插入图片描述

② read函数和write函数中,为什么采用kmalloc函数

  kmalloc即动态分配内存,正常一个应用程序运行,会产生两个栈空间,用户栈(位于用户空间,用于存储函数调用、局部变量等用户态数据)和内核栈(位于内核空间,当进程通过系统调用或中断进入内核态时使用),执行read或者write函数时,cpu从用户态切换到内核态,在内核开辟栈空间,这个空间一般比较小,为8kb或者16kb,因此在read或者write函数中,①如果创建比较大的结构体变量或者是数组数据以及其他数据时候,其大小可能会超过栈空间,采用kmalloc函数在其他内存中开辟出空间,来放这些数据。②栈变量的声明周期仅限于函数执行期间,当函数执行完毕后,这个栈空间就没了,但是spi如果采用的是spi_async异步传输,即想等某些条件满足,再往数组里面写数据,但是这个时候read函数已经执行完毕了,栈空间都没了,就算有数据,也写不进来了,因此采用kmalloc函数开辟。

③DMA技术

  Direct Memory Access,就是不用CPU管了,减少CPU负担,直接能够让外设与内存交换数据,不用CPU管但是肯定要有一个管这个事情,即DMA控制器。模式:①内存到外设:视频流传输、网络通信,②外设到内存:比如摄像头,③内存到内存:比如磁盘之间数据拷贝。

④cs-gpios

cs-gpios 是设备树(Device Tree)中用于配置 SPI 设备的片选信号(Chip Select, CS),CS 引脚用于选择当前通信的从设备,确保同一时间只有一个设备响应主机的指令。

⑤同步串行和异步串行

  同步就是有时钟线,异步就是没有时钟线。
  同步串行:有时钟信号(SCLK):收发双方严格依赖共享时钟同步数据。连续传输:数据以固定长度的形式发送,无起始/停止位。高速高效:适合大数据量、高实时性场景(SPI I2C)。
  异步串行:无时钟信号:依赖预定义的波特率(Baud Rate)和起始/停止位同步。按字节传输:每个数据包(通常1字节)独立封装,带起始位和停止位。灵活性高:适合低速、间歇性数据传输。

三,UART驱动

3.1 UART驱动

uart_driver结构体变量主要是注册UART驱动到内核,uart_port描述UART硬件端口,即设备树中对复用属性和电器属性配置完成后,将这个与uart_port相关联。uart_ops,即uart_port里面的操作函数,实现具体功能。

3.2 关于 I2C、SPI、TTL、RS232、RS485和RS422

在这里插入图片描述

3.2.1 TTL UART和RS232、RS485和RS422的关系

  TTL UART是协议层面,就相当于是根基,RS232、RS485和RS422属于物理层,就相当于这个房子的外观,TTL想要实现什么样的效果,比如长距离传输,但是它这种方式实现不了,于是就通过芯片,比如MAX485将TTL的电平信号转化为了RS485的格式要求,从而实现了长距离传输。单片机 (TX/RX, TTL) → MAX485(控制DE/RE) → A/B差分线 MAX485(设备内部) →设备,有点像高压电,其他的也是如此,因此UART驱动,无论是RS什么方式,最终实现都是TTL,驱动是一样的。

3.2.2 RS485能一主多从,但是RS232只能点对点

  RS485实现一主多从:DE/RE 引脚,这个是MAX485芯片内的,DE为高电平,RS485发送,RE为低电平,RS485接收,通常用一个GPIO控制这俩引脚,从而实现485通信的收发,短接。DE=1, RE=0 → 发送模式,DE=0, RE=1 → 接收模式。当主机和多个从机进行信息交互时,①主机发送命令(DE=1,RE=0),指定目标设备地址(如 0x01),②目标从机响应(DE=0,RE=1),只有地址匹配的从机回复,其他从机保持静默,③主机切换至下一个设备,重复以上工作。RS485也能实现多主多从,多个主机工作时,要去检查A和B的电压差,如果发现其他主机工作,那么这个主机现在就不能发送数据,等待其他主机发送完后才能发送。
  RS232三根线,TX和RX和GND,工作时就把数据放到信号线上,没有什么设备地址这种操作,当多个设备并联时,如果几个设备同时工作,都会去控制TX和RX电压,就会出现问题,它不像RS485一样能够实现这个设备发送时候,其他设备就不能发送了,其次是不同设备地线电压不同(?)。
  i2c和spi也能实现一主多从,关键在意i2c能够传输设备地址,spi有片选信号线,当某个设备工作时候,就会拉低它这个设备对应的专属片选线。

3.2.3 帧

在这里插入图片描述  I2C这个写数据,先发设备地址,再发寄存器地址,最后发写的数据,这部分是一帧数据发送的,因为如果是三帧数据的话,第二步给寄存器地址都不知道往哪个设备去发。

相关文章:

  • UE中不同摄像机震动的区别Camera Shake
  • 分布式系统
  • P9246 [蓝桥杯 2023 省 B] 砍树-题解(最近公共祖先LCA + 树上差分)
  • Matlab人脸识别考勤系统【PCA(主成分分析)+ SVM(支持向量机)】
  • 知识表示方法之四:语义网络表示法(Semantic Network Representation)
  • 脑疾病分类的疑惑【6】:脑疾病分类比较适合使用具有哪些特点的模型?
  • OpenIPC开源FPV之Adaptive-Link关键RF参数
  • python下载m3u8格式视频
  • 【前端】【React】第三章:深入理解 React 事件处理与性能优化
  • MySQL日期时间函数
  • Redis 源码硬核解析系列专题 - 第五篇:事件驱动模型与网络层
  • AutoCAD Map 3D:CAD与GIS集成工具
  • Lesson 7 Too late
  • ISIS-4 LSP计算
  • 1.3 斐波那契数列模型:LeetCode 746. 使用最小花费爬楼梯
  • LangChian除了load_qa_chain还有什么chain
  • HTTP和HTTPS区别
  • RK3588使用笔记:设置程序/服务开机自启
  • [Linux]基础IO
  • C语言pthread库的线程休眠和唤醒的案例