我们需要“脚手架”
关于代码,我想体现出这么一个过程:我是如何一步一步修改代码的。我认为,从学习的角度来考虑,直接看最终的代码没有什么意义。 写代码就像工人盖房子,盖房子过程中,工人要搭建脚手架;房子盖好以后,脚手架要拆除。直接领着学生看盖好的房子,说,你就照着这个样子来盖房子,学生是做不出来。他不知道怎么搭建脚手架,甚至都不知道什么是脚手架。 所以我打算这个系列的讲义,每一篇代码都在上一篇的基础上做一些改进,保留代码“进化”的过程。有些过程代码在最终的代码中不会体现,但是也很重要,就像房子盖好以后,你看不到脚手架一样。我们需要脚手架。
目前用到的代码先用压缩包的形式上传,等教程写完以后,后续代码的维护使用Git。
各种初始化
下载压缩包并打开工程以后,可以在main.c里找到主函数。目前为止,主函数进行了一些初始化,死循环内什么都没写,后续可以根据我们的需要写业务逻辑代码。
//main.c
int main(void)
{
LED_Init();
KEY_Init();
delay_init();
initIIC();
initOLED();
while(1)
{
}
}
LED与SLED的初始化
函数的作用从名字就可以看出来,比较简单。例如LED的初始化。需要初始化哪个引脚,可以从电路图中看出。
//IO.c
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
GPIO_Init(GPIOC, &GPIO_InitStructure);
AllLED_ON();
LED1 = LED_ON;
LED2 = LED_OFF;
}
具体LED或者SLED对应的引脚可以在原理图中看出来。为了方便使用,我根据外设与引脚关系做了宏定义。
//IO.h
#define SLED1 PCout(0)
#define SLED2 PCout(1)
#define SLED3 PCout(2)
#define SLED4 PCout(3)
#define LED1 PCout(4)
#define LED2 PCout(5)
#define SLED5 PBout(12)
#define SLED6 PBout(13)
#define SLED7 PBout(14)
#define SLED8 PBout(15)
#define SKEY1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
#define SKEY2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1)
#define SKEY3 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_2)
#define SKEY4 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_3)
#define SKEY5 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_6)
#define SKEY6 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_7)
#define SKEY7 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_8)
#define SKEY8 GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_9)
#define PAUSE GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_0)
#define BEEP PBout(1)
#define KEY1_PRES 1 //KEY1按下
#define KEY2_PRES 2 //KEY2按下
#define KEY3_PRES 3 //KEY3按下
#define KEY4_PRES 4 //KEY4按下
#define KEY5_PRES 5 //KEY5按下
#define KEY6_PRES 6 //KEY6按下
#define KEY7_PRES 7 //KEY7按下
#define KEY8_PRES 8 //KEY8按下
#define PAUSE_PRES 9
#define LED_ON 0
#define LED_OFF 1
#define DOWN 0 //按键按下
#define FREE 1
然后编写了两个函数,用于点亮所有LED或者关闭所有LED
//IO.c
void AllLED_ON(void)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
GPIO_ResetBits(GPIOC,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);
}
void AllLED_OFF(void)
{
GPIO_SetBits(GPIOB,GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
GPIO_SetBits(GPIOC,GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);
}
这些代码。可能对于某些没有使用过STM32的同学来说不太好看懂,不过没有关系,可以仅仅应用的话,这是很简单的。在我之前的博客里也提过一些库函数的基础。例如设置某引脚为推挽输出GPIO_Mode_Out_PP
,不知道什么事推挽输出可以自己查一下。设置哪个引脚为推挽输出?PB12到PB15,PC0到PC5。
再比如,让某个LED亮起来GPIO_ResetBits(GPIOB,GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);
很容易看出来,这个操作是把PB12到PB15置为低电平,结合原理图可知,引脚低电平可以点亮LED。
按键初始化与扫描函数
按键设置为上拉输入,检测到低电平,说明按键被按下。按键扫描函数课可以返回被按下的按键值。
void KEY_Init(void) //PB0-PB3
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/*
//PB3与PB4默认用作调试口,如果用作普通的IO,需要加上以下两句
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
*/
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
GPIO_Init(GPIOC, &GPIO_InitStructure);
}
unsigned char KEY_Scan(unsigned char mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(SKEY1==DOWN||SKEY2==DOWN||SKEY3==DOWN||SKEY4==DOWN||SKEY5==DOWN||SKEY6==DOWN||SKEY7==DOWN||SKEY8==DOWN||PAUSE==DOWN))
{
delay_ms(10);//去抖动
key_up=0;
if(PAUSE==DOWN)return PAUSE_PRES;
else if(SKEY1==DOWN)return KEY1_PRES;
else if(SKEY2==DOWN)return KEY2_PRES;
else if(SKEY3==DOWN)return KEY3_PRES;
else if(SKEY4==DOWN)return KEY4_PRES;
else if(SKEY5==DOWN)return KEY5_PRES;
else if(SKEY6==DOWN)return KEY6_PRES;
else if(SKEY7==DOWN)return KEY7_PRES;
else if(SKEY8==DOWN)return KEY8_PRES;
}else if(SKEY1==FREE && SKEY2==FREE && SKEY3==FREE && SKEY4==FREE && SKEY5==FREE && SKEY6==FREE && SKEY7==FREE && SKEY8==FREE && PAUSE==FREE)key_up=1;
return 0;// 无按键按下
}
其它初始化
延时函数的初始化借用了别人的代码,就不贴了。
0.96OLED屏幕用到了IIC总线,所以既需要初始化IIC总线,也需要初始化OLED屏幕。其实初始化的代码也不是我写的,借用的。有的读者可能会说,屏幕的初始化好像挺难的,我不知道这些初始化函数怎么写出来。这个问题很好解决——如果厂家不提供初始化的代码,我们不买它的屏幕就行了。谁提供代码,提供技术支持,我们选谁的屏幕。毕竟
另外,对于定时器和蜂鸣器,我也写好了初始化代码。用到再说吧。
带灯按键检测
在LED的初始化函数中,已经点亮了所有的带灯按键。如果按键上的灯不亮,就只能找硬件问题了。接下来写一小段测试代码,判断按键按下能否检测到。
我的思路是,如果按下了某个按键,那么按键对应的LED灯状态翻转。STM32中,让引脚状态翻转其实是比较高阶的操作,原理比较复杂,感兴趣的可以搜下STM32的位带操作。但是用起来很简单,把寄存器当数据操作,假装寄存器可以取反。例如,LED1的状态取反LED1 = !LED1
,如此操作,可以不用关心LED之前是亮是灭,看到的现象就是状态变了。
我们可以写一个switch case语句,根据按键值来操作对应的LED。
while(1)
{
switch(KEY_Scan(0))
{
case PAUSE_PRES:
LED1 = !LED1;
LED2 = !LED2;
break;
case KEY1_PRES:
SLED1 = !SLED1;
break;
case KEY2_PRES:
SLED2 = !SLED2;
break;
case KEY3_PRES:
SLED3 = !SLED3;
break;
case KEY4_PRES:
SLED4 = !SLED4;
break;
case KEY5_PRES:
SLED5 = !SLED5;
break;
case KEY6_PRES:
SLED6 = !SLED6;
break;
case KEY7_PRES:
SLED7 = !SLED7;
break;
case KEY8_PRES:
SLED8 = !SLED8;
break;
default:
break;
}
}
现象就是,上电以后,所有的按键灯都亮,如果按下某个按键,那么按键对应的灯状态翻转。