当初下定决心要走嵌入式的时候买了一堆传感器,但是因为懒和忙所以闲置了一堆,今天考完了最后一门,所以打算一个个都玩一遍,今天先从这个摇杆开始,当初买这个是想着以后做个遥控小车的,现在先把这个模块玩明白。
网购就能买到,一个大概也就一两块。我买的这个,客服就给了个pdf,看得出来写的很随意(内容比较口语化),给的代码示例也是arduino的,不过我结合着看了看就发现,这个摇杆模块实际上就是一个电位器,使用ADC转换可以获取到摇杆的状态。
从上图可以看出有5针,从上到下的GND,+5V,URX,URY,SW。
虽然上面写着5V,并且示例代码给的arduino也是5V供电的,但是我试过了,STM32F103C8T6的3.3V也是能使用的。
URX和URY分别输出的是X轴和Y轴的信号量。
SW什么意思我不知道,不过它输出的是Z轴的信号量,在摇杆的侧面可以看到一个按钮,当整个摇杆向下按的时候,凸出来的白色的东西会挤压这个按钮,从而将按钮按下(朴实无华的物理结构)
我用万用表测了一下URX和URY的电压范围都是0~3.3V(接的STM32的3.3V和GND)。
但是SW似乎没有电压,但是按下的时候却可以使初始化成上拉输入的GPIO口发生下降沿,因此我推测SW连接的按钮的通道GND的,GPIO口配置成上拉输入,默认就是高电平,一旦SW按下,也就是接地了,高电平就变成下降沿,触发了下降沿。
一共是三个信号量,XYZ三轴,XY轴可以使用ADC来获取具体的数值,而Z轴本质就是一个按键,因此Z轴使用GPIO口外部中断来判断是否按下。
而使用ADC来转换的XY一共有两个通道,因此可以配置为ADC和DMA联动,并且配上ADC的循环转换和扫描转换,这样就不用一直手动触发ADC转换。
外部中断和ADC在我之前的文章中有介绍,这里就只简单说下DMA吧。
另外需要注意的是接线,我下面的代码是用的ADC1的通道0和通道1,对应的GPIO口是GPIOA的0号引脚和1号引脚,如果要更改GPIO口的话需要查询引脚定义图然后修改ADC的通道参数。
DMA(Direct Memory Access)直接存储器访问,不需要CPU的干预就可以将数据快速移动,CPU只要下达命令之后,就不用再管DMA了,DMA会自动把数据搬运好,可以从外设的数据搬到存储器里,也可以把存储器的数据搬到外设,也可以把存储器的数据搬到存储器。
?从上图可知,我们搬运的目标和目的地可以是Flash,SRAM,APB1和APB2上的外设。
STM32F103C8T6属于中容量产品,因此只有DMA1,一共七个通道,不同通道对应的外设不一样,我们需要查询对应的映射关系。
可以看出我们计算搬运的ADC1在通道1里,因此我们需要使用DMA1的通道1。
了解上面内容之后就可以直接来使用DMA了,使用步骤很简单,一共三步,第一步打开时钟,第二步配置初始化,第三步上电使能。
DMA不在APB1和APB2上,而是在AHB上,所以打开时钟的函数会和之前打开外设时钟不一样。
就使用上图最下面的例子就是打开DMA时钟的。
传入DMA通道和一个DMA_InitTypeDef指针类型的参数。
DMA_BufferSize?:DMA缓存大小,搬几个数据就填几。
DMA_DIR:根据传入的参数决定搬运方向。
DMA_M2M:是否从存储器搬运到存储器,我们这里是从ADC外设搬到存储器中,因此我们选择DMA_M2M_Disable。
DMA_MemoryBaseAddr:存储器基地址。
DMA_MemoryDataSize:存储器数据大小。由于ADC转换结果的范围为0~4095,因此我们选择半字(16位,0~65535,主要是因为8位不够)
DMA_PeripheralInc:外设地址是否自增
DMA_Priority:优先级
DMA_MemoryInc:存储器地址是否自增
DMA_Mode:DMA搬运模式,我们使用循环模式
DMA_PeripheralBaseAddr:外设基地址
DMA_PeripheralDataSize:外设数据大小。
上电使能。
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
uint16_t xy[2];
void interruptInit(void){ //外部中断初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA外设时钟
GPIO_InitTypeDef gitd;
gitd.GPIO_Mode=GPIO_Mode_IPU; //上拉输入模式
gitd.GPIO_Pin=GPIO_Pin_6; //选择GPIOA的6号引脚
gitd.GPIO_Speed=GPIO_Speed_50MHz; //不懂选啥就选50MHz
GPIO_Init(GPIOA,&gitd);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6);//配置AFIO外部中断配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //抢占优先级和响应优先级各占两位.
NVIC_InitTypeDef nitd;
nitd.NVIC_IRQChannel=EXTI9_5_IRQn; //选择中断通道
nitd.NVIC_IRQChannelCmd=ENABLE;
nitd.NVIC_IRQChannelPreemptionPriority=2; //抢占优先级
nitd.NVIC_IRQChannelSubPriority=2; //响应优先级
NVIC_Init(&nitd);
EXTI_InitTypeDef eitd;
eitd.EXTI_Line=EXTI_Line6; //6号中断线,对应6号引脚
eitd.EXTI_LineCmd=ENABLE;
eitd.EXTI_Mode=EXTI_Mode_Interrupt;
eitd.EXTI_Trigger=EXTI_Trigger_Falling; //脉冲下降沿触发
EXTI_Init(&eitd);
}
int main(void){
OLED_Init();
interruptInit();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIOA外设时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE); //使能ADC1外设时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE); //使能DMA1外设时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef gitd;
gitd.GPIO_Mode=GPIO_Mode_AIN; //选择GPIO口的模式
gitd.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1; //选择具体是哪一个GPIO口
gitd.GPIO_Speed=GPIO_Speed_50MHz; //默认选择50MHz
GPIO_Init(GPIOA,&gitd);
ADC_InitTypeDef itd;
itd.ADC_ContinuousConvMode=ENABLE; //开启连续转换模式
itd.ADC_DataAlign=ADC_DataAlign_Right; //数据右对齐
itd.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None; //关闭外部触发,即软件触发ADC转换
itd.ADC_Mode=ADC_Mode_Independent; //独立模式
itd.ADC_NbrOfChannel=2; //2个通道数目
itd.ADC_ScanConvMode=ENABLE; //开启扫描转换模式
ADC_Init(ADC1,&itd);
//配置规则组通道
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
DMA_InitTypeDef ditd;
ditd.DMA_BufferSize=2;
ditd.DMA_DIR=DMA_DIR_PeripheralSRC; //传输方向为外设到存储器
ditd.DMA_M2M=DMA_M2M_Disable; //软件触发
ditd.DMA_MemoryBaseAddr=(uint32_t)&xy; //数据存储器地址
ditd.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord; //存储器数据大小为半字(16位,0~65535)
ditd.DMA_MemoryInc=DMA_MemoryInc_Enable; //开启存储器地址自增
ditd.DMA_Mode=DMA_Mode_Circular; //循环模式
ditd.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR; //外设地址
ditd.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//外设数据大小为半字
ditd.DMA_PeripheralInc=DMA_PeripheralInc_Disable; //外设地址不自增
ditd.DMA_Priority=DMA_Priority_Medium; //优先级
DMA_Init(DMA1_Channel1,&ditd);
DMA_Cmd(DMA1_Channel1,ENABLE);
ADC_DMACmd(ADC1,ENABLE); //开启ADC和DMA的联动
ADC_Cmd(ADC1,ENABLE);
ADC_ResetCalibration(ADC1); //复位校准
while(SET==ADC_GetResetCalibrationStatus(ADC1)); //等到复位校准完成
ADC_StartCalibration(ADC1); //开始校准
while(SET==ADC_GetCalibrationStatus(ADC1)); //等待校准完毕
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //开启软件触发ADC转换
OLED_ShowString(1,1,"X:");
OLED_ShowString(2,1,"Y:");
while(1){
int x=0,y=0;
for(uint8_t i=0;i<10;++i) x+=xy[0];
for(uint8_t i=0;i<10;++i) y+=xy[1];
x/=10;y/=10;
OLED_ShowNum(1,3,x,5);
OLED_ShowNum(2,3,y,5);
}
}
void EXTI9_5_IRQHandler(void){
if(EXTI_GetITStatus(EXTI_Line6)){ //确认中断来自那个中断线
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0){
OLED_ShowString(3,1,"Z DOWN");
Delay_ms(20); //消除机械按键抖动
while(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_6)==0); //长按可能会影响主程序运行
Delay_ms(20); //消除机械按键抖动
OLED_ShowString(3,1," ");
}
EXTI_ClearITPendingBit(EXTI_Line6); //清除中断线标志位
}
}
《STM32F10xxx参考手册(中文)》
《ARM Cortex-M3 嵌入式原理及应用 基于STM32F103微控制器》
《STM32F103xx固件函数库用户手册》