ESP32基于esp32 idf开发

每一层目录都有一个CMakeLists.txt配置文件存在。每当添加一个目录时都有对CMakeLIsts.txt文件修改

官方已经帮我们写好了很多常用外设和系统功能的驱动,头文件大多在 components/driver/include/components/esp_system/include/ 等目录下,我们可以直接使用。

例如像:

  • GPIO#include "driver/gpio.h"

  • UART#include "driver/uart.h"

  • I2C#include "driver/i2c.h"

注意:启动GDB调试时,记得把launch.json里的"environment": [{ "name": "PATH", "value": "${config:idf.customExtraPaths}" }],注释掉

LED(GPIO的使用)

与STM32的GPIO使用方法类似。

多了个gpio_init_struct.pin_bit_mask = 1ull << LED_GPIO_PIN,用于使能

代码实现

led.c

#include "led.h"


/**
 * @brief       初始化LED
 * @param       无
 * @retval      无
 */
void led_init(void)
{
    gpio_config_t gpio_init_struct = {0};

    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;         /* 失能引脚中断 */
    gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;         /* 输入输出模式 */
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;       /* 使能上拉 */
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;  /* 失能下拉 */
    gpio_init_struct.pin_bit_mask = 1ull << LED_GPIO_PIN;   /* 设置的引脚的位掩码 */
    gpio_config(&gpio_init_struct);                         /* 配置GPIO */

    LED(1);                                                 /* 关闭LED */
}

led.h

#ifndef __LED_H_
#define __LED_H_

#include "driver/gpio.h"

/* 引脚定义 */
#define LED_GPIO_PIN GPIO_NUM_1 /* LED连接的GPIO端口 */

/* 引脚的输出的电平状态 */
enum GPIO_OUTPUT_STATE
{
    PIN_RESET,
    PIN_SET
};

/* LED端口定义 */
#define LED(x)                                                                               \
    do                                                                                       \
    {                                                                                        \
        x ? gpio_set_level(LED_GPIO_PIN, PIN_SET) : gpio_set_level(LED_GPIO_PIN, PIN_RESET); \
    } while (0) /* LED翻转 */

/* LED取反定义 */
#define LED_TOGGLE()                                                 \
    do                                                               \
    {                                                                \
        gpio_set_level(LED_GPIO_PIN, !gpio_get_level(LED_GPIO_PIN)); \
    } while (0) /* LED翻转 */

/* 函数声明*/
void led_init(void); /* 初始化LED */

#endif

外部中断

ESP32与STM32的对比

相同点

  • 都是:配置 IO → 配置触发方式(上升沿/下降沿) → 注册 ISR → 在 ISR 中处理。

  • ISR 里都要尽量简短,复杂逻辑放任务里处理。

不同点

  • STM32 要开时钟 + NVIC 配置,ESP32 自动管理,不用手动开时钟。

  • STM32 EXTI 一个中断线可能映射多个 IO,需要软件区分;ESP32 每个 IO 可独立注册 ISR,更灵活。

  • ESP32 ISR 必须加 IRAM_ATTR,保证 ISR 在内部 RAM 中运行;STM32 没这个要求。

代码实现

.c

#include "exit.h"


/**
 * @brief       外部中断服务函数
 * @param       arg:中断引脚号
 * @note        IRAM_ATTR: 这里的IRAM_ATTR属性用于将中断处理函数存储在内部RAM中,目的在于减少延迟
 * @retval      无
 */
static void IRAM_ATTR exit_gpio_isr_handler(void *arg)
{
    uint32_t gpio_num = (uint32_t) arg;
    
    if (gpio_num == BOOT_INT_GPIO_PIN)
    {
        LED_TOGGLE();
    }
}

/**
 * @brief       外部中断初始化程序
 * @param       无
 * @retval      无
 */
void exit_init(void)
{
    gpio_config_t gpio_init_struct;

    /* 配置BOOT引脚和外部中断 */
    gpio_init_struct.mode = GPIO_MODE_INPUT;                    /* 选择为输入模式 */
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;           /* 上拉使能 */
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;      /* 下拉失能 */
    gpio_init_struct.intr_type = GPIO_INTR_NEGEDGE;             /* 下降沿触发 */
    gpio_init_struct.pin_bit_mask = 1ull << BOOT_INT_GPIO_PIN;  /* 配置BOOT按键引脚 */
    gpio_config(&gpio_init_struct);                             /* 配置使能 */
    
    /* 注册中断服务 */
    gpio_install_isr_service(0);
    
    /* 设置GPIO的中断回调函数 */
    gpio_isr_handler_add(BOOT_INT_GPIO_PIN, exit_gpio_isr_handler, (void*) BOOT_INT_GPIO_PIN);
}

.h

#ifndef __EXIT_H_
#define __EXIT_H_

#include "esp_err.h"
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_system.h" 
#include "esp_log.h"
#include "sdkconfig.h"
#include "led.h"


/* 引脚定义 */
#define BOOT_INT_GPIO_PIN   GPIO_NUM_0

/*IO操作*/
#define BOOT                gpio_get_level(BOOT_INT_GPIO_PIN)

/* 函数声明 */
void exit_init(void);   /* 外部中断初始化程序 */

#endif

串口

实现功能:串口接收到字符‘5’,反转LED状态。

熟悉下那几个常见的数据接收和发送API即可。

代码实现

usart.c

#include "usart.h"


/**
 * @brief       初始化串口
 * @param       baudrate: 波特率, 根据自己需要设置波特率值
 * @note        注意: 必须设置正确的时钟源, 否则串口波特率就会设置异常.
 * @retval      无
 */
void usart_init(uint32_t baudrate)
{
    uart_config_t uart_config;                          /* 串口配置句柄 */

    uart_config.baud_rate = baudrate;                   /* 波特率 */
    uart_config.data_bits = UART_DATA_8_BITS;           /* 字长为8位数据格式 */
    uart_config.parity = UART_PARITY_DISABLE;           /* 无奇偶校验位 */
    uart_config.stop_bits = UART_STOP_BITS_1;           /* 一个停止位 */
    uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;   /* 无硬件控制流 */
    uart_config.source_clk = UART_SCLK_APB;             /* 配置时钟源 */
    uart_config.rx_flow_ctrl_thresh = 122;              /* 硬件控制流阈值 */
    uart_param_config(USART_UX, &uart_config);          /* 配置uart端口 */

    /* 配置uart引脚 */
    uart_set_pin(USART_UX, USART_TX_GPIO_PIN, USART_RX_GPIO_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

    /* 安装串口驱动 */
    uart_driver_install(USART_UX, RX_BUF_SIZE * 2, RX_BUF_SIZE * 2, 20, NULL, 0);
}

usart.h

#ifndef _USART_H
#define _USART_H

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/uart.h"
#include "driver/uart_select.h"
#include "driver/gpio.h"


/* 引脚和串口定义 */
#define USART_UX            UART_NUM_0
#define USART_TX_GPIO_PIN   GPIO_NUM_43
#define USART_RX_GPIO_PIN   GPIO_NUM_44

/* 串口接收相关定义 */
#define RX_BUF_SIZE         1024    /* 环形缓冲区大小 */

/* 函数声明 */
void usart_init(uint32_t baudrate); /* 初始化串口 */

#endif

main.c

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "led.h"
#include "usart.h"

/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t ret;
    uint8_t len = 0;
    unsigned char data[RX_BUF_SIZE] = {0};

    ret = nvs_flash_init(); /* 初始化NVS */

    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();
    }

    led_init();         /* 初始化LED */
    usart_init(115200); /* 初始化串口, 波特率115200 */

    while (1)
    {
        uart_get_buffered_data_len(USART_UX, (size_t *)&len); /* 获取环形缓冲区数据长度 */

        if (len > 0) /* 判断数据长度 */
        {
            memset(data, 0, RX_BUF_SIZE); /* 对缓冲区清零 */
            printf("\r\n您发送的消息为:\r\n");
            uart_read_bytes(USART_UX, data, len, 100); /* 读数据 */
            if (data[0] == '5')
            {
                LED_TOGGLE();
            }
            uart_write_bytes(USART_UX, (const char *)data, strlen((const char *)data)); /* 写数据 */
        }
        // LED_TOGGLE();
        vTaskDelay(50);
    }
}

定时器

高精度定时器

使用流程

esp_timer_create 创建 → esp_timer_start_onceesp_timer_start_periodic 启动 → 回调函数执行任务 → esp_timer_stop/delete 释放。

实现功能:每1S反转一次LED状态

esptim.c

#include "esptim.h"


/**
 * @brief       初始化高精度定时器(ESP_TIMER)
 * @param       tps: 定时器周期,以微妙为单位(μs),以一秒为定时器周期来执行一次定时器中断,
 *                   那此处tps = 1s = 1000000μs
 * @retval      无
 */

void esptim_int_init(uint64_t tps)
{
    esp_timer_handle_t esp_tim_handle;                      /* 定时器回调函数句柄 */

    /* 定义一个定时器结构体 */
    esp_timer_create_args_t tim_periodic_arg = {
        .callback = &esptim_callback,                       /* 设置回调函数 */
        .arg = NULL,                                        /* 不携带参数 */
    };

    esp_timer_create(&tim_periodic_arg, &esp_tim_handle);   /* 创建一个事件 */
    esp_timer_start_periodic(esp_tim_handle, tps);          /* 每周期内触发一次 */
}

/**
 * @brief       定时器回调函数
 * @param       arg: 不携带参数
 * @retval      无
 */
void esptim_callback(void *arg)
{
    LED_TOGGLE();
}

esptim.h

#ifndef __ESPTIM_H_
#define __ESPTIM_H_

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_timer.h"
#include "led.h"


/* 函数声明 */
void esptim_int_init(uint64_t tps);                 /* 初始化初始化高分辨率定时器 */
void esptim_callback(void *arg);                    /* 定时器回调函数 */

#endif

main.c

/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t ret;
    
    ret = nvs_flash_init();         /* 初始化NVS */

    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();
    }
    
    led_init();                     /* 初始化LED */
    esptim_int_init(1000000);       /* 初始化高分辨率定时器,此处设置定时器周期为1秒,
                                       但该函数事宜微妙为单位进行计算,
                                       故而1秒钟换算为1000000微秒 */
}

通用定时器

gtim.c

#include "gptim.h"


QueueHandle_t queue;

/**
 * @brief       初始化通用定时器
 * @param       counts: 计数值
 * @param       resolution: 定时器周期,resolution = 1s = 1000000μs(此处,定时器以微秒作为计算单位,)
 * @retval      无
 */
void gptim_int_init(uint16_t counts, uint32_t resolution)
{
    gptimer_config_t g_tim_handle;
    gptimer_alarm_config_t alarm_config;
    gptimer_event_callbacks_t g_tim_callbacks;

    uint64_t count;

    /* 配置通用定时器 */
    ESP_LOGI("GPTIMER_ALARM", "配置通用定时器");                                         /* 创建通用定时器句柄 */
    gptimer_handle_t g_tim = NULL;
    g_tim_handle.clk_src = GPTIMER_CLK_SRC_DEFAULT;                                     /* 选择定时器时钟源 */
    g_tim_handle.direction = GPTIMER_COUNT_UP;                                          /* 递增计数模式 */         
    g_tim_handle.resolution_hz = resolution;                                            /* 计数器分辨率 */
    g_tim_callbacks.on_alarm = gptimer_callback;                                        /* 注册用户回调函数 */ 
    alarm_config.alarm_count = 1000000;                                                 /* 报警目标计数值 */
    ESP_ERROR_CHECK(gptimer_new_timer(&g_tim_handle, &g_tim));                          /* 创建新的通用定时器,并返回句柄 */

    queue = xQueueCreate(10, sizeof(gptimer_event_t));                                  /* 创建一个队列,并引入一个事件 */

    if (!queue)
    {
        ESP_LOGE("GPTIMER_ALARM", "创建队列失败");                                       /* 创建队列失败 */

        return;
    }

    /* 设置和获取计数值 */
    ESP_LOGI("GPTIMER_ALARM", "设置计数值");
    ESP_ERROR_CHECK(gptimer_set_raw_count(g_tim, counts));                              /* 设置计数值 */
    ESP_LOGI("GPTIMER_ALARM", "获取计数值");
    ESP_ERROR_CHECK(gptimer_get_raw_count(g_tim, &count));                              /* 获取计数值 */
    ESP_LOGI("GPTIMER_ALARM", "定时器计数值: %llu", count);

    /* 注册事件回调函数 */
    ESP_ERROR_CHECK(gptimer_register_event_callbacks(g_tim, &g_tim_callbacks, queue));  /* 配置通用定时器回调函数 */

    /* 设置报警动作 */
    ESP_LOGI("GPTIMER_ALARM", "使能通用定时器");
    ESP_ERROR_CHECK(gptimer_enable(g_tim));                                             /* 使能通用定时器 */
    ESP_ERROR_CHECK(gptimer_set_alarm_action(g_tim, &alarm_config));                    /* 配置通用定时器报警事件 */
    ESP_ERROR_CHECK(gptimer_start(g_tim));                                              /* 启动通用定时器 */
}

/**
 * @brief       定时器回调函数
 * @param       无
 * @retval      无
 */
bool IRAM_ATTR gptimer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data)
{
    BaseType_t high_task_awoken = pdFALSE;
    queue = (QueueHandle_t)user_data;

    /* 从事件数据中检索计数值 */
    gptimer_event_t ele = {
        .event_count = edata->count_value
    };

    /* 可选:通过操作系统队列将事件数据发送到其他任务 */
    xQueueSendFromISR(queue, &ele, &high_task_awoken);
    
    /* 重新配置报警值 */ 
    gptimer_alarm_config_t alarm_config = {
        .alarm_count = edata->alarm_value + 1000000, /* 在接下来的1秒内报警 */ 
    };
    gptimer_set_alarm_action(timer, &alarm_config);
    
    /* 返回是否需要在ISR结束时让步 */ 
    return high_task_awoken == pdTRUE;
}

gtim.h

#ifndef __GPTIM_H_
#define __GPTIM_H_

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gptimer.h"
#include "esp_log.h"


/* 参数引用 */
typedef struct {
    uint64_t event_count;
} gptimer_event_t;

extern QueueHandle_t queue;

/* 函数声明 */
void gptim_int_init(uint16_t counts, uint32_t resolution);                                                          /* 初始化通用定时器 */
bool IRAM_ATTR gptimer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_data);  /* 定时器回调函数 */

#endif

main.c

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "led.h"
#include "gptim.h"


/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    uint8_t record;
    esp_err_t ret;
    gptimer_event_t g_tim_evente;
    
    ret = nvs_flash_init();                                                                   /* 初始化NVS */

    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();
    }
    
    led_init();                                                                               /* 初始化LED */
    gptim_int_init(100, 1000000);                                                             /* 初始化通用定时器 */
    
    while (1)
    {
        record = 1;

        if (xQueueReceive(queue, &g_tim_evente, 2000))
        {
            ESP_LOGI("GPTIMER_ALARM", "定时器报警, 计数值: %llu", g_tim_evente.event_count);   /* 打印通用定时器发生一次计数事件后获取到的值 */
            record--;
        }
        else
        {
            ESP_LOGW("GPTIMER_ALARM", "错过一次计数事件");
        }
    }
    vQueueDelete(queue);
}

看门狗

代码实现

wdt.c

#include "wdt.h"

esp_timer_handle_t esp_tim_handle;                          /* 定时器回调函数句柄 */

/**
 * @brief       初始化任务看门狗计时器
 * @param       arr: 自动重装载值
 *              tps: 定时器周期
 */
void wdt_init(uint16_t arr, uint64_t tps)
{
    /* 定义一个定时器结构体 */
    esp_timer_create_args_t tim_periodic_arg = {
    .callback =	&wdt_isr_handler,                           /* 设置回调函数 */
    .arg = NULL,                                            /* 不携带参数 */
    };

    /* 创建定时器事件 */
    esp_timer_create(&tim_periodic_arg, &esp_tim_handle);   /* 创建一个事件 */
    esp_timer_start_periodic(esp_tim_handle, tps);          /* 每周期内触发一次 */
}

/**
 * @brief       重新启动当前运行的计时器
 * @param       timeout: 定时器超时时间,该超时时间以微妙作为基本计算单位,故而设置超时时间为1s,则需要转换为微妙(μs),即timeout = 1s = 1000000μs
 * @retval      无
 */
void restart_timer(uint64_t timeout)
{
    esp_timer_restart(esp_tim_handle, timeout);             /* 重新启动当前运行的计时器,用以模拟喂狗过程 */
}

/**
 * @brief       看门狗回调函数
 * @param       arg: 无参数传入
 * @retval      无
 */
void IRAM_ATTR wdt_isr_handler(void *arg)
{
    esp_restart();                                          /* 若没有及时进行喂狗,那么芯片将一直进行复位 */
}

wdt.h

#ifndef __WDT_H_
#define __WDT_H_

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"


/* 参数定义 */
#define TWDT_TIMEOUT_MS         3000
#define TASK_RESET_PERIOD_MS    2000
#define MAIN_DELAY_MS           10000

/* 函数声明 */
void wdt_init(uint16_t arr, uint64_t tps);  /* 初始化独立看门狗 */
void restart_timer(uint64_t timeout);       /*  */
void IRAM_ATTR wdt_isr_handler(void *arg);

#endif

main.c

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "led.h"
#include "key.h"
#include "wdt.h"


/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t rets;
    
    rets = nvs_flash_init();            /* 初始化NVS */

    if (rets == ESP_ERR_NVS_NO_FREE_PAGES || rets == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        rets = nvs_flash_init();
    }

    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    wdt_init(5000, 1000000);            /* 初始化定时器 */
    LED(0);                             /* LED灯常亮 */

    while (1)
    {
        if (key_scan(0) == BOOT_PRES)   /* 如果BOOT按下则喂狗 */
        {
            restart_timer(1000000);     /* 喂狗 */
        }

        vTaskDelay(10);                 /* LED闪烁 */
    }
}

软件PWM

乐鑫官方甚至给PWM封装了对应的结构体。

代码实现

pwm.c

#include "pwm.h"


/**
 * @brief       初始化PWM
 * @param       resolution: PWM占空比分辨率
 *              freq: PWM信号频率
 * @retval      无
 */
void pwm_init(uint8_t resolution, uint16_t freq)
{
    ledc_timer_config_t ledc_timer = {0};           /* LEDC定时器句柄 */
    ledc_channel_config_t ledc_channel = {0};       /* LEDC通道配置句柄 */

    /* 配置LEDC定时器 */
    ledc_timer.duty_resolution = resolution;        /* PWM占空比分辨率 */
    ledc_timer.freq_hz = freq;                      /* PWM信号频率 */
    ledc_timer.speed_mode = LEDC_LOW_SPEED_MODE;    /* 定时器模式 */
    ledc_timer.timer_num = LEDC_PWM_TIMER;          /* 定时器序号 */
    ledc_timer.clk_cfg = LEDC_AUTO_CLK;             /* LEDC时钟源 */
    ledc_timer_config(&ledc_timer);                 /* 配置定时器 */

    /* 配置LEDC通道 */
    ledc_channel.gpio_num = LEDC_PWM_CH0_GPIO;      /* LED控制器通道对应引脚 */
    ledc_channel.speed_mode = LEDC_LOW_SPEED_MODE;  /* LEDC高速模式 */
    ledc_channel.channel = LEDC_PWM_CH0_CHANNEL;    /* LEDC控制器通道号 */
    ledc_channel.intr_type = LEDC_INTR_DISABLE;     /* LEDC失能中断 */
    ledc_channel.timer_sel = LEDC_PWM_TIMER;        /* 定时器序号 */
    ledc_channel.duty = 0;                          /* 占空比值 */
    ledc_channel_config(&ledc_channel);             /* 配置LEDC通道 */
}

/**
 * @brief       PWM占空比设置
 * @param       duty:PWM占空比
 * @retval      无
 */
void pwm_set_duty(uint16_t duty)
{
    ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_PWM_CH0_CHANNEL, duty); /* 设置占空比 */
    ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_PWM_CH0_CHANNEL);    /* 更新占空比 */
}

pwm.h

#ifndef __PWM_H_
#define __PWM_H_

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/ledc.h"
#include "driver/gpio.h"


/* 引脚以及重要参数定义 */
#define LEDC_PWM_TIMER          LEDC_TIMER_1        /* 使用定时器0 */
#define LEDC_PWM_CH0_GPIO       GPIO_NUM_1          /* LED控制器通道对应GPIO */
#define LEDC_PWM_CH0_CHANNEL    LEDC_CHANNEL_1      /* LED控制器通道号 */

/* 函数声明 */
void pwm_init(uint8_t resolution, uint16_t freq);   /* 初始化PWM */
void pwm_set_duty(uint16_t duty);                   /* PWM占空比设置 */

#endif

main.c示例

#include "nvs_flash.h"
#include "pwm.h"


/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t ret;
    uint8_t dir = 1;
    uint16_t ledpwmval = 0;

    ret = nvs_flash_init(); /* 初始化NVS */

    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();
    }

    pwm_init(10, 1000);     /* 初始化PWM */

    while(1) 
    {
        vTaskDelay(10);

        if (dir == 1)
        {
            ledpwmval += 5; /* dir==1 ledpwmval递增 */
        }
        else
        {
            ledpwmval -= 5; /* dir==0 ledpwmval递减 */
        }

        if (ledpwmval > 1005)
        {
            dir = 0;        /* ledpwmval到达1005后,方向为递减 */
        }

        if (ledpwmval < 5)
        {
            dir = 1;        /* ledpwmval递减到5后,方向改为递增 */
        }

        /* 设置占空比 */
        pwm_set_duty(ledpwmval);
    }
}

硬件PWM

代码实现

pwm.c

#include "pwm.h"


/**
 * @brief       初始化PWM
 * @param       resolution:PWM占空比分辨率
 *              freq: PWM信号频率
 * @retval      无
 */
void pwm_init(uint8_t resolution, uint16_t freq)
{
    ledc_timer_config_t ledc_timer;                 /* LEDC定时器句柄 */
    ledc_channel_config_t ledc_channel;             /* LEDC通道配置句柄 */

    /* 配置LEDC定时器 */
    ledc_timer.duty_resolution = resolution;        /* PWM占空比分辨率 */
    ledc_timer.freq_hz = freq;                      /* PWM信号频率 */
    ledc_timer.speed_mode = LEDC_PWM_MODE;          /* 定时器模式 */
    ledc_timer.timer_num = LEDC_PWM_TIMER;          /* 定时器序号 */
    ledc_timer.clk_cfg = LEDC_AUTO_CLK;             /* LEDC时钟源 */
    ledc_timer_config(&ledc_timer);                 /* 配置定时器 */

    /* 配置LEDC通道 */
    ledc_channel.gpio_num = LEDC_PWM_CH0_GPIO;      /* LED控制器通道对应引脚 */
    ledc_channel.speed_mode = LEDC_PWM_MODE;        /* LEDC高速模式 */
    ledc_channel.channel = LEDC_PWM_CH0_CHANNEL;    /* LEDC控制器通道号 */
    ledc_channel.intr_type = LEDC_INTR_DISABLE;     /* LEDC失能中断 */
    ledc_channel.timer_sel = LEDC_PWM_TIMER;        /* 定时器序号 */
    ledc_channel.duty = 0;                          /* 占空比值 */
    ledc_channel_config(&ledc_channel);             /* 配置LEDC通道 */

    ledc_fade_func_install(0);                      /* 使能渐变(该函数不可或缺) */
}

/**
 * @brief       PWM占空比设置
 * @param       duty:PWM占空比
 * @retval      无
 */
void pwm_set_duty(uint16_t duty)
{
    ledc_set_fade_with_time(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, duty, LEDC_PWM_FADE_TIME);   /* 设置占空比以及渐变时长 */
    ledc_fade_start(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, LEDC_FADE_NO_WAIT);                  /* 开始渐变 */

    ledc_set_fade_with_time(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, 0, LEDC_PWM_FADE_TIME);      /* 设置占空比以及渐变时长 */
    ledc_fade_start(LEDC_PWM_MODE, LEDC_PWM_CH0_CHANNEL, LEDC_FADE_NO_WAIT);                  /* 开始渐变 */
}

pwm.h

#ifndef __PWM_H_
#define __PWM_H_

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/ledc.h"
#include "driver/gpio.h"


/* 引脚以及重要参数定义 */
#define LEDC_PWM_TIMER          LEDC_TIMER_0        /* 使用定时器0 */
#define LEDC_PWM_MODE           LEDC_LOW_SPEED_MODE /* 模式设定必须使用LEDC低速模式 */
#define LEDC_PWM_CH0_GPIO       GPIO_NUM_1          /* LED控制器通道对应GPIO */
#define LEDC_PWM_CH0_CHANNEL    LEDC_CHANNEL_0      /* LED控制器通道号 */
#define LEDC_PWM_DUTY           8000                /* 渐变的变大最终目标占空比 */
#define LEDC_PWM_FADE_TIME      3000                /* 变化时长 */

/* 函数声明 */
void pwm_init(uint8_t resolution, uint16_t freq);   /* 初始化PWM */
void pwm_set_duty(uint16_t duty);                   /* PWM占空比设置 */

#endif

main.c示例

#include "nvs_flash.h"
#include "pwm.h"


/**
 * @brief       程序入口
 * @param       无
 * @retval      无
 */
void app_main(void)
{
    esp_err_t ret;

    ret = nvs_flash_init();             /* 初始化NVS */

    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();
    }

    pwm_init(13, 5000);                 /* 初始化PWM */

    while(1) 
    {
        vTaskDelay(10);
        pwm_set_duty(LEDC_PWM_DUTY);    /* 设置占空比 */
    }
}

ADC

完全就是调用官方封装好的库函数。

代码实现

adc1.c

#include "adc1.h"

/**
 * @brief       初始化ADC
 * @param       无
 * @retval      无
 */
void adc_init(void)
{
    adc_digi_pattern_config_t adc1_digi_pattern_config;         /* ADC1配置句柄 */
    adc_digi_configuration_t adc1_init_config;                  /* ADC1初始化句柄 */
    
    /* 配置ADC1 */
    adc1_digi_pattern_config.atten = ADC_ATTEN_DB_11;           /* 配置ADC衰减程度 */
    adc1_digi_pattern_config.channel = ADC_ADCX_CHY;            /* 配置ADC通道 */
    adc1_digi_pattern_config.unit = ADC_UNIT_1;                 /* 配置ADC单元 */
    adc1_digi_pattern_config.bit_width = ADC_BITWIDTH_12;       /* 配置ADC位宽 */
    adc1_init_config.adc_pattern = &adc1_digi_pattern_config;   /* 配置将要使用的每个ADC参数 */
    adc_digi_controller_configure(&adc1_init_config);           /* 配置ADC1 */
}

/**
 * @brief       获取ADC转换且进行均值滤波后的结果
 * @param       ch      : 通道号, 0~9
 * @param       times   : 获取次数
 * @retval      通道ch的times次转换结果平均值
 */
uint32_t adc_get_result_average(uint32_t ch, uint32_t times)
{
    uint32_t temp_val = 0;
    uint8_t t;

    for (t = 0; t < times; t++) /* 获取times次数据 */
    {
        temp_val += adc1_get_raw(ch);
        vTaskDelay(5);
    }

    return temp_val / times;    /* 返回平均值 */
}

adc1.h

#ifndef __ADC_H_
#define __ADC_H_

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "driver/adc.h"
#include "esp_log.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"


#define ADC_ADCX_CHY   ADC1_CHANNEL_7 

/* 函数声明 */
void adc_init(void);                                            /* 初始化ADC */
uint32_t adc_get_result_average(uint32_t ch, uint32_t times);   /* 获取ADC转换且进行均值滤波后的结果 */

#endif

RTC

代码实现

rtc.c

#include "esp_rtc.h"


_calendar_obj calendar;         /* 时间结构体 */

/**
 * @brief       RTC设置时间
 * @param       year    :年
 * @param       mon     :月
 * @param       mday    :日
 * @param       hour    :时
 * @param       min     :分
 * @param       sec     :秒
 * @retval      无
 */
void rtc_set_time(int year,int mon,int mday,int hour,int min,int sec)
{
    struct tm datetime;
    /* 设置时间 */
    datetime.tm_year = year - 1900;
    datetime.tm_mon = mon - 1;
    datetime.tm_mday = mday;
    datetime.tm_hour = hour;
    datetime.tm_min = min;
    datetime.tm_sec = sec;
    datetime.tm_isdst = -1;
    /* 获取1970.1.1以来的总秒数 */
    time_t second = mktime(&datetime);
    struct timeval val = { .tv_sec = second, .tv_usec = 0 };
    /* 设置当前时间 */
    settimeofday(&val, NULL);
}

/**
 * @brief       获取当前的时间
 * @param       无
 * @retval      无
 */
void rtc_get_time(void)
{
    struct tm *datetime;
    time_t second;
    /* 返回自(1970.1.1 00:00:00 UTC)经过的时间(秒) */
    time(&second);
    datetime = localtime(&second);

    calendar.hour = datetime->tm_hour;          /* 时 */
    calendar.min = datetime->tm_min;            /* 分 */
    calendar.sec = datetime->tm_sec;            /* 秒 */
    /* 公历年月日周 */
    calendar.year = datetime->tm_year + 1900;   /* 年 */
    calendar.month = datetime->tm_mon + 1;      /* 月 */
    calendar.date = datetime->tm_mday;          /* 日 */
    /* 周 */
    calendar.week = rtc_get_week(calendar.year, calendar.month, calendar.date);
}

/**
 * @brief       将年月日时分秒转换成秒钟数
 *   @note      输入公历日期得到星期(起始时间为: 公元0年3月1日开始, 输入往后的任何日期, 都可以获取正确的星期)
 *              使用 基姆拉尔森计算公式 计算, 原理说明见此贴:
 *              https://www.cnblogs.com/fengbohello/p/3264300.html
 * @param       syear : 年份
 * @param       smon  : 月份
 * @param       sday  : 日期
 * @retval      0, 星期天; 1 ~ 6: 星期一 ~ 星期六
 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day)
{
    uint8_t week = 0;

    if (month < 3)
    {
        month += 12;
        --year;
    }

    week = (day + 1 + 2 * month + 3 * (month + 1) / 5 + year + (year >> 2) - year / 100 + year / 400) % 7;
    return week;
}

rtc.h

#ifndef __ESP_RTC_H
#define __ESP_RTC_H

#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <sys/time.h>


/* 时间结构体, 包括年月日周时分秒等信息 */
typedef struct
{
    uint8_t hour;       /* 时 */
    uint8_t min;        /* 分 */
    uint8_t sec;        /* 秒 */
    /* 公历年月日周 */
    uint16_t year;      /* 年 */
    uint8_t  month;     /* 月 */
    uint8_t  date;      /* 日 */
    uint8_t  week;      /* 周 */
} _calendar_obj;

extern _calendar_obj calendar;      /* 时间结构体 */

/* 函数声明 */
void rtc_set_time(int year,int mon,int mday,int hour,int min,int sec);  /* 设置时间 */
void rtc_get_time(void);                                                /* 获取时间 */
uint8_t rtc_get_week(uint16_t year, uint8_t month, uint8_t day);        /* 获取周几 */

#endif

IIC

与STM32 HAL 库API的对应关系

功能ESP32 驱动函数STM32 HAL 库函数
初始化 I2Ciic_initHAL_I2C_Init
单写数据i2c_transfer(WRITE)HAL_I2C_Master_Transmit
单读数据i2c_transfer(READ)HAL_I2C_Master_Receive
写地址 + 读数据i2c_transfer(WRITE+READ)HAL_I2C_Mem_Read

代码实现

iic.c

#include "iic.h"


i2c_obj_t iic_master[I2C_NUM_MAX];  /* 为IIC0和IIC1分别定义IIC控制块结构体 */

/**
 * @brief       初始化IIC
 * @param       iic_port:I2C编号(I2C_NUM_0 / I2C_NUM_1)
 * @retval      IIC控制块0 / IIC控制块1
 */
i2c_obj_t iic_init(uint8_t iic_port)
{
    uint8_t i;
    i2c_config_t iic_config_struct = {0};

    if (iic_port == I2C_NUM_0)
    {
        i = 0;
    }
    else
    {
        i = 1;
    }
    
    iic_master[i].port = iic_port;
    iic_master[i].init_flag = ESP_FAIL;

    if (iic_master[i].port == I2C_NUM_0)
    {
        iic_master[i].scl = IIC0_SCL_GPIO_PIN;
        iic_master[i].sda = IIC0_SDA_GPIO_PIN;
    }
    else
    {
        iic_master[i].scl = IIC1_SCL_GPIO_PIN;
        iic_master[i].sda = IIC1_SDA_GPIO_PIN;
    }

    iic_config_struct.mode = I2C_MODE_MASTER;                               /* 设置IIC模式-主机模式 */
    iic_config_struct.sda_io_num = iic_master[i].sda;                       /* 设置IIC_SDA引脚 */
    iic_config_struct.scl_io_num = iic_master[i].scl;                       /* 设置IIC_SCL引脚 */
    iic_config_struct.sda_pullup_en = GPIO_PULLUP_ENABLE;                   /* 配置IIC_SDA引脚上拉使能 */
    iic_config_struct.scl_pullup_en = GPIO_PULLUP_ENABLE;                   /* 配置IIC_SCL引脚上拉使能 */
    iic_config_struct.master.clk_speed = IIC_FREQ;                          /* 设置IIC通信速率 */
    i2c_param_config(iic_master[i].port, &iic_config_struct);               /* 设置IIC初始化参数 */

    /* 激活I2C控制器的驱动 */
    iic_master[i].init_flag = i2c_driver_install(iic_master[i].port,        /* 端口号 */
                                                 iic_config_struct.mode,    /* 主机模式 */
                                                 I2C_MASTER_RX_BUF_DISABLE, /* 从机模式下接收缓存大小(主机模式不使用) */
                                                 I2C_MASTER_TX_BUF_DISABLE, /* 从机模式下发送缓存大小(主机模式不使用) */     
                                                 0);                        /* 用于分配中断的标志(通常从机模式使用) */            

    if (iic_master[i].init_flag != ESP_OK)
    {
        while(1)
        {
            printf("%s , ret: %d", __func__, iic_master[i].init_flag);
            vTaskDelay(1000);
        }
    }

    return iic_master[i];
}

/**
 * @brief       IIC读写数据
 * @param       self:设备控制块
 * @param       addr:设备地址
 * @param       n   :数据大小
 * @param       bufs:要发送的数据或者是读取的存储区
 * @param       flags:读写标志位
 * @retval      无
 */
esp_err_t i2c_transfer(i2c_obj_t *self, uint16_t addr, size_t n, i2c_buf_t *bufs, unsigned int flags)
{
    int data_len = 0;
    esp_err_t ret = ESP_FAIL;

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();                                                       /* 创建一个命令链接,将一系列待发送给从机的数据填充命令链接 */

    /* 根据器件通信时序去决定flags参数,进而选择如下代码不同的执行情况 */
    if (flags & I2C_FLAG_WRITE)
    {
        i2c_master_start(cmd);                                                                          /* 启动位 */
        i2c_master_write_byte(cmd, addr << 1, ACK_CHECK_EN);                                            /* 从机地址 + 写操作位 */
        i2c_master_write(cmd, bufs->buf, bufs->len, ACK_CHECK_EN);                                      /* len个数据 */
        data_len += bufs->len; 
        --n;
        ++bufs;
    }

    i2c_master_start(cmd);                                                                              /* 启动位 */
    i2c_master_write_byte(cmd, addr << 1 | (flags & I2C_FLAG_READ), ACK_CHECK_EN);                      /* 从机地址 + 读/写操作位 */

    for (; n--; ++bufs)
    {
        if (flags & I2C_FLAG_READ)
        {
            i2c_master_read(cmd, bufs->buf, bufs->len, n == 0 ? I2C_MASTER_LAST_NACK : I2C_MASTER_ACK); /* 读取数据 */
        }
        else
        {
            if (bufs->len != 0)
            {
                i2c_master_write(cmd, bufs->buf, bufs->len, ACK_CHECK_EN);                              /* len个数据 */
            }
        }
        data_len += bufs->len;
    }

    if (flags & I2C_FLAG_STOP)
    {
        i2c_master_stop(cmd);                                                                           /* 停止位 */
    }

    ret = i2c_master_cmd_begin(self->port, cmd, 100 * (1 + data_len) / portTICK_PERIOD_MS);             /* 触发I2C控制器执行命令链接,即命令发送 */
    i2c_cmd_link_delete(cmd);                                                                           /* 释放命令链接使用的资源 */

    return ret;
}

iic.h

#ifndef __IIC_H
#define __IIC_H

#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_err.h"


/* IIC控制块 */
typedef struct _i2c_obj_t {
    i2c_port_t port;
    gpio_num_t scl;
    gpio_num_t sda;
    esp_err_t init_flag;
} i2c_obj_t;

/* 读写数据结构体 */
typedef struct _i2c_buf_t {
    size_t len;
    uint8_t *buf;
} i2c_buf_t;

extern i2c_obj_t iic_master[I2C_NUM_MAX];

/* 读写标志位 */
#define I2C_FLAG_READ                   (0x01)                                                          /* 读标志 */
#define I2C_FLAG_STOP                   (0x02)                                                          /* 停止标志 */
#define I2C_FLAG_WRITE                  (0x04)                                                          /* 写标志 */

/* 引脚与相关参数定义 */
#define IIC0_SDA_GPIO_PIN               GPIO_NUM_41                                                     /* IIC0_SDA引脚 */
#define IIC0_SCL_GPIO_PIN               GPIO_NUM_42                                                     /* IIC0_SCL引脚 */
#define IIC1_SDA_GPIO_PIN               GPIO_NUM_5                                                      /* IIC1_SDA引脚 */
#define IIC1_SCL_GPIO_PIN               GPIO_NUM_4                                                      /* IIC1_SCL引脚 */
#define IIC_FREQ                        400000                                                          /* IIC通信频率 */
#define I2C_MASTER_TX_BUF_DISABLE       0                                                               /* I2C主机不需要缓冲区 */
#define I2C_MASTER_RX_BUF_DISABLE       0                                                               /* I2C主机不需要缓冲区 */
#define ACK_CHECK_EN                    0x1                                                             /* I2C master将从slave检查ACK */

/* 函数声明 */
i2c_obj_t iic_init(uint8_t iic_port);                                                                   /* 初始化IIC */
esp_err_t i2c_transfer(i2c_obj_t *self, uint16_t addr, size_t n, i2c_buf_t *bufs, unsigned int flags);  /* IIC读写数据 */

#endif

SPI 

代码实现

spi.c

#include "spi.h"


/**
 * @brief       初始化SPI
 * @param       无
 * @retval      无
 */
void spi2_init(void)
{
    esp_err_t ret = 0;
    spi_bus_config_t spi_bus_conf = {0};

    /* SPI总线配置 */
    spi_bus_conf.miso_io_num = SPI_MISO_GPIO_PIN;                               /* SPI_MISO引脚 */
    spi_bus_conf.mosi_io_num = SPI_MOSI_GPIO_PIN;                               /* SPI_MOSI引脚 */
    spi_bus_conf.sclk_io_num = SPI_CLK_GPIO_PIN;                                /* SPI_SCLK引脚 */
    spi_bus_conf.quadwp_io_num = -1;                                            /* SPI写保护信号引脚,该引脚未使能 */
    spi_bus_conf.quadhd_io_num = -1;                                            /* SPI保持信号引脚,该引脚未使能 */
    spi_bus_conf.max_transfer_sz = 320 * 240 * 2;                               /* 配置最大传输大小,以字节为单位 */
    
    /* 初始化SPI总线 */
    ret = spi_bus_initialize(SPI2_HOST, &spi_bus_conf, SPI_DMA_CH_AUTO);        /* SPI总线初始化 */
    ESP_ERROR_CHECK(ret);                                                       /* 校验参数值 */
}

/**
 * @brief       SPI发送命令
 * @param       handle : SPI句柄
 * @param       cmd    : 要发送命令
 * @retval      无
 */
void spi2_write_cmd(spi_device_handle_t handle, uint8_t cmd)
{
    esp_err_t ret;
    spi_transaction_t t = {0};

    t.length = 8;                                       /* 要传输的位数 一个字节 8位 */
    t.tx_buffer = &cmd;                                 /* 将命令填充进去 */
    ret = spi_device_polling_transmit(handle, &t);      /* 开始传输 */
    ESP_ERROR_CHECK(ret);                               /* 一般不会有问题 */
}

/**
 * @brief       SPI发送数据
 * @param       handle : SPI句柄
 * @param       data   : 要发送的数据
 * @param       len    : 要发送的数据长度 
 * @retval      无
 */
void spi2_write_data(spi_device_handle_t handle, const uint8_t *data, int len)
{
    esp_err_t ret;
    spi_transaction_t t = {0};

    if (len == 0)
    {
        return;                                     /* 长度为0 没有数据要传输 */
    }

    t.length = len * 8;                             /* 要传输的位数 一个字节 8位 */
    t.tx_buffer = data;                             /* 将命令填充进去 */
    ret = spi_device_polling_transmit(handle, &t);  /* 开始传输 */
    ESP_ERROR_CHECK(ret);                           /* 一般不会有问题 */
}

/**
 * @brief       SPI处理数据
 * @param       handle       : SPI句柄
 * @param       data         : 要发送的数据 
 * @retval      t.rx_data[0] : 接收到的数据
 */
uint8_t spi2_transfer_byte(spi_device_handle_t handle, uint8_t data)
{
    spi_transaction_t t;

    memset(&t, 0, sizeof(t));

    t.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA;
    t.length = 8;
    t.tx_data[0] = data;
    spi_device_transmit(handle, &t);

    return t.rx_data[0];
}

spi.h

#ifndef __SPI_H
#define __SPI_H

#include <string.h>
#include "esp_log.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"


/* 引脚定义 */
#define SPI_MOSI_GPIO_PIN   GPIO_NUM_11         /* SPI2_MOSI */
#define SPI_CLK_GPIO_PIN    GPIO_NUM_12         /* SPI2_CLK */
#define SPI_MISO_GPIO_PIN   GPIO_NUM_13         /* SPI2_MISO */

/* 函数声明 */
void spi2_init(void);                                                               /* 初始化SPI2 */
void spi2_write_cmd(spi_device_handle_t handle, uint8_t cmd);                       /* SPI发送命令 */
void spi2_write_data(spi_device_handle_t handle, const uint8_t *data, int len);     /* SPI发送数据 */
uint8_t spi2_transfer_byte(spi_device_handle_t handle, uint8_t byte);               /* SPI处理数据 */
#endif

XL9555(IO拓展)

代码实现

xl9555.c

#include "xl9555.h"


i2c_obj_t xl9555_i2c_master;
static uint16_t xl9555_failed = 0;

/**
 * @brief       读取XL9555的16位IO值
 * @param       data:读取数据的存储区
 * @param       len:读取数据的大小
 * @retval      ESP_OK:读取成功;其他:读取失败
 */
esp_err_t xl9555_read_byte(uint8_t *data, size_t len)
{
    uint8_t memaddr_buf[1];
    memaddr_buf[0]  = XL9555_INPUT_PORT0_REG;

    i2c_buf_t bufs[2] = {
        {.len = 1, .buf = memaddr_buf},
        {.len = len, .buf = data},
    };

    return i2c_transfer(&xl9555_i2c_master, XL9555_ADDR, 2, bufs, I2C_FLAG_WRITE | I2C_FLAG_READ | I2C_FLAG_STOP);
}

/**
 * @brief       向XL9555写入16位IO值
 * @param       reg:寄存器地址
 * @param       data:要写入的数据
 * @param       len:要写入数据的大小
 * @retval      ESP_OK:读取成功;其他:读取失败
 */
esp_err_t xl9555_write_byte(uint8_t reg, uint8_t *data, size_t len)
{
    i2c_buf_t bufs[2] = {
        {.len = 1, .buf = &reg},
        {.len = len, .buf = data},
    };

    return i2c_transfer(&xl9555_i2c_master, XL9555_ADDR, 2, bufs, I2C_FLAG_STOP);
}

/**
 * @brief       控制某个IO的电平
 * @param       pin     : 控制的IO
 * @param       val     : 电平
 * @retval      返回所有IO状态
 */
uint16_t xl9555_pin_write(uint16_t pin, int val)
{
    uint8_t w_data[2];
    uint16_t temp = 0x0000;

    xl9555_read_byte(w_data, 2);

    if (pin <= GBC_KEY_IO)
    {
        if (val)
        {
            w_data[0] |= (uint8_t)(0xFF & pin);
        }
        else
        {
            w_data[0] &= ~(uint8_t)(0xFF & pin);
        }
    }
    else
    {
        if (val)
        {
            w_data[1] |= (uint8_t)(0xFF & (pin >> 8));
        }
        else
        {
            w_data[1] &= ~(uint8_t)(0xFF & (pin >> 8));
        }
    }

    temp = ((uint16_t)w_data[1] << 8) | w_data[0]; 

    xl9555_write_byte(XL9555_OUTPUT_PORT0_REG, w_data, 2);

    return temp;
}

/**
 * @brief       获取某个IO状态
 * @param       pin     : 要获取状态的IO
 * @retval      此IO口的值(状态, 0/1)
 */
int xl9555_pin_read(uint16_t pin)
{
    uint16_t ret;
    uint8_t r_data[2];

    xl9555_read_byte(r_data, 2);

    ret = r_data[1] << 8 | r_data[0];

    return (ret & pin) ? 1 : 0;
}

/**
 * @brief       XL9555的IO配置
 * @param       config_value:IO配置输入或者输出
 * @retval      返回设置的数值
 */
uint16_t xl9555_ioconfig(uint16_t config_value)
{
    /* 从机地址 + CMD + data1(P0) + data2(P1) */
    /* P00、P01、P14、P15、P16、P17为输入,其他引脚为输出 -->1111 0000 0000 0011 注意:0为输出,1为输入*/
    uint8_t data[2];
    esp_err_t err;
    int retry = 3;

    data[0] = (uint8_t)(0xFF & config_value);
    data[1] = (uint8_t)(0xFF & (config_value >> 8));

    do
    {
        err = xl9555_write_byte(XL9555_CONFIG_PORT0_REG, data, 2);
        
        if (err != ESP_OK)
        {
            retry--;
            vTaskDelay(100); 
            ESP_LOGE("IIC", "%s configure %X failed, ret: %d", __func__, config_value, err);
            xl9555_failed = 1;
            
            if ((retry <= 0) && xl9555_failed)
            {
                vTaskDelay(5000); 
                esp_restart();
            }
        }
        else
        {
            xl9555_failed = 0;
            break;
        }
        
    } while (retry);
    
    return config_value;
}

/**
 * @brief       初始化XL9555
 * @param       无
 * @retval      无
 */
void xl9555_init(i2c_obj_t self)
{
    uint8_t r_data[2];

    if (self.init_flag == ESP_FAIL)
    {
        iic_init(I2C_NUM_0);        /* 初始化IIC */
    }

    xl9555_i2c_master = self;
    gpio_config_t gpio_init_struct = {0};
    
    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
    gpio_init_struct.mode = GPIO_MODE_INPUT;
    gpio_init_struct.pin_bit_mask = (1ull << XL9555_INT_IO);
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
    gpio_config(&gpio_init_struct);     /* 配置XL_INT引脚 */

    /* 上电先读取一次清除中断标志 */
    xl9555_read_byte(r_data, 2);
    
    xl9555_ioconfig(0xF003);
    xl9555_pin_write(BEEP_IO, 1);
    xl9555_pin_write(SPK_EN_IO, 1);
}

/**
 * @brief       按键扫描函数
 * @param       mode:0->不连续;1->连续
 * @retval      键值, 定义如下:
 *              KEY0_PRES, 1, KEY0按下
 *              KEY1_PRES, 2, KEY1按下
 *              KEY2_PRES, 3, KEY2按下
 *              KEY3_PRES, 4, KEY3按下
 */
uint8_t xl9555_key_scan(uint8_t mode)
{
    uint8_t keyval = 0;
    static uint8_t key_up = 1;                                          /* 按键按松开标志 */

    if (mode)
    {
        key_up = 1;                                                     /* 支持连按 */
    }
    
    if (key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0  || KEY3 == 0 )) /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        vTaskDelay(10);                                                 /* 去抖动 */
        key_up = 0;

        if (KEY0 == 0)
        {
            keyval = KEY0_PRES;
        }

        if (KEY1 == 0)
        {
            keyval = KEY1_PRES;
        }

        if (KEY2 == 0)
        {
            keyval = KEY2_PRES;
        }

        if (KEY3 == 0)
        {
            keyval = KEY3_PRES;
        }
    }
    else if (KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && KEY3 == 1)          /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
    }

    return keyval;                                                      /* 返回键值 */
}

xl9555.h

#ifndef __XL9555_H
#define __XL9555_H

#include "driver/gpio.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "iic.h"


/* 引脚与相关参数定义 */
#define XL9555_INT_IO               GPIO_NUM_40                     /* XL9555_INT引脚 */
#define XL9555_INT                  gpio_get_level(XL9555_INT_IO)   /* 读取XL9555_INT的电平 */

/* XL9555命令宏 */
#define XL9555_INPUT_PORT0_REG      0                               /* 输入寄存器0地址 */
#define XL9555_INPUT_PORT1_REG      1                               /* 输入寄存器1地址 */
#define XL9555_OUTPUT_PORT0_REG     2                               /* 输出寄存器0地址 */
#define XL9555_OUTPUT_PORT1_REG     3                               /* 输出寄存器1地址 */
#define XL9555_INVERSION_PORT0_REG  4                               /* 极性反转寄存器0地址 */
#define XL9555_INVERSION_PORT1_REG  5                               /* 极性反转寄存器1地址 */
#define XL9555_CONFIG_PORT0_REG     6                               /* 方向配置寄存器0地址 */
#define XL9555_CONFIG_PORT1_REG     7                               /* 方向配置寄存器1地址 */

#define XL9555_ADDR                 0X20                            /* XL9555地址(左移了一位)-->请看手册(9.1. Device Address) */

/* XL9555各个IO的功能 */
#define AP_INT_IO                   0x0001
#define QMA_INT_IO                  0x0002
#define SPK_EN_IO                   0x0004
#define BEEP_IO                     0x0008
#define OV_PWDN_IO                  0x0010
#define OV_RESET_IO                 0x0020
#define GBC_LED_IO                  0x0040
#define GBC_KEY_IO                  0x0080
#define LCD_BL_IO                   0x0100
#define CT_RST_IO                   0x0200
#define SLCD_RST_IO                 0x0400
#define SLCD_PWR_IO                 0x0800
#define KEY3_IO                     0x1000
#define KEY2_IO                     0x2000
#define KEY1_IO                     0x4000
#define KEY0_IO                     0x8000

#define KEY0                        xl9555_pin_read(KEY0_IO)        /* 读取KEY0引脚 */
#define KEY1                        xl9555_pin_read(KEY1_IO)        /* 读取KEY1引脚 */
#define KEY2                        xl9555_pin_read(KEY2_IO)        /* 读取KEY2引脚 */
#define KEY3                        xl9555_pin_read(KEY3_IO)        /* 读取KEY3引脚 */

#define KEY0_PRES                   1                               /* KEY0按下 */
#define KEY1_PRES                   2                               /* KEY1按下 */
#define KEY2_PRES                   3                               /* KEY1按下 */
#define KEY3_PRES                   4                               /* KEY1按下 */

/* 函数声明 */
void xl9555_init(i2c_obj_t self);                                   /* 初始化XL9555 */
int xl9555_pin_read(uint16_t pin);                                  /* 获取某个IO状态 */
uint16_t xl9555_pin_write(uint16_t pin, int val);                   /* 控制某个IO的电平 */
esp_err_t xl9555_read_byte(uint8_t* data, size_t len);              /* 读取XL9555的16位IO值 */
uint8_t xl9555_key_scan(uint8_t mode);                              /* 扫描按键值 */

#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值