3. 普通IO反应模块程序流程思路

3.1. 继电器

继电器本质是一个开关,由单片机给出电压信号控制使得内部电磁铁吸合,从而再控制机械部分常开与常闭状态互换,也就是原本与COM断开的常开变导通,原本与COM导通的常闭变断开, 根据以上逻辑来设计需要的连接情况。例如控制大电压的灯泡、电磁铁、电气设备,准备好设备需要的单独供电源, 如果逻辑是单片机触发信号后设备通电动作,设备的供电源通过常开与COM;如果设备持续运行,单片机触发信号后设备断电停止,设备的供电源通过常闭与COM。

更多继电器概念请参考:https://www.zhihu.com/question/37052790

继电器模块

如图所示,模块上有IN1、IN2、IN3、IN4,就是说单片机的四个IO口可以分别控制4个继电器。

注意

继电器上的 J29直流输入5V 是给继电器本身供电使其正常工作,在实际接到用电器的时候需要另外找电源给对应用电器供电。很多人会把继电器错误的理解成一个带开关的电源,只把继电器与用电器接在一起而不接电源。

在编写继电器驱动时,要考虑更改硬件环境的情况。我们把继电器检测引脚相关的宏定义到 “relay.h”文件中, 这样的话在更改或移植的时候只用改宏定义就可以。

将RELAYx_ON定义为使继电器x的控制引脚输出高电平,将RELAYx_ON定义为使继电器x的控制引脚输出低电平。

继电器相关引脚控制定义(以霸道开发板为例)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 #define RELAY1_GPIO_PORT        GPIOB
 #define RELAY1_GPIO_CLK         RCC_APB2Periph_GPIOB
 #define RELAY1_GPIO_PIN         GPIO_Pin_12         //用于控制继电器1

 #define RELAY2_GPIO_PORT        GPIOB
 #define RELAY2_GPIO_CLK         RCC_APB2Periph_GPIOB
 #define RELAY2_GPIO_PIN         GPIO_Pin_13         //用于控制继电器2

 #define RELAY3_GPIO_PORT        GPIOB
 #define RELAY3_GPIO_CLK         RCC_APB2Periph_GPIOB
 #define RELAY3_GPIO_PIN         GPIO_Pin_14         //用于控制继电器3

 #define RELAY4_GPIO_PORT        GPIOB
 #define RELAY4_GPIO_CLK         RCC_APB2Periph_GPIOB
 #define RELAY4_GPIO_PIN         GPIO_Pin_15         //用于控制继电器4

 #define digitalHi(p,i)      {p->BSRR=i;}    //使用寄存器控制,输出为高电平
 #define digitalLo(p,i)      {p->BRR=i;}     //使用寄存器控制,输出低电平

 /* 定义控制IO的宏 */
 #define RELAY1_TOGGLE       digitalToggle(RELAY1_GPIO_PORT,RELAY1_GPIO_PIN) //翻转继电器1的控制引脚电平
 #define RELAY1_ON           digitalHi(RELAY1_GPIO_PORT,RELAY1_GPIO_PIN)     //使继电器1的控制引脚输出高电平
 #define RELAY1_OFF          digitalLo(RELAY1_GPIO_PORT,RELAY1_GPIO_PIN)     //使继电器1的控制引脚输出低电平

 #define RELAY2_TOGGLE       digitalToggle(RELAY2_GPIO_PORT,RELAY2_GPIO_PIN)
 #define RELAY2_ON           digitalHi(RELAY2_GPIO_PORT,RELAY2_GPIO_PIN)
 #define RELAY2_OFF          digitalLo(RELAY2_GPIO_PORT,RELAY2_GPIO_PIN)

 #define RELAY3_TOGGLE       digitalToggle(RELAY3_GPIO_PORT,RELAY3_GPIO_PIN)
 #define RELAY3_ON           digitalHi(RELAY3_GPIO_PORT,RELAY3_GPIO_PIN)
 #define RELAY3_OFF          digitalLo(RELAY3_GPIO_PORT,RELAY3_GPIO_PIN)

 #define RELAY4_TOGGLE       digitalToggle(RELAY4_GPIO_PORT,RELAY4_GPIO_PIN) //翻转继电器4的控制引脚电平
 #define RELAY4_ON           digitalHi(RELAY4_GPIO_PORT,RELAY4_GPIO_PIN)     //使继电器4的控制引脚输出高电平
 #define RELAY4_OFF          digitalLo(RELAY4_GPIO_PORT,RELAY4_GPIO_PIN)     //使继电器4的控制引脚输出低电平

利用上面的宏,编写继电器的初始化函数。

程序在运行的第一件事,就是将用到的外设初始化。这里用普通IO来控制继电器,先对GPIO结构体进行声明,一会需要对其中的成员赋值。 使用RCC_APB2PeriphClockCmd库函数和之前定义的宏开启控制继电器的引脚时钟,GPIO_Pin选择需要控制的引脚号,GPIO_Mode为该引脚的工作模式, 这里设置为推挽输出,GPIO_Speed设置引脚的工作速率,这里定义为50MHZ,配置完一个引脚的结构体成员后就用GPIO_Init函数将其初始化。

其他管脚的配置方式一样,这里就不再赘述。

继电器控制引脚初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* 继电器引脚初始化 */
 void RELAY_GPIO_Config(void)
 {

     GPIO_InitTypeDef GPIO_InitStructure;

     /* 开启控制继电器的引脚时钟 */
     RCC_APB2PeriphClockCmd( RELAY1_GPIO_CLK | RELAY2_GPIO_CLK | RELAY3_GPIO_CLK | RELAY4_GPIO_CLK, ENABLE);

     /*选择要控制的GPIO引脚*/
     GPIO_InitStructure.GPIO_Pin = RELAY1_GPIO_PIN;
     /*设置引脚模式为通用推挽输出*/
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
     /*设置引脚速率为50MHz */
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     /*调用库函数,初始化GPIO*/
     GPIO_Init(RELAY1_GPIO_PORT, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = RELAY2_GPIO_PIN;
     GPIO_Init(RELAY2_GPIO_PORT, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = RELAY3_GPIO_PIN;
     GPIO_Init(RELAY3_GPIO_PORT, &GPIO_InitStructure);

     GPIO_InitStructure.GPIO_Pin = RELAY4_GPIO_PIN;
     GPIO_Init(RELAY4_GPIO_PORT, &GPIO_InitStructure);

     /* 断开所有的继电器 */
     GPIO_ResetBits(RELAY1_GPIO_PORT, RELAY1_GPIO_PIN);
     GPIO_ResetBits(RELAY2_GPIO_PORT, RELAY2_GPIO_PIN);
     GPIO_ResetBits(RELAY3_GPIO_PORT, RELAY3_GPIO_PIN);
     GPIO_ResetBits(RELAY4_GPIO_PORT, RELAY4_GPIO_PIN);
 }

因为GPIO初始化完成之后默认为高电平,这样的话GPIO会使控制器的三极管导通,驱动光耦开关将我们的继电器吸合,所以这里需要将其变为低电平,上电时就不会吸合继电器,继电器驱动电路如下图所示。

3-2 继电器驱动

在我们的主程序中,首先在调用 RELAY_GPIO_Config 函数初始化继电器控制引脚之后,我们还要调用 Key_GPIO_Config 函数初始化按键,因为要用开发板上面的按键来控制这四个继电器的通断。

最后就是在主循环编写判断按键按下后控制继电器的代码。for(;;)是无限循环的一种写法,在里面判断若 KEY1 被按下,则翻转继电器1和2;若 KEY2 被按下,则翻转继电器3和4。

继电器控制例程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
 int main(void)
 {
     RELAY_GPIO_Config(); //继电器控制引脚初始化

     Key_GPIO_Config(); //按键初始化

     for(;;)
     {

         if( Key_Scan(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == KEY_ON )
         {
             RELAY1_TOGGLE;
             RELAY2_TOGGLE;
         }

         if( Key_Scan(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == KEY_ON )
         {
             RELAY3_TOGGLE;
             RELAY4_TOGGLE;
         }
     }
 }

3.2. 光敏

光敏电阻顾名思义就是遇到不同的光强,电阻的阻值不同。

YH-LDR是野火设计的光强传感器,使用一个光敏电阻作为采集源,使用LM393作为电压比较器,可以通过调节模块的滑变来采集不同强度的光强。而且不需要使用ADC,模块输出的就是一个数字量的信号,使操作更简单。

YH-LDR模块
YH-LDR模块的引脚说明

编号

名称

说明

1

VCC

电源正极

2

GND

地线

3

OD

信号输出

模块通过光敏电阻将光强转换成电压信号,输入到LM 393的一个电压比较通道,然后LM393在输出比较结果,可以通过滑变调节比较的电压。有强时DO输出低电平,光弱时DO输出高电平。电源接3V3时高电平时1.4V,此时STM32把此电平识别为低电平, 电源接5V时,DO输出的是2.6V,此时STM32可以识别出是高电平,所以在测试时电源是需要接5V的。

在编写YH-LDR模块驱动时,也要考虑更改硬件环境的情况。我们把YH-LDR模块检测引脚相关的宏定义到 “bsp_idr.h”文件中, 在更改或移植的时候只用改宏定义就可以。

这里YH-LDR模块用到了C13引脚,在头文件中将其定义一下。

bsp_idr.h
1
2
3
4
5
6
 #define    LDR_GPIO_CLK     RCC_APB2Periph_GPIOC  //对应引脚时钟
 #define    LDR_GPIO_PORT    GPIOC
 #define    LDR_GPIO_PIN     GPIO_Pin_13          //YH-LDR模块控制引脚

 #define LDR_ON      1     // 有光
 #define LDR_OFF     0     // 无光

结合宏定义的内容初始化YH-LDR模块,还是先定义一个GPIO结构体,再开启GPIO时钟,GPIO_Pin赋值为LDR_GPIO_PIN(C13引脚), GPIO_Speed赋值为50MHz,GPIO_Mode设置为下拉输入,最后初始化GPIO。

为了使用方便,需要写一个小小的测试函数LDR_Test(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin),这个函数需要传递两个参数,一个引脚端口号,一个引脚号, 函数内部主要实现对管脚的检测,检测管脚就相当于是在检测YH-LDR模块引脚输出的电平信号。

YH-LDR模块相关外设初始化
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 /* YH-LDR模块引脚初始化 */
 void LDR_GPIO_Config(void)
 {
     GPIO_InitTypeDef GPIO_InitStructure;

     /* 开启端口的时钟 */
     RCC_APB2PeriphClockCmd(LDR_GPIO_CLK,ENABLE);

     /* 选择输入的引脚 */
     GPIO_InitStructure.GPIO_Pin = LDR_GPIO_PIN;

     /* 设置引脚速率为50MHz */
     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

     /* 设置光敏输入的引脚为下拉输入 */
     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;

     GPIO_Init(LDR_GPIO_PORT, &GPIO_InitStructure);
 }

 /* 光敏电阻测试 */
 uint8_t LDR_Test(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
 {
     /*检测光敏输入状态 */
     if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == 1 )
     {
         return LDR_OFF;    // 无光
     }
     else
     {
         return LDR_ON;    // 有光
     }
 }

最后是在主函数中编写控制程序。

首先初始化LED外设,关闭LED1,初始化光敏端口,在死循环中使用LDR_Test一直判断C13引脚的状态,低电平LED1亮,高电平LED1灭。

YH-LDR模块控制例程
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
 int main(void)
 {
     /* LED端口初始化 */
     LED_GPIO_Config();
     LED1_OFF;

     /* 光敏端口初始化 */
     LDR_GPIO_Config();

     /* 轮询光敏状态,若有光则点亮灯 */
     while(1)
     {
         if (LDR_Test(LDR_GPIO_PORT,LDR_GPIO_PIN) == LDR_ON)
         {
             LED1_ON;    // 有光灯亮
         }
         else
             LED1_OFF;   // 无光灯灭
     }
 }

注: 这里重点分析YH-LDR模块, LED初始化相关没有贴出,完整源码请参考YH-LDR模块例程。