1. 引言
在嵌入式开发中,模拟量输入设备是实现人机交互的重要组件。本文以常见的HW504双轴摇杆模块为例,详细介绍如何在STM32F103C8T6微控制器上实现摇杆数据采集,并通过串口实时显示X/Y轴坐标和按键状态,下面代码每一行都有注释讲解,用户可以根本代码注释来进行理解
源码链接:文件分享
2. 硬件介绍
2.1 硬件清单
-
STM32F103C8T6核心板
-
HW504双轴摇杆模块(5引脚)
-
VRX:X轴模拟输出
-
VRY:Y轴模拟输出
-
SW:按键数字输出
-
+5V:电源正极
-
GND:电源地
-
2.2 硬件连接
摇杆引脚 | STM32引脚 |
---|---|
VRX | PA0 |
VRY | PA1 |
SW | PB9 |
+5V | 3.3V/5V |
GND | GND |
3. 代码实现
代码设计外设:
1.ADC
2.XY摇杆模块
3.串口
3.1 ADC初始化AD.c源文件配置
#include "stm32f10x.h" // Device header
/*******************************
@函数:ADC初始化
@参数:void
返回值:void
*******************************/
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1内部时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA内部时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //ADC时钟:72/6=12Mz, 配置ADC分频(*2、*4、6、8)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入,ADC专属
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //ADC输入的引脚的GPIO的配置
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //工作模式,独立
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发源选择,此处为不使用外部触发,使用软件触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //连续或单次模式,ENABLE为连续
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //扫描或单次,ENABLE为扫描
ADC_InitStructure.ADC_NbrOfChannel = 1; //0——16,通道数目,选择1哥通道
ADC_Init(ADC1, &ADC_InitStructure); //初始化ADC
ADC_Cmd(ADC1, ENABLE); //开启ADC电源
ADC_ResetCalibration(ADC1); //复位校准
while(ADC_GetResetCalibrationStatus(ADC1) == SET); //等待校准完成
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1) == SET); //等待开启完成
}
/*******************************
@函数:获取ADC的值
@参数:ADC_Channel
返回值:uint16_t
*******************************/
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5); //ADC_Channel_0,通道0,对应PA0, “1”代表序列1,ADC_SampleTime_55Cycles5代表采样时间55.5*ADCCLK(T)
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC转换
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //等待转换完成
return ADC_GetConversionValue(ADC1); //获取转换值
}
ADC的AD.h头文件
#ifndef __AD_H
#define __AD_H
void AD_Init();
uint16_t AD_GetValue(uint8_t ADC_Channel);
#endif
关键点说明:
-
采用单次转换模式降低功耗
-
校准过程确保ADC精度
-
采样时钟设置为12MHz(主频72MHz / 6)
3.2 摇杆模块驱动实现
摇杆HW504.C
#include "stm32f10x.h" // Device header
#include "AD.h"
/**************
引脚连接:
VRX - A0
VRY - A1
SW - PB12
5V- 5V
GND - GND
by: 普普通通
***************/
/*******************************
@函数:HW504摇杆的初始化
@参数:void
返回值:void
*******************************/
void HW504_Init(void)
{
AD_Init(); //ADC初始化
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //打开SW引脚对应的I/O口的时钟
GPIO_InitTypeDef GPIO_InitStructure; //SW引脚对应的I/O口的配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //数字量输出引脚,为高低电平,配置成浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_ResetBits(GPIOB, GPIO_Pin_9); //复位
}
/*******************************
@函数:获取HW504_SW引脚的值
@参数:void
返回值:uint8_t
*******************************/
uint8_t HW504_R_SW()
{
return GPIO_ReadOutputDataBit(GPIOB,GPIO_Pin_9);
}
/*******************************
@函数:获取HW504_X引脚的值
@参数:void
返回值:uint16_t
*******************************/
uint16_t HW504_R_X()
{
uint16_t DataX = AD_GetValue(ADC_Channel_0); //PA0
return DataX;
}
/*******************************
@函数:获取HW504_Y引脚的值
@参数:void
返回值:uint16_t
*******************************/
uint16_t HW504_R_Y()
{
uint16_t DataY = AD_GetValue(ADC_Channel_1); //PA1
return DataY;
}
串口USART.C
/***************STM32F103C8T6**********************
* 文件名 :usart1.c
* 描述 :将printf函数重定向到USART1。
* 硬件连接:------------------------
* | PA9 - USART1(Tx) |
* | PA10 - USART1(Rx) |
* ------------------------
* 库版本 :ST3.0.0 *
********************LIGEN*************************/
#include "usart.h"
#include <stdarg.h>
void USART1_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* 使能 USART1 时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
/* USART1 使用IO端口配置 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA
/* USART1 工作模式配置 */
USART_InitStructure.USART_BaudRate = 115200; //波特率设置:115200
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位数设置:8位
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位设置:1位
USART_InitStructure.USART_Parity = USART_Parity_No ; //是否奇偶校验:无
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制模式设置:没有使能
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//接收与发送都使能
USART_Init(USART1, &USART_InitStructure); //初始化USART1
USART_Cmd(USART1, ENABLE);// USART1使能
}
/* 描述 :重定向c库函数printf到USART1*/
/*
注意:
即使编写了 fputc 函数,若未在 Keil 中启用 MicroLib,printf 将无法正确重定向到串口。
解决方案:
右键工程 → Options for Target → Target 标签。
勾选 Use MicroLib(确保标准库支持 printf 重定向)。
*/
int fputc(int ch, FILE *f)
{
/* 将Printf内容发往串口 */
USART_SendData(USART1, (unsigned char) ch);
while (!(USART1->SR & USART_FLAG_TXE));
return (ch);
}
串口usart.h
#ifndef __USART1_H
#define __USART1_H
#include "stm32f10x.h"
#include <stdio.h>
void USART1_Config(void);
int fputc(int ch, FILE *f);
void USART1_printf(USART_TypeDef* USARTx, uint8_t *Data,...);
#endif /* __USART1_H */
主函数main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "HW504.h"
#include "usart.h"
#include <stdio.h>
uint16_t X, Y;
uint8_t SW;
int main(void)
{
// OLED_Init();
HW504_Init();
USART1_Config();
while (1)
{
X = HW504_R_X();
SW = HW504_R_SW();
Y = HW504_R_Y();
printf("\r\ntest:\n");
printf("\r\nX:%d\n",X);
printf("\r\nY:%d\n",Y);
printf("\r\nSW:%d\n",SW);
Delay_ms(500);
}
}
最后看效果: