stm32之USB应用实例(自制简易鼠标设备,详细源码)

发布者:温文儒雅最新更新时间:2019-08-20 来源: eefocus关键字:stm32  USB应用  鼠标设备 手机看文章 扫描二维码
随时随地手机看文章

前言: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,这个原因暂时不明,可能是我电脑的原因。


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


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


关键字:stm32  USB应用  鼠标设备 引用地址:stm32之USB应用实例(自制简易鼠标设备,详细源码)

上一篇:stm32之IIC应用实例(AT24C02芯片,硬件和软件方式驱动)
下一篇:stm32之USB应用实例(官方例程资料下载使用)

推荐阅读最新更新时间:2024-11-11 09:52

基于STM32在IAR中调用printf()函数
在主文件main.c中添加如下代码: #ifdef __GNUC__ #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) #else #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) #endif PUTCHAR_PROTOTYPE { USART_SendData(EVAL_COM1, (uint8_t) ch); while (USART_GetFlagStatus(EVAL_COM1, USART_FLAG_TC) == RESET) {} return ch; } #ifdef USE_F
[单片机]
STM32串口通信中使用printf发送数据配置方法
在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。 下面就说一下使用printf需要做哪些配置。 有两种配置方法: 一、对工程属性进行配置,详细步骤如下 1、首先要在你的main 文件中 包含 stdio.h (标准输入输出头文件)。 2、在main文件中重定义 fputc 函数 如下: // 发送数据 int fputc(int ch, FILE *f) { USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成
[单片机]
STM32关于USB的相关寄存器
这里要贴上STM32 USB相关的寄存器的具体设置,对学习STM32的USB很有帮助。对于涉及到寄存部分的代码,参考这些寄存器的说明,就可以差不多都懂了。由于网页贴不上表格,所以就截屏成图片贴上来,如果看不清楚,就下载图片去看 1.编程好习惯之变量定义: 定义变量时总是按变量类型所占空间大小来排序是最好的! 如果是只有某个函数使用些变量,而且你又需要在函数让函数退出时不被销毁,那么就使用static吧 2.编程好习惯之函数定义: 如果我们定义的C函数仅在此C文件中被调用那么加上static吧(适用于多文件编程) 如果我们想躲避C函数参数的类型检查,那 么K&R的函数定义声明方式还
[单片机]
<font color='red'>STM32</font>关于<font color='red'>USB</font>的相关寄存器
利用ST-LINK配合ST-LINK Utility 将bin文件下载到STM32的FLASH中
背景 项目需求,要把字模文件导入到32中FLASH的指定地址,使用了ST-LINK V2 连接单片机和计算机,然后通过ST-LINK Utility 软件的配合将弄好的字模bin文件导入到FLASH指定地址。下面记录一下整个过程: 1.连接ST-LINK V2与单片机 博主的只需要连接4个线即可: VDD GND SWCLK SWDIO 2.配置工程 然后打开工程,进行配置,选择ST-Link Debugger 然后查看是否检测到ST-Link(选择SW) 如果你的可以显示如图内容,那么就成功连接了。 3.配置ST-LINK Utility 打开软件后的界面如示: 点击蓝色的球,打开设置界面,设置Por
[单片机]
利用ST-LINK配合ST-LINK Utility 将bin文件下载到<font color='red'>STM32</font>的FLASH中
STM32 GPIO管脚工作模式和输出速度总结笔记
GPIO 输出速度 I/O口输出模式下有三种输出速度可选(2MHz,10MHz,50MHz),这个速度是指I/O口驱动电路的响应速度;I/O管脚内部有多个响应不同的驱动电路,用户可以根据自己的需要选择合适的驱动电路。 高低频比较 高频驱动电路:输出频率高,噪音大,功耗高,电磁干扰强; 低频驱动电路:输出频率低,噪音小,功耗低,电磁干扰弱;提高系统EMI(电磁干扰)性能; 总结:通过选择速度来选择不同的输出驱动模块,达到最佳的噪音控制和降低功耗的目的如果需要选择较高频率信号,但是却选择了低频驱动模块,很有可能会失真的输出信号;所以GPIO的引脚速度应与应用匹配。 举几个栗子: 1. 对于串口来说,加入最大波特率为115200,这样只
[单片机]
<font color='red'>STM32</font> GPIO管脚工作模式和输出速度总结笔记
基于STM32电源模块开发
1. 需求分析 使用220-9V的电源适配器进行供电 输出2-5V可调,并且可以监控输出电流 带oled屏幕显示电压电流等信息 带MCU主控可做通信或测试功能使用 2. 硬件设计 2.1 硬件选型及原理图设计 MCU部分原理图设计如下,主要参考的是我之前买的一个STM32F103的一个核心板提供的原理图,主要部分有USB接口、9.6英寸OLED屏幕接口(SPI通信)、SWD及UART接口、按键及指示灯、多余的引脚尽量通过排针引出。 电源部分原理图如下,芯片选型主要使用的是TI的WEBENCH设计工具设计后提供的参考芯片,数字电阻和电流监控的跨阻放大器也都是在TI官网搜的。这里为什么我都用TI的芯片,主要是因为TI
[单片机]
基于<font color='red'>STM32</font>电源模块开发
assert_param函数的意义
在STM32的固件库和提供的例程中,到处都可以见到assert_param()的使用。如果打开任何一个例程中的stm32f10x_conf.h文件,就可以看到实际上assert_param是一个宏定义; 在固件库中,它的作用就是检测传递给函数的参数是否是有效的参数。 所谓有效的参数是指满足规定范围的参数,比如某个参数的取值范围只能是小于3的正整数,如果给出的参数大于3,则这个assert_param()可以在运行的程序调用到这个函数时报告错误,使程序员可以及时发现错误,而不必等到程序运行结果的错误而大费周折。 这是一种常见的软件技术,可以在调试阶段帮助程序员快速地排除那些明显的错误。 它确实在程序的运行上牺牲了效率(但
[单片机]
STM32的三种延时方法的代码实现_纯软件延时, 系统定时器延时, 定时器延时
/* 外部调用: delay_init(72); //系统主频, 单位为MHZ, 仅对 SYSTICK_DELAY 有效 delay_ms(1000); */ #include stm32f10x.h //#define SOFT_DELAY //纯软件延时 //#define SYSTICK_DELAY //系统定时器延时 #define TIMER_DELAY //定时器延时 #ifdef TIMER_DELAY #define TIMER_DELAY TIM4 #define TIMER_DELAY_PERIOD 1000 #de
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved