Rice我叫加饭? · 1月13日

rt-smart用户态通过IPC通信玩转传感器数据

首发:Rice 嵌入式开发技术分享
作者:RiceDIY

简介

  • 首先纠正一下上一篇文章中,在我的仓库中,1月11日的代码会出现系统崩溃。原因在于我的驱动中内存物理地址映射到虚拟地址的操作有问题,我已经把这个bug解决了,如果有兴趣,欢迎拉取最新的代码。
  • 这一篇来介绍我在rt-smart的第二个应用。这个应用将加入rt-smart与rt-thread区别之处--进程间的通信。
  • 功能主要是在用户态读取传感器数据,传感器是100ASK\_imx6ull板载的ap3216c,它是采用I2C总线进行通信。
  • 为啥这次会先对接I2C呢?因为接下来想把屏幕在rt-smart跑起来,但是屏幕的触摸芯片采用I2C,所以就把他先跑起来。
  • 目前屏幕已经在rt-thread上跑起来,但是在rt-smart没有跑起来,目前在研究LCD的缓存是一个什么样一个形式。
  • 100ask\_imx6ull驱动对接情况:

image.png

100ask\_imx6ull的rtt仓库:

环境

  1. 100ask\_imx6ull开发板。
  2. 两条micro USB线。
  3. 电源。
  4. windows电脑一台。

I2C驱动适配

  1. 在imx6ull中,我适配的是硬件I2C,imx6ull有4组I2C接口。软件I2C后续不会进行适配,因为在这颗芯片上,软件I2C的必要性不大。
  2. 如果你要了解RT-Thread的I2C设备驱动框架,可以看一下我之前的文章《rt-thread驱动框架分析》-i2c驱动
  3. 在上一篇文章中《rt-smart的第一个应用程序,imx6ull用户态点灯》讲到,rt-smart不能直接使用物理地址访问硬件,而需要采用虚拟地址。所以需要进行地址映射(rtt提供的API:rt\_hw\_kernel\_phys\_to\_virt)。
  4. 首先需要查看imx6ull的芯片手册,需要将I2C相关的物理地址找到。为了不要重复造轮子,定义了一个结构体:struct i2c\_addr\_config,并把4组I2C相关的地址作为一个表格。如下:
#define I2C1_SCL_MUX_BASE       0x020E00B4U  
#define I2C2_SCL_MUX_BASE       0x020E00BCU  
#define I2C3_SCL_MUX_BASE       0x020E00E4U  
#define I2C4_SCL_MUX_BASE       0x020E00ECU  
  
#define I2C1_SCL_CFG_BASE       0x020E0340U  
#define I2C2_SCL_CFG_BASE       0x020E0348U  
#define I2C3_SCL_CFG_BASE       0x020E0370U  
#define I2C4_SCL_CFG_BASE       0x020E0378U  
  
#define I2C1_SCL_INPUT_BASE     0x020E05A4U  
#define I2C2_SCL_INPUT_BASE     0x020E05ACU  
#define I2C3_SCL_INPUT_BASE     0x020E05B4U  
#define I2C4_SCL_INPUT_BASE     0x020E05BCU  
  
#define I2C1_SDA_MUX_BASE       0x020E00B8U  
#define I2C2_SDA_MUX_BASE       0x020E00C0U  
#define I2C3_SDA_MUX_BASE       0x020E00E8U  
#define I2C4_SDA_MUX_BASE       0x020E00F0U  
  
#define I2C1_SDA_CFG_BASE       0x020E0344U  
#define I2C2_SDA_CFG_BASE       0x020E034CU  
#define I2C3_SDA_CFG_BASE       0x020E0374U  
#define I2C4_SDA_CFG_BASE       0x020E037CU  
  
#define I2C1_SDA_INPUT_BASE     0x020E05A8U  
#define I2C2_SDA_INPUT_BASE     0x020E05B0U  
#define I2C3_SDA_INPUT_BASE     0x020E05B8U  
#define I2C4_SDA_INPUT_BASE     0x020E05C0U  
  
struct i2c_addr_config  
{  
    I2C_Type *i2c;  
    size_t i2c_scl_mux_base;  
    size_t i2c_scl_config_base;  
    size_t i2c_scl_input_base;  
    size_t i2c_sda_mux_base;  
    size_t i2c_sda_config_base;  
    size_t i2c_sda_input_base  
};  
  
static struct i2c_addr_config addr_config[] =   
{  
    {I2C1, I2C1_SCL_MUX_BASE, I2C1_SCL_CFG_BASE, I2C1_SCL_INPUT_BASE, I2C1_SDA_MUX_BASE, I2C1_SDA_CFG_BASE, I2C1_SDA_INPUT_BASE},  
    {I2C2, I2C2_SCL_MUX_BASE, I2C2_SCL_CFG_BASE, I2C2_SCL_INPUT_BASE, I2C2_SDA_MUX_BASE, I2C2_SDA_CFG_BASE, I2C2_SDA_INPUT_BASE},  
    {I2C3, I2C3_SCL_MUX_BASE, I2C3_SCL_CFG_BASE, I2C3_SCL_INPUT_BASE, I2C3_SDA_MUX_BASE, I2C3_SDA_CFG_BASE, I2C3_SDA_INPUT_BASE},  
    {I2C4, I2C4_SCL_MUX_BASE, I2C4_SCL_CFG_BASE, I2C4_SCL_INPUT_BASE, I2C4_SDA_MUX_BASE, I2C4_SDA_CFG_BASE, I2C4_SDA_INPUT_BASE},  
};  
  1. 将物理地址转为虚拟地址,代码如下:
for(i = 0; i < sizeof(addr_config) / sizeof(addr_config[0]); i++)  
{  
   addr_config[i].i2c                 = (I2C_Type *)rt_hw_kernel_phys_to_virt((void*)(addr_config[i].i2c), 0x1000);  
   addr_config[i].i2c_scl_mux_base    = (size_t)rt_hw_kernel_phys_to_virt((void*)(addr_config[i].i2c_scl_mux_base), 0x1000);  
   addr_config[i].i2c_scl_config_base = (size_t)rt_hw_kernel_phys_to_virt((void*)(addr_config[i].i2c_scl_config_base), 0x1000);  
   addr_config[i].i2c_scl_input_base  = (size_t)rt_hw_kernel_phys_to_virt((void*)(addr_config[i].i2c_scl_input_base), 0x1000);  
   addr_config[i].i2c_sda_mux_base    = (size_t)rt_hw_kernel_phys_to_virt((void*)(addr_config[i].i2c_sda_mux_base), 0x1000);  
   addr_config[i].i2c_sda_config_base = (size_t)rt_hw_kernel_phys_to_virt((void*)(addr_config[i].i2c_sda_config_base), 0x1000);  
   addr_config[i].i2c_sda_input_base  = (size_t)rt_hw_kernel_phys_to_virt((void*)(addr_config[i].i2c_sda_input_base), 0x1000);  
}  
  1. 在imx6ull中,I2C需要的步骤,引脚初始化为I2C,然后I2C总线初始化便可以了。
  2. 目前imx6ull上,rt-thread和rt-smart都适配I2C,所以可以先看一下rt-thread的仓库,然后再看rt-smart的仓库,可能更加理解它的区别。

I2C的应用:

  1. 100ask\_imx6ull中,板载有ap3216c传感器,挂载在I2C1总线上。而且RT-Thread中有相应的软件包,对接了RT-Thread的传感器设备框架,这给我验证代码提供便携。不过要在用户态中使用该软件包,还需要做一点操作,需要注册该传感器设备。
int ap3216c_test()  
{  
    struct rt_sensor_config cfg;  
    cfg.intf.dev_name = "i2c1";  
    cfg.mode = RT_SENSOR_MODE_POLLING;  
  
    rt_hw_ap3216c_init("ap", &cfg);  
    return RT_EOK;  
}  
INIT_DEVICE_EXPORT(ap3216c_test);  
  1. 然后编译烧录,通过list\_device就可以看到相对应的设备(pr\_ap和li\_ap),如下:

image.png

  1. RT\_Thread的传感器框架很贴心,提供了测试命令(sensor\_polling li\_ap),这样就可以初步验证传感器是否正常工作,通过验证,传感器和I2C适配都能正常工作:

image.png

  1. 上面的验证都是在内核态中测试的,而这篇文章的目的是要在用户态中读取传感器数据,为了进一步了解rt-smart和RT-Thread的区别,我这个应用采用进程通信(IPC)做了例子,该例子将上一篇文章例子结合起来:
  • 有两个进程, 进程1和进程2
  • 进程1,通过接收等待进程2读取的传感器数据是否超标的状态,来进行闪灯。
  • 进程2,通过读取ap3216c传感器光强度数据,判断是否超过50lux,如果超过则通知进程1进行闪灯提示。
  1. IPC通信,详情可以查看官网:https://www.rt-thread.org/doc...
  2. 进程1,等待接收通道发来的"warning"信息,然后进行闪灯操作:
int main(int argc, char **argv)  
{  
    struct rt_device_pin_mode pin_mode;  
    struct rt_device_pin_status pin_status;  
  
    int server_ch;  
    int shmid;  
    struct rt_channel_msg msg_text;  
    char *str;  
  
    printf("RiceChen rt-smart first app\n");  
  
    /* create the IPC channel for 'server' */  
    server_ch = rt_channel_open("server", O_CREAT);  
    if (server_ch == -1) {  
        printf("Error: rt_channel_open: fail to create the IPC channel for server!\n");  
        return -1;  
    }  
    printf("\nserver: wait on the IPC channel: %d\n", server_ch);  
  
    pin_dev = rt_device_find("pin");  
    if(pin_dev == RT_NULL)  
    {  
        printf("not find pin device\n");  
        return RT_ERROR;  
    }  
  
    rt_device_open(pin_dev, RT_DEVICE_OFLAG_RDWR);  
  
    pin_mode.pin = LED_PIN;  
    pin_mode.mode = 0;      //OUTPUT  
  
    rt_device_control(pin_dev, 0, (void *)&pin_mode);  
  
    pin_status.pin = LED_PIN;  
    while(1)  
    {  
        rt_channel_recv(server_ch, &msg_text); //接收通道信息  
  
        shmid = (int)msg_text.u.d;  
        if (shmid < 0 || !(str = (char *)lwp_shmat(shmid, NULL)))  
        {  
            msg_text.u.d = (void *)-1;  
            printf("server: receive an invalid shared-memory page.\n");  
            rt_channel_reply(server_ch, &msg_text);   /* send back -1 */  
            continue;  
        }  
  
        if(strcmp(str, "warning") == 0)  //判断是否接收到"warning"信息  
        {  
            printf("light warning.\n");  
            pin_status.status = 1;  
            rt_device_write(pin_dev, 0, (void *)&pin_status, sizeof(pin_status));  
            rt_thread_mdelay(200);  
            pin_status.status = 0;  
            rt_device_write(pin_dev, 0, (void *)&pin_status, sizeof(pin_status));  
            rt_thread_mdelay(200);  
            pin_status.status = 1;  
            rt_device_write(pin_dev, 0, (void *)&pin_status, sizeof(pin_status));  
            rt_thread_mdelay(200);  
            pin_status.status = 0;  
            rt_device_write(pin_dev, 0, (void *)&pin_status, sizeof(pin_status));  
            rt_thread_mdelay(200);  
        }  
        lwp_shmdt(str);  
  
        msg_text.type = RT_CHANNEL_RAW;  
        msg_text.u.d = (void *)1;  
        rt_channel_reply(server_ch, &msg_text);  
    }  
  
    return 0;  
}  
  1. 进程2,间隔两面读取一次传感器光强度数据,然后判断是否操作50lux,超过则通过通道通知进程1进行闪灯:
int main(int argc, char **argv)  
{  
    rt_device_t ap3216c_dev;  
    struct rt_sensor_data sensor_data;  
    int res;  
  
    int server_ch;  
    char warning_msg[256] = { 0 };  
    size_t len = 0;  
    /* channel messages to send and return back */  
    struct rt_channel_msg ch_msg, ch_msg_ret;  
  
    printf("RiceChen rt-smart second app\n");  
  
    /* open the IPC channel created by 'pong' */  
    server_ch = rt_channel_open("server", 0);  
    if (server_ch == -1)  
    {  
        printf("Error: rt_channel_open: could not find the \'server\' channel!\n");  
        return -1;  
    }  
  
    ap3216c_dev = rt_device_find(SENSOR_NAME);  
    if (ap3216c_dev == RT_NULL)  
    {  
        rt_kprintf("Can't find device:%s", SENSOR_NAME);  
        return -1;  
    }  
  
    if (rt_device_open(ap3216c_dev, RT_DEVICE_FLAG_RDWR) != RT_EOK)  
    {  
        rt_kprintf("open device failed!");  
        return -1;  
    }  
    rt_device_control(ap3216c_dev, RT_SENSOR_CTRL_SET_ODR, (void *)100);  
  
    while(1)  
    {  
        res = rt_device_read(ap3216c_dev, 0, &sensor_data, 1); //读取传感器数值  
        if (res != 1)  
        {  
            rt_kprintf("read data failed!size is %d\n", res);  
        }  
        else  
        {  
            rt_kprintf("light:%5d lux, timestamp:%5d\n", sensor_data.light, sensor_data.timestamp);  
        }  
        if(sensor_data.light > 50)  //判断阈值  
        {  
            ch_msg.type = RT_CHANNEL_RAW;  
            snprintf(warning_msg, 255, "%s", "warning");  
            len = strlen(warning_msg) + 1;  
            warning_msg[len] = '\0';  
            int shmid = prepare_data(warning_msg, len);  
  
            if (shmid < 0)  
            {  
                printf("clent: fail to prepare the clent message.\n");  
                continue;  
            }  
            ch_msg.u.d = (void *)shmid;  
  
            rt_channel_send_recv(server_ch, &ch_msg, &ch_msg_ret); //发送警报信息  
            lwp_shmrm(shmid);  
        }  
        rt_thread_mdelay(2000);  
    }  
    rt_device_close(ap3216c_dev);  
    rt_channel_close(server_ch);  
  
    return 0;  
}  

演示

image.png

推荐阅读

1 阅读 440
推荐阅读
0 条评论
关注数
11
内容数
17
一个周末很无聊的嵌入式软件工程师,写写经验,写写总结。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
Arm中国学堂公众号
关注Arm中国学堂
实时获取免费 Arm 教学资源信息
Arm中国招聘公众号
关注Arm中国招聘
实时获取 Arm 中国职位信息