第十一届蓝桥杯嵌入式 决赛 解析及代码
0 摘要
我参加了第十一届蓝桥杯嵌入式决赛,但成绩并不理想,只得了国三。最近我备赛第十二届蓝桥杯,决定把这套题再做一次。
本文主要会介绍:
1、一种简单的LED控制方法;
2、一种简单的按键下降沿读取方式;
3、一种简单的基于Systick的系统;
4、LCD与LED冲突的解决办法;
5、设置DMA方式读取ADC1两个通道时的注意事项;
6、设置TIM2 CH2和CH3两个通道捕获时的注意事项;
如果你,参加蓝桥杯或者学习STM32,存在或想了解以上问题,你可以参考本篇文章。
1 题目
2 解析
2.1 所需模块
本项目所需模块有6个,即LED、KEY、LCD、ADC、PWM输出和输入捕获。我把模块分为六个,因为这篇文章主要面向的对象是蓝桥杯备赛选手。
由于十一届题目比较简单,所以不需要一些多余地分析,十一届细节较多,我们在代码中具体讲怎么操作。
下面我将按摘要中的问题顺序将这几个模块一一介绍。
2.2 模块介绍
2.2.1 一种简单的LED控制
控制LED我们肯定是求快,写好所有基础模块初始化的速度决定了我们留给后面攻坚克难的时间。
LED控制中,我觉得寄存器GPIOC->ODR很重要,利用这个寄存器进行位异或操作,可以精确操作每一个LED。这在文中也有体现。
首先一些小tip大家注意,一定要在头文件里给LED引脚定义一下,或者写一个enum,因为则可以在后期大大简化你的代码。
我的定义并不好,主要是为了粘贴复制,你可以有自己的方式。
#define LED1 GPIO_Pin_8
#define LED2 GPIO_Pin_9
#define LED3 GPIO_Pin_10
#define LED4 GPIO_Pin_11
#define LED5 GPIO_Pin_12
#define LED6 GPIO_Pin_13
#define LED7 GPIO_Pin_14
#define LED8 GPIO_Pin_15
#define LEDALL LED1| LED2| LED3| LED4| LED5| LED6| LED7| LED8
下面是控制代码,我一直都这么写,发现挺实用的,这是真心推荐。
void LED_Control(uint16_t LEDx,uint8_t state)
{
if(state == 0)//关灯
GPIO_SetBits(GPIOC,LEDx);
else if(state == 1)//开灯
GPIO_ResetBits(GPIOC,LEDx);
else if(state == 2)//反转
GPIOC->ODR ^= LEDx;
GPIO_SetBits(GPIOD,GPIO_Pin_2);
GPIO_ResetBits(GPIOD,GPIO_Pin_2);
}
2.2.2 一种简单的按键下降沿读取
蓝桥杯对按键的读取一般都有要求,传统的三段式中断消抖代码量实在有点大,所以我们来尝试一下只读下降沿。
以单个按键来说明一下核心思想。给每个按键读取设置一个Buff,即KEY_Buff[4]。
你读代码可知,我们系统20ms一个周期,每个周期都在读按键的值,当这个按键为高电平即没有按下的时候,我们把这个按键的Buff设置为0。
如果下一次读取按键满足(Buff == 0 && 按键低电平),我们才认为读取有效,并且按键不恢复高电平,不重置Buff为0。
这样,我们就实现了下降沿读取。代码如下,看的话,看一个就好了。
uint8_t KEY_Read(void)
{
if(GPIO_ReadInputDataBit(GPIOA,KEY1) == 1)
KEY_Buff[0] = 0;
else if(GPIO_ReadInputDataBit(GPIOA,KEY1) == 0 && KEY_Buff[0] == 0)
{
KEY_Buff[0] = 1;
return 1;
}
if(GPIO_ReadInputDataBit(GPIOA,KEY2) == 1)
KEY_Buff[1] = 0;
else if(GPIO_ReadInputDataBit(GPIOA,KEY2) == 0 && KEY_Buff[1] == 0)
{
KEY_Buff[1] = 1;
return 2;
}
if(GPIO_ReadInputDataBit(GPIOB,KEY3) == 1)
KEY_Buff[2] = 0;
else if(GPIO_ReadInputDataBit(GPIOB,KEY3) == 0 && KEY_Buff[2] == 0)
{
KEY_Buff[2] = 1;
return 3;
}
if(GPIO_ReadInputDataBit(GPIOB,KEY4) == 1)
KEY_Buff[3] = 0;
else if(GPIO_ReadInputDataBit(GPIOB,KEY4) == 0 && KEY_Buff[3] == 0)
{
KEY_Buff[3] = 1;
return 4;
}
return 0;
}
2.2.3一种简单的基于Systick的系统
记住,这是面向蓝桥杯参赛选手的一篇文章。
我所讲的系统其实就是简单的时基操作系统,但重点是,让你如何快速地建立起这个系统。时基操作系统可以简单地实现定时操作,比如我这个LCD的显示,多少ms一次。简单的画一下时基。
下面上代码,标志位的定义我一般这样做,因为在比赛中,我们不知道会有多少的需要定时操作的事件。如第八届省赛电梯控制题,我感觉这样子做真的方便,需要一个新的事件,就在这里添加一下就可以了。
typedef struct
{
u8 led;
u8 display;
u8 adc;
}flag_t;
extern flag_t flag;
然后给大家看看主函数和中断函数部分的代码,都非常的简洁,说实话,大家写代码都会错,但是你如果花时间检查是不是自己系统逻辑错了,那你完蛋了。//所有函数都在driver.c中
中断函数
flag_t flag;
void SysTick_Handler(void)
{
static u8 ms50;
ms50++;
if(ms50 == 50)
{
ms50 = 0;
flag.display = 1;
}
}
主函数
#include "driver.h"
//所有函数都在driver.c中
int main(void)
{
SysTick_Config(SystemCoreClock /1000);//这一段代码就能开启systick的ms中断
LED_Init();
KEY_Init();
ADC_Config();
PWM_Config(20000);
Capture_Config();
STM3210B_LCD_Init();
LCD_SetTextColor(White);
LCD_SetBackColor(Black);
LCD_Clear(Black);
while (1)
{
if(flag.display == 1)
{
flag.display = 0;
Display();
}
}
}
2.2.4 LCD与LED冲突的解决办法
首先,我们要明白,他们为什么冲突。
不用想哈,那肯定是共用了引脚,虽然我们看原理图会觉得这不是分的清清楚楚吗?这个我也没细查,但你看板子上,是不是那一堆PC口都分给了LCD?
废话少说吧,我们直接说怎么解决,其实很方便啊,我们看到LCD与写有关的函数只有三个,那我们的方法就是在每一个写函数之前都保存GPIOC->ODR的值,在写操作结束后又把它重新赋值回去。
void LCD_WriteReg(u8 LCD_Reg, u16 LCD_RegValue);
void LCD_WriteRAM_Prepare(void);
void LCD_WriteRAM(u16 RGB_Code);
拿第一个函数为例
void LCD_WriteReg(u8 LCD_Reg, u16 LCD_RegValue)
{
u16 Buff = GPIOC->ODR;//添加
GPIOB->BRR = 0x0200;
GPIOB->BRR = 0x0100;
GPIOB->BSRR = 0x0020;
GPIOC->ODR = LCD_Reg;
GPIOB->BRR = 0x0020;
GPIOB->BSRR = 0x0020;
GPIOB->BSRR = 0x0100;
GPIOC->ODR = LCD_RegValue;
GPIOB->BRR = 0x0020;
GPIOB->BSRR = 0x0020;
GPIOB->BSRR = 0x0100;
GPIOC->ODR = Buff;//添加
}
2.2.5 使用DMA读取ADC1两个通道时的注意事项
这一段直接上代码,看代码里的注释吧
单独说一下这个函数 ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_1Cycles5);单通道用多了的同学,必须注意里面的参数Rank。
void ADC_Config(void)
{
ADC_InitTypeDef ADC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&(*ADC_Value);
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为源,即从外设到存储器(数组)
DMA_InitStructure.DMA_BufferSize = 2;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//这里一定是允许储存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//这里一定是半字16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;//这里一定是半字16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//这里一定是允许循环
DMA_InitStructure.DMA_Priority = DMA_Mode_Normal;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
ADC_DMACmd(ADC1, ENABLE);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;//这两个都要ENABLE,你如果要中断一次取一次值,还不如不用DMA
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 2;
ADC_Init(ADC1, &ADC_InitStructure);
ADC_Cmd(ADC1, ENABLE);
//他妈的这个地方一定是1和2,我单通道用多了,写了两个1,吃了很大的亏,这个1,2就是序号
ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 1, ADC_SampleTime_1Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_5, 2, ADC_SampleTime_1Cycles5);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
2.2.6 设置TIM2的CH2和CH3两个通道捕获时的注意事项
依然是直接上代码,因为一些代码都可以在标准库中直接复制,我们只需要知道一些注意事项就可以了。
单独说明一下,TIM做输入捕获时,特别是多通道时,配置不好,很容易直接把代码搞炸。
void Capture_Config(void)
{
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_TimeBaseStructure.TIM_Period = 0xffff;
TIM_TimeBaseStructure.TIM_Prescaler = 71;//最好设置预分频系数,不然低频段捕获出错
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//此处为常见问题,必须分开初始化,不然代码打包票地炸
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ITConfig(TIM2, TIM_IT_CC2 | TIM_IT_CC3, ENABLE);
TIM_Cmd(TIM2, ENABLE);
}
3 工程链接
代码已经经过测试,确认可用。
链接:https://pan.baidu.com/s/1T1UA8LWFMElyHNRy4ijGug
提取码:jsl0
4 结束语
如果有错误,请一定指出,我还是个学生,所以主要任务都还是学习。
创作不易,记得三连哟;