蓝桥杯嵌入式各模块资源整理(两星期突击蓝桥杯)

目录

一、cubemx时钟树配置

二、LED

三、按键

四、LCD

五、串口

六、定时器


一、cubemx时钟树配置

(1)

(2)

时钟树的配置方法不单图中所示,可以通过调节第4、第5步的地方保证SYSCLK(MHz)处的频率为80,符合蓝桥杯题目要求即可,如果题目要求其他频率也可通过这些地方调整达到题目要求。 

二、LED

将PC8~PC15、PD2在cubemx上设置为GPIO_Output模式

(1)一个函数控制所有灯

void Led_Disp(uint8_t ucled)
{
  HAL_GPIO_WritePin(GPIOC,0xFF<<8,GPIO_PIN_SET);//指定所有灯灭
  HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
  HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
  
  HAL_GPIO_WritePin(GPIOC,ucled<<8,GPIO_PIN_RESET);
  HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
  HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}

该函数的参数是一个字节,0x00 = 0000 0000,每个位代表一个LED灯,从左往右分别代表LED1-LED8

LED1 : 0x01

LED2 : 0x02

LED3 : 0x04

LED4 : 0x08

LED5 : 0x10

LED6 : 0x20

LED7 : 0x40

LED8 : 0x80

流水灯演示:

uint8_t leds[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
while (1)
{
	uint i;
	for(i=0;i<8;i++)
	{
		Led_Disp(leds[i]);
		HAL_Delay(100);
	}
}

(2)一个函数只控制一个灯

uint8_t led_sta;
void Led_Pro(uint8_t num,uint8_t sta)//单独开关灯函数
{
	uint8_t pos=0x01<<(num-1);
	led_sta=(led_sta&(~pos))|(pos*sta);
	Led_Disp(led_sta);
}

灯演示

Led_Pro(6,1);//打开led6
Led_Pro(6,1);//关闭led6

三、按键

将PB0、PB1、PB2、PA0在cubemx上配置为GPIO_Input模式

如果按键想用外部中断的话,需要注意PA0、PB0是同一个中断线,外部中断只能用三个按键,剩下的一个按键还是需要读写操作,所以最好还是都使用读写操作吧。

(1)按键初始化代码

uint8_t Key_Scan(void)
{
	uint8_t key_val=0;
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==GPIO_PIN_RESET)
	{
		key_val = 1;
	}
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==GPIO_PIN_RESET)
	{
		key_val = 2;
	}
	if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)==GPIO_PIN_RESET)
	{
		key_val = 3;
	}
	if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_RESET)
	{
		key_val = 4;
	}
	return key_val;
}

(2)按键长按、短按模板

注意:这个代码中不可以将if分支语句改为switch分支语句,两者理论上看似无差别,但实际操作中存在区别。

if分支在实际操作时,当长按800ms后自动切换操作。

switch分支实际操作时,需要按下超过800ms后松开按键才能切换操作,if则不需要

void Key_Proc(void)
{
	key_val = Key_Scan();
	key_down = key_val & (key_val^key_old);
	key_up =~ key_val & (key_val^key_old);
	key_old = key_val;
	
	if(key_down)
	{
		uskey = 0;
	}
	if(uskey<800)
	{
		if(key_up==1)//短按
		{
            //按下后需要做的事情
			ucled=0x01;
		}
		if(key_up==2)//短按
		{
			
		}
		if(key_up==3)//短按
		{
			
		}
		if(key_up==4)//短按
		{
			
		}
		
	}
	else
	{
		if(key_val==1)//长按
		{
			ucled=0x02;
		}
		if(key_val==2)//长按
		{
			
		}
		if(key_val==3)//长按
		{
			
		}
		if(key_val==4)//长按
		{
			
		}
	}
	
}

注意:定义:uint16_t uskey;

        同时uskey需要放到stm32g4xx_it.c文件的void SysTick_Handler(void)中,如下图。

(3)按键单、双击模板

逻辑:

当按键抬起时,开始计时。

双击:当在规定时间内再次按下该按键。

单击:超时没有按键按下。

void Key_Proc(void)
{
	key_val = Key_Scan();
	key_down = key_val & (key_val^key_old);
	key_up =~ key_val & (key_val^key_old);
	key_old = key_val;
	
	if(key_up)
	{
		key_temp=key_up;
		if(key_flag==0)
		{
		uskey=0;
		key_flag=1;
		}
		else
		{
			key_flag = 0;//双击结束后的下一次可以开始计时
		}
	}
	if(key_flag==1)
	{
		if(uskey<300)
		{
			if(key_down==1&&key_temp==1)//双击
			{
				ucled=0x02;
			}
			if(key_down==2&&key_temp==2)//双击
			{
				
			}
			if(key_down==3&&key_temp==3)//双击
			{
				
			}
			if(key_down==4&&key_temp==4)//双击
			{
				
			}
			
		}
		else
		{
			if(key_temp==1)//单击
			{
				ucled=0x01;
			}
			if(key_temp==2)//单击
			{
			
			}
			if(key_temp==3)//单击
			{
				
			}
			if(key_temp==4)//单击
			{
				
			}
			key_flag=0;
		}
	}
	
	
}

四、LCD

在官方提供的资源包中找到fonts.h 、lcd.h、lcd.c文件加到自己的工程文件中即可。

注意:如果是使用操作系统的朋友需要将lcd.c文件中的所有HAL_Delay函数替换成操作系统对应的延时函数 

(1)LCD与LED引脚冲突

在lcd.c中找到

LCD_WriteReg();

LCDWriteRAM_Prepare();

LCD_WriteRAM();

三个函数的首尾添加这两句,也就是先把GPIOC- >ODR的数据先存起来等LCD操作完后再把存起来的数据放回ODR寄存器。

u16 pcout = GPIOC->ODR;
/*
三个函数中的原代码最上和最下面加上这两段代码
*/
GPIOC->ODR = pcout;

五、ADC采样

double getADC(ADC_HandleTypeDef *pin)
{
  unsigned int adc;
  HAL_ADC_Start(pin);//HAL库内置函数,开启ADC
  adc = HAL_ADC_GetValue(pin);//HAL库内置函数,得到ADC的值
  return adc*3.3/4096;//测量的值均分4096份,看在3.3(电源电压占的比例)因为是12位精度所以除以4096
}

下面这行放主函数中


adc_value2 = getADC(&hadc2);
sprintf(text,"ADC2:%.2f",adc_value2);
LCD_DisplayStringLine(Line4,(uint8_t*)text);

六、串口

注意:串口的cubemx配置一定要严格按照上面的顺序操作,不然容易USART_RX和USART_TX会自动配置载PC4和PC5,忘记收到改回来。最后造成串口不发送也不接收。

根据题目要求注意在parameter Settings中改波特率,这里就不展示了

案例:每次上电后发送Hello World!

 sprintf(tx_buf,"hello world!\r\n");
  HAL_UART_Transmit(&huart1,(uint8_t*)tx_buf,strlen(tx_buf),50);

(1)接收单个字符

案例:接收到0时,点亮LED1并发送LED1 Open!,接收到C时,熄灭LED1并发送LED1 Close!

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1)
	{
	if(rx_data=='X')
	{
		LCD_Clear(Black);
		LCD_DisplayStringLine(Line4,"      UART TEST");
		sprintf(temp,"X\r\n");
		HAL_UART_Transmit(&huart1,(uint8_t*)temp,strlen(temp),50);
	}
	else
	{
		LCD_Clear(Black);
		LCD_DisplayStringLine(Line4,"       ERROR");
		sprintf(temp,"other\r\n");
		HAL_UART_Transmit(&huart1,(uint8_t*)temp,strlen(temp),50);
	}
	}
	
	HAL_UART_Receive_IT(&huart1,&rx_data,1);
}

注意:需要将HAL_UART_Receive_IT(&huart1,&rx_data,1)放到主函数中才能触发中断

        HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)在stm32g4xx_hal_uart.h里面,在1632行的位置(如果记不住,就记1600多行的位置)

(2)接收长字符串

void Uart_Proc()
{
	if(rx_count==4)
	{
		if(strcmp(rx_buf,"LED0")==0)
		{
			ucled = 0x01;
			sprintf(tx_buf,"LED1 Open!\r\n");
			HAL_UART_Transmit(&huart1,(uint8_t*)tx_buf,strlen(tx_buf),50);
		}
		if(strcmp(rx_buf,"LEDC")==0)
		{
			ucled = 0x00;
			sprintf(tx_buf,"LED1 Close!\r\n");
			HAL_UART_Transmit(&huart1,(uint8_t*)tx_buf,strlen(tx_buf),50);
		}
	}
	
	rx_count = 0;
	memset(rx_buf,0,20);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	rx_buf[rx_count++] = rx_data;
	HAL_UART_Receive_IT(&huart1,&rx_data,1);
}

注意:串口显示发送的数据时,可以在HAL_UART_Transmit函数之前延时10-20ms

例如:

if(rx_count != 0)
	{
		HAL_Delay(20);
		
		HAL_UART_Transmit(&huart1,(u8*)rx_buf,strlen(rx_buf),100);
		if(rx_buf[0]==password[0]&&rx_buf[1]==password[1]&&rx_buf[2]==password[2]&&rx_buf[3]=='-')
		{
			password[0]=rx_buf[4];
			password[1]=rx_buf[5];
			password[2]=rx_buf[6];
		}
	}

	rx_count = 0;
	memset(rx_buf,0,20);

补充:解决接收不完整,接收错误指令问题

补充:调用Uart_Proc()时,可以增加延时,解决接收不完整问题。

if(rx_count != 0)
{
  uint8_t temp = rx_count;
  HAL_Delay(1);//1ms后,若任在进入中断,xx_count改变,说明接收未完成
  if(temp==rx_count)  Uart_Pro();
}

七、定时器

(1)定时器定时

向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后回到0重新开始向上计数

向下计数模式:计数器从自动加载值(TIMx_ARR)开始向下计数到0,再回到自动加载值重新开始向下计数。

中央对齐模式:计数器从0开始计数到自动载入的值-1,产生一个计数器溢出事件,然后向下计数 到1产生一个计数器溢出事件,然后从0开始重新计数。

定时器周期计算公式:T = (arr + 1)*(PSC+1)/Tck其中Tck为时钟频率,PSC为时钟预分频系数,arr为自动重载值,例如如果Tck=80MHz,PSC=80-1,arr=1000-1,则T=1ms,即中断开启的话1ms进入一次中断。

主函数调用此函数打开定时器

HAL_TIM_Base_Start_IT(&htim6);

将定时的代码写在stm32g4xxit.c中的TIM6DAC_IRQHandler(void)

第二种是使用定时器的中断回调函数进行定时

(2)脉冲信号输出

下面这串代码放在主函数中用于开启pwm输出

HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);//pwm输出开启

改变重装载值

__HAL_TIM_SetAutoreload(&htim3,1000000/pa7-1);//改变Counter Period (AutoReload Register)的值来改变频率
TIM3->CNT = 0;//每次改变都要置0一次

例:系统为80MHz,pa7=1000,配置的预分频值为80-1,那么重装载值就为1000000/1000-1=1000-1。频率f=80000000/1000/80=1000。

改变占空比

__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_2,(1000000/pa7+1)*pwm/100);//第三个参数除以Counter Period (AutoReload Register)的值就是占空比

__HAL_TIM_SetCompare的第三个参数除以Counter Period的值就是占空比。

(3)定时器输入捕获

主函数中开启输入捕获

 HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
 HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);

中断函数调用

//PWM捕获中断的回调函数
double cap1,cap2,R39_frq,R40_frq;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	
	if(htim->Instance==TIM2)
	{
		cap1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
		TIM2->CNT=0;
		R40_frq = 80000000/80/cap1;
		HAL_TIM_IC_Start_IT(&htim2,TIM_CHANNEL_1);
	}
	if(htim->Instance==TIM3)
	{
		cap2 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
		TIM3->CNT=0;
		R39_frq = 80000000/80/cap2;
		HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
	}
}

占空比测量

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance==TIM3)
	{
		if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
		{
			duty1 = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1);
			cap = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2);
			TIM3->CNT = 0;
			frq = 1000000/cap;
			P = (1.0*cap)/(1.0*duty1)*100;
			HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_2);
			HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);
		}
		
	}
	
	 
}

八、eeprom读写

eeprom掉电保存数据需要用到I2C

注意:官方提供了i2c_hal.c文件,需要自己手动添加到自己的工程文件中

将以下代码封装在i2c_hal.c文件中

void eeprom_write(uint8_t addr,uint8_t dat)
{
    I2CStart();
    I2CSendByte(0xa0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();
    I2CSendByte(dat);
    I2CWaitAck();
    I2CStop();
    HAL_Delay(20);
}

uint8_t eeprom_read(uint8_t addr)
{
    I2CStart();
    I2CSendByte(0xa0);
    I2CWaitAck();
    I2CSendByte(addr);
    I2CWaitAck();
    I2CStop();

    I2CStart();
    I2CSendByte(0xa1);
    I2CWaitAck();
    uint8_t dat = I2CReceiveByte();
    I2CSendNotAck();
    I2CStop();
    
    return dat;
}

在主函数数中进行读操作

uint8_t dat=eeprom_read(0);//读0地址上的值
char temp[20];
sprintf(temp,"     %d    ",dat);
LCD_DisplayStringLine(Line0,(u8*)temp);

写操作

eeprom_write(0,10)//在片区0地址写入10

九、rtc实时时钟

注意:在开始写之前需要对rtc进行移植,在main.c中滑到最下找到void SystemClock_Config(void)

在这个函数中添加以下代码即可完成移植

RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_RTC|RCC_PERIPHCLK_ADC12;
  PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
  PeriphClkInit.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;

  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }

首先先在main.c中定义下面两个全局变量结构

RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef sDate = {0};

然后主函数中调用

    HAL_RTC_GetTime(&hrtc,&sTime,RTC_FORMAT_BIN);//获取时间
	HAL_RTC_GetDate(&hrtc,&sDate,RTC_FORMAT_BIN);//获取日期

注意:即使你不需要获取日期,也要将获取日期的函数添上,不然的话时间就不会流动

最后显示

    sprintf(temp,"      %2d:%2d:%2d  ",sTime.Hours,sTime.Minutes,sTime.Seconds);
	LCD_DisplayStringLine(Line4,(u8*)temp);
	sprintf(temp,"      %d-%d-%d-%d",sDate.Year,sDate.Month,sDate.Date,sDate.WeekDay);
	LCD_DisplayStringLine(Line5,(u8*)temp);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值