前言
上次写Drv/PWM的格式,感觉还不错,条理和逻辑感觉还是比较清晰,这次还是采用这样的模式来写I2C的驱动;在porting guide的手册上将I2C归结为Specialized Device Drivers,和之前的PWM、Serial的Character Device Drivers是明显两类设备,那么在实现上肯定是有区别的;下面还是来分析下;
基础知识
I2C在nuttx中分为了master/slave,这次主要按master的配置来讲解;
启动流程
I2C的启动流程和之前的PWM很类似,也就是在config/stm32f103-minimum/src/stm32_appinit.c文件中board_app_initialize()函数;
- board_app_initialize-- #板载应用初始化
- |
- v
- stm32_bringup()------ #逐个启动板载应用
- |
- v
- stm32_i2c_setup()---- #执行i2c初始化,原本板载没有,我后面单独加上去的
- |
- v
- stm32_i2cbus_initialize(CONFIG_STM32_I2C1)--- #初始化指定的i2c
- i2c_register(i2c, CONFIG_STM32_I2C1)--- #注册i2c驱动到内核文件按系统
- .....
- #初始化其他板载应用
- .....
这里需要说明的是I2C的i2c_register()的形参和PWM的就有区别了,开始我直接按照PWM的格式直接就依葫芦画瓢了,奇怪的是,编译警告了参数类型不对,但是还是生成了*.bin文件,我试着烧录进去,系统正常运行,查看/dev下居然出现了i2c758?为啥是758…后来才发现形参搞错了;
不过这倒是让我对driver_register()有新的认识,其实系统抽象的驱动层压根只会拿到ops的指针,不会去对所拿到的函数指针做检查,也就是说系统只是给你提供一种接口方法,你去填充就好了,至于实现那是你驱动自己该干的事;所以到此我对Nuttx中的驱动理解就是,定义了一个file_operations数组,这个里面全是函数指针以及对应驱动的状态配置信息;
stm32_i2cbus_initialize
该函数的路径nuttx/arch/arm/src/stm32/stm32_i2c.c,主要实现了对stm32_i2c_priv_s定义的所有板卡的驱动实现的选择和配置中断信号量以及管脚初始化【stm32_i2c_sem_init(priv);stm32_i2c_init(priv);】,ops结构里面定义了i2c驱动所涉及到数据结构stm32_i2c_priv_s、i2c_ops_s、i2c_msg_s、stm32_i2c_config_s以及其他状态变量,接下来就按照这四个数据结构逐个展开来分析;
stm32_i2c_priv_s
/* I2C Device Private Data */
struct stm32_i2c_priv_s
{
const struct i2c_ops_s *ops; /* Standard I2C operations I2C的操作函数*/
const struct stm32_i2c_config_s *config; /* Port configuration I2C的配置函数*/
int refs; /* Referernce count */
sem_t sem_excl; /* Mutual exclusion semaphore */
#ifndef CONFIG_I2C_POLLED
sem_t sem_isr; /* Interrupt wait semaphore */
#endif
volatile uint8_t intstate; /* Interrupt handshake (see enum stm32_intstate_e) */
uint8_t msgc; /* Message count */
struct i2c_msg_s *msgv; /* Message list */
uint8_t *ptr; /* Current message buffer */
uint32_t frequency; /* Current I2C frequency */
int dcnt; /* Current message length */
uint16_t flags; /* Current message flags */
/* I2C trace support */
#ifdef CONFIG_I2C_TRACE
int tndx; /* Trace array index */
systime_t start_time; /* Time when the trace was started */
/* The actual trace data */
struct stm32_trace_s trace[CONFIG_I2C_NTRACE];
#endif
uint32_t status; /* End of transfer SR2|SR1 status */
};
上面的的成员变量就不一一列举说明了,这里先占个坑,后面具体问题具体分析吧,需要说明的一点就是和之前的的Character device driver 一致的是,I2C也存在这样一个类似dev的数据结构,我猜测在Nuttx下估计多数驱动都是这样来实现的,之所以存在几类驱动,差异化也就集中体现在ops的接口上,对与驱动层面来说,dev服务的对象还是ops;
这里要补充一点就nuttx的驱动分的很细(后面要单独一章,独立来说明),上层接口有file_operations,ta的实现专门放在了/nuttx/driver下这里存放了涵盖几乎所有外设的驱动(传感器、模组等驱动也都在这里);下层接口来说就是通过ops来来操作寄存器实现片上的驱动了,主要放在了nuttx/arch/arm/src/stm32下面;
所以Nuttx的驱动编写的时候思路应该是片上的驱动编写和片外的驱动编写两部分,共同约束接口(nuttx/driver)实现files_operations(driver)与片上外设的dev的对接;
总的来说,Nuttx的驱动分为片上、片外,每种都是以dev+ops的方式来实现;都是套路~~
i2c_ops_s
接下来重点来了,既然I2C是special device driver 那么ta的驱动是不是体现在ops上呢,接下来来分析一下这个数据结构;
struct i2c_ops_s
{
CODE int (*transfer)(FAR struct i2c_master_s *dev,
FAR struct i2c_msg_s *msgs, int count);
#ifdef CONFIG_I2C_RESET
CODE int (*reset)(FAR struct i2c_master_s *dev);
#endif
};
好吧,这是我见过最短的ops,不过这里有个细节,就是在ops里面定义了i2c_msg_s的指针,之前我们分析过就是ops,用来向上填充file_operations的数据结构体的,多个驱动会公用这个接口,【这句话应该这样来说,通过这个唯一的接口来操作多个dev】
i2c_msg_s
顾名思义这个数据结构是传输I2C总线上数据的最小单元,在每个dev中都存在,可想而知的是每个dev的msg都不一样;
/* I2C transaction segment beginning with a START. A number of these can
* be transferred together to form an arbitrary sequence of write/read transfer
* to an I2C slave device.
*/
struct i2c_msg_s
{
uint32_t frequency; /* I2C frequency */
uint16_t addr; /* Slave address (7- or 10-bit) */
uint16_t flags; /* See I2C_M_* definitions */
FAR uint8_t *buffer; /* Buffer to be transferred */
ssize_t length; /* Length of the buffer in bytes */
};
这个数据结构需要结合OPS的函数实现来看,这里先占个坑,后面来填;
stm32_i2c_config_s
/* This structure contains the full state of I2C as needed for a specific
* transfer. It is passed to I2C methods so that I2C transfer may be
* performed in a thread safe manner.
*/
struct i2c_config_s
{
uint32_t frequency; /* I2C frequency */
uint16_t address; /* I2C address (7- or 10-bit) */
uint8_t addrlen; /* I2C address length (7 or 10 bits) */
};