蓝桥杯嵌入式使用的单片机是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灯与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
#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.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采样电路,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);
板子上有两个定时器输入输出口,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输出使用函数或者寄存器操作即可修改占空比或者频率
板子上有一个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);
可编程电阻(变阻器),存储器类型为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
(博主比赛时完成,有些地方如密码验证并未想的很周到,其余功能均已完成,供大家学习使用)
另外,博主手里还有两块闲置的板卡CT117E-M4,如果有需要可以私信我,可走某鱼,诚信第一。