ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

项目:智能台灯+光敏电阻传感器

项目:智能台灯+光敏电阻传感器

模块:光敏电阻传感器

光敏电阻作为一种重要的光敏元件,在各种光控应用中发挥着关键作用,比如光控开关、光敏传感器、光敏报警器等等。其特性在光照强度变化时产生的电阻变化一一光照越强,电阻越小。通过测量光敏电阻的电阻值,我们可以实时监测光照条件的变化,并据此进行相应的控制和反馈。

常见的光敏电阻模块有3脚和4脚两种,3脚的只有DO、、4脚的有DO和AO。

给萌新小伙伴科普下:DO数字开关量输出(0和1)和AO模拟电压输出。

·模块中蓝色的电位器是用于调节阀值,顺时针旋转,阈值会越大,逆时针越小。

上电后电源指示灯亮。

当外界环境光线亮度达不到设定阈值时,DO端输出高电平,开关指示灯灭;

当外界环境光线亮度超过设定阈值时,DO端输出低电平,开关指示灯亮。

环境光线亮度越亮,AO端经过AD转换的数值越小。

项目:智能台灯

项目需求
1. 红外传感器检测是否有人,有人的话实时检测距离,过近则报警;同时计时,超过固定时间则报警;
2. 按键 1 切换工作模式:智能模式、按键模式、远程模式;
3. 智能模式下,根据光照强度自动调整光照档位(低亮、中亮、高亮),没人则自动关灯;
4. 按键模式下,按键 2 可以手动调整光照档位;
5. 远程模式下,可以通过蓝牙控制光照档位、计时等;
6. 按键 3 暂停/开始计时,按键 4 清零计时;
7. OLED 显示各项数据/状态。

硬件清单
蓝牙模块
超声波传感器
红外传感器
光敏电阻传感器
OLED
高功率LED灯
蜂鸣器
KEY × 4
杜邦线
上官二号
ST-Link
USB转TTL

项目框图

项目流程图

代码示例

蜂鸣器、蓝牙、超声波、红外传感器(原中断实验exti)、按键、led(新建高功率led)、光照传感器(原adc实验)、oled、定时器中断。 “品”魔法棒 之后编译会发现有错误,一个个来改,将ia_sensor、light_sensor对应名字都改一下。还有timer重复定义的错误,就是timer文件中有个定时器2的msp函数,然后在超声波传感器中有这个定时器2 的msp函数。先将超声波这里的注释掉,后面再改。

之后timer中也有一部分的led函数,由于led函数现在是空的,所以先注释掉。

beep

只需要改一下引脚即可。PB13。

#include "beep.h" #include "sys.h" //初始化GPIO函数 void beep_init(void) { GPIO_InitTypeDef gpio_initstruct; //打开时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟 //调用GPIO初始化函数 gpio_initstruct.Pin = GPIO_PIN_13; // 蜂鸣器对应的引脚 gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 gpio_initstruct.Pull = GPIO_PULLUP; // 上拉 gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速 HAL_GPIO_Init(GPIOB, &gpio_initstruct); //关闭蜂鸣器 beep_off(); } //打开蜂鸣器的函数 void beep_on(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET); // 拉低蜂鸣器引脚,打开蜂鸣器 } //关闭蜂鸣器的函数 void beep_off(void) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET); // 拉高蜂鸣器引脚,关闭蜂鸣器 }
#ifndef __BEEP_H__ #define __BEEP_H__ void beep_init(void); void beep_on(void); void beep_off(void); #endif

bluetooth

还是串口2的msp函数加入到uart1中。

void HAL_UART_MspInit(UART_HandleTypeDef *huart) { GPIO_InitTypeDef gpio_init_struct; if (huart->Instance == USART1) /* 如果是串口1,进行串口1 MSP初始化 */ { __HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口TX脚时钟 */ __HAL_RCC_USART1_CLK_ENABLE(); /* 使能串口时钟 */ gpio_init_struct.Pin = GPIO_PIN_9; /* 串口发送引脚号 */ gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */ gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */ HAL_GPIO_Init(GPIOA, &gpio_init_struct); gpio_init_struct.Pin = GPIO_PIN_10; /* 串口RX脚 模式设置 */ gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */ HAL_NVIC_EnableIRQ(USART1_IRQn); /* 使能USART1中断通道 */ HAL_NVIC_SetPriority(USART1_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */ __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART1接收中断 */ __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART1总线空闲中断 */ } else if (huart->Instance == USART2) /* 如果是串口2,进行串口2 MSP初始化 */ { __HAL_RCC_GPIOA_CLK_ENABLE(); /* 使能串口TX脚时钟 */ __HAL_RCC_USART2_CLK_ENABLE(); /* 使能串口时钟 */ gpio_init_struct.Pin = GPIO_PIN_2; /* 串口发送引脚号 */ gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用推挽输出 */ gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */ gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* IO速度设置为高速 */ HAL_GPIO_Init(GPIOA, &gpio_init_struct); gpio_init_struct.Pin = GPIO_PIN_3; /* 串口RX脚 模式设置 */ gpio_init_struct.Mode = GPIO_MODE_AF_INPUT; HAL_GPIO_Init(GPIOA, &gpio_init_struct); /* 串口RX脚 必须设置成输入模式 */ HAL_NVIC_EnableIRQ(USART2_IRQn); /* 使能USART2中断通道 */ HAL_NVIC_SetPriority(USART2_IRQn, 3, 3); /* 组2,最低优先级:抢占优先级3,子优先级3 */ __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE); /* 使能UART2接收中断 */ __HAL_UART_ENABLE_IT(huart, UART_IT_IDLE); /* 使能UART2总线空闲中断 */ } }

要添加一个函数,用来远程控制台灯的不同亮度。

#include "sys.h" #include "bluetooth.h" #include "string.h" #include "stdarg.h" UART_HandleTypeDef uart2_handle; /* uart2句柄 */ uint8_t command = 0; /*定义个变量,获取rx_buf的第零个字节*/ uint8_t uart2_rx_buf[UART2_RX_BUF_SIZE]; /* uart2接收缓冲区 */ uint16_t uart2_rx_len = 0; /* uart2接收字符长度 */ /** * @brief 串口1初始化函数 * @param baudrate: 波特率, 根据自己需要设置波特率值 * @retval 无 */ void bt_init(uint32_t baudrate) { /*uart2 初始化设置*/ uart2_handle.Instance = USART2; /* USART */ uart2_handle.Init.BaudRate = baudrate; /* 波特率 */ uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */ uart2_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */ uart2_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */ uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */ uart2_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */ HAL_UART_Init(&uart2_handle); /* HAL_UART_Init()会使能uart2 */ } /** * @brief uart2接收缓冲区清除 * @param 无 * @retval 无 */ void uart2_rx_clear(void) { memset(uart2_rx_buf, 0, sizeof(uart2_rx_buf)); /* 清空接收缓冲区 */ uart2_rx_len = 0; /* 接收计数器清零 */ } /** * @brief 串口2中断服务函数 * @note 在此使用接收中断及空闲中断,实现不定长数据收发 * @param 无 * @retval 无 */ void USART2_IRQHandler(void) { uint8_t receive_data = 0; if(__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_RXNE) != RESET){ /* 获取接收RXNE标志位是否被置位 */ if(uart2_rx_len >= sizeof(uart2_rx_buf)) /* 如果接收的字符数大于接收缓冲区大小, */ uart2_rx_len = 0; /* 则将接收计数器清零 */ HAL_UART_Receive(&uart2_handle, &receive_data, 1, 1000); /* 接收一个字符 */ uart2_rx_buf[uart2_rx_len++] = receive_data; /* 将接收到的字符保存在接收缓冲区 */ } if (__HAL_UART_GET_FLAG(&uart2_handle, UART_FLAG_IDLE) != RESET) /* 获取接收空闲中断标志位是否被置位 */ { printf("bt recv: %s\r\n", uart2_rx_buf); /* 将接收到的数据打印出来 */ command = uart2_rx_buf[0]; /*如果接收1,这是1是字符形式的*/ uart2_rx_clear(); __HAL_UART_CLEAR_IDLEFLAG(&uart2_handle); /* 清除UART总线空闲中断 */ } } //void bt_send(char *send_buf, uint8_t size) //{ // HAL_UART_Transmit(&uart2_handle, (uint8_t *)send_buf, size, 100); //} void bt_send(char * format, ...) { uint8_t send_buf[128] ={0}; va_list arg; va_start(arg, format); vsprintf((char *)send_buf, format, arg); va_end(arg); HAL_UART_Transmit(&uart2_handle, send_buf, sizeof(send_buf), 100); } uint8_t bt_rx_get(void) { uint8_t temp = command; /*防止受到上一次传递信息的干扰,使用中间变量来承接*/ command = '9'; /*承接之后给command一个无意义的值*/ return temp; }
#ifndef __BLUETOOTH_H__ #define __BLUETOOTH_H__ #include "stdio.h" #include "sys.h" /* UART收发缓冲大小 */ #define UART2_RX_BUF_SIZE 128 #define UART2_TX_BUF_SIZE 64 void bt_init(uint32_t bound); /* 串口初始化函数 */ //void bt_send(char *send_buf, uint8_t size); void bt_send(char * format, ...); uint8_t bt_rx_get(void); #endif

hcsr04

这模块只需要在.h文件中改引脚即可 PB6--PA11 PB7--PA12。仅仅将开机时钟函数加入init中。

#include "hcsr04.h" #include "delay.h" TIM_HandleTypeDef tim2_handle = {0}; //定时器初始化函数 void tim2_init(void) { __HAL_RCC_TIM2_CLK_ENABLE(); tim2_handle.Instance = TIM2; tim2_handle.Init.Prescaler = 72 - 1; tim2_handle.Init.Period = 65536 - 1; tim2_handle.Init.CounterMode = TIM_COUNTERMODE_UP; tim2_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_Base_Init(&tim2_handle); } //msp函数 //void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) //{ // if(htim->Instance == TIM2) // { // __HAL_RCC_TIM2_CLK_ENABLE(); // } //} void tim2_start(void) { HAL_TIM_Base_Start(&tim2_handle); } void tim2_stop(void) { HAL_TIM_Base_Stop(&tim2_handle); } uint16_t tim2_get_cnt(void) { return __HAL_TIM_GetCounter(&tim2_handle); } void tim2_set_cnt(uint16_t val) { __HAL_TIM_SetCounter(&tim2_handle, val); } void hcsr04_gpio_init(void) { GPIO_InitTypeDef gpio_initstruct; //打开时钟 TRIG_GPIO_CLK_ENABLE(); ECHO_GPIO_CLK_ENABLE(); //初始化Trig引脚 gpio_initstruct.Pin = TRIG_PIN; gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; gpio_initstruct.Pull = GPIO_NOPULL; gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(TRIG_PORT, &gpio_initstruct); gpio_initstruct.Pin = ECHO_PIN; gpio_initstruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(ECHO_PORT, &gpio_initstruct); } void hcsr04_init(void) { tim2_init(); hcsr04_gpio_init(); } float hcsr04_get_length(void) { uint16_t total_time = 0; float distance = 0; //1. Trig,给Trig端口至少10us的高电平 TRIG_HIGH(); delay_us(15); TRIG_LOW(); //2. Echo引脚,由低电平跳转到高电平,表示开始发送波 //波发出去的那一下,开始启动定时器 while(ECHO_STATUS() == GPIO_PIN_RESET); tim2_start(); tim2_set_cnt(0); //3. Echo,由高电平跳转回低电平,表示波回来了 //波回来的那一下,我们开始停止定时器,计算出中间经过多少时间 while(ECHO_STATUS() == GPIO_PIN_SET); tim2_stop(); //4. 计算出中间经过多少时间 total_time = tim2_get_cnt(); //5. 距离 = 速度(343m/s) * 时间 / 2 distance = total_time * 0.01715; return distance; }
#ifndef __HCSR04_H__ #define __HCSR04_H__ #include "sys.h" #define TRIG_PORT GPIOA #define TRIG_PIN GPIO_PIN_11 #define TRIG_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define TRIG_HIGH() HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET) #define TRIG_LOW() HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET) #define ECHO_PORT GPIOA #define ECHO_PIN GPIO_PIN_12 #define ECHO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define ECHO_STATUS() HAL_GPIO_ReadPin(ECHO_PORT, ECHO_PIN) void hcsr04_init(void); float hcsr04_get_length(void); #endif

light_sensor

光敏传感器初始化,实际上也就是adc的初始化。还有就是获取返回值的函数为什么是通道1呢。看一下我们的接线是PA1,看下手册对应的就是ADC1的通道1。就添加了这两个函数。

#include "adc.h" ADC_HandleTypeDef adc_handle = {0}; //全局变量句柄 init 和 msp都用。 void adc_init(void) { adc_handle.Instance = ADC1; //基地址 adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //数据对齐 右对齐 adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE; //是否扫描,本次只有光敏传感器一个通道 adc_handle.Init.ContinuousConvMode = DISABLE; //单次转换or连续转换 adc_handle.Init.NbrOfConversion = 1; // 转换的个数 只用一个通道 就1 adc_handle.Init.DiscontinuousConvMode = DISABLE; // 间断模式 adc_handle.Init.NbrOfDiscConversion = 0; //间断模式个数 没用0 adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START; //触发转换的外部事件 软件触发 HAL_ADC_Init(&adc_handle); //初始化库函数 上方为句柄对应的成员变量 HAL_ADCEx_Calibration_Start(&adc_handle); //校准函数一般写在init函数中 } void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) { if(hadc->Instance == ADC1) //防止其他adc在用此msp函数,判断一下是否adc1 { RCC_PeriphCLKInitTypeDef adc_clk_init = {0}; //对应句柄的定义 GPIO_InitTypeDef gpio_init_struct = {0}; //GPIO对应句柄 __HAL_RCC_ADC1_CLK_ENABLE(); //ADC1时钟使能 __HAL_RCC_GPIOA_CLK_ENABLE(); //GPIOA时钟使能 gpio_init_struct.Pin = GPIO_PIN_1; //PA1 gpio_init_struct.Mode = GPIO_MODE_ANALOG; //ADC DAC的特供模式 模拟量输入 HAL_GPIO_Init(GPIOA, &gpio_init_struct); adc_clk_init.PeriphClockSelection = RCC_PERIPHCLK_ADC;//外设的时钟选择 adc_clk_init.AdcClockSelection = RCC_ADCPCLK2_DIV6;//adc预分频器 12M对应6分频 HAL_RCCEx_PeriphCLKConfig(&adc_clk_init); //ADC时钟配置函数 参数为结构体句柄 } } /*配置adc的通道 参数分别:操作哪个adc(句柄)、具体哪个通道、通道放在哪个顺序位置、采集时间*/ void adc_channel_config(ADC_HandleTypeDef* hadc, uint32_t ch, uint32_t rank, uint32_t stime) { ADC_ChannelConfTypeDef adc_ch_config = {0};//第二个句柄定义 adc_ch_config.Channel = ch; //通道 adc_ch_config.Rank = rank; //序列 adc_ch_config.SamplingTime = stime; //采样时间 HAL_ADC_ConfigChannel(hadc, &adc_ch_config); // 参数为两个句柄 } /*获取adc的值(获取哪个通道)*/ uint32_t adc_get_result(uint32_t ch)/*获取结果*/ { /*调用配置通道的函数*/ adc_channel_config(&adc_handle, ch, ADC_REGULAR_RANK_1, ADC_SAMPLETIME_239CYCLES_5); HAL_ADC_Start(&adc_handle); /*开始转换*/ HAL_ADC_PollForConversion(&adc_handle, 10); /*等待检测完成。句柄、超时时间*/ return (uint16_t)HAL_ADC_GetValue(&adc_handle);/*就是获取DR寄存器里的值*/ } /*ADC1转换的结果在DR寄存器中的低16位,强制位uint16_t就可以直接获取,高16位有效的是ADC2的结果*/ void ls_init(void) { adc_init(); } uint8_t ls_get_value(void) /*光照越强 电压电阻越小,但我们希望光照越强 获取到的值越大,用减法*/ { return (uint8_t)(100 - (adc_get_result(ADC_CHANNEL_1) * 100 /4096));/*变100以内的数**/ } /*adc获取的值范围是0-4096,因为adc是12位的最大4096*/
#ifndef __ADC_H__ #define __ADC_H__ #include "sys.h" void adc_init(void); uint32_t adc_get_result(uint32_t ch); void ls_init(void); uint8_t ls_get_value(void); #endif

oled

引脚不用改 就是PB6和PB7。再就是取模到font.h的事情。const unsigned char lamp_16x16[][32]

:、模、式、智、能、按、键、远、程、有、无、人、亮、度、低、中、高、关、计、时。

#include "oled.h" #include "delay.h" #include "font.h" void oled_gpio_init(void) { GPIO_InitTypeDef gpio_initstruct; OLED_I2C_SCL_CLK(); OLED_I2C_SDA_CLK(); gpio_initstruct.Pin = OLED_I2C_SCL_PIN; gpio_initstruct.Mode = GPIO_MODE_OUTPUT_PP; gpio_initstruct.Pull = GPIO_PULLUP; gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(OLED_I2C_SCL_PORT, &gpio_initstruct); gpio_initstruct.Pin = OLED_I2C_SDA_PIN; HAL_GPIO_Init(OLED_I2C_SDA_PORT, &gpio_initstruct); } void oled_i2c_start(void) { OLED_SCL_SET(); OLED_SDA_SET(); OLED_SDA_RESET(); OLED_SCL_RESET(); } void oled_i2c_stop(void) { OLED_SCL_SET(); OLED_SDA_RESET(); OLED_SDA_SET(); } void oled_i2c_ack(void) { OLED_SCL_SET(); OLED_SCL_RESET(); } void oled_i2c_write_byte(uint8_t data) { uint8_t i, tmp; tmp = data; for(i = 0; i < 8; i++) { if((tmp & 0x80) == 0x80) OLED_SDA_SET(); else OLED_SDA_RESET(); tmp = tmp << 1; OLED_SCL_SET(); OLED_SCL_RESET(); } } void oled_write_cmd(uint8_t cmd) { oled_i2c_start(); oled_i2c_write_byte(0x78); oled_i2c_ack(); oled_i2c_write_byte(0x00); oled_i2c_ack(); oled_i2c_write_byte(cmd); oled_i2c_ack(); oled_i2c_stop(); } void oled_write_data(uint8_t data) { oled_i2c_start(); oled_i2c_write_byte(0x78); oled_i2c_ack(); oled_i2c_write_byte(0x40); oled_i2c_ack(); oled_i2c_write_byte(data); oled_i2c_ack(); oled_i2c_stop(); } void oled_init(void) { oled_gpio_init(); delay_ms(100); oled_write_cmd(0xAE); //设置显示开启/关闭,0xAE关闭,0xAF开启 oled_write_cmd(0xD5); //设置显示时钟分频比/振荡器频率 oled_write_cmd(0x80); //0x00~0xFF oled_write_cmd(0xA8); //设置多路复用率 oled_write_cmd(0x3F); //0x0E~0x3F oled_write_cmd(0xD3); //设置显示偏移 oled_write_cmd(0x00); //0x00~0x7F oled_write_cmd(0x40); //设置显示开始行,0x40~0x7F oled_write_cmd(0xA1); //设置左右方向,0xA1正常,0xA0左右反置 oled_write_cmd(0xC8); //设置上下方向,0xC8正常,0xC0上下反置 oled_write_cmd(0xDA); //设置COM引脚硬件配置 oled_write_cmd(0x12); oled_write_cmd(0x81); //设置对比度 oled_write_cmd(0xCF); //0x00~0xFF oled_write_cmd(0xD9); //设置预充电周期 oled_write_cmd(0xF1); oled_write_cmd(0xDB); //设置VCOMH取消选择级别 oled_write_cmd(0x30); oled_write_cmd(0xA4); //设置整个显示打开/关闭 oled_write_cmd(0xA6); //设置正常/反色显示,0xA6正常,0xA7反色 oled_write_cmd(0x8D); //设置充电泵 oled_write_cmd(0x14); oled_write_cmd(0xAF); //开启显示 } void oled_set_cursor(uint8_t x, uint8_t y) { oled_write_cmd(0xB0 + y); oled_write_cmd((x & 0x0F) | 0x00); oled_write_cmd(((x & 0xF0) >> 4) | 0x10); } void oled_fill(uint8_t data) { uint8_t i, j; for(i = 0; i < 8; i++) { oled_set_cursor(0, i); for(j = 0; j < 128; j++) oled_write_data(data); } } void oled_show_char(uint8_t x, uint8_t y, uint8_t num, uint8_t size) { uint8_t i, j, page; num = num - ' '; page = size / 8; if(size % 8) page++; for(j = 0; j < page; j++) { oled_set_cursor(x, y + j); for(i = size / 2 * j; i < size /2 * (j + 1); i++) { if(size == 12) oled_write_data(ascii_6X12[num][i]); else if(size == 16) oled_write_data(ascii_8X16[num][i]); else if(size == 24) oled_write_data(ascii_12X24[num][i]); } } } void oled_show_string(uint8_t x, uint8_t y, char *p, uint8_t size) { while(*p != '\0') { oled_show_char(x, y, *p, size); x += size/2; p++; } } void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N) { uint16_t i, j; for(j = 0; j < 2; j++) { oled_set_cursor(x, y + j); for(i = 16 *j; i < 16 * (j + 1); i++) oled_write_data(lamp_16x16[N][i]); } } void oled_show_init(void) /*界面初始化*/ { oled_fill(0x00); //清屏 oled_show_chinese(10,0,1); //模式: oled_show_chinese(25,0,2); oled_show_chinese(40,0,0); oled_show_chinese(10,2,12); //亮度: oled_show_chinese(25,2,13); oled_show_chinese(40,2,0); oled_show_chinese(25,4,11); // 人 cm oled_show_string(96,4,"cm",16); oled_show_chinese(10,6,18); //计时: oled_show_chinese(25,6,19); oled_show_chinese(40,6,0); oled_show_char(100,6,'s',16); } void oled_show_image(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t *bmp) { uint8_t i, j; for(j = 0; j < height; j++) { oled_set_cursor(x, y + j); for(i = 0; i < width; i++) oled_write_data(bmp[width * j + i]); } }
#ifndef __OLED_H__ #define __OLED_H__ #include "sys.h" #define OLED_I2C_SCL_CLK() __HAL_RCC_GPIOB_CLK_ENABLE(); #define OLED_I2C_SCL_PORT GPIOB #define OLED_I2C_SCL_PIN GPIO_PIN_6 #define OLED_I2C_SDA_CLK() __HAL_RCC_GPIOB_CLK_ENABLE(); #define OLED_I2C_SDA_PORT GPIOB #define OLED_I2C_SDA_PIN GPIO_PIN_7 #define OLED_SCL_RESET() HAL_GPIO_WritePin(OLED_I2C_SCL_PORT, OLED_I2C_SCL_PIN, GPIO_PIN_RESET) #define OLED_SCL_SET() HAL_GPIO_WritePin(OLED_I2C_SCL_PORT, OLED_I2C_SCL_PIN, GPIO_PIN_SET) #define OLED_SDA_RESET() HAL_GPIO_WritePin(OLED_I2C_SDA_PORT, OLED_I2C_SDA_PIN, GPIO_PIN_RESET) #define OLED_SDA_SET() HAL_GPIO_WritePin(OLED_I2C_SDA_PORT, OLED_I2C_SDA_PIN, GPIO_PIN_SET) void oled_init(void); void oled_write_cmd(uint8_t cmd); void oled_write_data(uint8_t data); void oled_fill(uint8_t data); void oled_show_char(uint8_t x, uint8_t y, uint8_t num, uint8_t size); void oled_show_string(uint8_t x, uint8_t y, char *p, uint8_t size); void oled_show_chinese(uint8_t x, uint8_t y, uint8_t N); void oled_show_image(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint8_t *bmp); void oled_show_init(void); #endif

LED

我们的高功率LED要实现的是四个亮度的效果,那么我们怎么实现呢?当然是PWM波,就是之前做过的呼吸灯的那个实验。所以把之前这个实验的pwm.c.h拿来改一改。

我们这里用定时器3通道3 对应PB0。之后再补充控制led的函数。

#include "led.h" #include "oled.h" uint8_t led_level = 0; TIM_HandleTypeDef pwm_handle = {0}; // init函数 void pwm_init(uint16_t arr, uint16_t psc) { TIM_OC_InitTypeDef pwm_config = {0}; pwm_handle.Instance = TIM3; pwm_handle.Init.Prescaler = psc; pwm_handle.Init.Period = arr; pwm_handle.Init.CounterMode = TIM_COUNTERMODE_UP; HAL_TIM_PWM_Init(&pwm_handle); pwm_config.OCMode = TIM_OCMODE_PWM1; pwm_config.Pulse = 0; /*初始占空比0*/ pwm_config.OCPolarity = TIM_OCPOLARITY_HIGH; /*高电平有效 亮灯*/ HAL_TIM_PWM_ConfigChannel(&pwm_handle, &pwm_config, TIM_CHANNEL_3); HAL_TIM_PWM_Start(&pwm_handle, TIM_CHANNEL_3); } //msp函数 void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM3) { GPIO_InitTypeDef gpio_initstruct; //打开时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟 __HAL_RCC_TIM3_CLK_ENABLE(); //调用GPIO初始化函数 gpio_initstruct.Pin = GPIO_PIN_0; // 两个LED对应的引脚 gpio_initstruct.Mode = GPIO_MODE_AF_PP; // 推挽输出 gpio_initstruct.Pull = GPIO_PULLUP; // 上拉 gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速 HAL_GPIO_Init(GPIOB, &gpio_initstruct); } } //修改CCR值的函数 void pwm_compare_set(uint16_t val) { __HAL_TIM_SET_COMPARE(&pwm_handle, TIM_CHANNEL_3, val); } void led_init(void) { pwm_init(500 - 1, 72 - 1); } void led_off(void) { pwm_compare_set(0); led_level = 0; oled_show_chinese(70, 2, 17); } void led_low(void) { pwm_compare_set(150); led_level = 1; oled_show_chinese(70, 2, 14); } void led_medium(void) { pwm_compare_set(300); led_level = 2; oled_show_chinese(70, 2, 15); } void led_high(void) { pwm_compare_set(450); led_level = 3; oled_show_chinese(70, 2, 16); } uint8_t led_leve_get(void) { return led_level; }
#ifndef __PWM_H__ #define __PWM_H__ #include "sys.h" void pwm_init(uint16_t arr, uint16_t psc); void pwm_compare_set(uint16_t val); void led_init(void); void led_off(void); void led_low(void); void led_medium(void); void led_high(void); uint8_t led_leve_get(void); #endif

key

原本的额代码中key只有两个,但这里我们用到的是四个,所以增加两个。

#include "key.h" #include "delay.h" //初始化GPIO void key_init(void) { GPIO_InitTypeDef gpio_initstruct; //打开时钟 KEY1_GPIO_CLK_ENABLE(); KEY2_GPIO_CLK_ENABLE(); KEY3_GPIO_CLK_ENABLE(); KEY4_GPIO_CLK_ENABLE(); //调用GPIO初始化函数 gpio_initstruct.Pin = KEY1_GPIO_PIN; gpio_initstruct.Mode = GPIO_MODE_INPUT; // 输入 gpio_initstruct.Pull = GPIO_PULLUP; // 上拉 gpio_initstruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速 HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_initstruct); gpio_initstruct.Pin = KEY2_GPIO_PIN; HAL_GPIO_Init(KEY2_GPIO_PORT, &gpio_initstruct); gpio_initstruct.Pin = KEY3_GPIO_PIN; HAL_GPIO_Init(KEY3_GPIO_PORT, &gpio_initstruct); gpio_initstruct.Pin = KEY4_GPIO_PIN; HAL_GPIO_Init(KEY4_GPIO_PORT, &gpio_initstruct); } //按键扫描函数 uint8_t key_scan(void) { // static,如果定义在函数内,变量会保留上次调用结束时的值 static uint8_t key_up = 1; //按键松开标志 uint8_t key_val = 0; if(key_up && (KEY1 == 0 || KEY2 == 0 || KEY3 == 0 || KEY4 == 0)) { delay_ms(10); key_up = 0; if(KEY1 == 0) key_val = 1; if(KEY2 == 0) key_val = 2; if(KEY3 == 0) key_val = 3; if(KEY4 == 0) key_val = 4; } else if(KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 == 1) key_up = 1; return key_val; }
#ifndef __KEY_H__ #define __KEY_H__ #include "sys.h" #define KEY1_GPIO_PORT GPIOA #define KEY1_GPIO_PIN GPIO_PIN_4 #define KEY1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) #define KEY2_GPIO_PORT GPIOA #define KEY2_GPIO_PIN GPIO_PIN_5 #define KEY2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) #define KEY3_GPIO_PORT GPIOA #define KEY3_GPIO_PIN GPIO_PIN_6 #define KEY3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) #define KEY4_GPIO_PORT GPIOA #define KEY4_GPIO_PIN GPIO_PIN_7 #define KEY4_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) #define KEY1 HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN) #define KEY2 HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_GPIO_PIN) #define KEY3 HAL_GPIO_ReadPin(KEY3_GPIO_PORT, KEY3_GPIO_PIN) #define KEY4 HAL_GPIO_ReadPin(KEY4_GPIO_PORT, KEY4_GPIO_PIN) void key_init(void); uint8_t key_scan(void); #endif

ia_sensor

红外传感器,之前使用中断的方式来写的,我们要做的事情呢,是希望能够一直检测到有人,中断的话只会检测到从无到有的这么一个状态。有人一直做是不会持续触发的。这里是PB12

#include "ia_sensor.h" #include "sys.h" #include "delay.h" #include "led.h" uint8_t ia_flag = FALSE; /*是否有人标志*/ void ia_init(void) { GPIO_InitTypeDef gpio_initstruct; //打开时钟 __HAL_RCC_GPIOB_CLK_ENABLE(); // 使能GPIOB时钟 //调用GPIO初始化函数 gpio_initstruct.Pin = GPIO_PIN_12; // 对应的引脚 gpio_initstruct.Mode = GPIO_MODE_INPUT; gpio_initstruct.Pull = GPIO_PULLUP; // 上拉 HAL_GPIO_Init(GPIOA, &gpio_initstruct); } uint8_t ia_flag_get(void) { if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET) return TRUE; else return FALSE; }
#ifndef __EXTI_H__ #define __EXTI_H__ #include "sys.h" void ia_init(void); uint8_t ia_flag_get(void); #define TRUE 1 #define FALSE 0 #endif

timer

只用到计时的功能,这里改成定时器4,、定时器2在超声波那里用了。

再封装几个函数,启动、暂停、清理、翻转定时器(跑的时候 就让停,停的时候就启动)。

timer_init到时候设置成arr=10000 psc=7200,就是一秒钟一个中断 记一个数。

#include "timer.h" #include "led.h" TIM_HandleTypeDef timer_handle = {0}; uint32_t sit_time = 0; /*坐下的时间*/ uint8_t timer_running = 0; /*是否在运行的标志*/ //定时器初始化函数 void timer_init(uint16_t arr, uint16_t psc) { timer_handle.Instance = TIM4; timer_handle.Init.Prescaler = psc; timer_handle.Init.Period = arr; timer_handle.Init.CounterMode = TIM_COUNTERMODE_UP; timer_handle.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; HAL_TIM_Base_Init(&timer_handle); // HAL_TIM_Base_Start_IT(&timer_handle); } //msp函数 void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM4) { __HAL_RCC_TIM4_CLK_ENABLE(); HAL_NVIC_SetPriority(TIM4_IRQn, 2, 2); HAL_NVIC_EnableIRQ(TIM4_IRQn); } } //中断服务函数 void TIM4_IRQHandler(void) { HAL_TIM_IRQHandler(&timer_handle); } //更新中断回调函数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM4) { sit_time++; // printf("sit_time: %d\r\n", sit_time); // led1_toggle(); } } void timer_start(void) { HAL_TIM_Base_Start_IT(&timer_handle); timer_running = 1; } void timer_stop(void) { HAL_TIM_Base_Stop(&timer_handle); timer_running = 0; } void timer_toggle(void) { if(timer_running) timer_stop(); else timer_start(); } uint32_t sit_time_get(void) { return sit_time; } void sit_time_set(uint32_t value) { sit_time = value; }
#ifndef __TIMER_H__ #define __TIMER_H__ #include "sys.h" void timer_init(uint16_t arr, uint16_t psc); void timer_start(void); void timer_stop(void); void timer_toggle(void); uint32_t sit_time_get(void); void sit_time_set(uint32_t value); #endif

main

前面已经把所有的模块的代码都已经封装好了,接下来的话呢 就可以开始main函数,主要是业务逻辑的代码,根据之前的流程图来编写。

首先就是要先把所有的头文件包含进去 ,之后init函数都初始化上。然后根据流程图的顺序来写

#include "sys.h" #include "delay.h" #include "uart1.h" #include "led.h" #include "beep.h" #include "bluetooth.h" #include "hcsr04.h" #include "ia_sensor.h" #include "key.h" #include "light_sensor.h" #include "oled.h" #include "timer.h" enum lamp_mode { AUTO_MODE = 0, MANUAL_MODE, REMOTE_MODE }; int main(void) { HAL_Init(); /* 初始化HAL库 */ stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ // led_init(); /* 初始化LED灯 */ uart1_init(115200); led_init(); beep_init(); bt_init(9600); //蓝牙模块默认波特率9600 不是115200 hcsr04_init(); ia_init(); key_init(); ls_init(); oled_init(); timer_init(10000 - 1, 7200 - 1); //一秒钟 中断一次 printf("hello world!\r\n"); oled_show_init(); //首先进行屏幕的初始化 uint8_t person_flag = 0, first_loop = 1, dis = 0, dis_str[3] = {0}, sit_time_str[3] = {0}, key_num = 0, mode = AUTO_MODE, light_value = 0, led_level = 0; while(1) { person_flag = ia_flag_get(); //判断是否有人 if(person_flag == TRUE) //有人 { oled_show_chinese(10, 4, 9); if(first_loop) //保证只第一次计时一次 不会一直while计时 { first_loop = 0; timer_start(); //开启定时器 计时 } dis = hcsr04_get_length(); //有人则一直获取距离 if(dis < 10) beep_on(); else beep_off(); if(sit_time_get() >= 5) beep_on(); } else //判断无人 { oled_show_chinese(10, 4, 10); timer_stop(); //定时器停止 sit_time_set(0); //定时器计数清0 first_loop = 1; //使下一次来人还会打开定时器 beep_off(); dis = 0; } //%3d表示按十进制整数输出,至少占 3 个字符宽度 sprintf((char *)dis_str, "%3d", dis); //oled只能显示字符 需要sprintf oled_show_string(60, 4, (char *)dis_str, 16); sprintf((char *)sit_time_str, "%3d", sit_time_get()); //显示计时时间 oled_show_string(60, 6, (char *)sit_time_str, 16); key_num = key_scan(); if(key_num == 1) { if(mode++ > 1) //先取值判断之后再++,最终取到的是012. mode = AUTO_MODE; //=0 } else if(key_num == 3) //暂停定时器 就是翻转 { timer_toggle(); } else if(key_num == 4) //停止并清零定时器 { timer_stop(); sit_time_set(0); } switch(mode) { case AUTO_MODE: //智能调节灯光 { oled_show_chinese(60, 0, 3); //智 oled_show_chinese(75, 0, 4); //能 if(person_flag == TRUE) //有人再亮灯 { light_value = ls_get_value(); if(light_value < 40) led_high(); else if(light_value >= 40 && light_value <= 70) led_medium(); else if(light_value > 70) led_low(); } else led_off(); break; } case MANUAL_MODE: { oled_show_chinese(60, 0, 5); //按 oled_show_chinese(75, 0, 6); //键 if(key_num == 2) { led_level = led_leve_get(); //获取当前的档位 if(led_level++ > 2) //如果为3 恢复0 led_level = 0; switch(led_level) { case 0: led_off(); break; case 1: led_low(); break; case 2: led_medium(); break; case 3: led_high(); break; default: break; } } break; } case REMOTE_MODE: { oled_show_chinese(60, 0, 7); //远 oled_show_chinese(75, 0, 8); //程 switch(bt_rx_get() - '0') /*传过来是字符类型的,要变成数字,有函数atoi*/ { /*或者直接减去ASCII码的字符0*/ case 0: led_off(); break; case 1: led_low(); break; case 2: led_medium(); break; case 3: led_high(); break; case 4: timer_start(); break; case 5: timer_stop(); break; case 6: timer_stop(); sit_time_set(0); break; default: break; } break; } default: break; } delay_ms(20); } }
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进