前言
ESP32-C3模组是4月初发布上线的一款双模(2.4GWiFi+BLE5.0)的通信模块,博主手上的是一款外置2M Flash的型号ESP32-C3F:
本文是在Linux 开发环境用的是乐鑫的ESP-IDF的master分 支的SDK基础上做的二次开发。 所以需要准备的软件:
Linux 开发环境(当然Windos也是可以的,请参考: ESP-IDF编程指南.)
ESP-IDF: master.(ESP-IDF的使用请参考 ESP-IDF编程指南.)
注意: 在进行配置menuconifg 的时候需要把 Revision 设置为Rev2
硬件准备:
1、ESP32-C3F小开发板
2、VB01离线语音模块
3、WS2812 RGB灯条
一、新建工程文件
把ESP-IDF中的一个空例程复制到自己工程目录中,空例程在ESP-IDF中的路径如下:
.../esp=idf/examples/get-started/sample_project
主函数文件就在该目录下的:main文件夹中的main.c
二、WS2812RGB驱动
WS2812的驱动其实乐鑫的ESP-IDF中就做有相关的例程:…/examples/peripherals/rmt/led_strip,这个例程使用了RMT红外驱动,还连接了les_strip 驱动库(库路径:…/examples/common_components/led_strip)。但是那个例程无法驱动WS2812灯条,但是它的库却可以使用,所以要移植一下这个库。
1.led_strip 驱动库移植
在main文件夹的同级目录下创建一个名为compornents的文件夹:
mkidr compornents
然后把/examples/common_components/ 下的 led_strip 文件夹全部复制到刚刚创建的compornents文件夹中 。并且在main.c中引用以下头文件:
#include "driver/rmt.h"
#include "led_strip.h"
#include "esp_system.h"
#include "esp_log.h"
2.初始化WS2812 灯条
定义RMT发送管脚和频道及灯珠个数的宏
#define RMT_TX_NUM 3 //发送口
#define RMT_TX_CHANNEL RMT_CHANNEL_0//发送频道
#define LED_STRIP_NUM 24//灯珠数量
void init_led()
{
rmt_config_t config = RMT_DEFAULT_CONFIG_TX(RMT_TX_NUM, RMT_TX_CHANNEL);
// set counter clock to 40MHz
config.clk_div = 2;
ESP_ERROR_CHECK(rmt_config(&config));
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0));
// install ws2812 driver
led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(24, (led_strip_dev_t)config.channel);
strip = led_strip_new_rmt_ws2812(&strip_config);
if (!strip)
{
ESP_LOGE(TAG, "install WS2812 driver failed");
}
// Clear LED strip (turn off all LEDs)
ESP_ERROR_CHECK(strip->clear(strip, 100));
}
3.设置RGB颜色的函数
缓存颜色的结构体
struct WS2812_COLOR{
uint32_t red;
uint32_t green ;
uint32_t blue;
};
struct WS2812_COLOR WS2812_RGB;
void set_rgb(uint16_t Red, uint16_t Green, uint16_t Blue)
{
for (int i = 0; i < LED_STRIP_NUM; i++)
{
strip->set_pixel(strip, i, Red, Green, Blue);//设置颜色
}
WS2812_RGB.red = Red;
WS2812_RGB.green = Green;
WS2812_RGB.blue = Blue;
strip->refresh(strip, 10);
}
三、UART 串口驱动与VB01通信
UART通信同样也可以使用ESP-IDF的example,参考的例程在:examples/peripherals/uart/uart_async_rxtxtasks 目录之下。这个例程使用了FreeRTOS来管理UART的发送函数和接收函数,博主也照样使用了freeRTOS ,UART驱动这里只是使用了FreeRTOS 创建任务,并没有使用太多FreeRTOS的各种功能,所以对于不太熟悉FreeRTOS 的同学也不要怕。
1.串口初始化配置
同样的,先要引用一下相关头文件
/*********RTOS Handle-file****************/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
/*********UART Handle-file*****************/
#include "driver/gpio.h"
#include "driver/uart.h"
串口初始化配置函数:
void UART_Init(void){
const uart_config_t uart_config = {
.baud_rate =38400, //波特率
.data_bits = UART_DATA_8_BITS,//数据位
.parity = UART_PARITY_DISABLE,//校验位
.stop_bits = UART_STOP_BITS_1,//停止位
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,//流控制
.source_clk = UART_SCLK_APB,//时钟
};
// We won't use a buffer for sending data.
uart_driver_install(UART_NUM_1, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
uart_param_config(UART_NUM_1, &uart_config);
uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
}
2.串口接收任务创建
在主函数中初始化UART之后,就创建串口的接收数据的任务,提高数据接收的速度:
//开启串口接收任务
xTaskCreate(uartRxTask, "uart_rx_task", 1024*3, NULL, 4, NULL);
任务函数:
static void uartRxTask(void *arg)
{
static const char *RX_TASK_TAG = "RX_TASK";
esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);
uint8_t *data=(uint8_t *)malloc(RX_BUF_SIZE+1);
while (1) {
const int rxBytes = uart_read_bytes(UART_NUM_1, data, RX_BUF_SIZE, 1000 / portTICK_RATE_MS);
if (rxBytes > 0) {
data[rxBytes] = 0;
ESP_LOGI(RX_TASK_TAG, "Read %d bytes: '%s'", rxBytes, data);
memset(cJson_data, 0, sizeof(cJson_data));
uartControlLedStrip(uartDataHandle(data)); //设置灯条颜色
ESP_LOG_BUFFER_HEXDUMP(RX_TASK_TAG, data, rxBytes, ESP_LOG_INFO);
}
}
free(data);
}
说明:
uartControlLedStrip函数和uartDataHandle函数是博主自己写一个串口数据处理函数,目的是把串口接收的数据中提取控制RGB的指令或者数据,各位同学可通过自己的需求去写这样的函数,比如提取指令ID号,只是用一些字符串操作的函数就可以了。
四、WiFi 配置连接
WiFi的配置连接极为简单,不需要些配置函数,也不需要写初始化函数,需要连接的WiFi名称和密码都可以通过menuconfig来配置。因为ESP-IDF的例程中,已经写好了相关的配置文件,只需要简单的修改就可以配置连接WiFi。
1.引入头文件并使用几个函数
(1)头文件的引用
/**********WiFI Handle-file**************/
#include "esp_netif.h"
#include "protocol_examples_common.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
(2)在main()中使用以下函数
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
2.修改CMakefile.txt 文件
在工程文件目录下,打开CMakefile文件并加入以下内容:
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
3.运行idf.py menuconfig
idf.py flash //烧录程序进ESP32C3
idf.py monitor //监控串口
获取到IP地址,连接成功。
五、MQTT 配置连接阿里云
1.阿里云接入要点
本项目是使用阿里云远程控制RGB灯条,所以需要在阿里云物联网平台创建响应的产品和设备。创建方式请参考: 阿里云创建产品与设备.
除此之外,还需要了解阿里云连接所需要的三元组与MQTT客户端和密码的关系。这个请参考:安信可ESP32-S 模组AT固件连接阿里云物联网平台使用文档.中的一机一密接入。
2.配置MQTT接入阿里云物联网平台
博主是使用创建RTOS任务的方式来配置MQTT,因为在配置的过程中需要引入MQTT事件回调函数,该函数可以负责处理各个事件所要执行的动作,比如MQTT连接成功事件:MQTT_EVENT_CONNECTED;订阅成功事件:MQTT_EVENT_SUBSCRIBED等。当然收到数据事件也在其中。
当MQTT有一个事件被触发的时候,就会跳到事件回调函数去执行相关事件下的动作。所以创建任务来管理是很有必要的。
(1)头文件
/***********MQTT Handle-file************/
#include "mqtt_client.h"
#include "lwip/sockets.h"
#include "lwip/dns.h"
#include "lwip/netdb.h"
(2) MQTT配置
void TaskXMqttRecieve(void *p)
{
//连接的配置参数
#if MQTT_LINK_Aliyum
sprintf(MQTT_LINK_USERNAME,"%s&%s",MQTT_DeviceName,MQTT_ProductKey);
#endif
esp_mqtt_client_config_t mqtt_cfg = {
.host = MQTT_LINK_HOST, //连接的域名 ,请务必修改为您的
.port = MQTT_LINK_PORT, //端口,请务必修改为您的
.username = MQTT_LINK_USERNAME, //用户名,请务必修改为您的
.password = MQTT_LINK_PASS, //密码,请务必修改为您的
.client_id = MQTT_LINK_CLIENT_ID,
.event_handle = MqttCloudsCallBack, //设置回调函数
.keepalive = 120, //心跳
.disable_auto_reconnect = false, //开启自动重连
.disable_clean_session = false, //开启 清除会话
};
client = esp_mqtt_client_init(&mqtt_cfg);
esp_mqtt_client_start(client);
vTaskDelete(NULL);
}
博主自己定义的宏
#define MQTT_LINK_HOST "www.baidu.com" //域名:改成自己的域名
#define MQTT_LINK_PORT 1883//端口
//是否开启发布 1 为开启发布 0:关闭发布
/***** 阿里云设备三元组 ************/
#define MQTT_DeviceName "xxxxx"//设备名:改成你们自己的域名
#define MQTT_ProductKey "xxxx"//
char MQTT_LINK_USERNAME[128];
#define MQTT_LINK_PASS "xxxxxxxxxxxxx" //加过hmacmd5加密后的密码
#define MQTT_LINK_CLIENT_ID "改成自己的设备名|securemode=3,signmethod=hmacmd5|"//客户端ID 阿里云
域名查看方式:注意看鼠标箭头
只有用户名,密码,MQTT客户端及域名都正确才能连上阿里云,物联网平台的设备才会是在线状态
如果一直不在线,请仔细阅读:安信可ESP32-S 模组AT固件连接阿里云物联网平台使用文档.中的一机一密接入。
3.主题订阅
如果通过阿里云控制设备,就必须订阅阿里云的设备Topic,
先查看以下阿里云的产品 ——>Topic类列表——>属性Topic 中的属性设置。
Topic:/sys/g6oroz0VLBN/${deviceName}/thing/service/property/set
这个Topic中,
“g6oroz0VLBN” 是产品的:ProductKey
“${deviceName}” 是设备名:DeviceName
比如现有有以下设备信息:ProductKey = abcdf12;DeviceName=asdvsa。那么该Topic就是:/sys/abcdf12/asdvsa/thing/service/property/set。
订阅主题的代码:
sprintf(MqttTopicSub, "/sys/%s/%s/thing/service/property/set",MQTT_ProductKey,MQTT_DeviceName);
ESP_LOGI(TAG, "MqttTopicSub: %s", MqttTopicSub);
//ESP_LOGI(TAG, "MqttTopicPub: %s", MqttTopicPub);
ESP_ERROR_CHECK(esp_event_loop_create_default());
4.事件回调函数中的数据接收事件
case MQTT_EVENT_DATA:
{
printf("TOPIC=%.*s \r\n", event->topic_len, event->topic);
printf("DATA=%.*s \r\n\r\n", event->data_len, event->data);
//发送数据到队列
struct __User_data *pTmper;
memset(&user_data,0,sizeof(struct __User_data));
sprintf(user_data.allData, "%.*s",event->data_len,event->data);
pTmper = &user_data;
user_data.dataLen = event->data_len;
//把数据发送到消息队列
xQueueSend(ParseJSONQueueHandler, (void *)&pTmper, portMAX_DELAY);
break;
}
5.消息队列处理函数
void Task_ParseJSON(void *pvParameters)
{
printf("[SY] Task_ParseJSON_Message creat ... \n");
while (1)
{
struct __User_data *pMqttMsg;
printf("Task_ParseJSON_Message xQueueReceive wait [%d] ... \n", esp_get_free_heap_size());
xQueueReceive(ParseJSONQueueHandler, &pMqttMsg, portMAX_DELAY);
printf("Task_ParseJSON_Message xQueueReceive get [%s] ... \n", pMqttMsg->allData);
首先整体判断是否为一个json格式的数据
cJSON *pJsonRoot = cJSON_Parse(pMqttMsg->allData);
//如果是否json格式数据
if (pJsonRoot == NULL)
{
printf("[SY] Task_ParseJSON_Message xQueueReceive not json ... \n");
goto __cJSON_Delete;
}
cJSON *pJsonParams = cJSON_GetObjectItem(pJsonRoot,"params");
if(pJsonParams==NULL){
printf("pJsonParams NULL\r\n");
goto __cJSON_Delete;
}
cJSON *pJsonGRB =cJSON_GetObjectItem(pJsonParams,"RGB");
if(pJsonGRB==NULL) goto __cJSON_Delete;
cJSON *pJSON_Item_Red = cJSON_GetObjectItem(pJsonGRB, "R");
cJSON *pJSON_Item_Gree = cJSON_GetObjectItem(pJsonGRB, "G");
cJSON *pJSON_Item_Blue = cJSON_GetObjectItem(pJsonGRB, "B");
//设置RGB颜色
set_rgb(pJSON_Item_Red->valueint, pJSON_Item_Gree->valueint, pJSON_Item_Blue->valueint);
//播放提示音
if(WS2812_RGB.red!=0&&WS2812_RGB.green!=0&&WS2812_RGB.blue!=0);
else uart_write_bytes(UART_NUM_1,"AT+PLAY=21\r\n",13);//播放:已经打开灯了
if(WS2812_RGB.red!=254&&WS2812_RGB.green!=254&&WS2812_RGB.blue!=254)goto __cJSON_Delete;
else uart_write_bytes(UART_NUM_1,"AT+PLAY=22\r\n",13);//播放:已经关灯了
__cJSON_Delete:
cJSON_Delete(pJsonRoot);
}
}