免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

I2C子系统、IIO子系统、SPI子系统

I2C子系统、IIO子系统、SPI子系统 I2C子系统一、IIC总线介绍IIC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的串行总线用于连接微控制器及其外围设备它具有如下特点。只有两条总线线路:一条串行数据线(SDA),一条串行时钟线(SCL)每个连接到总线的器件都可以使用软件根据它的唯一的地址来识别传输数据的设备间是简单的主/从关系主机可以用作主机发送器或主机接收器它是一个真正的多主机总线两个或多个主机同时发起数据传输时可以通过冲突检 测和仲裁来防止数据被破坏串行的8位双向数据传输位速率在标准模式下可达100kbit/s,在快速模式下可达 400kbit/s,在高速模式下可达3.4Mbit/s。注意:当多个主机试图去控制总线时通过仲裁可以使得只有一个主机获得总线控制权并且它传输的信息不被破坏二、IIC总线的信号类型1.开始信号(S):SCL 为高电平时SDA由高电平向低电平跳变开始传送数据2.结束信号(P):SCL为高电平时SDA由低电平向高电平跳变结束传送数据3.响应信号(ACK):接收器在接收到8位数据后在第9个时钟周期拉低SDA电平注意:SDA上传输的数据必须在SCL为高电平期间保持稳定SDA上的数据只能在SCL为低电平期间变化三、IIC总线的数据传输格式发送到SDA线上的每个字节必须是8位的每次传输可以发送的字节数量不受限制。首先传输的是数据的最高位(MSB)。启动一个传输时主机先发送S信号然后发出8位数据。这8位数据中前7位为从机的地址第8位表示传输的方向(0表示写操作1表示读操作)。从机收到后会发出一个ACK信号Linux IIC子系统采用经典的总线-设备-驱动分层模型从下到上依次为物理层通过SCL和SDA两条开漏线连接LM75温度传感器、EEPROM等从设备SOC厂商编写的IIC总线驱动层直接操作IICON控制寄存器、IICSTAT状态寄存器、IICADD地址寄存器、IICCDS数据移位寄存器等硬件寄存器通过s3c24xx_i2c_doxfer通信函数接口实现具体的IIC时序控制核心层i2c-core.c是整个子系统的枢纽它维护i2c_driver和i2c_client两个注册链表实现设备与驱动的匹配向上提供i2c_register_driver注册驱动、i2c_new_device注册设备、i2c_transfer发起IIC传输三个关键API向下管理适配器从而将设备驱动与具体SOC硬件完全解耦mpu6050原理图查找芯片管脚的功能外设具体的通信方式针对I2C总线需要知道具体的从机地址对寄存器操作查找GPB的第2和第3个管脚设备树第一个 IDSPI Port No.是芯片厂商自己编的号不用在设备树里第二个 ID 是 GIC 硬件的原始中断号用在设备树里时需要减去 32。0~31号中断预留给了SGI软件中断和PPI私有外设中断SPI共享外设中断实际从32号开始编号所以芯片手册里的硬件ID必须减去32才能得到设备树里SPI类型的正确中断号。设置管脚工作模式打开exynos4x12-pinctrl.dtsi查看写出来我们的设备树I2C总线上描述设备I2C总线上描述驱动#xxx-cells后面的值代表用多少个32位整数描述xxx的信息I2C总线上的设备节点要写成I2C控制器节点下面的子节点s3c2410.c的probe函数中调用注册adapter的函数接口i2c_add_numbered_adapter ---i2c_add_adapter ---i2c_register_adapter ---of_i2c_register_devices在函数of_i2c_register_devices中会遍历这个adapter对应的device node的child device node这些child device node对应的就是挂载i2c bus上的板级外设的硬件信息(如mpu6050)这些板级外设使用I2C接口跟SOC通信然后调用of_i2c_register_device这个函数根据每个child devicenode的信息构造i2c_board_info并调用i2c_new_device在i2c_new_device中会创建并注册i2c_client注册i2c_client的时候如果找到了对应的设备驱动程序如mpu6050的驱动程序设备驱动程序的probe函数就会被调动。电源寄存器的地址第6位置1就是睡眠寄存器这里的默认值是1传感器的寄存器地址映射表温度寄存器的地址查看i2c-core.c如果没有id_table就直接不调用probe函数所以我的驱动里要写id_table实验现象列出注册了哪些控制器扫描I2C 总线看看有哪些设备响应IIO子系统通道3查看内核自带驱动drivers/iio/adc/exynos_adc.c所以我们的设备树节点的compatible写成samsung,exynos-adc-v1发现probe函数中获取了两次资源第一个获取的是ADC控制器基址第二个是ADC PHY 使能寄存器查手册可得起始地址为0x126c0000,大小为0x20 (32字节)ADC PHY 是共享的模拟前端必须先打开电源否则两个 ADC 都读不到东西找到ADC_PHY_CONTROL地址0x1002_0000 0x0718 0x1002_0718ADC_PHY_CONTROL 就一个 32 位寄存器只需 4 字节所以reg 0x126c0000 0x20,0x10020718 0x4;从表格中找到ADC的中断源并且知道它是一个组合中断源由combiner控制器控制找combiner控制器表示第一个填组号SPI Port No第二个填组内线号ID中的数字所以interrupt-parentcombiner; interrupts10 3;看看别人clocks怎么写的都是引用clock节点然后后面跟一个数字找到这个节点找#clock-cells表示每个控制都有它的ID在内核目录Documentation/devicetree/bindings/regulator/regulator.txt中查看结合它的解释和示例可知属性要写成name-supply指向某个电源节点在驱动代码中得到名字是vdd,所以是vdd-supply??;查看原理图找adc供电引脚看它连到 PMIC 的哪一路发现是VDD18_ADC,搜索VDD18_ADC搜VDDIOAP_18发现连在VLOD3这一路在设备树中查看所以我们引用ldo3_reg这个节点写出来是vdd-supplyldo3_reg;所以设备树写成查看adc底板原理图可知,电源是由VDD1V8_EXT关联的搜索VDD1V8_EXT可知, 是将3.3V的电压经过持续有效使能降为1.8V发现pmic管理的电源中并没有VDD1V8_EXT,得到pmic未对VDD1V8_EXT进行管理所以vdd-supply属性可写也可不写只读原始数据并加锁保证同一时刻只有一个采样在进行阻塞等待挂起当前进程等 ISR 的complete()唤醒或 1 秒超时。读数据寄存器取低 12 位清中断标志唤醒read_raw里挂起的进程并发与竞态并发通过算法将CPU使用权合理分配给多个任务并行多核处理器的每个核心都可以独立执行任务竞态并发可能造成多个程序同时访问一个共享资源同时访问一个共享资源产生的问题就是竞争竞争产生原因多线程的并发访问Linux是多任务操作系统多线程访问是竞争产生的基本原因。处理器间并发访问多个处理器之间存在核间并发访问。抢占式的并发访问高优先级任务可以打断低优先级任务。中断程序并发访问中断任务产生后CPU会停止当前任务执行中断任务中断任务修改共享资源产生竞争。处理竞争的方法1.互斥锁互斥锁 Mutual Exclusion Lock 是多线程编程中用于保护共享资源的一种机制确保同一时间只有一个线程进入临界区 访问临界资源。void mutex_init(struct mutex *lock)初始化互斥锁通常在驱动的 probe 函数中调用void mutex_lock(struct mutex *lock)获取互斥锁若锁已被占用则进入睡眠等待通常在 open 函数中调用void mutex_unlock(struct mutex *lock)释放互斥锁唤醒等待的进程通常在 release 函数中调用2.自旋锁自旋锁 spin lock 也是为了保护共享资源的一种锁机制 是一种非阻塞锁如果线程获取自旋锁但锁已经被其他线程占用该线程不会挂起而是消耗 CPU 的时间不停尝试获取锁。spin_lock_init(spinlock_t *lock)初始化自旋锁通常在驱动的 probe 函数中调用void spin_lock(spinlock_t *lock)获取自旋锁若锁已被占用则原地忙等自旋直到获取成功void spin_unlock(spinlock_t *lock)释放自旋锁允许其他进程/CPU获取锁3.信号量本质上是一个全局变量信号量的值表示控制访问资源的线程数当线程访问共享资源时信号量执行“减一”操作访问完成后再执行“加一”操作void sema_init(struct semaphore *sem, int val)初始化信号量并设置初始计数值int down_interruptible(struct semaphore *sem)获取信号量计数值减1若信号量不可用则进入睡眠等待且等待可被信号打断void up(struct semaphore *sem)释放信号量计数值加1并唤醒一个等待者4.原子操作只能做一些简单的增减将操作变为一个整体中间不会有上下文切换无法被打断。SPISPI (Serial Peripheral Interface),中文名叫 串行外围设备接口。 是由Motorola公司开发用来在微控制器和外围设备芯片之间提供一个低成本、易使用的接口。 SPI 接口可以用来连接存储器(存储数据)、AD转换器、DA转换器、实时时钟、LCD驱动器、传感器、音频芯片、甚至其他处理器.SPI是一个 同步协议接口所有的传输都参照一个共同的时钟这个同步时钟信号由主机 (处理器)产生。 当 SPI 总线上接入多个设备时,则需要通过 片选,没有被选中的外设不会参与 SPI 传输工作原理主设备启动通信 拉低目标从设备的 SS/CS 信号线 激活从设备 生成时钟信号 SCLK数据传输 数据通过 MOSI 和 MISO 同时传输 全双工 每个时钟周期传输 1 bit 从高位或低位开始 取决于配置通信结束 拉高 SS/CS 信号线 释放从设备.主机和从机都包含一个串行移位寄存器主机通过向它的SPI串行寄存器写入一个字节发起 一次传输。寄存器通过MOSI信号线将字节传送给从机从机也将自己的移位寄存器中的内容通过 MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的因此SPI 成为一个很有效的协议在同步传输时,需要注意一下几点:如果只是进行写操作主机只需忽略收到的字节如果主机要读取从机的一个字节就必须发送一个空字节来引发从机的传输通过SPI总线在主机和从机之间传输数据必须保证主机发出的时序和从机所要求的 时序一致才可以从上图可以看出 mcp2515 与 Exynos 4412 连接的接口为 spi_2mcp2515 芯片支持的 spi 的模式为 支持 0,0 和 1,1 的 SPI 模式在设备树看看别人怎么写查看Document/devicetree/bindings下grep spics-gpio填你实际用哪个 GPIO 引脚来充当片选CS告诉内核去控制这个引脚来选中设备作用是手动管片选。samsung,spi-feedback-delay填 0、1、2 或 3告诉内核在 MISO 线上采样时延迟多少度作用是补偿高速通信时的信号延迟让数据读得准。可选看原理图和数据手册可知SPI2 的片选信号在GPC1 GPIO 组的第 2 号引脚从 0 开始编号gpc1中#gpio-cells 2,用两个数描述在Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt中看到其中flags参数写什么查看内核源码可知使用的是of_get_named_gpio()不是of_get_named_gpio_flags()。所以设备树里写的 flags0 或 1被直接忽略了驱动只拿到 GPIO 编号。驱动硬编码将 CS GPIO 初始化为输出高电平CS 无效状态CS 有效选通从设备SPI_CS_HIGH0 时 → 输出 0 (低电平选通)CS 无效释放从设备SPI_CS_HIGH0 时 → 输出 1 (高电平释放)驱动行为取决于是否在设备树中加了spi-cs-high属性DT 属性spi-modeCS 选通电平不写spi-cs-highSPI_CS_HIGH 0CS0 选通写了spi-cs-highSPI_CS_HIGH 1CS1 选通所以cs-gpio 写 0 还是 1 实际运行效果完全一样。按惯例写0即可我们的cs-gpio gpc1 2 0MCP2515 的 /CS 是标准低有效所以不加spi-cs-high驱动就会自动输出低电平选通、高电平释放看到实例中写了从设备节点其中spi-max-frequency不知道写多少找它在spi-bus.txt中看到主设备节点必须写#address-cells、#size-cells、compatible分别表示片选地址格式、固定为0、控制器型号从设备节点必须写reg、compatible、spi-max-frequency分别表示片选编号、设备型号、最高通信速率从设备最大 10MHz所以我们写spi-max-frequency 10000000;在内核源码中查看drivers/spi/spi.c关键代码reg的值就是片选号CS 号存进spi-chip_select。这是 SPI 总线用来识别挂在哪根 CS 线上的编号从0 开始。在内核源码中的drivers/spi/spi-s3c64xx.c查看它会检查cs-gpio只要存在这个属性就启用 cs-gpio 功能不存在就禁用整个功能。所以我的设备树写出来可得寄存器的地址寄存器地址CNF30x28CNF20x29CNF10x2ACANINTE0x2BCANINTF0x2CCANSTAT0x0ECANCTRL0x0FCAN控制寄存器可得寄存器的复位值SPI命令时序图在 spi 子系统中在进行数据传输时,会将数据封装成 spi_message 结构体,形成消息队列再进行发送. spi_message 结构体定义如下:一个 spi_message 对象可以由多个 spi_transfer 来构成,spi_transfer 的定义如下:spi_message 与 spi_transfer 的关系如下图:图中最下层是硬件空间SPI总线控制器总线控制器负责硬件上的数据交互。内核空间中需要有对应的控制器驱动对硬件进行操作。核心层的作用是向控制器驱动层提供注册SPI控制器驱动的接口并提供一些需要控制器驱动实现的回调函数。核心层向上对SPI设备驱动提供标准的spi收发API以及设备注册函数。所以当有SPI设备驱动发起一次传输时设备驱动会调用SPI核心层的收发函数核心层的收发函数会回调控制器驱动层实现的硬件相关的发送回调函数。从而实现SPI数据的收发。spidev_test在内核源码里找spidev.c在设备树中compatible改成rohm,dh2228fv内核Documentation/spi下找到spidev_test.c
返回列表