06. SPI通信

news/2025/3/12 20:51:00/文章来源:https://www.cnblogs.com/FlurryHeart/p/18768512

一、SPI通信协议简介

  SPI 是 Serial Peripheral interface 缩写,顾名思义就是串行外围设备接口。SPI 通信协议是 Motorola 公司首先在其 MC68HCXX 系列处理器上定义的。SPI 接口是一种高速的全双工同步的通信总线。

SPI总线挂在多个设备

  • SCK(Serial Clock)时钟信号,由主设备产生。
  • MOSI(Master Out / Slave In)主设备数据输出,从设备数据输入。
  • MISO(Master In / Slave Out)主设备数据输入,从设备数据输出。
  • CS(Chip Select)从设备片选信号,由主设备产生。

SPI 总线具有三种传输方式:全双工、单工以及半双工传输方式。

二、SPI工作模式

  SPI 通信协议就具备 4 种工作模式,在讲这 4 种工作模式前,首先先知道两个单词 CPOL 和 CPHA。

  • CPOL,详称 Clock Polarity,就是 时钟极性,当主从机没有数据传输的时候 SCL 线的电平状态(即空闲状态)。假如空闲状态是高电平,CPOL=1;若空闲状态时低电平,那么 CPOL=0。
  • CPHA,详称 Clock Phase,就是 时钟相位。 同步通信时,数据的变化和采样都是在时钟边沿上进行的,每一个时钟周期都会有上升沿和下降沿两个边沿,那么数据的变化和采样就分别安排在两个不同的边沿,由于数据在产生和到它稳定是需要一定的时间,那么假如我们在第 1 个边沿信号把数据输出了,从机只能从第 2 个边沿信号去采样这个数据。

  CPHA 实质指的是数据的采样时刻,CPHA=0 的情况就表示数据的采样是从第 1 个边沿信号上即奇数边沿,具体是上升沿还是下降沿的问题,是由 CPOL 决定的。这里就存在一个问题:当开始传输第一个 bit 的时候,第 1 个时钟边沿就采集该数据了,那数据是什么时候输出来的呢?那么就有两种情况:一是 CS 使能的边沿,二是上一帧数据的最后一个时钟沿。

  CPHA=1 的情况就是表示数据采样是从第 2 个边沿即偶数边沿,它的边沿极性要注意一点,不是和上面 CPHA=0 一样的边沿情况。前面的是奇数边沿采样数据,从 SCL 空闲状态的直接跳变,空闲状态是高电平,那么它就是下降沿,反之就是上升沿。由于 CPHA=1 是偶数边沿采样,所以需要根据偶数边沿判断,假如第一个边沿即奇数边沿是下降沿,那么偶数边沿的边沿极性就是上升沿。

  由于 CPOL 和 CPHA 都有两种不同状态,所以 SPI 分成了 4 种模式。

SPI工作模式表

#define SPI_SCK_GPIO_NUM                GPIO_NUM_1
#define SPI_MOSI_GPIO_NUM               GPIO_NUM_2
#define SPI_MISO_GPIO_NUM               GPIO_NUM_3#define SPI_SCK(x)                      gpio_set_level(SPI_SCK_GPIO_NUM, x)
#define SPI_MOSI(x)                     gpio_set_level(SPI_MOSI_GPIO_NUM, x)
#define SPI_MISO()                      gpio_get_level(SPI_MISO_GPIO_NUM)
/*** @brief SPI初始化函数* */
void bsp_spi_simulate_init(void)
{gpio_config_t gpio_config_struct = {0};gpio_config_struct.pin_bit_mask = (1ULL << SPI_SCK_GPIO_NUM) | (1ULL << SPI_MOSI_GPIO_NUM); // 设置引脚gpio_config_struct.intr_type = GPIO_INTR_DISABLE;                                           // 不使用中断gpio_config_struct.mode = GPIO_MODE_OUTPUT;                                                 // 输出模式gpio_config_struct.pull_up_en = GPIO_PULLUP_DISABLE;                                        // 不使用上拉gpio_config_struct.pull_down_en = GPIO_PULLUP_DISABLE;                                      // 不使用下拉gpio_config(&gpio_config_struct);                                                           // 配置GPIOgpio_config_struct.pin_bit_mask = (1ULL << SPI_MISO_GPIO_NUM);gpio_config_struct.mode = GPIO_MODE_INPUT;gpio_config(&gpio_config_struct);SPI_SCK(0);                                                                 // SPI的SCK引脚默认为低电平,选择工作模式0或1// SPI_SCK(1);                                                                 // SPI的SCK引脚默认为高电平,选择工作模式2或3
}

【1】、工作模式 0:串行时钟的奇数边沿上升沿采样

串行时钟的奇数边沿上升沿采样时序图

  CPOL= 0 && CPHA= 0 的情形,由于配置了 CPOL= 0,可以看到当数据未发送或者发送完毕,SCK 的状态是 低电平,再者 CPHA = 0 即是 奇数边沿采集。所以传输的数据会在 奇数边沿上升沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 奇数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/*** @brief SPI交换一个字节函数* * @param data 待交换的数据* @return uint8_t 交换后的数据*/
uint8_t bsp_spi_simulate_swap_one_byte(uint8_t data)
{for (uint8_t i = 0; i < 8; i++){// 移出数据SPI_MOSI(data & 0x80);data <<= 1;// SCK上升沿SPI_SCK(1);// 移入数据if (SPI_MISO()){data |= 0x01;}// SCK下降沿SPI_SCK(0);}return data;
}

【2】、工作模式 1:串行时钟的偶数边沿下降沿采样

串行时钟的偶数边沿下降沿采样图

  CPOL= 0 && CPHA= 1 的情形,由于配置了 CPOL= 0,可以看到当数据未发送或者发送完毕,SCK 的状态是 低电平,再者 CPHA = 1 即是 偶数边沿采集。所以传输的数据会在 偶数边沿下降沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 偶数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/*** @brief SPI交换一个字节函数* * @param data 待交换的数据* @return uint8_t 交换后的数据*/
uint8_t bsp_spi_simulate_swap_one_byte(uint8_t data)
{for (uint8_t i = 0; i < 8; i++){// SCK上升沿SPI_SCK(1);// 移出数据SPI_MOSI(data & 0x80);data <<= 1;// SCK下降沿SPI_SCK(0);// 移入数据if (SPI_MISO()){data |= 0x01;}}return data;
}

【3】、工作模式 2:串行时钟的奇数边沿下降沿采样

串行时钟的奇数边沿下降沿采样图

  CPOL= 1 && CPHA= 0 的情形,由于配置了 CPOL= 1,可以看到当数据未发送或者发送完毕,SCK 的状态是 高电平,再者 CPHA = 0 即是 奇数边沿采集。所以传输的数据会在 奇数边沿下升沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 奇数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/*** @brief SPI交换一个字节函数* * @param data 待交换的数据* @return uint8_t 交换后的数据*/
uint8_t bsp_spi_simulate_swap_one_byte(uint8_t data)
{for (uint8_t i = 0; i < 8; i++){// 移出数据SPI_MOSI(data & 0x80);data <<= 1;// SCK下降沿SPI_SCK(0);// 移入数据if (SPI_MISO()){data |= 0x01;}// SCK上升沿SPI_SCK(1);}return data;
}

【4】、工作模式 3:串行时钟的偶数边沿上升沿采样

串行时钟的偶数边沿上升沿采样图

  CPOL= 1 && CPHA= 1 的情形,由于配置了 CPOL= 1,可以看到当数据未发送或者发送完毕,SCK 的状态是 高电平,再者 CPHA = 1 即是 偶数边沿采集。所以传输的数据会在 偶数边沿上升沿 被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 偶数边沿保持稳定且被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生变化。

/*** @brief SPI交换一个字节函数* * @param data 待交换的数据* @return uint8_t 交换后的数据*/
uint8_t bsp_spi_simulate_swap_one_byte(uint8_t data)
{for (uint8_t i = 0; i < 8; i++){// SCK下降沿SPI_SCK(0);// 移出数据SPI_MOSI(data & 0x80);data <<= 1;// SCK上升沿SPI_SCK(1);// 移入数据if (SPI_MISO()){data |= 0x01;}}return data;
}

三、SPI控制器介绍

  ESP32-S3 芯片集成了四个 SPI 控制器,分别为 SPI0、SPI1、SPI2 和 SPI3。SPI0 和 SPI1 控制器主要供内部使用以访问外部 FLASH 和 PSRAM,所以只能使用 SPI2 和 SPI3。SPI2 又称为 HSPI(高速 SPI),而 SPI3 又称为 VSPI(通用 SPI)。

四、SPI常用函数

  ESP-IDF 提供了一套 API 来配置 SPI。要使用此功能,需要导入必要的头文件:

#include "driver/spi_master.h"

4.1、初始化SPI总线

  我们需要使用 spi_bus_initialize() 函数用于** 初始化 SPI 总线**,并配置其 GPIO 引脚和主模式下的时钟等参数,该函数原型如下所示:

/*** @brief 初始化SPI总线* * @param host_id 指定SPI总线的主机设备ID* @param bus_config 用于配置SPI总线的引脚* @param dma_chan 指定使用哪个DMA通道* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t *bus_config, spi_dma_chan_t dma_chan);

  该函数使用 spi_host_device_t 类型的结构体变量来指定 SPI 总线的主机设备 ID。该结构体的定义如下所示:

// 带有三个 SPI 外围设备的枚举,这些外围设备可通过软件访问
typedef enum 
{SPI1_HOST=0,        // SPI1SPI2_HOST=1,        // SPI2
#if SOC_SPI_PERIPH_NUM > 2SPI3_HOST=2,        // SPI3
#endifSPI_HOST_MAX,       // 无效的主机值
} spi_host_device_t;

  该函数使用 spi_bus_config_t 类型的结构体变量来配置 SPI 总线的SCLK、MISO、MOSI 等引脚以及其他参数。该结构体的定义如下所示:

typedef struct 
{union {int mosi_io_num;      // MISO引脚号int data0_io_num;     // GPIO pin for spi data0 signal in quad/octal mode, or -1 if not used.};union{int miso_io_num;      // MOSI引脚号int data1_io_num;     // GPIO pin for spi data1 signal in quad/octal mode, or -1 if not used.};int sclk_io_num;        // 时钟引脚号union {int quadwp_io_num;    // 用于 Quad 模式的 WP 引脚号,未使用时设置为-1int data2_io_num;     // GPIO pin for spi data2 signal in quad/octal mode, or -1 if not used.};union {int quadhd_io_num;    // 用于 Quad 模式的 HD 引脚号,未使用时设置为-1int data3_io_num;     // GPIO pin for spi data3 signal in quad/octal mode, or -1 if not used.};int data4_io_num;       // GPIO pin for spi data4 signal in octal mode, or -1 if not used.int data5_io_num;       // GPIO pin for spi data5 signal in octal mode, or -1 if not used.int data6_io_num;       // GPIO pin for spi data6 signal in octal mode, or -1 if not used.int data7_io_num;       // GPIO pin for spi data7 signal in octal mode, or -1 if not used.int max_transfer_sz;    // 最大传输大小uint32_t flags;         // Abilities of bus to be checked by the driver. Or-ed value of ``SPICOMMON_BUSFLAG_*`` flags.esp_intr_cpu_affinity_t  isr_cpu_id;    ///< Select cpu core to register SPI ISR.int intr_flags;         // 中断标志的总线设置优先级
} spi_bus_config_t;

4.2、设备配置

  我们需要使用 spi_bus_add_device() 函数用于 在 SPI 总线上分配设备,函数原型如下所示:

/*** @brief 在SPI总线上分配设备* * @param host_id 指定SPI总线的主机设备ID* @param dev_config 配置SPI设备的通信参数* @param handle 保存创建的设备句柄* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t spi_bus_add_device(spi_host_device_t host_id, const spi_device_interface_config_t *dev_config, spi_device_handle_t *handle);

  该函数使用 spi_host_device_t 类型以及 spi_device_interface_config_t 类型的结构体变量传入 SPI 外围设备的配置参数,该结构体的定义如下所示:

typedef struct 
{uint8_t command_bits;               // 命令阶段的位数uint8_t address_bits;               // 址阶段的位数uint8_t dummy_bits;                 // 虚拟阶段的位数uint8_t mode;                       // SPI模式spi_clock_source_t clock_source;    // SPI的时钟源,默认是SPI_CLK_SRC_DEFAULTuint16_t duty_cycle_pos;            // 有效时钟的占空比uint16_t cs_ena_pretrans;           // cs在传输前应该被激活SPI位周期的数量(0-16)uint8_t cs_ena_posttrans;           // cs在传输后应该保持活跃SPI位周期的数量(0-16)int clock_speed_hz;                 // 时钟速率int input_delay_ns;                 // 从机数据最大有效时间int spics_io_num;                   // CS引脚号uint32_t flags;                     // Bitwise OR of SPI_DEVICE_* flagsint queue_size;                     // 事务队列大小transaction_cb_t pre_cb;            // 在传输开始之前调用的回调函数transaction_cb_t post_cb;           // 在传输完成后调用的回调函数
} spi_device_interface_config_t;

4.3、数据传输

/*** @brief 发送一个SPI事务,等待它完成,并返回结果* * @param handle 设备的句柄* @param trans_desc 描述了要发送的事务详情* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t SPI_MASTER_ATTR spi_device_transmit(spi_device_handle_t handle, spi_transaction_t *trans_desc);
/*** @brief 该函数用于发送一个轮询事务,等待它完成,并返回结果* * @param handle 设备的句柄* @param trans_desc 描述了要发送的事务详情* @return esp_err_t ESP_OK配置成功,其它配置失败*/
esp_err_t SPI_MASTER_ISR_ATTR spi_device_polling_transmit(spi_device_handle_t handle, spi_transaction_t* trans_desc);

  形参 trans_descspi_transaction_t 结构体类型的指针,它描述了要发送的事务详情,它的定义如下:

struct spi_transaction_t {uint32_t flags;                     // SPI传输标志uint16_t cmd;                       // 命令数据,其长度在spi_device_interface_config_t结构体中配置uint64_t addr;                  // 地址数据,其长度在spi_device_interface_config_t结构体中配置size_t length;                      // 数据长度size_t rxlength;                    // 接收的数据长度void *user;                         // 用户数据union {const void *tx_buffer;          // 传输的数据缓冲区uint8_t tx_data[4];             // 如果设置了SPI_TRANS_USE_TXDATA,则这里的数据集将直接从该变量发送};union {void *rx_buffer;                // 接收的数据缓冲区uint8_t rx_data[4];             // 如果设置了SPI_TRANS_USE_RXDATA,则直接将数据接收到该变量};
};
typedef struct spi_transaction_t spi_transaction_t;

五、LCD简介

  液晶显示器,即 Liquid Crystal Display,利用了液晶导电后透光性可变的特性,配合显示器光源、彩色滤光片和电压控制等工艺,最终可以在液晶阵列上显示彩色的图像。目前液晶显示技术以 TN、STN、TFT 三种技术为主,TFT-LCD 即采用了 TFT(Thin Film Transistor)技术的液晶显示器,也叫薄膜晶体管液晶显示器。

  这里,LCD 使用 ST7735S 控制芯片。ST7735S 是一款用于 262K 色彩、图形类型 TFT-LCD 的单芯片控制器/驱动器。它包含 396 个源码线和 162 个网线驱动电路。该芯片可以直接连接到外部微处理器,并接受串行外围接口(SPI)、8 位 / 9 位 / 16 位 / 18 位并行接口。显示数据可以存储在 132 x 162 x 18 位的芯片内显示数据 RAM 中。 它可以在没有外部操作时钟的情况下执行显示数据 RAM 读写操作,以最小化功耗。此外,由于集成了驱动液晶所需的电源电路,可以使用更少的元件构建显示系统。

引脚 说明
GND 电源负极
VCC 电源正极,3.3V ~ 5.0V
SCL SPI 时钟线,接 SPI SCLK 引脚
SDA SPI 数据线,接 SPI  MOSI 引脚
RST 复位接口(低电平有效)
DC 数据/命令选择
CS SPI 片选线
BLK 背光控制(低电平关闭)

六、实验例程

  这里,我们在【components】文件夹下的【peripheral】文件夹下的【inc】文件夹(用来存放头文件)新建一个 bsp_spi.h 文件,在【components】文件夹下的【peripheral】文件夹下的【src】文件夹(用来存放源文件)新建一个 bsp_spi.c 文件。

#ifndef __BSP_SPI_H__
#define __BSP_SPI_H__#include "driver/spi_master.h"
#include "driver/gpio.h"void bsp_spi_init(spi_host_device_t host_id, gpio_num_t spi_sclk_io_num, gpio_num_t spi_mosi_io_num, gpio_num_t spi_miso_io_num);
void bsp_spi_device_interface_config(spi_host_device_t host_id, gpio_num_t cs_gpio_num, uint8_t mode, int clock_speed, spi_device_handle_t *handle);
void bsp_spi_send_one_byte(spi_device_handle_t handle, uint8_t data);
void bsp_spi_send_bytes(spi_device_handle_t handle, const uint8_t *data, uint16_t length);
uint8_t bsp_spi_transfer_one_byte(spi_device_handle_t handle, uint8_t data);#endif // !__BSP_SPI_H__
#include "bsp_spi.h"/*** @brief 初始化SPI* * @param host_id SPI总线的主机设备ID* @param sclk_io_num SPI的SCLK引脚* @param miso_io_num SPI的MISO引脚* @param mosi_io_num SPI的MOSI引脚*/
void bsp_spi_init(spi_host_device_t host_id, gpio_num_t spi_sclk_io_num, gpio_num_t spi_mosi_io_num, gpio_num_t spi_miso_io_num)
{spi_bus_config_t spi_bus_config = {0};// SPI总线配置spi_bus_config.sclk_io_num = spi_sclk_io_num;                   // SPI的SCLK引脚spi_bus_config.mosi_io_num = spi_mosi_io_num;                   // SPI的MOSI引脚spi_bus_config.miso_io_num = spi_miso_io_num;                   // SPI的MISO引脚spi_bus_config.quadwp_io_num = -1;                              // SPI写保护信号引脚,该引脚未使能spi_bus_config.quadhd_io_num = -1;                              // SPI保持信号引脚,该引脚未使能spi_bus_config.max_transfer_sz = 1024 * 10;                     // 配置最大传输大小,以字节为单位spi_bus_initialize(host_id, &spi_bus_config, SPI_DMA_CH_AUTO);
}/*** @brief SPI设备接口配置函数* * @param host_id SPI总线的主机设备ID* @param cs_gpio_num SPI的片选引脚* @param mode SPI的工作模式* @param clock_speed SPI的时钟频率* @param handle SPI设备句柄*/
void bsp_spi_device_interface_config(spi_host_device_t host_id, gpio_num_t cs_gpio_num, uint8_t mode, int clock_speed, spi_device_handle_t *handle)
{spi_device_interface_config_t spi_device_interface_config = {0};spi_device_interface_config.spics_io_num = cs_gpio_num;     // SPI的片选引脚spi_device_interface_config.mode = mode;                    // SPI的工作模式spi_device_interface_config.clock_speed_hz = clock_speed;   // SPI的时钟频率spi_device_interface_config.queue_size = 8;                 // 事务队列大小spi_bus_add_device(host_id, &spi_device_interface_config, handle);
}/*** @brief SPI发送一个字节数据* * @param handle SPI句柄* @param data 要发送的一个字节的数据*/
void bsp_spi_send_one_byte(spi_device_handle_t handle, uint8_t data)
{spi_transaction_t spi_transaction = {0};spi_transaction.length = 8;                             // 要传输的位数,一个字节8位spi_transaction.tx_buffer = &data;                      // 要传输的数据spi_device_polling_transmit(handle, &spi_transaction);  // 发送数据
}/*** @brief SPI发送多个字节数据* * @param handle SPI句柄* @param data 要发送的多个字节的数据的缓冲区*/
void bsp_spi_send_bytes(spi_device_handle_t handle, const uint8_t *data, uint16_t length)
{spi_transaction_t spi_transaction = {0};spi_transaction.length = length * 8;                    // 要传输的位数,一个字节8位spi_transaction.tx_buffer = data;                       // 将命令填充进去spi_device_polling_transmit(handle, &spi_transaction);  // 开始传输
}/*** @brief SPI传输一个字节数据* * @param handle SPI句柄* @param data 要传输的一个字节的数据* @return uint8_t 接收的数据*/
uint8_t bsp_spi_transfer_one_byte(spi_device_handle_t handle, uint8_t data)
{spi_transaction_t spi_transaction = {0};spi_transaction.flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA;spi_transaction.length = 8;spi_transaction.tx_data[0] = data;spi_device_transmit(handle, &spi_transaction);return spi_transaction.rx_data[0];
}

SPI0 和 SPI1 控制器主要供内部使用以访问外部 FLASH 和 PSRAM,所以只能使用 SPI2 和 SPI3。

 我们在【components】文件夹下的【device】文件夹中新增了一个 【lcd】 文件夹,用于存放 lcd.c 和 lcd.h 这两个文件。

#ifndef __LCD_H__
#define __LCD_H__#include <string.h>
#include <stdio.h>
#include <stdarg.h> #include "bsp_spi.h"#include "screen/color.h"
#include "screen/font.h"
#include "screen/image.h"#define LCD_CS_GPIO_NUM         GPIO_NUM_4
#define LCD_CS(x)               do{ x ? \gpio_set_level(LCD_CS_GPIO_NUM, 1) : \gpio_set_level(LCD_CS_GPIO_NUM, 0); \}while(0)#define LCD_DC_GPIO_NUM         GPIO_NUM_5
#define LCD_DC(x)               do{ x ? \gpio_set_level(LCD_DC_GPIO_NUM, 1) : \gpio_set_level(LCD_DC_GPIO_NUM, 0); \}while(0)#define LCD_RESET_GPIO_NUM      GPIO_NUM_6
#define LCD_RESET(x)            do{ x ? \gpio_set_level(LCD_RESET_GPIO_NUM, 1) : \gpio_set_level(LCD_RESET_GPIO_NUM, 0); \}while(0)#define LCD_BLK_GPIO_NUM        GPIO_NUM_7
#define LCD_BLK(x)              do{ x ? \gpio_set_level(LCD_BLK_GPIO_NUM, 1) : \gpio_set_level(LCD_BLK_GPIO_NUM, 0); \}while(0)#define LCD_WIDTH               128
#define LCD_HEIGHT              160typedef enum LCD_Write_Mode_t
{LCD_MODE_CMD = 0,LCD_MODE_DATA = 1
} lcd_write_mode_t;typedef enum LCD_Display_Mode_t
{LCD_DISPLAY_NORMAL = 0,LCD_DISPLAY_OVERLAPPING = 1
} lcd_display_mode_t;extern spi_device_handle_t g_lcd_spi_device_handle;void lcd_init(void);
void lcd_reset(void);void lcd_set_cursor(uint8_t x, uint8_t y);
void lcd_set_cursor_area(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
void lcd_display_direction(uint8_t mode);
void lcd_clear(uint16_t color);
void lcd_clear_area(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color);void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color);
void lcd_show_char(uint16_t x, uint16_t y, char chr, uint16_t size, uint16_t forecolor, uint16_t backcolor, lcd_display_mode_t display_mode);
void lcd_show_string(uint16_t x, uint16_t y, char *str, uint16_t size, uint16_t forecolor, uint16_t backcolor, lcd_display_mode_t display_mode);
void lcd_show_chinese(uint16_t x, uint16_t y, char *chinese, uint16_t size, uint16_t forecolor, uint16_t backcolor, lcd_display_mode_t display_mode);void lcd_show_picture(uint8_t image[], uint16_t x, uint16_t y, uint16_t width, uint16_t height);#endif // !__LCD_H__
#include "lcd.h"spi_device_handle_t g_lcd_spi_device_handle;static void lcd_gpio_init(void);
static void lcd_st7735_init(void);static void lcd_write_byte(uint8_t data, lcd_write_mode_t mode);
static void lcd_write_bytes(uint8_t *data, uint16_t length, lcd_write_mode_t mode);
static uint8_t lcd_read_one_byte(void);/*** @brief LCD初始化函数* * @param handle SPI设备地址*/
void lcd_init(void)
{lcd_gpio_init();lcd_reset();                                                                // 复位LCDLCD_BLK(1);                                                                 // 开启背光LCD_CS(1);lcd_st7735_init();lcd_clear(MAGENTA);
}/*** @brief LCD底层初始化函数* */
static void lcd_gpio_init(void)
{gpio_config_t gpio_config_struct = {0};gpio_config_struct.pin_bit_mask = 1ULL << LCD_CS_GPIO_NUM;                  // 设置引脚gpio_config_struct.intr_type = GPIO_INTR_DISABLE;                           // 不使用中断gpio_config_struct.mode = GPIO_MODE_OUTPUT;                                 // 输出模式gpio_config_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;                    // 不使用下拉gpio_config_struct.pull_up_en = GPIO_PULLUP_DISABLE;                        // 不使用上拉gpio_config(&gpio_config_struct);                                           // 配置GPIOgpio_config_struct.pin_bit_mask = 1ULL << LCD_DC_GPIO_NUM;                  // 设置引脚gpio_config(&gpio_config_struct);                                           // 配置GPIOgpio_config_struct.pin_bit_mask = 1ULL << LCD_RESET_GPIO_NUM;               // 设置引脚gpio_config(&gpio_config_struct);                                           // 配置GPIOgpio_config_struct.pin_bit_mask = 1ULL << LCD_BLK_GPIO_NUM;                 // 设置引脚gpio_config(&gpio_config_struct);                                           // 配置GPIO
}/*** @brief 向寄存器写入一个字节数据函数* * @param data 一字节数据* @param mode LCD状态的枚举值*/
static void lcd_write_byte(uint8_t data, lcd_write_mode_t mode)
{LCD_CS(0);                                                                  // 片选LCD_DC(mode);                                                               // 数据/命令选择bsp_spi_send_one_byte(g_lcd_spi_device_handle, data);                       // 发送数据LCD_CS(1);                                                                  // 取消片选
}/*** @brief 向寄存器写入多个字节数据函数* * @param data 指向要写入的数据的指针* @param length 要写入的数据长度* @param mode LCD状态的枚举值*/
static void lcd_write_bytes(uint8_t *data, uint16_t length, lcd_write_mode_t mode)
{LCD_CS(0);                                                                  // 片选LCD_DC(mode);                                                               // 数据/命令选择bsp_spi_send_bytes(g_lcd_spi_device_handle, data, length);                  // 发送数据LCD_CS(1);                                                                  // 取消片选 
}/*** @brief 从寄存器读取一个字节数据函数* * @param mode LCD状态的枚举值* @return uint8_t 读取的一字节数据*/
static uint8_t lcd_read_one_byte(void)
{uint8_t data = 0;LCD_CS(0);                                                                  // 片选LCD_DC(LCD_MODE_DATA);                                                      // 数据/命令选择data = bsp_spi_transfer_one_byte(g_lcd_spi_device_handle, 0);               // 读取数据LCD_CS(1);                                                                  // 取消片选return data;
}/*** @brief ST7735初始化函数* */
static void lcd_st7735_init(void)
{// LCD Init For 1.44Inch LCD Panel with ST7735R.lcd_write_byte(0x11, LCD_MODE_CMD);                                         // Sleep exit vTaskDelay(120);// ST7735R Frame Ratelcd_write_byte(0xB1, LCD_MODE_CMD); lcd_write_byte(0x01, LCD_MODE_DATA); lcd_write_byte(0x2C, LCD_MODE_DATA); lcd_write_byte(0x2D, LCD_MODE_DATA); lcd_write_byte(0xB2, LCD_MODE_CMD); lcd_write_byte(0x01, LCD_MODE_DATA); lcd_write_byte(0x2C, LCD_MODE_DATA); lcd_write_byte(0x2D, LCD_MODE_DATA); lcd_write_byte(0xB3, LCD_MODE_CMD); lcd_write_byte(0x01, LCD_MODE_DATA); lcd_write_byte(0x2C, LCD_MODE_DATA); lcd_write_byte(0x2D, LCD_MODE_DATA); lcd_write_byte(0x01, LCD_MODE_DATA); lcd_write_byte(0x2C, LCD_MODE_DATA); lcd_write_byte(0x2D, LCD_MODE_DATA); lcd_write_byte(0xB4, LCD_MODE_CMD);                                         // Column inversion lcd_write_byte(0x07, LCD_MODE_DATA); //ST7735R Power Sequencelcd_write_byte(0xC0, LCD_MODE_CMD); lcd_write_byte(0xA2, LCD_MODE_DATA); lcd_write_byte(0x02, LCD_MODE_DATA); lcd_write_byte(0x84, LCD_MODE_DATA); lcd_write_byte(0xC1, LCD_MODE_CMD); lcd_write_byte(0xC5, LCD_MODE_DATA); lcd_write_byte(0xC2, LCD_MODE_CMD); lcd_write_byte(0x0A, LCD_MODE_DATA); lcd_write_byte(0x00, LCD_MODE_DATA); lcd_write_byte(0xC3, LCD_MODE_CMD); lcd_write_byte(0x8A, LCD_MODE_DATA); lcd_write_byte(0x2A, LCD_MODE_DATA); lcd_write_byte(0xC4, LCD_MODE_CMD); lcd_write_byte(0x8A, LCD_MODE_DATA); lcd_write_byte(0xEE, LCD_MODE_DATA); lcd_write_byte(0xC5, LCD_MODE_CMD);                                         // VCOM lcd_write_byte(0x0E, LCD_MODE_DATA); lcd_write_byte(0x36, LCD_MODE_CMD);                                         // MX, MY, RGB mode lcd_write_byte(0xC0, LCD_MODE_DATA); // ST7735R Gamma Sequencelcd_write_byte(0xe0, LCD_MODE_CMD); lcd_write_byte(0x0F, LCD_MODE_DATA); lcd_write_byte(0x1A, LCD_MODE_DATA); lcd_write_byte(0x0F, LCD_MODE_DATA); lcd_write_byte(0x18, LCD_MODE_DATA); lcd_write_byte(0x2F, LCD_MODE_DATA); lcd_write_byte(0x28, LCD_MODE_DATA); lcd_write_byte(0x20, LCD_MODE_DATA); lcd_write_byte(0x22, LCD_MODE_DATA); lcd_write_byte(0x1F, LCD_MODE_DATA); lcd_write_byte(0x1B, LCD_MODE_DATA); lcd_write_byte(0x23, LCD_MODE_DATA); lcd_write_byte(0x37, LCD_MODE_DATA); lcd_write_byte(0x00, LCD_MODE_DATA); lcd_write_byte(0x07, LCD_MODE_DATA); lcd_write_byte(0x02, LCD_MODE_DATA); lcd_write_byte(0x10, LCD_MODE_DATA); lcd_write_byte(0xE1, LCD_MODE_CMD); lcd_write_byte(0x0F, LCD_MODE_DATA); lcd_write_byte(0x1B, LCD_MODE_DATA); lcd_write_byte(0x0F, LCD_MODE_DATA); lcd_write_byte(0x17, LCD_MODE_DATA); lcd_write_byte(0x33, LCD_MODE_DATA); lcd_write_byte(0x2C, LCD_MODE_DATA); lcd_write_byte(0x29, LCD_MODE_DATA); lcd_write_byte(0x2E, LCD_MODE_DATA); lcd_write_byte(0x30, LCD_MODE_DATA); lcd_write_byte(0x30, LCD_MODE_DATA); lcd_write_byte(0x39, LCD_MODE_DATA); lcd_write_byte(0x3F, LCD_MODE_DATA); lcd_write_byte(0x00, LCD_MODE_DATA); lcd_write_byte(0x07, LCD_MODE_DATA); lcd_write_byte(0x03, LCD_MODE_DATA); lcd_write_byte(0x10, LCD_MODE_DATA);  lcd_write_byte(0x2A, LCD_MODE_CMD);lcd_write_byte(0x00, LCD_MODE_DATA);lcd_write_byte(0x00, LCD_MODE_DATA);lcd_write_byte(0x00, LCD_MODE_DATA);lcd_write_byte(0x7F, LCD_MODE_DATA);lcd_write_byte(0x2B, LCD_MODE_CMD);lcd_write_byte(0x00, LCD_MODE_DATA);lcd_write_byte(0x00, LCD_MODE_DATA);lcd_write_byte(0x00, LCD_MODE_DATA);lcd_write_byte(0x9F, LCD_MODE_DATA);lcd_write_byte(0xF0, LCD_MODE_CMD);                                         // Enable test command  lcd_write_byte(0x01, LCD_MODE_DATA); lcd_write_byte(0xF6, LCD_MODE_CMD);                                         // Disable ram power save mode lcd_write_byte(0x00, LCD_MODE_DATA); lcd_write_byte(0x3A, LCD_MODE_CMD);                                         // 65k mode lcd_write_byte(0x05, LCD_MODE_DATA); lcd_write_byte(0x29, LCD_MODE_CMD);                                         // Display on	 
}/*** @brief LCD复位函数* */
void lcd_reset(void)
{LCD_RESET(0);vTaskDelay(1);LCD_RESET(1);vTaskDelay(120);
}/*** @brief OLED设置坐标函数* * @param x 坐标所在的列,范围: 0 ~ 127* @param y 坐标所在的行: 0 ~ 159*/
void lcd_set_cursor(uint8_t x, uint8_t y)
{lcd_write_byte(0x2A, LCD_MODE_CMD);                                         // 设置列地址lcd_write_byte(0x00, LCD_MODE_DATA);                                        // 发送列地址的起始地址高8位lcd_write_byte(x, LCD_MODE_DATA);                                           // 发送列地址的起始地址低8位lcd_write_byte(0x2B, LCD_MODE_CMD);                                         // 发送页地址lcd_write_byte(0x00, LCD_MODE_DATA);                                        // 发送页地址的起始地址高8位lcd_write_byte(y, LCD_MODE_DATA);   
}/*** @brief 设置LCD的光标范围* * @param x1 光标的起始位置的列* @param y1 光标的起始位置的行* @param x2 光标的结束位置的列* @param y2 光标的结束位置的行*/
void lcd_set_cursor_area(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{lcd_write_byte(0x2A, LCD_MODE_CMD);                                         // 设置列地址lcd_write_byte(0x00, LCD_MODE_DATA);                                        // 发送列地址的起始地址高8位lcd_write_byte(x1, LCD_MODE_DATA);                                          // 发送列地址的起始地址低8位lcd_write_byte(0x00, LCD_MODE_DATA);                                        // 发送列地址的起始地址高8位lcd_write_byte(x2, LCD_MODE_DATA);                                          // 发送列地址的起始地址低8位lcd_write_byte(0x2B, LCD_MODE_CMD);                                         // 发送页地址lcd_write_byte(0x00, LCD_MODE_DATA);                                        // 发送页地址的起始地址高8位lcd_write_byte(y1, LCD_MODE_DATA);                                          // 发送页地址的起始地址低8位lcd_write_byte(0x00, LCD_MODE_DATA);                                        // 发送页地址的起始地址高8位lcd_write_byte(y2, LCD_MODE_DATA);                                          // 发送页地址的起始地址低8位
}/*** @brief LCD设置显示方向函数* * @param direction 0:从左到右,从上到下*                  1:从上到下,从左到右*                  2:从右到左,从上到下*                  3:从上到下,从右到左*                  4:从左到右,从下到上*                  5:从下到上,从左到右*                  6:从右到左,从下到上*                  7:从下到上,从右到左*/
void lcd_display_direction(uint8_t mode)
{lcd_write_byte(0x36, LCD_MODE_CMD);                                         //设置彩屏显示方向的寄存器lcd_write_byte(0x00 | (mode << 5), LCD_MODE_DATA);switch (mode){case 0:case 2:case 4:case 6:lcd_set_cursor_area(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);break;case 1:case 3:case 5:case 7:lcd_set_cursor_area(0, 0, LCD_HEIGHT - 1, LCD_WIDTH - 1);default:break;}
}/*** @brief LCD清屏函数* * @param color 颜色*/
void lcd_clear(uint16_t color)
{uint16_t total_point = LCD_WIDTH * LCD_HEIGHT;                              // 得到总点数uint8_t data[2] = {color >> 8, color & 0xFF};lcd_set_cursor_area(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1);                   // 设置光标位置lcd_write_byte(0x2C, LCD_MODE_CMD);                                         // 发送写GRAM指令for (uint16_t index = 0; index < total_point; index++){lcd_write_bytes(data, 2, LCD_MODE_DATA);                                // 发送颜色数据}
}/*** @brief LCD局部清屏函数* * @param x 要清空的区域的左上角的列坐标* @param y 要清空的区域的左上角的行坐标* @param width 要清空的区域的宽度* @param height 要清空的区域的高度* @param color 要清空的区域的颜色*/
void lcd_clear_area(uint8_t x, uint8_t y, uint8_t width, uint8_t height, uint16_t color)
{for (uint8_t i = y; i < y + height; i++){lcd_set_cursor(x, i);                                                   // 设置光标位置lcd_write_byte(0x2C, LCD_MODE_CMD);                                     // 发送写GRAM指令for (uint8_t j = x; j < x + width; j++){uint8_t data[2] = {color >> 8, color & 0xFF};                       // 颜色数据lcd_write_bytes(data, 2, LCD_MODE_DATA);                            // 发送颜色数据}}
}/*** @brief LCD画点函数* * @param x 列* @param y 行* @param color 颜色*/
void lcd_draw_point(uint16_t x, uint16_t y, uint16_t color)
{uint8_t data[2] = {color >> 8, color & 0xFF};                               // 16位颜色lcd_set_cursor(x, y);                                                       // 设置坐标lcd_write_byte(0x2C, LCD_MODE_CMD);                                         // 发送写GRAM指令lcd_write_bytes(data, 2, LCD_MODE_DATA);                                    // 写入颜色值
}/*** @brief LCD读点函数* * @param x 列数* @param y 行数* @return uint16_t */
uint16_t lcd_read_point(uint16_t x, uint16_t y)
{uint16_t r = 0, g = 0, b = 0;lcd_set_cursor(x, y);                                                       // 设置坐标lcd_write_byte(0x2E, LCD_MODE_CMD);                                         // 读GRAM数据指令lcd_read_one_byte();                                                        // 假读r = lcd_read_one_byte();                                                    // 读取R通道和G通道的值b = lcd_read_one_byte();                                                    // 读取B通道的值g = r & 0xFF;                                                               // 获取G通道的值return (((r >> 11) << 11) | ((g >> 2) << 5) | (b >> 11));
}/*** @brief LCD显示字符函数* * @param x 列* @param y 行* @param chr 显示的字符* @param size 字体大小,这里字符的高度等于字重,字符的宽度等于字重的一半* @param forecolor 字符的颜色* @param backcolor 背景色* @param display_mode 显示模式的枚举值*/
void lcd_show_char(uint16_t x, uint16_t y, char chr, uint16_t size, uint16_t forecolor, uint16_t backcolor, lcd_display_mode_t display_mode)
{uint8_t *pfont = NULL;uint8_t temp = 0;uint8_t high = size / 8 + ((size % 8) ? 1 : 0);                            // 得到一个字符对应的字节数switch (size){case 12:pfont = (uint8_t *)ascii_06x12[chr - ' '];                          // 调用06x12字体break;case 16:pfont = (uint8_t *)ascii_08x16[chr - ' '];                          // 调用08x16字体break;case 24:pfont = (uint8_t *)ascii_12x24[chr - ' '];                          // 调用12x24字体break;case 32:pfont = (uint8_t *)ascii_16x32[chr - ' '];                          // 调用16x32字体break;default:return ;}for (uint8_t h = 0; h < high; h++)                                         // 遍历字符的高度{for (uint8_t w = 0; w < size / 2; w++)                                  // 遍历字符的宽度{temp = pfont[h * size / 2 + w];                                     // 获取字符对应的字节数据for (uint8_t k = 0; k < 8; k++)                                     // 一个字节8个像素点{if (temp & 0x01)                                                // 绘制字符{lcd_draw_point(x + w, y + k + 8 * h , forecolor);}else{if (display_mode == LCD_DISPLAY_NORMAL)                      // 是否绘制背景{lcd_draw_point(x + w, y + k + 8 * h , backcolor);}}temp >>= 1;}}}
}/*** @brief LCD显示字符串函数* * @param x 列* @param y 行* @param str 显示的字符串* @param size 字体大小,这里字符的高度等于字重,字符的宽度等于字重的一半* @param forecolor 字符串的颜色* @param backcolor 背景色* @param display_mode 显示模式的枚举值*/
void lcd_show_string(uint16_t x, uint16_t y, char *str, uint16_t size, uint16_t forecolor, uint16_t backcolor, lcd_display_mode_t display_mode)
{uint16_t x0 = x;for (uint16_t i = 0; str[i] != '\0'; i++){if (str[i] == '\n'){x = x0;y += size;continue;}lcd_show_char(x, y, str[i], size, forecolor, backcolor, display_mode);x += (size / 2);}
}/*** @brief LCD显示汉字函数* * @param x 列* @param y 行* @param chinese 要显示的汉字* @param size 要显示的汉字大小* @param forecolor 汉字的颜色* @param backcolor 背景色* @param display_mode 显示模式的枚举值*/
void lcd_show_chinese(uint16_t x, uint16_t y, char *chinese, uint16_t size, uint16_t forecolor, uint16_t backcolor, lcd_display_mode_t display_mode)
{char sigle_chinese[4] = {0};                                                // 存储单个汉字uint16_t index = 0;                                                         // 汉字索引uint16_t high = size / 8 + ((size % 8) ? 1 : 0);                            // 得到一个字符对应的字节数uint16_t temp = 0;uint16_t j = 0;switch (size){case 32:for (uint16_t i = 0; chinese[i] != '\0'; i++)                           // 遍历汉字字符串{// 获取单个汉字,一般UTF-8编码使用3个字节存储汉字,GBK编码使用2个字节存储汉字sigle_chinese[index] = chinese[i];index++;index = index % 3;if (index == 0)                                                     // 汉字索引为0,说明已经获取了一个汉字{for (j = 0; strcmp(chinese_32x32[j].index, "") != 0; j++)       // 遍历汉字数组{if (strcmp(chinese_32x32[j].index, sigle_chinese) == 0)      // 找到汉字{break;} }for (uint16_t h = 0; h < high; h++)                            // 遍历字符的高度{for (uint16_t w = 0; w < size; w++)                         // 遍历字符的宽度{temp = chinese_32x32[j].data[h * size  + w];            // 获取字符对应的字节数据for (uint16_t k = 0; k < 8; k++)                        // 一个字节8个像素点{if (temp & 0x01)                                    // 绘制字体{// ((i + 1) / 3)定位到第几个汉字lcd_draw_point(x + w + ((i + 1) / 3 - 1) * size, y + k + 8 * h , forecolor);}else{if (display_mode == LCD_DISPLAY_NORMAL)         // 是否绘制背景{lcd_draw_point(x + w + ((i + 1) / 3 - 1) * size, y + k + 8 * h , backcolor);}  }temp >>= 1;}}}}}break;default:break;}
}/*** @brief LCD显示图片函数* * @param image 图片数据* @param x 列* @param y 行* @param width 图片的宽度* @param height 图片的高度*/
void lcd_show_picture(uint8_t image[], uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{uint8_t color[2] = {0};uint32_t i = 0, j = 0, k = 0;for (i = 0; i < height; i++){lcd_set_cursor(x, y + i);                                                // 设置光标位置lcd_write_byte(0x2C, LCD_MODE_CMD);                                      // 发送写GRAM指令for (j = 0; j < width; j++){color[0] = image[k + 1];                                            // 获取图片数据color[1] = image[k];                                                // 获取图片数据k += 2;lcd_write_bytes(color, 2, LCD_MODE_DATA);                            // 写入颜色值}}
}

  然后,我们修改【components】文件夹下的【device】文件夹下的 CMakeLists.txt 文件。

# 源文件路径
set(src_dirslcd
)# 头文件路径
set(include_dirslcd# ${CMAKE_SOURCE_DIR}表示顶级 CMakeLists.txt 文件所在的目录的绝对路径${CMAKE_SOURCE_DIR}/components/peripheral/inc${CMAKE_SOURCE_DIR}/components/toolkit
)# 设置依赖库
set(requiresdriver
)# 注册组件到构建系统的函数
idf_component_register(# 源文件路径SRC_DIRS ${src_dirs}# 自定义头文件的路径INCLUDE_DIRS ${include_dirs}# 依赖库的路径REQUIRES ${requires}
)# 设置特定组件编译选项的函数
# -ffast-math: 允许编译器进行某些可能减少数学运算精度的优化,以提高性能。
# -O3: 这是一个优化级别选项,指示编译器尽可能地进行高级优化以生成更高效的代码。
# -Wno-error=format: 这将编译器关于格式字符串不匹配的警告从错误降级为警告。
# -Wno-format: 这将完全禁用关于格式字符串的警告。
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

  修改【main】文件夹下的 main.c 文件。

#include "freertos/FreeRTOS.h"#include "lcd.h"// app_main()函数是ESP32的入口函数,它是FreRTOS的一个任务,任务优先级是1
// main()函数是C语言入口函数,它会在编译过程中插入到二进制文件中的
void app_main(void)
{bsp_spi_init(SPI2_HOST, GPIO_NUM_1, GPIO_NUM_2, GPIO_NUM_3);bsp_spi_device_interface_config(SPI2_HOST, LCD_CS_GPIO_NUM, 0, 60000000, &g_lcd_spi_device_handle);lcd_init();lcd_show_char(0, 0, 'A', 12, BLUE, RED, LCD_DISPLAY_OVERLAPPING);lcd_show_char(10, 0, 'A', 16, BLUE, RED, LCD_DISPLAY_OVERLAPPING);lcd_show_string(10, 20, "Hello Shana!", 32, BLUE, RED, LCD_DISPLAY_OVERLAPPING);lcd_show_chinese(80, 60, "小樱", 32, BLUE, RED, LCD_DISPLAY_OVERLAPPING);while (1){// 将一个任务延迟给定的滴答数,IDF中提供pdMS_TO_TICKS可以将指定的ms转换为对应的tick数vTaskDelay(pdMS_TO_TICKS(10));}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/897864.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20241118戴子涵猜数字

# 学号 2024-2025-2 《Python程序设计》实验1报告 课程:《Python程序设计》 班级: 2411 姓名: 戴子涵 学号:20241118 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 此处填写实验的具体内容; import random print(""" 😍😍…

SkekchUp Pro 2024下载与安装教程

SketchUp Pro 2024作为一款专业的3D建模软件,在建筑设计、城市规划和室内设计等领域具有广泛的应用前景. ‌安装SketchUp Pro 2024的基本要求包括操作系统、处理器、内存、显卡等方面的要求。‌ 操作系统要求‌Windows系统‌:SketchUp Pro 2024支持Windows 10和Windows 11操作…

理解GET和POST区别的全面指南

在使用API时,两个最常用的HTTP方法是GET和POST。那么这两者究竟有什么区别呢?在本文中,我们将澄清GET和POST之间的区别,重点介绍它们的独特属性和适用的用例。 REST API原理与API通信 要理解GET和POST的作用,首先需要理解REST API的原理及其如何操作。REST(表现层状态转移…

3.12 拉格朗日乘数法(条件极值)

1 求最大值,最小值 1.1 条件极值1.2 拉格朗日数乘法推导下图可看成是一个四元函数的偏导数即可构造出拉格朗日函数总结: 1.构造拉格朗日函数 原函数+λ条件函数 2.求拉格朗日函数的偏导,另其等于0 3.求出x0,y0,z0 1.3 例题 有时候可以求等价最大小/值(便于求导)可等价于求…

直播预告:慢热的 MCP 终于火了;什么是 MCP,以及智能体通信协议的未来丨RTE Dev Talk

MCP(Model Context Protocol)是一种标准化协议,可将 AI 智能体连接到各种外部工具和数据源。(图:Norah Sakal)慢热的 MCP 终于火了。与此同时,开发者社区中热议的话题还包括 Manus 及其开源复现、Computer Use、Deep Research 等议题——agentic Al 的「ChatGPT」时刻愈…

【设计模式】使用解释器模式简化复杂的语法规则

概述如上图,设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。 //用于两个整数相加 public static int add(int a,int b){return a + b; }//用于两个整数相加 public static int add(int a,int b,int c){return a + b + c; }//用于…

20243106 实验一《Python程序设计》实验报告

20243106 2024-2025-2 《Python程序设计》实验x报告 课程:《Python程序设计》 班级: 2431 姓名: 董洪瑞 学号:20243106 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 猜数字啊 2. 实验过程及结果3. 实验代码 import random level=int(input(&q…

Android的不同布局

LinearLayout线性布局:根据orientation的属性值排布:当属性值为horizontal时,在视图水平方向从左向右排列;当属性值为vertical时,在视图垂直方向从上向下排列。 RelativelLayout相对布局:GridLayout网格布局:默认从左往右,从上到下的布局。定义了两个新的属性: column…

【设计模式】备忘录模式教你如何优雅地处理状态快照

概述 备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能…

20243113 实验一《Python程序设计》实验报告

20243113 2025-3-12 《Python程序设计》实验一报告 课程:《Python程序设计》 班级: 2431 姓名: 应超群 学号:20243113 实验教师:王志强 实验日期:2025年3月12日 必修/选修: 公选课 1.实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能;(编写书中的程序,…

【设计模式】通过访问者模式实现分离算法与对象结构

概述 定义:封装一些作用于某种数据结构中的各元素的操作(将数据结构于元素进行分离),它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。 结构 访问者模式包含以下主要角色:抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就…

【设计模式】遍历集合的艺术:深入探索迭代器模式的无限可能

概述 定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。 结构 迭代器模式主要包含以下角色:抽象聚合(Aggregate)角色:定义存储、添加、删除聚合元素以及创建迭代器对象的接口。具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一…