概述
RTEMS的beaglebone black BSP并不包含I2C的驱动,而很多传感器模块的通信方式都只支持I2C,因此限制了RTEMS在IOT物联网方面的应用。
本文对I2C驱动的实现思想进行详细描述。参考代码是RTEMS源码中ATSAM的i2C实现。
首先要知道,RTEMS中I2C bus的代码已经存在,因此我们只关心具体实现代码即可,也就是BSP部分的i2C驱动部分/
RTEMS对于I2C设备的管理方式是采用nexus总线,因此首先要在总线上对I2C设备进行注册。
注册函数如下:
int am335x_i2c_bus_register(
const char *bus_path,
uintptr_t register_base,
uint32_t input_clock,
rtems_vector_number irq
)
{
bbb_i2c_bus *bus;
rtems_status_code sc;
int err;
/*check bus number is >0 & <MAX*/
bus = (bbb_i2c_bus *) i2c_bus_alloc_and_init(sizeof(*bus));
if (bus == NULL) {
return -1;
}
bus->regs = (volatile bbb_i2c_regs *) register_base;
// 1. Enable clock for I2CX
I2C0ModuleClkConfig();
// 2. pinmux setup
am335x_i2c0_pinmux(bus);
// 3. RESET : Disable Master, autoideal
am335x_i2c_reset(bus);
// 4. configure bus speed
bus->input_clock = input_clock; // By default 100KHz. Normally pass 100KHz as argument
err = am335x_i2c_set_clock(&bus->base, I2C_BUS_CLOCK_DEFAULT);
if (err != 0) {
(*bus->base.destroy)(&bus->base);
rtems_set_errno_and_return_minus_one(-err);
}
bus->irq = irq;
//bring I2C out of reset
udelay(1000);
flush_fifo(&bus->base);
writew(0xFFFF, &bus->regs->BBB_I2C_IRQSTATUS);
// 5. Start interrupt service routine & one interrupt at a time
sc = rtems_interrupt_handler_install(
irq,
"BBB I2C",
RTEMS_INTERRUPT_UNIQUE,
am335x_i2c_interrupt,
bus
);
if (sc != RTEMS_SUCCESSFUL) {
(*bus->base.destroy)(&bus->base);
rtems_set_errno_and_return_minus_one(EIO);
}
// 6. start transfer for reading and writing
bus->base.transfer = am335x_i2c_transfer;
bus->base.set_clock = am335x_i2c_set_clock;
bus->base.destroy = am335x_i2c_destroy;
return i2c_bus_register(&bus->base,bus_path);
}
首先是调用i2c_bus_alloc_and_init函数,对i2c bus进行资源分配和初始化,该函数具体实现在i2c-bus.c 文件中,属于rtems设备驱动系统函数,不做过多分析。然后进行判断,若函数返回值为null,表示分配资源未成功,返回-1,表示失败,并跳出注册函数。
接下来给出I2C寄存器的基础地址:
bus->regs = (volatile bbb_i2c_regs *) register_base;
这行代码非常重要,它决定了后面的读写寄存器的地址是否正确。参数register_base是注册函数的输入参数,该参数定义在i2c.h文件中,依据不同的i2c设备进行选择,比如i2c0也就是EEPROM占用的通道,该基础地址是0x44E0_B000。
接下来为i2c使能时钟,设置引脚复用类型,以及重置i2c,这里具体分析reset i2c实现:
static void am335x_i2c_reset(bbb_i2c_bus *bus)
{
volatile bbb_i2c_regs *regs = bus->regs;
int timeout = I2C_TIMEOUT;
if (readw(®s->BBB_I2C_CON) & I2C_CON_EN) {
writew(0, ®s->BBB_I2C_CON);
udelay(50000);
}
writew(0x2, ®s->BBB_I2C_SYSC); /* for ES2 after soft reset */
udelay(1000);
writew(I2C_CON_EN, ®s->BBB_I2C_CON);
while (!(readw(®s->BBB_I2C_SYSS) & I2C_SYSS_RDONE) && timeout--) {
if (timeout <= 0) {
puts("ERROR: Timeout in soft-resetn");
return;
}
udelay(1000);
}
}
然后进行中断句柄安装:
sc = rtems_interrupt_handler_install(
irq,
"BBB I2C",
RTEMS_INTERRUPT_UNIQUE,
am335x_i2c_interrupt,
bus
);
其中am335x_i2c_interrupt就是中断处理函数,非常重要,将在后面详细介绍。
最后设置bus的各种接口函数,transfer、destroy等,并返回i2c_bus_register函数,该函数也属于i2c系统总线函数,因此不做分析。
接下来介绍transfer函数,该函数统一处理i2c的读和写操作,是整个i2c驱动的关键部分:
static int am335x_i2c_transfer(i2c_bus *base, i2c_msg *msgs, uint32_t msg_count)
{
rtems_status_code sc;
bbb_i2c_bus *bus = (bbb_i2c_bus *)base;
volatile bbb_i2c_regs *regs;
uint32_t i;
rtems_task_wake_after(1);
if (msg_count < 1){
return 1;
}
for (i=0; i<msg_count;++i) {
if ((msgs[i].flags & I2C_M_RECV_LEN) != 0) {
return -EINVAL;
}
}
bus->msgs = &msgs[0];
bus->msg_todo = msg_count;
bus->current_msg_todo = msgs[0].len;// current data size
bus->current_msg_byte = msgs[0].buf;// current data
bus->task_id = rtems_task_self();
regs = bus->regs;
am335x_i2c_setup_transfer(bus,regs);
REG(®s->BBB_I2C_IRQENABLE_SET) = BBB_I2C_IRQ_USED;
sc = rtems_event_transient_receive(RTEMS_WAIT, bus->base.timeout);
// If timeout then return timeout error
if (sc != RTEMS_SUCCESSFUL) {
am335x_i2c_reset(bus);
rtems_event_transient_clear();
return -ETIMEDOUT;
}
return 0;
}
首先将要传输的数据的信息结构体赋给bus结构体中的各个变量,然后进入到am335x_i2c_setup_transfer函数进行设置。该函数管理传输和发送函数。
static void am335x_i2c_setup_transfer(
bbb_i2c_bus *bus,
volatile bbb_i2c_regs *regs
)
{
const i2c_msg *msgs = bus->msgs;
uint32_t msg_todo = bus->msg_todo;
bool send_stop = false;
uint32_t i;
bus->current_todo = msgs[ 0 ].len;
for ( i = 1; i < msg_todo && ( msgs[ i ].flags & I2C_M_NOSTART ) != 0;
++i ) {
bus->current_todo += msgs[ i ].len;
}
regs = bus->regs;
REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_TXFIFO_CLR;
REG( &bus->regs->BBB_I2C_BUF ) |= AM335X_I2C_BUF_RXFIFO_CLR;
am335x_i2c_set_address_size( msgs, regs );
bus->read = ( msgs->flags & I2C_M_RD ) != 0;
bus->already_transferred = ( bus->read == true ) ? 0 : 1;
if ( bus->read ) {
if ( bus->current_msg_todo == 1 ) {
send_stop = true;
}
am335x_i2c_setup_read_transfer( bus, regs, msgs, send_stop );
} else {
am335x_i2c_setup_write_transfer( bus, regs, msgs );
}
}
该函数就是通过 bus->read判断是要进行读操作还是写操作。然后进入对应的函数处理。
接下来分析中断处理函数:
static void am335x_i2c_interrupt(void *arg)
{
bbb_i2c_bus *bus = arg;
volatile bbb_i2c_regs *regs = bus->regs;
/* get status of enabled interrupts */
uint32_t irqstatus = REG(®s->BBB_I2C_IRQSTATUS);
bool done = false;
/* Clear all enabled interrupt except receive ready and transmit ready interrupt in status register */
REG(®s->BBB_I2C_IRQSTATUS) = (irqstatus & ~( AM335X_I2C_IRQSTATUS_RRDY | AM335X_I2C_IRQSTATUS_XRDY));
if (irqstatus & AM335X_I2C_INT_RECV_READY) {
delay_bbb_i2c
am335x_i2c_continue_read_transfer(bus, regs);
}
if (irqstatus & AM335X_I2C_IRQSTATUS_XRDY) {
am335x_i2c_continue_write(bus,regs);
}
if (irqstatus & AM335X_I2C_IRQSTATUS_NACK) {
done = true;
am335x_i2c_masterint_disable(regs,AM335X_I2C_IRQSTATUS_NACK);
}
if (irqstatus & AM335X_I2C_IRQSTATUS_ARDY) {
done = true;
writew(I2C_STAT_ARDY, ®s->BBB_I2C_IRQSTATUS);
}
if (irqstatus & AM335X_I2C_IRQSTATUS_BF) {
done = true;
}
if (done) {
uint32_t err = irqstatus & BBB_I2C_IRQ_ERROR;
am335x_i2c_next_byte(bus);
if (bus->msg_todo == 0 ) {
rtems_status_code sc;
am335x_i2c_masterint_disable(regs, (AM335X_I2C_IRQSTATUS_RRDY | AM335X_I2C_IRQSTATUS_XRDY | AM335X_I2C_IRQSTATUS_BF));
REG(®s->BBB_I2C_IRQSTATUS) = err;
sc = rtems_event_transient_send(bus->task_id);
_Assert(sc == RTEMS_SUCCESSFUL);
(void) sc;
} else {
am335x_i2c_setup_transfer(bus, regs);
}
}
}
am335x_i2c_continue_write函数
static void am335x_i2c_continue_write(
bbb_i2c_bus *bus,
volatile bbb_i2c_regs *regs
)
{
if ( bus->already_transferred == bus->msg_todo ) {
REG( ®s->BBB_I2C_DATA ) =
bus->current_msg_byte[ bus->already_transferred ];
REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY;
am335x_i2c_masterint_disable( regs, AM335X_I2C_IRQSTATUS_XRDY );
REG( ®s->BBB_I2C_CON ) |= AM335X_I2C_CON_STOP;
} else {
writeb( bus->current_msg_byte[ bus->already_transferred ],
®s->BBB_I2C_DATA );
REG( ®s->BBB_I2C_IRQSTATUS ) = AM335X_I2C_IRQSTATUS_XRDY;
bus->already_transferred++;
}
}
这里要注意的是,在对BBB_I2C_DATA寄存器写入数据时,要用writeb函数,也就是8位数据写入,因为 BBB_I2C_DATA寄存器就是八位的:
#define writeb(v,c) ({ unsigned char __v = v; __arch_putb(__v,c); __v; })
最后
以上就是怡然人生为你收集整理的实现RTEMS Beaglebone Black I2C驱动的全部内容,希望文章能够帮你解决实现RTEMS Beaglebone Black I2C驱动所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复