前言
- RT-Thread 的AT组件,可以用于对接基于串口的蜂窝模块,如SIM800这样的模块,实现电话与短信等业务
- 目前部分蜂窝厂商,提供的Modem芯片,可能不是uart串口,而是SPI、USB或其他串口,他们提供了xMUX多路复用的组件,也就说,底层可以提供一个类uart的AT收发接口。
- 如何使用AT组件对接这种xMUX出来的AT收发接口呢?如物理上是SPI接口,通过xMUX出来几个接口,包括数据服务与AT命令的接口。
解决方法
- 这时,需要手动注册一个基于RT-Thread rt_device 字符设备,因为AT组件,就是通过字符设备,对底层进行AT命令的收发控制的。
- 如何注册这样的设备?我开个BSP_USING_UART?不用,这个字符设备不是具体的外设的接口设备,只是一个字符设备即可。
- 上一篇我们讲过把一个引脚(LED)抽象成一个设备,本篇就用这个方法,改改名字,抽象一个ril设备出来,用于对接Modem xMUX的AT命令接口。
- 后续可以根据这个思路,抽象一个【虚拟网卡】设备出来,用于对接Modem xMUX的【数据服务】接口。
操作步骤
- 验证平台:STM32L475 Pandora 开发板
board\Kconfig 配置,用于控制是否开启设备,这里使用RIL_VDEVICE命名。
config BSP_USING_RIL_VDEVICE
bool "Enable RIL Virtual Device"
default n
if BSP_USING_RIL_VDEVICE
config BSP_RIL_VDEVICE_NAME
string "RIL Virtual Device Name"
default "ril_dev"
endif
if GetDepend(['BSP_USING_RIL_VDEVICE']):
src += Glob('ril_vdevice/*.c')
-
添加的文件 -
开启AT组件
#include "ril_vdev.h"
#include "board.h"
#ifndef BSP_RIL_VDEVICE_NAME
#define BSP_RIL_VDEVICE_NAME "ril_dev"
#endif
static struct rt_device _ril_vdev;
static rt_err_t _ril_vdev_init(rt_device_t dev)
{
return RT_EOK;
}
static rt_err_t _ril_vdev_open(rt_device_t dev, rt_uint16_t oflag)
{
if (dev == RT_NULL)
return -RT_ERROR;
return RT_EOK;
}
static rt_err_t _ril_vdev_close(rt_device_t dev)
{
if (dev == RT_NULL)
return -RT_ERROR;
return RT_EOK;
}
rt_size_t _ril_vdev_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
rt_kprintf("%s : buffer = %s, size = %d\r\n", __func__, buffer, size);
return RT_EOK;
}
rt_size_t _ril_vdev_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
rt_kprintf("%s : buffer = %s, size = %d\r\n", __func__, buffer, size);
return RT_EOK;
}
rt_err_t _ril_vdev_rx_indicate(rt_device_t dev, rt_size_t size)
{
rt_kprintf("%s : rx_in size = %d\r\n", __func__, size);
return RT_EOK;
}
rt_err_t _ril_vdev_tx_complete(rt_device_t dev, void *buffer)
{
rt_kprintf("%s : buffer=%s\r\n", __func__, buffer);
return RT_EOK;
}
static rt_err_t _ril_vdev_control(rt_device_t dev, int cmd, void *args)
{
if (dev == RT_NULL)
return -RT_ERROR;
switch (cmd)
{
case RIL_VDEV_CTRL_CMD_POWER_ON:
break;
case RIL_VDEV_CTRL_CMD_POWER_OFF:
break;
default:
break;
}
return RT_EOK;
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops ril_vdev_ops =
{
_ril_vdev_init,
_ril_vdev_open,
_ril_vdev_close,
_ril_vdev_read,
_ril_vdev_write,
_ril_vdev_control
};
#endif
static int ril_vdevice_register(const char *name, void *user_data)
{
_ril_vdev.type = RT_Device_Class_Char;
_ril_vdev.rx_indicate = _ril_vdev_rx_indicate;
_ril_vdev.tx_complete = _ril_vdev_tx_complete;
#ifdef RT_USING_DEVICE_OPS
_ril_vdev.ops = &ril_vdev_ops;
#else
_ril_vdev.init = _ril_vdev_init;
_ril_vdev.open = _ril_vdev_open;
_ril_vdev.close = _ril_vdev_close;
_ril_vdev.read = _ril_vdev_read;
_ril_vdev.write = _ril_vdev_write;
_ril_vdev.control = _ril_vdev_control;
#endif
_ril_vdev.user_data = user_data;
rt_device_register(&_ril_vdev, name, RT_DEVICE_FLAG_RDWR);
return 0;
}
int ril_vdevice_init(void)
{
return ril_vdevice_register(BSP_RIL_VDEVICE_NAME, RT_NULL);
}
#ifndef __RIL_VDEV_H__
#define __RIL_VDEV_H__
#include <rtthread.h>
#include <rtdevice.h>
#define RIL_VDEV_CTRL_CMD_POWER_OFF 0xAA00
#define RIL_VDEV_CTRL_CMD_POWER_ON 0xAA01
int ril_vdevice_init(void);
#endif
ril_app.c ,ril device的操作接口,AT client设备的初始化与简单操作
#include "ril_app.h"
#include "at.h"
#define DBG_ENABLE
#define DBG_SECTION_NAME "rild.test"
#define DBG_LEVEL DBG_LOG
#include <rtdbg.h>
#ifndef BSP_RIL_VDEVICE_NAME
#define BSP_RIL_VDEVICE_NAME "ril_dev"
#endif
#ifndef BSP_RIL_RECV_MAX_SIZE
#define BSP_RIL_RECV_MAX_SIZE 1600
#endif
static rt_device_t _ril_dev = RT_NULL;
static rt_device_t get_ril_dev(void)
{
if (_ril_dev != RT_NULL)
return _ril_dev;
_ril_dev = rt_device_find(BSP_RIL_VDEVICE_NAME);
return _ril_dev;
}
static rt_err_t ril_dev_open(void)
{
rt_device_t dev = get_ril_dev();
if (dev == RT_NULL)
return -RT_ERROR;
return rt_device_open(dev, RT_DEVICE_FLAG_RDWR);
}
rt_err_t ril_dev_power_on(void)
{
rt_device_t dev = get_ril_dev();
if (dev == RT_NULL)
return -RT_ERROR;
return rt_device_control(dev, RIL_VDEV_CTRL_CMD_POWER_ON, RT_NULL);
}
rt_err_t ril_dev_power_off(void)
{
rt_device_t dev = get_ril_dev();
if (dev == RT_NULL)
return -RT_ERROR;
return rt_device_control(dev, RIL_VDEV_CTRL_CMD_POWER_OFF, RT_NULL);
}
rt_err_t ril_dev_init(void)
{
ril_vdevice_init();
return ril_dev_open();
}
int ril_at_device_init(void)
{
ril_dev_init();
at_client_init(BSP_RIL_VDEVICE_NAME, BSP_RIL_RECV_MAX_SIZE);
return 0;
}
static void at_response_dump(at_response_t resp)
{
rt_uint8_t line_num = 0;
LOG_D("%s: line_counts=%d\n", __func__, resp->line_counts);
for (line_num = 1; line_num <= resp->line_counts; line_num++)
{
const char *resp_line_buf = at_resp_get_line(resp, line_num);
if (resp_line_buf != RT_NULL)
{
LOG_D("%s:line=%d, buf=%s\n", __func__, line_num, resp_line_buf);
}
}
}
rt_err_t ril_test_check_sim(void)
{
at_response_t resp = RT_NULL;
resp = at_create_resp(64, 3, rt_tick_from_millisecond(500));
if (resp == RT_NULL)
{
LOG_E("%s:at_create_resp error!", __func__);
return -RT_ERROR;
}
at_exec_cmd(resp, "AT+CPIN?");
at_response_dump(resp);
if (at_resp_get_line_by_kw(resp, "READY"))
{
LOG_D("at_check_sim success.");
if (resp != RT_NULL)
at_delete_resp(resp);
return RT_EOK;
}
if (resp != RT_NULL)
at_delete_resp(resp);
LOG_E("at_check_sim failed.");
return -RT_ERROR;
}
MSH_CMD_EXPORT(ril_test_check_sim, ril_test_check_sim);
static void ril_test_at_cmd(int argc, char **argv)
{
at_response_t resp = RT_NULL;
char *at_cmd = RT_NULL;
if (argc >= 2)
{
at_cmd = argv[1];
}
resp = at_create_resp(1024, 4, rt_tick_from_millisecond(1000));
if (resp == RT_NULL)
{
LOG_E("%s:at_create_resp error!", __func__);
return;
}
at_exec_cmd(resp, at_cmd);
at_response_dump(resp);
if (resp != RT_NULL)
at_delete_resp(resp);
}
MSH_CMD_EXPORT(ril_test_at_cmd, ril_test_at_cmd);
INIT_DEVICE_EXPORT(ril_at_device_init);
#ifndef __RIL_APP_H__
#define __RIL_APP_H__
#include "ril_vdev.h"
rt_err_t ril_app_init(void);
rt_err_t ril_vdev_power_on(void);
rt_err_t ril_vdev_power_off(void);
int ril_at_device_init(void);
#endif
测试验证
- 构建、编译、下载,打开串口终端,
list_device 查看设备是否正常注册
\ | /
- RT - Thread Operating System
/ | \ 4.1.0 build Jan 23 2022 13:17:49
2006 - 2021 Copyright by rt-thread team
_ril_vdev_read : buffer = , size = 1
[I/at.clnt] AT client(V1.3.1) on device ril_dev initialize success.
[I/sensor] rt_sensor[humi_humidity] init success
[I/sensor] rt_sensor[temp_temperature] init success
[I/sensor] rt_sensor[baro_barometer] init success
[I/sensor] rt_sensor[gyro_gyroscope] init success
[I/sensor] rt_sensor[acce_accelerometer] init success
msh >
msh >
msh >
msh >
msh >list_de
list_device
msh >list_device
device type ref count
-------- -------------------- ----------
acce_acc Sensor Device 0
gyro_gyr Sensor Device 0
baro_bar Sensor Device 0
temp_tem Sensor Device 0
humi_hum Sensor Device 0
ril_dev Character Device 2
uart2 Character Device 0
uart1 Character Device 2
pin Miscellaneous Device 0
msh >
msh >ril_test_check_sim
_ril_vdev_write : buffer = AT+CPIN?
, size = 10
[W/at.clnt] execute command (AT+CPIN?) timeout (500 ticks)!
[D/rild.test] at_response_dump: line_counts=0
[E/rild.test] at_check_sim failed.
msh >ril_test_at_cmd AT+CSQ?
_ril_vdev_write : buffer = AT+CSQ?
, size = 9
[W/at.clnt] execute command (AT+CSQ?) timeout (1000 ticks)!
[D/rild.test] at_response_dump: line_counts=0
msh >
- 上面是发送命令,发现已经成功调用了我们注册的ril_dev write接口:_ril_vdev_write,无响应是因为没有使用xMUX接口。xMUX接口需要根据具体Modem xMUX的接口对接。
使用总结
- AT组件对接的是字符设备,不需要一定是uart设备
- 对接xMUX虚拟出来的AT命令接口,需要实现rt_device的
rt_size_t _ril_vdev_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
rt_kprintf("%s : buffer = %s, size = %d\r\n", __func__, buffer, size);
return RT_EOK;
}
rt_size_t _ril_vdev_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
rt_kprintf("%s : buffer = %s, size = %d\r\n", __func__, buffer, size);
return RT_EOK;
}
rt_err_t _ril_vdev_rx_indicate(rt_device_t dev, rt_size_t size)
{
rt_kprintf("%s : rx_in size = %d\r\n", __func__, size);
return RT_EOK;
}
- 后续可以根据AT命令做一个【虚拟Modem】设备,从而让AT组件【空跑】运转起来
小结
- 熟悉AT组件的AT client的使用
- 熟悉AT组件对接Modem RIL层的xMUX AT命令接口的方法
|