STM32 触摸式低频信号发生系统(DAC+DMA+TIM配置)(码表配置)#触摸屏识别

本文详细描述了一种使用STM32微控制器开发的触摸式系统,通过DAC、DMA和TIM协同工作,生成频率从1Hz到200KHz的正弦、三角和矩形信号。文章介绍了各个模块的配置方法,包括DAC初始化、定时器配置、DMA配置,以及如何利用码表生成不同波形并配合人机交互界面。
摘要由CSDN通过智能技术生成

前言

该系统在以ARMCortex-M4为处理器的嵌入式芯片STM32开发板下运行。系统通过TFT-LCD电阻触摸屏作为人机交互的方式和信号的输入方式,输入的消息在屏幕上实时可见。

通过DAC+DMA+TIMER的方式,通过引脚,输出频率从1Hz到200KHz的正弦信号、三角信号、矩形信号。其中,矩形信号能够调节占空比从10%至90%。具体的频率可以根据板子的主频更改。在项目验收时系统的误差很小。在100K以下,示波器矫正好的情况下,调多少输出多少。

文章目前还在更新中。可以提供项目源码。

DAC+DMA+TIM配置

芯片用的stm32f407。平台是正点原子的stm32f407最小系统板。
初始化参考《stm32f4xx中文参考手册》
寄存器编写。

stm32f4xx中文参考手册》链接: https://pan.baidu.com/s/1aeG4RD4r8rJq5vnV16efIA 提取码: tgfb 

DAC初始化

(开始先讲讲我的编写思路以及过程。
需要CV的话,代码在最后~~)
初始化DAC时钟
RCC_APB1ENR寄存器

RCC->APB1ENR|=1<<29;   	//使能DAC时钟	

使能porta
RCC AHB1 外设时钟使能寄存器 (RCC_AHB1ENR)

RCC->AHB1ENR|=1<<0;  	//使能PORTA时钟

配置DAC
dac控制寄存器

DAC->CR|=1<<1;	//DAC1输出缓存不使能 BOFF1=1
DAC->CR|=1<<2;	//使用触发功能 TEN1=0
DAC->CR&=~(7<<3);	//DAC TIM6 TRGO,不过要TEN1=1才行
DAC->CR|=0<<6;	//不使用波形发生
DAC->CR|=0<<8;	//屏蔽、幅值设置
DAC->CR|=1<<12;	//DAC1 DMA使能 		
DAC->DHR12R1=0;//使能通道1

总的代码如下:

/******************DAC初始化*************************/
void SineWave_DAC_Config(u8 NewState1)
{
  	RCC->APB1ENR|=1<<29;   	//使能DAC时钟	   
	RCC->AHB1ENR|=1<<0;  	//使能PORTA时钟	  
	DAC->CR|=1<<0;	//使能DAC1
	DAC->CR|=1<<1;	//DAC1输出缓存不使能 BOFF1=1
	DAC->CR|=1<<2;	//使用触发功能 TEN1=0
 	DAC->CR&=~(7<<3);	//DAC TIM6 TRGO,不过要TEN1=1才行
	DAC->CR|=0<<6;	//不使用波形发生
	DAC->CR|=0<<8;	//屏蔽、幅值设置
	DAC->CR|=1<<12;	//DAC1 DMA使能 		
  	DAC->DHR12R1=0;//使能通道1
}

定时器配置

首先使能定时器
时钟使能寄存器

RCC->APB1ENR|=1<<4;	//TIM6时钟使能  

对于波形输出,使用的是基本定时器tim6控制的。
TIM6 和 TIM7 控制寄存器 1 (TIMx_CR1)
在这里插入图片描述
在这里插入图片描述
反应到代码上:

TIM6->CR1 &= 0xFF00;
TIM6->CR1|=0<<4;//向上计数模式

TIM6->CR1|=1<<0;//在控制寄存器中,使能timer6

通过预分频寄存器分频
在这里插入图片描述

TIM6->PSC=0x0;//

TIM6频率其实关系这最终输出波形的频率。如果想输出速度低一些的话可以通过更改tim6的预分频实现
在这里插入图片描述

TIM6->CR2&=~(7<<4);//设置控制寄存器2,将TIM6更新设为TRGO
TIM6->CR2|=2<<4;
TIM6->DIER|=1<<8;   //允许更新DMA请求	 

在这里插入图片描述
最终的代码如下

/*********定时器配置************/
void SineWave_TIM_Config( u32 Wave1_Fre ,u8 NewState1)
{
if(NewState1){
  RCC->APB1ENR|=1<<4;	//TIM6时钟使能  
  TIM6->PSC=0x0;//
  TIM6->CR1 &= 0xFF00;
  TIM6->CR1|=0<<4;//向上计数模式
  TIM6->ARR=Wave1_Fre;
  TIM6->CR2&=~(7<<4);//设置控制寄存器2,将TIM6更新设为TRGO
  TIM6->CR2|=2<<4;
  TIM6->DIER|=1<<8;   //允许更新DMA请求	 
  TIM6->CR1|=1<<0;//在控制寄存器中,使能timer6
	// TIM6->CR2 &= (u16)~((u16)0x0070);//设置TIM2输出触发为更新模式
	// TIM6->CR2 |=0x0020;//设置TIM2输出触发为更新模式
	}
}

DMA配置

void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u8 chx,u16 *mar)
{ 
	DMA_TypeDef *DMAx;
	u8 streamx;
	if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1
	{
		DMAx=DMA2;
		RCC->AHB1ENR|=1<<22;//DMA2时钟使能 
	}else 
	{
		DMAx=DMA1; 
 		RCC->AHB1ENR|=1<<21;//DMA1时钟使能 
	}
	while(DMA_Streamx->CR&0X01);//等待DMA可配置 
	streamx=(((u32)DMA_Streamx-(u32)DMAx)-0X10)/0X18;		//得到stream通道号
 	if(streamx>=6)DMAx->HIFCR|=0X3D<<(6*(streamx-6)+16);	//清空之前该stream上的所有中断标志
	else if(streamx>=4)DMAx->HIFCR|=0X3D<<6*(streamx-4);    //清空之前该stream上的所有中断标志
	else if(streamx>=2)DMAx->LIFCR|=0X3D<<(6*(streamx-2)+16);//清空之前该stream上的所有中断标志
	else DMAx->LIFCR|=0X3D<<6*streamx;						//清空之前该stream上的所有中断标志
	
	DMA_Streamx->PAR=DAC_DHR12R1;		//DMA外设地址
	DMA_Streamx->M0AR=(u32)mar;		//DMA 存储器0地址
	DMA_Streamx->NDTR=256;		//DMA 存储器0地址
	DMA_Streamx->CR=0;			//先全部复位CR寄存器值 
	
	DMA_Streamx->CR|=1<<6;		//存储器到外设模式
	DMA_Streamx->CR|=1<<8;		//循环模式
	DMA_Streamx->CR|=0<<9;		//外设非增量模式
	DMA_Streamx->CR|=1<<10;		//存储器增量模式

	DMA_Streamx->CR|=1<<11;		//外设数据长度:16位
	DMA_Streamx->CR|=1<<13;		//存储器数据长度:16位

	DMA_Streamx->CR|=1<<16;		//top优先级
  DMA_Streamx->CR|=1<<17;		
  DMA_Streamx->CR|=0<<21;	//外设突发单次传输	
	DMA_Streamx->CR|=0<<23;		//存储器突发单次传输
	
	DMA_Streamx->CR|=(u32)chx<<25;//通道选择
  DMA_Streamx->CR|=1<<0;		//开启DMA传输
	//DMA_Streamx->FCR=0X21;	//FIFO控制寄存器
} 

码表设置

正弦码表

首先用一个数组保存码表,里面实际上存储的是电压值

#define Vref 3.2		//0.1~3.3V可调
#define Um  (Vref/2)
#define N 256
u16 SineWave_Value[256];		//已用函数代替
SineWave_Data( N ,SineWave_Value);		//生成波形表1
/********生成正弦波形输出表***********/
void SineWave_Data( u16 cycle ,u16 *D)
{
	u16 i;
	for( i=0;i<cycle;i++)
	{
		D[i]=(u16)((Um*sin((1.0*i/(cycle-1))*2*PI)+Um)*4095/3.3);
	}
}	

对算式的解释

D[i]=(u16)((Umsin((1.0i/(cycle-1))2PI)+Um)*4095/3.3);

  1. (1.0*i/(cycle-1)): 这部分是用来生成正弦波的周期。i 是循环变量,从 0 到 cycle-1,这个值会决定正弦波形的采样点数。将 i 值除以 cycle-1 可以确保在一个周期内等间隔地采样。

  2. 2*PI: 这是一个常量,表示一个完整的圆周。在这里乘以前面的部分相当于将周期映射到 0 到 2π 的范围内。

  3. sin(): 这是一个数学函数,用来计算正弦值。它接受一个角度(以弧度为单位)作为输入,并返回该角度的正弦值。

  4. Um: 是一个振幅参数,用来控制正弦波的振幅大小。

  5. *4095/3.3: 这部分是将正弦波的振幅映射到 0 到 4095 的范围内,以适应12位的DAC。通常在嵌入式系统中,模拟信号的范围是 0 到 3.3V,将其映射到 0 到 4095 对应着一种标准的DAC(数模拟转换器)输出范围。

三角波码表

#define N 256
u16 TriWave_Value[256]
TreAngle_Data(N,TriWave_Value);
/********生成锯齿波形输出表***********/
void TreAngle_Data( u16 cycle ,u16 *D)
{
	u16 i;
	for( i=0;i<cycle/2;i++)
	{
		D[i]= (u16)(1.0*i/(cycle/2-1)*4095);
    D[cycle-1-i]= D[i];
	}
  D[cycle/2-1]=(u16)(1.0*4095);
}

在函数中,通过一个 for 循环来生成三角波形数据。循环从 0 开始,逐步增加到 cycle/2。在循环体内,使用了如下公式来计算每个点的数值:

D[i] = (u16)(1.0 * i / (cycle/2 - 1) * 4095);

这个公式的含义是,将当前循环的索引 i 除以周期的一半减一 (cycle/2 - 1),然后乘以 4095,即12位的DAC最大值,这样就生成了三角波形的上半部分数据。
接着,通过 D[cycle-1-i] = D[i]; 将生成的数据复制到下半部分。
最后,将数组中间位置的值设置为最大值,以确保波形的上半部分和下半部分连接起来形成闭合的波形。

这样就完成了三角波形输出表的生成。

矩形码表

u16 Square_Value[100];
Square_Data( 5*10 ,Square_Value);
/
void Square_Data(u16 Duty,u16 *D)
{
	u16 i;
	for( i=0;i<100;i++)
	{
		if(i<Duty)D[i]= (u16)(1.0*4095);
    else D[i]=0;
	}
}

函数接受两个参数:Duty 是方波的占空比,D 是一个指向 u16 类型的数组的指针,用来存储生成的方波数据。

在函数中,通过一个 for 循环来生成方波形数据。循环从 0 开始,逐步增加到 99。在循环体内,通过判断当前索引 i 是否小于 Duty,来确定方波的状态。如果 i 小于 Duty,则将数组元素设为最大值 4095(高电平),否则设为 0(低电平),这样就形成了方波。

这段代码生成的方波数据长度为 100,你传入的 Duty 参数表示的是方波的占空比,即方波高电平持续的时间在总周期内的比例。

对于幅度,也可以通过与4095(即12位的DAC最大值)的比例构成。

4095/num=3.2/volt

码表与定时器、dma的配合使用

直接上代码!

#define N 256


u16 SineWave_Value[256];		
MZYWave_Init(100);

/***********正弦波初始化***************/

void MZYWave_Init(u16 Wave_Fre)
{
  
  u16 f1=(u16)(84000000/sizeof(SineWave_Value)*2/Wave_Fre);
  SineWave_Data( N ,SineWave_Value);		//生成波形表1
  SineWave_Data( N/2 ,SineWave_Value2);		//生成波形表
  SineWave_Data( N/4 ,SineWave_Value3);		//生成波形表
  SineWave_Data( N/16 ,SineWave_Value4);		//生成波形表

  TreAngle_Data(N,TriWave_Value);
  TreAngle_Data(N/16,TriWave_Value2);

  Square_Data( 5*10 ,Square_Value);
  
  SineWave_GPIO_Config(ENABLE);			  //初始化引脚 
  SineWave_TIM_Config(f1,1);			  //初始化定时器 
  SineWave_DAC_Config(1);			  //初始化DAC
  MYDMA_Config(DMA1_Stream5,7,TriWave_Value);//DMA2,STEAM7,CH4,外设为串口1,存储器为SendBuff,长度为:SEND_BUF_SIZE		  //初始化DMA
  TIM6->CR1|=0x01;    //使能定时器2;			 //使能TIM6,开始产生波形  
  DMA1_Stream5->CR |=1<<0;		//开启DMA传输

  // DMA1_Stream5->CR &= ~0x0001;
  // TIM6->CR1&=~0x0001; //关定时器
}

计算方式是基于系统时钟频率(84MHz)除以波形表的大小’(sizeof(SineWave_Value))',然后乘以 2,再除以所需的波形频率(Wave_Fre)。这样计算得到的 f1 就是使得定时器产生所需频率的周期。

触摸屏幕的处理

硬件使用正点原子 2.8 寸TFTLCD屏。像素是320*240.
屏幕触摸识别的原理不赘述。在触摸时,屏幕返还给单片机的其实是横纵像素的位置。横坐标(0到320),纵坐标(0到240)。基于这个原理,我们就可以通过识别坐标的方式,判断屏幕按下的位置。

typedef struct _st_2d_int_point_info_
{
  unsigned   short m_i16x;//
  unsigned   short m_i16y;//
} ST_2D_INT_POINT_INFO;

这段代码用于定义x,y坐标。这种方式可以用于各种二维坐标的判断。比如飞卡二维图像的处理。

typedef struct _my_lcd_key_
{
  ST_2D_INT_POINT_INFO SPOT_PUTIN;
  uint32_t KeyNum;
  unsigned short KeySelceted;
  unsigned short KeySelcetedTimes;
  uint32_t FreIn;
  unsigned char Singal_Type;
  unsigned char Square_Duty;
  unsigned char Duty_Seceled;
} MY_LCD_KEY;

SPOT_PUTIN是有用的,其余变量只是做个例子。

		  tp_dev.scan(0); //扫描放TIM3里面		 
		  if(TIMCnt.KeyDown==3)			//触摸屏被按下
      {	
        if(tp_dev.x[0]<lcddev.width&&tp_dev.y[0]<lcddev.height)
        {
          LCD_ZONE_SECLECT(tp_dev.x[0],tp_dev.y[0]);
        }
      }else
      { 
        LCD_KEY.KeySelceted=0;   
      }

-tp_dev.scan(0) 是正点原子官方提供的函数,在touch.c里面。还是有几百行,我就不想写了。

  • lcddev.width是屏幕长度,lcddev.height是屏幕宽度。
  • KeyDown=3前面跟着一个消抖过程。
void LCD_ZONE_SECLECT(u16 spotx,u16 spoty)
{
  uint32_t FreSet=LCD_KEY.FreIn;
	u16 f1;
	 int i;
  u16 *add;
  u16 IntPart;
  u16 TempPart;
	ST_2D_INT_POINT_INFO LCD_UI={20,150};
  ST_2D_INT_POINT_INFO STEP={60,30};
  LCD_KEY.SPOT_PUTIN.m_i16x=spotx;
  LCD_KEY.SPOT_PUTIN.m_i16y=spoty;
 
	
//	LCD_Clear(WHITE);
//	for(i=0;i<16;i++)
//  LCD_Fast_DrawPoint(SineWave_Value4[i]/50,i,RED);
  if (!LCD_KEY.KeySelceted)
  {
    LCD_KEY.KeySelceted=1;//防长按,按一次不抬起认为是按一次
    LCD_KEY.KeySelcetedTimes++;
    if (LCD_KEY.SPOT_PUTIN.m_i16x > LCD_UI.m_i16x && LCD_KEY.SPOT_PUTIN.m_i16x < LCD_UI.m_i16x + STEP.m_i16x)//按了按键1
    {
      if (LCD_KEY.SPOT_PUTIN.m_i16y> LCD_UI.m_i16y && LCD_KEY.SPOT_PUTIN.m_i16y<LCD_UI.m_i16y + STEP.m_i16y )
      {
        LCD_KEY.KeyNum=1;//后面用来运算
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 2*STEP.m_i16y && LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y + STEP.m_i16y )
      {
        LCD_KEY.KeyNum=4;//后面用来运算
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 3*STEP.m_i16y && LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y + 2* STEP.m_i16y )
      {
        LCD_KEY.KeyNum=7;//后面用来运算
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 4*STEP.m_i16y&& LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y + 3* STEP.m_i16y)
      {
        
        LCD_KEY.KeyNum=11;
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 5*STEP.m_i16y&& LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y + 4* STEP.m_i16y)
      {
        LCD_KEY.KeyNum=13;
        LCD_KEY.Singal_Type++;
        if (LCD_KEY.Singal_Type==3)
        {
          LCD_KEY.Singal_Type=0;
        }
        
        if (LCD_KEY.Singal_Type==0)
        {
          LCD_KEY.Duty_Seceled=1;
          Show_Str(10,60,200,16,"波形类型:正弦",16,0);
        }
        else if (LCD_KEY.Singal_Type==1)
        {
          LCD_KEY.Duty_Seceled=1;
          Show_Str(10,60,200,16,"波形类型:三角",16,0);
        }
        else if (LCD_KEY.Singal_Type==2)
        {
          Show_Str(10,60,200,16,"波形类型:方波",16,0);
        }
      }
    }
    else if (LCD_KEY.SPOT_PUTIN.m_i16x > LCD_UI.m_i16x + STEP.m_i16x && LCD_KEY.SPOT_PUTIN.m_i16x < LCD_UI.m_i16x + 2*STEP.m_i16x)
    {
      if (LCD_KEY.SPOT_PUTIN.m_i16y> LCD_UI.m_i16y && LCD_KEY.SPOT_PUTIN.m_i16y<LCD_UI.m_i16y + STEP.m_i16y )
      {
        LCD_KEY.KeyNum=2;//后面用来运算
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 2*STEP.m_i16y && LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y +  STEP.m_i16y)
      {
        LCD_KEY.KeyNum=5;//后面用来运算
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 3*STEP.m_i16y && LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y + 2* STEP.m_i16y)
      {
        LCD_KEY.KeyNum=8;//后面用来运算
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 4*STEP.m_i16y&& LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y + 3* STEP.m_i16y)
      {
        LCD_KEY.KeyNum=12;
      }
      else if (LCD_KEY.SPOT_PUTIN.m_i16y < LCD_UI.m_i16y + 5*STEP.m_i16y&& LCD_KEY.SPOT_PUTIN.m_i16y>LCD_UI.m_i16y + 4* STEP.m_i16y)
      {
        LCD_KEY.KeyNum=0;
        Show_Str(90,120,200,16,"停止",16,0);	
        TIM6->CR1&=~0x0001;    //关定时器
      }
    }

上图是关于识别屏幕按下范围的部分代码。您可以按照自己的想法优化一下。首先解释下变量:

  • 1

LCD_KEY.SPOT_PUTIN.m_i16x=spotx;
LCD_KEY.SPOT_PUTIN.m_i16y=spoty;
是传入屏幕按下的横纵坐标。不使用全局变量,提高函数内聚性。

  • 2

ST_2D_INT_POINT_INFO LCD_UI={20,150};是如下图的,一个键盘,坐上脚,的起始点。

在这里插入图片描述

ST_2D_INT_POINT_INFO STEP={60,30};是键盘中每条横线的间隔。

  • 3
    u16 IntPart;
    u16 TempPart;
    用于保存需要输出的频率的整数和小数部分。(使用前把示波器自校准下,小数部分也能准确输出)

判断的方式其实很粗糙和直接,判断按下的像素点是否处于某个区间。由于误差的存在,每个数字的判断范围拉的稍微大一点。

如果你有其它的方法也说出来吧我也学习下。

下面是关于代码的解释。例如
if (LCD_KEY.SPOT_PUTIN.m_i16x > LCD_UI.m_i16x && LCD_KEY.SPOT_PUTIN.m_i16x < LCD_UI.m_i16x + STEP.m_i16x)//按了按键1
判断是否处于数字一的范围。如果是,就保存1.在最后,参与最终的频率值的计算。如下

    if (LCD_KEY.Duty_Seceled)
    {
      if (LCD_KEY.KeyNum==11)
      {
        FreSet =  FreSet/10;
      }
      else if (LCD_KEY.KeyNum==12)
      {
        FreSet = FreSet * 10;
      }
      else if (LCD_KEY.KeyNum>0&&LCD_KEY.KeyNum<=9)
      {
        FreSet = FreSet*10+LCD_KEY.KeyNum;
      }
      LCD_KEY.KeyNum=0;
    }
    else
    {
      if (LCD_KEY.KeyNum>0&&LCD_KEY.KeyNum<=9)
        LCD_KEY.Square_Duty=LCD_KEY.KeyNum;
    }
    if (FreSet<200001)
    {
      LCD_KEY.FreIn = FreSet;
    }

LCD_KEY.KeyNum 就是按下的值,不同的数字对应不同的计算方式。

–最新写于2024.7.13-- 更新中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值