前言
目前网上分享的基本都是基于APM 2.X飞控内部Atmega2560芯片,连接ADNS-3080光流传感器的HEX文件和.pde文件,但入手一个飞控恐怕需要花费不少RMB,本文则是基于Arduino Uno开发板,探索实现了另一种操作ADNS-3080光流传感器的途径。
准备工作
硬件上,你需要1块Aruduino Uno开发板、1个ADNS-3080传感器、若干杜邦线、1个Type-B型USB转接线;软件上,你当然需要Arduino IDE啦。
文件解析
共2个文件,ADNS3080.h头文件和ADNS3080.tpp文件,主要是以C++模板特化参数1的方式,实现编译器静态编译,以此减小内存使用率。
ADNS3080.h
根据ADNS3080开发手册2,根据需要,定义了一些延时常量和寄存器地址,当然每帧像素长度30也定义了宏。
//------------ 延时常量和相关寄存器 ---------------
// 参阅ADNS-3080芯片手册
// 部分延时常量:
#define ADNS3080_T_IN_RST 500 // Input delay after reset 500us
#define ADNS3080_T_PW_RESET 10 // Reset pulse witdh 10us
#define ADNS3080_T_SRAD_MOT 75 // SPI motion read address-data delay
#define ADNS3080_T_SWW 50 // SPI time between write commands
#define ADNS3080_T_SRAD 50 // SPI time between write and read commands
#define ADNS3080_T_LOAD 10 // SROM download and frame capture byte-to-byte delay
#define ADNS3080_T_BEXIT 4 // NCS to burst mode exit
// ADNS-3080 ISA芯片像素长度:
#define ADNS3080_PIXELS 30
// 部分寄存器地址:
#define ADNS3080_PRODUCT_ID 0x00 // ADNS-3080芯片标识码
#define ADNS3080_CONFIGURATION_BITS 0x0a // ADNS-3080配置参数
#define ADNS3080_MOTION_CLEAR 0x12 // ADNS-3080清除Motion寄存器,此寄存器写入任何值均可清除 Delta_X, Delta_Y和Motion寄存器,实现快速复位
#define ADNS3080_FRAME_CAPTURE 0x13 // ADNS-3080帧捕获寄存器
#define ADNS3080_PIXEL_BURST 0x40 // ADNS-3080Pixel_Burst寄存器,此寄存器用于在高速读取像素值时使用,该模式下可以一直读完1又2/3帧的像素,约1500个像素左右(实际为1536个像素值)
#define ADNS3080_MOTION_BURST 0x50 // ADNS-3080Motion_Burst寄存器,读取该寄存器即启动了Motion_Burst模式,该模式下Motion_Burst寄存器随即会按照Motion, Delta_X, Delta_Y, SQUAL, Shutter_Upper, Shutter_Lower, Maximum_Pixel这7个寄存器的顺序返回对应值
#define ADNS3080_PRODUCT_ID_VALUE 0x17 // ADNS-3080的PRODUCT_ID默认值,用于验证SPI通信是否正常
最重要的是定义了模板类,使用RESET(注意是连接ADNS-3080RST引脚的Arduino开发板对应的引脚号)、NCS引脚号(同理)作为参数,实现了编译器级别的多态,虽然是在Arduino Uno平台开发的,但更改RESET和NCS引脚号就可轻而易举地应用于其他开发板。
//--------------- 特化的类模板类型参数 ---------------- [斜线后不能有任何字符]
#define TEMPLATE_TYPE \
uint8_t PIN_RESET, \
uint8_t PIN_NCS
#define TEMPLATE_INPUTS \
PIN_RESET, \
PIN_NCS
//----------------类模板定义------------------
template <TEMPLATE_TYPE>
class ADNS3080 {
private:
// 读写寄存器通用操作函数:
void writeRegister( const uint8_t, uint8_t ); // 地址、数据
uint8_t readRegister( const uint8_t ); // 地址
public:
// 工具函数:
void reset(); // 主动复位
bool setup( const bool=false, const bool=false ); // 初始化函数,设置LED_MODE、Resolution,默认是LED常亮、400dpi
void motionClear(); // 清除Motion数据
// 主要功能函数:
void motionBurst( uint8_t*, int8_t*, int8_t*, uint8_t*, uint16_t*, uint8_t* ); //Motion_Burst模式顺序读取7个寄存器数据值,注意uint16_t是2个字节,代表Shutter_Upper, Shutter_Lower的组合
void displacement( int8_t*, int8_t* ); //读取单次向量Delt_X & Delt_Y
void frameCapture( uint8_t[ADNS3080_PIXELS][ADNS3080_PIXELS] ); //帧捕获函数,整个帧共30X30个像素,存放进对应的数据,形参是个二维数组
};
ADNS3080.tpp
你不要对后缀名那么疑惑,其实跟cpp没区别,我的理解就是template cpp,仅仅好记而已。
公共实现部分,包括2个基础函数,读和写。
/*------------ 读写寄存器通用操作函数 --------------*/
template<TEMPLATE_TYPE>
void ADNS3080<TEMPLATE_INPUTS>::writeRegister( const uint8_t reg, uint8_t output ) {
digitalWrite( PIN_NCS, LOW ); //使能SPI
SPI.transfer( reg | B10000000 ); // 写数据寄存器地址第7位置1
SPI.transfer( output ); // 发送数据
digitalWrite( PIN_NCS, HIGH ); //SPI失效
delayMicroseconds( ADNS3080_T_SWW ); //SWW=SWR,两者均可,确保本次数据写入完整
}
template<TEMPLATE_TYPE>
uint8_t ADNS3080<TEMPLATE_INPUTS>::readRegister( const uint8_t reg ) {
digitalWrite( PIN_NCS, LOW ); //使能SPI
SPI.transfer( reg ); // 发送读取寄存器地址
delayMicroseconds( ADNS3080_T_SRAD_MOT ); //SRAD < SRAD_MOT,为确保函数通用,使用最大的延时
uint8_t output = SPI.transfer(0x00); // 读取数据
digitalWrite( PIN_NCS, HIGH ); //SPI失效
return output;
}
工具函数部分,包括复位、初始化、清除Motion函数。
复位操作文档中如是说:
/*复位函数*/
template<TEMPLATE_TYPE>
void ADNS3080<TEMPLATE_INPUTS>::reset() {
digitalWrite( PIN_RESET, HIGH );
delayMicroseconds( ADNS3080_T_PW_RESET ); // Reset pulse witdh 10us
digitalWrite( PIN_RESET, LOW );
delayMicroseconds( ADNS3080_T_IN_RST ); // Input delay after reset 500us
}
初始化主要说明对ADNS-3080的Configuration_bits寄存器(0x0a)和Product_ID寄存器(0x00)操作。
/*初始化函数*/
template<TEMPLATE_TYPE>
bool ADNS3080<TEMPLATE_INPUTS>::setup( const bool led_mode, const bool resolution ) {
// 配置SPI
SPI.begin();
SPI.setClockDivider( SPI_CLOCK_DIV8 ); //ADNS-3080最高支持SPI速率2MHz
SPI.setDataMode( SPI_MODE3 ); // 空闲时高电平,上升沿发送
SPI.setBitOrder( MSBFIRST ); // 大头在前
// 配置引脚
pinMode( PIN_RESET, OUTPUT ); // 复位引脚为输出模式
pinMode( PIN_NCS, OUTPUT ); // 片选引脚为输出模式
// 禁用SPI通信、复位
digitalWrite( PIN_NCS, HIGH );
reset(); //复位ADNS-3080
// 配置ADNS-3080参数
// LED Shutter位 High resolution位
uint8_t mask = B00000000 | led_mode << 6 | resolution << 4;
writeRegister( ADNS3080_CONFIGURATION_BITS, mask );
// 检查SPI通信是否正常
if( readRegister(ADNS3080_PRODUCT_ID) == ADNS3080_PRODUCT_ID_VALUE ) { //若读取到Product_ID是0x17,则说明SPI读取数据正确
return true;
} else {
return false;
}
}
/*清除 DELTA_X, DELTA_Y 和 motion 寄存器函数*/
template<TEMPLATE_TYPE>
void ADNS3080<TEMPLATE_INPUTS>::motionClear() {
writeRegister( ADNS3080_MOTION_CLEAR, 0x00 );
}
3个主要功能函数,包括Motion Burst模式、取位移向量、Frame Capture模式3个相应的函数。
/*-----------------主要功能函数--------------------*/
/*获取motion burst数据*/
template<TEMPLATE_TYPE>
void ADNS3080<TEMPLATE_INPUTS>::motionBurst( uint8_t *motion, int8_t *dx, int8_t *dy, uint8_t *squal, uint16_t *shutter, uint8_t *max_pix ) {
digitalWrite( PIN_NCS, LOW ); // 使能SPI
SPI.transfer( ADNS3080_MOTION_BURST ); // 读取Motion_Burst寄存器,读取该寄存器即启动了Motion_Burst模式
delayMicroseconds( ADNS3080_T_SRAD_MOT ); // Motion Burst模式特有的地址-数据间延时
// 按照Motion, Delta_X, Delta_Y, SQUAL, Shutter_Upper, Shutter_Lower, Maximum_Pixel这7个寄存器的顺序返回对应值
*motion = (SPI.transfer(0x00) & B10000000) >> 7; //取bool值,0或1
*dx = SPI.transfer(0x00); // motion为false时默认为0
*dy = SPI.transfer(0x00); // motion为false时默认为0
*squal = SPI.transfer(0x00);
*shutter = SPI.transfer16(0x00);
*max_pix = SPI.transfer(0x00);
digitalWrite( PIN_NCS, HIGH ); // 结束SPI通信
delayMicroseconds( ADNS3080_T_LOAD + ADNS3080_T_BEXIT ); // 延时退出Burst模式
// if( *motion == false ) {
// *dx = 0;
// *dy = 0;
// }
}
/*获取位移数据*/
template<TEMPLATE_TYPE>
void ADNS3080<TEMPLATE_INPUTS>::displacement( int8_t *dx, int8_t *dy ) {
digitalWrite( PIN_NCS, LOW ); // 使能SPI
SPI.transfer( ADNS3080_MOTION_BURST ); // 读取Motion_Burst寄存器,读取该寄存器即启动了Motion_Burst模式
delayMicroseconds( ADNS3080_T_SRAD_MOT ); // Motion Burst模式特有的地址-数据间延时
// 检查motion数据是否准备好
uint8_t motion = (SPI.transfer(0x00) & B10000000) >> 7; //取bool值,0或1
if( motion == true ) {
*dx = SPI.transfer(0x00); // 取位移向量dx
*dy = SPI.transfer(0x00); // 取位移向量dy
} else {
*dx = 0;
*dy = 0;
}
digitalWrite( PIN_NCS, HIGH ); // 结束SPI通信
delayMicroseconds( ADNS3080_T_LOAD + ADNS3080_T_BEXIT ); // 延时退出Burst模式
}
/*帧捕获函数*/
template<TEMPLATE_TYPE>
void ADNS3080<TEMPLATE_INPUTS>::frameCapture( uint8_t output[ADNS3080_PIXELS][ADNS3080_PIXELS] ) {
writeRegister( ADNS3080_FRAME_CAPTURE, 0x83 ); // 向开启帧捕获模式,注意是写
digitalWrite( PIN_NCS, LOW ); // 使能SPI
// 读取帧
SPI.transfer( ADNS3080_PIXEL_BURST ); // 传送读取像素数据寄存器地址
delayMicroseconds( ADNS3080_T_SRAD ); // 地址-数据间延时
// 读取第1像素
uint8_t pixel = 0;
// 寻找第1个像素,根据ADNS-3080手册,第1个像素值的第6位是1,其余像素第6位都是0
while( (pixel & B01000000) == 0 ) {
pixel = SPI.transfer(0x00); // 读取下一个像素值
delayMicroseconds( ADNS3080_T_LOAD ); // 数据间延时
}
//读取第1帧(共ADNS3080_PIXELS*ADNS3080_PIXELS个)
for( int y = 0; y < ADNS3080_PIXELS; y++ ) {
for( int x = 0; x < ADNS3080_PIXELS; x++ ) { // 按行读取
output[x][y] = pixel << 2; //6位像素数据变成8位
pixel = SPI.transfer(0x00); // 读取下一个像素值
delayMicroseconds( ADNS3080_T_LOAD ); // 数据间延时
}
}
digitalWrite( PIN_NCS, HIGH ); //结束SPI通信
delayMicroseconds( ADNS3080_T_LOAD + ADNS3080_T_BEXIT ); // 延时退出Burst模式
}
测试用例
- motionBurst函数
/*
通过ADNS-3080的Motion Burst模式获取数据.
(c)hele 2024
*/
#include "ADNS3080.h"
#define PIN_RESET 9
#define PIN_CS 10
//传感器配置参数
#define LED_MODE false // If true, enables LED Mode
#define RESOLUTION false // If true, enable high resolution mode
ADNS3080 <PIN_RESET, PIN_CS> sensor; //实例化
// 位移累积
int x = 0;
int y = 0;
void setup() {
sensor.setup( LED_MODE, RESOLUTION );
Serial.begin(9600);
}
void loop() {
uint8_t motion; // motion标志位 true or false
int8_t dx, dy; // 位移向量分量
uint8_t squal; // 表面质量
uint16_t shutter; // 快门
uint8_t max_pixel; // 最亮像素值
sensor.motionBurst( &motion, &dx, &dy, &squal, &shutter, &max_pixel );
// Integrate displacements
x += dx;
y += dy;
// Other values:
Serial.print( "motn=" );
Serial.print( motion );
Serial.print( " squal=" );
Serial.print( squal );
Serial.print( " shutr=" );
Serial.print( shutter );
Serial.print( " maxpix=" );
Serial.print( max_pixel );
// Displacement:
Serial.print( " dx=" );
Serial.print( dx );
Serial.print( " dy=" );
Serial.print( dy );
Serial.print( " x=" );
Serial.print( x );
Serial.print( " y=" );
Serial.print( y );
Serial.println();
}
- displacement函数
代码大同小异,直接看结果。
- frameCapture函数
ADNS3080.tpp中说明了frameCapture会对像素值进行<<2位移操作,变成8位,在测试用例中将8位>>4变成4位,然后用16进制打印。
/*
获取帧数据,对ADNS-3080来说会生成1个30X30的像素数据矩阵
(c) hehe 2024
*/
#include "ADNS3080.h"
// SPI pins:
#define PIN_RESET 9
#define PIN_CS 10
ADNS3080 <PIN_RESET, PIN_CS> sensor;
void setup() {
sensor.setup();
Serial.begin(9600);
}
void loop() {
// 帧像素矩阵
uint8_t frame[ADNS3080_PIXELS][ADNS3080_PIXELS]; // The number of pixels are included in the header file. Nx = Ny = 30
// 捕获1帧
sensor.frameCapture( frame );
// 逐行显示
for(int j = 0; j < ADNS3080_PIXELS; j += 1 ) {
for(int i = 0; i < ADNS3080_PIXELS; i += 1 ) {
Serial.print( frame[i][j]>>4 , HEX); //8位像素值变成4位,输出成16进制
Serial.print(' ');
}
Serial.println();
}
Serial.println();
}
相关链接
本文所描述的ADNS-3080类库已开发成基于Arduino平台的ADNS-3080光流传感器库,欢迎下载交流。