Celex5-MIPI 代码测试

1. celex相机事件的极性是通过计算得到的,和像素点的上一个灰度值(准确来说是analog to digital converter(ADC)值)比较得到

// src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5dataprocessor.cpp line:2700
//--- cal polarity ---
if (adc12bit > m_pLastADC[index])
	eventData.polarity = 1;
else if (adc12bit < m_pLastADC[index])
	eventData.polarity = -1;
else
	eventData.polarity = 0;

2. open usb 追踪

2.1 类CeleX5ROSBean的成员调用CeleX5的openSensor函数

// src/CeleX5-ROS/celex5_ros/src/bean/celex5_ros_bean.cpp 
// void celex5_ros::CeleX5ROSBean::Run() 
p_celex5_sensor_->openSensor(static_cast<CeleX5::DeviceType>(device_type));
// std::shared_ptr<CeleX5> p_celex5_sensor_;

2.2 类CeleX5的成员调用CeleDriver的openUSB函数

// src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5.cpp 
bool CeleX5::openSensor(DeviceType type)
if (!m_pCeleDriver->openUSB())
// CeleDriver* m_pCeleDriver;

2.3 类CeleDriver的成员调用Cypress的openUSB函数

// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/celedriver.cpp
bool CeleDriver::openUSB()
{
	return m_pCypress->openUSB();
}
// Cypress*    m_pCypress;

2.4 类Cypress继承了USBInterface,因此也继承了USBInterface的公有函数usbOpen

// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/cypress.cpp
bool Cypress::openUSB(void)
{
	if (usbOpen(CYPRESS_DEVICE_VENDOR_ID, CYPRESS_DEVICE_PRODUCT_ID, LIBUSB_TRANSFER_TYPE_BULK))
	{
		return true;
	}
	usbClose();
	return false;
}
// class Cypress : public USBInterface

2.5 usbOpen主要用来打开usb设备,并获得usb设备的句柄:m_pDeviceHandle和视频末端地址:m_iVideoEndpointAddress,这两个变量很重要!!!

// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/usbinterface.cpp
// 只考虑对后面代码有用的关键变量
bool USBInterface::usbOpen(int vid, int pid, int transMode)
if (usbGetInterface(vid, pid, transMode) == true)
// 如果打开成功:会得到device handle  libusb_device_handle* m_pDeviceHandle;
m_pDeviceHandle = libusb_open_device_with_vid_pid(nullptr, vid, pid);

2.6 打开usb成功,获得关键变量之后,返回到CeleX5的openSensor函数,开始配置usb

// src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5.cpp
bool CeleX5::openSensor(DeviceType type)
m_emDeviceType = type;
if (!m_pCeleDriver->openUSB()) // 如果打开usb成功,才有下文,否则返回false
if (CeleX5::CeleX5_MIPI == type) // 当前就是这个type
	if (!configureSettings(type))  return false; // 配置设置
	setISOLevel(m_uiISOLevel);
	//m_pDataProcessor->setISOLevel(m_uiISOLevel);
	clearData();
	m_pDataProcessThread->start();

2.7 在配置结束之后,configureSettings函数有个很关键的步骤,开启视频流

bool CeleX5::configureSettings(CeleX5::DeviceType type)
if (m_pCeleDriver) m_pCeleDriver->openStream(); // 开启视频流
// 配置结束之后开启视频流

2.8 openStream这个函数最终是Cypress的函数,调用了继承来自USBInterface的start函数

// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/cypress.cpp
bool Cypress::openStream(void)
{
	//开启视频流
	uint8_t video_start[] = { 0x00, 0x00,0x01,0x02,0x0A,0x8B,0x02,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x00, 0x00,0x20,0x1C,0x00, 0x00,0x90,0x00, 0x00 };
	//usb_control(0x41, 0x99, 0x00, 0x00, nullptr, 0);
	if (usbControl(0x21, 0x01, 0x201, 0x0, video_start, sizeof(video_start)))
	{
		if (start() == true) //关键这个start!!!!!!!
		{
			return true;
		}
		usbControl(0x41, 0x88, 0x00, 0x00, nullptr, 0);
	}
	return false;
}

2.9 USBInterface的start函数有两个关键调用videoStart和usbAllocBulkTransfer,因为不用imu,因此暂时不考虑usbAllocInterruptTransfer

  • videoStart开启了一个分离线程,专门检测usb的数据传输是否失败,失败则关闭然后重启,videoStop可以结束这个线程
  • usbAllocBulkTransfer最终设置了接受到数据的回调函数callbackUSBTransferCompleted,获得原始数据buffer和actual_length,这里就是最原始的数据buffer来源!!!!
  • callbackUSBTransferCompleted传输正常的话会调用generate_image将buffer转化为其他数据类型!!
// src/CeleX5-ROS/celex5_ros/src/sdk/cx3driver/usbinterface.cpp
bool USBInterface::start(void)
{
    if (videoStart() == true) // 这个也是关键!!!!
    {
		bool bSucceed1 = false, bSucceed2 = false;
		if (usbAllocBulkTransfer() == true)
		{
			bSucceed1 = true;
			printf("usb_alloc_bulk_transfer was successful!\n");
			//return true;
		}
		//added by xiaoqin @2019.06.11 for receiving IMU data
		if (g_bUsingIMUCallback)
		{
			if (usbAllocInterruptTransfer() == true)
			{
				bSucceed2 = true;
				printf("usb_alloc_interrupt_transfer was successful!\n");
				//return true;
			}
		}
		if (bSucceed1)
		{
			return true;
		}
        videoStop();
    }
    return false;
}
// videoStart()开启了一个后台线程!!!!
/*
*  @function: videoStart
*  @brief   : start USB transfer thread
*/
bool USBInterface::videoStart(void)
{
	m_bRunning = true;
	m_threadProcessor = std::thread(&USBInterface::worker, this);
	m_threadProcessor.detach();
    // detach()函数
    // 称为分离线程函数,使用detach()函数会让线程在后台运行,即说明主线程不会等待子线程运行结束才结束
    // 通常称分离线程为守护线程(daemon threads),UNIX中守护线程是指,没有任何显式的用户接口,并在后台运行的线程。
    // 这种线程的特点就是长时间运行;线程的生命周期可能会从某一个应用起始到结束
	return true;
}

3. 追踪void generate_image(uint8_t *buffer, int length)

在生成数据之后,首先调用的就是这个函数,这个函数是对g_PackageList包数组的循环填充,usb传递过来的数据是一个一个的包,这个函数将包一个一个放入g_PackageList中,g_PackageList是一个类数组,类的成员有m_pPackageBuffer,专门用来存放包数据。g_uiTailIndex表示将要填充的位置,填充完成之后会指向下一个位置,因此只要不读取g_uiTailIndex指向的位置,就不会发生数据冲突!!!!g_uiPackageCount描述未读的包数量,g_uiHeadIndex指向下一个要读的包位置,包读取完成之后才会指向下一个位置,或者未读的包数量超过最大之后,因为当未读的包数量达到最大时,g_uiTailIndex和g_uiHeadIndex相差1个单位,也就是g_uiTailIndex当前指向的读取位置。

void generate_image(uint8_t *buffer, int length)
{
	if (current_package == nullptr) // current_package这玩意儿只是个指针
	{
		current_package = &g_PackageList[g_uiTailIndex]; // 指向数据末尾地址
		// CPackage    g_PackageList[MAX_IMAGE_BUFFER_NUMBER];
		//ofTest << "------- " << g_uiTailIndex << std::endl;
		current_package->clearData(); // 状态设置为空,偏置为0 m_emStatus = BUFFER_STATUS_EMPTY; m_uiOffset = 0;
		// 这里的clearData只是针对末尾的这个数据,而不是整个g_PackageList数组!!!!
		if (g_uiPackageCount >= MAX_IMAGE_BUFFER_NUMBER) // 先假设这个不发生,那就只是获取数组末端的指针和设置状态
		{
			g_uiPackageCount--; 
			g_uiHeadIndex++; 
			// 当不考虑读取导致的g_uiPackageCount变化时,进入新一轮数据加入后,数据会重新从0开始加入,也就是舍弃掉0位置的包
			// 时间最早的数据index变为了1,因此g_uiHeadIndex++,同时g_uiPackageCount--
			// g_uiPackageCount表示未读的包数量
			if (g_uiHeadIndex >= MAX_IMAGE_BUFFER_NUMBER)
				g_uiHeadIndex = 0;
			printf("------- generate_image: buffer is full! -------\n");
			//ofTest << "------- generate_image: buffer is full! -------" << std::endl;
		}
	}
	//
	if (current_package) // 当前的package
	{
		if (!g_bUsingIMUCallback) // 没有使用IMU,这里是false,不考虑
		{
			if (buffer[7] == 1)
			{
				IMURawData imu_data;
				memcpy(imu_data.imuData, buffer + 8, 20);
				imu_data.timestamp = getTimeStamp();

				current_package->m_vecIMUData.push_back(imu_data);
			}
		}
		if (buffer[1] & 0x02) // 这是什么标志位??表示 Package 的类型(ID)
		{
			current_package->m_lTimestampEnd = getTimeStamp(); // 获取时间戳
			//cout << "------------ image time stamp = " << current_package->m_lTime_Stamp_End << endl;
			buffer[length] = *(buffer + 6);
			current_package->insert(buffer + buffer[0], length - buffer[0] + 1);
			// 将buffer保存到这个m_pPackageBuffer = new uint8_t[MAX_ELEMENT_BUFFER_SIZE * 50];
			//current_package->Insert(buffer + 6, 1);
			current_package->end(); // m_emStatus = BUFFER_STATUS_FULL; m_uiOffset描述m_pPackageBuffer的偏置offset
			current_package = nullptr; // 重新指向空

			g_uiTailIndex++; 
			// g_uiTailIndex的值基本只在这里赋予,除了clearData
			// g_PackageList这个数组里循环填充
			if (g_uiTailIndex >= MAX_IMAGE_BUFFER_NUMBER)
				g_uiTailIndex = 0;
			g_uiPackageCount++;
		}
		else // 没有标志位的话就是package没有读取结束
		{
			current_package->insert(buffer + buffer[0], length - buffer[0]);
		}
	}
}

4. 数据的从usb获取后就存放在g_PackageList中,接下来考虑其他程序的读取。从g_PackageList开始反推

4.1 g_PackageList只在以下getSensorData函数中使用

bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length)
bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imuData)
4.1.1 首先来看第一个getSensorData函数:
bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length)
{
	// 不读时间戳,也不读imu数据
	if (g_uiPackageCount > 0) // 未读包数量
	{
		g_mtxSensorData.lock(); // 线程锁起到保护g_IMURawDataList的作用,但是目前没有使用这个数据。。。。。。
		if (g_uiHeadIndex != g_uiTailIndex) // 太机智了!!!!!!!!避免了数据冲突!!!!牛逼!!!
		{
			g_PackageList[g_uiHeadIndex].getData(pData, length);
			// 将包中的m_pPackageBuffer全部读入pData,length就是偏置,之后将包的偏置设置为0,状态为空,防止重复读取
			if (g_bUsingIMUCallback)
			{
				g_IMURawDataList.clear();
			}
			g_PackageList[g_uiHeadIndex].m_vecIMUData.clear(); // 没有用imu,不考虑
			g_uiHeadIndex++;
			if (g_uiHeadIndex >= MAX_IMAGE_BUFFER_NUMBER)
				g_uiHeadIndex = 0;
			g_uiPackageCount--;
			if (length > 0)
			{
				g_mtxSensorData.unlock();
				return true;
			}
		}
		else
		{}
		g_mtxSensorData.unlock();
	}
}
4.1.2 首先来看第二个getSensorData函数:

只是加了时间戳和IMU数据的读取而已!没啥变化!

bool CeleDriver::getSensorData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imuData)
{
	if (g_uiPackageCount > 0)
	{
		g_mtxSensorData.lock();
		if (g_uiHeadIndex != g_uiTailIndex)
		{
			g_PackageList[g_uiHeadIndex].getData(pData, length);
			timestampEnd = g_PackageList[g_uiHeadIndex].m_lTimestampEnd;
			if (g_bUsingIMUCallback)
			{
				imuData = g_IMURawDataList;
				g_IMURawDataList.clear();
			}
			else
			{
				imuData = g_PackageList[g_uiHeadIndex].m_vecIMUData;
			}
			g_PackageList[g_uiHeadIndex].m_vecIMUData.clear();
			g_uiHeadIndex++;
			if (g_uiHeadIndex >= MAX_IMAGE_BUFFER_NUMBER)
				g_uiHeadIndex = 0;
			g_uiPackageCount--;
			if (length > 0)
			{
				g_mtxSensorData.unlock();
				return true;
			}
		}
		else
		{ }
		g_mtxSensorData.unlock();
	}
	return false;
}

4.3 getSensorData被getCeleXRawData函数调用

void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length)
void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imu_data)
4.3.1 考虑第一个函数,就是单纯调用一下,没其他处理
void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length)
{
	if (CeleX5::CeleX5_MIPI != m_emDeviceType)
	{
		return;
	}
	m_pCeleDriver->getSensorData(pData, length);
}
4.3.2 考虑第二个函数,因为不用imu数据,不考虑写入bin,只考虑前三种模式,因此和第一个没什么差别
void CeleX5::getCeleXRawData(uint8_t* pData, uint32_t& length, std::time_t& timestampEnd, std::vector<IMURawData>& imu_data)
{
	if (CeleX5::CeleX5_MIPI != m_emDeviceType)
	{
		return;
	}
	std::vector<IMURawData> imu_raw_data;
	// pData, length, timestampEnd, imu_raw_data 都是通过getSensorData获得数据,不是输入
	if (m_pCeleDriver->getSensorData(pData, length, timestampEnd, imu_raw_data)) // 获取数据
	{
		imu_data = std::vector<IMURawData>(imu_raw_data.size()); // 不考虑imu
		for (int i = 0; i < imu_raw_data.size(); i++)
		{
			//cout << "--------------"<<imu_raw_data[i].time_stamp << endl;
			memcpy(imu_data[i].imuData, imu_raw_data[i].imuData, sizeof(imu_raw_data[i].imuData)); // 复制数据到imu_data
			imu_data[i].timestamp = imu_raw_data[i].timestamp; // 获得时间戳
			//imu_data[i] = ((struct IMURawData*)&imu_raw_data[i]);
		}
		//record sensor data
		if (m_pDataRecorder->isRecording()) // 不考虑写入bin,可以考虑写入txt,或者其他!!!!
		{
			m_pDataRecorder->writeData(pData, length, timestampEnd, imu_data);
		}

		//calculate the package count per second
		if (!m_bLoopModeEnabled && getSensorFixedMode() > 2)
		{
			m_uiPackageCounter++;
#ifdef _WIN32
			uint32_t t2 = GetTickCount();
#else
			uint32_t t2 = clock() / 1000;
#endif
			m_uiPackageTDiff += (t2 - m_uiPackageBeginT);
			m_uiPackageBeginT = t2;
			if (m_uiPackageTDiff > 1000)
			{
				//cout << "--- package count = " << m_uiPackageCounter << endl;
				m_pDataProcessor->getProcessedData()->setFullFrameFPS(m_uiPackageCountPS);
				m_uiPackageTDiff = 0;
				m_uiPackageCountPS = m_uiPackageCounter;
				m_uiPackageCounter = 0;
			}
		}
	}
}

4.4 getCeleXRawData被DataProcessThread线程中被调用

通过getCeleXRawData获得数据,之后用processMIPIData处理数据。

void DataProcessThread::run()
{
	while (m_bRun)
	{
		//cout << "---------- DataProcessThread::run ----------" << endl;
#ifndef _WIN32
		pthread_mutex_lock(&m_mutex); // //这个mutex主要是用来保证pthread_cond_wait的并发性
		//  //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何这里要有一个while (m_bSuspended)呢?
		// 因为pthread_cond_wait里的线程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。
		// 这个时候,应该让线程继续进入pthread_cond_wait
		while (m_bSuspended)
		{
			pthread_cond_wait(&m_cond, &m_mutex);
		}
		// pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,然后阻塞在等待对列里休眠,
		// 直到再次被唤醒(大多数情况下是等待的条件成立而被唤醒,唤醒后,
		// 该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源

		pthread_mutex_unlock(&m_mutex); // 在这就释放了?????
#endif
		if (m_bPlaybackBinFile) //for playback
		{	
		}
		else //--- for real display ---
		{
			std::time_t time_stamp_end = 0;
			std::vector<IMURawData> imu_data;
			uint32_t dataLen = 0;
			m_pCeleX5->getCeleXRawData(m_pMipiPackage, dataLen, time_stamp_end, imu_data); 
			// 获取原始数据,形参都是返回的数据
			if (dataLen > 0)
			{
				if (!m_bRecordData || (m_bRecordData && m_bShowImagesEnabled))
					m_pDataProcessor->processMIPIData(m_pMipiPackage, dataLen, time_stamp_end, imu_data); // 数据处理
			}
		}
	}
}
4.4.1 processMIPIData数据处理函数
void CeleX5DataProcessor::processMIPIData(uint8_t* pData, uint32_t dataSize, std::time_t time_stamp_end, std::vector<IMURawData>& imuData)
{
	int size = imuData.size();
	for (int i = 0; i < size; i++) // 不考虑imu
	{
		m_vectorIMURawData.push_back(imuData[i]);
	}
	if (*(pData + dataSize - 1) == 0 /*|| dataSize > 500000*/) //full pic mode: package size = 1536001 = 1280 * 800 * 1.5 + 1
	{	}
	else
	{
		if (m_iMIPIDataFormat == 0)// Format0 (CSR_73 = 0), Package Size: 1024*200*1.5 = 307200 Byte
		{
			parseEventDataFormat0(pData, dataSize, time_stamp_end);
		}
		else if (m_iMIPIDataFormat == 1)// Format1 (CSR_73 = 1), Package Size: 1024*200*1.75 = 358400 Byte
		{
			parseEventDataFormat1(pData, dataSize, time_stamp_end);
		}
		// 当前使用2
		else if (m_iMIPIDataFormat == 2)// Format2 (CSR_73 = 2), Package Size: 1024*200*1.75 = 358400 Byte
		{
			parseEventDataFormat2(pData, dataSize, time_stamp_end);
		}
	}
	m_lLastPackageTimestamp = time_stamp_end;
}
4.4.1.1 parseEventDataFormat2数据处理函数

4.5 DataProcessThread线程类的实例m_pDataProcessThread在 CeleX5::openSensor开启线程

在usb配置好之后,线程就开启了

4.5.1 m_iMIPIDataFormat(1)改为1

尝试把src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5dataprocessor.cpp, m_iMIPIDataFormat(1)改为1
没发现问题!这里直接决定了数据处理的方式,在src/CeleX5-ROS/celex5_ros/src/sdk/eventproc/celex5dataprocessor.cpp的CeleX5DataProcessor::processMIPIData函数中数据的解析形式!

4.5.2 parseEventDataFormat1到图片生成

parseEventDataFormat1每生成一个数据,调用processMIPIEventTimestamp,processMIPIEventTimestamp每次调用checkIfShowImage,当达到一定的时间段之后,checkIfShowImage开始show图片bShowImage = true;调用创建图片函数:createImage(0);

4.5.3 默认固定模式改变出错

将src/CeleX5-ROS/celex5_ros/launch/celex5_ros_node.launch里的
改为2,就出现了下面的错误
CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 355216

4.5.3.1 追踪这个问题!
  • parseEventDataFormat1的package的来源:初步来源于m_pDataProcessor->processMIPIData(m_pMipiPackage, dataLen, time_stamp_end, imu_data)
  • processMIPIData 的数据来源线程DataProcessThread::run里的m_pCeleX5->getCeleXRawData(m_pMipiPackage, dataLen, time_stamp_end, imu_data);
  • getCeleXRawData的数据来源:m_pCeleDriver->getSensorData(pData, length, timestampEnd, imu_raw_data)
  • getSensorData数据来源g_PackageList[g_uiHeadIndex].getData(pData, length);
  • g_PackageList的数据只在这里生成generate_image(uint8_t *buffer, int length),current_package->insert(buffer + buffer[0], length - buffer[0] + 1);除了这里涉及长度外,其他都没有处理,都是直接使用
  • generate_image函数被调用callbackUSBTransferCompleted(libusb_transfer *xfr),而这个函数就是usb数据传递过来的回调函数。
  • 错误对比
    m_uiOffset:42672
    m_uiOffset:85344
    m_uiOffset:128016
    m_uiOffset:170688
    m_uiOffset:213360
    m_uiOffset:256032
    m_uiOffset:298704
    m_uiOffset:341376
    m_uiOffset:355216
    CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 355216
    m_uiOffset:42672
    m_uiOffset:85344
    m_uiOffset:128016
    m_uiOffset:170688
    m_uiOffset:213360
    m_uiOffset:256032
    m_uiOffset:298704
    m_uiOffset:341376
    m_uiOffset:357001
    最后一个出错了
  • 错误分析:由此看来应该是配置usb时候出现的问题,因为从数据生成之后的处理应该没啥问题。固定模式如何影响usb数据传输。。。。。。我草
4.5.3.2 追踪usb配置
  • callbackUSBTransferCompleted设置alloc_bulk_transfer(libusb_device_handle
    *device_handle, uint8_t address, uint8_t *buffer)
  • alloc_bulk_transfer:USBInterface::usbAllocBulkTransfer(void)中alloc_bulk_transfer(m_pDeviceHandle, m_iVideoEndpointAddress, m_uiBulkBuffer[i]);
  • 所以关键还是m_pDeviceHandlem_iVideoEndpointAddress
  • m_pDeviceHandle:在函数USBInterface::usbOpen(int vid, int pid, int transMode)中
USBInterface::usbOpen(int vid, int pid, int transMode)
{
    if (libusb_init(nullptr) == LIBUSB_SUCCESS)
    {
		m_vecInterfaceNumberList.clear(); // std::vector<int> m_vecInterfaceNumberList;
        if (usbGetInterface(vid, pid, transMode) == true)//m_iVideoEndpointAddress
        {
			m_pDeviceHandle = libusb_open_device_with_vid_pid(nullptr, vid, pid);
  • 但是usbOpen的参数都是默认的。。。。。
  • 尝试其他模式1
    m_uiOffset:42672
    m_uiOffset:85344
    m_uiOffset:128016
    m_uiOffset:170688
    m_uiOffset:213360
    m_uiOffset:256032
    m_uiOffset:298704
    m_uiOffset:341376
    m_uiOffset:355216
    CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 355216
    saveIntensityEvent

CeleX5::setSensorFixedMode(CeleX5Mode mode)
m_iEventDataFormat = 1;
wireIn(EVENT_PACKET_SELECT, m_iEventDataFormat, 0xFF); //EVENT_PACKET_SELECT
m_pDataProcessor->setMIPIDataFormat(m_iEventDataFormat);

writeRegister(MIPI_ROW_NUM_EVENT_H, -1, MIPI_ROW_NUM_EVENT_L, 400); //
CeleX5DataProcessor::parseEventDataFormat1: Not a full package: 712216

好久没来了,继续作死,这次多删除一点东西!

1. 固定模式切换

直接从src/CeleX5-ROS/celex5_ros/launch/celex5_ros_node.launch里修改fixed_mode为2的话会失败
通过Event_Intensity_Mode获得的灰度也只是事件位置处的灰度,不是整张照片的灰度!!!

2. 追踪full pic生成

  • cv::Mat full_frame_img = p_celex5_sensor_->getFullPicMat();获取full pic
  • memcpy(buffer, m_pFullPic[s_uiReadFrameIndex], CELEX5_PIXELS_NUMBER);m_pFullPic转化
  • uint8_t* pFullPic = m_pFullPic[s_uiWriteFrameIndex];m_pFullPic生成bool CeleX5DataProcessor::createImage(std::time_t timestampEnd),所以图片都是在这里生成的!!!
  • 看createImage被什么调用void CeleX5DataProcessor::checkIfShowImage()、void CeleX5DataProcessor::processFullPicData(uint8_t* pData, int dataSize, std::time_t timestampEnd)
  • checkIfShowImage 只被processMIPIEventTimestamp调用,所以这里生成的应该是事件图片,而不是full pic,所以应该是processFullPicData生成的full pic
  • processFullPicData只被void CeleX5DataProcessor::processMIPIData调用
  • processMIPIData只被void DataProcessThread::run()调用
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MIPI驱动代码通常由物理层驱动和显示控制器驱动两部分组成。以下是一个简单的MIPI DSI驱动例子,展示了如何使用Linux内核的MIPI DSI子系统来配置和控制MIPI显示器。 物理层驱动: ```c static struct mipi_dsi_phy_ops phy_ops = { .reset = mipi_dsi_phy_reset, .power_on = mipi_dsi_phy_power_on, .power_off = mipi_dsi_phy_power_off, .prepare = mipi_dsi_phy_prepare, .unprepare = mipi_dsi_phy_unprepare, .enable_hs = mipi_dsi_phy_enable_hs, .enable_lp = mipi_dsi_phy_enable_lp, .set_tlp = mipi_dsi_phy_set_tlp, .set_hs_clk = mipi_dsi_phy_set_hs_clk, }; static struct mipi_dsi_phy dsi_phy = { .dev = { .of_node = dev->of_node, }, .ops = &phy_ops, }; ret = mipi_dsi_attach_phy(dev, &dsi_phy); if (ret < 0) { dev_err(dev, "failed to attach phy\n"); return ret; } ``` 显示控制器驱动: ```c static struct mipi_dsi_device_ops dsi_ops = { .attach = mipi_dsi_attach, .detach = mipi_dsi_detach, .transfer = mipi_dsi_transfer, }; static struct mipi_dsi_device dsi_device = { .dev = { .of_node = dev->of_node, }, .mode_flags = MIPI_DSI_MODE_VIDEO, .lanes = 2, .format = MIPI_DSI_FMT_RGB888, .ops = &dsi_ops, }; ret = mipi_dsi_attach(&dsi_device); if (ret < 0) { dev_err(dev, "failed to attach dsi device\n"); return ret; } ``` 这个例子中,mipi_dsi_phy结构体定义了物理层驱动所需的函数指针。在mipi_dsi_attach_phy函数中,将DSI设备与物理层驱动绑定起来。 同时,mipi_dsi_device结构体定义了显示控制器驱动所需的参数,包括像素格式、数据通道数等。在mipi_dsi_attach函数中,将DSI设备与显示控制器驱动绑定起来,以便后续调用mipi_dsi_transfer函数来传输显示数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值