《蓝桥杯嵌入式编程:快速入门》

蓝桥杯嵌入式快速入门

介绍

​ 蓝桥杯嵌入式使用的单片机是STM32G431RBT6,内核ARM Cortex – M4,MCU+FPU,170MHz/213DMIPS,高达128KB Flash,32KB SRAM,其余的外设就不多介绍了,参照数据芯片数据手册

​ CT117E-M4开发板资源:微控制器STM32G431RBT6、一路USB转串口、2.4寸TFT-LCD、4个功能按键、1个复位按键、8个LED、一个E2PROM(AT24C02)、一个可编程电阻(100K)、2路信号发生器、2个分压电位器、2个扩展接口、一个CMSIS DAP Link调试器

前期准备

​ ①安装串口驱动(通过设备管理器),若为windows10则不需手动安装

​ ②在cubemx中安装hal库

​ ③在keil中安装器件包

第一个程序

​ ①cubemx中选择芯片型号

​ ②使能必要IO口:RCC_OSC_IN and RCC_OSC_OUT(External crystal oscillator)、SWDIO and SWCLK(CMSIS DAP Link)

​ ③配置时钟Clock Configuration。外部时钟设置为24MHz,第一个选择器选择HSI即内部RC振荡器(没用HSE是因为引脚与LED冲突),PLLM为2分频,PLL内部先乘20再除以2,最终得80MHz,第二个选择器选择PLLCLK,后APB1和APB2总线时钟均设置为80MHz(此设置根据官方学习程序配置)

​ ④在Project Manager中,确定工程名称、位置、IDE,勾选为每个外设初始化生成c和h文件。GENERATE CODE生成工程

​ ⑤在keil中,打开Options for Target(魔术棒),Output勾选Create HEX File,Debug菜单右上角选择CMSIS-DAP Debugger,进入Setting,Port选择SW,Max Clock选择10MHz,如果插上开发板(注意板子有两个接口,插上DOWNLOAD接口),在SW Device中可以看到芯片IDCODE和Name,进入Flash Download选择Erase Full Chip,Reset and Run,然后下方Add添加Flash编程算法,选择STM32G4X,128K,确定保存

​ ⑥编译,下载,可以看到程序下载成功,但是LED灯不稳定,原因是SN74HC573ADWR芯片引脚电平不稳定导致,可以不用管

LED

​ 创建新工程,LED灯与MCU之间使用SN74HC573ADWR芯片连接,使能PD2(信号锁存引脚,点亮灯的操作需要先对灯操作,然后依次拉高、拉低PD2引脚)拉低,使能PC8-PC15对应LED1-LED8,低电平亮,高电平灭

​ 代码如下:

  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);
  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET); //拉高PD2,将PC信号送入输入端
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);//拉低PD2,锁存输出端信号
  HAL_Delay(1000);

注:操作LED时需要设置全部的LED状态,因为对LCD操作也会影响LED状态

按键和定时器中断

​ 为什么按键和定时器中断要一起讲呢?因为传统按键检测有两种方式,一种直接读IO电平状态,另一种是中断,这两种都不太好处理按键抖动,因此使用定时器来计数判断按键是否按下,经过测试效果还行

​ 创建新工程,使用tim1,时钟源选择Internal Clock,分频系数选择80,意味着一个tick是1us,计数值设为9999,则定时器中断周期为10ms,在NVIC Settings中使能更新中断

​ 代码中需要在主循环上方手动启动定时器中断:

HAL_TIM_Base_Start_IT(&htim1);

​ 在tim.c文件最后加上回调函数:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    if (htim == (&htim1)){
			static uint8_t key1count = 0;  //按键计数
			if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET){
				key1count ++;
			}else key1count = 0;
			if(key1count == 8){   //这个值根据情况设定,该if内为key1按下执行的内容
				HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8);
				HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);					
				key1count = 0;   //计数清零
                 a++;
			}        
    }	 
}

// 长、短按
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    if (htim == (&htim1)){
			static int key1count = 0,key11count = 0;  //按键计数  key1count是判断有按键按下,key11count是判断长或短按
			if(key1count >= 8){   //这个值根据情况设定  消抖						  
              key11count++;
				 if(key11count>=100){ //长按
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_RESET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15, GPIO_PIN_SET);			
					HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);					 				 
				 }
				 else if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_SET){  //短按 		
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_14, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOC, GPIO_PIN_15, GPIO_PIN_SET);			
					HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
					HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);								
				 }
			} 
			if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET){
				key1count ++;
			}else {
                key1count = 0;key11count=0;
            }	
    }	 
}

​ 其实直接读取IO电平状态判断按键是否按下也是可行的:

if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET){
	HAL_Delay(100);
	if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET){
		HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_14 | GPIO_PIN_15);
		HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
		HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);
	}
}

串口重定义、发送和接收中断

​ CT117E-M4的DOWNLOAD接口默认与USART1即PA9、PA10连接,因此这个接口可以用于调试

​ cubemx中使能USART1为Asynchronous即异步通信,使能PA9和PA10为串口功能,其余串口通信参数可以自行设定

​ 重定向:

#include <stdio.h>
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
  HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1 , 0xffff);
  return ch;
}

​ 串口中断仍以USART1为例,只需在cubemx中勾选NVIC Settings允许中断即可

​ 需要在主循环上方开启中断接收:

HAL_UART_Receive_IT(&huart1, &RES, 1);

​ 在usart.c中调用中断回调函数并编写用户代码:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
	if(huart == (&huart1)){
		if(RES == 'A'){
				HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);		
		}	else{
				HAL_GPIO_WritePin(GPIOC,GPIO_PIN_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_UART_Receive_IT(&huart1, &RES, 1);
	}
}

LCD

​ 比赛时会提供资源数据包,在其中有lcd.h、lcd.c、font.h三个文件,采用IO口模拟通信方式,根据原理图初始化所有的IO口为推挽输出形式,其余配置均不变,然后将上述三个文件添加到工程中

​ 使用时直接调用lcd.h中函数即可:

LCD_Init();
LCD_Clear(White); //清屏,并将背景设置为白色

LCD_SetBackColor(Blue);  // 设置即将显示的文字背景颜色
LCD_SetTextColor(White);	//  设置即将显示的文字的颜色
// void LCD_DisplayStringLine(u8 Line, u8 *ptr)
LCD_DisplayStringLine(Line0, (uint8_t *)"                    ");  //  显示字符串
LCD_DisplayStringLine(Line1, (uint8_t *)"                    ");
LCD_DisplayStringLine(Line2, (uint8_t *)"      LCD Test      ");
LCD_DisplayStringLine(Line3, (uint8_t *)"                    ");
LCD_DisplayStringLine(Line4, (uint8_t *)"                    ");

//  显示浮点数  num为35.2  0x30为'0'的ascii码
LCD_SetTextColor(Red);
LCD_DisplayChar(Line0,320,(int)num/10+0x30);
LCD_SetTextColor(Red);
LCD_DisplayChar(Line0,304,(int)num%10+0x30);
LCD_SetTextColor(Red);
LCD_DisplayChar(Line0,288,0x2E);  //  0x2E为 '.'的ascii码
LCD_SetTextColor(Red);
LCD_DisplayChar(Line0,272,((int)(num*10))%10+0x30);	
//  另一种显示浮点数方式
char buf[20];
LCD_SetBackColor(Blue);
LCD_SetTextColor(Red);
sprintf(buf, "      VAL:%.2fV", getADC()*3.3/4096);
HAL_Delay(100);
LCD_DisplayStringLine(Line8, (uint8_t *)buf);

注:LCD为320*240,一共10行,Line0-Line9,每行可以显示20个字符,即每个字符320/20=16,最左侧为320,最右侧为1

ADC、DAC

​ 蓝桥杯板子上有两路ADC采样电路,R37对应PB15,R38对应PB12

​ 以PB15为例,初始化为ADC2_IN15,勾选左侧IN15 Single-ended,时钟分频器选择异步时钟1分频,如果1分频不行选择2分频,总之选择异步时钟,12bit,右对齐,其余不变

uint32_t adcValue = 0;char adcArray[20];HAL_ADC_Start(&hadc2);  // 每次读值都要先手动启动
ADCadcValue = HAL_ADC_GetValue(&hadc2);  //  读值
sprintf(adcArray," VAL:%.2fV",adcValue*3.3/4096);  //  转换
LCD_SetTextColor(Red);  //  设置文字颜色//  
HAL_Delay(100);
LCD_DisplayStringLine(Line8, (uint8_t *)adcArray);  //  显示

​ R38同理

​ DAC以PA4为例,它在板子的J3扩展口上

​ 使能PA4的DAC1_OUT1功能,OUT1_mode设置为Connected to external pin only,其余参数不变

HAL_DAC_Start(&hdac1, DAC_CHANNEL_1); //  以下可以封装成一个函数
vol = 2.5;  //  输出2.5V
temp = (4096*vol/3.3);
HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1,DAC_ALIGN_12B_R,temp);

PWM和输入捕获

​ 板子上有两个定时器输入输出口,PA15对应R40、J10跳线帽,PB4对应R39、J9跳线帽

​ 输入捕获时,是捕获信号发生器生成的方波频率,以PA15为例,使能其为定时器二通道一功能,Input Capture direct mode,使能定时器二全局中断,其余不变直接生成

HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);  //  启动定时器输入捕获模式
uint32_t  cc1_value_2 = 0;  									// TIMx_CCR1 的值
uint32_t  f40 = 0;//  中断服务函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){	
	cc1_value_2 = __HAL_TIM_GET_COUNTER(&htim2);	
	__HAL_TIM_SetCounter(&htim2,0);	
	f40 = 1000000/cc1_value_2;		
	HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}
//  显示频率
sprintf(buf, "  FRQ(R40):%dHz   ",f40);
LCD_DisplayStringLine(Line8, (uint8_t *)buf);

​ PWM输出时需要将跳线帽拔下

​ cubemx中选择定时器时钟源为内部时钟,通道一设置为PWM Generation CH1,然后设置分频系数、计数模式、计数周期值(ARR)、使能自重装载预装载、PWM模式、脉宽(Duty)、极性

HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel); //  开启PWM输出使用函数或者寄存器操作即可修改占空比或者频率

EE

​ 板子上有一个M24C02的EEPROM,容量为2048bit,2Kbit,8×256,即256Byte

​ 使用模拟IIC驱动,时钟线为PB6,数据线为PB7

​ cubemx初始化引脚为输出,然后进入keil,将i2c.c和i2c.h文件移植到工程中,添加入工程中

I2CInit();  //  初始化I2C,其实这句不需要,因为cube已经帮我们初始化了引脚
//  编写读写函数
//  24c02读函数
uint8_t x24c02_read(uint8_t address){	
	unsigned char val;
	I2CStart(); 	
	I2CSendByte(0xa0);	
	I2CWaitAck(); 		
	I2CSendByte(address);	
	I2CWaitAck(); 		
	I2CStart();	
	I2CSendByte(0xa1); 	
	I2CWaitAck();	
	val = I2CReceiveByte(); 	
	I2CWaitAck();	
	I2CStop();		
	return(val);
}
//  24c02写函数
void x24c02_write(unsigned char address,unsigned char info){	
	I2CStart(); 	
	I2CSendByte(0xa0); 	
	I2CWaitAck(); 		
	I2CSendByte(address);		
	I2CWaitAck(); 	
	I2CSendByte(info); 	
	I2CWaitAck(); 	
	I2CStop();
}
//  然后调用即可
val = x24c02_read(0);
x24c02_write(0, ++val);

RES

​ 可编程电阻(变阻器),存储器类型为RAM

​ 采用模拟I2C,PB6为SCL,PB7为SDA

I2CInit();  //  初始化I2C,其实这句不需要,因为cube已经帮我们初始化了引脚
//  编写读写函数
//  写电阻,设置电阻值
void write_resistor(uint8_t value){   	
	I2CStart();	
	I2CSendByte(0x5E);  	
	I2CWaitAck();		
	I2CSendByte(value);  	
	I2CWaitAck();	
	I2CStop();
}
//  读电阻,读取电阻值
uint8_t read_resistor(void){   	
	uint8_t value; 	
	I2CStart();	
	I2CSendByte(0x5F);  	
	I2CWaitAck();		
	value = I2CReceiveByte();	
	I2CSendNotAck();	
	I2CStop();	
	return value;
}
//  打印实际电阻值
LCD_SetTextColor(Red);
sprintf(buf, "   RES VAL:%.1fK  ", (0.78740*read_resistor()));
LCD_DisplayStringLine(Line8, (uint8_t *)(buf));

附2022年十三届蓝桥杯嵌入式省赛代码:https://pan.baidu.com/s/1SnbMTCun6-MkDPDCdr8-yw
提取码:3h0s
(题主比赛时花费两小时完成,有些地方如密码验证并未想的很周到,其余功能均已完成,供大家学习使用)

物联沃分享整理
物联沃-IOTWORD物联网 » 《蓝桥杯嵌入式编程:快速入门》

发表评论