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会使控制器的三极管导通,驱动光耦开关将我们的继电器吸合,所以这里需要将其变为低电平,上电时就不会吸合继电器,继电器驱动电路如下图所示。
在我们的主程序中,首先在调用 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,模块输出的就是一个数字量的信号,使操作更简单。
编号 |
名称 |
说明 |
---|---|---|
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引脚,在头文件中将其定义一下。
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模块引脚输出的电平信号。
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灭。
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模块例程。