diff --git a/README.md b/README.md index e1efd01..76b527a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ -# 驱动列表 +# 目录说明 +手册:驱动文件对应的芯片手册 + +bsp:部分驱动中用到的底层实现 + +# 驱动列表 | 驱动 | 分类 | 驱动方式 | 功能 | | ------------ | -------- | -------- | ------------------- | diff --git a/bsp/gpios.c b/bsp/gpios.c new file mode 100644 index 0000000..cb59b80 --- /dev/null +++ b/bsp/gpios.c @@ -0,0 +1,76 @@ +#include "gpios.h" +#include "gpio.h" +/** + * @brief 设置GPIO引脚为高电平 + * @param {gpio_t} gpio - GPIO对象 + * @note: 用于设置指定GPIO引脚为高电平。 + */ +static void _set(gpio_t gpio) +{ + GPIO_SET(gpio.port, gpio.pin); +} + +/** + * @brief 设置GPIO引脚为低电平 + * @param {gpio_t} gpio - GPIO对象 + * @note: 用于设置指定GPIO引脚为低电平。 + */ +static void _reset(gpio_t gpio) +{ + GPIO_RESET(gpio.port, gpio.pin); +} + +/** + * @brief 切换GPIO引脚状态 + * @param {gpio_t} gpio - GPIO对象 + * @note: 用于切换指定GPIO引脚的状态,即高电平变为低电平,低电平变为高电平。 + */ +static void _toggle(gpio_t gpio) +{ + GPIO_TOGGLE(gpio.port, gpio.pin); +} + +/** + * @brief 读取GPIO引脚状态 + * @param {gpio_t} gpio - GPIO对象 + * @return {*} - GPIO引脚当前状态,即0表示低电平,1表示高电平 + * @note: 用于读取指定GPIO引脚的状态,即返回0或1。 + */ +static uint8_t _read(gpio_t gpio) +{ + return (uint8_t)GPIO_READ(gpio.port, gpio.pin); +} + +/** + * @brief 创建GPIO对象 + * @param {GPIO_TypeDef} *port - GPIO寄存器指针 + * @param {uint16_t} pin - 引脚号 + * @return {gpio_t *} - 创建的GPIO对象指针 + * @note: 用于创建一个GPIO对象,用于操作特定端口和引脚的GPIO功能。 + */ +gpio_t *gpio_create(GPIO_TypeDef *port, uint16_t pin) +{ + gpio_t *gpio = (gpio_t *)osel_mem_alloc(sizeof(gpio_t)); + DBG_ASSERT(gpio != NULL __DBG_LINE); + gpio->port = port; + gpio->pin = pin; + gpio->set = _set; + gpio->reset = _reset; + gpio->toggle = _toggle; + gpio->read = _read; + return gpio; +} + +/** + * @brief 释放GPIO对象 + * @param {gpio_t} *gpio - GPIO对象指针 + * @return {*} + * @note: 用于释放一个GPIO对象,释放后不能再使用该对象。 + */ +void gpio_free(gpio_t *gpio) +{ + if (gpio != NULL) + { + osel_mem_free(gpio); + } +} diff --git a/bsp/gpios.h b/bsp/gpios.h new file mode 100644 index 0000000..35b86fc --- /dev/null +++ b/bsp/gpios.h @@ -0,0 +1,144 @@ +/** + * @file gpios.h + * @brief Header file for GPIO configuration and control. + * + * This file contains the declarations and definitions for GPIO configuration and control functions. + * + * @author xxx + * @date 2023-12-27 14:44:03 + * @version 1.0 + * @copyright Copyright (c) 2024 by xxx, All Rights Reserved. + */ + +#ifndef __GPIOS_H__ +#define __GPIOS_H__ +#include "lib.h" +#include "main.h" +/** + * @brief Set the GPIO pin to high. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + */ +#define GPIO_SET(port, pin) (LL_GPIO_SetOutputPin(port, pin)) + +/** + * @brief Set the GPIO pin to low. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + */ +#define GPIO_RESET(port, pin) (LL_GPIO_ResetOutputPin(port, pin)) + +/** + * @brief Toggle the state of the GPIO pin. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + */ +#define GPIO_TOGGLE(port, pin) (LL_GPIO_TogglePin(port, pin)) + +/** + * @brief Read the state of the GPIO pin. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + * @return The state of the GPIO pin (1 if high, 0 if low). + */ +#define GPIO_READ(port, pin) (LL_GPIO_IsInputPinSet(port, pin)) + +/** + * @brief Set the GPIO pin as input. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + */ +#define GPIO_SET_INPUT(port, pin) (LL_GPIO_SetPinMode(port, pin, LL_GPIO_MODE_INPUT)) + +/** + * @brief Set the GPIO pin as output. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + */ +#define GPIO_SET_OUTPUT(port, pin) \ + do \ + { \ + LL_GPIO_SetPinMode(port, pin, LL_GPIO_MODE_OUTPUT); \ + } while (0) + +/** + * @brief Set the GPIO pin as alternate function. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + */ +#define GPIO_SET_ALTERNATE(port, pin) (LL_GPIO_SetPinMode(port, pin, LL_GPIO_MODE_ALTERNATE)) + +/** + * @brief Set the GPIO pin as analog. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + */ +#define GPIO_SET_ANALOG(port, pin) \ + do \ + { \ + LL_GPIO_SetPinMode(port, pin, LL_GPIO_MODE_ANALOG); \ + } while (0) + +/** + * @brief Structure representing a GPIO pin. + */ +typedef struct GPIO +{ + GPIO_TypeDef *port; ///< The GPIO port. + uint16_t pin; ///< The GPIO pin. + + /** + * @brief Set the GPIO pin to high. + * + * @param gpio The GPIO pin. + */ + void (*set)(struct GPIO gpio); + + /** + * @brief Set the GPIO pin to low. + * + * @param gpio The GPIO pin. + */ + void (*reset)(struct GPIO gpio); + + /** + * @brief Toggle the state of the GPIO pin. + * + * @param gpio The GPIO pin. + */ + void (*toggle)(struct GPIO gpio); + + /** + * @brief Read the state of the GPIO pin. + * + * @param gpio The GPIO pin. + * @return The state of the GPIO pin (1 if high, 0 if low). + */ + uint8_t (*read)(struct GPIO gpio); +} gpio_t; + +/** + * @brief Create a GPIO pin. + * + * @param port The GPIO port. + * @param pin The GPIO pin. + * @return The created GPIO pin. + */ +extern gpio_t *gpio_create(GPIO_TypeDef *port, uint16_t pin); + +/** + * @brief Free the memory allocated for a GPIO pin. + * + * @param gpio The GPIO pin to free. + */ +extern void gpio_free(gpio_t *gpio); + +#endif ///< __GPIOS_H__ diff --git a/bsp/i2cs.c b/bsp/i2cs.c new file mode 100644 index 0000000..c6f53ba --- /dev/null +++ b/bsp/i2cs.c @@ -0,0 +1,669 @@ +#include "i2cs.h" +#include "main.h" + +static inline void delay(i2c_t *handle); // 延时函数 +static inline void _ack(i2c_t *handle); // 应答 +static inline void _nack(i2c_t *handle); // 非应答 + +/** + * @brief 启动I2C总线 + * @param {i2c_t} *handle - I2C总线句柄 + * @note: 用于启动I2C总线的操作。在发送或接收数据之前,需要先启动总线。 + */ +static void _start(i2c_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + // 获取gpios指针 + i2c_gpio_group_t *gpios = &handle->gpios; + // 获取scl指针 + gpio_t *scl = gpios->scl; + // 获取sda指针 + gpio_t *sda = gpios->sda; + // 设置sda + gpios->sda->set(*sda); + // 设置scl + gpios->scl->set(*scl); + // 延时 + delay(handle); + // 重置sda + gpios->sda->reset(*sda); + // 延时 + delay(handle); + // 重置scl + gpios->scl->reset(*scl); + // 延时 + delay(handle); +} + +/** + * @brief 停止I2C总线 + * @param {i2c_t} *handle - I2C总线句柄 + * @note: 用于停止I2C总线的操作。在发送或接收数据之后,需要先停止总线。 + */ +static void _stop(i2c_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + i2c_gpio_group_t *gpios = &handle->gpios; + gpio_t *scl = gpios->scl; + gpio_t *sda = gpios->sda; + gpios->scl->reset(*scl); + gpios->sda->reset(*sda); + delay(handle); + gpios->scl->set(*scl); + gpios->sda->set(*sda); + delay(handle); +} +/** + * @brief 等待应答信号 + * @param {i2c_t} *handle - I2C总线句柄 + * @return {BOOL} - 等待成功返回TRUE,否则返回FALSE + * @note: 用于等待I2C总线上发送的应答信号。 + */ +static BOOL _wait_ack(i2c_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + uint8_t count = 0; + i2c_gpio_group_t *gpios = &handle->gpios; + gpio_t *scl = gpios->scl; + gpio_t *sda = gpios->sda; + gpios->sda->set(*sda); + gpios->scl->set(*scl); + delay(handle); + while (gpios->sda->read(*sda)) + { + count++; + if (count > 250) + { + _stop(handle); + return FALSE; + } + } + gpios->scl->reset(*scl); + delay(handle); + return TRUE; +} + +/** + * @brief 读取一个字节 + * @param {i2c_t} *handle - I2C总线句柄 + * @param {BOOL} ack - 应答信号标志 + * @return {uint8_t} - 读取到的字节 + * @note: 用于从I2C总线上读取一个字节。在读取一个字节后,需要发送应答信号。 + */ +static uint8_t _read_byte(i2c_t *handle, BOOL ack) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + uint8_t i = 0, receive = 0; + i2c_gpio_group_t *gpios = &handle->gpios; + gpio_t *scl = gpios->scl; + gpio_t *sda = gpios->sda; + for (i = 0; i < 8; i++) + { + gpios->sda->set(*sda); + gpios->scl->set(*scl); + receive <<= 1; + delay(handle); + + if (gpios->sda->read(*sda)) + receive++; + + gpios->scl->reset(*scl); + delay(handle); + } + + if (TRUE == ack) + { + _ack(handle); + } + else + { + _nack(handle); + } + return receive; +} +/** + * @brief 发送一个字节的数据到I2C总线上 + * @param {i2c_t} *handle I2C总线的句柄 + * @param {uint8_t} data 要发送的字节数据 + * @return {*} 无 + * @note: 该函数用于在I2C总线上发送一个字节的数据。它首先定义了一个循环,用于遍历要发送的字节中的每一位。在循环中,首先检查当前位是否为1,如果是,则设置SDA为1,否则设置SDA为0。然后设置SCL为1,延时1ms。接着将数据右移一位,然后设置SCL为0,延时1ms。当位遍历完后,最后设置SDA为1,以确保正确的结束传输。 + */ +static void _write_byte(i2c_t *handle, uint8_t data) +{ + // 定义变量i + uint8_t i = 0; + // 断言参数handle不为空 + DBG_ASSERT(handle != NULL __DBG_LINE); + // 定义变量gpios + i2c_gpio_group_t *gpios = &handle->gpios; + // 定义变量scl + gpio_t *scl = gpios->scl; + // 定义变量sda + gpio_t *sda = gpios->sda; + // 遍历每一位 + for (i = 0; i < 8; i++) + { + // 如果data的最低位为1 + if (data & 0x80) + { + // 设置sda的状态为1 + gpios->sda->set(*sda); + } + // 否则,设置sda的状态为0 + else + { + // 设置sda的状态为0 + gpios->sda->reset(*sda); + } + + // 设置scl的状态为1 + gpios->scl->set(*scl); + // 延时1ms + delay(handle); + // 将data右移1位 + data <<= 1; + // 设置scl的状态为0 + gpios->scl->reset(*scl); + // 延时1ms + delay(handle); + + // 如果i等于7 + if (i == 7) + { + // 设置sda的状态为1 + gpios->sda->set(*sda); + } + } +} + +/** + * @brief 发送一个字节的数据到I2C总线上 + * @param {i2c_t} *handle I2C总线的句柄 + * @param {uint16_t} data 要发送的字节数据 + * @return {*} 无 + * @note: 该函数用于在I2C总线上发送一个字节的数据。它首先定义了一个循环,用于遍历要发送的字节中的每一位。在循环中,首先检查当前位是否为1,如果是,则设置SDA为1,否则设置SDA为0。然后设置SCL为1,延时1ms。接着将数据右移一位,然后设置SCL为0,延时1ms。当位遍历完后,最后设置SDA为1,以确保正确的结束传输。 + */ +static void _write_word(i2c_t *handle, uint16_t data) +{ + // 循环写入2个字节 + for (uint8_t i = 0; i < 2; i++) + { + // 将data的第i个字节写入i2c接口 + _write_byte(handle, (uint8_t)(data >> (8 * i))); + // 等待ACK + _wait_ack(handle); + } +} + +/** + * @brief 创建一个I2C总线设备 + * @param {i2c_gpio_group_t} gpios I2C总线的GPIO配置 + * @param {uint16_t} delay_ticks I2C总线的延时参数 + * @return {i2c_t *} 创建的I2C总线设备句柄 + * @note: 该函数用于创建一个I2C总线设备。它首先创建一个i2c_t结构体,并将gpios和delay_ticks的内存地址复制到handle结构体中。然后,它为handle结构体定义了start、stop、wait_ack、read_byte、write_byte和write_word函数。最后,它返回handle结构体。 + */ +i2c_t *i2c_create(i2c_gpio_group_t gpios, uint16_t delay_ticks) +{ + // 创建一个i2c_t结构体 + i2c_t *handle = (i2c_t *)osel_mem_alloc(sizeof(i2c_t)); + // 将gpios的内存地址复制到handle结构体中 + osel_memcpy((uint8_t *)&handle->gpios, (uint8_t *)&gpios, sizeof(i2c_gpio_group_t)); + // 将delay_ticks的内存地址复制到handle结构体中 + handle->delay_ticks = delay_ticks; + + // 创建一个start函数 + handle->interface.start = _start; + // 创建一个stop函数 + handle->interface.stop = _stop; + // 创建一个wait_ack函数 + handle->interface.wait_ack = _wait_ack; + // 创建一个read_byte函数 + handle->interface.read_byte = _read_byte; + // 创建一个write_byte函数 + handle->interface.write_byte = _write_byte; + // 创建一个write_word函数 + handle->interface.write_word = _write_word; + + handle->dead_count = 0; + // 返回handle结构体 + return handle; +} + +/** + * @brief 设置I2C器件地址 + * @param {i2c_t} *handle + * @param {uint8_t} w_address 写地址 + * @param {uint8_t} r_address 读地址 + * @return {*} + * @note + */ +void i2c_dma_set_address(i2c_t *handle, uint8_t w_address, uint8_t r_address) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->w_address = w_address; + handle->r_address = r_address; +} + +/** + * @brief 释放I2C总线设备 + * @param {i2c_t} *handle 需要释放的I2C总线设备句柄 + * @return {*} 无 + * @note: 该函数用于释放一个I2C总线设备。它首先检查handle是否为空,如果不为空,则释放所有的GPIO。最后,释放handle的内存。 + */ +void i2c_free(i2c_t *handle) +{ + // 如果handle不为空,则释放所有的gpio + if (NULL != handle) + { + gpio_free(handle->gpios.scl); + gpio_free(handle->gpios.sda); + osel_mem_free(handle); + } +} + +/** + * @brief I2C DMA TX回调函数 + * @param {i2c_t} handle + * @return {*} + * @note + */ +void i2c_dma_callback(i2c_t *handle) +{ + // 检查输入参数是否为空 + DBG_ASSERT(handle != NULL __DBG_LINE); + // 清除传输完成标志位 + if (handle->dma_tx_cb != NULL) + { + handle->dma_tx_cb(handle); + } + if (handle->dma_rx_cb != NULL) + { + handle->dma_rx_cb(handle); + } +} + +#if defined(STM32L4xx_LL_I2C_H) +static void i2c_reset(i2c_t *handle); +static BOOL _read_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size); +static BOOL _write_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size); +/** + * @brief 创建一个I2C总线设备 DMA + * @param {I2C_TypeDef} *i2c + * @param {DMA_TypeDef} *dma + * @param {uint16_t} rxsize + * @param {uint32_t} dma_rx_channel + * @param {uint16_t} txsize + * @param {uint32_t} dma_tx_channel + * @return {*} + * @note + */ +i2c_t *i2c_create_dma(I2C_TypeDef *i2c, DMA_TypeDef *dma, uint16_t rxsize, uint32_t dma_rx_channel, + i2cs_dma_callback *dma_rx_cb, uint16_t txsize, uint32_t dma_tx_channel, i2cs_dma_callback *dma_tx_cb) +{ + i2c_t *handle = (i2c_t *)osel_mem_alloc(sizeof(i2c_t)); + handle->i2c = i2c; + handle->dma = dma; + handle->dma_rx_channel = dma_rx_channel; + handle->dma_tx_channel = dma_tx_channel; + handle->rxbuf = (uint8_t *)osel_mem_alloc(rxsize); + handle->txbuf = (uint8_t *)osel_mem_alloc(txsize); + handle->rxsize = rxsize; + handle->txsize = txsize; + handle->tx_dma_ok = TRUE; + handle->interface.write_mem_dma = _write_mem_dma; + handle->interface.read_mem_dma = _read_mem_dma; + if (dma_rx_cb != NULL) + { + handle->dma_rx_cb = dma_rx_cb; + } + + if (dma_tx_cb != NULL) + { + handle->dma_tx_cb = dma_tx_cb; + } + + LL_DMA_DisableChannel(dma, dma_tx_channel); + LL_DMA_DisableChannel(dma, dma_rx_channel); + + // TX + uint8_t *pTransmitBuffer = handle->txbuf; + LL_DMA_ConfigTransfer(dma, dma_tx_channel, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_PRIORITY_HIGH | LL_DMA_MODE_NORMAL | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | LL_DMA_MDATAALIGN_BYTE); + LL_DMA_ConfigAddresses(dma, dma_tx_channel, (uint32_t)pTransmitBuffer, (uint32_t)LL_I2C_DMA_GetRegAddr(i2c, LL_I2C_DMA_REG_DATA_TRANSMIT), LL_DMA_GetDataTransferDirection(dma, dma_tx_channel)); + LL_DMA_SetPeriphRequest(dma, dma_tx_channel, LL_DMA_REQUEST_3); + + // RX + uint8_t *pReceiveBuffer = handle->rxbuf; + LL_DMA_ConfigTransfer(dma, dma_rx_channel, LL_DMA_DIRECTION_PERIPH_TO_MEMORY | LL_DMA_PRIORITY_HIGH | LL_DMA_MODE_NORMAL | LL_DMA_PERIPH_NOINCREMENT | LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | LL_DMA_MDATAALIGN_BYTE); + LL_DMA_ConfigAddresses(dma, dma_rx_channel, (uint32_t)LL_I2C_DMA_GetRegAddr(i2c, LL_I2C_DMA_REG_DATA_RECEIVE), (uint32_t)pReceiveBuffer, LL_DMA_GetDataTransferDirection(dma, dma_rx_channel)); + LL_DMA_SetPeriphRequest(dma, dma_rx_channel, LL_DMA_REQUEST_3); + + LL_DMA_EnableIT_TC(dma, dma_tx_channel); + LL_DMA_EnableIT_TE(dma, dma_tx_channel); + LL_DMA_EnableIT_TC(dma, dma_rx_channel); + LL_DMA_EnableIT_TE(dma, dma_rx_channel); + + LL_I2C_EnableDMAReq_TX(i2c); + LL_I2C_EnableDMAReq_RX(i2c); + LL_I2C_Enable(i2c); + + return handle; +} + +/** + * @brief 非阻塞模式下使用DMA从特定内存地址读取数据 + * @param {i2c_t} *handle 指向一个i2c_t结构体,其中包含指定I2C的配置信息 + * @param {uint16_t} dev_address 目标设备地址:在数据手册中,设备7位地址值需要向左移动以调用接口 + * @param {uint16_t} mem_address 内部内存地址 + * @param {uint16_t} mem_addsize 内部内存地址大小 + * @param {uint8_t} *data 数据缓冲区的指针 + * @param {uint16_t} size 要发送的数据量 + * @return {*} + * @note + */ +static BOOL _read_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(data != NULL __DBG_LINE); + if (LL_I2C_IsActiveFlag_BUSY(handle->i2c) == 1) + { + i2c_reset(handle); // xsh:重置I2C,修复一段时间后无法读写的问题 + return FALSE; + } + uint16_t count = 2000; + handle->txsize = 0; + handle->tx_dma_ok = FALSE; + handle->rx_dma_ok = FALSE; + + for (uint8_t i = mem_addsize; i > 0; i--) + { + handle->txbuf[handle->txsize++] = (uint8_t)(mem_address >> (8 * (i - 1))); + } + + LL_DMA_SetDataLength(handle->dma, handle->dma_tx_channel, handle->txsize); + LL_DMA_EnableChannel(handle->dma, handle->dma_tx_channel); + + LL_I2C_HandleTransfer(handle->i2c, handle->w_address, LL_I2C_ADDRSLAVE_7BIT, handle->txsize, LL_I2C_MODE_SOFTEND, LL_I2C_GENERATE_START_WRITE); + count = 2000; + while (!handle->tx_dma_ok) + { + if (count-- == 0) + { + handle->tx_dma_ok = TRUE; + return FALSE; + } + } + + count = 2000; + while (LL_I2C_IsActiveFlag_TC(handle->i2c) != 1) + { + if (count-- == 0) + { + handle->tx_dma_ok = TRUE; + return FALSE; + } + } + + handle->tx_dma_ok = FALSE; + handle->rx_dma_ok = FALSE; + handle->rxsize = size; + osel_memset(handle->rxbuf, 0, handle->rxsize); + LL_DMA_DisableChannel(handle->dma, handle->dma_tx_channel); + LL_DMA_SetDataLength(handle->dma, handle->dma_rx_channel, handle->rxsize); + LL_DMA_EnableChannel(handle->dma, handle->dma_rx_channel); + LL_I2C_HandleTransfer(handle->i2c, handle->r_address, LL_I2C_ADDRSLAVE_7BIT, handle->rxsize, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_RESTART_7BIT_READ); + + count = 2000; + while (!LL_I2C_IsActiveFlag_STOP(handle->i2c)) + { + if (count-- == 0) + { + handle->tx_dma_ok = TRUE; + return FALSE; + } + } + + LL_DMA_DisableChannel(handle->dma, handle->dma_rx_channel); + LL_I2C_ClearFlag_STOP(handle->i2c); + osel_memcpy(data, handle->rxbuf, handle->rxsize); + return TRUE; +} + +/** + * @brief 非阻塞模式下使用DMA将数据写入特定内存地址 + * @param {i2c_t} *handle 指向一个i2c_t结构体,其中包含指定I2C的配置信息 + * @param {uint16_t} dev_address 目标设备地址:在数据手册中,设备7位地址值需要向左移动以调用接口 + * @param {uint16_t} mem_address 内部内存地址 + * @param {uint16_t} mem_addsize 内部内存地址大小 + * @param {uint8_t} *data 数据缓冲区的指针 + * @param {uint16_t} size 要发送的数据量 + * @return {*} + * @note + */ +static BOOL _write_mem_dma(i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(data != NULL __DBG_LINE); + uint16_t count = 2000; + if (LL_I2C_IsActiveFlag_BUSY(handle->i2c) == 1) + { + i2c_reset(handle); // xsh:重置I2C,修复一段时间后无法读写的问题 + return FALSE; + } + + handle->txsize = 0; + handle->tx_dma_ok = FALSE; + for (uint8_t i = mem_addsize; i > 0; i--) + { + handle->txbuf[handle->txsize++] = (uint8_t)(mem_address >> (8 * (i - 1))); + } + osel_memcpy(&handle->txbuf[handle->txsize], data, size); + handle->txsize += size; + LL_DMA_SetDataLength(handle->dma, handle->dma_tx_channel, handle->txsize); + LL_DMA_EnableChannel(handle->dma, handle->dma_tx_channel); + + LL_I2C_HandleTransfer(handle->i2c, handle->w_address, LL_I2C_ADDRSLAVE_7BIT, handle->txsize, LL_I2C_MODE_AUTOEND, LL_I2C_GENERATE_START_WRITE); + + count = 2000; + while (!handle->tx_dma_ok) + { + if (count-- == 0) + { + handle->tx_dma_ok = TRUE; + return FALSE; + } + } + count = 2000; + while (!LL_I2C_IsActiveFlag_STOP(handle->i2c)) + { + if (count-- == 0) + { + handle->tx_dma_ok = TRUE; + return FALSE; + } + } + + LL_DMA_DisableChannel(handle->dma, handle->dma_tx_channel); + LL_I2C_ClearFlag_STOP(handle->i2c); + + return TRUE; +} + +/** + * @brief I2C重置 + * @param {I2C_TypeDef} *I2Cx + * @return {*} + * @note 解决I2C总线死锁问题 + */ +static void i2c_reset(i2c_t *handle) +{ + LL_I2C_Disable(handle->i2c); + LL_I2C_Enable(handle->i2c); + handle->dead_count++; +} + +/** + * @brief I2C 中断回调函数 + * @param {i2c_t} *handle + * @return {*} + * @note + */ +void i2c_ev_callback(i2c_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + if (LL_I2C_IsActiveFlag_ADDR(handle->i2c)) + { + /* Verify the Address Match with the OWN Slave address */ + if (LL_I2C_GetAddressMatchCode(handle->i2c) == handle->w_address || LL_I2C_GetAddressMatchCode(handle->i2c) == handle->r_address) + { + /* Verify the transfer direction, a write direction, Slave enters receiver mode */ + if (LL_I2C_GetTransferDirection(handle->i2c) == LL_I2C_DIRECTION_WRITE) + { + /* Clear ADDR flag value in ISR register */ + LL_I2C_ClearFlag_ADDR(handle->i2c); + + /* Enable Receive Interrupt */ + LL_I2C_EnableIT_RX(handle->i2c); + } + else if (LL_I2C_GetTransferDirection(handle->i2c) == LL_I2C_DIRECTION_READ) + { + /* Clear ADDR flag value in ISR register */ + LL_I2C_ClearFlag_ADDR(handle->i2c); + + /* Enable Transmit Interrupt */ + LL_I2C_EnableIT_TX(handle->i2c); + } + } + else + { + /* Clear ADDR flag value in ISR register */ + LL_I2C_ClearFlag_ADDR(handle->i2c); + + /* Call Error function */ + DBG_ASSERT(FALSE __DBG_LINE); + } + } + /* Check NACK flag value in ISR register */ + else if (LL_I2C_IsActiveFlag_NACK(handle->i2c)) + { + /* End of Transfer */ + LL_I2C_ClearFlag_NACK(handle->i2c); + } + /* Check RXNE flag value in ISR register */ + else if (LL_I2C_IsActiveFlag_RXNE(handle->i2c)) + { + /* Call function Slave Reception Callback */ + } + /* Check TXIS flag value in ISR register */ + else if (LL_I2C_IsActiveFlag_TXIS(handle->i2c)) + { + /* Call function Slave Ready to Transmit Callback */ + } + /* Check STOP flag value in ISR register */ + else if (LL_I2C_IsActiveFlag_STOP(handle->i2c)) + { + /* End of Transfer */ + LL_I2C_ClearFlag_STOP(handle->i2c); + + /* Check TXE flag value in ISR register */ + if (!LL_I2C_IsActiveFlag_TXE(handle->i2c)) + { + /* Flush TX buffer */ + LL_I2C_ClearFlag_TXE(handle->i2c); + } + + /* Call function Slave Complete Callback */ + } + /* Check TXE flag value in ISR register */ + else if (!LL_I2C_IsActiveFlag_TXE(handle->i2c)) + { + /* Do nothing */ + /* This Flag will be set by hardware when the TXDR register is empty */ + /* If needed, use LL_I2C_ClearFlag_TXE() interface to flush the TXDR register */ + } + else + { + /* Call Error function */ + DBG_ASSERT(FALSE __DBG_LINE); + } +} +#endif + +/*下面是内部实现方法*/ + +/** + * @brief 此方法是一个简单的延迟函数,它使用循环来执行指定数量的NOP(无操作)指令。这个延迟函数的目的是在程序的执行中引入延迟,这在需要精确定时的某些应用中很有用。延迟时间由输入参数“count”的值决定,该参数指定要执行的NOP指令的数量。延迟时间通常以微秒或毫秒为单位测量,具体取决于使用延迟的环境。 + * @param {uint16_t} count NOP指令的数量 + * @return {*} + */ +static inline void delay(i2c_t *handle) +{ + // 断言参数handle不为空 + DBG_ASSERT(handle != NULL __DBG_LINE); + // 定义循环计数变量count + uint16_t count = 0; + // 设置循环计数变量count的值为handle->delay_ticks + count = handle->delay_ticks; + // 循环计数变量count的值,直到count的值为0 + while (count--) + { + // 每次循环调用__NOP()函数 + __NOP(); + } +} + +/** + * @brief 发送ACK信号 + * @param {i2c_t} *handle I2C总线的句柄 + * @return {*} 无 + * @note: 该函数用于在I2C总线上发送ACK信号。它首先断言handle不为空,然后获取gpios和scl、sda的指针。接着重置sda,延时1ms,设置scl为1,延时1ms,重置scl,确保正确的结束传输。 + */ +static inline void _ack(i2c_t *handle) +{ + // 断言handle不为空 + DBG_ASSERT(handle != NULL __DBG_LINE); + // 获取gpios指针 + i2c_gpio_group_t *gpios = &handle->gpios; + // 获取scl指针 + gpio_t *scl = gpios->scl; + // 获取sda指针 + gpio_t *sda = gpios->sda; + + // 重置sda + gpios->sda->reset(*sda); + // 延时 + delay(handle); + // 设置scl + gpios->scl->set(*scl); + // 延时 + delay(handle); + // 重置scl + gpios->scl->reset(*scl); +} + +/** + * @brief 发送NACK信号 + * @param {i2c_t} *handle I2C总线的句柄 + * @return {*} 无 + * @note: 该函数用于在I2C总线上发送NACK信号。它首先断言handle不为空,然后获取gpios和scl、sda的指针。接着设置sda为1,延时1ms,设置scl为1,延时1ms,重置scl,确保正确的结束传输。 + */ +static inline void _nack(i2c_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + // 获取gpios指针 + i2c_gpio_group_t *gpios = &handle->gpios; + // 获取scl指针 + gpio_t *scl = gpios->scl; + // 获取sda指针 + gpio_t *sda = gpios->sda; + + // 设置sda引脚 + gpios->sda->set(*sda); + // 等待延时 + delay(handle); + // 设置scl引脚 + gpios->scl->set(*scl); + // 等待延时 + delay(handle); + // 重置scl引脚 + gpios->scl->reset(*scl); +} diff --git a/bsp/i2cs.h b/bsp/i2cs.h new file mode 100644 index 0000000..bf83806 --- /dev/null +++ b/bsp/i2cs.h @@ -0,0 +1,127 @@ +/** + * @file i2cs.h + * @brief Header file for I2C Slave module. + * + * This file contains the declarations and definitions for the I2C Slave module. + * It provides functions to initialize and configure the I2C peripheral as a slave, + * as well as functions to send and receive data over the I2C bus. + * + * @author xxx + * @date 2023-12-27 14:44:03 + * @version 1.0 + * @copyright Copyright (c) 2024 by xxx, All Rights Reserved. + */ + +#ifndef __I2CS_H__ +#define __I2CS_H__ +#include "lib.h" +#include "gpios.h" +/** + * @file i2cs.h + * @brief Header file containing the definition of the I2C slave (I2CS) structure and related functions. + */ + +typedef struct I2CS i2c_t; +typedef void i2cs_dma_callback(i2c_t *handle); + +typedef struct +{ + void (*start)(i2c_t *handle); ///< Function pointer to start the I2C communication. + void (*stop)(i2c_t *handle); ///< Function pointer to stop the I2C communication. + BOOL(*wait_ack) + (i2c_t *handle); ///< Function pointer to wait for the acknowledgment from the I2C bus. + + void (*write_byte)(i2c_t *handle, uint8_t data); ///< Function pointer to write a byte of data to the I2C bus. + uint8_t (*read_byte)(i2c_t *handle, BOOL ack); ///< Function pointer to read a byte of data from the I2C bus. + + void (*write_word)(i2c_t *handle, uint16_t data); ///< Function pointer to write two bytes of data to the I2C bus. + + BOOL(*write_mem_dma) + (i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size); ///< Function pointer to write multiple bytes of data to a memory address using DMA. + BOOL(*read_mem_dma) + (i2c_t *handle, uint16_t mem_address, uint16_t mem_addsize, uint8_t *data, uint16_t size); ///< Function pointer to read multiple bytes of data from a memory address using DMA. +} i2c_interface_t; + +typedef struct +{ + struct GPIO *scl; ///< Pointer to the GPIO pin used for the I2C clock (SCL). + struct GPIO *sda; ///< Pointer to the GPIO pin used for the I2C data (SDA). +} i2c_gpio_group_t; + +struct I2CS +{ + ///< Analog part definition + i2c_gpio_group_t gpios; ///< Structure containing the GPIO pins used for the I2C communication. + uint16_t delay_ticks; ///< Number of NOP instructions to delay the I2C communication. + + ///< Hardware part definition + I2C_TypeDef *i2c; ///< Pointer to the I2C peripheral. + DMA_TypeDef *dma; ///< Pointer to the DMA peripheral. + uint32_t dma_rx_channel; ///< DMA channel used for receiving data. + uint32_t dma_tx_channel; ///< DMA channel used for transmitting data. + uint8_t *rxbuf; ///< Pointer to the receive buffer. + uint16_t rxsize; ///< Size of the receive buffer. + uint8_t *txbuf; ///< Pointer to the transmit buffer. + uint16_t txsize; ///< Size of the transmit buffer. + uint8_t w_address; ///< 7-bit write address. + uint8_t r_address; ///< 7-bit read address. + __IO BOOL rx_dma_ok; ///< Flag indicating the completion of receive DMA. + __IO BOOL tx_dma_ok; ///< Flag indicating the completion of transmit DMA. + + i2cs_dma_callback *dma_rx_cb; ///< Callback function called when receive DMA is completed. + i2cs_dma_callback *dma_tx_cb; ///< Callback function called when transmit DMA is completed. + + i2c_interface_t interface; ///< Structure containing the function pointers for the I2C interface. + uint16_t dead_count; ///< Counter for the number of deadlocks. +}; + +/** + * @brief Creates an I2C slave instance with GPIO pins for clock and data. + * @param gpios The GPIO pins used for the I2C communication. + * @param delay_ticks The number of NOP instructions to delay the I2C communication. + * @return A pointer to the created I2C slave instance. + */ +extern i2c_t *i2c_create(i2c_gpio_group_t gpios, uint16_t delay_ticks); + +/** + * @brief Creates an I2C slave instance with DMA support. + * @param i2c Pointer to the I2C peripheral. + * @param dma Pointer to the DMA peripheral. + * @param rxsize Size of the receive buffer. + * @param dma_rx_channel DMA channel used for receiving data. + * @param dma_rx_cb Callback function called when receive DMA is completed. + * @param txsize Size of the transmit buffer. + * @param dma_tx_channel DMA channel used for transmitting data. + * @param dma_tx_cb Callback function called when transmit DMA is completed. + * @return A pointer to the created I2C slave instance. + */ +extern i2c_t *i2c_create_dma(I2C_TypeDef *i2c, DMA_TypeDef *dma, uint16_t rxsize, uint32_t dma_rx_channel, + i2cs_dma_callback *dma_rx_cb, uint16_t txsize, uint32_t dma_tx_channel, i2cs_dma_callback *dma_tx_cb); + +/** + * @brief Sets the write and read addresses for the I2C slave instance with DMA support. + * @param handle Pointer to the I2C slave instance. + * @param w_address 7-bit write address. + * @param r_address 7-bit read address. + */ +extern void i2c_dma_set_address(i2c_t *handle, uint8_t w_address, uint8_t r_address); + +/** + * @brief Frees the resources used by the I2C slave instance. + * @param handle Pointer to the I2C slave instance. + */ +extern void i2c_free(i2c_t *handle); + +/** + * @brief Callback function called when an I2C event occurs. + * @param handle Pointer to the I2C slave instance. + */ +extern void i2c_ev_callback(i2c_t *handle); + +/** + * @brief Callback function called when an I2C DMA event occurs. + * @param handle Pointer to the I2C slave instance. + */ +extern void i2c_dma_callback(i2c_t *handle); + +#endif ///< __I2CS_H__ diff --git a/bsp/readme.md b/bsp/readme.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/bsp/readme.md @@ -0,0 +1 @@ + diff --git a/bsp/spis.c b/bsp/spis.c new file mode 100644 index 0000000..15bf791 --- /dev/null +++ b/bsp/spis.c @@ -0,0 +1,811 @@ +#include "spis.h" +#include "delay.h" + +#define SPI_TIMEOUT 2000 + +#define CMD_RDSR 0x05 /*!< Read Status Register instruction */ +#define CMD_WRSR 0x01 /*!< Write Status Register instruction */ +#define CMD_WREN 0x06 /*!< Write enable instruction */ +#define CMD_WRDI 0x04 /*!< Write disable instruction */ +#define CMD_READ 0x03 /*!< Read from Memory instruction */ +#define CMD_WRITE 0x02 /*!< Write to Memory instruction */ +#define DUMMY_BYTE 0xA5 ///< 虚拟字节 + +static inline void spi_delay(spi_t *handle); // 延时函数 + +static inline void spi_rdy_high(spi_t *handle); // RDY高电平 +static inline void spi_rdy_low(spi_t *handle); // RDY低电平 +static inline void spi_cs_high(spi_t *handle); // CS高电平 +static inline void spi_cs_low(spi_t *handle); // CS低电平 +static inline void spi_mosi_high(spi_t *handle); // MOSI高电平 +static inline void spi_mosi_low(spi_t *handle); // MOSI低电平 +static inline void spi_sck_high(spi_t *handle); // SCK高电平 +static inline void spi_sck_low(spi_t *handle); // SCK低电平 +static inline uint8_t spi_miso_read(spi_t *handle); // 读取MISO电平 + +static uint8_t spi_read_write_byte(spi_t *handle, uint8_t tx_data); // 读写一个字节 +static void spi_reset(spi_t *handle); // 复位 +static BOOL spi_write(spi_t *handle, uint32_t write_addr, uint8_t *data, uint16_t length); // 写数据 +static BOOL spi_read(spi_t *handle, uint32_t write_addr, uint8_t *data, uint16_t length); // 读数据 +static void spi_write_reg(spi_t *handle, uint8_t reg, uint8_t data); // 写寄存器 +static uint8_t spi_read_reg(spi_t *handle, uint8_t reg); // 读寄存器 + +static void _hardware_enable(spi_t *handle, SPI_TypeDef *spi); // 硬件SPI +static void _dma_enable(spi_t *handle, DMA_TypeDef *dma, uint32_t dma_rx_channel, spis_dma_callback *dma_rx_cb, + uint32_t dma_tx_channel, spis_dma_callback *dma_tx_cb); // DMA SPI +static void _spi_dma_callback(spi_t *handle); // DMA发送完成回调 +static BOOL _spi_dma_send(spi_t *handle, uint8_t *data, uint16_t length); // DMA发送数据 +static uint8_t _read_drdy(spi_t *handle); // 读取DRDY电平 +static uint8_t _write_regs(spi_t *handle, uint8_t reg, uint8_t *data, uint8_t len); // 写多个寄存器 +static uint8_t _read_regs(spi_t *handle, uint8_t reg, uint8_t *data, uint8_t len); // 读多个寄存器 +static uint8_t _spi_write_reg(spi_t *handle, uint8_t reg, uint8_t data); // 写单个寄存器 +static uint8_t _spi_read_reg(spi_t *handle, uint8_t reg); // 读单个寄存器 +static uint8_t _spi_write_cmd(spi_t *handle, uint8_t cmd); // 写命令 +static uint8_t _spi_write_data(spi_t *handle, uint8_t *data, uint16_t len); // 写数据 + +/** + * @brief 创建一个SPI总线设备 + * @param {spi_type_e} spi_type SPI总线的类型 + * @param {spi_gpio_group_t} gpios SPI总线的GPIO配置 + * @param {uint16_t} delay_ticks SPI总线的延时参数 + * @return {*} 创建的SPI总线设备句柄 + * @note: 该函数用于创建一个SPI总线设备。它首先断言spi_type在有效的范围内,然后创建一个spi_t结构体,并将gpios和delay_ticks的内存地址复制到handle结构体中。接着,根据spi_type的值,设置不同的接口函数。最后,返回handle结构体。 + */ +spi_t *spi_create(spi_type_e spi_type, spi_gpio_group_t gpios, uint16_t delay_ticks) +{ + DBG_ASSERT(spi_type < SPI_TYPE_MAX __DBG_LINE); + spi_t *handle = (spi_t *)osel_mem_alloc(sizeof(spi_t)); + osel_memcpy((uint8_t *)&handle->gpios, (uint8_t *)&gpios, sizeof(spi_gpio_group_t)); + handle->delay_ticks = delay_ticks; + handle->spi_type = spi_type; + handle->simualte_gpio = TRUE; + handle->interface.hardware_enable = _hardware_enable; + handle->interface.dma_enable = _dma_enable; + handle->interface.spi_dma_send = _spi_dma_send; + handle->interface.spi_dma_callback = _spi_dma_callback; + if (spi_type == SPI_TYPE_NORMAL) + { + handle->interface.u.normal.write_reg = _spi_write_reg; + handle->interface.u.normal.read_reg = _spi_read_reg; + handle->interface.u.normal.write_regs = _write_regs; + handle->interface.u.normal.read_regs = _read_regs; + handle->interface.u.normal.read_drdy = _read_drdy; + + handle->interface.u.normal.spi_send = spi_read_write_byte; + handle->interface.u.normal.spi_reset = spi_reset; + handle->interface.u.normal.spi_read = spi_read; + handle->interface.u.normal.spi_write = spi_write; + handle->interface.u.normal.spi_write_reg = spi_write_reg; + handle->interface.u.normal.spi_read_reg = spi_read_reg; + + handle->cfg.cmd_rdsr = CMD_RDSR; + handle->cfg.cmd_wrsr = CMD_WRSR; + handle->cfg.cmd_wren = CMD_WREN; + handle->cfg.cmd_wrdi = CMD_WRDI; + handle->cfg.cmd_read = CMD_READ; + handle->cfg.cmd_write = CMD_WRITE; + handle->cfg.dummy_byte = DUMMY_BYTE; + handle->cfg.continuous_write = FALSE; + } + else if (spi_type == SPI_TYPE_LCD) + { + handle->interface.u.lcd.write_cmd = _spi_write_cmd; + handle->interface.u.lcd.write_data = _spi_write_data; + } + else + { + DBG_ASSERT(FALSE __DBG_LINE); + } + return handle; +} + +void spi_free(spi_t *handle) +{ + if (handle != NULL) + { + gpio_free(handle->gpios.cs); + gpio_free(handle->gpios.rdy); + gpio_free(handle->gpios.rst); + gpio_free(handle->gpios.mosi); + gpio_free(handle->gpios.miso); + gpio_free(handle->gpios.sck); + + osel_mem_free(handle); + } +} + +/** + * @brief 硬件SPI模式 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {SPI_TypeDef} *spi SPI总线的寄存器结构体指针 + * @return {*} 无 + * @note: 该函数用于设置SPI总线的硬件模式。它首先断言handle不为空,然后断言spix不为空。接着,将handle的simulate_gpio设置为FALSE,并将spix的地址赋值给handle->spix。最后,调用SPI_ENABLE函数启用SPI总线。 + */ +static void _hardware_enable(spi_t *handle, SPI_TypeDef *spi) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(spi != NULL __DBG_LINE); + handle->simualte_gpio = FALSE; + handle->spi = spi; + SPI_ENABLE(spi); +} + +static void _dma_enable(spi_t *handle, DMA_TypeDef *dma, uint32_t dma_rx_channel, spis_dma_callback *dma_rx_cb, + uint32_t dma_tx_channel, spis_dma_callback *dma_tx_cb) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(dma != NULL __DBG_LINE); + DBG_ASSERT(handle->spi != NULL __DBG_LINE); + handle->dma = dma; + handle->dma_rx_channel = dma_rx_channel; + handle->dma_tx_channel = dma_tx_channel; + handle->rx_dma_ok = TRUE; + handle->tx_dma_ok = TRUE; + if (dma_rx_cb != NULL) + { + handle->dma_rx_cb = dma_rx_cb; + } + if (dma_tx_cb != NULL) + { + handle->dma_tx_cb = dma_tx_cb; + } + + LL_DMA_SetPeriphAddress(dma, dma_tx_channel, LL_SPI_DMA_GetRegAddr(handle->spi)); + LL_DMA_ClearFlag_GI1(dma); + LL_DMA_ClearFlag_TC1(dma); + LL_DMA_EnableIT_TC(dma, dma_tx_channel); + LL_SPI_EnableDMAReq_TX(handle->spi); + LL_SPI_Enable(handle->spi); +} + +static void _spi_dma_callback(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + if (handle->dma_tx_cb != NULL) + { + handle->dma_tx_cb(handle); + } + + if (handle->dma_rx_cb != NULL) + { + handle->dma_rx_cb(handle); + } +} + +static BOOL _spi_dma_send(spi_t *handle, uint8_t *data, uint16_t length) +{ + handle->tx_dma_ok = FALSE; + LL_DMA_DisableChannel(handle->dma, handle->dma_tx_channel); + + LL_DMA_SetMemoryAddress(handle->dma, handle->dma_tx_channel, (uint32_t)data); + LL_DMA_SetDataLength(handle->dma, handle->dma_tx_channel, length); + + LL_DMA_EnableChannel(handle->dma, handle->dma_tx_channel); + + uint16_t i = 0; + while (handle->tx_dma_ok == FALSE) + { + i++; + if (i > 50000) + { + __NOP(); + break; + } + } + + return handle->tx_dma_ok == TRUE ? TRUE : FALSE; +} + +/** + * @brief 读取数据准备好信号 + * @param {spi_t} *handle SPI总线设备句柄 + * @return {uint8_t} 数据准备好信号的值 + * @note: 该函数用于读取SPI总线的数据准备好信号。它首先断言handle不为空,然后返回handle的gpios.rdy的读取结果。 + */ +static uint8_t _read_drdy(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + return handle->gpios.rdy->read(*handle->gpios.rdy); +} + +/** + * @brief 写入单个寄存器 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {uint8_t} reg 寄存器号 + * @param {uint8_t} data 寄存器值 + * @return {*} 操作结果 + * @note: 该函数用于写入SPI总线的单个寄存器。它首先断言handle不为空,然后断言data不为空。接着,将SPI总线的CS引脚设置为低电平,发送寄存器号和寄存器值,最后将SPI总线的CS引脚设置为高电平,返回操作结果。 + */ +static uint8_t _write_regs(spi_t *handle, uint8_t reg, uint8_t *data, uint8_t len) +{ + uint8_t status = 0; + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(data != NULL __DBG_LINE); + DBG_ASSERT(len != 0 __DBG_LINE); + spi_cs_low(handle); + status = spi_read_write_byte(handle, reg); // 发送寄存器号 + while (len--) + { + spi_read_write_byte(handle, *data); // 写入寄存器的值 + data++; + } + spi_cs_high(handle); + + return status; +} + +/** + * @brief 读取多个寄存器 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {uint8_t} reg 寄存器号 + * @param {uint8_t} *data 寄存器值的指针 + * @param {uint8_t} len 寄存器值的个数 + * @return {uint8_t} 操作结果 + * @note: 该函数用于读取SPI总线的多个寄存器。它首先断言handle不为空,然后断言data不为空,接着断言len大于0。接着,将SPI总线的CS引脚设置为低电平,发送寄存器号,然后循环读取多个寄存器的值,最后将SPI总线的CS引脚设置为高电平,返回操作结果。 + */ +static uint8_t _read_regs(spi_t *handle, uint8_t reg, uint8_t *data, uint8_t len) +{ + uint8_t status = 0; + + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(data != NULL __DBG_LINE); + DBG_ASSERT(len != 0 __DBG_LINE); + + spi_cs_low(handle); + status = spi_read_write_byte(handle, reg); + for (uint8_t i = 0; i < len; i++) + { + data[i] = spi_read_write_byte(handle, 0xff); // 读出数据 + } + spi_cs_high(handle); + + return status; +} + +/** + * @brief 写入单个寄存器 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {uint8_t} reg 寄存器号 + * @param {uint8_t} data 寄存器值 + * @return {*} 操作结果 + * @note: 该函数用于写入SPI总线的单个寄存器。它首先断言handle不为空,然后将SPI总线的CS引脚设置为低电平,发送寄存器号和寄存器值,最后将SPI总线的CS引脚设置为高电平,返回操作结果。 + */ +static uint8_t _spi_write_reg(spi_t *handle, uint8_t reg, uint8_t data) +{ + uint8_t status = 0; + DBG_ASSERT(handle != NULL __DBG_LINE); + + spi_cs_low(handle); + status = spi_read_write_byte(handle, reg); // 发送寄存器号 + spi_read_write_byte(handle, data); // 写入寄存器的值 + + spi_cs_high(handle); + + return status; +} + +/** + * @brief 读取单个寄存器 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {uint8_t} reg 寄存器号 + * @return {*} 寄存器值 + * @note: 该函数用于读取SPI总线的单个寄存器。它首先断言handle不为空,然后将SPI总线的CS引脚设置为低电平,发送寄存器号,接着读取寄存器的值,最后将SPI总线的CS引脚设置为高电平,返回寄存器值。 + */ +static uint8_t _spi_read_reg(spi_t *handle, uint8_t reg) +{ + uint8_t reg_val = 0; + DBG_ASSERT(handle != NULL __DBG_LINE); + + spi_cs_low(handle); + spi_read_write_byte(handle, reg); // 发送寄存器号 + reg_val = spi_read_write_byte(handle, 0); + spi_cs_high(handle); + return reg_val; +} + +/** + * @brief 写入命令 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {uint8_t} cmd 命令 + * @return {*} 操作结果 + * @note: 该函数用于写入SPI总线的命令。它首先断言handle不为空,然后将SPI总线的RDY引脚设置为低电平,发送命令,最后将SPI总线的CS引脚设置为高电平,返回操作结果。 + */ +static uint8_t _spi_write_cmd(spi_t *handle, uint8_t cmd) +{ + uint8_t status = 0; + + DBG_ASSERT(handle != NULL __DBG_LINE); + spi_rdy_low(handle); + spi_cs_low(handle); + status = spi_read_write_byte(handle, cmd); // 发送命令 + spi_cs_high(handle); + return status; +} + +/** + * @brief 写入数据 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {uint8_t} *data 要写入的数据的指针 + * @param {uint16_t} len 要写入的数据的长度 + * @return {*} 操作结果 + * @note: 该函数用于写入SPI总线的数据。它首先断言handle不为空,然后将SPI总线的RDY引脚设置为高电平,发送数据,最后将SPI总线的CS引脚设置为高电平,返回操作结果。 + */ +static uint8_t _spi_write_data(spi_t *handle, uint8_t *data, uint16_t len) +{ + uint8_t status = 0; + DBG_ASSERT(handle != NULL __DBG_LINE); + spi_rdy_high(handle); + spi_cs_low(handle); + for (uint16_t i = 0; i < len; i++) + { + status = spi_read_write_byte(handle, *data); + data++; + } + spi_cs_high(handle); + return status; +} + +/** + * @brief SPI延时函数 + * @param {spi_t} *handle SPI总线设备句柄 + * @return {*} 无 + * @note: 该函数用于SPI总线的延时。它首先断言handle不为空,然后根据handle的delay_ticks的值,循环执行NOP指令。 + */ +static inline void spi_delay(spi_t *handle) +{ + uint16_t count = 0; + DBG_ASSERT(handle != NULL __DBG_LINE); + count = handle->delay_ticks; + if (count > 0) + { + while (count--) + { + __NOP(); + } + } +} + +static BOOL spi_wait_flag(spi_t *handle, uint32_t flag, uint32_t timeout) +{ + uint32_t i = 0; + DBG_ASSERT(handle != NULL __DBG_LINE); + if (flag == LL_SPI_SR_TXE) + { + while (LL_SPI_IsActiveFlag_TXE(handle->spi) == 0) + { + if (i++ > timeout) + { + return FALSE; // 超时 + } + else + { + __NOP(); + } + } + } + else if (flag == LL_SPI_SR_RXNE) + { + while (LL_SPI_IsActiveFlag_RXNE(handle->spi) == 0) + { + if (i++ > timeout) + { + return FALSE; // 超时 + } + else + { + __NOP(); + } + } + } + else if (flag == LL_SPI_SR_BSY) + { + while (LL_SPI_IsActiveFlag_BSY(handle->spi) == 0) + { + if (i++ > timeout) + { + return FALSE; // 超时 + } + else + { + __NOP(); + } + } + } + else + { + DBG_ASSERT(FALSE __DBG_LINE); + } + + return TRUE; // 成功 +} + +/** + * @brief 读写一个字节 + * @param {spi_t} *handle SPI总线设备句柄 + * @param {uint8_t} tx_data 要写入的数据 + * @return {*} 读取到的数据 + * @note: 该函数用于SPI总线的读写一个字节。它首先断言handle不为空,然后根据handle的simulate_gpio的值,选择模拟SPI或硬件SPI。最后,返回读取到的数据。 + */ +static uint8_t spi_read_write_byte(spi_t *handle, uint8_t tx_data) +{ + uint8_t bit_ctr; + uint8_t rdata = 0xff; + DBG_ASSERT(handle != NULL __DBG_LINE); + if (handle->simualte_gpio == TRUE) + { + // 模拟SPI + for (bit_ctr = 0; bit_ctr < 8; bit_ctr++) + { + spi_sck_low(handle); + spi_delay(handle); + + if (tx_data & 0x80) + spi_mosi_high(handle); + else + spi_mosi_low(handle); + + spi_delay(handle); + spi_sck_high(handle); + spi_delay(handle); + tx_data = (tx_data << 1); + rdata = rdata << 1; + + if (NULL != handle->gpios.miso->port) + { + if (spi_miso_read(handle) == 1) + rdata += 0x01; + } + } + return (rdata); + } + else + { + LL_SPI_TransmitData8(handle->spi, tx_data); + + if (spi_wait_flag(handle, LL_SPI_SR_RXNE, SPI_TIMEOUT) == FALSE) + { + return 0xff; + } + rdata = LL_SPI_ReceiveData8(handle->spi); + return rdata; + } +} + +static void _write_enable(spi_t *handle) +{ + handle->gpios.cs->reset(*handle->gpios.cs); + handle->interface.u.normal.spi_send(handle, handle->cfg.cmd_wren); + handle->gpios.cs->set(*handle->gpios.cs); +} + +static void _write_disable(spi_t *handle) +{ + handle->gpios.cs->reset(*handle->gpios.cs); + handle->interface.u.normal.spi_send(handle, handle->cfg.cmd_wrdi); + handle->gpios.cs->set(*handle->gpios.cs); +} + +static uint8_t _read_status(spi_t *handle) +{ + uint8_t data; + handle->gpios.cs->reset(*handle->gpios.cs); + handle->interface.u.normal.spi_send(handle, handle->cfg.cmd_rdsr); + data = handle->interface.u.normal.spi_send(handle, handle->cfg.dummy_byte); + handle->gpios.cs->set(*handle->gpios.cs); + return data; +} + +static void _ready(spi_t *handle) +{ + uint16_t count = 0; + while (_read_status(handle) & 0x01) + { + if (count++ > 20000) + { + break; + } + else + { + __NOP(); + } + } +} + +static BOOL spi_write(spi_t *handle, uint32_t write_addr, uint8_t *data, uint16_t length) +{ + BOOL ret = TRUE; + uint8_t cnt = 0; // 返回值检查 + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(data != NULL __DBG_LINE); + DBG_ASSERT(length > 0 __DBG_LINE); + DBG_ASSERT(handle->cfg.page_size > 0 __DBG_LINE); + uint32_t page_size = handle->cfg.page_size; + _write_enable(handle); // 写入使能命令 + handle->gpios.cs->reset(*handle->gpios.cs); // 设置CS引脚为低电平,准备开始SPI通信 + + cnt = handle->interface.u.normal.spi_send(handle, handle->cfg.cmd_write); // 发送写入命令 + if (cnt == 0) + { + return FALSE; + } + + if (handle->cfg.address_bytes == 2) + { + cnt = handle->interface.u.normal.spi_send(handle, write_addr >> 8); // 发送高位地址 + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, write_addr); // 发送低位地址 + if (cnt == 0) + { + return FALSE; + } + } + else + { + cnt = handle->interface.u.normal.spi_send(handle, write_addr >> 16); + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, write_addr >> 8); + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, write_addr); + if (cnt == 0) + { + return FALSE; + } + } + + while (length--) + { + cnt = handle->interface.u.normal.spi_send(handle, *data); // 发送一个字节数据 + if (cnt == 0) + { + return FALSE; + } + data++; + if (handle->cfg.continuous_write == FALSE) + { + write_addr++; + if (((write_addr % page_size) == 0) && (length > 0)) + { + // 一页写完 + handle->gpios.cs->set(*handle->gpios.cs); // 设置CS引脚为高电平,完成SPI通信 + _ready(handle); + + _write_enable(handle); // 写入使能命令 + handle->gpios.cs->reset(*handle->gpios.cs); // 设置CS引脚为低电平,准备开始SPI通信 + + cnt = handle->interface.u.normal.spi_send(handle, handle->cfg.cmd_write); // 发送写入命令 + if (cnt == 0) + { + return FALSE; + } + if (handle->cfg.address_bytes == 2) + { + cnt = handle->interface.u.normal.spi_send(handle, write_addr >> 8); // 发送高位地址 + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, write_addr); // 发送低位地址 + if (cnt == 0) + { + return FALSE; + } + } + else + { + cnt = handle->interface.u.normal.spi_send(handle, write_addr >> 16); + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, write_addr >> 8); + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, write_addr); + if (cnt == 0) + { + return FALSE; + } + } + } + } + } + handle->gpios.cs->set(*handle->gpios.cs); // 设置CS引脚为高电平,完成SPI通信 + + _ready(handle); + _write_disable(handle); + return ret; +} + +static void spi_write_reg(spi_t *handle, uint8_t reg, uint8_t value) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + _write_enable(handle); // 写入使能命令 + handle->interface.u.normal.write_reg(handle, reg, value); + _ready(handle); + _write_disable(handle); // 写入禁止命令 +} + +static uint8_t spi_read_reg(spi_t *handle, uint8_t reg) +{ + uint8_t data; + handle->gpios.cs->reset(*handle->gpios.cs); + handle->interface.u.normal.spi_send(handle, reg); + data = handle->interface.u.normal.spi_send(handle, handle->cfg.dummy_byte); + handle->gpios.cs->set(*handle->gpios.cs); + return data; +} + +static BOOL spi_read(spi_t *handle, uint32_t read_addr, uint8_t *data, uint16_t length) +{ + BOOL ret = TRUE; + uint8_t cnt = 0; // 返回值检查 + DBG_ASSERT(handle != NULL __DBG_LINE); + DBG_ASSERT(data != NULL __DBG_LINE); + DBG_ASSERT(length > 0 __DBG_LINE); + handle->gpios.cs->reset(*handle->gpios.cs); // 设置CS引脚为低电平,准备开始SPI通信 + + cnt = handle->interface.u.normal.spi_send(handle, handle->cfg.cmd_read); // 发送读取命令 + if (cnt == 0) + { + return FALSE; + } + if (handle->cfg.address_bytes == 2) + { + cnt = handle->interface.u.normal.spi_send(handle, read_addr >> 8); // 发送高位地址 + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, read_addr); // 发送低位地址 + if (cnt == 0) + { + return FALSE; + } + } + else + { + cnt = handle->interface.u.normal.spi_send(handle, read_addr >> 16); + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, read_addr >> 8); + if (cnt == 0) + { + return FALSE; + } + cnt = handle->interface.u.normal.spi_send(handle, read_addr); + if (cnt == 0) + { + return FALSE; + } + } + for (uint16_t i = 0; i < length; i++) // 循环读取数据 + { + data[i] = handle->interface.u.normal.spi_send(handle, handle->cfg.dummy_byte); // 发送空字节,读取实际数据 + } + handle->gpios.cs->set(*handle->gpios.cs); // 设置CS引脚为高电平,完成SPI通信 + + return ret; +} + +static void spi_reset(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.cs->reset(*handle->gpios.cs); + delay_tick(10); + handle->gpios.cs->set(*handle->gpios.cs); + delay_tick(10); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的复位(RST)引脚设置为高 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_rdy_high(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.rdy->set(*handle->gpios.rdy); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的复位(RST)引脚设置为低 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_rdy_low(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.rdy->reset(*handle->gpios.rdy); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的芯片选择(CS)引脚设置为高 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_cs_high(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.cs->set(*handle->gpios.cs); + spi_delay(handle); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的芯片选择(CS)引脚设置为低 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_cs_low(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.cs->reset(*handle->gpios.cs); + spi_delay(handle); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的时钟(SCK)引脚设置为高 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_mosi_high(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.mosi->set(*handle->gpios.mosi); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的输出(MOSI)引脚设置为低 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_mosi_low(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.mosi->reset(*handle->gpios.mosi); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的时钟(SCK)引脚设置为高 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_sck_high(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.sck->set(*handle->gpios.sck); +} + +/** + * @brief 用于将SPI(串行外围接口)设备的时钟(SCK)引脚设置为低 + * @param {spi_id_e} id + * @return {*} + */ +static inline void spi_sck_low(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + handle->gpios.sck->reset(*handle->gpios.sck); +} + +/** + * @brief 用于读取SPI(串行外围接口)设备的输入(MISO)引脚的电平 + * @param {spi_id_e} id + * @return {*} + */ +static inline uint8_t spi_miso_read(spi_t *handle) +{ + DBG_ASSERT(handle != NULL __DBG_LINE); + return handle->gpios.miso->read(*handle->gpios.miso); +} diff --git a/bsp/spis.h b/bsp/spis.h new file mode 100644 index 0000000..ac2d77e --- /dev/null +++ b/bsp/spis.h @@ -0,0 +1,165 @@ +/** + * @file spis.h + * @brief SPI驱动, 用于SPI设备的读写操作 + * + * This file contains the SPI driver used for reading and writing operations on SPI devices. + * + * @date 2023-08-01 + * @version 1.0 + * + * @note This file is part of the STM32 controller-v2 project. + * + */ + +#ifndef __SPIS_H__ +#define __SPIS_H__ + +#include "lib.h" +#include "gpios.h" + +#define SPI_ENABLE(SPIX) LL_SPI_Enable(SPIX) + +typedef struct SPIS spi_t; +typedef void spis_dma_callback(spi_t *handle); + +/** + * @brief SPI type enumeration + */ +typedef enum +{ + SPI_TYPE_NORMAL = 0, ///< SPI1:NORMAL + SPI_TYPE_LCD, ///< SPI2:LCD + SPI_TYPE_MAX, +} spi_type_e; + +/** + * @brief SPI GPIO group structure + */ +typedef struct +{ + gpio_t *mosi; ///< MOSI + gpio_t *miso; ///< MISO + gpio_t *sck; ///< SCK + gpio_t *cs; ///< CS + gpio_t *rst; ///< RST + gpio_t *rdy; ///< DRDY +} spi_gpio_group_t; + +/** + * @brief SPI normal interface structure + */ +typedef struct +{ + uint8_t (*write_reg)(spi_t *handle, uint8_t reg, uint8_t data); ///< Write a single register via SPI + uint8_t (*read_reg)(spi_t *handle, uint8_t reg); ///< Read the value of a single register via SPI + uint8_t (*read_drdy)(spi_t *handle); ///< Get the value of the SPI DRDY pin + uint8_t (*write_regs)(spi_t *handle, uint8_t reg, uint8_t *data, uint8_t len); ///< Write multiple registers via SPI + uint8_t (*read_regs)(spi_t *handle, uint8_t reg, uint8_t *data, uint8_t len); ///< Read multiple registers via SPI + uint8_t (*spi_send)(spi_t *handle, uint8_t data); ///< Send data via SPI + void (*spi_reset)(spi_t *handle); ///< Reset SPI + + BOOL(*spi_write) + (spi_t *handle, uint32_t write_addr, uint8_t *data, uint16_t length); ///< Write data via SPI + + BOOL(*spi_read) + (spi_t *handle, uint32_t read_addr, uint8_t *data, uint16_t length); ///< Read data via SPI + + void (*spi_write_reg)(spi_t *handle, uint8_t reg, uint8_t data); ///< Write a single register via SPI + uint8_t (*spi_read_reg)(spi_t *handle, uint8_t reg); ///< Read a single register via SPI +} spi_normal_interface_t; + +/** + * @brief SPI LCD interface structure + */ +typedef struct +{ + uint8_t (*write_cmd)(spi_t *handle, uint8_t cmd); ///< Write a command via SPI + uint8_t (*write_data)(spi_t *handle, uint8_t *data, uint16_t len); ///< Write data via SPI +} spi_lcd_interface_t; + +/** + * @brief SPI interface structure + */ +typedef struct +{ + union + { + spi_normal_interface_t normal; + spi_lcd_interface_t lcd; + } u; + void (*hardware_enable)(spi_t *handle, SPI_TypeDef *spi); ///< Enable hardware SPI + void (*dma_enable)(spi_t *handle, DMA_TypeDef *dma, uint32_t dma_rx_channel, spis_dma_callback *dma_rx_cb, + uint32_t dma_tx_channel, spis_dma_callback *dma_tx_cb); ///< Enable DMA SPI + void (*spi_dma_callback)(spi_t *spi); ///< DMA send completion callback + BOOL(*spi_dma_send) + (spi_t *handle, uint8_t *data, uint16_t length); ///< DMA send +} spi_interface_t; + +/** + * @brief SPI structure + */ +typedef struct SPIS spi_t; + +/** + * @brief SPI DMA callback function + */ +typedef void spis_dma_callback(spi_t *handle); + +typedef struct +{ + // CMD + uint8_t cmd_rdsr; ///< Read Status Register instruction + uint8_t cmd_wrsr; ///< Write Status Register instruction + uint8_t cmd_wren; ///< Write enable instruction + uint8_t cmd_wrdi; ///< Write disable instruction + uint8_t cmd_read; ///< Read from Memory instruction + uint8_t cmd_write; ///< Write to Memory instruction + + uint8_t dummy_byte; ///< Dummy byte + + uint8_t address_bytes; + uint32_t page_size; + uint32_t total_size; + uint8_t ticks; ///< Delay in NOP ticks + BOOL continuous_write; ///< Continuous write +} spi_normal_config_t; + +struct SPIS +{ + spi_type_e spi_type; ///< SPI type + uint16_t delay_ticks; ///< Delay in NOP ticks + spi_gpio_group_t gpios; ///< SPI GPIOs + spi_interface_t interface; ///< SPI interface + SPI_TypeDef *spi; ///< SPI peripheral + BOOL simualte_gpio; ///< Simulate GPIO + spi_normal_config_t cfg; ///< Normal SPI configuration + ///< DMA + DMA_TypeDef *dma; ///< External setting + uint32_t dma_rx_channel; ///< External setting + uint32_t dma_tx_channel; ///< External setting + __IO BOOL rx_dma_ok; + __IO BOOL tx_dma_ok; + spis_dma_callback *dma_rx_cb; ///< DMA receive callback function + spis_dma_callback *dma_tx_cb; ///< DMA send callback function + + void *params; ///< 扩展参数 +}; + +/** + * @brief Create a new SPI instance + * + * @param spi_type The type of SPI + * @param gpios The SPI GPIO group + * @param delay_ticks The delay in NOP ticks + * @return spi_t* The created SPI instance + */ +extern spi_t *spi_create(spi_type_e spi_type, spi_gpio_group_t gpios, uint16_t delay_ticks); + +/** + * @brief Free the SPI instance + * + * @param spi The SPI instance to free + */ +extern void spi_free(spi_t *spi); + +#endif ///< __SPIS_H__ diff --git a/手册/24LC02BT-ISN.pdf b/芯片手册/24LC02BT-ISN.pdf similarity index 100% rename from 手册/24LC02BT-ISN.pdf rename to 芯片手册/24LC02BT-ISN.pdf diff --git a/手册/FM25W256.pdf b/芯片手册/FM25W256.pdf similarity index 100% rename from 手册/FM25W256.pdf rename to 芯片手册/FM25W256.pdf diff --git a/手册/GP8302-TC50-EW.pdf b/芯片手册/GP8302-TC50-EW.pdf similarity index 100% rename from 手册/GP8302-TC50-EW.pdf rename to 芯片手册/GP8302-TC50-EW.pdf diff --git a/手册/M95M02-DRMN6TP.pdf b/芯片手册/M95M02-DRMN6TP.pdf similarity index 100% rename from 手册/M95M02-DRMN6TP.pdf rename to 芯片手册/M95M02-DRMN6TP.pdf diff --git a/手册/MX-02模块用户使用手册.pdf b/芯片手册/MX-02模块用户使用手册.pdf similarity index 100% rename from 手册/MX-02模块用户使用手册.pdf rename to 芯片手册/MX-02模块用户使用手册.pdf diff --git a/手册/NTC MF52A 104F3950.pdf b/芯片手册/NTC MF52A 104F3950.pdf similarity index 100% rename from 手册/NTC MF52A 104F3950.pdf rename to 芯片手册/NTC MF52A 104F3950.pdf diff --git a/手册/PT100/Modbus RTU协议手册.pdf b/芯片手册/PT100/Modbus RTU协议手册.pdf similarity index 100% rename from 手册/PT100/Modbus RTU协议手册.pdf rename to 芯片手册/PT100/Modbus RTU协议手册.pdf diff --git a/手册/PT100/数字量输入输出系列使用手册(RS485&RS232版).pdf b/芯片手册/PT100/数字量输入输出系列使用手册(RS485&RS232版).pdf similarity index 100% rename from 手册/PT100/数字量输入输出系列使用手册(RS485&RS232版).pdf rename to 芯片手册/PT100/数字量输入输出系列使用手册(RS485&RS232版).pdf diff --git a/手册/SHT4x_Datasheet.pdf b/芯片手册/SHT4x_Datasheet.pdf similarity index 100% rename from 手册/SHT4x_Datasheet.pdf rename to 芯片手册/SHT4x_Datasheet.pdf diff --git a/手册/TCA9555(IO扩展).pdf b/芯片手册/TCA9555(IO扩展).pdf similarity index 100% rename from 手册/TCA9555(IO扩展).pdf rename to 芯片手册/TCA9555(IO扩展).pdf diff --git a/手册/dac161p997.pdf b/芯片手册/dac161p997.pdf similarity index 100% rename from 手册/dac161p997.pdf rename to 芯片手册/dac161p997.pdf diff --git a/手册/rtc-RX8010SJ_cn.pdf b/芯片手册/rtc-RX8010SJ_cn.pdf similarity index 100% rename from 手册/rtc-RX8010SJ_cn.pdf rename to 芯片手册/rtc-RX8010SJ_cn.pdf