基于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参数:
7,pwm驱动
pwm输出通过定时器pwm输出通道,实现pwm输出,通过修改pwm分频系数实现pwm频率修改.下面是stm32cubeide配置pwm参数:
8,硬件仿真分析
运算放大器电路分析,简化后如下图:
图三中,由虚短知: 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)
仿真环境采用电子森林网站提供的电路仿真,仿真电路如下图:
从图中可以看出,当输入为0-3.3V时,输出波形有失真,和实际测试一致,下图是实际测试波形,进而继续测试失真电压为多少,下图可以看出3.2V已经存在失真;
通过失真电压点-3.7V反推输入失真值,可以计算出输入失真点电压
计算公式为;(x-1.5)*gain=y
计算得出y=3.02V
通过理论基础计算,为保证输出波形不失真,单片机dac输出波形不得高于3V.
注:x标识输入电压,y表示输出电压
9总结报告
本次项目学到很多东西,遇到很多问题:
- 前期单片机输出dac电压0-3.3V时,没考虑到输出波形失真,通过理论计算和实际电压测量有误差,一致怀疑程序问题,后来通过电子森林提供的硬件仿真电路看出波形失真问题,纸上得来终觉浅,绝知此事要躬行这次真的给我深深的上了一课。
- 本次硬件不带有仿真接口,为调试程序带来了极大的不方便,特别是调试程序遇到问题的时候,无法硬件仿真,。
- 本想采用lvglgui界面,在移植官方测试例程时候,由于单片机flash太小,无法运行,只好自己写个简单的gui界面。
- 宝剑锋从磨砺出,梅花香自苦寒来,不经历风雨怎见彩虹,不抛弃,不放弃,坚持克服困难,才能迈出铿锵步伐,感谢主办方给我一次锻炼的机会,让我学会成长。