鼠标宏系列之二-光电鼠标设计案例

本文详细解析了一个使用ADNS2620芯片的光电鼠标项目,涉及FW代码中的传感器读写、USB通信和I/O操作,展示了从底层驱动到硬件交互的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们会从一个真实的鼠标设计案例开始,来看看一个真实的光电鼠标是什么样子的,在文章的最后我会将鼠标项目打包上传到资源部分。

这个方案主要使用了adns2620芯片,这个芯片是光电传感器的集成芯片,方案中也有PCB图以及对应的程序代码,PCB结构之类的和本系列无关,就不去一一赘述了,我们主要看代码部分。

fireware代码如下:

/* 自动捕获鼠标坐标 */
static void getMouseMovement(void)
{
	reportBuffer.dx = -read(DELTA_X_REG);
	reportBuffer.dy = read(DELTA_Y_REG);
}

/* ------------------------------------------------------------------------- */

usbMsgLen_t usbFunctionSetup(uchar data[8])
{
    usbRequest_t    *rq = (void *)data;

    // 以下请求从未使用过。但由于它们是规范,我们在这个例子中实现了它们。

    if((rq->bmRequestType & USBRQ_TYPE_MASK) == USBRQ_TYPE_CLASS)
    {    
        // 类请求类型
        DBG1(0x50, &rq->bRequest, 1);   
        // 调试输出: 打印请求 
        if(rq->bRequest == USBRQ_HID_GET_REPORT)
        {   
            // wValue:ReportType(高字节),ReportID(低字节)
            usbMsgPtr = (void *)&reportBuffer;
            return sizeof(reportBuffer);
        }
        else if(rq->bRequest == USBRQ_HID_GET_IDLE)
        {
            usbMsgPtr = &idleRate;
            return 1;
        }    
        else if(rq->bRequest == USBRQ_HID_SET_IDLE)
        {
            idleRate = rq->wValue.bytes[1];
        }
    }
    else
    {
        // 未实现特定于供应商的请求 未实现请求,就不向主机返回任何数据
    }
    return 0;   
}

/* ------------------------------------------------------------------------- */

/* ------------------- ADNS2610 Functions ---------------------------------- */
//Function Description: 从ADNS2610读取指定地址的字节    
//Inputs:  ADNS2610要写入的寄存器地址
//Outputs: None
//Return:  从ADNS2610读取的字节
//usage: value = read(CONFIGURATION_REG);
char read(char address)
{
    char value=0;
	sbi(ADNS_DIR, ADNS_SDIO);	// 确保SDIO引脚设置为输出。
    sbi(ADNS_PORT, ADNS_SCL);   // 确保时钟引脚拉高。
    address &= 0x7F;            // 请确保地址字节的最高位为“0”以指示读取。
 
	int address_bit=0;
    // 发送地址到 ADNS2610
    for(address_bit=7; address_bit >=0; address_bit--)
    {
        cbi(ADNS_PORT, ADNS_SCL);  // 拉低时钟引脚
        sbi(ADNS_DIR, ADNS_SDIO);  // 确保SDIO引脚设置为输出
        
        // 如果当前位为1,则设置SDIO引脚。否则,清除SDIO引脚
        if(address & (1<<address_bit))
        {
            sbi(ADNS_PORT, ADNS_SDIO);
        }
        else
        {
            cbi(ADNS_PORT, ADNS_SDIO);
        }
        _delay_us(1);
        sbi(ADNS_PORT, ADNS_SCL);
        _delay_us(1);
    }
    
    // 允许ADNS2610有额外的时间转换SDIO引脚(根据数据表)
    _delay_us(120);   

    // 将SDIO作为微控制器上的输入
    cbi(ADNS_DIR, ADNS_SDIO);	// 确保将SDIO引脚设置为输入。
	sbi(ADNS_PORT, ADNS_SDIO);	// SDIO引脚的上拉。
        
	int value_bit=0;
    // 将值按字节发送到ADNS2610
    for(value_bit=7; value_bit >= 0; value_bit--)
    {
        cbi(ADNS_PORT, ADNS_SCL);     // 拉低 clock 引脚
        _delay_us(1);                 // 允许ADNS2610配置SDIO引脚
        sbi(ADNS_PORT, ADNS_SCL);     // 上拉 clock 引脚
        _delay_us(1);
        // 如果SDIO引脚为高电平,则在value变量中设置当前位。如果为低,则保留默认值位(0)。
        //if(sdio.read())value|=(1<<value_bit);     
		if((ADNS_PIN & (1<<ADNS_SDIO)) == (1<<ADNS_SDIO))value|=(1<<value_bit);
    }
    
    return value;
}

// Function Description: 将值写入ADNS2619上的指定地址
// Inputs: 字符地址-ADNS2610要写入的寄存器地址
// Outputs: None
// Return:  None
// usage: write(CONFIGURATION_REGISTER, config_setting);
 void write(char address, char value)
 {
	sbi(ADNS_DIR, ADNS_SDIO);	// 确保 SDIO 引脚设置为输出。
    sbi(ADNS_PORT, ADNS_SCL);   // 确认 clock 引脚为高.
    address |= 0x80;            // 请确保地址字节的最高位为“1”以指示写入.

	int address_bit=0;
    //Send the Address to the ADNS2610
    for(address_bit=7; address_bit >=0; address_bit--)
    {
        cbi(ADNS_PORT, ADNS_SCL);  // 拉低 clock 引脚
        
        // 提供一个小延迟(仅用于第一次迭代,以确保ADNS2610放弃
        
        _delay_us(1); 

        // 如果我们在“读取”命令之后执行此写入,则控制SDIO。
        
        // 如果当前位为1,则设置SDIO引脚。如果没有,清除SDIO引脚
        if(address & (1<<address_bit))sbi(ADNS_PORT, ADNS_SDIO);
        else cbi(ADNS_PORT, ADNS_SDIO);
        _delay_us(1);
        sbi(ADNS_PORT, ADNS_SCL);
        _delay_us(1);
    }
    
	int value_bit=0;
    //Send the Value byte to the ADNS2610
    for(value_bit=7; value_bit >= 0; value_bit--)
    {
        cbi(ADNS_PORT, ADNS_SCL);  // 拉低 clock 引脚
        // 如果当前位为1,则设置SDIO引脚。否则,清除SDIO引脚
        if(value & (1<<value_bit))
            sbi(ADNS_PORT, ADNS_SDIO);
        else 
            cbi(ADNS_PORT, ADNS_SDIO);
        _delay_us(1);
        sbi(ADNS_PORT, ADNS_SCL);
        _delay_us(1);
    }
}

void sync(void)
{
    sbi(ADNS_PORT, ADNS_SCL);
    _delay_ms(1);
    cbi(ADNS_PORT, ADNS_SCL);
    _delay_ms(1);
    sbi(ADNS_PORT, ADNS_SCL);
    _delay_ms(100);
}

//Function Description: 初始化ADNS2610的I/O。假设已经定义了sck和sdio的实例.
//Inputs: None
//Outputs: None
//Return:  None. 
//usage: setup();
void setupAdns(void)
{
	sbi(ADNS_DIR, ADNS_SCL);	// 将时钟设置为输出
	sbi(ADNS_DIR, ADNS_SDIO); 	// 将SDIO设置为输出

    sbi(ADNS_PORT, ADNS_SCL);  // 设置 Clock 引脚为高
    sbi(ADNS_PORT, ADNS_SDIO); // 设置 SDIO 引脚为高
 }
 
 /* ------------------------------------------------------------------------------------------- */

int __attribute__((noreturn)) main(void)
{
    uchar   i;

    wdt_enable(WDTO_1S);
    // 即使不用看门狗,也要把它关掉。在较新的设备上,
    // 看门狗的状态(开/关,周期)!


    // RESET状态:所有端口位均为无上拉输入。
    // 这就是我们需要D+和D-的方式。所以我们不需要
    // 额外的硬件初始化。

    odDebugInit();
    DBG1(0x00, 0, 0);       /* debug output: main starts */
    setupAdns();
	_delay_ms(100);
	sync();
	usbInit();
    usbDeviceDisconnect();  /* 强制重新枚举,在禁用中断时执行此操作! */
    i = 0;
    while(--i)
    {             
        /* USB断开连接 > 250 ms */
        wdt_reset();
        _delay_ms(1);
    }
    usbDeviceConnect();
    sei();
    DBG1(0x01, 0, 0);       /* debug output: main loop starts */
    for(;;){                /* 主循环 */
        DBG1(0x02, 0, 0);   /* debug output: main loop iterates */
        wdt_reset();
        usbPoll();
        if(usbInterruptIsReady())
        {
            // 在每次轮询中断端点后调用
            //advanceCircleByFixedAngle();
			getMouseMovement();
            DBG1(0x03, 0, 0);   /* debug output: interrupt report prepared */
            usbSetInterrupt((void *)&reportBuffer, sizeof(reportBuffer));
        }
    }
}

/* ------------------------------------------------------------------------- */

这部分主要是对一些芯片上的引脚的操作。

可以看一下Auduino库:

// 从ADNS2620传感器读取寄存器。将结果返回给调用函数。
// Example: value = mouse.read(CONFIGURATION_REG);
char ADNS2620::read(char address)
{
    char value=0;
	pinMode(_sda, OUTPUT);     // 确保SDIO引脚设置为输出。
    digitalWrite(_scl, HIGH);  // 确保时钟引脚为高。
    address &= 0x7F;           // 请确保地址字节的最高位为“0”以指示读取。
 
    //Send the Address to the ADNS2620
    for(int address_bit=7; address_bit >=0; address_bit--)
    {
        digitalWrite(_scl, LOW); 
        pinMode(_sda, OUTPUT); 
        
        if(address & (1<<address_bit))
        {
            digitalWrite(_sda, HIGH);
        }
        else
        {
            digitalWrite(_sda, LOW);
        }
        delayMicroseconds(10);
        digitalWrite(_scl, HIGH);
        delayMicroseconds(10);
    }
    
    delayMicroseconds(120); 
    pinMode(_sda, INPUT);	
	digitalWrite(_sda, HIGH); 
        
    //Send the Value byte to the ADNS2620
    for(int value_bit=7; value_bit >= 0; value_bit--)
    {
        digitalWrite(_scl, LOW);   
        delayMicroseconds(10);     
        digitalWrite(_scl, HIGH);  
        delayMicroseconds(10);

		if(digitalRead(_sda))value |= (1<<value_bit);

    }
    
    return value;
}	

我们对比FW程序和Auduino库,会发现几乎一致,实际上就是这么修改的,在读写的时候,会根据引脚的状态来设置value。

不过我估计很多人看了这个还是一头雾水,所以下一个文档会从最上层的开发方式来再做一个鼠标键盘输入器,读者可以体会两者之间的差距。

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值