用博客来记录一下参加蓝桥杯嵌入式第十六届省赛的学习经历
- 工具
- 环境准备
- cubemx配置
- 外部高速时钟使能
- 设置串口
- 时钟配置
- 项目配置
- keil配置
- 烧录方式
- 注意
- 代码规范
- 头文件配置
- 模块
- led
- cubemx配置
- keil代码
- 实现点亮一只灯
- 实现具体操作的灯,以及点亮还是熄灭
- 按键
- cubemx配置
- keil代码
- LCD
- keil代码
- 引脚冲突问题
- LED闪烁
- cubemx配置
- keil代码
- 按键长按
- cubemx配置
- keil代码
- LCD高亮显示
- keil代码
- PWM输出
- 计算公式
- cubemx配置
- keil代码
- 输入捕获测量引脚输出PWM波周期
- 原理图
- 要求:用PA7来测量PA1生成PWM波的频率
- cubemx配置
- keil代码
- 输入捕获测量555定时器频率
- 原理图
- cubemx配置
- keil代码
- ADC测量
- 原理图
- cubemx配置
- keil代码
- 串口发送和接收
- 原理图
- cubemx配置
- keil代码
- 利用定时器进行串口不定长数据接收
- 原理
- cubemx配置
- keil代码
- EEPROM读写
- 原理
- keil代码
工具
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主要代码
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代码
void led_show(void);
main.c代码(需要在主函数内)
led_show();
实现具体操作的灯,以及点亮还是熄灭
fun.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,所以用左移移位符elseHAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (led - 1), GPIO_PIN_SET);HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);}
fun.h代码(需要加上那个头函数)
#include "stm32g4xx.h" // Device headervoid led_show(uint8_t led, uint8_t mode);
main.c代码(灯1, 4,8亮)
led_show(1, 1);led_show(4, 1);led_show(8, 1);
按键
原理图,如图,需要将PB0~2,PA0配置为上拉输入模式。按下为低电平,松开为高电平
cubemx配置
keil代码
fun.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代码
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代码
#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);elseHAL_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 = 20void 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代码
#ifndef _fun_h
#define _fun_h#include "stm32g4xx.h" // Device headervoid 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函数加上
uint16_t temp = GPIOC->ODR;
GPIOC->ODR = temp;
解决方法如下
main.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中定义一个变量
uint8_t led_mode;
按键长按
原理,用一个计数器cnt(取10000)计时1s,20000对应2s,5000对应0.5s来计时,达到识别按键长按的目的
cubemx配置
此时TIM3不使能!!
keil代码
fun.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代码
定义变量
uint8_t lcd_highshow;
按键b1按下时
lcd_highshow ++;lcd_highshow %= 3;
逻辑流程
应该蛮简单的,就不写注释了
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
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
设置占空比
TIM2->CCR2 = 50;
原理图(抄袭版),不懂的话看嵌入式的书去
输入捕获测量引脚输出PWM波周期
原理图
要求:用PA7来测量PA1生成PWM波的频率
需要用杜邦线连接PA7和PA1.
cubemx配置
这里PA7用TIM17或者TIM2都行,我就根据视频来了
使能定时器
keil代码
为了省事,将code文件复制到这个工程目录下,记得在main.c文件中添加头文件定义
main.c函数
使能中断17
HAL_TIM_IC_Start_IT(&htim17, TIM_CHANNEL_1);
fun.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初始化
/* 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函数放到循环里
/* 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 */
}
&&& 不要忘记了添加头文件!
#include "headfile.h"
fun.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代码
#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); //R38sprintf(text, " value:%d ", adc_value);LCD_DisplayStringLine(Line3, (uint8_t *)text);}
main.c
记得将LCD初始化,然后在主函数中调用lcdshow();
即可在lcd中实时读取adc1的值
keil代码2
若是要读取两个ad的电压值
fun.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); //R38return 3.3*adc_value/4096; //因为AD是12位,所以ad最大值是4096}
fun.h
void lcdshow(void);double get_vol(ADC_HandleTypeDef *hadc);
串口发送和接收
原理图
cubemx配置
keil代码
串口发送
main.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代码
#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代码
加一个外部变量定义
extern uint8_t rec_data;
main.c代码
利用定时器进行串口不定长数据接收
原理
cubemx配置
主要是把波特率设置为9600
keil代码
main.c
把定时器使能!!!&&调用函数
fun.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
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代码
主要就是添加两个文件
void eeprom_write(uint8_t addr, uint8_t data);
uint8_t eeprom_read(uint8_t addr);
main.c代码