项目介绍
非常感谢电子森林的fastbond活动。
本次我要设计的产品是智能门廊效果灯。
设计这个产品的原因是由于我恶劣条件的租房环境。加班回家开门满眼深深的黑,拖鞋放钥匙需要摸索一阵,不太方便。
于是想设计本次产品,首先要点亮几个led。再加一点特效,显得有点科技感。为了增加交互感,增加网络控制灯效功能。
选择了ws2812bled,一根线控制所有的led。
按照datasheet手册,数据发送速度可达800Kbps,那能够串在一根线上的led数量就相当可观了。
目前已经实现了网络连接,tcp客户端,和ws2812bled控制的三个模块。能够通过tcp服务端下发一些灯效控制指令,来实现不同的灯效,目前实现的灯效是数字0到9。
目前打板了个样板,计划焊接几个样品,送朋友作为一个小礼物。因为具有网络控制功能,可以实现两个人呼唤对方的小工具。想念对方的时候,点亮一个灯效,做个小玩意还是挺不错的。
项目结构
项目结构如下
esp32模组启动wifi无线模块,连接路由器,这里使用的是官网的esptouch协议配网。
创建tcp客户端,用于连接tcp服务器。服务器接收到板子,并下发控制命令,用于设置led灯板的灯效。
对led的控制使用了rmt控制器,用于灯效的控制数据编码并通过leddta发送编码,控制led灯珠的颜色。
目前实现灯效:
数字显示
通过tcp服务器发送数值字符到esp里面,判断第一个字符是数字几,展示数字字符在led面板上。
原理图讲解
原理图主分为两个模块: ws2812b灯板和esp32控制系统。
1. ws2812b灯板
安排了36个led,通过一根线穿起来。
通过一个mos管作为电源的开关,用一个esp32引脚控制开关的通断。leddata作为控制led的数据入口。
2.esp32控制系统
esp32的外围设计了两个按钮和一个led。
用在功能实现的初期测试esp32是否好用。
boot引脚使用排针设计,按上短接帽,用于进入一级boot。
en引脚设计了轻触按键,用于进入下载模式。用于我们通过uart串口下载程序。
esp32开发流程
命令讲解:
配置编译条目
设置编译的目标芯片类型
idf.py set-target esp32
idf.py menuconfig
编译
idf.py build
烧写
idf.py -p COM14 [-b BAUD] flash
idf.py -p COM14 flash
这里浪费了两天时间,如下图所示,已经可以正常进入下载状态。
strapping引脚的电平也是正确的,但是一直出现错误:
A fatal error occurred: Failed to connect to ESP32: No serial data received
偶尔有一次竟然成功,没有触发条件。所以没有得到成功烧写的方法。
后来拔了串口的电源,再次插上就成功了,大概就是官方的文档所说,启动的上电时序有问题吧。
测试场景1:点灯
首先看一下原理图,引脚27
所以配置27引脚就能控制灯了。
使用idf.py munuconfig 配置blink引脚号为27,所以就能让led闪烁了。
测试场景2: 按钮的使用
本项目还会使用到按钮,下面来使用按键的按键事件获取。
首先看原理图。
可以看到使用的引脚是 io22 和io23,先测试引脚22.
首先使用idf.py menuconfig 配置 GPIO_INPUT_IO_0使用的引脚number为22。
拿来官方的通用gpio例程,修改开启中断。代码如下
/* GPIO Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
/**
* Brief:
* This test code shows how to configure gpio and how to use gpio interrupt.
*
* GPIO status:
* GPIO18: output (ESP32C2/ESP32H2 uses GPIO8 as the second output pin)
* GPIO19: output (ESP32C2/ESP32H2 uses GPIO9 as the second output pin)
* GPIO4: input, pulled up, interrupt from rising edge and falling edge
* GPIO5: input, pulled up, interrupt from rising edge.
*
* Note. These are the default GPIO pins to be used in the example. You can
* change IO pins in menuconfig.
*
* Test:
* Connect GPIO18(8) with GPIO4
* Connect GPIO19(9) with GPIO5
* Generate pulses on GPIO18(8)/19(9), that triggers interrupt on GPIO4/5
*
*/
#define GPIO_OUTPUT_IO_0 CONFIG_GPIO_OUTPUT_0
#define GPIO_OUTPUT_PIN_SEL (1ULL<<GPIO_OUTPUT_IO_0)
/*
* Let's say, GPIO_OUTPUT_IO_0=18, GPIO_OUTPUT_IO_1=19
* In binary representation,
* 1ULL<<GPIO_OUTPUT_IO_0 is equal to 0000000000000000000001000000000000000000 and
* 1ULL<<GPIO_OUTPUT_IO_1 is equal to 0000000000000000000010000000000000000000
* GPIO_OUTPUT_PIN_SEL 0000000000000000000011000000000000000000
* */
#define GPIO_INPUT_IO_0 CONFIG_GPIO_INPUT_0
#define GPIO_INPUT_PIN_SEL (1ULL<<GPIO_INPUT_IO_0)
/*
* Let's say, GPIO_INPUT_IO_0=4, GPIO_INPUT_IO_1=5
* In binary representation,
* 1ULL<<GPIO_INPUT_IO_0 is equal to 0000000000000000000000000000000000010000 and
* 1ULL<<GPIO_INPUT_IO_1 is equal to 0000000000000000000000000000000000100000
* GPIO_INPUT_PIN_SEL 0000000000000000000000000000000000110000
* */
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
void app_main(void)
{
//zero-initialize the config structure.
gpio_config_t io_conf = {};
//interrupt of rising edge
io_conf.intr_type = GPIO_INTR_POSEDGE;
//bit mask of the pins, use GPIO4/5 here
io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
//set as input mode
io_conf.mode = GPIO_MODE_INPUT;
//enable pull-up mode
io_conf.pull_up_en = 1;
gpio_config(&io_conf);
//change gpio interrupt type for one pin
gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE);
//install gpio isr service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
printf("Minimum free heap size: %"PRIu32" bytes\n", esp_get_minimum_free_heap_size());
int cnt = 0;
while (1) {
printf("cnt: %d\n", cnt++);
vTaskDelay(1000 / portTICK_PERIOD_MS);
gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2);
}
}
烧录进板子中。
打开串口,效果如下
测试场景3: wifi启用以及连接一个tcp服务器
使用的官方的实例:
esp-idf-v5.1.1\examples\wifi\smart_config
esp-idf-v5.1.1\examples\protocols\sockets\tcp_client
例程smart_config 使用了官方的 esptouch协议发送WiFi账号密码,还挺好用。手机下载官方的esptouch app,截图如下:
强烈推荐感受一下,方便快捷。
实现的方案大概是使用无线协议的广播,通过app把账号密码广播出来,esp模块会接收此条广播,解析账号密码,并尝试连接账号密码。
------
下面来点一个ws2812b灯,使用官方的例程。
官方例程使用了rmt设备来驱动ws2812b。
路径如下:esp-idf-v5.1.1\examples\peripherals\rmt\led_strip。
点灯效果如下:
代码讲解
wifi连接路由器
初始化wifi
static void initialise_wifi(void)
{
ESP_ERROR_CHECK(esp_netif_init());
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
assert(sta_netif);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL) );
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK( esp_wifi_start() );
}
wifi的事件回调
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
;;;;
}
获取到WiFi连接到路由器后,创建tcp客户端
static void smartconfig_example_task(void * parm)
{
.......
while (1) {
........................
initTcp();
.....................
}
}
连接到tcp服务器,创建读取服务器数据无限循环任务,读取到数据后截取第一个字符,判断字符数值并显示到led面板。
0-9 字符显示取模
char numstr[10][20]= {
{0,1,2,3,4,7,8,11,12,15,16,17,18,19,0},
{1,2,4,6,10,14,16,17,18,19,0},
{0,1,2,3,7,8,9,10,11,12,16,17,18,19,0},
{0,1,2,3,7,8,9,10,11,15,16,17,18,19,0},
{0,2,4,6,8,9,10,11,14,18,0},
{0,1,2,3,4,8,9,10,11,15,16,17,18,19,0},
{0,1,2,3,4,8,9,10,11,12,15,16,17,18,19,0},
{0,1,2,3,4,7,11,15,19,0},
{0,1,2,3,4,7,8,9,10,11,12,15,16,17,18,19,0},
{0,1,2,3,4,7,8,9,10,11,15,19,0}
};
读取tcp服务器数据,并解析
void vTask1( void *pvParameters )
{ /* 任务函数的主体一般都是无限循环 */
int len =0;
char rx_buffer[128];
if(!sock) {
ESP_LOGI(TAG, "Socket created, fail");
goto endL;
}
for( ;; )
{
memset(rx_buffer,0,128);
len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
if (len < 0) {
ESP_LOGE(TAG, "recv failed: errno %d", errno);
break;
}
ESP_LOGI(TAG, "------------%s-----------------", rx_buffer);
showNum(rx_buffer[0]);
ESP_LOGI(TAG, "T1----------------\r\n");
vTaskDelay(pdMS_TO_TICKS(2000));
}
endL:
vTaskDelete(NULL);
}
疑难杂症
开发期间,遇到一些问题。
烧写esp32,卡了好久。esp32能够正确进入download模式,就是接收不到esp32的数据,一直下载不成功。解决办法是,换了个串口工具。
期间用了一款hub,然后板子不停的重启。后来查询才知道 esp32会检测失压异常,然后重启。解决办法是,扔掉hub,直接连接到电脑上。
总结报告
这次活动让我受益匪浅。电子森林的活动真的很用心。学习了很多知识,非常感谢电子森林提供的学习平台。比自己瞎头乱学有用的多,方向也定了,使劲去研究学习,时间久了,总能成大佬的。
希望电子森林出一款基于Linux的学习项目。linux能够设计的项目比较多,电子森林出一个项目范围,有个学习的方向,学习起来会高效的多。
再次感谢电子森林。