VSCode+IDF环境使用
注:以下所有代码,本人亲自测试有效!
环境搭建
抱歉!用Arduino,不得劲!改用IDF。(所以,Arduino不再更新…)
环境下载: 链接
环境教程: 链接
ps:乐鑫官网下载离线安装包链接
第一个程序hello world
对于 Hello world 程序,我们可以看到目录结构如下:
- hello_world/
- Makefile
- main/ - hello_world_main.c
- component.mk
- README.md
- sdkconfig(编译生成或者自己添加)
- build/(编译生成)
按照环境教程信息,创建一个hello world工程,会自动生成代码:
#include <stdio.h>
#include <inttypes.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_chip_info.h"
#include "esp_flash.h"
#include "esp_system.h"
void app_main(void)
{
printf("Hello world!\n");
/* Print chip information */
esp_chip_info_t chip_info;
uint32_t flash_size;
esp_chip_info(&chip_info);
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
CONFIG_IDF_TARGET,
chip_info.cores,
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
unsigned major_rev = chip_info.revision / 100;
unsigned minor_rev = chip_info.revision % 100;
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
printf("Get flash size failed");
return;
}
printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024),
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size());
for (int i = 10; i >= 0; i--) {
printf("Restarting in %d seconds...\n", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
printf("Restarting now.\n");
fflush(stdout);
esp_restart();
}
这是我的板子信息:
esptool.py v4.7.0
Serial port COM7
Connecting...
Chip is ESP32-S3 (QFN56) (revision v0.1)
Features: WiFi, BLE, Embedded Flash 4MB (XMC), Embedded PSRAM 2MB (AP_3v3)
Crystal is 40MHz
MAC: dc:54:75:df:72:30
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 460800
Changed.
Configuring flash size...
Flash will be erased from 0x00000000 to 0x00005fff...
Flash will be erased from 0x00010000 to 0x00048fff...
Flash will be erased from 0x00008000 to 0x00008fff...
Compressed 21072 bytes to 13056...
Wrote 21072 bytes (13056 compressed) at 0x00000000 in 0.4 seconds (effective 417.2 kbit/s)...
Hash of data verified.
Compressed 231088 bytes to 122492...
Wrote 231088 bytes (122492 compressed) at 0x00010000 in 2.3 seconds (effective 819.4 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 103...
Wrote 3072 bytes (103 compressed) at 0x00008000 in 0.1 seconds (effective 480.4 kbit/s)...
Hash of data verified.
编译、调试
通过串口来烧写,配置好端口与波特率即可。
如果是支持usb调试移步到:链接
ESP32S3各个管脚使用需要注意的情况说明:链接
快速入门,使用
首先,默认正常有单片机的基础,即可快速入门,否则,建议还是看arduino的方式进行编译。
气氛烘托到这了,这篇文章可以快速的看一下(不要超过1分钟):ESP32 基础篇:ESP-IDF 编程指南
做软件开发正常来说,你应该懂硬件的,总之,不管你懂不懂,先下载下来看看呗~~~:来吧,附上手册下载地址
啥?你说没找到API接口手册?那好,来来来,拿走:链接
freeRTOS创建任务
这个链接写的很好,我就不用再写一遍了, 哈哈哈…FreeRTOS入门–任务
直接上代码:
创建任务:
/*
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级 优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务
*/
xTaskCreate(frt_wifi_Task, "frt_wifi_Task", (4*1024), NULL, 5, NULL);
任务线程:
void frt_wifi_Task( void *pvParameters )
{
for( ;; )
{
vTaskDelay(1000 / portTICK_PERIOD_MS);
printf("111111111111111\n");
}
}
遇坑
什么?你说不停重启?,我不允许你有问题!!!
来来来,移步到:链接
ADC采集程序
#include "driver/adc.h"
void adc_init() {
adc1_config_width(ADC_WIDTH_BIT_12); // 设置ADC分辨率
adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); // 设置通道0的电压衰减
}
uint16_t adc_read(adc1_channel_t channel) {
return adc1_get_raw(channel); // 读取通道的原始ADC值
}
void app_main() {
adc_init(); // 初始化ADC
while (1) {
uint16_t adc_value = adc_read(ADC1_CHANNEL_0); // 读取通道0的ADC值
// 处理adc_value
}
}
很多人问,网上教程都是ADC1,没有ADC2的吗?好吧,拿走:
//读取ADC2的电压值
float adc_voltage = 0.0;
int adc_value;
adc2_get_raw(ADC_CHANNEL_7, ADC_BITWIDTH_12, &adc_value);
adc_voltage = adc_value * (3.3 / 4095); // 根据采样值计算电压值并存储到adc_voltage变量中
return adc_voltage;
ADC2的坑,我已经整理好了,在这里:链接
ESP32S3 ADC DMA使用记录(坑记录)(大牛欢迎给出建议)链接
PWM输出程序
关于PWM,官方的接口采用的LED的渐变作为API,说实话,我感觉太奇怪了,,,
按照官方的说法:
LED 控制器 (LEDC) 主要用于控制 LED,也可产生 PWM 信号用于其他设备的控制。该控制器有 8 路通道,可以产生独立的波形,驱动 RGB LED 等设备。
所以,代码中头文件是#include “driver/ledc.h”。
直接上代码
#include "driver/ledc.h"
ledc_channel_config_t g_ledc_ch; //PWM配置结构体
void pwm_init(void) {
ledc_timer_config_t ledc_timer={
.duty_resolution = LEDC_TIMER_12_BIT,//LEDC_TIMER_13_BIT, //PWM分辨率
.freq_hz = 1000, //频率
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0, //选择定时器
};
ledc_timer_config(&ledc_timer); //设置定时器PWM模式
//PWM通道0配置->GPIO13->LED灯
g_ledc_ch.channel = LEDC_CHANNEL_0; //PWM通道
g_ledc_ch.duty = 1000; //占空比
g_ledc_ch.gpio_num = 13; //IO映射
g_ledc_ch.speed_mode = LEDC_LOW_SPEED_MODE; //速度
g_ledc_ch.timer_sel = LEDC_TIMER_0; //选择定时器
ledc_channel_config(&g_ledc_ch); //配置PWM
}
void z_set_pwm(uint16_t duty)
{
ledc_set_duty(LEDC_LOW_SPEED_MODE,0,duty);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}
串口通讯(UART1 UART2)
ESP32S3一共有3路串口:UART0,UART1,UART2
这里实现UART1和UART2两路,UART0作为调试口这里不做讲解。
ESP32-S3 UART的默认引脚说明:
端口 | 发送(TX) | 接收(RX) |
---|---|---|
UART0 | IO43 | IO44 |
UART1 | IO17 / 任意IO | IO18 / 任意IO |
UART2 | 任意IO | 任意IO |
很明显,UART0是固定的口,这边不再描述。
串口1初始化:
#define Uart_Num UART_NUM_1 //串口通信x 即UART1,UART2...
#define Uart_Tx_Pin_Num GPIO_NUM_1 //tx引脚
#define Uart_Rx_Pin_Num GPIO_NUM_2//rx引脚
#define Uart_RxBuffer_Size (1024)//接收缓存区大小
#define Uart_TxBuffer_Size (1024)//发送缓冲区大小
void z_uart_init(int baudRate)
{
uart_config_t uart_config_InitStructure=
{
.baud_rate=baudRate, //波特率
.data_bits=UART_DATA_8_BITS, //数据位 8位
.flow_ctrl=UART_HW_FLOWCTRL_DISABLE, //流控制 失能
.parity=UART_PARITY_DISABLE,//奇偶校验位 无校验
.stop_bits=UART_STOP_BITS_1,//停止位 1位
.rx_flow_ctrl_thresh=122,
.source_clk = UART_SCLK_DEFAULT//配置时钟,默认时钟
};
uart_param_config(Uart_Num,&uart_config_InitStructure);//设置UsartNumx x 为 0 1 2 以及把上面的参数配置进来
uart_set_pin(Uart_Num,Uart_Tx_Pin_Num,Uart_Rx_Pin_Num,-1,-1); //设置引脚,tx为17,rx为16
uart_driver_install(Uart_Num,Uart_RxBuffer_Size,Uart_TxBuffer_Size,0,NULL,0);//安装uart
}
串口2初始化:(主要区别就是修改了引脚和Uart_Num )
#define Uart_Num UART_NUM_2 //串口通信x 即UART1,UART2...
#define Uart_Tx_Pin_Num GPIO_NUM_17 //tx引脚
#define Uart_Rx_Pin_Num GPIO_NUM_18//rx引脚
#define Uart_RxBuffer_Size (1024)//接收缓存区大小
#define Uart_TxBuffer_Size (1024)//发送缓冲区大小
void z_uart_init(int baudRate)
{
uart_config_t uart_config_InitStructure=
{
.baud_rate=baudRate, //波特率
.data_bits=UART_DATA_8_BITS, //数据位 8位
.flow_ctrl=UART_HW_FLOWCTRL_DISABLE, //流控制 失能
.parity=UART_PARITY_DISABLE,//奇偶校验位 无校验
.stop_bits=UART_STOP_BITS_1,//停止位 1位
.rx_flow_ctrl_thresh=122,
.source_clk = UART_SCLK_DEFAULT//配置时钟,默认时钟
};
uart_param_config(Uart_Num,&uart_config_InitStructure);//设置UsartNumx x 为 0 1 2 以及把上面的参数配置进来
uart_set_pin(Uart_Num,Uart_Tx_Pin_Num,Uart_Rx_Pin_Num,-1,-1); //设置引脚,tx为17,rx为16
uart_driver_install(Uart_Num,Uart_RxBuffer_Size,Uart_TxBuffer_Size,0,NULL,0);//安装uart
}
UART1和UART2初始化是不是很简单?
那么他们的发送和接收也是一样的,我又废话了,继续上代码:
发送:
void z_uart_SendByte(uint8_t byte)
{
uart_write_bytes(Uart_Num,&byte,sizeof(byte));
}
void z_uart_SendArray(uint8_t *Array,uint16_t Length)
{
uint16_t i;
for(i=0;i<Length;i++)
{
z_uart_SendByte(Array[i]);
}
}
接收:
int receiveSize = uart_read_bytes(Uart_Num,receiveDataBuffer,Uart_RxBuffer_Size,(100/portTICK_PERIOD_MS));//接收数据,100ms超时
ps:接收比较简单,但是,你可以放在线程中阻塞接收,蛮方便的。
由于串口太简单,这边不再上测试图片了,看官们自己测试吧,嘿嘿。。。
这篇写的蛮详细,若觉得这边写的太粗旷,也可以看这篇:
链接
触摸外设(等等,为了小白们,值得吗,这也要写?博主,你昏了头了吧???)
博主回答:
值得!!!
但:
未完,待续…(继续挖坑)
wifi扫描
你电脑、手机都带的功能,你esp32难道不带?
拿出来吧。
上代码,初始化:
// 初始化NVS
nvs_flash_init();
// 初始化WiFi驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_start();
// 配置扫描参数
wifi_scan_config_t scan_config = {
.scan_type = WIFI_SCAN_TYPE_ACTIVE, // 主动扫描
.show_hidden = true, // 包含隐藏网络
.scan_time.active.min = 100, // 最小扫描时间
.scan_time.active.max = 300 // 最大扫描时间
};
// 启动扫描
esp_wifi_scan_start(&scan_conf, false);
获取打印扫描参数
// 定义 wifi_scan 函数,扫描并打印 Wi-Fi 列表
void z_wifi_scan(void)
{
uint16_t ap_num;
wifi_ap_record_t *ap_records;// 存储 Wi-Fi 热点信息的指针
esp_wifi_scan_get_ap_num(&ap_num);// 获取可用 Wi-Fi 热点数量
if (ap_num == 0)
{
ESP_LOGI(TAG, "No AP found");// 如果没有可用 Wi-Fi 热点,输出提示信息
return;
}
// 分配内存空间存储 Wi-Fi 热点信息
ap_records = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * ap_num);
if (!ap_records)
{
ESP_LOGE(TAG, "malloc error");
return;
}
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_num, ap_records));// 获取所有可用 Wi-Fi 热点信息
ESP_LOGI(TAG, "Found %d APs", ap_num);// 输出可用 Wi-Fi 热点数量
// 遍历所有可用 Wi-Fi 热点,并输出信息
for (int i = 0; i < ap_num; i++)
{
printf("line=%d,SSID:%s,RSSI:%d\n", __LINE__,ap_records[i].ssid, ap_records[i].rssi);
}
free(ap_records);// 释放内存
}
测试,扫描到10个ssid,并且展示了rssi信号值:
wifi通讯(含TCP通讯)
你玩ESP32很多时候也是奔着wifi来的,那么这边就把wifi程序贴出来,亲测哦!
不废话,上代码:
//先是wifi初始化(sta模式),用于连接路由器
// SSID & Password
#define client_ssid "你自己填写呗" // 你的SSID
#define client_password "你自己填写呗" // 你的密码
static const char *TAG = "wifi";
static u8_t g_wifi_status = 0;
/** 事件回调函数
* @param arg 用户传递的参数
* @param event_base 事件类别
* @param event_id 事件ID
* @param event_data 事件携带的数据
* @return 无
*/
static void wifi_event_handler(void* arg, esp_event_base_t event_base,int32_t event_id, void* event_data)
{
if(event_base == WIFI_EVENT)
{
switch (event_id)
{
case WIFI_EVENT_STA_START: //WIFI以STA模式启动后触发此事件
{
g_wifi_status = 0;
esp_wifi_connect(); //启动WIFI连接
}
break;
case WIFI_EVENT_STA_CONNECTED: //WIFI连上路由器后,触发此事件
{
g_wifi_status = 1;
ESP_LOGI(TAG, "connected to AP");
}
break;
case WIFI_EVENT_STA_DISCONNECTED: //WIFI从路由器断开连接后触发此事件
{
g_wifi_status = 0;
esp_wifi_connect(); //继续重连
ESP_LOGI(TAG,"connect to the AP fail,retry now");
}
break;
default:
{
ESP_LOGI(TAG,"unknown event");
}
break;
}
}
if(event_base == IP_EVENT) //IP相关事件
{
switch(event_id)
{
case IP_EVENT_STA_GOT_IP: //只有获取到路由器分配的IP,才认为是连上了路由器
{
g_wifi_status = 2;
ESP_LOGI(TAG,"get ip address:%d.",g_wifi_status);
}
break;
}
}
}
void z_wifi_init(void)
{
ESP_ERROR_CHECK(esp_netif_init()); //用于初始化tcpip协议栈
ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建一个默认系统事件调度循环,之后可以注册回调函数来处理系统的一些事件
esp_netif_create_default_wifi_sta(); //使用默认配置创建STA对象
//初始化WIFI
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
//注册WIFI事件回调函数
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&wifi_event_handler,NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&wifi_event_handler,NULL));
//WIFI配置
wifi_config_t wifi_config =
{
.sta = {
.ssid = client_ssid, //WIFI的SSID
.password = client_password, //WIFI密码
.threshold.authmode = WIFI_AUTH_WPA2_PSK, //加密方式
.pmf_cfg ={
.capable = true,
.required = false,
}
},
};
//启动WIFI
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); //设置工作模式为STA
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); //设置wifi配置
ESP_ERROR_CHECK(esp_wifi_start() ); //启动WIFI
ESP_LOGI(TAG, "wifi_init_sta finished.");
}
对,你连上了路由器,那么,你需要链接socket呀,来上代码:
static int sock=0;
void z_wifi_socket_init(void)
{
g_wifi_status = 2;
// 创建socket:socket();
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
ESP_LOGE(TAG, "create socket failed!");
return;
}
ESP_LOGI(TAG, "create socket successfully!");
// 初始化server的地址结构体sockaddr_in
struct sockaddr_in destaddr = {0};
destaddr.sin_family = AF_INET;
destaddr.sin_port = htons(7777); // 填写网络调试助手服务端实际端口
destaddr.sin_addr.s_addr = inet_addr("192.168.x.xxx"); // 填写网络调试助手服务端实际IP地址(ip地址自己填写呗)
// 建立socket连接:
socklen_t len = sizeof(struct sockaddr);
if (connect(sock, (struct sockaddr *)&destaddr, len) < 0)
{
ESP_LOGE(TAG, "connect to server failed!");
close(sock);
return;
}
ESP_LOGI(TAG, "connect to server successfully!");
g_wifi_status = 3;
}
那么,来发送一下数据:
void z_wifi_process(void)
{
//进行tcp通讯
if(g_wifi_status == 3)
{
ESP_LOGI(TAG, "send data...");
// 发送数据
const char *send_data = "hello,world!";
send(sock, send_data, strlen(send_data), 0);//发送数据
}
else if( g_wifi_status == 2 )
{
ESP_LOGI(TAG, "z_wifi_socket_init");
z_wifi_socket_init();
}
else
{
ESP_LOGI(TAG, "waiting for wifi connection...");
}
}
运行结果:
1、连上路由器:
2、socket连接成功:
3、发送hello world数据:
wifi通讯(服务端)
未完,待续…(继续挖坑)
数据存储
未完,待续…(继续挖坑)
OTA升级问题
未完,待续…(继续挖坑)
MQTT通讯
未完,待续…(继续挖坑)
RTC时钟(假装是独家)
学过单片机的都知道,RTC是啥,我们常说是实时时钟,说白了,就是用来看年月日的东西。
目前很多MCU都有RTC功能,并且,RTC功能往往是伴随着低功耗功能,以及后备寄存器存储功能的一起打包的。
ESP32-S3内部带有一个RTC的实时时钟的单元。
卧槽,我怎么变废话了,,,(可能是这块太简单了,没啥的写的,为了凑点字数。。。 )
使用RTC的硬件条件:
需要一颗32.768khz的晶振。
晶振两端连接至ESP32-S3的32K_XP(GPIO0)和32K_XN(GPIO1)引脚。
需并联负载电容(通常6~12pF,具体参考晶振手册)。
那么,此时在menuconfig中,配置一下,如下:
时间持久性说明:
时间持久性
RTC时间在深度睡眠/复位后仍保留,但完全断电后会丢失(需外置电池供电RTC模块或使用RTC内存)。
好了,关键环节,上代码:
//esp32只要调用标准time.h中的函数即可设置、读取时间。
#include <sys/time.h>
#include <time.h>
// 设置RTC时间
void set_rtc_time(struct tm *timeinfo) {
struct timeval tv;
tv.tv_sec = mktime(timeinfo); // 转换为时间戳
settimeofday(&tv, NULL); // 写入系统时间(同步到RTC)
}
// 读取RTC时间
void get_rtc_time(struct tm *timeinfo) {
time_t now;
time(&now); // 从RTC获取时间戳
localtime_r(&now, timeinfo); // 转换为tm结构体
}
测试代码,读取时间:
struct tm set_time = {
.tm_year = 2025 - 1900,
.tm_mon = 4, // 10月(0-based)注意点,这里的4代表5月,0代表1月
.tm_mday = 1,
.tm_hour = 12,
.tm_min = 0,
.tm_sec = 0
};
set_rtc_time(&set_time);
测试代码,读取时间:
// 读取并打印时间
struct tm get_time;
get_rtc_time(&get_time);
//打印时间
printf("TIME:%04d-%02d-%02d %02d:%02d:%02d\n",
get_time.tm_year + 1900, get_time.tm_mon + 1, get_time.tm_mday,
get_time.tm_hour, get_time.tm_min, get_time.tm_sec);
测试结果(由于设置了时间,所以是从5月1日开始的,对了祝大家51假期玩的开心哈):
时间戳读取(肯定是独家)
这个对于esp32,太简单了,不磨叽,不玻璃心,不扭扭捏捏,不绿茶,不嗷嗷叫,不绕弯弯,不…
直接,上代码:
xTickCount = xTaskGetTickCount();
printf("xTickCount:%ld\n",xTickCount);
ps:你个博主,这么简单的东西都拿来说,你不是独家,谁是独家?
来来,附上测试结果(这个时间戳单位是10ms):
ble通讯
未完,待续…(蓝牙比较复杂,最后搞,麻烦 )
lcd显示
未完,待续…(其实早就弄完,无奈没空写 )
CAN通讯
不想篇幅拉的太长,来下面链接看,这个链接也是本人亲自写的哦:
链接
ps:嘘,不要说我为了多加几篇文章,才这样干的,也不要奔走相告哈。。。[笑哭]
最后,鉴于博主水平,一定有错误的地方,若有错误的地方,请及时更正,谢谢。