以ets_airspeed.cpp为例
以airspeed为例,首先看到src\drivers\differential_pressure\ets\ets_airspeed.cpp这个位置的文件
看到measure函数
这里的READ_CMD实际上是寄存器的地址 这个函数的功能在transfer中实现
int
ETSAirspeed::measure()
{
int ret;
/*
* Send the command to begin a measurement.
*/
uint8_t cmd = READ_CMD; //read cmd其实是寄存器的地址
ret = transfer(&cmd, 1, nullptr, 0);//看这里这个transfer
if (OK != ret) {
perf_count(_comms_errors);
}
return ret;
}
所以需要找到transfer函数 位置在src\lib\drivers\device\nuttx\I2C.cpp
I2C transfer函数
这里先看定义 send指针是发送给设备的数据 是一个指针。 send_len和recv_len一般是有一个是1一个是0表示我这次做的操作是读还是写
I2C::transfer(const uint8_t *send, const unsigned send_len, uint8_t *recv, const unsigned recv_len)
像在measure中,应该是要向传感器请求数据 那么也就是发送命令?所以这里recv_len是0然后,用于接受设备数据的缓冲区指针*recv这里也是nullptr表示不需要存储接受的设备数据。然后send说过了是一个寄存器的地址 地址中的内容就是发送的内容
if (send_len > 0) {
msgv[msgs].frequency = _bus_clocks[get_device_bus() - 1];
msgv[msgs].addr = get_device_address();
msgv[msgs].flags = 0;
msgv[msgs].buffer = const_cast<uint8_t *>(send);
msgv[msgs].length = send_len;
msgs++;
}
if (recv_len > 0) {
msgv[msgs].frequency = _bus_clocks[get_device_bus() - 1];
msgv[msgs].addr = get_device_address();
msgv[msgs].flags = I2C_M_READ;
msgv[msgs].buffer = recv;
msgv[msgs].length = recv_len;
msgs++;
}
接下来transfer会对send_len和recv_len做判断 如果是发送就走发送的内容 否则就是接收
在这里msgv是一个i2c_msg的消息格式类型
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 */
};
这里比如 当读数据也就是recv_len非零是 flag被设置成0x0001 /* Read data, from slave to master */从属到主机
设置完之后还是没有执行写、读操作 具体的执行函数在
int ret_transfer = I2C_TRANSFER(_dev, &msgv[0], msgs);
这个函数其实是一个宏可以找到具体的执行操作
#define I2C_TRANSFER(d,m,c) ((d)->ops->transfer(d,m,c))
三个参数
dev是一组设备特定的参数 就是说每个设备自己的不变的id
msgs是指向消息的指针
count是执行的传输次数
但是后面这个transfer(有三个传递参数)的定义是什么
首先不知道ops是什么
struct i2c_master_s
{
FAR const struct i2c_ops_s *ops; /* I2C vtable */
};
这里看不下去了 大概有个概念measure函数就是在传感器从传感器读数据之前要发什么内容,就在measure中实现
int
ETSAirspeed::collect()
然后是进入collect函数
首先定义了一个两个元素的数组
然后使用transfer函数来做读操作
/* read from the sensor */
uint8_t val[2] = {0, 0};
perf_begin(_sample_perf);
ret = transfer(nullptr, 0, &val[0], 2);
可以看到这里send_len是0
而recv是读数据的数组位置(取地址数组[0])然后要读两个所以recv_len=2
collect后面的内容是对读到的数据进行处理 打包时间标签 然后整合成一个report
report是一个消息被发布出去(uorb)
_airspeed_pub.publish(report);
看measure和collect,发和收最后都是transfer来完成
发和收的区分在于msgv.flags 区分
ads1115驱动
我的任务是根据自己装的一个角度传感器-过一个ADC(ads1115)-连接飞控的i2c口,读取数据并将数据采集作为一个实时的消息话题topic
我的理解是:模拟输出传感器+adc等于其他的传感器
巧的是adc文件夹下由两部分
ads1115和board_adc,难道是板子上继承的传感器过板载adc,外挂传感器过ads1115(PX4已经考虑好了?)
ads1115文件夹下面包含
ads1115.h
包含了一些ads1115的寄存器地址,看不太懂,有用到的我陈列在下面
#define ADDRESSPOINTER_REG_CONFIG 0x01 //配置寄存器地址
ads1115.cpp是放函数方法的定义
比如ads1115::readReg是读寄存器(字面意思)实现方法也是用transfer
那根据上面transfer的认识 这里看起来既读也在写?
int ADS1115::readReg(uint8_t addr, uint8_t *buf, size_t len)
{
return transfer(&addr, 1, buf, len);
}
所以readReg必定包含一次send操作,send的对象比如说是ads1115的寄存器
getmeasurement
跟着这个函数来认识一下驱动怎么跟ads1115联动
首先初始化缓冲区。这里也是很有讲究 这里使用两个8位(1字节)数组元素来保存一个16位的寄存器值。这是因为许多 I2C 或 SPI 设备的寄存器是 16 位的,但它们的通信总线一次只能传输 8 位数据。
假设你要读取一个 16 位寄存器。读取操作可能需要两次 8 位的数据传输:
读取高字节(MSB,最高字节)。
读取低字节(LSB,最低字节)。
uint8_t buf[2] = {0x00};
readReg函数从配置寄存器读取数据
readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1);
数据当然是保存在buf缓冲区里面,为什么这里是1呢?按照上面的说法这里应该读两次 因为一次是8位
但是这里只需要读取高字节的数据MSB,所以一次就够了
接下来需要解析当前的测量通道
switch ((buf[0] & (uint8_t) 0x70) >> 4) {
case 0x04:
channel = A0;
break;
case 0x05:
channel = A1;
break;
case 0x06:
channel = A2;
break;
case 0x07:
channel = A3;
break;
default:
return Invalid;
}
这部分我写在另一篇文章中了,不赘述。总之就是判断channel的值并设置
读取转换寄存器的值
readReg(ADDRESSPOINTER_REG_CONVERSATION, buf, 2);
uint16_t raw_adc_val = buf[0] * 256 + buf[1];
这里看的出来就是做read有两个动作
高位*256拼成一个16位原始ADC值
检查和处理负值
if (raw_adc_val & (uint16_t) 0x8000) { // Negative value
raw_adc_val = ~raw_adc_val + 1; // 2's complement
*value = -raw_adc_val;
} else {
*value = raw_adc_val;
}
值存储到value指针指向的内存位置
最后return的内容是当前通道 channel
ADS1115::ChannelSelection ADS1115::getMeasurement(int16_t *value)
函数的功能是获得测量值 保存到value 返回测量通道
这是单次测量的函数方法
cyclemeasure
这个函数的目的是进行一次测量 并在不同通道之间循环测量
ADS1115::ChannelSelection ADS1115::cycleMeasure(int16_t *value) //返回类型还是channel通道
{
uint8_t buf[2] = {0x00};
readReg(ADDRESSPOINTER_REG_CONFIG, buf, 1); // Pull config register
ChannelSelection channel;
uint8_t next_mux_reg = CONFIG_HIGH_MUX_P0NG;
//一样的操作因为这一步的目的是获取当前的通道值
switch ((buf[0] & (uint8_t) 0x70) >> 4) {
case 0x04:
channel = A0;
next_mux_reg = CONFIG_HIGH_MUX_P1NG;
break;
case 0x05:
channel = A1;
next_mux_reg = CONFIG_HIGH_MUX_P2NG;
break;
case 0x06:
channel = A2;
next_mux_reg = CONFIG_HIGH_MUX_P3NG;
break;
case 0x07:
channel = A3;
next_mux_reg = CONFIG_HIGH_MUX_P0NG;
break;
default:
return Invalid;
}//根据当前这次的通道 来给nextmuxreg赋值这次是什么 下一次是什么
readReg(ADDRESSPOINTER_REG_CONVERSATION, buf, 2);
uint16_t raw_adc_val = buf[0] * 256 + buf[1];
//这里是具体的读数据操作
if (raw_adc_val & (uint16_t) 0x8000) { // Negetive value
raw_adc_val = ~raw_adc_val + 1; // 2's complement
*value = -raw_adc_val;
} else {
*value = raw_adc_val;
}
buf[0] = CONFIG_HIGH_OS_START_SINGLE | next_mux_reg | CONFIG_HIGH_PGA_6144 | CONFIG_HIGH_MODE_SS;
buf[1] = CONFIG_LOW_DR_250SPS | CONFIG_LOW_COMP_MODE_TRADITIONAL | CONFIG_LOW_COMP_POL_RESET |
CONFIG_LOW_COMP_LAT_NONE | CONFIG_LOW_COMP_QU_DISABLE;
writeReg(ADDRESSPOINTER_REG_CONFIG, buf, 2); // must write whole register to take effect
//这里内容单次测量没有见过 是什么意思呢?
return channel;
}
这里看一下最后writereg前的内容
buf0即高字节分别是:
CONFIG_HIGH_OS_START_SINGLE:启动单次转换。
next_mux_reg:下一个通道的MUX配置。
CONFIG_HIGH_PGA_6144:设定增益。
CONFIG_HIGH_MODE_SS:单次转换模式。
低字节的共8位
分别是 传输速率CONFIG_LOW_DR_250SPS(表示250次每秒)
比较器模式CONFIG_LOW_COMP_MODE_TRADITIONAL表示传统模式
比较器极性这里设置低电平有效
比较器锁定这里设置none不锁定
比较器队列禁用设置disable
顺便补充一下配置寄存器的位定义
ADS1115的配置寄存器是一个16位的寄存器,其中高8位和低8位分别设置不同的功能。
低8位
Bit 7-5: 数据速率(DR)
这三个位用于设置ADC的采样率。不同的组合表示不同的采样率。
Bit 4: 比较器模式(COMP_MODE)
这个位设置比较器的工作模式。传统模式为0,窗口比较器模式为1。
Bit 3: 比较器极性(COMP_POL)
这个位设置比较器输出的极性。低电平有效为0,高电平有效为1。
Bit 2: 比较器锁定(COMP_LAT)
这个位设置比较器的锁定功能。不锁定为0,锁定为1。
Bit 1-0: 比较器队列/禁用(COMP_QUE)
这两个位设置比较器的队列功能或禁用比较器。0b11表示禁用比较器。
高8位
启动单次转换(Operational Status or Single-shot Start, OS)
1位(第15位)
用于启动一次单次转换或者指示当前转换状态。
输入多路复用器(Input Multiplexer Configuration, MUX)
3位(第14-12位)
选择ADC的输入通道。
可编程增益放大器(Programmable Gain Amplifier, PGA)
3位(第11-9位)
设置输入电压范围。
操作模式(Device Operating Mode, MODE)
1位(第8位)
选择单次转换模式或连续转换模式。
I2C配置
我使用的是fmu-v5那么i2c接口应该是硬件的一部分
在文件的boards\px4\fmu-v5\src\i2c.cpp这个位置
找到
constexpr px4_i2c_bus_t px4_i2c_buses[I2C_BUS_MAX_BUS_ITEMS] = {
initI2CBusExternal(4),
initI2CBusExternal(2),
initI2CBusExternal(1),
initI2CBusInternal(3),
};
这段代码表示系统初始化配置了4个I2C总线 分别是三个外部总线和一个内部总线(板载的传感器我想都是走内部i2c总线的吧)
这些init初始化函数是在不同的文件中被定义的。不要谎因为这些都是飞控支持的nxp芯片 i2c协议就是nxp半导体公司开发的总线协议
所以飞控用哪个nxp芯片不重要。
注意这个不是函数 是个数组
数组的元素是px4_i2c_bus_t
形式是
struct px4_i2c_bus_t {
int bus{-1}; ///< physical bus number (1, ...) (-1 means this is unused)
bool is_external; ///< static external configuration. Use px4_i2c_bus_external() to check if a bus is really external
};
这样我们就告诉飞控 我使用哪些i2c总线 这些i2c总线是内部还是外部的
当我把传感器或者什么东西接到总线上:
The driver is not started automatically (in any airframe). You will need to start it manually, either using the QGroundControl MAVLink Console (opens new window)or by adding the driver to the startup script on an SD card.
驱动不会自己运行,要么手动开启要么把驱动的startup脚本添加到sd卡里
通过console启动驱动的命令是
pcf8583 start -X -b <bus number>
-X表示是external bus外部总线
is the bus number to which the device is connected
在px4 user guide找到