2021暑假一起练-基于stm32f072制作dds信号发生器
基于stm32f072单片机制作的dds信号函数发生器,能够产生正弦波,方波,锯齿波,三角波
标签
嵌入式系统
STM32
显示
DDS
darren185
更新2021-09-08
1392

基于stm32f072制作的简易信号发生器

本项目实现简易函数发生器和pwm输出两个通道,函数发生器通道实现三角波,方波,锯齿波,正弦波形,频率,幅值,频率,偏置电压可调,pwm输出通道实现占空比,频率可调节,由几下9部分组成:

选择平台及基础设置,选择项目模板,液晶驱动,RTC时钟,按键,波形发生器驱动,pwm驱动,硬件仿真分析,人机交互界面,总结报告.
1,选择平台及基础设置
      此项目使用开发软件用到意法半导体生态链中重要的软件之一——stm32cubemxIDE及stm32cubeprgram.其中stm32cubemxIDE包含stm32cubemx,通过使用stm32cubemx 软件配置外设驱动,液晶驱动采用硬件spi驱动,按键驱动,dac,定时器,以及单片机内部时钟配置等,通过可视化配置外设,大大提高开发效率,stm32cubemxIDE用于代码编写,编译,通过stm32cubeprgram烧录
2,选择项目模板
采用stm32cubemx软件生成,配置包底层驱动后,通过查看液晶屏数据数据手册,及液晶屏初始化配置代码,完成液晶屏清屏,设置显示窗口,单点显示功能, 配合按键实现参数设定及功能切换,配置dac定时器触发dma通道,通过查表法实现波形切换,幅值修改,偏置电压修改,通过修改定时器实现频率修改。通过定时器pwm输出通道实现方波输出,通过修改定时时间,实现方波频率修改.
3,液晶驱动
      液晶屏为串行spi接口,硬件连接到stm32硬件spi接口,为提高刷新速率,spi驱动部分采用stm32官方提供的LL库,相比stm32官方提供的HAL库有明显的优化,本项目实现多参数设置,通过修改文字背景颜色实现选中与未选中的区.下面是液晶驱动程序:

#ifndef __LCD_H
#define __LCD_H		
#include "gpio.h"

#include "stdlib.h"

//LCD重要参数集
typedef struct  
{										    
	uint16_t width;			//LCD 宽度
	uint16_t height;			//LCD 高度
	uint16_t id;				  //LCD ID
	uint8_t  dir;			  //横屏还是竖屏控制:0,竖屏;1,横屏。
	uint16_t	 wramcmd;		//开始写gram指令
	uint16_t  setxcmd;		//设置x坐标指令
	uint16_t  setycmd;		//设置y坐标指令
  uint8_t   xoffset;    
  uint8_t	 yoffset;
}_lcd_dev; 	

//LCD参数
extern _lcd_dev lcddev;	//管理LCD重要参数
/////////////////////////////////////用户配置区///////////////////////////////////
#define USE_HORIZONTAL  	 3 //定义液晶屏顺时针旋转方向 	0-0度旋转,1-90度旋转,2-180度旋转,3-270度旋转

//////////////////////////////////////////////////////////////////////////////////	  
//定义LCD的尺寸
#define LCD_W 240
#define LCD_H 240

//TFTLCD部分外要调用的函数
extern uint16_t  POINT_COLOR;//默认红色
extern uint16_t  BACK_COLOR; //背景颜色.默认为白色

#define LCD_RS  15       //寄存器/数据选择引脚 D2
#define LCD_RST  4      //复位引脚            	B6


//QDtech全系列模块采用了三极管控制背光亮灭,用户也可以接PWM调节背光亮度
#define	LCD_LED PBout(LED) //LCD背光    		 PB9
//如果使用官方库函数定义下列底层,速度将会下降到14帧每秒,建议采用我司推荐方法
//以下IO定义直接操作寄存器,快速IO操作,刷屏速率可以达到28帧每秒!


#define	LCD_RS_SET	GPIOA->BSRR=1<<LCD_RS    //数据/命令  PB10
#define	LCD_RST_SET	GPIOB->BSRR=1<<LCD_RST   //复位			  PB12

 
#include "lcd.h"
#include "stdlib.h"
#include "string.h"
#include "font.h"
#include "SPI.h"

_lcd_dev lcddev;

uint16_t POINT_COLOR = 0x0000,BACK_COLOR = 0xFFFF;
uint16_t DeviceCode;

void LCD_WR_REG(uint8_t data)
{

    LCD_RS_CLR;
   // HAL_SPI_Transmit(&hspi1, &data, 1, 1000);
    while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {}
	LL_SPI_TransmitData8(SPI1, data);
}

void LCD_WR_DATA(uint8_t data)
{

    LCD_RS_SET;
    while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {}
	LL_SPI_TransmitData8(SPI1, data);            //发送8位数据

}

void LCD_WriteReg(uint8_t LCD_Reg, uint16_t LCD_RegValue)
{
    LCD_WR_REG(LCD_Reg);
    LCD_WR_DATA(LCD_RegValue);
}

void LCD_WriteRAM_Prepare(void)
{
    LCD_WR_REG(lcddev.wramcmd);
}

void Lcd_WriteData_16Bit(uint16_t Data)
{
	LCD_RS_SET;
	while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {}
	LL_SPI_TransmitData8(SPI1, Data>>8);
	LL_SPI_TransmitData8(SPI1, Data&0xff);
}


void LCD_DrawPoint(uint16_t x,uint16_t y,uint16_t pc)
{
    LCD_SetCursor(x,y);//设置光标位置
    Lcd_WriteData_16Bit(pc);
}

void LCD_Clear(uint16_t Color)
{

    unsigned int i,m;
    LCD_SetWindows(0,0,lcddev.width-1,lcddev.height-1);
    LCD_RS_SET;
    for(i=0; i<240; i++)
    {
        for(m=0; m<240; m++)
        {
        	  while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {}
        	  LL_SPI_TransmitData8(SPI1, Color>>8);
        	  while(!LL_SPI_IsActiveFlag_TXE(SPI1)) {}
        	  LL_SPI_TransmitData8(SPI1, Color&0xff);
        }
    }

}


void LCD_Init(void)
{

    LCD_RST_CLR;
    HAL_Delay (200);
    LCD_RST_SET;
    HAL_Delay(200);

    LCD_WR_REG(0x36);
    LCD_WR_DATA(0x00);

    LCD_WR_REG(0x3A);
    LCD_WR_DATA(0x05);

    LCD_WR_REG(0xB2);
    LCD_WR_DATA(0x0C);
    LCD_WR_DATA(0x0C);
    LCD_WR_DATA(0x00);
    LCD_WR_DATA(0x33);
    LCD_WR_DATA(0x33);

    LCD_WR_REG(0xB7);
    LCD_WR_DATA(0x35);

    LCD_WR_REG(0xBB);
    LCD_WR_DATA(0x19);

    LCD_WR_REG(0xC0);
    LCD_WR_DATA(0x2C);

    LCD_WR_REG(0xC2);
    LCD_WR_DATA(0x01);

    LCD_WR_REG(0xC3);
    LCD_WR_DATA(0x12);

    LCD_WR_REG(0xC4);
    LCD_WR_DATA(0x20);

    LCD_WR_REG(0xC6);
    LCD_WR_DATA(0x0F);

    LCD_WR_REG(0xD0);
    LCD_WR_DATA(0xA4);
    LCD_WR_DATA(0xA1);

    LCD_WR_REG(0xE0);
    LCD_WR_DATA(0xD0);
    LCD_WR_DATA(0x04);
    LCD_WR_DATA(0x0D);
    LCD_WR_DATA(0x11);
    LCD_WR_DATA(0x13);
    LCD_WR_DATA(0x2B);
    LCD_WR_DATA(0x3F);
    LCD_WR_DATA(0x54);
    LCD_WR_DATA(0x4C);
    LCD_WR_DATA(0x18);
    LCD_WR_DATA(0x0D);
    LCD_WR_DATA(0x0B);
    LCD_WR_DATA(0x1F);
    LCD_WR_DATA(0x23);

    LCD_WR_REG(0xE1);
    LCD_WR_DATA(0xD0);
    LCD_WR_DATA(0x04);
    LCD_WR_DATA(0x0C);
    LCD_WR_DATA(0x11);
    LCD_WR_DATA(0x13);
    LCD_WR_DATA(0x2C);
    LCD_WR_DATA(0x3F);
    LCD_WR_DATA(0x44);
    LCD_WR_DATA(0x51);
    LCD_WR_DATA(0x2F);
    LCD_WR_DATA(0x1F);
    LCD_WR_DATA(0x1F);
    LCD_WR_DATA(0x20);
    LCD_WR_DATA(0x23);

    LCD_WR_REG(0x21);

    LCD_WR_REG(0x11);
    //Delay (120);

    LCD_WR_REG(0x29);
    LCD_direction(USE_HORIZONTAL);//设置LCD显示方向


    LCD_Clear(BLACK);//清全屏白色
}

void LCD_SetWindows(uint16_t xStar, uint16_t yStar,uint16_t xEnd,uint16_t yEnd)
{
    LCD_WR_REG(lcddev.setxcmd);
    LCD_WR_DATA((xStar+lcddev.xoffset)>>8);
    LCD_WR_DATA(xStar+lcddev.xoffset);
    LCD_WR_DATA((xEnd+lcddev.xoffset)>>8);
    LCD_WR_DATA(xEnd+lcddev.xoffset);

    LCD_WR_REG(lcddev.setycmd);
    LCD_WR_DATA((yStar+lcddev.yoffset)>>8);
    LCD_WR_DATA(yStar+lcddev.yoffset);
    LCD_WR_DATA((yEnd+lcddev.yoffset)>>8);
    LCD_WR_DATA(yEnd+lcddev.yoffset);

    LCD_WriteRAM_Prepare();	//开始写入GRAM
}


void LCD_SetCursor(uint16_t Xpos, uint16_t Ypos)
{
    LCD_SetWindows(Xpos,Ypos,Xpos,Ypos);
}

void LCD_direction(uint8_t direction)
{
    lcddev.setxcmd=0x2A;
    lcddev.setycmd=0x2B;
    lcddev.wramcmd=0x2C;
    switch(direction) {
    case 0:
        lcddev.width=LCD_W;
        lcddev.height=LCD_H;
        lcddev.xoffset=0;
        lcddev.yoffset=0;
        LCD_WriteReg(0x36,0);//BGR==1,MY==0,MX==0,MV==0
        break;
    case 1:
        lcddev.width=LCD_H;
        lcddev.height=LCD_W;
        lcddev.xoffset=0;
        lcddev.yoffset=0;
        LCD_WriteReg(0x36,(1<<6)|(1<<5));//BGR==1,MY==1,MX==0,MV==1
        break;
    case 2:
        lcddev.width=LCD_W;
        lcddev.height=LCD_H;
        lcddev.xoffset=0;
        lcddev.yoffset=80;
        LCD_WriteReg(0x36,(1<<6)|(1<<7));//BGR==1,MY==0,MX==0,MV==0
        break;
    case 3:
        lcddev.width=LCD_H;
        lcddev.height=LCD_W;
        lcddev.xoffset=80;
        lcddev.yoffset=0;
        LCD_WriteReg(0x36,(1<<7)|(1<<5));//BGR==1,MY==1,MX==0,MV==1
        break;
    default:
        break;
    }
}


void LCD_Fill(uint16_t sx,uint16_t sy,uint16_t ex,uint16_t ey,uint16_t color)
{
    uint16_t i,j;
    uint16_t width=ex-sx+1; 		//得到填充的宽度
    uint16_t height=ey-sy+1;		//高度
    LCD_SetWindows(sx,sy,ex,ey);//设置显示窗口
    for(i=0; i<height; i++)
    {
        for(j=0; j<width; j++)
            Lcd_WriteData_16Bit(color);	//写入数据
    }
    //LCD_SetWindows(0,0,lcddev.width-1,lcddev.height-1);//恢复窗口设置为全屏
}
void GUI_DrawFont3232(uint16_t x, uint16_t y, uint16_t fc, uint16_t bc,    char str[3] )//显示汉字
{
  uint16_t i, j;
  uint16_t k;
  uint16_t HZnum;
  uint16_t x0 = x;
  HZnum = sizeof(tfont3232) / sizeof(typFNT_GB32_32); //自动统计汉字数目
  for (k = 0; k < HZnum; k++)
  {
	  //LCD_SetWindows(x,y,x+32,y+32);//设置显示窗口
    if ((tfont3232[k].Index[0] == *(str)  ) && (tfont3232[k].Index[1] == *(str + 1)  ) && (tfont3232[k].Index[2] == *(str + 2)  ))
    {
    	 LCD_SetWindows(x,y,x+31,y+31);//设置显示窗口

    	    for(i=0; i<32*4; i++)
    	    {

    	    	        for (j = 0; j < 8; j++)
    	    	        {
    	    	          if (tfont3232 [k].Msk[i] & (0x80 >> j)) {
    	    	        	 // LCD_DrawPoint(x, y , fc);
    	    	        	  Lcd_WriteData_16Bit(fc);	//写入数据
    	    	          }
    	    	          else {

    	    	        	  Lcd_WriteData_16Bit(bc);
    	    	          }

    	    	        }
    	    }

//      for (i = 0; i < 128; i++)
//      {
//        for (j = 0; j < 8; j++)
//        {
//          if (tfont3232 [k].Msk[i] & (0x80 >> j)) {
//        	  LCD_DrawPoint(x, y , fc);
//          }
//          else {
//        	  LCD_DrawPoint(x, y, bc);
//          }
//          x++;
//          if ((x - x0) == 32) {
//            x = x0;
//            y++;
//            break;
//          }
//        }
//      }
    }
    continue;  //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
  }
}

void GUI_DrawFont3216(uint16_t x, uint16_t y, uint16_t fc, uint16_t bc,   char str[2] )//显示数字字符
{
  uint16_t i, j;
  uint16_t k;
  uint16_t HZnum;
  uint16_t x0 = x;
  HZnum = sizeof(tfont3216) / sizeof(typFNT_GB32_16); //自动统计汉字数目

  for (k = 0; k < HZnum; k++)
  {
    if ((tfont3216[k].Index[0] == *(str)  ))
    {

   	 LCD_SetWindows(x,y,x+15,y+31);//设置显示窗口

   	    for(i=0; i<32*2; i++)
   	    {
			for (j = 0; j < 8; j++)
			{
			  if (tfont3216 [k].Msk[i] & (0x80 >> j)) {
				  Lcd_WriteData_16Bit(fc);	//写入数据
			  }
			  else {
				  Lcd_WriteData_16Bit(bc);
			  }

			}
   	    }


//      for (i = 0; i < 64; i++)
//      {
//        for (j = 0; j < 8; j++)
//        {
//          if (tfont3216 [k].Msk[i] & (0x80 >> j)) {
//        	  LCD_DrawPoint(x, y , fc);
//          }
//          // else{gfx->drawPixel(x,y,(( gImage_pic[2*(x+(y-24)*320)+1])<<8)|( gImage_pic[2*(x+(y-24)*320)]));}
//          else {
//        	  LCD_DrawPoint(x, y, bc);
//          }
//          x++;
//          if ((x - x0) == 16) {
//            x = x0;
//            y++;
//            break;
//          }
//        }
//      }
    }
    continue;  //查找到对应点阵字库立即退出,防止多个汉字重复取模带来影响
  }
}

void show_string(int32_t x, int32_t y,   char str[], uint32_t length, uint16_t fc,uint16_t bc )//显示字符串
{
  uint8_t i = 0, j = 0;
  for (i = 0; i < length; i++)
  {
    if ((*(str) >= 0x20) && (*(str) <= 0x7e)) //字符
    {
      GUI_DrawFont3216(x, y , fc, bc,str  );
      x  += 16;
      str++;
    }
    else
    {
      GUI_DrawFont3232(x, y , fc,bc, str );//汉字
      x  += 32;
      str += 3;
      i += 2;
    }
  }
}

4,RTC时钟
      由于硬件没有外部高速时钟及外部低速时钟,本系统采用内部高速时钟。Stm32f072相比于常用的stm32f103系列内部时钟稳定一点,stm32f072的内部时钟为USB功能提供时钟.实现USB烧录功能.

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48;
  RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI48;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
  {
    Error_Handler();
  }
}

5,按键驱动
      配置按键引脚为输入上拉模式,创建一个20ms周期的线程,循环扫描,硬件有5个按键,即可实现功能切换,设置参数。按键驱动采用开源按键库,实现短按,双击,长按及按键消抖等功能,极大的提高按键功能及扩展性.下面是按键驱动程序:

#ifndef BUTTON_H
#define BUTTON_H


#include "stm32f0xx.h"
#define AssertCalled(char,int) 	printf("\nError:%s,%d\r\n",char,int)
#define ASSERT(x)   if((x)==0)  AssertCalled(__FILE__,__LINE__)
#define BTN_NAME_MAX  32     //名字最大为32字节

/* 按键消抖时间40ms, 建议调用周期为20ms
 只有连续检测到40ms状态不变才认为有效,包括弹起和按下两种事件
*/

#define CONTINUOS_TRIGGER             0  //是否支持连续触发,连发的话就不要检测单双击与长按了

/* 是否支持单击&双击同时存在触发,如果选择开启宏定义的话,单双击都回调,只不过单击会延迟响应,
   因为必须判断单击之后是否触发了双击否则,延迟时间是双击间隔时间 BUTTON_DOUBLE_TIME。
   而如果不开启这个宏定义,建议工程中只存在单击/双击中的一个,否则,在双击响应的时候会触发一次单击,
   因为双击必须是有一次按下并且释放之后才产生的 */
#define SINGLE_AND_DOUBLE_TRIGGER     0

/* 是否支持长按释放才触发,如果打开这个宏定义,那么长按释放之后才触发单次长按,
   否则在长按指定时间就一直触发长按,触发周期由 BUTTON_LONG_CYCLE 决定 */
#define LONG_FREE_TRIGGER             0 

#define BUTTON_DEBOUNCE_TIME 	  2   //消抖时间      (n-1)*调用周期
#define BUTTON_CONTINUOS_CYCLE  1	  //连按触发周期时间  (n-1)*调用周期
#define BUTTON_LONG_CYCLE       1	  //长按触发周期时间  (n-1)*调用周期
#define BUTTON_DOUBLE_TIME      15 	//双击间隔时间  (n-1)*调用周期  建议在200-600ms
#define BUTTON_LONG_TIME 	      50		/* 持续n秒((n-1)*调用周期 ms),认为长按事件 */

#define TRIGGER_CB(event)   \
        if(btn->CallBack_Function[event]) \
          btn->CallBack_Function[event]((Button_t*)btn)

typedef void (*Button_CallBack)(void*);   /* 按键触发回调函数,需要用户实现 */

typedef enum {
  BUTTON_DOWM = 0,
  BUTTON_UP,
  BUTTON_DOUBLE,
  BUTTON_LONG,
  BUTTON_LONG_FREE,
  BUTTON_CONTINUOS,
  BUTTON_CONTINUOS_FREE,
  BUTTON_ALL_RIGGER,
  number_of_event, /* 触发回调的事件 */
  NONE_TRIGGER
}Button_Event;

/*
	每个按键对应1个全局的结构体变量。
	其成员变量是实现滤波和多种按键状态所必须的
*/
typedef struct button
{
	/* 下面是一个函数指针,指向判断按键手否按下的函数 */
	uint8_t (*Read_Button_Level)(void); /* 读取按键电平函数,需要用户实现 */
  
  char Name[BTN_NAME_MAX];
  	
  uint8_t Button_State              :   4;	  /* 按键当前状态(按下还是弹起) */
  uint8_t Button_Last_State         :   4;	  /* 上一次的按键状态,用于判断双击 */
  uint8_t Button_Trigger_Level      :   2;    /* 按键触发电平 */
  uint8_t Button_Last_Level         :   2;    /* 按键当前电平 */
  
  uint8_t Button_Trigger_Event;     /* 按键触发事件,单击,双击,长按等 */
  
  Button_CallBack CallBack_Function[number_of_event];
  
	uint8_t Button_Cycle;	           /* 连续按键周期 */
  
  uint8_t Timer_Count;			/* 计时 */
	uint8_t Debounce_Time;		/* 消抖时间 */
  
	uint8_t Long_Time;		  /* 按键按下持续时间 */
  
  struct button *Next;
  
}Button_t;

/* 供外部调用的函数声明 */

void Button_Create(const char *name,
                  Button_t *btn, 
                  uint8_t(*read_btn_level)(void),
                  uint8_t btn_trigger_level);
                  
void Button_Attach(Button_t *btn,Button_Event btn_event,Button_CallBack btn_callback);   
                  
void Button_Cycle_Process(Button_t *btn);   
                  
void Button_Process(void);
                  
void Button_Delete(Button_t *btn);   
  
void Search_Button(void);     
                  
void Get_Button_EventInfo(Button_t *btn);
uint8_t Get_Button_Event(Button_t *btn);
uint8_t Get_Button_State(Button_t *btn);
void Button_Process_CallBack(void *btn);
                  
#endif
#include "button.h"
#include "stdio.h"
#include "string.h"
/*******************************************************************
 *                          变量声明
 *******************************************************************/

static struct button* Head_Button = NULL;


/*******************************************************************
 *                         函数声明
 *******************************************************************/
static char *StrnCopy(char *dst, const char *src, uint32_t n);
static void Print_Btn_Info(Button_t* btn);
static void Add_Button(Button_t* btn);


/************************************************************
  * @brief   按键创建
	* @param   name : 按键名称
	* @param   btn : 按键结构体
  * @param   read_btn_level : 按键电平读取函数,需要用户自己实现返回uint8_t类型的电平
  * @param   btn_trigger_level : 按键触发电平
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Button_Create(const char *name,
                  Button_t *btn, 
                  uint8_t(*read_btn_level)(void),
                  uint8_t btn_trigger_level)
{
  if( btn == NULL)
  {


  }
  
  memset(btn, 0, sizeof(struct button));  //清除结构体信息,建议用户在之前清除
 
  StrnCopy(btn->Name, name, BTN_NAME_MAX); /* 创建按键名称 */
  
  
  btn->Button_State = NONE_TRIGGER;           //按键状态
  btn->Button_Last_State = NONE_TRIGGER;      //按键上一次状态
  btn->Button_Trigger_Event = NONE_TRIGGER;   //按键触发事件
  btn->Read_Button_Level = read_btn_level;    //按键读电平函数
  btn->Button_Trigger_Level = btn_trigger_level;  //按键触发电平
  btn->Button_Last_Level = btn->Read_Button_Level(); //按键当前电平
  btn->Debounce_Time = 0;
  
  

  Add_Button(btn);          //创建的时候添加到单链表中
  
  Print_Btn_Info(btn);     //打印信息
 
}

/************************************************************
  * @brief   按键触发事件与回调函数映射链接起来
	* @param   btn : 按键结构体
	* @param   btn_event : 按键触发事件
  * @param   btn_callback : 按键触发之后的回调处理函数。需要用户实现
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  ***********************************************************/
void Button_Attach(Button_t *btn,Button_Event btn_event,Button_CallBack btn_callback)
{
  if( btn == NULL)
  {

  }
  
  if(BUTTON_ALL_RIGGER == btn_event)
  {
    for(uint8_t i = 0 ; i < number_of_event-1 ; i++)
      btn->CallBack_Function[i] = btn_callback; 			//按键事件触发的回调函数,用于处理按键事件
  }
  else
  {
    btn->CallBack_Function[btn_event] = btn_callback; //按键事件触发的回调函数,用于处理按键事件
  }
}

/************************************************************
  * @brief   删除一个已经创建的按键
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Button_Delete(Button_t *btn)
{
  struct button** curr;
  for(curr = &Head_Button; *curr;) 
  {
    struct button* entry = *curr;
    if (entry == btn) 
    {
      *curr = entry->Next;
    } 
    else
    {
      curr = &entry->Next;
    }
  }
}

/************************************************************
  * @brief   获取按键触发的事件
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  ***********************************************************/
void Get_Button_EventInfo(Button_t *btn)
{
  //按键事件触发的回调函数,用于处理按键事件
  for(uint8_t i = 0 ; i < number_of_event-1 ; i++)
  {
    if(btn->CallBack_Function[i] != 0)
    {

    }      
  } 
}

uint8_t Get_Button_Event(Button_t *btn)
{
  return (uint8_t)(btn->Button_Trigger_Event);
}

/************************************************************
  * @brief   获取按键触发的事件
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  ***********************************************************/
uint8_t Get_Button_State(Button_t *btn)
{
  return (uint8_t)(btn->Button_State);
}

/************************************************************
  * @brief   按键周期处理函数
  * @param   btn:处理的按键
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    必须以一定周期调用此函数,建议周期为20~50ms
  ***********************************************************/
void Button_Cycle_Process(Button_t *btn)
{
  uint8_t current_level = (uint8_t)btn->Read_Button_Level();//获取当前按键电平
  
  if((current_level != btn->Button_Last_Level)&&(++(btn->Debounce_Time) >= BUTTON_DEBOUNCE_TIME)) //按键电平发生变化,消抖
  {
      btn->Button_Last_Level = current_level; //更新当前按键电平
      btn->Debounce_Time = 0;                 //确定了是按下
      
      //如果按键是没被按下的,改变按键状态为按下(首次按下/双击按下)
      if((btn->Button_State == NONE_TRIGGER)||(btn->Button_State == BUTTON_DOUBLE))
      {
        btn->Button_State = BUTTON_DOWM;
      }
      //释放按键
      else if(btn->Button_State == BUTTON_DOWM)
      {
        btn->Button_State = BUTTON_UP;
        TRIGGER_CB(BUTTON_UP);    // 触发释放

      }
  }
  
  switch(btn->Button_State)
  {
    case BUTTON_DOWM :            // 按下状态
    {
      if(btn->Button_Last_Level == btn->Button_Trigger_Level) //按键按下
      {
        #if CONTINUOS_TRIGGER     //支持连续触发

        if(++(btn->Button_Cycle) >= BUTTON_CONTINUOS_CYCLE)
        {
          btn->Button_Cycle = 0;
          btn->Button_Trigger_Event = BUTTON_CONTINUOS; 
          TRIGGER_CB(BUTTON_CONTINUOS);    //连按

        }
        
        #else
        
        btn->Button_Trigger_Event = BUTTON_DOWM;
      
        if(++(btn->Long_Time) >= BUTTON_LONG_TIME)  //释放按键前更新触发事件为长按
        {
          #if LONG_FREE_TRIGGER
          
          btn->Button_Trigger_Event = BUTTON_LONG; 
          
          #else
          
          if(++(btn->Button_Cycle) >= BUTTON_LONG_CYCLE)    //连续触发长按的周期
          {
            btn->Button_Cycle = 0;
            btn->Button_Trigger_Event = BUTTON_LONG; 
            TRIGGER_CB(BUTTON_LONG);    //长按
          }
          #endif
          
          if(btn->Long_Time == 0xFF)  //更新时间溢出
          {
            btn->Long_Time = BUTTON_LONG_TIME;
          }

        }
          
        #endif
      }

      break;
    } 
    
    case BUTTON_UP :        // 弹起状态
    {
      if(btn->Button_Trigger_Event == BUTTON_DOWM)  //触发单击
      {
        if((btn->Timer_Count <= BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State == BUTTON_DOUBLE)) // 双击
        {
          btn->Button_Trigger_Event = BUTTON_DOUBLE;
          TRIGGER_CB(BUTTON_DOUBLE);    

          btn->Button_State = NONE_TRIGGER;
          btn->Button_Last_State = NONE_TRIGGER;
        }
        else
        {
            btn->Timer_Count=0;
            btn->Long_Time = 0;   //检测长按失败,清0
          
          #if (SINGLE_AND_DOUBLE_TRIGGER == 0)
            TRIGGER_CB(BUTTON_DOWM);    //单击
          #endif
            btn->Button_State = BUTTON_DOUBLE;
            btn->Button_Last_State = BUTTON_DOUBLE;
          
        }
      }
      
      else if(btn->Button_Trigger_Event == BUTTON_LONG)
      {
        #if LONG_FREE_TRIGGER
          TRIGGER_CB(BUTTON_LONG);    //长按
        #else
          TRIGGER_CB(BUTTON_LONG_FREE);    //长按释放
        #endif
        btn->Long_Time = 0;
        btn->Button_State = NONE_TRIGGER;
        btn->Button_Last_State = BUTTON_LONG;
      } 
      
      #if CONTINUOS_TRIGGER
        else if(btn->Button_Trigger_Event == BUTTON_CONTINUOS)  //连按
        {
          btn->Long_Time = 0;
          TRIGGER_CB(BUTTON_CONTINUOS_FREE);    //连发释放
          btn->Button_State = NONE_TRIGGER;
          btn->Button_Last_State = BUTTON_CONTINUOS;
        } 
      #endif
      
      break;
    }
    
    case BUTTON_DOUBLE :
    {
      btn->Timer_Count++;     //时间记录
      if(btn->Timer_Count>=BUTTON_DOUBLE_TIME)
      {
        btn->Button_State = NONE_TRIGGER;
        btn->Button_Last_State = NONE_TRIGGER;
      }
      #if SINGLE_AND_DOUBLE_TRIGGER
      
        if((btn->Timer_Count>=BUTTON_DOUBLE_TIME)&&(btn->Button_Last_State != BUTTON_DOWM))
        {
          btn->Timer_Count=0;
           TRIGGER_CB(BUTTON_DOWM);    //单击
          btn->Button_State = NONE_TRIGGER;
          btn->Button_Last_State = BUTTON_DOWM;
        }
        
      #endif

      break;
    }

    default :
      break;
  }
  
}

/************************************************************
  * @brief   遍历的方式扫描按键,不会丢失每个按键
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    此函数要周期调用,建议20-50ms调用一次
  ***********************************************************/
void Button_Process(void)
{
  struct button* pass_btn;
  for(pass_btn = Head_Button; pass_btn != NULL; pass_btn = pass_btn->Next)
  {
      Button_Cycle_Process(pass_btn);
  }
}

/************************************************************
  * @brief   遍历按键
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    NULL
  ***********************************************************/
void Search_Button(void)
{
  struct button* pass_btn;
  for(pass_btn = Head_Button; pass_btn != NULL; pass_btn = pass_btn->Next)
  {
//    PRINT_INFO("button node have %s",pass_btn->Name);
  }
}

/************************************************************
  * @brief   处理所有按键回调函数
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    暂不实现
  ***********************************************************/
void Button_Process_CallBack(void *btn)
{
  uint8_t btn_event = Get_Button_Event(btn);

  switch(btn_event)
  {
    case BUTTON_DOWM:
    {
//      PRINT_INFO("添加你的按下触发的处理逻辑");
      break;
    }
    
    case BUTTON_UP:
    {
//      PRINT_INFO("添加你的释放触发的处理逻辑");
      break;
    }
    
    case BUTTON_DOUBLE:
    {
//      PRINT_INFO("添加你的双击触发的处理逻辑");
      break;
    }
    
    case BUTTON_LONG:
    {
//      PRINT_INFO("添加你的长按触发的处理逻辑");
      break;
    }
    
    case BUTTON_LONG_FREE:
    {
//      PRINT_INFO("添加你的长按释放触发的处理逻辑");
      break;
    }
    
    case BUTTON_CONTINUOS:
    {
//      PRINT_INFO("添加你的连续触发的处理逻辑");
      break;
    }
    
    case BUTTON_CONTINUOS_FREE:
    {
//      PRINT_INFO("添加你的连续触发释放的处理逻辑");
      break;
    }
      
  } 
}


/**************************** 以下是内部调用函数 ********************/

/************************************************************
  * @brief   拷贝指定长度字符串
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    NULL
  ***********************************************************/
static char *StrnCopy(char *dst, const char *src, uint32_t n)
{
  if (n != 0)
  {
    char *d = dst;
    const char *s = src;
    do
    {
        if ((*d++ = *s++) == 0)
        {
            while (--n != 0)
                *d++ = 0;
            break;
        }
    } while (--n != 0);
  }
  return (dst);
}

/************************************************************
  * @brief   打印按键相关信息
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    NULL
  ***********************************************************/
static void Print_Btn_Info(Button_t* btn)
{
  
//  PRINT_INFO("button struct information:\n\
//              btn->Name:%s \n\
//              btn->Button_State:%d \n\
//              btn->Button_Trigger_Event:%d \n\
//              btn->Button_Trigger_Level:%d \n\
//              btn->Button_Last_Level:%d \n\
//              ",
//              btn->Name,
//              btn->Button_State,
//              btn->Button_Trigger_Event,
//              btn->Button_Trigger_Level,
//              btn->Button_Last_Level);
  Search_Button();
}
/************************************************************
  * @brief   使用单链表将按键连接起来
	* @param   NULL
  * @return  NULL
  * @author  jiejie
  * @github  https://github.com/jiejieTop
  * @date    2018-xx-xx
  * @version v1.0
  * @note    NULL
  ***********************************************************/
static void Add_Button(Button_t* btn)
{
  btn->Next = Head_Button;
  Head_Button = btn;
}



6,波形发生器驱动
       波形发生器通道采用单片机dac功能产生0-3.3V电压,stm32f072 dac分辨率为12位,即数字量0-4095,通过定时器触发dma通道,将数据设置为dac输出,下面是stm32cubeide配置dac参数:

Flc_UDPmnoUvzjyh7bHcCZx1qCSBFt6yrZY_pskjjPHFlgZDr0i6ZXv_

 7,pwm驱动
      pwm输出通过定时器pwm输出通道,实现pwm输出,通过修改pwm分频系数实现pwm频率修改.下面是stm32cubeide配置pwm参数:

FuyiXkSnyg9OPkcIuRYAt0S0yDzs

8,硬件仿真分析
     运算放大器电路分析,简化后如下图:

FteMsKLt6DA5Lb-JmPx-ceJiEK4c

图三中,由虚短知: V- = V+ = 0 ……a

由虚断及基尔霍夫定律知,通过R2与R1的电流之和等于通过R3的电流,故 (V1 – V-)/R1 + (V2 – V-)/R2 = (Vout – V-)/R3 ……b

代入a式,b式变为V1/R1 + V2/R2 = Vout/R3 如果取R1=R2=R3,则上式变为Vout=V1+V2,这就是传说中的加法器了。

通过计算增益gain=4.3K/(750+750+270)

 仿真环境采用电子森林网站提供的电路仿真,仿真电路如下图:

FgZo9EZqUjwCYIQX6tPYEdUIVGIz

从图中可以看出,当输入为0-3.3V时,输出波形有失真,和实际测试一致,下图是实际测试波形,进而继续测试失真电压为多少,下图可以看出3.2V已经存在失真;

FneOxsiJuflSLP_L1z-vV6EwQZbhFsmSQ4oTEKSM3UIrJzaH_6KlbNjhFvVvY752q3SGtty_TRLxsm2h64w1

 

通过失真电压点-3.7V反推输入失真值,可以计算出输入失真点电压

计算公式为;(x-1.5)*gain=y

计算得出y=3.02V

通过理论基础计算,为保证输出波形不失真,单片机dac输出波形不得高于3V.

注:x标识输入电压,y表示输出电压

9总结报告
本次项目学到很多东西,遇到很多问题:

  1. 前期单片机输出dac电压0-3.3V时,没考虑到输出波形失真,通过理论计算和实际电压测量有误差,一致怀疑程序问题,后来通过电子森林提供的硬件仿真电路看出波形失真问题,纸上得来终觉浅,绝知此事要躬行这次真的给我深深的上了一课。
  2. 本次硬件不带有仿真接口,为调试程序带来了极大的不方便,特别是调试程序遇到问题的时候,无法硬件仿真,。
  3. 本想采用lvglgui界面,在移植官方测试例程时候,由于单片机flash太小,无法运行,只好自己写个简单的gui界面。
  4. 宝剑锋从磨砺出,梅花香自苦寒来,不经历风雨怎见彩虹,不抛弃,不放弃,坚持克服困难,才能迈出铿锵步伐,感谢主办方给我一次锻炼的机会,让我学会成长。

 

附件下载
F072_TEST.7z
项目代码
团队介绍
本人为打工人,从事嵌入式行业5年之久,做过的项目有工业灯联网,实现码头,港口及公共场合智能照明,做过电动车防盗项目,实现电动车的定位,主要负责软件研发及硬件设计。
团队成员
杨满意
本人为打工人,从事嵌入式行业5年之久,做过的项目有工业灯联网,实现码头,港口及公共场合智能照明,做过电动车防盗项目,实现电动车的定位,主要负责软件研发及硬件设计。
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号