蓝桥杯嵌入式学习笔记

用博客来记录一下参加蓝桥杯嵌入式第十六届省赛的学习经历

工具

keil5,stm32cube

环境准备

根据赛事方提供的板子来选择cubemx上的器件

cubemx配置

外部高速时钟使能
设置串口
时钟配置

时钟配置为24MHZ因为外部晶振为24MHZ,其余的暂时不清楚原因

项目配置

配置为MDK

需要注意,固件地址需要设置为下载解压后的文件地址(提前从数据包中下载)

勾选生成.c.h文件

注意一下,下载好软件后,要解压固件包,然后在help中设置地址

keil配置

烧录方式

烧录设置

要是没有芯片包,需要从赛事数据包中解压,会自动生成

keil代码自动补全

点击edit,configraution选择跟编译相关的Text Completion,然后勾线第三个

注意

代码需要写入begin - end之间

代码规范

新建code文件夹

右键CMSIS,点击Manage Project,新建一个文件夹

点击魔术棒,c++,include path,添加文件夹路径

然后添加fun.c,fun.h,headfile.h 三个文件

头文件可能显示不出来,所以需要添加existing file 再勾选all file,即可添加

头文件配置

headfile.h

fun.h

fun.c

main.c

模块

led

通过观察开发板led电路图可以发现,led的的引脚控制由PC8~15低电平点亮 ,同时PD2锁存器控制开关,需要置高电平

cubemx配置

将PC8~15设置为输出,PD2设置为输出

在GPIO配置中,将引脚全部配置为高电平,使得上电时LED灭

keil代码

实现点亮一只灯

fun.c主要代码

c 复制代码
void led_show()
{
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
	
	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_RESET);
	
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);


}

fun.h代码

c 复制代码
void led_show(void);

main.c代码(需要在主函数内)

c 复制代码
led_show();
实现具体操作的灯,以及点亮还是熄灭

fun.c代码

c 复制代码
void led_show(uint8_t led, uint8_t mode)
{
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
	if(mode)
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (led - 1), GPIO_PIN_RESET); //GPIO_PIN_8的二进制代码为0000 0001 0000 0000 GPIO_PIN_9的二进制代码为0000 0010 0000 0000,所以用左移移位符
	else
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (led - 1), GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);


}

fun.h代码(需要加上那个头函数)

c 复制代码
#include "stm32g4xx.h"                  // Device header


void led_show(uint8_t led, uint8_t mode);

main.c代码(灯1, 4,8亮)

c 复制代码
		led_show(1, 1);
		led_show(4, 1);
		led_show(8, 1);

按键

原理图,如图,需要将PB0~2,PA0配置为上拉输入模式。按下为低电平,松开为高电平

cubemx配置

keil代码

fun.c代码

c 复制代码
void key_scan()
{
	B1_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
	B2_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
	B3_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
	B4_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
	if(B1_state == 0 && B1_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(1, 1);
	}
	if(B2_state == 0 && B2_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(1, 0);
	}
	if(B3_state == 0 && B3_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(2, 1);
	}
	if(B4_state == 0 && B4_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(2, 0);
	}
	B1_last_state	= B1_state;
	B2_last_state	= B2_state;
	B3_last_state	= B3_state;
	B4_last_state	= B4_state;


}

fun.h代码

c 复制代码
void key_scan(void);//里面没有这个void会有waring,但影响似乎不大

main.c代码

同样放入while循环中

LCD

首先在赛事方提供的数据包中找到液晶驱动参考程序,HAL库版本,找到lcd.c,font.h,lcd,h,复制到code文件夹,打开keil工程文件,添加这三个文件

初始准备

main.c函数

headfile.h函数

keil代码

实现了在第一行显示字符,按键按下时,第三行数字变化的功能

fun.c代码

c 复制代码
#include "headfile.h"

int count = 0;

void led_show(uint8_t led, uint8_t mode)
{
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
	if(mode)
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (led - 1), GPIO_PIN_RESET);
	else
		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (led - 1), GPIO_PIN_SET);
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);


}



uint8_t B1_state;
uint8_t B1_last_state;
uint8_t B2_state;
uint8_t B2_last_state;
uint8_t B3_state;
uint8_t B3_last_state;
uint8_t B4_state;
uint8_t B4_last_state;


void key_scan()
{
	B1_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
	B2_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
	B3_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
	B4_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
	if(B1_state == 0 && B1_last_state == 1)//°´¼üB1°´ÏÂ
	{
		count ++;
	}
	if(B2_state == 0 && B2_last_state == 1)//°´¼üB1°´ÏÂ
	{
		count --;
	}
	if(B3_state == 0 && B3_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(2, 1);
	}
	if(B4_state == 0 && B4_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(2, 0);
	}
	B1_last_state	= B1_state;
	B2_last_state	= B2_state;
	B3_last_state	= B3_state;
	B4_last_state	= B4_state;


}

char text[20]; //linemax = 20

void lcd_show()
{
	sprintf(text, "zlfsgdsg");//¿ÉÒÔµ÷Õû×Ö·û´®Î>>Ö㬵<<ÊÇ×î¶à20¸ö×Ö·û
	LCD_DisplayStringLine(Line0, (uint8_t *)text);
	sprintf(text, "     count: %d     ", count);
	LCD_DisplayStringLine(Line3, (uint8_t *)text);

}

fun.h代码

c 复制代码
#ifndef _fun_h
#define _fun_h

#include "stm32g4xx.h"                  // Device header


void led_show(uint8_t led, uint8_t mode);

void key_scan(void);

void lcd_show(void);

#endif

main.c代码

引脚冲突问题

lcd和led的引脚有冲突

解决办法:将PD2引脚提前置低电平,这样数据就不能传入led,led就不会点亮了,但是下次点亮led时依旧会出现这样的问题,所以需要将所有用到的LCD.C函数加上

c 复制代码
uint16_t temp = GPIOC->ODR;
GPIOC->ODR = temp;

解决方法如下

main.c (在初始化部分)

c 复制代码
	HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);

lcd.c (部分代码)

问题成功解决!

LED闪烁

两个重要参数:PSC,ARR

其中系统频率f为80MHZ,如果要让T=1,则可令ARR = 9999,PSC = 7999;即可确定定时周期为1

初始化

在headfile.h文件中添加tim.h文件头

main.c

cubemx配置

需要配置定时器,正常使用通用定时器TIM2即可,重点是修改PSC和ARR

生成代码后可发现多了tim.c函数,找到stm32g4xx_hal_tim.h文件里面的call_back回调函数

将其复制到fun.c中

keil代码

在fun.c中定义一个变量

c 复制代码
uint8_t led_mode;

按键长按

原理,用一个计数器cnt(取10000)计时1s,20000对应2s,5000对应0.5s来计时,达到识别按键长按的目的

cubemx配置

此时TIM3不使能!!

keil代码

fun.c代码

c 复制代码
uint8_t B1_state;
uint8_t B1_last_state;
uint8_t B2_state;
uint8_t B2_last_state;
uint8_t B3_state;
uint8_t B3_last_state;
uint8_t B4_state;
uint8_t B4_last_state;


void key_scan()
{
	B1_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
	B2_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
	B3_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_2);
	B4_state = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0);
	if(B1_state == 0 && B1_last_state == 1)//°´¼üB1°´ÏÂ
	{
		TIM3->CNT = 0;
	}
	else if(B1_state == 0 && B1_last_state == 0) //°´¼üB1Ò>>Ö±°´ÏÂ
	{
		if(TIM3->CNT >= 10000)//°´¼üB1³¤°´
		{
				count++;
		}
	}
	else if	(B1_state == 1 && B1_last_state == 0)//°´¼üB1ËÉ¿ª
	{
		if(TIM3->CNT < 10000)//°´¼üB1¶Ì°´
		{
			count += 2;
		}
	}
	if(B2_state == 0 && B2_last_state == 1)//°´¼üB1°´ÏÂ
	{
		count --;
	}
	if(B3_state == 0 && B3_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(2, 1);
	}
	if(B4_state == 0 && B4_last_state == 1)//°´¼üB1°´ÏÂ
	{
		led_show(2, 0);
	}
	B1_last_state	= B1_state;
	B2_last_state	= B2_state;
	B3_last_state	= B3_state;
	B4_last_state	= B4_state;


}

main.c代码

需要注意!给TIM3使能,但因为不是中断,是定时器,所以不用IT

LCD高亮显示

其实就是调用 LCD_SetBackColor(Color);这个函数,把背景颜色改成黄色

keil代码

定义变量

c 复制代码
uint8_t lcd_highshow;

按键b1按下时

c 复制代码
			lcd_highshow ++;
			lcd_highshow %= 3;	

逻辑流程

应该蛮简单的,就不写注释了

c 复制代码
void lcd_show()
{
	sprintf(text, "zlfsgdsg");//¿ÉÒÔµ÷Õû×Ö·û´®Î>>Ö㬵<<ÊÇ×î¶à20¸ö×Ö·û
	LCD_DisplayStringLine(Line0, (uint8_t *)text);
	if(lcd_highshow == 0)
	{
	LCD_SetBackColor(Yellow);
	sprintf(text, "     count: %d     ", count);
	LCD_DisplayStringLine(Line3, (uint8_t *)text);
	LCD_SetBackColor(Black);
	sprintf(text, "     txysgdmn     ");
	LCD_DisplayStringLine(Line4, (uint8_t *)text);
	
	sprintf(text, "     baozi     ");
	LCD_DisplayStringLine(Line5, (uint8_t *)text); 
	}
	else if(lcd_highshow == 1)
	{

	sprintf(text, "     count: %d     ", count);
	LCD_DisplayStringLine(Line3, (uint8_t *)text);
	LCD_SetBackColor(Yellow);
	sprintf(text, "     txysgdmn     ");
	LCD_DisplayStringLine(Line4, (uint8_t *)text);
	LCD_SetBackColor(Black);
	sprintf(text, "     baozi     ");
	LCD_DisplayStringLine(Line5, (uint8_t *)text); 
	
	}

PWM输出

一般要求:让PA1输出频率为1000HZ,占空比为50%的方波

计算公式

cubemx配置

keil代码

在生成代码后,发现一个问题:文件中没有头文件!

后面找到解决办法, 编译之后,就能知道每个文件依赖于哪些头文件!还是第一次遇到这种问题
先在main.c函数中将定时器使能

这个函数在头文件里找--stm32g4xx_hal_tim.h

c 复制代码
	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);

设置占空比

c 复制代码
	TIM2->CCR2 = 50;

原理图(抄袭版),不懂的话看嵌入式的书去

输入捕获测量引脚输出PWM波周期

原理图

要求:用PA7来测量PA1生成PWM波的频率

需要用杜邦线连接PA7和PA1.

cubemx配置

这里PA7用TIM17或者TIM2都行,我就根据视频来了

使能定时器

keil代码

为了省事,将code文件复制到这个工程目录下,记得在main.c文件中添加头文件定义

main.c函数

使能中断17

c 复制代码
	HAL_TIM_IC_Start_IT(&htim17, TIM_CHANNEL_1);

fun.c代码

参考着原理图一起理解

c 复制代码
#include "headfile.h"

char	text[20]; 

uint32_t fre, capture_value;

void lcd_show()
{
	sprintf(text, "zlfsgdsg");//¿ÉÒÔµ÷Õû×Ö·û´®Î>>Ö㬵<<ÊÇ×î¶à20¸ö×Ö·û
	LCD_DisplayStringLine(Line0, (uint8_t *)text);
	sprintf(text, "   fre:%d   ", fre);
	LCD_DisplayStringLine(Line3, (uint8_t *)text);
	}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM17)
	{
		capture_value = TIM17->CCR1;
		TIM17->CNT = 0;
		fre = 80000000/(80*capture_value);
	
	}


}

main.c代码

注意不要忘记了给LCD初始化

c 复制代码
  /* USER CODE BEGIN 2 */
	LCD_Init();
	LCD_Clear(Black);
	LCD_SetBackColor(Black);
	LCD_SetTextColor(White);
	
	HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
	TIM2->CCR2 = 50;
	
	HAL_TIM_IC_Start_IT(&htim17, TIM_CHANNEL_1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		lcd_show();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

输入捕获测量555定时器频率

实现效果:能测量出两个信号发生器输出的PWM波的频率,两个频率可以通过R39,和R40调节

原理图

cubemx配置

将PA15设置为TIM2_CH1,PB4设置为TIM16_CH1;

分别将两个定时器设置为输入捕获模式,PCC设置为80-1,并且使能

keil代码

main.c

需要注意,不要忘记了LCD初始化,不要忘记了中断使能,不要忘记了把lcd_show函数放到循环里

c 复制代码
  /* USER CODE BEGIN 2 */
	LCD_Init();
	LCD_Clear(Black);
	LCD_SetBackColor(Black);
	LCD_SetTextColor(White);
	
	
	
	
	HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
	HAL_TIM_IC_Start_IT(&htim16, TIM_CHANNEL_1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		lcd_show();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

&&& 不要忘记了添加头文件!

c 复制代码
#include "headfile.h"

fun.c函数

具体逻辑,我现在看是蛮简单的,不管了,吃饭去了,自己悟去吧

c 复制代码
#include "headfile.h"

char	text[20]; 

uint32_t fre1, capture_value1, fre2, capture_value2 ;

void lcd_show()
{
	sprintf(text, "zlfsgdsg");//¿ÉÒÔµ÷Õû×Ö·û´®Î>>Ö㬵<<ÊÇ×î¶à20¸ö×Ö·û
	LCD_DisplayStringLine(Line0, (uint8_t *)text);
	sprintf(text, "   R39_fre1:%d   ", fre1);
	LCD_DisplayStringLine(Line2, (uint8_t *)text);
	sprintf(text, "   R40_fre2:%d   ", fre2);
	LCD_DisplayStringLine(Line4, (uint8_t *)text);
	}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim->Instance == TIM16)// R39
	{
		capture_value1 = TIM16->CCR1;
		TIM16->CNT = 0;
		fre1 = 80000000/(80*capture_value1);
	
	}
	
		if(htim->Instance == TIM2)//R40
	{
		capture_value2 = TIM2->CCR1;
		TIM2->CNT = 0;
		fre2 = 80000000/(80*capture_value2);
	
	}


}

ADC测量

原理图

通过R37和R38两个旋钮,测量AD的电压值

cubemx配置

分别对引脚PB15和PB12进行配置。

keil代码

记得在headfile.h文件中添加ADC头文件

fun.c代码

c 复制代码
#include "headfile.h"




char text[20];	
void lcdshow()
{
	sprintf(text, "      zlfsgdsg      ");
	LCD_DisplayStringLine(Line0, (uint8_t *)text);
	
	HAL_ADC_Start(&hadc1); 
	uint32_t	adc_value =  HAL_ADC_GetValue(&hadc1); //R38
	
	sprintf(text, "     value:%d      ", adc_value);
	LCD_DisplayStringLine(Line3, (uint8_t *)text);

}

main.c

记得将LCD初始化,然后在主函数中调用lcdshow();

即可在lcd中实时读取adc1的值

keil代码2

若是要读取两个ad的电压值

fun.c代码

c 复制代码
#include "headfile.h"




char text[20];	
void lcdshow()
{
	sprintf(text, "      zlfsgdsg      ");
	LCD_DisplayStringLine(Line0, (uint8_t *)text);
	
	
	sprintf(text, "     R37_volt:%.2f      ", get_vol(&hadc2));
	LCD_DisplayStringLine(Line3, (uint8_t *)text);
	
	sprintf(text, "     R38_volt:%.2f      ", get_vol(&hadc1));
	LCD_DisplayStringLine(Line5, (uint8_t *)text);

}




double get_vol(ADC_HandleTypeDef *hadc)
{
	
	HAL_ADC_Start(hadc); 
	uint32_t	adc_value =  HAL_ADC_GetValue(hadc); //R38
	return	3.3*adc_value/4096; //因为AD是12位,所以ad最大值是4096

}

	

fun.h

c 复制代码
void lcdshow(void);

double get_vol(ADC_HandleTypeDef *hadc);

串口发送和接收

原理图

cubemx配置


keil代码

串口发送

main.c代码
记得在前面添加头文件

c 复制代码
  while (1)
  {
		char text01[20];
		sprintf(text01, "zlfsgdgs\r\n");
		HAL_UART_Transmit(&huart1, (uint8_t *)text01, sizeof(text01), 50);
		HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

串口接收

fun.c代码

c 复制代码
#include "headfile.h"

uint8_t rec_data;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1)
	{
		
		HAL_UART_Transmit(huart, &rec_data, 1, 50);
		
		HAL_UART_Receive_IT(huart, &rec_data, 1);
		
	}


}

fun.h代码

加一个外部变量定义

c 复制代码
extern uint8_t rec_data;

main.c代码

利用定时器进行串口不定长数据接收

原理

cubemx配置

主要是把波特率设置为9600

keil代码

main.c
把定时器使能!!! &&调用函数

fun.c函数

c 复制代码
#include "headfile.h"

char	send_buff[20];
uint8_t rec_data, count;
uint8_t	rec_flag;
uint8_t	rec_buff[20];


void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART1)
	{
		
		//HAL_UART_Transmit(huart, &rec_data, 1, 50);
		TIM4->CNT = 0;
		rec_flag = 1;
		rec_buff[count]	= rec_data;
		count ++;
		HAL_UART_Receive_IT(huart, &rec_data, 1);
		
	}

}

void	uart_data_rec()
{
		if(rec_data)
		{
			if(TIM4->CNT > 15)
			{
				if(rec_buff[0] == 'z'	&& rec_buff[1] == 'l' && rec_buff[2] == 'f')
				{
					sprintf(send_buff, "zlf\r\n");
					HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
				}
				else if(rec_buff[3] == 's'	&& rec_buff[4] == 'g' && rec_buff[5] == 'd')
				{
					sprintf(send_buff, "sgd\r\n");
					HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
				}
				else if(rec_buff[6] == 's'	&& rec_buff[7] == 'g')
				{
					sprintf(send_buff, "sgd\r\n");
					HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
				}
				else
				{
					sprintf(send_buff, "error\r\n");
					HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, sizeof(send_buff), 50);
				}
				rec_flag = 0;
				for(int	i=0; i<count; i++)
					rec_buff[i] = 0;
					count	= 0;
			}
		
		}

}
	

fun.h

headfile.h

要添加usart.h的头文件

EEPROM读写

原理

原理就不说了,学嵌入式I2C的时候学了挺久

keil代码

i2c_hal_c

c 复制代码
void eeprom_write(uint8_t addr, uint8_t data)
{
	I2CStart();
	I2CSendByte(0xa0);
	I2CWaitAck();
	I2CSendByte(addr);
	I2CWaitAck();
	I2CSendByte(data);
	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 data = I2CReceiveByte();
	I2CSendNotAck();
	I2CStop();
	
	return	data;
}

i2c_hal_h代码

主要就是添加两个文件

c 复制代码
void eeprom_write(uint8_t addr, uint8_t data);
uint8_t eeprom_read(uint8_t addr);

main.c代码

相关推荐
f狐0狸x1 小时前
【蓝桥杯每日一题】4.1
c语言·c++·算法·蓝桥杯
ん贤1 小时前
2023第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(真题&题解)(C++/Java题解)
java·c语言·数据结构·c++·算法·蓝桥杯
Qwertyuiop20167 小时前
搭建开源笔记平台:outline
笔记·开源
白夜易寒7 小时前
Docker学习之私有仓库(day10)
学习·docker·容器
淮北4948 小时前
ros调试工具foxglove使用指南三:在3d空间写写画画(Panel->3D ->Scene entity)
python·学习·3d·机器人
山河君9 小时前
音频进阶学习二十四——IIR滤波器设计方法
学习·算法·音视频·信号处理
Vic·Tory10 小时前
Go语言学习笔记
笔记·学习·golang
Small踢倒coffee_氕氘氚10 小时前
Python实现3D贴图渲染:解锁数字艺术新维度
经验分享·笔记
吴梓穆10 小时前
UE5学习笔记 FPS游戏制作28 显式玩家子弹数
笔记·学习·ue4
KevinRay_11 小时前
从零开始学习SQL
数据库·学习·mysql