ESP32-Board原理图
WS2812
RMT控制
核心代码
main.c
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/rmt_tx.h"
#include "led_ws2812.h"
#define WS2812_GPIO_NUM GPIO_NUM_26
#define WS2812_LED_NUM 12
/** HSV转RGB ,暂无用到
* @brief 将HSV色彩空间转换为RGB色彩空间。
*
* @param h 色相(0-360)
* @param s 饱和度(0-100)
* @param v 明度(此处按百分比0-100缩放,实际对应8位时为0-255)
* @param r 指针,用于存储计算出的红色分量(0-255)
* @param g 指针,用于存储计算出的绿色分量(0-255)
* @param b 指针,用于存储计算出的蓝色分量(0-255)
* @return 无
*/
void led_strip_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
{
h %= 360; // 将色相规范化到[0,360)范围内
// 将明度从[0,100]缩放到[0,255],饱和度保持在[0,100]
uint32_t rgb_max = v * 2.55f; // 基于明度的RGB分量的最大值
uint32_t rgb_min = rgb_max * (100 - s) / 100.0f; // 基于饱和度和明度的RGB分量的最小值
uint32_t i = h / 60; // 确定色相圆上的扇区(0-5)
uint32_t diff = h % 60; // 扇区内剩余的部分
// 根据扇区内的色相计算RGB调整量
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
// 根据扇区计算RGB值
switch (i) {
case 0: // 红色到黄色
*r = rgb_max;
*g = rgb_min + rgb_adj;
*b = rgb_min;
break;
case 1: // 黄色到绿色
*r = rgb_max - rgb_adj;
*g = rgb_max;
*b = rgb_min;
break;
case 2: // 绿色到青色
*r = rgb_min;
*g = rgb_max;
*b = rgb_min + rgb_adj;
break;
case 3: // 青色到蓝色
*r = rgb_min;
*g = rgb_max - rgb_adj;
*b = rgb_max;
break;
case 4: // 蓝色到洋红色
*r = rgb_min + rgb_adj;
*g = rgb_min;
*b = rgb_max;
break;
default: // 洋红色到红色
*r = rgb_max;
*g = rgb_min;
*b = rgb_max - rgb_adj;
break;
}
}
void app_main(void)
{
ws2812_strip_handle_t ws2812_handle = NULL;
int index = 0;
// 初始化WS2812 LED灯带
ws2812_init(WS2812_GPIO_NUM, WS2812_LED_NUM, &ws2812_handle);
while(1)
{
// 红色跑马灯
// 遍历每个LED,逐个点亮红色LED
for(index = 0; index < WS2812_LED_NUM; index++)
{
uint32_t r = 230, g = 20, b = 20; // 设定RGB值为红色
ws2812_write(ws2812_handle, index, r, g, b); // 将当前LED设置为红色
vTaskDelay(pdMS_TO_TICKS(80)); // 延迟80毫秒
}
// 绿色跑马灯
// 遍历每个LED,逐个点亮绿色LED
for(index = 0; index < WS2812_LED_NUM; index++)
{
uint32_t r = 20, g = 230, b = 20; // 设定RGB值为绿色
ws2812_write(ws2812_handle, index, r, g, b); // 将当前LED设置为绿色
vTaskDelay(pdMS_TO_TICKS(80)); // 延迟80毫秒
}
// 蓝色跑马灯
// 遍历每个LED,逐个点亮蓝色LED
for(index = 0; index < WS2812_LED_NUM; index++)
{
uint32_t r = 20, g = 20, b = 230; // 设定RGB值为蓝色
ws2812_write(ws2812_handle, index, r, g, b); // 将当前LED设置为蓝色
vTaskDelay(pdMS_TO_TICKS(80)); // 延迟80毫秒
}
}
}
led_ws2812.c
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "led_ws2812.h"
#include "driver/rmt_tx.h"
static const char *TAG = "led_encoder";
#define LED_STRIP_RESOLUTION_HZ 10000000 // 10MHz 分辨率, 也就是1tick = 0.1us,也就是可以控制的最小时间单元,低于0.1us的脉冲无法产生
//WS2812驱动的描述符
struct ws2812_strip_t
{
rmt_channel_handle_t led_chan; //rmt通道
rmt_encoder_handle_t led_encoder; //rmt编码器
uint8_t *led_buffer; //rgb数据
int led_num; //led个数
};
//自定义编码器
typedef struct {
rmt_encoder_t base; //编码器,里面包含三个需要用户实现的回调函数,encode,del,ret
rmt_encoder_t *bytes_encoder; //字节编码器,调用rmt_new_bytes_encoder函数后创建
rmt_encoder_t *copy_encoder; //拷贝编码器,调用rmt_new_copy_encoder函数后创建
int state; //状态控制
rmt_symbol_word_t reset_code; //结束位的时序
} rmt_led_strip_encoder_t;
/* 发送WS2812数据的函数调用顺序如下
* 1、调用rmt_transmit,需传入RMT通道、发送的数据、编码器参数
* 2、调用编码器的encode函数,在本例程中就是调用rmt_encode_led_strip函数
* 3、调用由rmt_new_bytes_encoder创建的字节编码器编码函数bytes_encoder->encode,将用户数据编码成rmt_symbol_word_t RMT符号
* 4、调用由rmt_new_copy_encoder创建的拷贝编码器编码函数copy_encoder->encode,将复位信号安装既定的电平时间进行编码
* 5、rmt_encode_led_strip函数返回,在底层将信号发送出去(本质上是操作IO管脚高低电平)
*/
/** 编码回调函数
* @param encoder 编码器
* @param channel RMT通道
* @param primary_data 待编码用户数据
* @param data_size 待编码用户数据长度
* @param ret_state 编码状态
* @return RMT符号个数
*/
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
/*
__containerof宏的作用:
通过结构的成员来访问这个结构的地址
在这个函数中,传入参数encoder是rmt_led_strip_encoder_t结构体中的base成员
__containerof宏通过encoder的地址,根据rmt_led_strip_encoder_t的内存排布找到rmt_led_strip_encoder_t* 的首地址
*/
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; //取出字节编码器
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; //取出拷贝编码器
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
rmt_encode_state_t state = RMT_ENCODING_RESET;
size_t encoded_symbols = 0;
switch (led_encoder->state) { //led_encoder->state是自定义的状态,这里只有两种值,0是发送RGB数据,1是发送复位码
case 0: // send RGB data
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
if (session_state & RMT_ENCODING_COMPLETE) { //字节编码完成
led_encoder->state = 1; // switch to next state when current encoding session finished
}
if (session_state & RMT_ENCODING_MEM_FULL) { //缓存不足,本次退出
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
// fall-through
case 1: // send reset code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
led_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE;
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there's no free space for encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
}
/**
* @brief 删除LED灯带编码器
*
* 删除指定的LED灯带编码器,并释放相关资源。
*
* @param encoder 指向要删除的LED灯带编码器的指针
*
* @return ESP_OK 表示成功,其他值表示失败
*/
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->bytes_encoder);
rmt_del_encoder(led_encoder->copy_encoder);
free(led_encoder);
return ESP_OK;
}
/**
* @brief 重置LED灯带编码器
*
* 将LED灯带编码器的状态重置为初始状态。
*
* @param encoder LED灯带编码器指针
*
* @return esp_err_t 类型为 esp_err_t 的错误码。成功时返回 ESP_OK。
*/
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
{
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_encoder_reset(led_encoder->bytes_encoder);
rmt_encoder_reset(led_encoder->copy_encoder);
led_encoder->state = RMT_ENCODING_RESET;
return ESP_OK;
}
/** 创建一个基于WS2812时序的编码器
* @param ret_encoder 返回的编码器,这个编码器在使用rmt_transmit函数传输时会用到
* @return ESP_OK or ESP_FAIL
*/
esp_err_t rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder)
{
esp_err_t ret = ESP_OK;
//创建一个自定义的编码器结构体,用于控制发送编码的流程
rmt_led_strip_encoder_t *led_encoder = NULL;
led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t));
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
led_encoder->base.encode = rmt_encode_led_strip; //这个函数会在rmt发送数据的时候被调用,我们可以在这个函数增加额外代码进行控制
led_encoder->base.del = rmt_del_led_strip_encoder; //这个函数在卸载rmt时被调用
led_encoder->base.reset = rmt_led_strip_encoder_reset; //这个函数在复位rmt时被调用
//新建一个编码器配置(0,1位持续时间,参考芯片手册)
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {
.level0 = 1,
.duration0 = 0.3 * LED_STRIP_RESOLUTION_HZ / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * LED_STRIP_RESOLUTION_HZ / 1000000, // T0L=0.9us
},
.bit1 = {
.level0 = 1,
.duration0 = 0.9 * LED_STRIP_RESOLUTION_HZ / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * LED_STRIP_RESOLUTION_HZ / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 //高位先传输
};
//传入编码器配置,获得数据编码器操作句柄
rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder);
//新建一个拷贝编码器配置,拷贝编码器一般用于传输恒定的字符数据,比如说结束位
rmt_copy_encoder_config_t copy_encoder_config = {};
rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder);
//设定结束位时序
uint32_t reset_ticks = LED_STRIP_RESOLUTION_HZ / 1000000 * 50 / 2; //分辨率/1M=每个ticks所需的us,然后乘以50就得出50us所需的ticks
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
//返回编码器
*ret_encoder = &led_encoder->base;
return ESP_OK;
err:
if (led_encoder) {
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}
/** 初始化WS2812外设
* @param gpio 控制WS2812的管脚
* @param maxled 控制WS2812的个数
* @param led_handle 返回的控制句柄
* @return ESP_OK or ESP_FAIL
*/
esp_err_t ws2812_init(gpio_num_t gpio,int maxled,ws2812_strip_handle_t* handle)
{
struct ws2812_strip_t* led_handle = NULL;
//新增一个WS2812驱动描述: 1-需要分配的内存块数量;sizeof(struct ws2812_strip_t)-需要分配的内存块大小。
led_handle = calloc(1, sizeof(struct ws2812_strip_t));
assert(led_handle);
//按照led个数来分配RGB缓存数据
led_handle->led_buffer = calloc(1,maxled*3);
assert(led_handle->led_buffer);
//设置LED个数
led_handle->led_num = maxled;
//定义一个RMT发送通道配置
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, //默认时钟源
.gpio_num = gpio, //GPIO管脚
.mem_block_symbols = 64, //内存块大小,即 64 * 4 = 256 字节
.resolution_hz = LED_STRIP_RESOLUTION_HZ, //RMT通道的分辨率10000000hz=0.1us,也就是可以控制的最小时间单元
.trans_queue_depth = 4, //底层后台发送的队列深度
};
//创建一个RMT发送通道
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_handle->led_chan));
//创建自定义编码器(重点函数),所谓编码,就是发射红外时加入我们的时序控制
ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&led_handle->led_encoder));
//使能RMT通道
ESP_ERROR_CHECK(rmt_enable(led_handle->led_chan));
//返回WS2812操作句柄
*handle = led_handle;
return ESP_OK;
}
/** 反初始化WS2812外设
* @param handle 初始化的句柄
* @return ESP_OK or ESP_FAIL
*/
esp_err_t ws2812_deinit(ws2812_strip_handle_t handle)
{
if(!handle)
return ESP_OK;
rmt_del_encoder(handle->led_encoder);
if(handle->led_buffer)
free(handle->led_buffer);
free(handle);
return ESP_OK;
}
/** 向某个WS2812写入RGB数据
* @param handle 句柄
* @param index 第几个WS2812(0开始)
* @param r,g,b RGB数据
* @return ESP_OK or ESP_FAIL
*/
esp_err_t ws2812_write(ws2812_strip_handle_t handle,uint32_t index,uint32_t r,uint32_t g,uint32_t b)
{
// 配置RMT传输参数
rmt_transmit_config_t tx_config = {
.loop_count = 0, // 不循环发送
};
// 检查索引是否超出LED数量范围
if(index >= handle->led_num)
return ESP_FAIL; // 返回失败
// 计算LED缓冲区的起始位置
uint32_t start = index*3;
// 设置LED颜色数据(注意:WS2812的数据顺序是GRB)
handle->led_buffer[start+0] = g & 0xff; // 设置绿色分量
handle->led_buffer[start+1] = r & 0xff; // 设置红色分量
handle->led_buffer[start+2] = b & 0xff; // 设置蓝色分量
// 发送LED数据
return rmt_transmit(handle->led_chan, handle->led_encoder, handle->led_buffer, handle->led_num*3, &tx_config);
}
led_ws2812.h
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include "driver/rmt_encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct ws2812_strip_t *ws2812_strip_handle_t;
/** 初始化WS2812外设
* @param gpio 控制WS2812的管脚
* @param maxled 控制WS2812的个数
* @param led_handle 返回的控制句柄
* @return ESP_OK or ESP_FAIL
*/
esp_err_t ws2812_init(gpio_num_t gpio,int maxled,ws2812_strip_handle_t* led_handle);
/** 反初始化WS2812外设
* @param handle 初始化的句柄
* @return ESP_OK or ESP_FAIL
*/
esp_err_t ws2812_deinit(ws2812_strip_handle_t handle);
/** 向某个WS2812写入RGB数据
* @param handle 句柄
* @param index 第几个WS2812(0开始)
* @param r,g,b RGB数据
* @return ESP_OK or ESP_FAIL
*/
esp_err_t ws2812_write(ws2812_strip_handle_t handle,uint32_t index,uint32_t r,uint32_t g,uint32_t b);
#ifdef __cplusplus
}
#endif
DHT11
介绍
main.c
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <nvs_flash.h>
#include <driver/rmt_rx.h>
#include <driver/rmt_tx.h>
#include <soc/rmt_reg.h>
#include "driver/gpio.h"
#include <esp_log.h>
#include <freertos/queue.h>
#include "esp32/rom/ets_sys.h"
#define DHT11_GPIO 25 // DHT11引脚定义
const static char *TAG = "DHT11_Demo";
// 温度是10倍,/10有1位小数
int temp_x10 = 123;
int humidity = 60;
const int channel = 0;
uint8_t DHT11_PIN = -1;
//rmt接收通道句柄
static rmt_channel_handle_t rx_chan_handle = NULL;
//数据接收队列
static QueueHandle_t rx_receive_queue = NULL;
// 将RMT读取到的脉冲数据处理为温度和湿度
static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10);
//接收完成回调函数
static bool example_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
QueueHandle_t rx_receive_queue = (QueueHandle_t)user_data;
// send the received RMT symbols to the parser task
xQueueSendFromISR(rx_receive_queue, edata, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
// DHT11 初始化
void DHT11_Init(uint8_t dht11_pin)
{
DHT11_PIN = dht11_pin;
rmt_rx_channel_config_t rx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // 选择时钟源
.resolution_hz = 1000 * 1000, // 1 MHz 滴答分辨率,即 1 滴答 = 1 µs
.mem_block_symbols = 64, // 内存块大小,即 64 * 4 = 256 字节
.gpio_num = dht11_pin, // GPIO 编号
.flags.invert_in = false, // 不反转输入信号
.flags.with_dma = false, // 不需要 DMA 后端
};
//创建rmt接收通道
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan_handle));
//新建接收数据队列
rx_receive_queue = xQueueCreate(20, sizeof(rmt_rx_done_event_data_t));
assert(rx_receive_queue);
//注册接收完成回调函数
ESP_LOGI(TAG, "register RX done callback");
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = example_rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_chan_handle, &cbs, rx_receive_queue));
//使能RMT接收通道
ESP_ERROR_CHECK(rmt_enable(rx_chan_handle));
}
// 将RMT读取到的脉冲数据处理为温度和湿度(rmt_symbol_word_t成为RMT符号)
static int parse_items(rmt_symbol_word_t *item, int item_num, int *humidity, int *temp_x10)
{
int i = 0;
unsigned int rh = 0, temp = 0, checksum = 0;
if (item_num < 42){ // 检查是否有足够的脉冲数
ESP_LOGI(TAG, "item_num < 42 %d",item_num);
return 0;
}
item++; // 跳过开始信号脉冲
for (i = 0; i < 16; i++, item++) // 提取湿度数据
{
uint16_t duration = 0;
if(item->level0)
duration = item->duration0;
else
duration = item->duration1;
rh = (rh << 1) + (duration < 35 ? 0 : 1);
}
for (i = 0; i < 16; i++, item++) // 提取温度数据
{
uint16_t duration = 0;
if(item->level0)
duration = item->duration0;
else
duration = item->duration1;
temp = (temp << 1) + (duration < 35 ? 0 : 1);
}
for (i = 0; i < 8; i++, item++){ // 提取校验数据
uint16_t duration = 0;
if(item->level0)
duration = item->duration0;
else
duration = item->duration1;
checksum = (checksum << 1) + (duration < 35 ? 0 : 1);
}
// 检查校验
if ((((temp >> 8) + temp + (rh >> 8) + rh) & 0xFF) != checksum){
ESP_LOGI(TAG, "Checksum failure %4X %4X %2X\n", temp, rh, checksum);
return 0;
}
// 返回数据
*humidity = rh >> 8;
*temp_x10 = (temp >> 8) * 10 + (temp & 0xFF);
return 1;
}
// 使用RMT接收DHT11数据
int DHT11_StartGet(int *temp_x10, int *humidity)
{
//发送20ms开始信号脉冲启动DHT11单总线
gpio_set_direction(DHT11_PIN, GPIO_MODE_OUTPUT);
gpio_set_level(DHT11_PIN, 1);
ets_delay_us(1000);
gpio_set_level(DHT11_PIN, 0);
ets_delay_us(20000);
//拉高20us
gpio_set_level(DHT11_PIN, 1);
ets_delay_us(20);
//信号线设置为输入准备接收数据
gpio_set_direction(DHT11_PIN, GPIO_MODE_INPUT);
gpio_set_pull_mode(DHT11_PIN,GPIO_PULLUP_ONLY);
//启动RMT接收器以获取数据
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 1000, //最小脉冲宽度(1us),信号长度小于这个值,视为干扰
.signal_range_max_ns = 200*1000, //最大脉冲宽度(200us),信号长度大于这个值,视为结束信号
};
static rmt_symbol_word_t raw_symbols[64]; //接收缓存
static rmt_rx_done_event_data_t rx_data; //实际接收到的数据
ESP_ERROR_CHECK(rmt_receive(rx_chan_handle, raw_symbols, sizeof(raw_symbols), &receive_config));
// wait for RX done signal
if (xQueueReceive(rx_receive_queue, &rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) {
// parse the receive symbols and print the result
return parse_items(rx_data.received_symbols, rx_data.num_symbols,humidity, temp_x10);
}
return 0;
}
// 温度 湿度变量
int temp = 0,hum = 0;
// 主函数
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
vTaskDelay(100 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "[APP] APP Is Start!~\r\n");
ESP_LOGI(TAG, "[APP] IDF Version is %d.%d.%d",ESP_IDF_VERSION_MAJOR,ESP_IDF_VERSION_MINOR,ESP_IDF_VERSION_PATCH);
ESP_LOGI(TAG, "[APP] Free memory: %lu bytes", esp_get_free_heap_size());
ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version());
DHT11_Init(DHT11_GPIO);
while (1){
if (DHT11_StartGet(&temp, &hum)){
ESP_LOGI(TAG, "temp->%i.%i C hum->%i%%", temp / 10, temp % 10, hum);
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
ADC-热敏电阻
介绍
main.c
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "ntc.h"
#define TAG "main"
void app_main(void)
{
temp_ntc_init();
while(1)
{
ESP_LOGI(TAG,"current temp:%.2f",get_temp());
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
ntc.c
#include <string.h>
#include <stdio.h>
#include "ntc.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_adc/adc_continuous.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_adc/adc_cali.h"
#include "esp_adc/adc_cali_scheme.h"
#define TAG "adc"
/*
GPIO32 ADC1_CH4
GPIO33 ADC1_CH5
GPIO34 ADC1_CH6
GPIO35 ADC1_CH7
GPIO36 ADC1_CH0
GPIO37 ADC1_CH1
GPIO38 ADC1_CH2
GPIO39 ADC1_CH3
*/
#define TEMP_ADC_CHANNEL ADC_CHANNEL_0 //ADC转换通道(ADC1有8个通道,对应gpio32 - gpio39)
#define NTC_RES 10000 //NTC电阻标称值(在电路中和NTC一起串进电路的那个电阻,一般是10K,100K)
#define ADC_V_MAX 3300 //最大接入电压值
#define ADC_VALUE_NUM 10 //每次采样的电压
static bool do_calibration1 = false; //是否需要校准
static volatile float s_temp_value = 0.0f; //室内温度
static int s_adc_raw[ADC_VALUE_NUM]; //ADC采样值
static int s_voltage_raw[ADC_VALUE_NUM]; //转换后的电压值
typedef struct
{
int8_t temp; //温度
uint32_t res; //温度对应的阻值
}temp_res_t;
//NTC温度表
static const temp_res_t s_ntc_table[] =
{
{-10,51815},
{-9,49283},
{-8,46889},
{-7,44624},
{-6,42481},
{-5,40450},
{-4,38526},
{-3,36702},
{-2,34971},
{-1,33329},
{0,31770},
{1,30253},
{2,28815},
{3,27453},
{4,26160},
{5,24935},
{6,23772},
{7,22668},
{8,21620},
{9,20626},
{10,19681},
{11,18784},
{12,17932},
{13,17122},
{14,16353},
{15,15621},
{16,14925},
{17,14263},
{18,13634},
{19,13035},
{20,12465},
{21,11922},
{22,11406},
{23,10914},
{24,10446},
{25,10000},
{26,9574},
{27,9169},
{28,8783},
{29,8415},
{30,8064},
{31,7729},
{32,7410},
{33,7105},
{34,6814},
{35,6537},
{36,6272},
{37,6019},
{38,5778},
{39,5547},
{40,5327},
{41,5116},
{42,4915},
{43,4722},
{44,4539},
{45,4363},
{46,4195},
{47,4034},
{48,3880},
{49,3733},
{50,3592},
};
//NTC表长度
static const uint16_t s_us_ntc_table_num = sizeof(s_ntc_table)/sizeof(s_ntc_table[0]);
//ADC操作句柄
static adc_oneshot_unit_handle_t s_adc_handle = NULL;
//转换句柄
static adc_cali_handle_t adc1_cali_handle = NULL;
//NTC温度采集任务
static void temp_adc_task(void*);
static float get_ntc_temp(uint32_t res);
/*---------------------------------------------------------------
ADC校准方案,创建校准方案后,会从官方预烧录的参数中对采集到的电压进行校准
---------------------------------------------------------------*/
static bool example_adc_calibration_init(adc_unit_t unit, adc_atten_t atten, adc_cali_handle_t *out_handle)
{
adc_cali_handle_t handle = NULL;
esp_err_t ret = ESP_FAIL;
bool calibrated = false;
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
if (!calibrated) {
ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
if (ret == ESP_OK) {
calibrated = true;
}
}
#endif
*out_handle = handle;
if (ret == ESP_OK) {
ESP_LOGI(TAG, "Calibration Success");
} else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
ESP_LOGW(TAG, "eFuse not burnt, skip software calibration");
} else {
ESP_LOGE(TAG, "Invalid arg or no memory");
}
return calibrated;
}
/**
* 温度检测初始化
* @param 无
* @return 无
*/
void temp_ntc_init(void)
{
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1, //WIFI和ADC2无法同时启用,这里选择ADC1
};
//启用单次转换模式
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &s_adc_handle));
//-------------ADC1 Config---------------//
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_12, //分辨率
.atten = ADC_ATTEN_DB_12,
//衰减倍数,ESP32设计的ADC参考电压为1100mV,只能测量0-1100mV之间的电压,如果要测量更大范围的电压
//需要设置衰减倍数
/*以下是对应可测量范围
ADC_ATTEN_DB_0 100 mV ~ 950 mV
ADC_ATTEN_DB_2_5 100 mV ~ 1250 mV
ADC_ATTEN_DB_6 150 mV ~ 1750 mV
ADC_ATTEN_DB_12 150 mV ~ 2450 mV
*/
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(s_adc_handle, TEMP_ADC_CHANNEL, &config));
//-------------ADC1 Calibration Init---------------//
do_calibration1 = example_adc_calibration_init(ADC_UNIT_1, ADC_ATTEN_DB_12, &adc1_cali_handle);
//新建一个任务,不断地进行ADC和温度计算
xTaskCreatePinnedToCore(temp_adc_task, "adc_task", 2048, NULL,2, NULL, 1);
}
/**
* 获取温度值
* @param 无
* @return 温度
*/
float get_temp(void)
{
return s_temp_value;
}
static void temp_adc_task(void* param)
{
uint16_t adc_cnt = 0;
while(1)
{
adc_oneshot_read(s_adc_handle, TEMP_ADC_CHANNEL, &s_adc_raw[adc_cnt]);
if (do_calibration1) {
adc_cali_raw_to_voltage(adc1_cali_handle, s_adc_raw[adc_cnt], &s_voltage_raw[adc_cnt]);
}
adc_cnt++;
if(adc_cnt >= 10)
{
int i = 0;
//用平均值进行滤波
uint32_t voltage = 0;
uint32_t res = 0;
for(i = 0;i < ADC_VALUE_NUM;i++)
{
voltage += s_voltage_raw[i];
}
voltage = voltage/ADC_VALUE_NUM;
if(voltage < ADC_V_MAX)
{
//电压转换为相应的电阻值
res = (voltage*NTC_RES)/(ADC_V_MAX-voltage);
//根据电阻值查表找出对应的温度
s_temp_value = get_ntc_temp(res);
}
adc_cnt = 0;
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
/** 线性插值,根据一元一次方程两点式,计算出K和B值,然后将X代入方程中计算出Y值
* 所谓的线性插值,就是已知两点的坐标,由于两点坐标比较接近,将这两点之间的连线认为是一条直线
* 然后通过这两点计算出这条直线的k和b值,
* 插值的意思是,有一个x值,处于x1和x2之间,想要求出对应的y值
* 这时候可以通过刚才计算出直线方程,计算出y值
* @param x 需要计算的X坐标
* @param x1,x2,y1,y2 两点式坐标
* @return y值
*/
static float linera_interpolation(int32_t x,int32_t x1, int32_t x2, int32_t y1, int32_t y2)
{
float k = (float)(y2 - y1) /(float)(x2 - x1);
float b = (float)(x2 * y1 - x1 * y2) / (float)(x2 - x1);
float y = k * x + b;
return y;
}
/** 二分查找,通过电阻值查找出温度值对应的下标
* @param res 电阻值
* @param left ntc表的左边界
* @param right ntc表的右边界
* @return 温度值数组下标
*/
static int find_ntc_index(uint32_t res,uint16_t left, uint16_t right)
{
uint16_t middle = (left + right) / 2;
if (right <= left || left == middle)
{
if (res >= s_ntc_table[left].res)
return left;
else
return right;
}
if (res > s_ntc_table[middle].res)
{
right = middle;
return find_ntc_index(res,left, right);
}
else if (res < s_ntc_table[middle].res)
{
left = middle;
return find_ntc_index(res,left, right);
}
else
return middle;
}
//根据电阻值、NTC表查出对应温度
static float get_ntc_temp(uint32_t res)
{
uint16_t left = 0;
uint16_t right = s_us_ntc_table_num - 1;
int index = find_ntc_index(res,left,right);
if (res == s_ntc_table[index].res)
return s_ntc_table[index].temp;
if (index == 0 || index == s_us_ntc_table_num - 1)
{
return s_ntc_table[index].temp;
}
else
{
int next_index = index + 1;
return linera_interpolation(res, s_ntc_table[index].res, s_ntc_table[next_index].res,
s_ntc_table[index].temp, s_ntc_table[next_index].temp);
}
}
ntc.h
#ifndef _NTC_H_
#define _NTC_H_
/**
* 温度检测初始化
* @param 无
* @return 无
*/
void temp_ntc_init(void);
/**
* 获取温度值
* @param 无
* @return 温度
*/
float get_temp(void);
#endif
HC-SR04超声波传感器
介绍
main.c
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_private/esp_clk.h"
#include "driver/mcpwm_cap.h"
#include "driver/gpio.h"
const static char *TAG = "example";
//TRIG和ECHO对应的GPIO
#define HC_SR04_TRIG_GPIO GPIO_NUM_32
#define HC_SR04_ECHO_GPIO GPIO_NUM_33
static bool hc_sr04_echo_callback(mcpwm_cap_channel_handle_t cap_chan, const mcpwm_capture_event_data_t *edata, void *user_data)
{
static uint32_t cap_val_begin_of_sample = 0;
static uint32_t cap_val_end_of_sample = 0;
TaskHandle_t task_to_notify = (TaskHandle_t)user_data;
BaseType_t high_task_wakeup = pdFALSE;
//判断边沿,上升沿记录捕获的计数值
if (edata->cap_edge == MCPWM_CAP_EDGE_POS) {
// store the timestamp when pos edge is detected
cap_val_begin_of_sample = edata->cap_value;
cap_val_end_of_sample = cap_val_begin_of_sample;
} else {
//如果是下降沿,也记录捕获的计数值
cap_val_end_of_sample = edata->cap_value;
//两个计数值的差值,就是脉宽长度
uint32_t tof_ticks = cap_val_end_of_sample - cap_val_begin_of_sample;
// 通知任务计数差值
xTaskNotifyFromISR(task_to_notify, tof_ticks, eSetValueWithOverwrite, &high_task_wakeup);
}
return high_task_wakeup == pdTRUE;
}
/**
* @brief generate single pulse on Trig pin to start a new sample
*/
static void gen_trig_output(void)
{
gpio_set_level(HC_SR04_TRIG_GPIO, 1); // set high
esp_rom_delay_us(10);
gpio_set_level(HC_SR04_TRIG_GPIO, 0); // set low
}
void app_main(void)
{
ESP_LOGI(TAG, "Install capture timer");
//新建一个捕获定时器,使用默认的时钟源
mcpwm_cap_timer_handle_t cap_timer = NULL;
mcpwm_capture_timer_config_t cap_conf = {
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
.group_id = 0,
};
ESP_ERROR_CHECK(mcpwm_new_capture_timer(&cap_conf, &cap_timer));
//新建一个捕获通道,把捕获定时器与捕获通道绑定起来,采取双边捕获的策略
ESP_LOGI(TAG, "Install capture channel");
mcpwm_cap_channel_handle_t cap_chan = NULL;
mcpwm_capture_channel_config_t cap_ch_conf = {
.gpio_num = HC_SR04_ECHO_GPIO,
.prescale = 1,
// capture on both edge
.flags.neg_edge = true,
.flags.pos_edge = true,
// pull up internally
.flags.pull_up = true,
};
ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer, &cap_ch_conf, &cap_chan));
//新建一个捕获事件回调函数,当捕获到相关的边沿,则调用这个回调函数
ESP_LOGI(TAG, "Register capture callback");
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
mcpwm_capture_event_callbacks_t cbs = {
.on_cap = hc_sr04_echo_callback,
};
ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_chan, &cbs, cur_task));
//使能捕获通道
ESP_LOGI(TAG, "Enable capture channel");
ESP_ERROR_CHECK(mcpwm_capture_channel_enable(cap_chan));
//初始化Trig引脚,设置为输出
ESP_LOGI(TAG, "Configure Trig pin");
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << HC_SR04_TRIG_GPIO,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
ESP_ERROR_CHECK(gpio_set_level(HC_SR04_TRIG_GPIO, 0));
//使能捕获定时器
ESP_LOGI(TAG, "Enable and start capture timer");
ESP_ERROR_CHECK(mcpwm_capture_timer_enable(cap_timer));
ESP_ERROR_CHECK(mcpwm_capture_timer_start(cap_timer));
uint32_t tof_ticks;
while (1) {
//产生一个Trig
gen_trig_output();
// 等待捕获完成信号,
if (xTaskNotifyWait(0x00, ULONG_MAX, &tof_ticks, pdMS_TO_TICKS(1000)) == pdTRUE) {
//计算脉宽时间长度,从任务通知获取到的tof_ticks是计数,我们需要计算出对应的时间
/*
tof_ticks:计数
esp_clk_apb_freq():计数时钟频率
1/esp_clk_apb_freq():计数时钟周期,也就是1个tof_ticks时间是多少秒
1000000/esp_clk_apb_freq():一个tof_ticks时间是多少us
因此tof_ticks * (1000000.0 / esp_clk_apb_freq())就是总的脉宽时长
*/
float pulse_width_us = tof_ticks * (1000000.0 / esp_clk_apb_freq());
if (pulse_width_us > 35000) {
// 脉宽太长,超出了SR04的计算范围
continue;
}
/*
计算公式如下:
距离D=音速V*T/2;(T是上述的脉宽时间,因为是来回时间,所以要除以2才是单程时间)
D(单位cm)=340m/s*Tus;
D=34000cm/s*(10^-6)Ts/2;(1us=(10^-6)s)
D=17000*(10^-6)*T
D=0.017*T
D=T/58
所以下面这个公式/58是这样来的
*/
float distance = (float) pulse_width_us / 58;
ESP_LOGI(TAG, "Measured distance: %.2fcm", distance);
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
SD-sdmmc
介绍
main.c
/* SD card and FAT filesystem 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.
*/
// This example uses SDMMC peripheral to communicate with SD card.
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "driver/sdmmc_host.h"
#define EXAMPLE_MAX_CHAR_SIZE 64
static const char *TAG = "example";
#define MOUNT_POINT "/sdcard" //挂载点名称
/**
* @brief 向指定路径的文件中写入数据
*
* 将给定的数据写入到指定路径的文件中。
*
* @param path 文件路径
* @param data 要写入的数据
*
* @return esp_err_t 错误码,ESP_OK 表示成功,ESP_FAIL 表示失败
*/
static esp_err_t s_example_write_file(const char *path, char *data)
{
// 记录日志,表示正在打开文件
ESP_LOGI(TAG, "Opening file %s", path);
// 以写入模式打开文件
FILE *f = fopen(path, "w");
// 判断文件是否成功打开
if (f == NULL) {
// 如果文件打开失败,记录错误日志
ESP_LOGE(TAG, "Failed to open file for writing");
// 返回打开文件失败的状态
return ESP_FAIL;
}
// 将数据写入文件
// fprintf(f, data);
fputs(data, f);
// 关闭文件
fclose(f);
// 记录日志,表示文件写入完成
ESP_LOGI(TAG, "File written");
// 返回文件写入成功的状态
return ESP_OK;
}
/**
* @brief 从指定路径读取文件内容
*
* 从给定的文件路径中读取一行内容,并打印出来。
*
* @param path 文件路径
*
* @return 返回 ESP_OK 表示成功,返回 ESP_FAIL 表示失败
*/
static esp_err_t s_example_read_file(const char *path)
{
// 打印读取文件的日志信息
ESP_LOGI(TAG, "Reading file %s", path);
// 尝试以只读模式打开文件
FILE *f = fopen(path, "r");
// 检查文件是否成功打开
if (f == NULL) {
// 如果文件打开失败,打印错误日志并返回失败错误码
ESP_LOGE(TAG, "Failed to open file for reading");
return ESP_FAIL;
}
// 定义一个字符数组用于存储文件内容
char line[EXAMPLE_MAX_CHAR_SIZE];
// 从文件中读取一行内容
fgets(line, sizeof(line), f);
// 关闭文件
fclose(f);
// 去除换行符
// strip newline
char *pos = strchr(line, '\n'); // 用于在字符串中查找第一次出现的指定字符
if (pos) {
*pos = '\0';
}
// 打印从文件中读取的内容
ESP_LOGI(TAG, "Read from file: '%s'", line);
// 返回成功错误码
return ESP_OK;
}
void app_main(void)
{
esp_err_t ret;
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = true, //挂载失败是否执行格式化
.max_files = 5, //最大可打开文件数
.allocation_unit_size = 4 * 1024 //执行格式化时的分配单元大小(分配单元越大,读写越快)
};
sdmmc_card_t *card;
const char mount_point[] = MOUNT_POINT;
ESP_LOGI(TAG, "Initializing SD card");
ESP_LOGI(TAG, "Using SDMMC peripheral");
//默认配置,速度20MHz,使用卡槽1
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
//默认的IO管脚配置,
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
//4位数据
slot_config.width = 4;
//管脚启用内部上拉
slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;
ESP_LOGI(TAG, "Mounting filesystem");
ret = esp_vfs_fat_sdmmc_mount(mount_point, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount filesystem. ");
} else {
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret));
}
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, card);
// Use POSIX and C standard library functions to work with files:
// First create a file.
const char *file_hello = MOUNT_POINT"/hello.txt";
char data[EXAMPLE_MAX_CHAR_SIZE];
snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Hello", card->cid.name);
ret = s_example_write_file(file_hello, data);
if (ret != ESP_OK) {
return;
}
const char *file_foo = MOUNT_POINT"/foo.txt";
// Check if destination file exists before renaming
struct stat st;
if (stat(file_foo, &st) == 0) {
// Delete it if it exists
unlink(file_foo);
}
// Rename original file
ESP_LOGI(TAG, "Renaming file %s to %s", file_hello, file_foo);
if (rename(file_hello, file_foo) != 0) {
ESP_LOGE(TAG, "Rename failed");
return;
}
ret = s_example_read_file(file_foo);
if (ret != ESP_OK) {
return;
}
// Format FATFS
#if 0
ret = esp_vfs_fat_sdcard_format(mount_point, card);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to format FATFS (%s)", esp_err_to_name(ret));
return;
}
if (stat(file_foo, &st) == 0) {
ESP_LOGI(TAG, "file still exists");
return;
} else {
ESP_LOGI(TAG, "file doesnt exist, format done");
}
#endif
const char *file_nihao = MOUNT_POINT"/nihao.txt";
memset(data, 0, EXAMPLE_MAX_CHAR_SIZE);
snprintf(data, EXAMPLE_MAX_CHAR_SIZE, "%s %s!\n", "Nihao", card->cid.name);
ret = s_example_write_file(file_nihao, data);
if (ret != ESP_OK) {
return;
}
//Open file for reading
ret = s_example_read_file(file_nihao);
if (ret != ESP_OK) {
return;
}
// All done, unmount partition and disable SDMMC peripheral
esp_vfs_fat_sdcard_unmount(mount_point, card);
ESP_LOGI(TAG, "Card unmounted");
}
UART
串行通信是一种常用的数据传输方式,其中UART(通用异步收发传输器)是一种重要的串行通信协议。
1. 工作原理
UART是一种异步串行通信协议,主要用于两个设备之间以字节(8位)为单位进行数据传输。它通过一根数据线进行发送(TX)和接收(RX),并使用起始位、数据位、校验位和停止位来框定每个数据包。
2. 数据格式
UART通信的数据格式通常包含以下几个部分:
- 起始位:通常是1位,表示数据传输的开始。
- 数据位:通常是5到9位,代表实际传输的数据。
- 校验位(可选):用于错误检测,通常是1位。
- 停止位:通常是1到2位,表示数据传输的结束。
3. 波特率
波特率是指每秒传输的码元数。在UART通信中,发送方和接收方的波特率必须匹配,以确保数据能够正确传输。
4. 硬件连接
UART通信通常使用两个信号线:发送线(TX)和接收线(RX)。在连接设备时,发送线应连接到接收线,反之亦然。
GPIO交换矩阵
GPIO(通用输入输出,General Purpose Input/Output)交换矩阵是一种用于数字电路和微控制器的功能,允许用户灵活配置和切换GPIO引脚的功能,从而实现不同的信号连接和控制。以下是关于GPIO交换矩阵的基本知识:
1. 概念
GPIO交换矩阵是一个逻辑网络,能够通过软件配置实现不同GPIO引脚的输入和输出功能。它使得同一组GPIO引脚可以根据需要被配置为不同的信号源或信号目的地。
2. 工作原理
在一个带有GPIO交换矩阵的系统中,每个GPIO引脚可以通过控制信号与内部的其他功能模块(如外设、传感器、驱动器等)进行连接。用户可以通过编程来设置这些连接,从而实现所需的功能。例如,可以将某个引脚配置为PWM输出,而另一个引脚配置为外部中断输入。
3. 主要特点
- 灵活性:允许多种功能灵活配置,适应不同应用的需求。
- 节省空间:减少了物理引脚的数量需求,降低了PCB设计的复杂性。
- 可编程性:用户可以通过软件轻松改变GPIO引脚的配置。
4. 应用场景
GPIO交换矩阵广泛应用于各种嵌入式系统、单片机、FPGA和其他数字电路设计中。常见的应用包括:
- 外设接口的选择与控制,如传感器、显示器等。
- 不同通信协议的选择,如SPI、I2C、UART等。
- 简化电路设计,提高系统的可扩展性。
- IO_MUX :GPIO复用寄存器
- GPIO34、35、36、39这几个引脚只能作为输入
代码示例
#include <stdio.h>
#include "driver/uart.h"
#include "esp_log.h"
#include "driver/gpio.h"
#define USER_UART UART_NUM_2
#define TAG "UART_TEST"
static uint8_t uart_buffer[1024];
static QueueHandle_t uart_queue;
void app_main(void)
{
uart_event_t uart_ev;
// 初始化UART配置结构体
uart_config_t uart_cfg = {
.baud_rate = 115200, // 波特率设置为115200
.data_bits = UART_DATA_8_BITS, // 数据位设置为8位
.parity = UART_PARITY_DISABLE, // 禁用奇偶校验
.stop_bits = UART_STOP_BITS_1, // 停止位设置为1位
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE // 禁用硬件流控
};
// 应用UART配置
uart_param_config(USER_UART, &uart_cfg);
// 设置UART引脚 32-RX, 33-TX
uart_set_pin(USER_UART, GPIO_NUM_32, GPIO_NUM_33, -1, -1);
// 安装UART驱动
uart_driver_install(USER_UART, 1024, 1024, 20, &uart_queue, 0);
// 主循环
while (1)
{
/* TODO: 测试UART功能 --- OK
// 从UART读取数据
int rec = uart_read_bytes(USER_UART, uart_buffer, 1024, pdMS_TO_TICKS(50));
// 如果读取到数据
if (rec) {
// 将读取到的数据写回UART
uart_write_bytes(USER_UART, uart_buffer, rec);
}
*/
// TODO: 测试QueueHandle_t队列句柄
if (pdTRUE == xQueueReceive(uart_queue, &uart_ev, portMAX_DELAY))
{
switch(uart_ev.type)
{
case UART_DATA:
ESP_LOGI(TAG, "UART2 receive len:%i", uart_ev.size);
uart_read_bytes(USER_UART, uart_buffer, uart_ev.size, pdMS_TO_TICKS(1000));
uart_write_bytes(USER_UART, uart_buffer, uart_ev.size);
break;
case UART_FIFO_OVF:
ESP_LOGI(TAG, "UART_FIFO_OVF\n");
uart_flush_input(USER_UART);
xQueueReset(uart_queue);
break;
case UART_BUFFER_FULL:
ESP_LOGI(TAG, "UART_BUFFER_FULL\n");
uart_flush_input(USER_UART);
xQueueReset(uart_queue);
break;
default:break;
}
}
}
}
SPI-ST7789-显示屏
SPI介绍
显示-SPI接口
触摸-I2C接口
SPI四种工作模式
这四种模式的区别在于CLK空闲时的状态和采样的时刻。
st7789_driver.h
#ifndef _ST7789_DRIVER_H_
#define _ST7789_DRIVER_H_
#include "driver/gpio.h"
#include "esp_err.h"
typedef void(*lcd_flush_done_cb)(void* param);
typedef struct
{
gpio_num_t mosi; //数据
gpio_num_t clk; //时钟
gpio_num_t cs; //片选
gpio_num_t dc; //命令
gpio_num_t rst; //复位
gpio_num_t bl; //背光
uint32_t spi_fre; //spi总线速率
uint16_t width; //宽
uint16_t height; //长
uint8_t spin; //选择方向(0不旋转,1顺时针旋转90, 2旋转180,3顺时针旋转270)
lcd_flush_done_cb done_cb; //数据传输完成回调函数
void* cb_param; //回调函数参数
}st7789_cfg_t;
/** st7789初始化
* @param st7789_cfg_t 接口参数
* @return 成功或失败
*/
esp_err_t st7789_driver_hw_init(st7789_cfg_t* cfg);
/** st7789写入显示数据
* @param x1,x2,y1,y2:显示区域
* @return 无
*/
void st7789_flush(int x1,int x2,int y1,int y2,void *data);
/** 控制背光
* @param enable 是否使能背光
* @return 无
*/
void st7789_lcd_backlight(bool enable);
#endif
st7789_driver.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_err.h"
#include "esp_log.h"
#include "st7789_driver.h"
#include "lvgl/lvgl.h"
#define LCD_SPI_HOST SPI2_HOST
static const char* TAG = "st7789";
//lcd操作句柄
static esp_lcd_panel_io_handle_t lcd_io_handle = NULL;
//刷新完成回调函数
static lcd_flush_done_cb s_flush_done_cb = NULL;
//背光GPIO
static gpio_num_t s_bl_gpio = -1;
/**
* @brief 通知刷新准备完成的回调函数
*
* 当LCD刷新准备完成时,该函数将被调用。
*
* @param panel_io LCD面板IO句柄
* @param edata 事件数据指针
* @param user_ctx 用户上下文指针
*
* @return 如果函数执行成功,则返回false;否则返回true
*/
static bool notify_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
// 如果存在刷新完成回调函数
if(s_flush_done_cb)
// 调用刷新完成回调函数
s_flush_done_cb(user_ctx);
// 返回false
return false;
}
/**
* @brief 初始化ST7789驱动硬件
*
* 此函数用于初始化ST7789液晶显示器的硬件部分,包括SPI总线、GPIO配置、LCD操作句柄的创建以及LCD的初始化命令发送。
*
* @param cfg 指向st7789_cfg_t结构的指针,包含初始化所需的配置信息
*
* @return esp_err_t 返回操作结果,成功返回ESP_OK,失败返回相应的错误码
*/
esp_err_t st7789_driver_hw_init(st7789_cfg_t* cfg)
{
// 初始化SPI总线配置
spi_bus_config_t buscfg = {
.sclk_io_num = cfg->clk, // 设置SPI时钟引脚
.mosi_io_num = cfg->mosi, // 设置数据输出引脚
.miso_io_num = -1, // 只写入不读取,所以此引脚不使用
.quadwp_io_num = -1, // 不使用四线模式的写保护引脚
.quadhd_io_num = -1, // 不使用四线模式的读保护引脚
.flags = SPICOMMON_BUSFLAG_MASTER, // 设置SPI为主模式
.max_transfer_sz = cfg->width * 40 * sizeof(uint16_t), // 最大DMA传输字节数,根据LCD分辨率和色深计算
};
// 初始化SPI总线,检查返回值以确保初始化成功
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
// 设置刷新完成的回调函数
s_flush_done_cb = cfg->done_cb;
// 设置背光GPIO引脚
s_bl_gpio = cfg->bl;
// 配置背光控制GPIO
gpio_config_t bl_gpio_cfg = {
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁止上拉电阻
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁止下拉电阻
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.intr_type = GPIO_INTR_DISABLE, // 禁用GPIO中断
.pin_bit_mask = (1 << cfg->bl) // 指定背光控制的GPIO引脚
};
// 应用GPIO配置
gpio_config(&bl_gpio_cfg);
// 如果复位脚有效,则进行GPIO配置
if (cfg->rst > 0) {
gpio_config_t rst_gpio_cfg = {
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁止上拉电阻
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁止下拉电阻
.mode = GPIO_MODE_OUTPUT, // 设置为输出模式
.intr_type = GPIO_INTR_DISABLE, // 禁用GPIO中断
.pin_bit_mask = (1 << cfg->rst) // 指定复位控制的GPIO引脚
};
// 应用复位引脚的GPIO配置
gpio_config(&rst_gpio_cfg);
}
// 创建基于SPI的LCD操作句柄
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = cfg->dc, // 数据/命令选择引脚,DC引脚设置
.cs_gpio_num = cfg->cs, // 片选引脚
.pclk_hz = cfg->spi_fre, // 设置SPI时钟频率
.lcd_cmd_bits = 8, // 设置LCD命令的位宽
.lcd_param_bits = 8, // 设置LCD参数的位宽
.spi_mode = 0, // 使用SPI模式0
.trans_queue_depth = 10, // 设置SPI传输事务队列深度
.on_color_trans_done = notify_flush_ready, // 刷新完成的回调函数
.user_ctx = cfg->cb_param, // 回调函数的参数
.flags = { // SPI时序相关参数
.sio_mode = 0, // 通过一根数据线(MOSI)读写数据
},
};
// 将LCD连接到SPI总线,并检查返回值以确保成功
ESP_LOGI(TAG, "create esp_lcd_new_panel");
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &lcd_io_handle));
// 如果复位引脚有效,执行硬件复位操作
if (cfg->rst > 0) {
gpio_set_level(cfg->rst, 0); // 拉低复位引脚
vTaskDelay(pdMS_TO_TICKS(20)); // 延时20毫秒以确保复位
gpio_set_level(cfg->rst, 1); // 拉高复位引脚
vTaskDelay(pdMS_TO_TICKS(20)); // 延时20毫秒以确保复位完成
}
// 向LCD写入初始化命令
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_SWRESET, NULL, 0); // 发送软件复位命令
vTaskDelay(pdMS_TO_TICKS(150)); // 延时150毫秒等待复位完成
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_SLPOUT, NULL, 0); // 发送退出休眠命令
vTaskDelay(pdMS_TO_TICKS(200)); // 延时200毫秒等待退出休眠完成
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_COLMOD, (uint8_t[]) {0x55,}, 1); // 设置RGB颜色格式,0x55表示RGB565
esp_lcd_panel_io_tx_param(lcd_io_handle, 0xb0, (uint8_t[]) {0x00, 0xF0}, 2); // 设置其他参数
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_INVON, NULL, 0); // 发送颜色翻转命令
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_NORON, NULL, 0); // 发送普通显示模式命令
uint8_t spin_type = 0; // 初始化旋转类型
switch (cfg->spin) {
case 0:
spin_type = 0x00; // 不旋转
break;
case 1:
spin_type = 0x60; // 顺时针旋转90度
break;
case 2:
spin_type = 0xC0; // 旋转180度
break;
case 3:
spin_type = 0xA0; // 顺时针旋转270度(逆时针旋转90度)
break;
default:
break; // 处理未知旋转类型
}
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_MADCTL, (uint8_t[]) {spin_type,}, 1); // 设置屏幕旋转方向
vTaskDelay(pdMS_TO_TICKS(150)); // 延时150毫秒等待配置完成
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_DISPON, NULL, 0); // 发送显示打开命令
vTaskDelay(pdMS_TO_TICKS(300)); // 延时300毫秒等待显示打开完成
return ESP_OK; // 返回成功状态
}
/**
* @brief 刷新ST7789显示屏幕指定区域的函数
*
* 该函数用于将指定区域的像素数据写入ST7789显示屏。
*
* @param x1 刷新区域的左上角x坐标
* @param x2 刷新区域的右下角x坐标
* @param y1 刷新区域的左上角y坐标
* @param y2 刷新区域的右下角y坐标
* @param data 指向包含像素数据的缓冲区的指针
*/
void st7789_flush(int x1,int x2,int y1,int y2,void *data)
{
// 定义MCU可以访问的帧内存区域
if(x2 <= x1 || y2 <= y1)
{
// 如果坐标不合法,调用回调函数并返回
if(s_flush_done_cb)
s_flush_done_cb(NULL);
return;
}
// 设置列地址集
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_CASET, (uint8_t[]) {
(x1 >> 8) & 0xFF,
x1 & 0xFF,
((x2 - 1) >> 8) & 0xFF,
(x2 - 1) & 0xFF,
}, 4);
// 设置行地址集
esp_lcd_panel_io_tx_param(lcd_io_handle, LCD_CMD_RASET, (uint8_t[]) {
(y1 >> 8) & 0xFF,
y1 & 0xFF,
((y2 - 1) >> 8) & 0xFF,
(y2 - 1) & 0xFF,
}, 4);
// 传输帧缓冲区
// transfer frame buffer
size_t len = (x2 - x1) * (y2 - y1) * 2;
esp_lcd_panel_io_tx_color(lcd_io_handle, LCD_CMD_RAMWR, data, len);
return ;
}
/** 控制背光
* @param enable 是否使能背光
* @return 无
*/
void st7789_lcd_backlight(bool enable)
{
if(enable)
{
gpio_set_level(s_bl_gpio,1);
}
else
{
gpio_set_level(s_bl_gpio,0);
}
}
main.c
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "lv_port.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "st7789_driver.h"
// 主函数
void app_main(void)
{
lv_port_init(); //初始化LVGL
lv_demo_widgets(); //初始化控件demo程序
//lv_demo_stress();
st7789_lcd_backlight(true); //打开背光
while(1)
{
vTaskDelay(pdMS_TO_TICKS(10));
lv_task_handler(); //LVGL循环处理
}
}
I2C-CST816T-触摸屏
I2C介绍
目前大多数触摸屏采用的是I2C总线,电容触摸方式。
cst816t.h
#ifndef _CST816T_DRIVER_H_
#define _CST816T_DRIVER_H_
#include "driver/gpio.h"
#include "esp_err.h"
//CST816T 触摸IC驱动
typedef struct
{
gpio_num_t scl; //SCL管脚
gpio_num_t sda; //SDA管脚
uint32_t fre; //I2C速率
uint16_t x_limit; //X方向触摸边界
uint16_t y_limit; //y方向触摸边界
}cst816t_cfg_t;
/** CST816T初始化
* @param cfg 配置
* @return err
*/
esp_err_t cst816t_init(cst816t_cfg_t* cfg);
/** 读取坐标值
* @param x x坐标
* @param y y坐标
* @param state 松手状态 0,松手 1按下
* @return 无
*/
void cst816t_read(int16_t *x,int16_t *y,int *state);
#endif
cst816t.c
#include "cst816t_driver.h"
#include "driver/i2c.h"
//#include "driver/i2c_master.h"
#include "esp_log.h"
#define TOUCH_I2C_PORT I2C_NUM_0
#define CST816T_ADDR 0x15
static const char *TAG = "cst816t";
//边界值
static uint16_t s_usLimitX = 0;
static uint16_t s_usLimitY = 0;
static esp_err_t i2c_read(uint8_t slave_addr, uint8_t register_addr, uint8_t read_len,uint8_t *data_buf);
/** CST816T初始化
* @param cfg 配置
* @return err
*/
esp_err_t cst816t_init(cst816t_cfg_t* cfg)
{
int i2c_master_port = TOUCH_I2C_PORT;
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = cfg->sda,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = cfg->scl,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = cfg->fre,
};
s_usLimitX = cfg->x_limit;
s_usLimitY = cfg->y_limit;
ESP_ERROR_CHECK(i2c_param_config(i2c_master_port, &conf));
ESP_ERROR_CHECK(i2c_driver_install(i2c_master_port, conf.mode, 0, 0, 0));
uint8_t data_buf;
i2c_read(CST816T_ADDR, 0xA7, 1,&data_buf);
ESP_LOGI(TAG, "\tChip ID: 0x%02x", data_buf);
i2c_read(CST816T_ADDR, 0xA9, 1,&data_buf);
ESP_LOGI(TAG, "\tFirmware version: 0x%02x", data_buf);
i2c_read(CST816T_ADDR, 0xAA, 1,&data_buf);
ESP_LOGI(TAG, "\tFactory ID: 0x%02x", data_buf);
return ESP_OK;
}
/** 读取坐标值
* @param x x坐标
* @param y y坐标
* @param state 松手状态 0,松手 1按下
* @return 无
*/
void cst816t_read(int16_t *x,int16_t *y,int *state)
{
uint8_t data_x[2]; // 2 bytesX
uint8_t data_y[2]; // 2 bytesY
uint8_t touch_pnt_cnt = 0; // Number of detected touch points
static int16_t last_x = 0; // 12bit pixel value
static int16_t last_y = 0; // 12bit pixel value
i2c_read(CST816T_ADDR, 0x02,1, &touch_pnt_cnt);
if (touch_pnt_cnt != 1) { // ignore no touch & multi touch
*x = last_x;
*y = last_y;
*state = 0;
return;
}
//读取X坐标
i2c_read(CST816T_ADDR,0x03,2,data_x);
//读取Y坐标
i2c_read(CST816T_ADDR,0x05,2,data_y);
int16_t current_x = ((data_x[0] & 0x0F) << 8) | (data_x[1] & 0xFF);
int16_t current_y = ((data_y[0] & 0x0F) << 8) | (data_y[1] & 0xFF);
if(last_x != current_x || current_y != last_y)
{
last_x = current_x;
last_y = current_y;
//ESP_LOGI(TAG,"touch x:%d,y:%d",last_x,last_y);
}
if(last_x >= s_usLimitX)
last_x = s_usLimitX - 1;
if(last_y >= s_usLimitY)
last_y = s_usLimitY - 1;
*x = last_x;
*y = last_y;
*state = 1;
}
/** 根据寄存器地址读取N字节
* @param slave_addr 器件地址
* @param register_addr 寄存器地址
* @param read_len 要读取的数据长度
* @param data_buf 数据
* @return err
*/
static esp_err_t i2c_read(uint8_t slave_addr, uint8_t register_addr, uint8_t read_len,uint8_t *data_buf)
{
i2c_cmd_handle_t i2c_cmd = i2c_cmd_link_create();
if(!i2c_cmd)
{
ESP_LOGE(TAG, "Error i2c_cmd creat fail!");
return ESP_FAIL;
}
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (slave_addr << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(i2c_cmd, register_addr, I2C_MASTER_ACK);
i2c_master_start(i2c_cmd);
i2c_master_write_byte(i2c_cmd, (slave_addr << 1) | I2C_MASTER_READ, true);
for(int i = 0;i < read_len;i++)
{
if(i == read_len - 1)
i2c_master_read_byte(i2c_cmd, &data_buf[i], I2C_MASTER_NACK);
else
i2c_master_read_byte(i2c_cmd, &data_buf[i], I2C_MASTER_ACK);
}
i2c_master_stop(i2c_cmd);
esp_err_t ret = i2c_master_cmd_begin(TOUCH_I2C_PORT, i2c_cmd, pdMS_TO_TICKS(1000));
i2c_cmd_link_delete(i2c_cmd);
return ret;
}
main.c
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "lv_port.h"
#include "lvgl.h"
#include "lv_demos.h"
#include "st7789_driver.h"
// 主函数
void app_main(void)
{
lv_port_init(); //初始化LVGL
lv_demo_widgets(); //初始化控件demo程序
//lv_demo_stress();
st7789_lcd_backlight(true); //打开背光
while(1)
{
vTaskDelay(pdMS_TO_TICKS(10));
lv_task_handler(); //LVGL循环处理
}
}
LVGL
组件介绍
组件是一个模块化的代码功能,一般用于实现特定的功能。
main是一个特殊的组件,默认可以使用ESP-IDF的所有组件,不需要在CMakeList.txt中声明对其它组件的依赖。
# For more information about build system see
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(lvgl_display)
set(EXTRA_COMPONENT_DIRS ./components)
添加组件
方式一:从乐鑫的组件管理服务器上下载,并以组件的形式加入到自己的项目工程中。
idf.py add-dependency "lvgl/lvgl^8.3.10"
方式二:从github下载 【具体过程见下图】
git clone --recursive https://github.com/lvgl/lvgl
切换版本
cd lvgl/
git tag
git checkout v8.3.10
lv_port.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lvgl.h"
#include "esp_log.h"
#include "st7789_driver.h"
#include "cst816t_driver.h"
#include "driver/gpio.h"
#include "esp_timer.h"
/*
1.初始化和注册LVGL显示驱动
2.初始化和注册LVGL触摸驱动
3.初始化ST7789硬件接口
4.初始化CST816T硬件接口
5.提供一个定时器给LVGL使用
*/
#define TAG "lv_port"
#define LCD_WIDTH 240
#define LCD_HEIGHT 280
static lv_disp_drv_t disp_drv;
void lv_flush_done_cb(void* param)
{
lv_disp_flush_ready(&disp_drv);
}
void disp_flush(struct _lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
st7789_flush(area->x1,area->x2 + 1,area->y1+20,area->y2+20 + 1,color_p);
}
void lv_disp_init(void)
{
static lv_disp_draw_buf_t disp_buf;
const size_t disp_buf_size = LCD_WIDTH*(LCD_HEIGHT/7);
lv_color_t *disp1 = heap_caps_malloc(disp_buf_size*sizeof(lv_color_t),MALLOC_CAP_INTERNAL|MALLOC_CAP_DMA);
lv_color_t *disp2 = heap_caps_malloc(disp_buf_size*sizeof(lv_color_t),MALLOC_CAP_INTERNAL|MALLOC_CAP_DMA);
if(!disp1 || !disp2)
{
ESP_LOGE(TAG,"disp buff malloc fail!");
return;
}
lv_disp_draw_buf_init(&disp_buf,disp1,disp2,disp_buf_size);
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LCD_WIDTH;
disp_drv.ver_res = LCD_HEIGHT;
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = disp_flush;
lv_disp_drv_register(&disp_drv);
}
void IRAM_ATTR indev_read(struct _lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
int16_t x,y;
int state;
cst816t_read(&x,&y,&state);
data->point.x = x;
data->point.y = y;
data->state = state;
}
void lv_indev_init(void)
{
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = indev_read;
lv_indev_drv_register(&indev_drv);
}
void st7789_hw_init(void)
{
st7789_cfg_t st7789_config = {
.bl = GPIO_NUM_26,
.clk = GPIO_NUM_18,
.cs = GPIO_NUM_5,
.dc = GPIO_NUM_17,
.mosi = GPIO_NUM_19,
.rst = GPIO_NUM_21,
.spi_fre = 40*1000*1000,
.height = LCD_HEIGHT,
.width = LCD_WIDTH,
.spin = 0,
.done_cb = lv_flush_done_cb,
.cb_param = &disp_drv,
};
st7789_driver_hw_init(&st7789_config);
}
void cst816t_hw_init(void)
{
cst816t_cfg_t cst816t_config =
{
.scl = GPIO_NUM_22,
.sda = GPIO_NUM_23,
.fre = 300*1000,
.x_limit = LCD_WIDTH,
.y_limit = LCD_HEIGHT,
};
cst816t_init(&cst816t_config);
}
void lv_timer_cb(void* arg)
{
uint32_t tick_interval = *((uint32_t*)arg);
lv_tick_inc(tick_interval);
}
void lv_tick_init(void)
{
static uint32_t tick_interval = 5;
const esp_timer_create_args_t arg =
{
.arg = &tick_interval,
.callback = lv_timer_cb,
.name = "",
.dispatch_method = ESP_TIMER_TASK,
.skip_unhandled_events = true,
};
esp_timer_handle_t timer_handle;
esp_timer_create(&arg,&timer_handle);
esp_timer_start_periodic(timer_handle,tick_interval*1000);
}
void lv_port_init(void)
{
lv_init();
st7789_hw_init();
cst816t_hw_init();
lv_disp_init();
lv_indev_init();
lv_tick_init();
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 jungle8884@163.com