用博客来记录一下参加蓝桥杯嵌入式第十六届省赛的学习经历
- 工具
- 模块
工具
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代码