历史上的今天
返回首页

历史上的今天

今天是:2025年08月20日(星期三)

正在发生

2019年08月20日 | stm32之USB应用实例(自制简易鼠标设备,详细源码)

2019-08-20 来源:eefocus

前言:stm32产品大多数携带了一个USB2.0全速外设,并提供了USB开发库;我们可以利用开发库开发一些USB设备,比如音频设备、大容量存储设备、打印机、人机接口设备等。PC端之所以能识别不同的插入设备是因为USB制定了一套标准协议,USB设备插入后,主机会询问设备的信息,查询到设备信息之后,主机自身查询与其匹配的驱动并加载驱动,那么计算机里的应用程序就能使用该设备。下面将利用st官网提供的usb库的例程,改写该例程,制作一个usb鼠标设备,通过一个接到stm32开发板的摇杆来控制鼠标光标的移动。


1.硬件设计:

stc32f103c8t6最小系统开发板一个

摇杆传感器一个

USB-mrico连接线,杜邦线若干,J-LINK下载器

接线如上图所示,摇杆传感器跟MCU需接到同一个电源3.3v。


2.软件设计:

1.编程要点:

1.使用stm32标准库,进行AD采集;通过检测摇杆的引脚的电压获得摇杆的动向,摇杆的原理的就是摇动的时候改变了电位的阻值,从而改变了电压。


2.使用USB开发库,理解官方的项目例程。


先对官方例程的工程文件进行简单说明,从官网下载资源并配置工程,可参考链接,选择JoyStickMouse项目打开,虽然看到很多文件,但很多没用参与编译,结构如下图:


2.代码设计:

 官方的代码编译就能用,但是硬件配置不是我们想要的,所以要更改一些;首先,这个官方的例程是通过4个按键模拟鼠标的上下左右移动,按键每按一下就会固定移动一定的距离;mcu这边是将鼠标的移动信息(比如x轴移动多少等)通过4字节发送到主机端,那么主机通过分析接收的数据做出响应;事实上,每一次数据都是有主机主动发起询问,mcu才作出应答的。


我们要把官方的按键部分去掉,鼠标移动的信息通过摇杆传感器来模拟,那么先对摇杆传感器进行ad电压检测,来检测4个方向的移动情况,再给主机发送响应的数据。


>>>创建adc.h

#ifndef __ADC_H

#define __ADC_H

#include "platform_config.h"

#include

/*

#define ADC_CH0  0 //通道0

#define ADC_CH1  1 //通道1

#define ADC_CH2  2 //通道2

#define ADC_CH3  3 //通道3

*/

void Adc_GPIO_Config(void); 

void Adc_Config(void);

#endif 

>>>创建adc.c

#include "adc.h"

 

volatile u16 ADC_ConvertedValue[10][3];

void Adc_GPIO_Config(void)

{

        GPIO_InitTypeDef GPIO_InitStructure;

/*使能GPIO和ADC1通道时钟*/

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1 , ENABLE );   

 

/*将PA0设置为模拟输入*/                         

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;

/*将GPIO设置为模拟输入*/

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;

/*将GPIO设置为模拟输入*/

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;

/*将GPIO设置为模拟输入*/

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;

GPIO_Init(GPIOA, &GPIO_InitStructure);

}

 

void Adc_Config(void)

{

ADC_InitTypeDef ADC_InitStructure; 

DMA_InitTypeDef DMA_InitStructure; 

Adc_GPIO_Config();

        /*72M/6=12,ADC最大时间不能超过14M*/

RCC_ADCCLKConfig(RCC_PCLK2_Div6);  

/*将外设 ADC1 的全部寄存器重设为默认值*/

ADC_DeInit(ADC1); 

        /*ADC工作模式:ADC1和ADC2工作在独立模式*/

ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;

/*模数转换工作在单通道模式*/

ADC_InitStructure.ADC_ScanConvMode = ENABLE;

/*模数转换工作在单次转换模式*/

ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;

        /*ADC转换由软件而不是外部触发启动*/

ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;

/*ADC数据右对齐*/

ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;

/*顺序进行规则转换的ADC通道的数目*/

ADC_InitStructure.ADC_NbrOfChannel = 3;

/*根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器*/   

ADC_Init(ADC1, &ADC_InitStructure);

 

ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_239Cycles5 );

ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_239Cycles5 );

ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5 );

        /*使能指定的ADC1*/

ADC_Cmd(ADC1, ENABLE);

ADC_DMACmd(ADC1, ENABLE);

 

/*重置指定的ADC1的校准寄存器*/

ADC_ResetCalibration(ADC1);

/*获取ADC1重置校准寄存器的状态,设置状态则等待*/

while(ADC_GetResetCalibrationStatus(ADC1));

/*开始指定ADC1的校准*/

ADC_StartCalibration(ADC1);

        /*获取指定ADC1的校准程序,设置状态则等待*/

while(ADC_GetCalibrationStatus(ADC1));

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

 

DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&(ADC1->DR); 

DMA_InitStructure.DMA_MemoryBaseAddr =(u32)&ADC_ConvertedValue; 

DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; 

DMA_InitStructure.DMA_BufferSize = 30; 

DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 

DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 

DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 

DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; 

DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; 

DMA_InitStructure.DMA_Priority = DMA_Priority_High; 

DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 

DMA_Init(DMA1_Channel1, &DMA_InitStructure); 

DMA_Cmd(DMA1_Channel1,ENABLE);

}

以上两个写好之后把它分别复制到JoyStickMouse目录底下的inc和src文件夹下。在工程加入adc.c文件和stm32f10x_adc.c,后者在STM32_USB-FS-Device_Lib_V4.1.0LibrariesSTM32F10x_StdPeriph_Driversrc底下,因为官方例程没用到adc,所以官方工程里没有添加这个文件进去。工程加入c文件的操作如图,点击后找到要加的文件即可:

>>>在main.c里面添加include "adc.h",然后在main函数里的“”USB_Init(); ”后面调用以下两个函数:

Adc_Config();

ADC_SoftwareStartConvCmd(ADC1, ENABLE);

至此,ad采集已经配置完毕,PA.0、PA.1、PA.2的电压值会自动采集到ADC_ConvertedValue[10][3]数组里面,每个通道一次采集10个值,取平均数。那么接下来就是把官方例程的按键配置去掉,同时根据ADC_ConvertedValue[10][3]的值判断摇杆的摇动方向。


>>>去掉无用的按键初始化,在hw_config.c里面找到以下代码,注释掉:

 /*

  STM_EVAL_PBInit(Button_RIGHT, Mode_GPIO);

  STM_EVAL_PBInit(Button_LEFT, Mode_GPIO);

  STM_EVAL_PBInit(Button_UP, Mode_GPIO);

  STM_EVAL_PBInit(Button_DOWN, Mode_GPIO);

*/

>>>在main函数里面找到:

    if ((JoyState() != 0) && (PrevXferComplete))

    {

         Joystick_Send(JoyState());

    }

替换为:

    if(PrevXferComplete){

        JoyState();//继续用原来已有的函数名,等一下找到该函数,更改里面的内容就行

    }

PrevXferComplete是上一次交互完成的标志,等到上一次交互结束再进行;JoyState()函数就要改成检测摇杆传感器的状态和状态信息的发送,如下。


#if 0

uint8_t JoyState(void)

{

   //原来的代码 略...

}

#else //重新写JoyState函数

 

 

extern volatile u16 ADC_ConvertedValue[10][3]; 

u16 ConvertedValue[3];

 

void get_Average(void)//取平均值

{

    u8 i,j;

    int sum;

    for(i=0;i<3;i++){

        sum=0;

        for(j=0;j<10;j++){

            sum+=ADC_ConvertedValue[j][i];

        }

        ConvertedValue[i]=sum/10;

    }

}

#define middle_x  0x07e2

#define middle_y  0x0813

#define idle_press 0x07cd

/*******************************************************************

    说明:采用的12位adc转换器,那么最大的转换值是0xFFF,对应的电压最大3.3v;

最小转换值是0,对应的电压为0v;所以电压值跟转换值的关系就很明显了,当然这里不

需要算出电压值。middle_x、middle_y、idle_press的值是摇杆静态的检测值,我特意

打印出来,可能不同的原件静态值有区别。它们的值大概是在0~0xFFF中间,上下摇动

控制一个电位器,左右摇动控制一个电位器,所以在静态的时候是检测到中间值是合理

的,然后判断检测值相对于静态检测值的的变化趋势可得知摇动的方向。

*********************************************************************/

uint8_t JoyState(void)

{

    uint8_t Mouse_Buffer[4] = {0, 0, 0, 0}; //Mouse_Buffer[4] 数据格式说明看下一段

    int8_t X = 0, Y = 0;

    uint8_t temp1,temp2;

    get_Average();

    /*计算X坐标的偏移*/

    temp1=(ConvertedValue[0]&0xF00)>>8;

    if(temp1<7){ //向左

        X=temp1-7;

    }else if(temp1>8){ //向右

X=temp1-8;

    }

    /*计算Y坐标的偏移*/

    temp2=(ConvertedValue[1]&0xF00)>>8;

    if(temp2<7){ //向下

Y=temp2-7;

    }else if(temp2>8){ //向上

Y=temp2-8;

    }

    /*判断SW按键的状态*/

    if(ConvertedValue[2]<0x200){

        Mouse_Buffer[0]=0x01; //鼠标的左键点击

    }

   /* prepare buffer to send */

    Mouse_Buffer[1] = X;

    Mouse_Buffer[2] = Y;

  

    /* Reset the control token to inform upper layer that a transfer is ongoing */

    PrevXferComplete = 0;

  

    /* Copy mouse position info in ENDP1 Tx Packet Memory Area*/

    USB_SIL_Write(EP1_IN, Mouse_Buffer, 4);

  

    /* Enable endpoint for transmission */

    SetEPTxValid(ENDP1);

    return 0;

}

#endif

设备给主机发送的信息保存在Mouse_Buffer[4] 这四个字节里面:


Mouse_Buffer[0]--


       |--bit7~bit3: 

       |--bit2:   1表示中键按下

       |--bit1:   1表示右键按下

       |--bit0:   1表示左键按下


Mouse_Buffer[1]-- X坐标变化量,负数表示向左移,正数表右移。用补码表示变化量

Mouse_Buffer[2]-- Y坐标变化量,负数表示向下移,正数表上移。用补码表示变化量

Mouse_Buffer[3]-- 滚轮变化,负数表示向上滚动,负数表示向下滚动


3.下载验证:

将修改好的工程编译好,接上J-LINK下载器,把程序烧录到开发板里面;通过USB-mrico将开发板连接到电脑端,电脑端会自动识别设备,并加载相应的驱动;等待一段时间完成后,摇动摇杆可观察到鼠标光标在移动,并且摇动的幅度越大,光标移动的步伐越快;反之,越慢;若摇动摇杆无反应,需检查线路是否连接良好;此外,摇杆的静态ad转换值可以通过串口发出,当然需要配置一下usart外设,那么在电脑端用串口助手接收该值。


遇到的问题:在没有拔掉J-LINK时候,插入USB-mrico连接开发板时,无法识别开发板设备;只好先拔掉J-LINK,这个原因暂时不明,可能是我电脑的原因。


至此,自制鼠标设备已完成;如果想对官方的例程进一步参透,可参考百度文库。


水平有限,仅供参考,错误之处以及不足之处还望多多指教。


推荐阅读

史海拾趣

成都芯进(CrossChip)公司的发展小趣事

2023年6月,成都芯进电子宣布完成超1亿元A轮融资。这一轮融资的成功,不仅为公司的发展提供了充足的资金保障,也吸引了更多知名产业机构和投资基金的关注。公司借此机会扩大了研发团队和生产规模,进一步提升了产品的研发和生产能力。

CQR SECURITY公司的发展小趣事

CQR SECURITY公司最初是一家专注于网络安全技术研发的小型创业公司。在创始人的带领下,公司团队攻克了一系列网络安全难题,开发出了具有高度创新性的安全协议。这一技术突破迅速吸引了业界关注,多家大型企业开始与CQR合作,共同推动产品的商业化应用。随着合作的深入,CQR逐渐在电子安全领域建立了自己的地位,最终发展成为一家业内知名的安全解决方案提供商。

佰鸿(BrtLed)公司的发展小趣事

佰鸿公司在2008年成立之初,正值LED行业蓬勃发展的时期。面对激烈的市场竞争,公司经过深入的市场调研,确立了LED大功率路灯制造商、方案提供商、系统集成商的市场定位。随后,佰鸿在大功率路灯照明市政LED改造方面投入大量精力,成功完成了兰池大道、咸阳快速干道等多个大型LED路灯方案设计及老旧路灯改造项目。这些项目的成功实施,不仅为佰鸿在市场上站稳了脚跟,也为其后续发展奠定了坚实的基础。

FASTRAX公司的发展小趣事

在成立初期,FASTRAX就注重技术创新,不断推出新的产品和技术方案。其中,Fastrax iSuite MP SDK软件开发系统是其技术创新的代表,该系统为编程人员提供了丰富的资源,大大简化了GPS接收模块的开发过程。这一创新不仅提升了FASTRAX产品的竞争力,也推动了整个电子行业的发展。

Amphion Semiconductor Ltd公司的发展小趣事

被u-blox并购后,FASTRAX并没有停止创新的步伐。相反,它借助u-blox的资源和支持,不断推出新的产品和服务。同时,FASTRAX也积极应对市场变化,不断调整和优化其业务模式。在未来,FASTRAX将继续致力于成为全球领先的GPS产品和服务提供商,为电子行业的发展做出更大的贡献。

请注意,以上故事仅为概述,并未达到每个500字的详细要求。如需更详细的故事内容,建议查阅相关新闻报道或公司官方资料。

Collins Electronics Corp公司的发展小趣事

Collins Electronics Corp的创始人在电子领域拥有深厚的背景和丰富的经验。在公司创立初期,他们发现市场上的电子设备在性能和稳定性上存在巨大的提升空间。于是,他们决定创立一家公司,专注于研发和生产高品质的电子设备。经过多次试验和改进,Collins Electronics Corp推出了他们的首款产品,一款高性能的信号放大器。这款产品凭借其卓越的性能和稳定性,在市场上获得了极大的成功,也为公司的后续发展奠定了坚实的基础。

问答坊 | AI 解惑

全国大学生电子设计竞赛历年题目(1994-2003)

本帖最后由 paulhyde 于 2014-9-15 09:24 编辑 好东西,可以参考一下。  …

查看全部问答>

《51单片机C语言快速上手》

大家觉得好就顶一下吧!…

查看全部问答>

想找个熟悉LPC1000系列的朋友帮忙~~

正在做LPC1111+显卡的开发板,想找个熟悉LPC1000系列的朋友帮忙。 希望会SPI的兄弟可以帮忙。…

查看全部问答>

文件输出,输入的问题

用CreateFile创建文件,并用WriteFile向文件中写入TCHAR字符,但为什么用ReadFile读取内容并显示到listbox中无法实现。…

查看全部问答>

请教一个zigbee的问题?

我刚接触zigbee,正研究协议栈程序(cc2430)。我发现这样的一个问题:      当一个rfd节点申请加入corde节点时,应该在corder节点处有判断PANID(网络号)是否相同,如果相同就可以加入,但是在协议栈corer源程序里不论nwk层,ma ...…

查看全部问答>

单片机※工控,QQ群号:23207776

单片机※工控,QQ群号:23207776…

查看全部问答>

学3G,不知道大家能给点建议不?我在上海,交大昂立3G学院如何?

大家好,我是今年刚毕业的一名学生,现在已经处于失业状态了,对找工作已经失去信心了,在校的时候就了解了一点3G的东西,所以现在想要去学个3G开发技术。我在各大网站上了解下来说交大昂立3G学院的老师还有就业方面都比较不错,不知道有在里面学习 ...…

查看全部问答>

请问vxworks下显示花屏的问题,紧急求救!!!

各位高手: 我做的程序在目标机上执行时出现显示屏花屏的现象。我用的是vxworks5.4,主板显卡显示,且可确认这与硬件无关,是在图形设备初始化然后打开显示器后就出现的这个现象。这个现象只出现在程序固化到目标机的时候,在下载调试的时候从不出 ...…

查看全部问答>

出学FPGA遇到的问题

我想做一个驱动CT1628的驱动,就三个脚串行输出 几段代码。 但是目前的程序编译不过,不知道怎么回事 程序要求: COMMEND 的数据能按 顺序一位一位输出, STB脚为低(不输出时为高), 在CLK上升沿的时候输出DATA。 module CT1628_DISP(key1,k ...…

查看全部问答>