基于Arduino Uno平台开发ADNS-3080类库

前言

目前网上分享的基本都是基于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模式
} 

测试用例

  1. 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();
}

在这里插入图片描述

  1. displacement函数
    代码大同小异,直接看结果。
    在这里插入图片描述
  2. 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光流传感器库,欢迎下载交流。


  1. c++学习笔记_3万字入门学习template ↩︎

  2. ADNS3080开发手册 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值