双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)
实验使用如下所示的双轴按键摇杆控制器,来控制TFTLCD上显示的直线。首先介绍一下双轴按键摇杆控制器。原理:十字摇杆为一个双向的10K电阻器,随着摇杆方向不同,抽头的阻值随着变化。本模块使用5V供电(在本实验中使用3.3V),原始状态下X,Y读出电压为2.5V左右(本实验为1.65V),当随箭头方向按下,读出电压值随着增加,最大到5V(本实验最大为3.3V);箭头相反方向按下,读出电压值减少,最小为0V。即模块特设二路模拟输出和一路数字输出接口,输出值分别对应(X,Y)双轴偏移量,其类型为模拟量;按键表示用户是否在Z轴上按下,其类型为数字开关量。坐标标识符清晰简明、准确定位;用其可以轻松控制物体(如二自由度舵机云台)在二维空间运动。
实验目的:
在屏幕的中心区域显示一条射线,射线起点为屏幕中心(120,160),射线方向与摇杆歪的方向相同,射线长度与歪的程度有关。线路连接:
PC1 - ADC1 channel_11;
PC0 - ADC1 channel_10;
PC2 - SW;
实验准备:
1、实验中对摇杆两个模拟段输入的检测需要使用STM32 的ADC功能;
2、在数据转换之后的移动数据时使用DMA,以将数据及时转移出ADC的寄存器;
我们先来看看主函数,在主函数中我们定义了浮点型数组float ADC_ConvertedValueLocal[2];用于保存转换计算后的电压值 ,还有在adc.c文件中定义的数组ADC_ConvertedValue[2];用来装转换后的数据。
#include <stdio.h>
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "timer.h"
#include "beep.h"
#include "usart.h"
#include "adc.h"
#include "lcd.h" // ADC1转换的电压值通过MDA方式传到SRAM
extern __IO uint16_t ADC_ConvertedValue[]; // 局部变量,用于保存转换计算后的电压值
float ADC_ConvertedValueLocal[]; int main(void)
{
u8 x=;
u8 lcd_id[]; //存放LCD ID字符串
SysTick_Init();//延时初始化
USART1_Int();
LCD_Init();
ADC1_Init();
POINT_COLOR=RED;
while()
{ if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)!=){
LCD_Clear( YELLOW);
LCD_ShowString(,,,,, "THANKS TO YOU!");
Delay_ms();
LCD_Clear( WHITE);
} for(x=;x<;x++){
ADC_ConvertedValueLocal[x] =(float) ADC_ConvertedValue[x]/*3.3; // 读取转换的AD值
LCD_ShowxNum(,+x*,ADC_ConvertedValue[x],,,);
} LCD_DrawLine(, , -(*ADC_ConvertedValueLocal[])+ , *ADC_ConvertedValueLocal[]+); Delay_ms();
LCD_Fill(,,,,LGRAY); }
}
在主函数的while(1)循环之前,我进了三个初始化,分别是:
USART1_Int();
LCD_Init();
ADC1_Init();
在这里第一个就不在介绍,不了解的可以参考:http://www.ciast.net/post/2015119.html 。第二个在这里也不作为重点介绍,TFTLCD的介绍可以参见 http://www.ciast.net/post/20151112.html ,在这里我们会使用到lcd.c中定义的一些操作液晶屏的函数,如下所示:
//清屏函数
//color:要清屏的填充色
void LCD_Clear(u16 color);
//在指定区域内填充单个颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色
void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 color);
//画线
//x1,y1:起点坐标
//x2,y2:终点坐标
void LCD_DrawLine(u16 x1, u16 y1, u16 x2, u16 y2);
//显示数字,高位为0,还是显示
//x,y:起点坐标
//num:数值(0~999999999);
//len:长度(即要显示的位数)
//size:字体大小
//mode:
//[7]:0,不填充;1,填充0.
//[6:1]:保留
//[0]:0,非叠加显示;1,叠加显示.
void LCD_ShowxNum(u16 x,u16 y,u32 num,u8 len,u8 size,u8 mode);
//显示字符串
//x,y:起点坐标
//width,height:区域大小
//size:字体大小
//*p:字符串起始地址
void LCD_ShowString(u16 x,u16 y,u16 width,u16 height,u8 size,u8 *p)
这些功能将在while(1)循环中使用。我们现在着重要将的是ADC1_Init();这个初始化函数,我们找到这个函数的定义,内容如下:
/*
* 函数名:ADC1_Init
* 描述 :无
* 输入 :无
* 输出 :无
* 调用 :外部调用
*/
void ADC1_Init(void)
{
ADC1_GPIO_Config();
ADC1_Mode_Config();
}
这个函数由另外两个函数组成,分别是:
/*
* 函数名:ADC1_GPIO_Config
* 描述 :使能ADC1和DMA1的时钟,初始化PC.00、PC.01和PC.02
* 输入 : 无
* 输出 :无
* 调用 :内部调用
*/
static void ADC1_GPIO_Config(void);
/* 函数名:ADC1_Mode_Config
* 描述 :配置ADC1的工作模式为MDA模式
* 输入 : 无
* 输出 :无
* 调用 :内部调用
*/
static void ADC1_Mode_Config(void);
首先看的是第一个函数,即引脚定义:
/*
* 函数名:ADC1_GPIO_Config
* 描述 :使能ADC1和DMA1的时钟,初始化PC.01
* 输入 : 无
* 输出 :无
* 调用 :内部调用
*/
static void ADC1_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure; /* Enable DMA clock */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* Enable ADC1 and GPIOC clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE); /* Configure PC.01 as analog input */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOC, &GPIO_InitStructure); // PC1 PC0,输入时不用设置速率 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure); // PC2 设置按键功能
}
我将两个模拟输入设置为GPIO_Mode_AIN(模拟输入),将PC2 按键功能键设置成上拉输入(摇杆的SW引脚已经被上拉)。下面是重点:
在ADC1_GPIO_Config(void)函数中,我们主要进行的是DMA和ADC1双通道的设置,下面我们对负责传输的DMA进行设置:
DMA_InitTypeDef DMA_InitStructure; /* DMA channel1 configuration */
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;//内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ;
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); /* Enable DMA channel1 */
DMA_Cmd(DMA1_Channel1, ENABLE);
下面我们开始使用ADC1的通道10和通道11来转换接受到的虚拟信号:
ADC_InitTypeDef ADC_InitStructure; /* ADC1 configuration */ ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立ADC模式
ADC_InitStructure.ADC_ScanConvMode = ENABLE ; //禁止扫描模式,扫描模式用于多通道采集
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换模式,即不停地进行ADC转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //不使用外部触发转换
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //采集数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = ; //要转换的通道数目2
ADC_Init(ADC1, &ADC_InitStructure); /*配置ADC时钟,为PCLK2的8分频,即9Hz*/
RCC_ADCCLKConfig(RCC_PCLK2_Div8);
/*配置ADC1的通道11为55. 5个采样周期,序列为1 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_11, , ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, , ADC_SampleTime_55Cycles5 );//ADC1;ADC1通道0;第2转换;采样时间为239.5周期 ADC_DMACmd(ADC1, ENABLE); /* Enable ADC1 DMA */
ADC_Cmd(ADC1, ENABLE); /* Enable ADC1 */
ADC_ResetCalibration(ADC1); /*复位校准寄存器 */
while(ADC_GetResetCalibrationStatus(ADC1)); /*等待校准寄存器复位完成 */
ADC_StartCalibration(ADC1); /* ADC校准 */
while(ADC_GetCalibrationStatus(ADC1)); /* 等待校准完成*/ /* 由于没有采用外部触发,所以使用软件触发ADC转换 */
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
我把整个ADC的设置分成了四个部分:
第一部分:主要是ADC的初始化参数配置函数,具体设置可以参考:http://www.ciast.net/post/20151226.html ,在这里使用独立ADC模式模式,由于使用了双通道,所以ADC_ScanConvMode = ENABLE ;即开启扫描模式,且ADC_NbrOfChannel = 2;
第二部分:主要是配置ADC时钟和规则通道设置。这里由于ADC的频率不能大于12MHz,所以我们选择分频为PCLK2的8分频,即9Hz。规则通道的设置是每一个通道都要进行的,可以设置通道和扫描顺序以及扫描周期等,这也是ADC设置的重点,可以参考 http://www.ciast.net/post/20151226.html 。
第三部分:主要是使能DMA和ADC,以及复位校准;
第四部分:使用软件触发ADC转换,转换开始。
至此初始化函数介绍完毕,下面开始while(1)循环部分的介绍:
while()
{
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)!=){
LCD_Clear( YELLOW);
LCD_ShowString(,,,,, "THANKS TO YOU!");
Delay_ms();
LCD_Clear( WHITE);
} for(x=;x<;x++){
ADC_ConvertedValueLocal[x] =(float) ADC_ConvertedValue[x]/*3.3; // 读取转换的AD值
LCD_ShowxNum(,+x*,ADC_ConvertedValue[x],,,);
} LCD_DrawLine(, , -(*ADC_ConvertedValueLocal[])+ , *ADC_ConvertedValueLocal[]+); Delay_ms();
LCD_Fill(,,,,LGRAY);
}
其中的重点是FOR语句:
for(x=;x<;x++){
ADC_ConvertedValueLocal[x] =(float) ADC_ConvertedValue[x]/*3.3; // 读取转换的AD值
LCD_ShowxNum(,+x*,ADC_ConvertedValue[x],,,);
}
在这个循环语句中,我们读取两个通道中的数据。其中ADC_ConvertedValue[x]为我们在一开始就定义的用来存数处理过的数据的数组,其中X取1和2。为什么这个数组可以接受到两个通道的数据呢?其实在DMA中我们设置了数据的传输:
DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //ADC地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&ADC_ConvertedValue;//内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
其中 传输方向为从外设到内存,而内存地址,我们取得就是这个数组的首地址,这样转换过的数据自然就被传到了这个数组中。那么传输之前数据的处理又是怎么进行的呢?从下面的程序可以看到,我们在ADC设置时,使能了扫描和连续转换,且通道数目为2,那么在连续转换时,ADC就会按照下面ADC_RegularChannelConfig中设置的转换顺序进行连续的转换所有的通道(即转换完通道1后转换通道2)。每次转换完一个通道后,转换好的数据就会被DMA转走,从而回到了上面的步骤。
ADC_InitStructure.ADC_ScanConvMode = ENABLE ; //禁止扫描模式,扫描模式用于多通道采集
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //开启连续转换模式,即不停地进行ADC转换
ADC_InitStructure.ADC_NbrOfChannel = ; //要转换的通道数目2
---------------------------------------------------------------------------------------- ADC_RegularChannelConfig(ADC1, ADC_Channel_11, , ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_10, , ADC_SampleTime_55Cycles5 );//ADC1;ADC1通道0;第2转换;采样时间为239.5周期
下面的语句是FOR语句下面的,实现的是话射线功能,我们使用的屏是240*320的,图形具体尺寸见下图:
LCD_DrawLine(, , -(*ADC_ConvertedValueLocal[])+ , *ADC_ConvertedValueLocal[]+); Delay_ms();
LCD_Fill(,,,,LGRAY);
中间一点是理论中心点(120,160),但是由于摇杆的电压不是太稳定,出现了电压波动,所示实际中心点是(124,159),中间的正方形是射线的边界。
还有最后的IF语句,设置的是按下是的操作,实现的是按下后在屏幕上刷黄色屏,出现一个字符串:
if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)!=){
LCD_Clear( YELLOW);
LCD_ShowString(,,,,, "THANKS TO YOU!");
Delay_ms();
LCD_Clear( WHITE);
}
现在整个实验就结束了。这个实验今天从早上7点多开始,我一直弄到下午4点半才成功,中间还百度了很多资料。
最终效果如下:
[完] 选自:http://www.ciast.net/post/20151227.html CIAST.NET @zgc261
双轴按键摇杆控制器控制TFTLCD(使用ADC1双通道DMA传输)的更多相关文章
- STM32驱动ILI9341控制器控制TFTLCD显示
STM32驱动ILI9341控制器控制TFTLCD显示 一.用STM32控制TFTLCD显示的编程方法,在编程驱动TFTLCD液晶显示器之前,我们先熟悉以下概念: 1.色彩深度,这是一个与TFTLCD ...
- 「雕爷学编程」Arduino动手做(38)——joystick双轴摇杆模块
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...
- iOS不得姐项目--推荐关注模块(一个控制器控制两个tableView),数据重复请求的问题,分页数据的加载,上拉下拉刷新(MJRefresh)
一.推荐关注模块(一个控制器控制两个tableView) -- 数据的显示 刚开始加载数据值得注意的有以下几点 导航控制器会自动调整scrollView的contentInset,最好是取消系统的设置 ...
- 循环灯控制器,该控制器控制红、绿、黄三个发光管循环发亮(VHDL语言)
设计一个循环灯控制器,该控制器控制红.绿.黄三个发光管循环发亮.要求红发光管亮2秒,绿发光管亮3秒,黄发光管亮1秒.(假设外部提供频率为1MHz的方波信号) library ieee; use iee ...
- HighCharts之2D柱状图、折线图的组合双轴图
HighCharts之2D柱状图.折线图的组合双轴图 1.实例源码 DoubleAxis.html: <!DOCTYPE html> <html> <head> & ...
- 如何拼接FusionCharts的JSON格式的双轴图
1.问题背景 假如,项目中遇到这样一个问题:利用FusionCharts中的JSON格式拼接双轴图,并将JSON字符串转换成JSON对象传输到前台,在页面上展示出来. 2.设计源码 /** * * @ ...
- Flex实现双轴条状图
1.问题背景 一般的,柱状图可以实现双轴图,但是如何实现双轴条状图? 2.实现实例 <?xml version="1.0" encoding="utf-8" ...
- Java DualPivotQuickSort 双轴快速排序 源码 笔记
DualPivotQuicksort source code 这个算法是Arrays.java中给基本类型的数据排序使用的具体实现.它针对每种基本类型都做了实现,实现的方式有稍微的差异,但是思路都是相 ...
- Python Pandas 时间序列双轴折线图
时间序列pv-gmv双轴折线图 import numpy as np import pandas as pd import matplotlib.pyplot as plt n = 12 date_s ...
随机推荐
- 如何实现动态水球图 --》 echars结合echarts-liquidfill实现
1)项目中作为项目依赖,安装到项目当中(注意必须要结合echars) npm install echarts vue-echarts --save npm install echarts-liquid ...
- oracle--表分区、分区索引
--|/ range分区 create table sale( product_id varchar2(5), sale_count number(10,2) ) partition by range ...
- javascript自定义Map对象
javascript定义map对象开发前端组件的重要性就不过多阐述了,直接参考以下案例即可 <script type=text/javascript charset=utf-8> func ...
- WOJ#2423 安全出行Safe Travel
描述 精灵最近在农场上泛滥,它们经常会阻止牛们从农庄(牛棚_1)走到别的牛棚(牛_i的目的 地是牛棚_i).每一个精灵只认识牛_i并且知道牛_i一般走到牛棚_i的最短路经.所以它们在牛_i到牛棚_i之 ...
- (转载)图解Java多态内存分配以及多态中成员方法的特点
图解Java多态内存分配以及多态中成员方法的特点 图解Java多态内存分配以及多态中成员方法的特点 Person worker = new Worker(); 子类实例对象地址赋值给父类类型引 ...
- ./configure 配置文件时出错checking for g++... no
checking for g++... no checking for c++... no checking for gpp... no checking for aCC... no 缺少C++编译器 ...
- hihocoder1954 : 压缩树
传送门 首先求出缩一个点 $x$ 的贡献,就是缩 $x$ 的父亲的贡献加上 $x$ 的子树多减少的深度 假设此时缩父亲的贡献已经考虑过了,那么 $x$ 的子树多减少的深度就是子树的节点数 注意此时要满 ...
- PCIe基础篇(一)、基础知识扫盲
1.PCIe:Peripheral Component interconnect Expess,外围组件接口互联,属于第三代IO总线,PCIe的传输速率指的是实际的有效传输速率,为RAW data(原 ...
- 数据分析之pandas(1)
一.Pandas的数据结构 1.Series (1)类似于一维数组 (2)通过list构建Series ser_obj=pd.Series(range(10)) (3)pandas数据结构案例
- TP5+阿里云OSS上传文件第三节,实现淘宝上传商品图片
**TP5+阿里云OSS上传文件第三节,实现淘宝上传商品图片首先我们来看看淘宝的功能和样式:** 之后看看制作完成的演示:(由于全部功能弄成GIF有点大,限制上传大小好像在1M之内,压缩之后也有1.9 ...