system
This commit is contained in:
parent
1e58b0a5d1
commit
f0d23f8870
|
|
@ -0,0 +1,416 @@
|
|||
/**
|
||||
* @file adcs.c
|
||||
* @author xxx
|
||||
* @date 2023-09-04 15:59:16
|
||||
* @brief LL库ADC驱动 STM32F407
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include "adcs.h"
|
||||
#include "dmas.h"
|
||||
adcs_t adcs[ADCS_MAX];
|
||||
|
||||
static uint8_t adc_get_channels_count(uint32_t channnels); // 通过用户配置的通道号获取通道数量
|
||||
|
||||
/**
|
||||
* @brief ADC初始化
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @param {ADC_TypeDef} *adc ADC外设
|
||||
* @param {DMA_TypeDef} *dma DMA外设
|
||||
* @param {uint32_t} dma_channel DMA通道
|
||||
* @param {uint8_t} adc_cct ADC采样次数
|
||||
* @param {uint32_t} channels 存储数据所在的序列号
|
||||
* @return {*}
|
||||
* @note TCONV(转换时间) = (采样时间 + 12.5 个周期)/(主频/ADC分频系数)
|
||||
*/
|
||||
void adc_init(adcs_e num, ADC_TypeDef *adc, DMA_TypeDef *dma, uint32_t dma_stream, uint32_t dma_channel, uint16_t adc_cct, uint32_t channels)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
DBG_ASSERT(adc != NULL __DBG_LINE);
|
||||
DBG_ASSERT(dma != NULL __DBG_LINE);
|
||||
DBG_ASSERT(adc_cct > 0 __DBG_LINE);
|
||||
|
||||
adcs_t *p = &adcs[num];
|
||||
osel_memset((uint8_t *)p, 0, sizeof(adcs_t));
|
||||
p->adc = adc;
|
||||
p->dma = dma;
|
||||
p->dma_stream = dma_stream;
|
||||
p->dma_channel = dma_channel;
|
||||
p->channels.data = channels;
|
||||
p->adc_cct = adc_cct;
|
||||
p->adc_chans_count = adc_get_channels_count(channels);
|
||||
p->adc_sum = adc_cct * p->adc_chans_count;
|
||||
|
||||
#if defined(SRAM2_BASE) // SRAM2速度更快
|
||||
p->adc_value = (uint16_t *)osel_mem_alloc2(sizeof(uint16_t) * p->adc_sum);
|
||||
#else
|
||||
p->adc_value = (uint16_t *)osel_mem_alloc(sizeof(uint16_t) * p->adc_sum);
|
||||
#endif
|
||||
|
||||
DBG_ASSERT(p->adc_value != NULL __DBG_LINE);
|
||||
osel_memset((uint8_t *)p->adc_value, 0, sizeof(uint16_t) * p->adc_sum);
|
||||
|
||||
LL_DMA_SetChannelSelection(p->dma, p->dma_stream, p->dma_channel);
|
||||
LL_DMA_ConfigTransfer(p->dma, p->dma_stream,
|
||||
LL_DMA_DIRECTION_PERIPH_TO_MEMORY |
|
||||
LL_DMA_MODE_CIRCULAR |
|
||||
LL_DMA_PERIPH_NOINCREMENT |
|
||||
LL_DMA_MEMORY_INCREMENT |
|
||||
LL_DMA_PDATAALIGN_HALFWORD |
|
||||
LL_DMA_MDATAALIGN_HALFWORD |
|
||||
LL_DMA_PRIORITY_HIGH);
|
||||
LL_DMA_ConfigAddresses(p->dma, p->dma_stream,
|
||||
LL_ADC_DMA_GetRegAddr(p->adc, LL_ADC_DMA_REG_REGULAR_DATA),
|
||||
(uint32_t)p->adc_value,
|
||||
LL_DMA_GetDataTransferDirection(p->dma, p->dma_stream));
|
||||
|
||||
LL_DMA_SetDataLength(p->dma, p->dma_stream, p->adc_sum);
|
||||
LL_DMA_EnableStream(p->dma, p->dma_stream);
|
||||
|
||||
LL_ADC_REG_SetContinuousMode(p->adc, LL_ADC_REG_CONV_CONTINUOUS);
|
||||
LL_ADC_REG_SetDMATransfer(p->adc, LL_ADC_REG_DMA_TRANSFER_UNLIMITED);
|
||||
|
||||
if (BIT_IS_SET(channels, INVREF))
|
||||
{
|
||||
// 使能VREFINT
|
||||
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(p->adc), LL_ADC_PATH_INTERNAL_VREFINT);
|
||||
}
|
||||
if (BIT_IS_SET(channels, INTEMP))
|
||||
{
|
||||
LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(p->adc), LL_ADC_PATH_INTERNAL_TEMPSENSOR);
|
||||
}
|
||||
|
||||
LL_mDelay(10);
|
||||
// 启动 ADC 转换
|
||||
LL_ADC_Enable(p->adc);
|
||||
LL_ADC_REG_StartConversionSWStart(p->adc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启动样本
|
||||
*
|
||||
* 根据给定的 ADCS 枚举值启动 ADC 样本校准。
|
||||
*
|
||||
* @param num ADCS 枚举值
|
||||
*/
|
||||
void start_sample(adcs_e num)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
// adcs_t *p = &adcs[num];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 停止样本采集
|
||||
*
|
||||
* 根据给定的 ADCS 枚举值,停止相应的 ADCS 样本采集。
|
||||
*
|
||||
* @param num ADCS 枚举值
|
||||
*/
|
||||
void stop_sample(adcs_e num)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
// adcs_t *p = &adcs[num];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ADC重新开始转换
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void adc_restart(adcs_e num)
|
||||
{
|
||||
stop_sample(num);
|
||||
start_sample(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ADC反初始化
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void adc_dinit(adcs_e num)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
adcs_t *p = &adcs[num];
|
||||
LL_ADC_Disable(p->adc);
|
||||
if (p->adc_value != NULL)
|
||||
{
|
||||
#if defined(SRAM2_BASE)
|
||||
osel_mem_free2((uint16_t *)p->adc_value);
|
||||
#else
|
||||
osel_mem_free((uint16_t *)p->adc_value);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief 获取ADC转换结果,只需要第一个值
|
||||
* @param {adcs_e} num
|
||||
* @param {uint8_t} chan
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
uint16_t adc_result_only_one(adcs_e num, uint8_t chan)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
adcs_t *p = &adcs[num];
|
||||
DBG_ASSERT(p != NULL __DBG_LINE);
|
||||
uint16_t(*gram)[p->adc_chans_count] = (uint16_t(*)[p->adc_chans_count])p->adc_value;
|
||||
return gram[0][chan];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 中位值平均滤波,获取ADC转换结果
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @param {uint8_t} chan 存储数据所在的序列号
|
||||
* @return {*}
|
||||
* @note 不适合在中断中使用,因为排序算法时间复杂度为O(n^2)
|
||||
*/
|
||||
uint16_t adc_result_median_average(adcs_e num, uint8_t chan)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
adcs_t *p = &adcs[num];
|
||||
DBG_ASSERT(p != NULL __DBG_LINE);
|
||||
|
||||
if (p->adc_cct <= 2)
|
||||
return 0; // 如果adc_cct小于等于2,直接返回0
|
||||
|
||||
uint16_t adc_temp[p->adc_cct];
|
||||
uint32_t adc_sum = 0;
|
||||
uint16_t count = p->adc_cct >> 2; // 使用位移操作计算n的值
|
||||
|
||||
// 减少重复计算,计算基础偏移量
|
||||
uint16_t *adc_values = (uint16_t *)p->adc_value + chan;
|
||||
for (uint16_t i = 0; i < p->adc_cct; ++i, adc_values += p->adc_chans_count)
|
||||
{
|
||||
adc_temp[i] = *adc_values;
|
||||
}
|
||||
|
||||
insertion_sort(adc_temp, p->adc_cct);
|
||||
|
||||
// 计算中间部分的和
|
||||
for (uint16_t i = count; i < p->adc_cct - count; ++i)
|
||||
{
|
||||
adc_sum += adc_temp[i];
|
||||
}
|
||||
|
||||
// 计算平均值,确保不会除以0
|
||||
uint16_t res = adc_sum / (p->adc_cct - (count << 1));
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 中位值滤波,获取ADC转换结果
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @param {uint8_t} chan 存储数据所在的序列号
|
||||
* @return {*}
|
||||
* @note 不适合在中断中使用,因为排序算法时间复杂度为O(n^2)
|
||||
*/
|
||||
uint16_t adc_result_median(adcs_e num, uint8_t chan)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
uint16_t res = 0;
|
||||
adcs_t *p = &adcs[num];
|
||||
DBG_ASSERT(p != NULL __DBG_LINE);
|
||||
|
||||
uint16_t adc_temp[p->adc_cct];
|
||||
// 减少重复计算,计算基础偏移量
|
||||
uint16_t *adc_values = (uint16_t *)p->adc_value + chan;
|
||||
for (uint16_t i = 0; i < p->adc_cct; ++i, adc_values += p->adc_chans_count)
|
||||
{
|
||||
adc_temp[i] = *adc_values;
|
||||
}
|
||||
insertion_sort(adc_temp, p->adc_cct);
|
||||
res = adc_temp[p->adc_cct >> 1];
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 平均值,获取ADC转换结果
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @param {uint8_t} chan 存储数据所在的序列号
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
uint16_t adc_result_average(adcs_e num, uint8_t chan)
|
||||
{
|
||||
DBG_ASSERT(num < ADCS_MAX __DBG_LINE);
|
||||
uint16_t res = 0;
|
||||
uint32_t adc_sum = 0;
|
||||
adcs_t *p = &adcs[num];
|
||||
DBG_ASSERT(p != NULL __DBG_LINE);
|
||||
|
||||
// 减少重复计算,计算基础偏移量
|
||||
uint16_t *adc_values = (uint16_t *)p->adc_value + chan;
|
||||
for (uint16_t i = 0; i < p->adc_cct; ++i, adc_values += p->adc_chans_count)
|
||||
{
|
||||
adc_sum += *adc_values;
|
||||
}
|
||||
res = adc_sum / p->adc_cct;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算内部温度
|
||||
* @param {uint16_t} adc_value ADC转换结果
|
||||
* @return {*}
|
||||
* @note 计算公式为:(measure * VDD_APPLI / VDD_CALIB) - (int32_t)*TEMP30_CAL_ADDR) * (int32_t)(130 - 30) / (int32_t)(*TEMP130_CAL_ADDR - *TEMP30_CAL_ADDR)) + 30
|
||||
* adc_value 是从ADC读取的温度传感器数据。
|
||||
TS_CAL1 是在30°C时校准的温度传感器数据,地址为TEMPSENSOR_CAL1_ADDR。
|
||||
TS_CAL2 是在110°C时校准的温度传感器数据,地址为TEMPSENSOR_CAL2_ADDR。
|
||||
TEMPSENSOR_CAL1_TEMP 是30°C。
|
||||
TEMPSENSOR_CAL2_TEMP 是110°C。
|
||||
*/
|
||||
float32 adc_result_temperature(uint16_t adc_value)
|
||||
{
|
||||
uint16_t ts_cal1 = *TEMPSENSOR_CAL1_ADDR;
|
||||
uint16_t ts_cal2 = *TEMPSENSOR_CAL2_ADDR;
|
||||
|
||||
float32 temperature = ((float32)(adc_value - ts_cal1) / (ts_cal2 - ts_cal1)) * (TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP) + TEMPSENSOR_CAL1_TEMP;
|
||||
return temperature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算内部电压
|
||||
* @param {uint16_t} adc_value ADC转换结果
|
||||
* @return {*} 电压值V
|
||||
* @note
|
||||
*/
|
||||
float32 adc_result_value_local(uint16_t adc_value)
|
||||
{
|
||||
float32 vdd = (VREFINT_CAL_VREF * (*VREFINT_CAL_ADDR)) / 4095;
|
||||
return ((vdd / adc_value) * 4095) / 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ADC DMA转换回调函数
|
||||
{
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void adc_dma_callback(adcs_e num)
|
||||
{
|
||||
adcs_t *p = &adcs[num];
|
||||
DBG_ASSERT(p != NULL __DBG_LINE);
|
||||
if (LL_DMA_IsActiveFlag_TC1(p->dma) != 0)
|
||||
{
|
||||
LL_DMA_ClearFlag_TC1(p->dma);
|
||||
}
|
||||
// 检查DMA1的传输错误标志是否为1,如果是,则清除该标志
|
||||
if (LL_DMA_IsActiveFlag_TE1(p->dma) != 0)
|
||||
{
|
||||
LL_DMA_ClearFlag_TE1(p->dma);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ADC回调函数
|
||||
* @param {adcs_e} num ADC编号
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void adc_env_callback(adcs_e num)
|
||||
{
|
||||
adcs_t *p = &adcs[num];
|
||||
DBG_ASSERT(p != NULL __DBG_LINE);
|
||||
if (LL_ADC_IsActiveFlag_OVR(p->adc) != 0)
|
||||
{
|
||||
p->ovr_count++;
|
||||
LL_ADC_ClearFlag_OVR(p->adc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 通过用户配置的通道号获取通道数量
|
||||
* @param {uint32_t} channnels 存储数据所在的序列号
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static uint8_t adc_get_channels_count(uint32_t channnels)
|
||||
{
|
||||
uint8_t ch_num = 0;
|
||||
if (BIT_IS_SET(channnels, IN0))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN1))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN2))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN3))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN4))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN5))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN6))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN7))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN8))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN9))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN10))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN11))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN12))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN13))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN14))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN15))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, IN16))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, INVREF))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, INVBAT))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
if (BIT_IS_SET(channnels, INTEMP))
|
||||
{
|
||||
ch_num++;
|
||||
}
|
||||
return ch_num;
|
||||
}
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* @file adcs.h
|
||||
* @brief Header file for ADC driver using LL library.
|
||||
*
|
||||
* This file contains the declarations and documentation for the ADC driver
|
||||
* using the LL (Low-Level) library.
|
||||
*
|
||||
* @date 2023-09-04 15:59:16
|
||||
* @author xxx
|
||||
* @version 1.0
|
||||
*
|
||||
* @note This code is proprietary and confidential.
|
||||
* Unauthorized copying of this file, via any medium is strictly prohibited.
|
||||
*
|
||||
* @note All rights reserved by xxx.
|
||||
*/
|
||||
|
||||
#ifndef __ADCS_H__
|
||||
#define __ADCS_H__
|
||||
#include "sys.h"
|
||||
|
||||
#define ADC_CHANNEL_MAX 18 ///< Maximum number of ADC channels
|
||||
typedef enum
|
||||
{
|
||||
IN0 = BIT0,
|
||||
IN1 = BIT1,
|
||||
IN2 = BIT2,
|
||||
IN3 = BIT3,
|
||||
IN4 = BIT4,
|
||||
IN5 = BIT5,
|
||||
IN6 = BIT6,
|
||||
IN7 = BIT7,
|
||||
IN8 = BIT8,
|
||||
IN9 = BIT9,
|
||||
IN10 = BIT10,
|
||||
IN11 = BIT11,
|
||||
IN12 = BIT12,
|
||||
IN13 = BIT13,
|
||||
IN14 = BIT14,
|
||||
IN15 = BIT15,
|
||||
IN16 = BIT16,
|
||||
INTEMP = BIT17,
|
||||
INVBAT = BIT18,
|
||||
INVREF = BIT19,
|
||||
} adc_num_e; ///< ADC channel number
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ADCS_1,
|
||||
ADCS_2,
|
||||
ADCS_3,
|
||||
ADCS_MAX,
|
||||
} adcs_e; ///< ADC number
|
||||
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
uint32_t in0 : 1;
|
||||
uint32_t in1 : 1;
|
||||
uint32_t in2 : 1;
|
||||
uint32_t in3 : 1;
|
||||
uint32_t in4 : 1;
|
||||
uint32_t in5 : 1;
|
||||
uint32_t in6 : 1;
|
||||
uint32_t in7 : 1;
|
||||
uint32_t in8 : 1;
|
||||
uint32_t in9 : 1;
|
||||
uint32_t in10 : 1;
|
||||
uint32_t in11 : 1;
|
||||
uint32_t in12 : 1;
|
||||
uint32_t in13 : 1;
|
||||
uint32_t in14 : 1;
|
||||
uint32_t in15 : 1;
|
||||
uint32_t invref : 1;
|
||||
uint32_t intemp : 1;
|
||||
};
|
||||
} adcs_channels_u; ///< ADC channels
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ADC_TypeDef *adc; ///< ADC peripheral
|
||||
DMA_TypeDef *dma; ///< DMA peripheral
|
||||
uint32_t dma_stream; ///< DMA stream
|
||||
uint32_t dma_channel; ///< DMA channel
|
||||
adcs_channels_u channels; ///< ADC channels
|
||||
uint32_t ovr_count; ///< ADC overflow count
|
||||
|
||||
uint16_t adc_cct; ///< Channel single acquisition count
|
||||
uint8_t adc_chans_count; ///< Number of channels
|
||||
uint16_t adc_sum; ///< Channel acquisition count
|
||||
__IO uint16_t *adc_value; ///< Address to store ADC conversion results
|
||||
|
||||
} adcs_t;
|
||||
|
||||
/**
|
||||
* @brief Initializes the ADC module.
|
||||
*
|
||||
* This function initializes the ADC module with the specified parameters.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
* @param adc Pointer to the ADC peripheral.
|
||||
* @param dma Pointer to the DMA peripheral.
|
||||
* @param dma_channel The DMA channel number.
|
||||
* @param adc_cct The ADC continuous conversion mode.
|
||||
* @param channels The number of ADC channels to be converted.
|
||||
*/
|
||||
extern void adc_init(adcs_e num, ADC_TypeDef *adc, DMA_TypeDef *dma, uint32_t dma_stream, uint32_t dma_channel, uint16_t adc_cct, uint32_t channels);
|
||||
|
||||
/**
|
||||
* @brief Starts the ADC conversion.
|
||||
*
|
||||
* This function starts the ADC conversion for the specified ADC number.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
*/
|
||||
extern void adc_restart(adcs_e num);
|
||||
|
||||
/**
|
||||
* @brief Deinitializes the ADC module.
|
||||
*
|
||||
* This function deinitializes the ADC module.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
*/
|
||||
extern void adc_dinit(adcs_e num);
|
||||
|
||||
/**
|
||||
* @brief Gets the ADC conversion result for a single channel.
|
||||
*
|
||||
* This function gets the ADC conversion result for a single channel.
|
||||
* It returns only the first converted value.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
* @param chan The ADC channel number.
|
||||
* @return The ADC conversion result.
|
||||
*/
|
||||
extern uint16_t adc_result_only_one(adcs_e num, uint8_t chan);
|
||||
|
||||
/**
|
||||
* @brief Gets the ADC conversion result using median average filtering.
|
||||
*
|
||||
* This function gets the ADC conversion result for a single channel.
|
||||
* It applies median average filtering to the converted values.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
* @param chan The ADC channel number.
|
||||
* @return The ADC conversion result.
|
||||
*/
|
||||
extern uint16_t adc_result_median_average(adcs_e num, uint8_t chan);
|
||||
|
||||
/**
|
||||
* @brief Gets the ADC conversion result using median filtering.
|
||||
*
|
||||
* This function gets the ADC conversion result for a single channel.
|
||||
* It applies median filtering to the converted values.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
* @param chan The ADC channel number.
|
||||
* @return The ADC conversion result.
|
||||
*/
|
||||
extern uint16_t adc_result_median(adcs_e num, uint8_t chan);
|
||||
|
||||
/**
|
||||
* @brief Gets the ADC conversion result using average filtering.
|
||||
*
|
||||
* This function gets the ADC conversion result for a single channel.
|
||||
* It applies average filtering to the converted values.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
* @param chan The ADC channel number.
|
||||
* @return The ADC conversion result.
|
||||
*/
|
||||
extern uint16_t adc_result_average(adcs_e num, uint8_t chan);
|
||||
|
||||
/**
|
||||
* @brief Gets the ADC conversion result using N-times average filtering.
|
||||
*
|
||||
* This function gets the ADC conversion result for a single channel.
|
||||
* It applies N-times average filtering to the converted values.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
* @param chan The ADC channel number.
|
||||
* @return The ADC conversion result.
|
||||
*/
|
||||
extern uint16_t adc_result_n_average(adcs_e num, uint8_t chan);
|
||||
|
||||
/**
|
||||
* @brief Calculates the temperature from the ADC conversion result.
|
||||
*
|
||||
* This function calculates the temperature in degrees Celsius
|
||||
* from the ADC conversion result of the internal temperature sensor.
|
||||
*
|
||||
* @param adc_value The ADC conversion result.
|
||||
* @return The temperature in degrees Celsius.
|
||||
*/
|
||||
extern float32 adc_result_temperature(uint16_t adc_value);
|
||||
|
||||
/**
|
||||
* @brief Calculates the voltage from the ADC conversion result.
|
||||
*
|
||||
* This function calculates the voltage in millivolts
|
||||
* from the ADC conversion result of the internal voltage reference.
|
||||
*
|
||||
* @param adc_value The ADC conversion result.
|
||||
* @return The voltage in millivolts.
|
||||
*/
|
||||
extern float32 adc_result_value_local(uint16_t adc_value);
|
||||
|
||||
/**
|
||||
* @brief ADC environment callback function.
|
||||
*
|
||||
* This function is called when an ADC conversion is completed.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
*/
|
||||
extern void adc_env_callback(adcs_e num);
|
||||
|
||||
/**
|
||||
* @brief DMA callback function for ADC.
|
||||
*
|
||||
* This function is called when a DMA transfer for ADC is completed.
|
||||
*
|
||||
* @param num The ADC number.
|
||||
*/
|
||||
extern void adc_dma_callback(adcs_e num);
|
||||
#endif ///< __ADCS_H__
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
|
||||
#include "bsp.h"
|
||||
|
||||
#define EXIT_LINE LL_EXTI_LINE_16
|
||||
|
||||
pvd_irq_handle_cb pvd_irq_handle_cb_func = NULL;
|
||||
/**
|
||||
* @brief 配置PVD(电源电压检测)
|
||||
*
|
||||
* 根据给定的电源电压等级配置PVD(电源电压检测)。
|
||||
*
|
||||
* @param pwr_level 电源电压等级
|
||||
* @param call PVD中断处理回调函数
|
||||
*/
|
||||
void pvd_configuration(uint32_t pwr_level, pvd_irq_handle_cb call)
|
||||
{
|
||||
// pvd_irq_handle_cb_func = call;
|
||||
// // 启用电源时钟
|
||||
// LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
|
||||
|
||||
// // 设置PVD电平阈值,例如设置为2.4V LL_PWR_PVDLEVEL_2
|
||||
// LL_PWR_SetPVDLevel(pwr_level);
|
||||
|
||||
// // 启用PVD
|
||||
// LL_PWR_EnablePVD();
|
||||
|
||||
// // 配置PVD中断
|
||||
// LL_EXTI_EnableIT_0_31(EXIT_LINE); // PVD连接到EXTI Line
|
||||
// LL_EXTI_EnableRisingTrig_0_31(EXIT_LINE);
|
||||
// LL_EXTI_EnableFallingTrig_0_31(EXIT_LINE);
|
||||
|
||||
// // 启用PVD中断向量
|
||||
// NVIC_EnableIRQ(PVD_PVM_IRQn);
|
||||
// NVIC_SetPriority(PVD_PVM_IRQn, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理PVD中断
|
||||
*
|
||||
* 当PVD中断触发时,该函数将被调用以处理中断事件。
|
||||
*
|
||||
* @note 无返回值
|
||||
*/
|
||||
void pvd_irq_handle(void)
|
||||
{
|
||||
// if (LL_EXTI_IsActiveFlag_0_31(EXIT_LINE))
|
||||
// {
|
||||
// LL_EXTI_ClearFlag_0_31(EXIT_LINE);
|
||||
// if (pvd_irq_handle_cb_func != NULL)
|
||||
// {
|
||||
// pvd_irq_handle_cb_func();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 禁用调试接口
|
||||
*
|
||||
* 禁用设备的调试接口,包括关闭调试停止模式、调试待机模式和调试睡眠模式。
|
||||
* 同时将 SWD 和 JTAG 接口的引脚配置为普通 GPIO。
|
||||
*
|
||||
* @note disable_debug_interface调用后 SWD 接口会被关闭,ST-LINK 等调试工具无法通过 SWD 连接 MCU,也无法使用 STM32 ST-LINK Utility 软件 通过 ST-LINK 连接MCU解除读保护
|
||||
* 解除读保护流程:Read Out Protection : 改为 Level 0 ,调整为 Level 0 后 Flash 中的程序会被自动擦除。注意:千万不要改为 Level 2 ,改成Level 2 后 MCU 将会被彻底锁死,相当于熔断保护,无法通过软件再恢复。
|
||||
*/
|
||||
void disable_debug_interface(void)
|
||||
{
|
||||
// // 使能 SYSCFG 时钟
|
||||
// LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SYSCFG);
|
||||
|
||||
// // 关闭调试接口
|
||||
// LL_DBGMCU_DisableDBGStopMode();
|
||||
// LL_DBGMCU_DisableDBGStandbyMode();
|
||||
// LL_DBGMCU_DisableDBGSleepMode();
|
||||
|
||||
// // 关闭 SWD 和 JTAG 接口
|
||||
// LL_GPIO_InitTypeDef GPIO_InitStruct = {0};
|
||||
|
||||
// // 配置 SWDIO (PA13) 和 SWCLK (PA14) 引脚为普通 GPIO
|
||||
// GPIO_InitStruct.Pin = LL_GPIO_PIN_13 | LL_GPIO_PIN_14;
|
||||
// GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
|
||||
// GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
|
||||
// LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
|
||||
|
||||
// // 如果使用的是 JTAG 接口,还需要配置 JTDI (PA15), JTDO (PB3), 和 NJTRST (PB4) 引脚
|
||||
// GPIO_InitStruct.Pin = LL_GPIO_PIN_15;
|
||||
// LL_GPIO_Init(GPIOA, &GPIO_InitStruct);
|
||||
|
||||
// GPIO_InitStruct.Pin = LL_GPIO_PIN_3 | LL_GPIO_PIN_4;
|
||||
// LL_GPIO_Init(GPIOB, &GPIO_InitStruct);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* @file bsp.h
|
||||
* @brief This file contains the declarations and definitions for the BSP (Board Support Package) module.
|
||||
*
|
||||
* The BSP module provides functions and configurations specific to the hardware platform, such as initializing
|
||||
* peripherals, configuring GPIO pins, and managing interrupts.
|
||||
*
|
||||
* @author xxx
|
||||
* @date 2023-12-27 14:44:03
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __BSP_H__
|
||||
#define __BSP_H__
|
||||
|
||||
#include "gpios.h"
|
||||
// #include "dmas.h"
|
||||
// #include "adcs.h"
|
||||
// #include "dacs.h"
|
||||
// #include "tims.h"
|
||||
// #include "pwms.h"
|
||||
// #include "uarts.h"
|
||||
// #include "eeprom.h"
|
||||
// #include "spis.h"
|
||||
// #include "i2cs.h"
|
||||
|
||||
///< 定义回调函数类型
|
||||
typedef void (*pvd_irq_handle_cb)(void);
|
||||
|
||||
extern void pvd_configuration(uint32_t pwr_level, pvd_irq_handle_cb call); ///< Configures the Programmable Voltage Detector (PVD) module
|
||||
extern void pvd_irq_handle(void); ///< Handles the PVD interrupt
|
||||
extern void disable_debug_interface(void); ///< Disables the debug interface
|
||||
|
||||
#endif ///< __BSP_H__
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* @file dacs.c
|
||||
* @brief This file contains the implementation of the DAC module.
|
||||
* It provides functions to initialize and de-initialize a DAC instance,
|
||||
* as well as write a 16-bit value to the specified DAC channel.
|
||||
* @author xxx
|
||||
* @date 2023-12-27 14:44:03
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#include "dacs.h"
|
||||
|
||||
/**
|
||||
* @brief Writes a 16-bit value to the specified DAC channel
|
||||
*
|
||||
* @param dac pointer to the DAC instance
|
||||
* @param value 16-bit value to write to the DAC
|
||||
*/
|
||||
static void _out(dac_t *dac, uint16_t value)
|
||||
{
|
||||
DBG_ASSERT(dac != NULL __DBG_LINE);
|
||||
DAC_OUT(dac->dac, dac->dac_channel, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes a DAC instance
|
||||
*
|
||||
* @param dac pointer to the DAC instance
|
||||
* @param dac_channel DAC channel to use
|
||||
* @return pointer to the initialized DAC instance
|
||||
*/
|
||||
dac_t *dac_create(DAC_TypeDef *dac, uint16_t dac_channel)
|
||||
{
|
||||
DBG_ASSERT(dac != NULL __DBG_LINE);
|
||||
dac_t *handle = (dac_t *)osel_mem_alloc(sizeof(dac_t));
|
||||
DBG_ASSERT(handle != NULL __DBG_LINE);
|
||||
handle->dac = dac;
|
||||
handle->dac_channel = dac_channel;
|
||||
|
||||
handle->out = _out;
|
||||
return handle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief De-initializes a DAC instance
|
||||
*
|
||||
* @param dac pointer to the DAC instance
|
||||
*/
|
||||
void dac_free(dac_t *dac)
|
||||
{
|
||||
DBG_ASSERT(dac != NULL __DBG_LINE);
|
||||
osel_mem_free(dac);
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @file dacs.h
|
||||
* @brief Header file for DACs module.
|
||||
*
|
||||
* This file contains the declarations and definitions for the DACs module.
|
||||
* DACs (Digital-to-Analog Converters) are used to convert digital signals into analog voltages.
|
||||
*
|
||||
* @author xxx
|
||||
* @date 2023-12-27 14:44:03
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __DACS_H__
|
||||
#define __DACS_H__
|
||||
#include "dac.h"
|
||||
#include "lib.h"
|
||||
#include "main.h"
|
||||
|
||||
/**
|
||||
* @brief Set the output value for a specific DAC channel.
|
||||
* @param dac: pointer to the @ref dac_t structure that contains the configuration information for the specified DAC.
|
||||
* @param value: the output value to be set.
|
||||
* @retval None
|
||||
*/
|
||||
#define DAC_OUT(DACx, DAC_Channel, Data) \
|
||||
do \
|
||||
{ \
|
||||
LL_DAC_ConvertData12RightAligned(DACx, DAC_Channel, Data); \
|
||||
LL_DAC_TrigSWConversion(DACx, DAC_Channel); \
|
||||
} while (__LINE__ == -1)
|
||||
|
||||
/**
|
||||
* @brief Enable the specified DAC channel.
|
||||
* @param dac: pointer to the @ref dac_t structure that contains the configuration information for the specified DAC.
|
||||
* @retval None
|
||||
*/
|
||||
#define DAC_START(DACx, DAC_Channel) LL_DAC_Enable(DACx, DAC_Channel)
|
||||
|
||||
/**
|
||||
* @brief Disable the specified DAC channel.
|
||||
* @param dac: pointer to the @ref dac_t structure that contains the configuration information for the specified DAC.
|
||||
* @retval None
|
||||
*/
|
||||
#define DAC_STOP(DACx, DAC_Channel) LL_DAC_Disable(DACx, DAC_Channel)
|
||||
|
||||
/**
|
||||
* @brief Structure definition for the DAC driver.
|
||||
*/
|
||||
typedef struct DACS
|
||||
{
|
||||
DAC_TypeDef *dac;
|
||||
uint16_t dac_channel;
|
||||
|
||||
void (*out)(struct DACS *dac, uint16_t value);
|
||||
} dac_t;
|
||||
#endif
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
#ifndef __DMAS_H__
|
||||
#define __DMAS_H__
|
||||
|
||||
/**
|
||||
* @brief 清除DMA传输完成标志
|
||||
* @param {DMA_HandleTypeDef} *DMAX DMA总线句柄
|
||||
* @param {uint32_t} CHx DMA通道号
|
||||
* @return {*} 操作结果
|
||||
* @note: 该宏用于清除DMA总线的传输完成标志。它首先检查DMA总线的传输完成标志是否已置位,如果已置位,则清除该标志。
|
||||
*/
|
||||
#define DMA_ClEAR_FLAG_TC(DMAX, CHx) \
|
||||
do \
|
||||
{ \
|
||||
if (LL_DMA_IsActiveFlag_TC##CHx(DMAX)) \
|
||||
{ \
|
||||
LL_DMA_ClearFlag_TC##CHx(DMAX); \
|
||||
} \
|
||||
} while (__LINE__ == -1)
|
||||
|
||||
/**
|
||||
* @brief 清除DMA传输错误标志
|
||||
* @param {DMA_HandleTypeDef} *DMAX DMA总线句柄
|
||||
* @param {uint32_t} CHx DMA通道号
|
||||
* @return {*} 操作结果
|
||||
* @note: 该宏用于清除DMA总线的传输错误标志。它首先检查DMA总线的传输错误标志是否已置位,如果已置位,则清除该标志。
|
||||
*/
|
||||
#define DMA_ClEAR_FLAG_TE(DMAX, CHx) \
|
||||
do \
|
||||
{ \
|
||||
if (LL_DMA_IsActiveFlag_TE##CHx(DMAX)) \
|
||||
{ \
|
||||
LL_DMA_ClearFlag_TE##CHx(DMAX); \
|
||||
} \
|
||||
} while (__LINE__ == -1)
|
||||
|
||||
/**
|
||||
* @file uarts.c
|
||||
* @brief This file contains the implementation of DMA_CLEAR_FLAG_TC_CHANNEL macro.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Clear the Transfer Complete (TC) flag of a specific DMA channel.
|
||||
*
|
||||
* @param dma The DMA peripheral.
|
||||
* @param channel The DMA channel number.
|
||||
*/
|
||||
#define DMA_CLEAR_FLAG_TC_CHANNEL(dma, channel) \
|
||||
switch (channel) \
|
||||
{ \
|
||||
case LL_DMA_CHANNEL_1: \
|
||||
DMA_ClEAR_FLAG_TC(dma, 1); \
|
||||
break; \
|
||||
case LL_DMA_CHANNEL_2: \
|
||||
DMA_ClEAR_FLAG_TC(dma, 2); \
|
||||
break; \
|
||||
case LL_DMA_CHANNEL_3: \
|
||||
DMA_ClEAR_FLAG_TC(dma, 3); \
|
||||
break; \
|
||||
case LL_DMA_CHANNEL_4: \
|
||||
DMA_ClEAR_FLAG_TC(dma, 4); \
|
||||
break; \
|
||||
case LL_DMA_CHANNEL_5: \
|
||||
DMA_ClEAR_FLAG_TC(dma, 5); \
|
||||
break; \
|
||||
case LL_DMA_CHANNEL_6: \
|
||||
DMA_ClEAR_FLAG_TC(dma, 6); \
|
||||
break; \
|
||||
case LL_DMA_CHANNEL_7: \
|
||||
DMA_ClEAR_FLAG_TC(dma, 7); \
|
||||
break; \
|
||||
default: \
|
||||
break; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear the Transfer Error (TE) flag for the specified DMA channel.
|
||||
*
|
||||
* @param dma The DMA peripheral.
|
||||
* @param channel The DMA channel number.
|
||||
*/
|
||||
#define DMA_CLEAR_FLAG_TE_CHANNEL(dma, channel) \
|
||||
switch (channel) \
|
||||
{ \
|
||||
case 1: \
|
||||
DMA_ClEAR_FLAG_TE(dma, 1); \
|
||||
break; \
|
||||
case 2: \
|
||||
DMA_ClEAR_FLAG_TE(dma, 2); \
|
||||
break; \
|
||||
case 3: \
|
||||
DMA_ClEAR_FLAG_TE(dma, 3); \
|
||||
break; \
|
||||
case 4: \
|
||||
DMA_ClEAR_FLAG_TE(dma, 4); \
|
||||
break; \
|
||||
case 5: \
|
||||
DMA_ClEAR_FLAG_TE(dma, 5); \
|
||||
break; \
|
||||
case 6: \
|
||||
DMA_ClEAR_FLAG_TE(dma, 6); \
|
||||
break; \
|
||||
case 7: \
|
||||
DMA_ClEAR_FLAG_TE(dma, 7); \
|
||||
break; \
|
||||
default: \
|
||||
break; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clears the flags of a DMA channel
|
||||
* @param DMAX DMAx register base
|
||||
* @param CHx DMA channel number
|
||||
* @param Flag a boolean variable that indicates if the transfer is complete
|
||||
* @note This function should be called within the interrupt service routine of the DMA channel
|
||||
*/
|
||||
#define DMA_ClEAR_FLAG(DMAX, CHx, Flag) \
|
||||
do \
|
||||
{ \
|
||||
if (LL_DMA_IsActiveFlag_TC##CHx(DMAX)) \
|
||||
{ \
|
||||
LL_DMA_ClearFlag_TC##CHx(DMAX); \
|
||||
LL_DMA_ClearFlag_GI##CHx(DMAX); \
|
||||
Flag = TRUE; \
|
||||
} \
|
||||
if (LL_DMA_IsActiveFlag_TE##CHx(DMAX)) \
|
||||
{ \
|
||||
LL_DMA_ClearFlag_TE##CHx(DMAX); \
|
||||
} \
|
||||
if (LL_DMA_IsActiveFlag_GI##CHx(DMAX)) \
|
||||
{ \
|
||||
LL_DMA_ClearFlag_GI##CHx(DMAX); \
|
||||
} \
|
||||
} while (__LINE__ == -1)
|
||||
|
||||
#endif // __DMAS_H__
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* @file eeprom.c
|
||||
* @author xxx
|
||||
* @date 2023-11-16 10:28:47
|
||||
* @brief STM32L072xx EEPROM 驱动
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#include "eeprom.h"
|
||||
#include "main.h"
|
||||
|
||||
#ifdef STM32L072xx
|
||||
#define PEKEY1 0x89ABCDEF // FLASH_PEKEYR
|
||||
#define PEKEY2 0x02030405 // FLASH_PEKEYR
|
||||
|
||||
#define LOCK __enable_irq(); // 系统开全局中断
|
||||
#define UNLOCK __disable_irq(); // 系统关全局中断
|
||||
|
||||
/**
|
||||
* @brief 用于配置读取和写入EEPROM的函数
|
||||
* @param {uint32_t} read_addr - 读取地址
|
||||
* @param {uint8_t} *data - 存储数据的指针
|
||||
* @param {uint16_t} length - 读取或写入的数据长度
|
||||
* @return {*}
|
||||
* @note: 这些函数用于在特定的地址读取或写入数据到EEPROM中。地址和数据以字节为单位,长度以字节为单位。
|
||||
*/
|
||||
void chip_eeprom_config_read(uint32_t read_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
uint8_t *wAddr;
|
||||
wAddr = (uint8_t *)(read_addr);
|
||||
while (length--)
|
||||
{
|
||||
*data++ = *wAddr++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 用于配置写入EEPROM的函数
|
||||
* @param {uint32_t} write_addr - 写入地址
|
||||
* @param {uint8_t} *data - 存储数据的指针
|
||||
* @param {uint16_t} length - 写入的数据长度
|
||||
* @return {*}
|
||||
* @note: 这些函数用于在特定的地址写入数据到EEPROM中。地址和数据以字节为单位,长度以字节为单位。在写入数据之前,需要先解锁EEPROM,写入数据,然后锁定EEPROM。
|
||||
*/
|
||||
void chip_eeprom_config_write(uint32_t write_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
uint8_t *addr;
|
||||
addr = (uint8_t *)(write_addr);
|
||||
UNLOCK
|
||||
FLASH->PDKEYR = PEKEY1;
|
||||
FLASH->PDKEYR = PEKEY2;
|
||||
while (FLASH->PECR & FLASH_PECR_PELOCK)
|
||||
;
|
||||
|
||||
while (length--)
|
||||
{
|
||||
*addr++ = *data++;
|
||||
while (FLASH->SR & FLASH_SR_BSY)
|
||||
;
|
||||
}
|
||||
FLASH->PECR |= FLASH_PECR_PELOCK;
|
||||
LOCK
|
||||
}
|
||||
|
||||
#endif // STM32L072xx
|
||||
|
||||
#ifdef STM32L476xx
|
||||
|
||||
/**
|
||||
* @brief 用于配置读取和写入SRAM2的函数
|
||||
* @param {uint32_t} read_addr - 读取地址
|
||||
* @param {uint8_t} *data - 存储数据的指针
|
||||
* @param {uint16_t} length - 读取或写入的数据长度
|
||||
* @return {*}
|
||||
* @note: 这些函数用于在特定的地址读取或写入数据到SRAM2中。地址和数据以字节为单位,长度以字节为单位。
|
||||
*/
|
||||
void chip_eeprom_config_read(uint32_t read_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 用于配置写入SRAM2的函数
|
||||
* @param {uint32_t} write_addr - 写入地址 从0开始
|
||||
* @param {uint8_t} *data - 存储数据的指针
|
||||
* @param {uint16_t} length - 写入的数据长度
|
||||
* @return {*}
|
||||
* @note: 这些函数用于在特定的地址写入数据到SRAM2中。地址和数据以字节为单位,长度以字节为单位。
|
||||
*/
|
||||
void chip_eeprom_config_write(uint32_t write_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
}
|
||||
#endif // STM32L476xx
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef __EEPROM_H__
|
||||
#define __EEPROM_H__
|
||||
#include "lib.h"
|
||||
|
||||
extern void chip_eeprom_config_read(uint32_t read_addr, uint8_t *data, uint16_t length); ///< 读取数据
|
||||
extern void chip_eeprom_config_write(uint32_t write_addr, uint8_t *data, uint16_t length); ///< 写入数据
|
||||
#endif ///< __EEPROM_H__
|
||||
|
|
@ -0,0 +1,550 @@
|
|||
/**
|
||||
* @file flash.c
|
||||
* @author xxx
|
||||
* @date 2024-02-07 11:49:34
|
||||
* @brief
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
* @attention
|
||||
*
|
||||
* ST 的官方驱动 LL 库并没有 flash 驱动。这里自己实现。
|
||||
*
|
||||
* 1. 由于在 stm32l4xx_ll_system.h 中存在部分 FLASH 操作函数(ACR寄存器的处理)且不全面
|
||||
* 因此这里需要额外处理(重命名)
|
||||
*
|
||||
* 2. Main memory
|
||||
* (1) FLASH_ACR 完成
|
||||
* (2) FLASH_PDKEYR 完成
|
||||
* (3) FLASH_KEYR 完成
|
||||
* (4) FLASH_OPTKEYR 完成
|
||||
* (5) FLASH_SR 完成
|
||||
* (6) FLASH_CR 完成
|
||||
* (7) FLASH_ECCR 完成
|
||||
* (8) FLASH_OPTR 未完成
|
||||
* (9) FLASH_PCROP1SR 未完成
|
||||
* 后续寄存器 均未完成
|
||||
* 3. Information block
|
||||
* - System memory
|
||||
* - OTP area
|
||||
* - Option bytes
|
||||
* 4. 根据 HAL 库的实现,相比于与手册的推荐流程,擦写的执行序列还有其他操作。
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
******************************************************************************
|
||||
*/
|
||||
/* Includes ------------------------------------------------------------------*/
|
||||
#include "flash.h"
|
||||
#include "stm32l4xx_ll_rcc.h"
|
||||
#include "stm32l4xx_ll_system.h"
|
||||
#include "stm32l4xx_ll_pwr.h"
|
||||
#ifdef USE_FULL_ASSERT
|
||||
#include "stm32_assert.h"
|
||||
#else
|
||||
#define assert_param(expr) ((void)0U)
|
||||
#endif /* USE_FULL_ASSERT */
|
||||
|
||||
#define WHILE_MAX 30000U
|
||||
/** @addtogroup STM32L4xx_LL_Driver
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @addtogroup FLASH_LL
|
||||
* @{
|
||||
*/
|
||||
|
||||
/* Private types -------------------------------------------------------------*/
|
||||
/* Private variables ---------------------------------------------------------*/
|
||||
/* Private constants ---------------------------------------------------------*/
|
||||
/** @addtogroup FLASH_LL_Private_Constants
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/* Private macros ------------------------------------------------------------*/
|
||||
/** @addtogroup FLASH_LL_Private_Macros
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define IS_LL_FLASH_WRITE_ADDR(__ADDR__) ((__ADDR__) % LL_FLASH_ALIGNMENT_MIN_SIZE == 0)
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
/** @defgroup FLASH_LL_Private_Functions FLASH Private functions
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/* Exported functions --------------------------------------------------------*/
|
||||
/** @addtogroup FLASH_LL_Exported_Functions
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Clear All Error in SR
|
||||
* @param FLASHx FLASH Instance
|
||||
* @retval None
|
||||
*/
|
||||
void LL_FLASH_ClearAllErrorFlag(void)
|
||||
{
|
||||
LL_FLASH_ClearFlag_OPTVERR(FLASH);
|
||||
LL_FLASH_ClearFlag_RDERR(FLASH);
|
||||
LL_FLASH_ClearFlag_FASTERR(FLASH);
|
||||
LL_FLASH_ClearFlag_MISERR(FLASH);
|
||||
LL_FLASH_ClearFlag_PGSERR(FLASH);
|
||||
LL_FLASH_ClearFlag_SIZERR(FLASH);
|
||||
LL_FLASH_ClearFlag_PGAERR(FLASH);
|
||||
LL_FLASH_ClearFlag_WRPERR(FLASH);
|
||||
LL_FLASH_ClearFlag_PROGERR(FLASH);
|
||||
LL_FLASH_ClearFlag_OPERR(FLASH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Flush the instruction and data caches.
|
||||
* @retval None
|
||||
*/
|
||||
void LL_FLASH_FlushCaches(void)
|
||||
{
|
||||
/* Flush instruction cache */
|
||||
if (LL_FLASH_IsEnabledInstructionCache(FLASH))
|
||||
{
|
||||
LL_FLASH_DisableInstructionCache(FLASH);
|
||||
/* Reset instruction cache */
|
||||
LL_FLASH_InstructionCacheReset(FLASH);
|
||||
/* Enable instruction cache */
|
||||
LL_FLASH_EnableInstructionCache(FLASH);
|
||||
}
|
||||
|
||||
/* Flush data cache */
|
||||
if (LL_FLASH_IsEnabledDataCache(FLASH))
|
||||
{
|
||||
LL_FLASH_ZCS_DisableDataCache(FLASH);
|
||||
/* Reset data cache */
|
||||
LL_FLASH_DataCacheReset(FLASH);
|
||||
/* Enable data cache */
|
||||
LL_FLASH_ZCS_EnableDataCache(FLASH);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erase Page
|
||||
* @param pageno Page number
|
||||
* @retval None
|
||||
*/
|
||||
ErrorStatus LL_FLASH_ErasePage(uint32_t pageno)
|
||||
{
|
||||
uint16_t count = 0;
|
||||
/* Check that no Flash memory operation is ongoing by checking the BSY bit */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
|
||||
/* Check and clear all error programming flags due to a previous programming. If not, PGSERR is set. */
|
||||
LL_FLASH_ClearAllErrorFlag();
|
||||
|
||||
/* Set the PER bit and select the page you wish to erase (PNB) with the associated bank (BKER) in the Flash control register (FLASH_CR). */
|
||||
LL_FLASH_EnablePageErase(FLASH);
|
||||
|
||||
if (pageno >= LL_FLASH_BANK1_PAGE_NUM)
|
||||
{
|
||||
pageno -= LL_FLASH_BANK1_PAGE_NUM;
|
||||
LL_FLASH_SetErasePageBank(FLASH, LL_FLASH_BANK2);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_FLASH_SetErasePageBank(FLASH, LL_FLASH_BANK1);
|
||||
}
|
||||
|
||||
LL_FLASH_SetErasePageNo(FLASH, pageno);
|
||||
|
||||
/* Set the STRT bit in the FLASH_CR register. */
|
||||
LL_FLASH_EraseStart(FLASH);
|
||||
|
||||
/* Wait for the BSY bit to be cleared in the FLASH_SR register. */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
|
||||
/* 完成只有需要清除擦除标志. */
|
||||
LL_FLASH_DisablePageErase(FLASH);
|
||||
|
||||
/* Flush the caches to be sure of the data consistency */
|
||||
LL_FLASH_FlushCaches();
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erase bank
|
||||
* @param bank This parameter can be one of the following values:
|
||||
* @arg @ref LL_FLASH_BANK1
|
||||
* @arg @ref LL_FLASH_BANK2
|
||||
* @retval None
|
||||
*/
|
||||
ErrorStatus LL_FLASH_EraseBank(uint32_t bank)
|
||||
{
|
||||
uint16_t count = 0;
|
||||
/* Check that no Flash memory operation is ongoing by checking the BSY bit */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
|
||||
/* Check and clear all error programming flags due to a previous programming. If not, PGSERR is set. */
|
||||
LL_FLASH_ClearAllErrorFlag();
|
||||
|
||||
/* Set the MER1 bit or/and MER2 (depending on the bank) in the Flash control register (FLASH_CR).
|
||||
Both banks can be selected in the same operation. */
|
||||
if (bank == LL_FLASH_BANK1)
|
||||
{
|
||||
LL_FLASH_EnableBank1Erase(FLASH);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_FLASH_EnableBank2Erase(FLASH);
|
||||
}
|
||||
|
||||
/* Set the STRT bit in the FLASH_CR register. */
|
||||
LL_FLASH_EraseStart(FLASH);
|
||||
|
||||
/* Wait for the BSY bit to be cleared in the FLASH_SR register. */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
|
||||
/* 完成只有需要清除擦除标志. */
|
||||
LL_FLASH_DisableBank1Erase(FLASH);
|
||||
LL_FLASH_DisableBank2Erase(FLASH);
|
||||
|
||||
/* Flush the caches to be sure of the data consistency */
|
||||
LL_FLASH_FlushCaches();
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Erase Chip
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
ErrorStatus LL_FLASH_EraseChip(void)
|
||||
{
|
||||
uint16_t count = 0;
|
||||
/* Check that no Flash memory operation is ongoing by checking the BSY bit */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
|
||||
/* Check and clear all error programming flags due to a previous programming. If not, PGSERR is set. */
|
||||
LL_FLASH_ClearAllErrorFlag();
|
||||
|
||||
/* Set the MER1 bit or/and MER2 (depending on the bank) in the Flash control register (FLASH_CR).
|
||||
Both banks can be selected in the same operation. */
|
||||
LL_FLASH_EnableBank1Erase(FLASH);
|
||||
LL_FLASH_EnableBank2Erase(FLASH);
|
||||
|
||||
/* Set the STRT bit in the FLASH_CR register. */
|
||||
LL_FLASH_EraseStart(FLASH);
|
||||
|
||||
/* Wait for the BSY bit to be cleared in the FLASH_SR register. */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
|
||||
/* 完成只有需要清除擦除标志. */
|
||||
LL_FLASH_DisableBank1Erase(FLASH);
|
||||
LL_FLASH_DisableBank2Erase(FLASH);
|
||||
|
||||
/* Flush the caches to be sure of the data consistency */
|
||||
LL_FLASH_FlushCaches();
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Program Double Word
|
||||
* @param address specifies the address to be programmed.
|
||||
* @param data specifies the data to be programmed.
|
||||
* @retval An ErrorStatus enumeration value:
|
||||
* - SUCCESS: Write successfully
|
||||
* - ERROR: error
|
||||
*/
|
||||
ErrorStatus LL_FLASH_ProgramDoubleWord(uint32_t address, uint64_t data)
|
||||
{
|
||||
assert_param(!IS_LL_FLASH_WRITE_ADDR(address));
|
||||
uint16_t count = 0;
|
||||
/* Check that no Flash memory operation is ongoing by checking the BSY bit */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
/* Check and clear all error programming flags due to a previous programming. If not, PGSERR is set. */
|
||||
LL_FLASH_ClearAllErrorFlag();
|
||||
|
||||
/* Set the PG bit in the Flash control register (FLASH_CR). */
|
||||
LL_FLASH_EnableProgram(FLASH);
|
||||
|
||||
/* Perform the data write operation at the desired memory address, inside main memory
|
||||
block or OTP area. Only double word can be programmed. */
|
||||
/* Program the double word */
|
||||
*(__IO uint32_t *)address = (uint32_t)data;
|
||||
*(__IO uint32_t *)(address + 4U) = (uint32_t)(data >> 32);
|
||||
|
||||
/* Check that no Flash memory operation is ongoing by checking the BSY bit */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
/* Check that EOP flag is set in the FLASH_SR register (meaning that the programming
|
||||
operation has succeed), and clear it by software. */
|
||||
if (LL_FLASH_IsActiveFlag_EOP(FLASH))
|
||||
{
|
||||
LL_FLASH_ClearFlag_EOP(FLASH);
|
||||
}
|
||||
|
||||
/* Clear the PG bit in the FLASH_CR register if there no more programming request anymore. */
|
||||
LL_FLASH_DisableProgram(FLASH);
|
||||
|
||||
/* Flush the caches to be sure of the data consistency */
|
||||
LL_FLASH_FlushCaches();
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Program
|
||||
* @param address specifies the address to be programmed.
|
||||
* @param data specifies the data to be programmed.
|
||||
* @param num specifies the data number
|
||||
* @retval An ErrorStatus enumeration value:
|
||||
* - SUCCESS: Write successfully
|
||||
* - ERROR: error
|
||||
*/
|
||||
ErrorStatus LL_FLASH_Program(uint32_t address, uint8_t data[], uint32_t num)
|
||||
{
|
||||
static uint64_t DataT = 0;
|
||||
uint32_t T = 0, S = 0;
|
||||
uint16_t count = 0;
|
||||
assert_param(!IS_LL_FLASH_WRITE_ADDR(address));
|
||||
|
||||
if (num == 0)
|
||||
{
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/* Check that no Flash memory operation is ongoing by checking the BSY bit */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
/* Check and clear all error programming flags due to a previous programming. If not, PGSERR is set. */
|
||||
LL_FLASH_ClearAllErrorFlag();
|
||||
|
||||
/* Set the PG bit in the Flash control register (FLASH_CR). */
|
||||
LL_FLASH_EnableProgram(FLASH);
|
||||
|
||||
/* Perform the data write operation at the desired memory address, inside main memory
|
||||
block or OTP area. Only double word can be programmed. */
|
||||
T = num;
|
||||
while (num > 0)
|
||||
{
|
||||
DataT = 0;
|
||||
if (num >= 8)
|
||||
{
|
||||
for (int i = 0; i < LL_FLASH_ALIGNMENT_MIN_SIZE; i++)
|
||||
{
|
||||
DataT = DataT << 8;
|
||||
DataT |= data[S + 7 - i];
|
||||
}
|
||||
S += LL_FLASH_ALIGNMENT_MIN_SIZE;
|
||||
num -= LL_FLASH_ALIGNMENT_MIN_SIZE;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
DataT = DataT << 8;
|
||||
DataT |= data[T - 1 - i];
|
||||
}
|
||||
num = 0;
|
||||
}
|
||||
|
||||
/* Program the double word */
|
||||
*(__IO uint32_t *)address = (uint32_t)DataT;
|
||||
*(__IO uint32_t *)(address + 4U) = (uint32_t)(DataT >> 32);
|
||||
|
||||
/* Check that no Flash memory operation is ongoing by checking the BSY bit */
|
||||
while (LL_FLASH_IsActiveFlag_BSY(FLASH))
|
||||
{
|
||||
if (count++ > WHILE_MAX)
|
||||
{
|
||||
return ERROR;
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
|
||||
/* Check that EOP flag is set in the FLASH_SR register (meaning that the programming
|
||||
operation has succeed), and clear it by software. */
|
||||
if (LL_FLASH_IsActiveFlag_EOP(FLASH))
|
||||
{
|
||||
LL_FLASH_ClearFlag_EOP(FLASH);
|
||||
}
|
||||
|
||||
address += LL_FLASH_ALIGNMENT_MIN_SIZE;
|
||||
}
|
||||
|
||||
/* Clear the PG bit in the FLASH_CR register if there no more programming request anymore. */
|
||||
LL_FLASH_DisableProgram(FLASH);
|
||||
|
||||
/* Flush the caches to be sure of the data consistency */
|
||||
LL_FLASH_FlushCaches();
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/** @addtogroup FLASH_LL_Private_Functions
|
||||
* @{
|
||||
*/
|
||||
/**
|
||||
* @brief Fast program a row double-word (64-bit) at a specified address.
|
||||
* @param address: specifies the address to be programmed.
|
||||
* @param DataAddress: specifies the address where the data are stored.
|
||||
* @retval None
|
||||
*/
|
||||
void LL_FLASH_ProgramFast(uint32_t address, uint32_t DataAddress)
|
||||
{
|
||||
uint8_t row_index = (2 * LL_FLASH_ROW_SIZE);
|
||||
__IO uint32_t *dest_addr = (__IO uint32_t *)address;
|
||||
__IO uint32_t *src_addr = (__IO uint32_t *)DataAddress;
|
||||
|
||||
/* Check the parameters */
|
||||
assert_param(IS_FLASH_MAIN_MEM_ADDRESS(address));
|
||||
|
||||
/* Set FSTPG bit */
|
||||
LL_FLASH_EnableFastProgram(FLASH);
|
||||
|
||||
/* Disable interrupts to avoid any interruption during the loop */
|
||||
__disable_irq();
|
||||
|
||||
/* Program the double word of the row */
|
||||
do
|
||||
{
|
||||
*dest_addr = *src_addr;
|
||||
dest_addr++;
|
||||
src_addr++;
|
||||
row_index--;
|
||||
} while (row_index != 0U);
|
||||
|
||||
/* Re-enable the interrupts */
|
||||
__enable_irq();
|
||||
|
||||
LL_FLASH_DisableFastProgram(FLASH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fast program a row double-word (64-bit) at a specified address.
|
||||
* @param address: specifies the address to be programmed.
|
||||
* @param data: specifies the data to be programmed.
|
||||
* @retval None
|
||||
*/
|
||||
ErrorStatus LL_FLASH_Read(uint32_t address, uint8_t data[], uint32_t num)
|
||||
{
|
||||
if (num == 0)
|
||||
{
|
||||
return SUCCESS;
|
||||
}
|
||||
for (uint32_t i = 0; i < num; i++)
|
||||
{
|
||||
data[i] = *(__IO uint8_t *)(address + i);
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 擦除指定地址的 FLASH
|
||||
*
|
||||
* 根据给定的地址和大小,擦除 FLASH 中指定范围的页面。
|
||||
*
|
||||
* @param address FLASH 中的起始地址
|
||||
* @param size 需要擦除的大小(以字节为单位)
|
||||
*/
|
||||
void LL_FLASH_EraseAddress(uint32_t address, uint16_t size)
|
||||
{
|
||||
uint16_t start_page = 0, end_page = 0;
|
||||
start_page = address / LL_FLASH_PAGE_SIZE;
|
||||
end_page = (address + size) / LL_FLASH_PAGE_SIZE;
|
||||
// 擦除页
|
||||
for (uint16_t i = start_page; i < end_page; i++)
|
||||
{
|
||||
LL_FLASH_ErasePage(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
/**
|
||||
* @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) (HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET))
|
||||
|
||||
/**
|
||||
* @brief Set the GPIO pin to low.
|
||||
*
|
||||
* @param port The GPIO port.
|
||||
* @param pin The GPIO pin.
|
||||
*/
|
||||
#define GPIO_RESET(port, pin) (HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET))
|
||||
|
||||
/**
|
||||
* @brief Toggle the state of the GPIO pin.
|
||||
*
|
||||
* @param port The GPIO port.
|
||||
* @param pin The GPIO pin.
|
||||
*/
|
||||
#define GPIO_TOGGLE(port, pin) (HAL_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) (HAL_GPIO_ReadPin(port, pin) == GPIO_PIN_SET)
|
||||
|
||||
/**
|
||||
* @brief Set the GPIO pin as input.
|
||||
*
|
||||
* @param port The GPIO port.
|
||||
* @param pin The GPIO pin.
|
||||
*/
|
||||
#define GPIO_SET_INPUT(port, pin) \
|
||||
do \
|
||||
{ \
|
||||
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
|
||||
GPIO_InitStruct.Pin = pin; \
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; \
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL; \
|
||||
HAL_GPIO_Init(port, &GPIO_InitStruct); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief Set the GPIO pin as output.
|
||||
*
|
||||
* @param port The GPIO port.
|
||||
* @param pin The GPIO pin.
|
||||
*/
|
||||
#define GPIO_SET_OUTPUT(port, pin) \
|
||||
do \
|
||||
{ \
|
||||
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
|
||||
GPIO_InitStruct.Pin = pin; \
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; \
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL; \
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; \
|
||||
HAL_GPIO_Init(port, &GPIO_InitStruct); \
|
||||
} 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, af) \
|
||||
do \
|
||||
{ \
|
||||
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
|
||||
GPIO_InitStruct.Pin = pin; \
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; \
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL; \
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; \
|
||||
GPIO_InitStruct.Alternate = af; \
|
||||
HAL_GPIO_Init(port, &GPIO_InitStruct); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief Set the GPIO pin as analog.
|
||||
*
|
||||
* @param port The GPIO port.
|
||||
* @param pin The GPIO pin.
|
||||
*/
|
||||
#define GPIO_SET_ANALOG(port, pin) \
|
||||
do \
|
||||
{ \
|
||||
GPIO_InitTypeDef GPIO_InitStruct = {0}; \
|
||||
GPIO_InitStruct.Pin = pin; \
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; \
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL; \
|
||||
HAL_GPIO_Init(port, &GPIO_InitStruct); \
|
||||
} 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__
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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__
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#include "iwdgs.h"
|
||||
|
||||
/**
|
||||
* @brief 检查判断CPU复位是否是看门狗复位
|
||||
* @return {BOOL}
|
||||
* @note
|
||||
*/
|
||||
BOOL check_watchdog_reset(void)
|
||||
{
|
||||
if (LL_RCC_IsActiveFlag_IWDGRST() == SET) // cpu is reset due to iwdg
|
||||
{
|
||||
LL_RCC_ClearResetFlags(); // clear flag
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调试模式冻结看门狗
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void debug_freeze_watchdog(void)
|
||||
{
|
||||
LL_DBGMCU_APB1_GRP1_FreezePeriph(LL_DBGMCU_APB1_GRP1_IWDG_STOP);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调试模式恢复看门狗
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void debug_unfreeze_watchdog(void)
|
||||
{
|
||||
LL_DBGMCU_APB1_GRP1_UnFreezePeriph(LL_DBGMCU_APB1_GRP1_IWDG_STOP);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @file iwdgs.h
|
||||
* @brief This file contains the declaration of the Independent Watchdog (IWDG) module.
|
||||
*
|
||||
* The Independent Watchdog (IWDG) is a hardware module in STM32 microcontrollers that provides a mechanism for system reset in case of software failures or malfunctions. This file declares the functions and constants related to the IWDG module.
|
||||
*
|
||||
* @author xxx
|
||||
* @date 2023-12-27 14:44:03
|
||||
* @version 1.0
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __IWDGS_H__
|
||||
#define __IWDGS_H__
|
||||
|
||||
#include "main.h"
|
||||
#include "lib.h"
|
||||
/**
|
||||
* @brief Reloads the watchdog counter.
|
||||
*
|
||||
* This macro is used to reload the watchdog counter, preventing the system from resetting.
|
||||
*/
|
||||
#define WATCHDOG_RESET() LL_IWDG_ReloadCounter(IWDG)
|
||||
|
||||
/**
|
||||
* @brief Checks if the system has been reset by the watchdog.
|
||||
*
|
||||
* @return BOOL Returns TRUE if the system has been reset by the watchdog, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL check_watchdog_reset(void);
|
||||
|
||||
/**
|
||||
* @brief Freezes the watchdog timer for debugging purposes.
|
||||
*
|
||||
* This function freezes the watchdog timer, allowing for debugging without triggering a watchdog reset.
|
||||
*/
|
||||
extern void debug_freeze_watchdog(void);
|
||||
|
||||
/**
|
||||
* @brief Unfreezes the watchdog timer after debugging.
|
||||
*
|
||||
* This function unfreezes the watchdog timer, allowing it to resume normal operation after debugging.
|
||||
*/
|
||||
extern void debug_unfreeze_watchdog(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* @file pwms.h
|
||||
* @brief Header file for PWMs module.
|
||||
*
|
||||
* This file contains the declarations and documentation for the PWMs module.
|
||||
*
|
||||
* @author xxx
|
||||
* @date 2023-12-27 14:44:03
|
||||
* @version 1.0
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __PWMS_H__
|
||||
#define __PWMS_H__
|
||||
#include "lib.h"
|
||||
|
||||
/**
|
||||
* @brief Starts the PWM for a specific channel
|
||||
* @param TIMx: TIM instance
|
||||
* @param CHx: Channel to be started
|
||||
* @retval None
|
||||
*/
|
||||
#define PWM_START(TIMx, CHx) \
|
||||
do \
|
||||
{ \
|
||||
LL_TIM_EnableCounter(TIMx); \
|
||||
LL_TIM_CC_EnableChannel(TIMx, CHx); \
|
||||
} while (__LINE__ == -1)
|
||||
|
||||
/**
|
||||
* @brief Stops the PWM for a specific channel
|
||||
* @param TIMx: TIM instance
|
||||
* @param CHx: Channel to be stopped
|
||||
* @retval None
|
||||
*/
|
||||
#define PWM_STOP(TIMx, CHx) \
|
||||
do \
|
||||
{ \
|
||||
LL_TIM_DisableCounter(TIMx); \
|
||||
LL_TIM_CC_DisableChannel(TIMx, CHx); \
|
||||
} while (__LINE__ == -1)
|
||||
|
||||
/**
|
||||
* @brief Sets the PWM frequency
|
||||
* @param TIMx: TIM instance
|
||||
* @param CHx: Channel to be set
|
||||
* @param COMPARE: Compare value
|
||||
* @retval None
|
||||
*/
|
||||
static inline void PWM_SET_COMPARE(TIM_TypeDef *TIMx, uint32_t CHx, uint16_t COMPARE)
|
||||
{
|
||||
switch (CHx)
|
||||
{
|
||||
case LL_TIM_CHANNEL_CH1:
|
||||
LL_TIM_OC_SetCompareCH1(TIMx, COMPARE);
|
||||
break;
|
||||
case LL_TIM_CHANNEL_CH2:
|
||||
LL_TIM_OC_SetCompareCH2(TIMx, COMPARE);
|
||||
break;
|
||||
case LL_TIM_CHANNEL_CH3:
|
||||
LL_TIM_OC_SetCompareCH3(TIMx, COMPARE);
|
||||
break;
|
||||
case LL_TIM_CHANNEL_CH4:
|
||||
LL_TIM_OC_SetCompareCH4(TIMx, COMPARE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置PWM占空比
|
||||
*
|
||||
* 设置指定定时器TIMx的指定通道CHx的PWM占空比。
|
||||
*
|
||||
* @param TIMx 定时器类型,例如TIM1、TIM2等
|
||||
* @param CHx 通道号,例如TIM_CHANNEL_1、TIM_CHANNEL_2等
|
||||
* @param DUTY 占空比,范围在0到100之间
|
||||
*/
|
||||
static inline void PWM_SET_DUTY(TIM_TypeDef *TIMx, uint32_t CHx, uint16_t DUTY)
|
||||
{
|
||||
PWM_SET_COMPARE(TIMx, CHx, DUTY * LL_TIM_GetAutoReload(TIMx) / 100);
|
||||
}
|
||||
|
||||
// 获取当前频率
|
||||
static inline uint32_t PWM_GET_FREQ(TIM_TypeDef *TIMx)
|
||||
{
|
||||
return SystemCoreClock / (LL_TIM_GetPrescaler(TIMx) + 1) / (LL_TIM_GetAutoReload(TIMx) + 1);
|
||||
}
|
||||
|
||||
#endif ///< __PWMS_H__
|
||||
|
|
@ -0,0 +1 @@
|
|||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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__
|
||||
|
|
@ -0,0 +1 @@
|
|||
#include "tims.h"
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* @file tims.h
|
||||
* @brief Header file for TIMS module.
|
||||
*
|
||||
* This file contains the declarations and definitions for the TIMS module.
|
||||
* TIMS stands for Timer System and provides functionality related to timers.
|
||||
*
|
||||
* @author xxx
|
||||
* @date 2024-01-16 22:23:43
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __TIMS_H__
|
||||
#define __TIMS_H__
|
||||
#include "lib.h"
|
||||
/**
|
||||
Timer overflow time calculation formula
|
||||
Tout = ((ARR + 1)*(PSC + 1)) / Tclk
|
||||
Given Tclk as 84MHz, we need Tout to be 200ms or 200000us. Let's assume PSC is 839, substituting it into the formula gives ARR = 19999.
|
||||
With these calculated values, both ARR and PSC are within the range of 0 to 65535, so we can use this parameter set.
|
||||
*/
|
||||
|
||||
#define ENABLE_TIM_COUNT(TIMx) \
|
||||
do \
|
||||
{ \
|
||||
LL_TIM_EnableCounter(TIMx); \
|
||||
} while (__LINE__ == -1);
|
||||
|
||||
#define RESET_TIM_COUNT(TIMx) \
|
||||
do \
|
||||
{ \
|
||||
LL_TIM_DisableCounter(TIMx); \
|
||||
LL_TIM_SetCounter(TIMx, 0); \
|
||||
LL_TIM_EnableCounter(TIMx); \
|
||||
} while (__LINE__ == -1);
|
||||
/**
|
||||
* @brief Enables the specified TIMx.
|
||||
* @param TIMx TIM instance.
|
||||
*/
|
||||
#define ENABLE_TIM(TIMx) \
|
||||
do \
|
||||
{ \
|
||||
LL_TIM_EnableCounter(TIMx); \
|
||||
LL_TIM_EnableIT_UPDATE(TIMx); \
|
||||
} while (__LINE__ == -1);
|
||||
|
||||
/**
|
||||
* @brief Checks if the specified TIMx is enabled.
|
||||
* @param TIMx TIM instance.
|
||||
* @retval The new state of TIMx (1 or 0).
|
||||
*/
|
||||
#define IS_ENABLE_TIM(TIMx) LL_TIM_IsEnabledIT_UPDATE(TIMx)
|
||||
|
||||
/**
|
||||
* @brief Disables the specified TIMx.
|
||||
* @param TIMx TIM instance.
|
||||
*/
|
||||
#define DISABLE_TIM(TIMx) \
|
||||
do \
|
||||
{ \
|
||||
LL_TIM_DisableCounter(TIMx); \
|
||||
LL_TIM_DisableIT_UPDATE(TIMx); \
|
||||
} while (__LINE__ == -1);
|
||||
|
||||
#define ENABLE_TIM_ARR_RELOAD(TIMx) LL_TIM_EnableARRPreload(TIMx)
|
||||
#define DISABLE_TIM_ARR_RELOAD(TIMx) LL_TIM_DisableARRPreload(TIMx)
|
||||
|
||||
/**
|
||||
* @brief Checks if the specified TIMx interrupt flag is set.
|
||||
* @param TIMx TIM instance.
|
||||
* @retval The new state of the specified TIMx interrupt flag (1 or 0).
|
||||
*/
|
||||
#define IS_TIM_IT_FLAG(TIMx) (LL_TIM_IsActiveFlag_UPDATE(TIMx) == 1)
|
||||
|
||||
/**
|
||||
* @brief TIM interrupt handler.
|
||||
* @param TIMx TIM instance.
|
||||
*/
|
||||
#define TIM_IRQ_HANDLER(TIMx) \
|
||||
do \
|
||||
{ \
|
||||
if (LL_TIM_IsActiveFlag_CC1(TIMx) == SET) \
|
||||
{ \
|
||||
if (LL_TIM_IsEnabledIT_CC1(TIMx) == SET) \
|
||||
{ \
|
||||
LL_TIM_ClearFlag_CC1(TIMx); \
|
||||
} \
|
||||
} \
|
||||
if (LL_TIM_IsActiveFlag_CC2(TIMx) == SET) \
|
||||
{ \
|
||||
if (LL_TIM_IsEnabledIT_CC2(TIMx) == SET) \
|
||||
{ \
|
||||
LL_TIM_ClearFlag_CC2(TIMx); \
|
||||
} \
|
||||
} \
|
||||
if (LL_TIM_IsActiveFlag_CC3(TIMx) == SET) \
|
||||
{ \
|
||||
if (LL_TIM_IsEnabledIT_CC3(TIMx) == SET) \
|
||||
{ \
|
||||
LL_TIM_ClearFlag_CC3(TIMx); \
|
||||
} \
|
||||
} \
|
||||
if (LL_TIM_IsActiveFlag_CC4(TIMx) == SET) \
|
||||
{ \
|
||||
if (LL_TIM_IsEnabledIT_CC4(TIMx) == SET) \
|
||||
{ \
|
||||
LL_TIM_ClearFlag_CC4(TIMx); \
|
||||
} \
|
||||
} \
|
||||
if (LL_TIM_IsActiveFlag_UPDATE(TIMx) == SET) \
|
||||
{ \
|
||||
if (LL_TIM_IsEnabledIT_UPDATE(TIMx) == SET) \
|
||||
{ \
|
||||
LL_TIM_ClearFlag_UPDATE(TIMx); \
|
||||
} \
|
||||
} \
|
||||
} while (__LINE__ == -1)
|
||||
|
||||
/**
|
||||
* @brief 获取定时器周期时间(单位:毫秒)
|
||||
*
|
||||
* 获取指定定时器TIMx的周期时间,以秒为单位。
|
||||
*
|
||||
* @param TIMx 定时器指针,指向需要查询的定时器
|
||||
*
|
||||
* @return 返回定时器周期时间(单位:毫秒)
|
||||
*/
|
||||
#define TIM_CYCLE(TIMx) ((LL_TIM_GetAutoReload(TIMx) + 1) * 0.1)
|
||||
#endif ///< __TIMS_H__
|
||||
|
|
@ -0,0 +1,486 @@
|
|||
/*
|
||||
* @Author:
|
||||
* @Date: 2023-07-31 11:47:35
|
||||
* @LastEditors: xxx
|
||||
* @LastEditTime: 2023-08-25 15:30:33
|
||||
* @Description: LL库的串口驱动
|
||||
* email:
|
||||
* Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#include "uarts.h"
|
||||
#include "dma.h"
|
||||
|
||||
// 清理接收中断错误标志
|
||||
static void uart_clear_error(uart_t *uart)
|
||||
{
|
||||
if (uart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
uart->rx_error_count = 0;
|
||||
if (uart->rx_err_en == TRUE && uart->rx_error != NULL)
|
||||
{
|
||||
osel_memset((uint8_t *)uart->rx_error, 0, sizeof(uarts_interupt_error_t) * uart->rxsize);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @brief 创建一个UART设备
|
||||
* @param {USART_TypeDef} *huart USART总线设备句柄
|
||||
* @param {BOOL} rx_dma_en 接收DMA使能标志
|
||||
* @param {uint16_t} rxsize 接收缓冲区大小
|
||||
* @param {rx_interrupt_cb_t} rx_cb 接收中断回调函数
|
||||
* @param {BOOL} tx_dma_en 发送DMA使能标志
|
||||
* @param {uint16_t} txsize 发送缓冲区大小
|
||||
* @param {tx_complete_cb_t} tx_complete_cb 发送完成回调函数
|
||||
* @return {*} 创建的UART设备指针
|
||||
* @note: 该函数用于创建一个UART设备。它首先断言huart不为空,然后分配内存用于接收缓冲区和发送缓冲区,并设置相关标志。最后,返回创建的UART设备指针。
|
||||
*/
|
||||
uart_t *uart_create(USART_TypeDef *huart, BOOL rx_dma_en, uint16_t rxsize, rx_interupt_cb_t rx_cb,
|
||||
BOOL tx_dma_en, uint16_t txsize, tx_complete_cb_t tx_complete_cb)
|
||||
{
|
||||
DBG_ASSERT(huart != NULL __DBG_LINE);
|
||||
// 分配内存
|
||||
uart_t *uart = (uart_t *)osel_mem_alloc(sizeof(uart_t));
|
||||
DBG_ASSERT(uart != NULL __DBG_LINE);
|
||||
|
||||
uart->rx_interupt_timeout = TRUE; // 接收超时标志
|
||||
uart->rx_interupt_cnt = 0; // 接收中断计数
|
||||
|
||||
// 设置接收回调函数
|
||||
uart->rx_interupt_cb = rx_cb;
|
||||
// 设置接收数据大小
|
||||
uart->rxsize = rxsize;
|
||||
|
||||
// 设置发送完成回调函数
|
||||
uart->tx_complete_cb = tx_complete_cb;
|
||||
// 设置发送数据大小
|
||||
uart->txsize = txsize;
|
||||
// 如果接收大小大于0,则分配内存
|
||||
if (rxsize > 0)
|
||||
{
|
||||
uart->rxbuf = (uint8_t *)osel_mem_alloc(rxsize);
|
||||
DBG_ASSERT(uart->rxbuf != NULL __DBG_LINE);
|
||||
}
|
||||
|
||||
// 如果发送大小大于0,则分配内存
|
||||
if (txsize > 0)
|
||||
{
|
||||
uart->txbuf = (uint8_t *)osel_mem_alloc(txsize);
|
||||
DBG_ASSERT(uart->txbuf != NULL __DBG_LINE);
|
||||
}
|
||||
// 设置接收DMA禁用
|
||||
uart->rx_dma_en = rx_dma_en;
|
||||
// 设置发送DMA禁用
|
||||
uart->tx_dma_en = tx_dma_en;
|
||||
|
||||
// 设置huart
|
||||
uart->huart = huart;
|
||||
|
||||
// 返回uart
|
||||
return uart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使能UART接收
|
||||
* @param {uart_t} *uart UART设备句柄
|
||||
* @param {BOOL} rx_err_en 接收错误使能标志,只能用于串口中断模式下使用
|
||||
* @return {*} 操作结果
|
||||
* @note: 该函数用于使能UART设备的接收功能。它首先检查UART设备的接收DMA使能标志,然后禁用接收中断,配置RX DMA并启用RX DMA通道。最后,检查UART设备的发送DMA使能标志,配置TX DMA并启用TX DMA通道。
|
||||
*/
|
||||
void uart_recv_en(uart_t *uart, BOOL rx_err_en)
|
||||
{
|
||||
if (FALSE == uart->rx_dma_en)
|
||||
{
|
||||
uart->rx_err_en = rx_err_en;
|
||||
LL_USART_EnableIT_RXNE(uart->huart); // 使用接收中断处理
|
||||
}
|
||||
else
|
||||
{
|
||||
uart->rx_err_en = FALSE;
|
||||
LL_USART_ClearFlag_IDLE(uart->huart);
|
||||
|
||||
// 配置RX DMA
|
||||
LL_DMA_DisableChannel(uart->dma, uart->dma_tx_channel);
|
||||
LL_DMA_DisableChannel(uart->dma, uart->dma_rx_channel);
|
||||
|
||||
// 配置RX DMA
|
||||
LL_DMA_SetPeriphAddress(uart->dma, uart->dma_rx_channel, LL_USART_DMA_GetRegAddr(uart->huart));
|
||||
LL_DMA_SetMemoryAddress(uart->dma, uart->dma_rx_channel, (uint32_t)uart->rxbuf);
|
||||
LL_DMA_SetDataLength(uart->dma, uart->dma_rx_channel, uart->rxsize);
|
||||
LL_DMA_EnableIT_TC(uart->dma, uart->dma_rx_channel);
|
||||
LL_DMA_EnableChannel(uart->dma, uart->dma_rx_channel);
|
||||
LL_USART_EnableDMAReq_RX(uart->huart);
|
||||
LL_USART_EnableIT_IDLE(uart->huart);
|
||||
|
||||
// 配置TX DMA
|
||||
LL_DMA_SetPeriphAddress(uart->dma, uart->dma_tx_channel, LL_USART_DMA_GetRegAddr(uart->huart));
|
||||
// 配置内存地址
|
||||
LL_DMA_SetMemoryAddress(uart->dma, uart->dma_tx_channel, (uint32_t)uart->txbuf);
|
||||
LL_DMA_EnableIT_TC(uart->dma, uart->dma_tx_channel);
|
||||
LL_USART_EnableDMAReq_TX(uart->huart);
|
||||
|
||||
uart->tx_dma_ok = TRUE;
|
||||
}
|
||||
|
||||
if (uart->rx_err_en == TRUE)
|
||||
{
|
||||
if (uart->rx_error == NULL)
|
||||
{
|
||||
uart->rx_error = (uarts_interupt_error_t *)osel_mem_alloc(sizeof(uarts_interupt_error_t) * uart->rxsize);
|
||||
DBG_ASSERT(uart->rx_error != NULL __DBG_LINE);
|
||||
}
|
||||
|
||||
LL_USART_EnableIT_PE(uart->huart); // 使能奇偶校验错误中断
|
||||
// 使能帧错误中断
|
||||
// 使能帧错误中断
|
||||
// 使能溢出错误中断
|
||||
// LL_USART_EnableIT_ERROR 可以使能上面3个中断
|
||||
/**
|
||||
* When set, Error Interrupt Enable Bit is enabling interrupt generation in case of a framing
|
||||
* error, overrun error or noise flag (FE=1 or ORE=1 or NF=1 in the USARTx_ISR register).
|
||||
*/
|
||||
LL_USART_EnableIT_ERROR(uart->huart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放UART设备资源
|
||||
* @param {uart_t} *uart UART设备句柄
|
||||
* @return {*} 操作结果
|
||||
* @note: 该函数用于释放UART设备的接收缓冲区、发送缓冲区和UART设备本身。
|
||||
*/
|
||||
void uart_free(uart_t *uart)
|
||||
{
|
||||
if (uart != NULL)
|
||||
{
|
||||
if (uart->rxbuf != NULL)
|
||||
{
|
||||
osel_mem_free(uart->rxbuf);
|
||||
}
|
||||
if (uart->rx_error != NULL)
|
||||
{
|
||||
osel_mem_free(uart->rx_error);
|
||||
}
|
||||
if (uart->txbuf != NULL)
|
||||
{
|
||||
osel_mem_free(uart->txbuf);
|
||||
}
|
||||
osel_mem_free(uart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置波特率
|
||||
* @param {uart_t} *uart
|
||||
* @param {uint32_t} baudrate 波特率
|
||||
* @return {*}
|
||||
* @note 可以在超频后串口数据重新通讯
|
||||
*/
|
||||
void uart_set_baudrate(USART_TypeDef *uart, uint32_t baudrate)
|
||||
{
|
||||
LL_USART_SetBaudRate(uart, SystemCoreClock, LL_USART_OVERSAMPLING_16);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 发送数据
|
||||
* @param {uart_t} *uart UART设备句柄
|
||||
* @param {uint8_t} *data 要发送的数据
|
||||
* @param {uint16_t} len 要发送的数据长度
|
||||
* @return {*} 操作结果
|
||||
* @note: 该函数用于发送数据。首先检查UART设备的发送DMA使能标志,然后禁用发送中断,配置TX DMA并启用TX DMA通道。最后,发送数据直到发送缓冲区满或发送中断发生。
|
||||
*/
|
||||
void uart_send_data(uart_t *uart, uint8_t *data, uint16_t len)
|
||||
{
|
||||
DBG_ASSERT(uart != NULL __DBG_LINE);
|
||||
DBG_ASSERT(data != NULL __DBG_LINE);
|
||||
DBG_ASSERT(len > 0 __DBG_LINE);
|
||||
uint16_t count = 0;
|
||||
if (TRUE == uart->tx_dma_en)
|
||||
{
|
||||
uart->tx_dma_ok = FALSE;
|
||||
osel_memcpy(uart->txbuf, data, len); // 拷贝数据到发送缓冲区
|
||||
LL_DMA_DisableChannel(uart->dma, uart->dma_tx_channel);
|
||||
// 配置 DMA 源地址
|
||||
LL_DMA_SetMemoryAddress(uart->dma, uart->dma_tx_channel, (uint32_t)uart->txbuf);
|
||||
// 配置数据长度
|
||||
LL_DMA_SetDataLength(uart->dma, uart->dma_tx_channel, len);
|
||||
// 使能DMA STREAM 也就是发送数据
|
||||
LL_DMA_EnableChannel(uart->dma, uart->dma_tx_channel);
|
||||
// 等待DMA发送完成
|
||||
while (uart->tx_dma_ok == FALSE)
|
||||
{
|
||||
if (count++ >= 2000)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
count = 0;
|
||||
for (uint16_t i = 0; i < len; i++)
|
||||
{
|
||||
count = 0;
|
||||
LL_USART_TransmitData8(uart->huart, data[i]);
|
||||
while (!LL_USART_IsActiveFlag_TXE(uart->huart))
|
||||
{
|
||||
if (count++ >= 0xFE)
|
||||
{
|
||||
count = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!LL_USART_IsActiveFlag_TC(uart->huart))
|
||||
{
|
||||
if (count++ >= 0xFE)
|
||||
{
|
||||
count = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (uart->tx_complete_cb != NULL)
|
||||
{
|
||||
uart->tx_complete_cb();
|
||||
}
|
||||
LL_USART_ClearFlag_TC(uart->huart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART接收超时定时器
|
||||
*
|
||||
* 当UART接收超时定时器触发时,调用此函数处理UART接收超时事件。
|
||||
*
|
||||
* @param uart UART对象指针
|
||||
*/
|
||||
void uart_rx_timeout_timer(uart_t *uart)
|
||||
{
|
||||
// DBG_ASSERT(uart != NULL __DBG_LINE);
|
||||
if (uart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (uart->rx_dma_en == FALSE && uart->rx_interupt_timeout == FALSE) // 中断方式
|
||||
{
|
||||
if (uart->rx_interupt_cnt++ == RX_TIMEOUT_MSEC)
|
||||
{
|
||||
uart->rx_interupt_timeout = TRUE;
|
||||
if (uart->rx_interupt_cb != NULL && uart->rx_index > 0)
|
||||
{
|
||||
uart->rx_interupt_cb(uart->uart_index, uart->rxbuf, uart->rx_index);
|
||||
}
|
||||
uart_data_storage_reset(uart);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief UART接收完成回调函数
|
||||
*
|
||||
* 当UART接收完成时调用此函数。
|
||||
*
|
||||
* @param uart UART设备指针
|
||||
*/
|
||||
void uart_rx_cd_callback(uart_t *uart)
|
||||
{
|
||||
if (uart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (uart->rx_cd_en == FALSE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (uart->rx_dma_en == FALSE) // 中断方式
|
||||
{
|
||||
if (uart->rx_interupt_cb != NULL && uart->rx_index > 0)
|
||||
{
|
||||
uart->rx_interupt_cb(uart->uart_index, uart->rxbuf, uart->rx_index);
|
||||
}
|
||||
uart_data_storage_reset(uart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取UART通信错误计数
|
||||
*
|
||||
* 获取UART设备的接收错误计数。
|
||||
*
|
||||
* @param uart UART设备指针
|
||||
*
|
||||
* @return uint16_t 返回接收错误计数,如果uart为NULL,则返回0
|
||||
*/
|
||||
uint16_t uart_get_error_count(uart_t *uart)
|
||||
{
|
||||
if (uart == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return uart->rx_error_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取UART通信中的错误信息
|
||||
*
|
||||
* 从UART设备中获取接收错误信息和错误计数。
|
||||
*
|
||||
* @param uart UART设备指针
|
||||
* @param count 用于存储错误计数的指针
|
||||
*
|
||||
* @return 如果存在错误,则返回错误类型指针;否则返回NULL
|
||||
*/
|
||||
uarts_interupt_error_t *uart_get_error(uart_t *uart)
|
||||
{
|
||||
if (uart == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
if (uart->rx_error_count > 0)
|
||||
{
|
||||
|
||||
return uart->rx_error;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重置UART数据存储
|
||||
*
|
||||
* 将UART接收到的数据缓冲区重置,并清除错误状态。
|
||||
*
|
||||
* @param uart UART结构体指针
|
||||
*/
|
||||
void uart_data_storage_reset(uart_t *uart)
|
||||
{
|
||||
if (uart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uart->rx_index = 0;
|
||||
uart_clear_error(uart);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 接收中断回调函数
|
||||
* @param {uart_t} *uart UART设备句柄
|
||||
* @return {*} 操作结果
|
||||
* @note: 该函数用于处理接收中断。首先检查接收DMA使能标志,然后禁用接收中断,配置RX DMA并启用RX DMA通道。当接收到数据时,将数据复制到接收缓冲区,并调用接收中断回调函数。当接收缓冲区满时,关闭RX DMA通道并重置接收索引。
|
||||
*/
|
||||
void uart_reception_callback(uart_t *uart)
|
||||
{
|
||||
// DBG_ASSERT(uart != NULL __DBG_LINE);
|
||||
if (uart == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (LL_USART_IsEnabledIT_RXNE(uart->huart) && LL_USART_IsActiveFlag_RXNE(uart->huart))
|
||||
{
|
||||
if (uart->rx_index >= uart->rxsize)
|
||||
{
|
||||
uart_data_storage_reset(uart);
|
||||
}
|
||||
|
||||
uart->rxbuf[uart->rx_index++] = LL_USART_ReceiveData8(uart->huart);
|
||||
uart->rx_interupt_cnt = 0;
|
||||
uart->rx_interupt_timeout = FALSE;
|
||||
// 数据交给超时中断处理 :uart_rx_timeout_timer
|
||||
}
|
||||
else if (LL_USART_IsEnabledIT_IDLE(uart->huart) && LL_USART_IsActiveFlag_IDLE(uart->huart))
|
||||
{
|
||||
if (uart->rx_dma_en == TRUE)
|
||||
{
|
||||
uart->rx_index = uart->rxsize - LL_DMA_GetDataLength(uart->dma, uart->dma_rx_channel);
|
||||
if (uart->rx_cd_en == FALSE)
|
||||
{
|
||||
LL_DMA_DisableChannel(uart->dma, uart->dma_rx_channel);
|
||||
if (uart->rx_interupt_cb != NULL && (uart->rx_index > 0 && uart->rx_index <= uart->rxsize))
|
||||
{
|
||||
uart->rx_interupt_cb(uart->uart_index, uart->rxbuf, uart->rx_index);
|
||||
osel_memset(uart->rxbuf, 0, uart->rxsize);
|
||||
}
|
||||
|
||||
LL_DMA_SetDataLength(uart->dma, uart->dma_rx_channel, uart->rxsize); // 这个不能少 先关闭DMA才能重新设置长度
|
||||
LL_DMA_EnableChannel(uart->dma, uart->dma_rx_channel);
|
||||
}
|
||||
}
|
||||
|
||||
uart->rx_index = 0;
|
||||
LL_USART_ClearFlag_IDLE(uart->huart);
|
||||
}
|
||||
|
||||
if (LL_USART_IsEnabledIT_TC(uart->huart) && LL_USART_IsActiveFlag_TC(uart->huart))
|
||||
{
|
||||
if (uart->tx_complete_cb != NULL)
|
||||
{
|
||||
uart->tx_complete_cb();
|
||||
}
|
||||
LL_USART_ClearFlag_TC(uart->huart);
|
||||
}
|
||||
|
||||
if (uart->rx_err_en == TRUE)
|
||||
{
|
||||
uarts_interupt_error_e err = UART_NO_ERROR;
|
||||
if (LL_USART_IsEnabledIT_PE(uart->huart) && LL_USART_IsActiveFlag_PE(uart->huart))
|
||||
{
|
||||
err = UART_PARITY_ERROR;
|
||||
LL_USART_ClearFlag_PE(uart->huart); // 清除奇偶校验错误标志
|
||||
}
|
||||
|
||||
if (LL_USART_IsActiveFlag_FE(uart->huart) && LL_USART_IsActiveFlag_FE(uart->huart))
|
||||
{
|
||||
err = UART_FRAME_ERROR;
|
||||
LL_USART_ClearFlag_FE(uart->huart); // 清除帧错误标志
|
||||
}
|
||||
|
||||
if (LL_USART_IsActiveFlag_NE(uart->huart) && LL_USART_IsActiveFlag_NE(uart->huart))
|
||||
{
|
||||
// err = UART_NOISE_ERROR;
|
||||
LL_USART_ClearFlag_NE(uart->huart); // 清除噪声错误标志
|
||||
}
|
||||
|
||||
if (LL_USART_IsActiveFlag_ORE(uart->huart) && LL_USART_IsActiveFlag_ORE(uart->huart))
|
||||
{
|
||||
err = UART_OVERRUN_ERROR;
|
||||
LL_USART_ClearFlag_ORE(uart->huart); // 清除溢出错误标志
|
||||
}
|
||||
|
||||
if (err != UART_NO_ERROR && uart->rx_error != NULL)
|
||||
{
|
||||
uint16_t index = uart->rx_index - 1;
|
||||
uart->rx_error[uart->rx_error_count].index = index;
|
||||
uart->rx_error[uart->rx_error_count].err = err;
|
||||
uart->rx_error_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 用于处理串口DMA接收中断的回调函数
|
||||
* @param {uart_t} *uart - 串口对象
|
||||
* @return {*} 无
|
||||
* @note:
|
||||
*/
|
||||
void uart_dma_reception_callback(uart_t *uart)
|
||||
{
|
||||
// 检查输入参数是否为空
|
||||
DBG_ASSERT(uart != NULL __DBG_LINE);
|
||||
|
||||
uart->tx_dma_ok = TRUE;
|
||||
|
||||
// 禁用串口DMA的发送通道
|
||||
LL_DMA_DisableChannel(uart->dma, uart->dma_tx_channel);
|
||||
|
||||
// 清除发送中断标志位
|
||||
DMA_CLEAR_FLAG_TC_CHANNEL(uart->dma, uart->dma_tx_channel);
|
||||
|
||||
// 使能发送中断,用于关闭发送使能引脚
|
||||
LL_USART_EnableIT_TC(uart->huart); // 使能发送中断,用于关闭发送使能引脚
|
||||
|
||||
// 清除传输错误标志
|
||||
DMA_CLEAR_FLAG_TE_CHANNEL(uart->dma, uart->dma_tx_channel);
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* @file uarts.h
|
||||
* @brief Header file for UARTs module.
|
||||
*
|
||||
* This file contains the definitions and function prototypes for UARTs module.
|
||||
* The UARTs module provides functions for creating and managing UART instances,
|
||||
* enabling reception, sending data, and handling interrupts.
|
||||
*/
|
||||
|
||||
#ifndef __UARTS_H__
|
||||
#define __UARTS_H__
|
||||
|
||||
#include "lib.h"
|
||||
#include "main.h"
|
||||
// 串口中断用于接收超时的参数
|
||||
#define RX_TIMEOUT_TICK (1U) /* 10ms的tick */
|
||||
#define RX_TIMEOUT_MSEC (20U / RX_TIMEOUT_TICK) /* 20毫秒需要的tick,可以根据需求添加其他时间更短的宏 */
|
||||
|
||||
/**
|
||||
* @brief Enumeration for UART status.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
UART_OK = 0x00u, /**< The action was successful. */
|
||||
UART_ERROR = 0xFFu /**< Generic error. */
|
||||
} uart_status_e;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
// 无错误
|
||||
UART_NO_ERROR = BIT0,
|
||||
// 奇偶校验错误中断
|
||||
UART_PARITY_ERROR = BIT1,
|
||||
// 帧错误中断
|
||||
UART_FRAME_ERROR = BIT2,
|
||||
// 噪声错误中断
|
||||
UART_NOISE_ERROR = BIT3,
|
||||
// 溢出错误中断
|
||||
UART_OVERRUN_ERROR = BIT4,
|
||||
} uarts_interupt_error_e; ///< UART中断错误枚举
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uarts_interupt_error_e err; ///< 错误标志
|
||||
uint16_t index; ///< 接收到的第几个字节
|
||||
} uarts_interupt_error_t;
|
||||
|
||||
/**
|
||||
* @brief Callback function type for UART receive interrupt.
|
||||
*
|
||||
* This function type is used to define the callback function for UART receive interrupt.
|
||||
* The callback function is called when data is received on the UART.
|
||||
*
|
||||
* @param uart_index The index of the UART.
|
||||
* @param uart_error The error code.
|
||||
* @param data The received data.
|
||||
* @param len The length of the received data.
|
||||
*/
|
||||
typedef void (*rx_interupt_cb_t)(uint8_t uart_index, uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Callback function type for UART transmit complete.
|
||||
*
|
||||
* This function type is used to define the callback function for UART transmit complete.
|
||||
* The callback function is called when the UART transmission is complete.
|
||||
*/
|
||||
typedef void (*tx_complete_cb_t)(void);
|
||||
|
||||
/**
|
||||
* @brief Structure representing a UART instance.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
uint8_t uart_index; /**< The index of the UART. */
|
||||
USART_TypeDef *huart; /**< The UART peripheral. */
|
||||
DMA_TypeDef *dma; /**< The DMA peripheral. */
|
||||
uint32_t dma_rx_channel; /**< The DMA receive channel. */
|
||||
uint32_t dma_tx_channel; /**< The DMA transmit channel. */
|
||||
|
||||
//*******************RX*************************/
|
||||
BOOL rx_cd_en; /**< Flag indicating if carrier detect is enabled. */
|
||||
BOOL rx_dma_en; /**< Flag indicating if DMA reception is enabled. */
|
||||
BOOL rx_err_en; /**< Flag indicating if error interrupt is enabled. */
|
||||
__IO BOOL rx_interupt_timeout; /**< Flag indicating if receive interrupt timeout. */
|
||||
__IO uint8_t rx_interupt_cnt; /**< The receive interrupt count. */
|
||||
uint8_t *rxbuf; /**< The receive buffer. */
|
||||
uint16_t rxsize; /**< The size of the receive buffer. */
|
||||
uarts_interupt_error_t *rx_error; /**< The receive error. */
|
||||
uint16_t rx_error_count; /**< The receive error count. */
|
||||
__IO uint16_t rx_index; /**< The receive data index. */
|
||||
|
||||
//*******************TX*************************/
|
||||
|
||||
BOOL tx_dma_en; /**< Flag indicating if DMA transmission is enabled. */
|
||||
uint8_t *txbuf; /**< The transmit buffer. */
|
||||
uint16_t txsize; /**< The size of the transmit buffer. */
|
||||
uint16_t tx_index; /**< The transmit data index. */
|
||||
__IO BOOL tx_dma_ok; /**< Flag indicating if DMA transmission is complete. */
|
||||
|
||||
rx_interupt_cb_t rx_interupt_cb; /**< The receive interrupt callback function. */
|
||||
tx_complete_cb_t tx_complete_cb; /**< The transmit complete callback function. */
|
||||
} uart_t;
|
||||
|
||||
/**
|
||||
* @brief Creates a UART instance.
|
||||
*
|
||||
* This function creates a UART instance with the specified parameters.
|
||||
*
|
||||
* @param huart The UART peripheral.
|
||||
* @param rx_dma_en Flag indicating if DMA reception is enabled.
|
||||
* @param rxsize The size of the receive buffer.
|
||||
* @param rx_cb The receive interrupt callback function.
|
||||
* @param tx_dma_en Flag indicating if DMA transmission is enabled.
|
||||
* @param txsize The size of the transmit buffer.
|
||||
* @param tx_complete_cb The transmit complete callback function.
|
||||
* @return The created UART instance.
|
||||
*/
|
||||
extern uart_t *uart_create(USART_TypeDef *huart, BOOL rx_dma_en, uint16_t rxsize, rx_interupt_cb_t rx_cb,
|
||||
BOOL tx_dma_en, uint16_t txsize, tx_complete_cb_t tx_complete_cb);
|
||||
|
||||
/**
|
||||
* @brief Frees the resources of a UART instance.
|
||||
*
|
||||
* This function frees the resources allocated for a UART instance.
|
||||
*
|
||||
* @param uart The UART instance to free.
|
||||
*/
|
||||
extern void uart_free(uart_t *uart);
|
||||
|
||||
/**
|
||||
* @brief Initializes a UART instance.
|
||||
*
|
||||
* This function initializes the specified UART instance with the specified parameters.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
* @param baudrate The baudrate.
|
||||
*/
|
||||
extern void uart_set_baudrate(USART_TypeDef *uart, uint32_t baudrate);
|
||||
|
||||
/**
|
||||
* @brief Enables UART reception.
|
||||
*
|
||||
* This function enables reception on the specified UART instance.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
*/
|
||||
extern void uart_recv_en(uart_t *uart, BOOL rx_err_en);
|
||||
|
||||
/**
|
||||
* @brief Sends data over UART.
|
||||
*
|
||||
* This function sends the specified data over the specified UART instance.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
* @param data The data to send.
|
||||
* @param len The length of the data.
|
||||
*/
|
||||
extern void uart_send_data(uart_t *uart, uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief UART receive carrier detect callback.
|
||||
*
|
||||
* This function is the callback for UART receive carrier detect.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
*/
|
||||
extern void uart_rx_cd_callback(uart_t *uart);
|
||||
|
||||
/**
|
||||
* @brief UART receive timeout timer.
|
||||
*
|
||||
* This function is the timer callback for UART receive timeout.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
*/
|
||||
extern void uart_rx_timeout_timer(uart_t *uart);
|
||||
/**
|
||||
* @brief UART receive interrupt callback.
|
||||
*
|
||||
* This function is the interrupt callback for UART receive interrupt.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
*/
|
||||
extern void uart_reception_callback(uart_t *uart);
|
||||
|
||||
/**
|
||||
* @brief Get the UART error count.
|
||||
*
|
||||
* This function returns the number of errors that have occurred on the specified UART instance.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
* @return The number of errors.
|
||||
*/
|
||||
extern uint16_t uart_get_error_count(uart_t *uart);
|
||||
|
||||
/**
|
||||
* @brief Get UART interrupt error.
|
||||
*
|
||||
* This function gets the UART interrupt error.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
* @param count The error count.
|
||||
* @return The error.
|
||||
*/
|
||||
extern uarts_interupt_error_t *uart_get_error(uart_t *uart);
|
||||
|
||||
extern void uart_data_storage_reset(uart_t *uart);
|
||||
|
||||
/**
|
||||
* @brief DMA receive interrupt callback.
|
||||
*
|
||||
* This function is the interrupt callback for DMA receive interrupt.
|
||||
*
|
||||
* @param uart The UART instance.
|
||||
*/
|
||||
extern void uart_dma_reception_callback(uart_t *uart);
|
||||
|
||||
#endif ///< __UARTS_H__
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
/*
|
||||
* @Author:
|
||||
* @Date: 2023-07-04 08:25:56
|
||||
* @LastEditors: xxx
|
||||
* @LastEditTime: 2023-08-25 11:13:52
|
||||
* @Description:一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式可以简化你的程序结构,去除冗余的按键处理硬编码,让你的按键业务逻辑更清晰
|
||||
* email:
|
||||
* Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include "btn.h"
|
||||
|
||||
#define EVENT_CB(ev) \
|
||||
if (handle->cb[ev]) \
|
||||
handle->cb[ev]((Button *)handle)
|
||||
|
||||
// button handle list head.
|
||||
static struct Button *head_handle = NULL;
|
||||
|
||||
/**
|
||||
* @brief 初始化按钮结构体句柄。
|
||||
* @param handle: 按钮句柄结构体。
|
||||
* @param pin_level: 读取按钮连接的HAL GPIOLevel。
|
||||
* @param active_level: 按下按钮的GPIOLevel。
|
||||
* @param button_id: 按钮ID。
|
||||
* @retval 无
|
||||
*/
|
||||
void button_init(struct Button *handle, uint8_t (*pin_level)(uint8_t), active_level_e active_level, uint8_t button_id, uint8_t button_id_reverse)
|
||||
{
|
||||
#ifdef STM32
|
||||
osel_memset((uint8_t *)handle, 0, sizeof(struct Button));
|
||||
#else
|
||||
memset(handle, 0, sizeof(struct Button));
|
||||
#endif
|
||||
|
||||
handle->event = (uint8_t)NONE_PRESS;
|
||||
handle->hal_button_Level = pin_level;
|
||||
handle->button_level = handle->hal_button_Level(button_id);
|
||||
handle->active_level = (uint8_t)active_level;
|
||||
handle->button_id = button_id;
|
||||
handle->button_id_reverse = button_id_reverse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 为按钮添加事件回调函数。
|
||||
* @param handle: 按钮句柄结构体。
|
||||
* @param event: 触发事件类型。
|
||||
* @param cb: 回调函数。
|
||||
* @retval 无
|
||||
*/
|
||||
void button_attach(struct Button *handle, PressEvent event, BtnCallback cb)
|
||||
{
|
||||
handle->cb[event] = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询按钮发生的事件。
|
||||
* @param handle: 按钮句柄结构体。
|
||||
* @retval 按钮事件。
|
||||
*/
|
||||
PressEvent get_button_event(struct Button *handle)
|
||||
{
|
||||
return (PressEvent)(handle->event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按钮驱动核心函数,驱动状态机。
|
||||
* @param handle: 按钮句柄结构体。
|
||||
* @retval 无
|
||||
*/
|
||||
void button_handler(struct Button *handle)
|
||||
{
|
||||
uint8_t read_gpio_level = handle->hal_button_Level(handle->button_id);
|
||||
|
||||
// ticks counter working..
|
||||
if ((handle->state) > 0)
|
||||
handle->ticks++;
|
||||
|
||||
/*------------button debounce handle---------------*/
|
||||
if (read_gpio_level != handle->button_level)
|
||||
{ // not equal to prev one
|
||||
// continue read 3 times same new level change
|
||||
if (++(handle->debounce_cnt) >= DEBOUNCE_TICKS)
|
||||
{
|
||||
handle->button_level = read_gpio_level;
|
||||
handle->debounce_cnt = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // leved not change ,counter reset.
|
||||
handle->debounce_cnt = 0;
|
||||
}
|
||||
|
||||
/*-----------------State machine-------------------*/
|
||||
switch (handle->state)
|
||||
{
|
||||
case 0:
|
||||
if (handle->button_level == handle->active_level)
|
||||
{ // start press down
|
||||
handle->event = (uint8_t)PRESS_DOWN;
|
||||
EVENT_CB(PRESS_DOWN);
|
||||
handle->ticks = 0;
|
||||
handle->repeat = 1;
|
||||
handle->state = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
handle->event = (uint8_t)NONE_PRESS;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (handle->button_level != handle->active_level)
|
||||
{ // released press up
|
||||
handle->event = (uint8_t)PRESS_UP;
|
||||
EVENT_CB(PRESS_UP);
|
||||
handle->ticks = 0;
|
||||
handle->state = 2;
|
||||
}
|
||||
else if (handle->ticks > LONG_TICKS)
|
||||
{
|
||||
handle->event = (uint8_t)LONG_PRESS_START;
|
||||
EVENT_CB(LONG_PRESS_START);
|
||||
handle->state = 5;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (handle->button_level == handle->active_level)
|
||||
{ // press down again
|
||||
handle->event = (uint8_t)PRESS_DOWN;
|
||||
EVENT_CB(PRESS_DOWN);
|
||||
handle->repeat++;
|
||||
EVENT_CB(PRESS_REPEAT); // repeat hit
|
||||
handle->ticks = 0;
|
||||
handle->state = 3;
|
||||
}
|
||||
else if (handle->ticks > SHORT_TICKS)
|
||||
{ // released timeout
|
||||
if (handle->repeat == 1)
|
||||
{
|
||||
handle->event = (uint8_t)SINGLE_CLICK;
|
||||
EVENT_CB(SINGLE_CLICK);
|
||||
}
|
||||
else if (handle->repeat == 2)
|
||||
{
|
||||
handle->event = (uint8_t)DOUBLE_CLICK;
|
||||
EVENT_CB(DOUBLE_CLICK); // repeat hit
|
||||
}
|
||||
handle->state = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if (handle->button_level != handle->active_level)
|
||||
{ // released press up
|
||||
handle->event = (uint8_t)PRESS_UP;
|
||||
EVENT_CB(PRESS_UP);
|
||||
if (handle->ticks < SHORT_TICKS)
|
||||
{
|
||||
handle->ticks = 0;
|
||||
handle->state = 2; // repeat press
|
||||
}
|
||||
else
|
||||
{
|
||||
handle->state = 0;
|
||||
}
|
||||
}
|
||||
else if (handle->ticks > SHORT_TICKS)
|
||||
{ // long press up
|
||||
handle->state = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if (handle->button_level == handle->active_level)
|
||||
{
|
||||
// continue hold trigger
|
||||
handle->event = (uint8_t)LONG_PRESS_HOLD;
|
||||
EVENT_CB(LONG_PRESS_HOLD);
|
||||
}
|
||||
else
|
||||
{ // releasd
|
||||
handle->event = (uint8_t)PRESS_UP;
|
||||
EVENT_CB(PRESS_UP);
|
||||
handle->state = 0; // reset
|
||||
}
|
||||
break;
|
||||
default:
|
||||
handle->state = 0; // reset
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启动按钮工作,将句柄添加到工作队列中。
|
||||
* @param handle: 目标句柄结构体。
|
||||
* @retval 0: 成功。-1: 已存在。
|
||||
*/
|
||||
int button_start(struct Button *handle)
|
||||
{
|
||||
struct Button *target = head_handle;
|
||||
while (target)
|
||||
{
|
||||
if (target == handle)
|
||||
return -1; // already exist.
|
||||
target = target->next;
|
||||
}
|
||||
handle->next = head_handle;
|
||||
head_handle = handle;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 停止按钮工作,从工作队列中移除句柄。
|
||||
* @param handle: 目标句柄结构体。
|
||||
* @retval None
|
||||
*/
|
||||
void button_stop(struct Button *handle)
|
||||
{
|
||||
struct Button **curr;
|
||||
for (curr = &head_handle; *curr;)
|
||||
{
|
||||
struct Button *entry = *curr;
|
||||
if (entry == handle)
|
||||
{
|
||||
*curr = entry->next;
|
||||
// free(entry);
|
||||
return;
|
||||
}
|
||||
else
|
||||
curr = &entry->next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 后台计时,定时器重复调用间隔为5ms。
|
||||
* @param None.
|
||||
* @retval None
|
||||
*/
|
||||
void button_ticks()
|
||||
{
|
||||
struct Button *target;
|
||||
for (target = head_handle; target; target = target->next)
|
||||
{
|
||||
button_handler(target);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
/***
|
||||
* @Author:
|
||||
* @Date: 2023-07-04 08:26:12
|
||||
* @LastEditors: xxx
|
||||
* @LastEditTime: 2023-08-09 22:49:14
|
||||
* @Description: btn是一个小巧简单易用的事件驱动型按键驱动模块,可无限量扩展按键,按键事件的回调异步处理方式
|
||||
* @email:
|
||||
* @Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
/**
|
||||
使用方法:
|
||||
1.先申请一个按键结构
|
||||
|
||||
struct Button button1;
|
||||
2.初始化按键对象,绑定按键的GPIO电平读取接口read_button_pin() ,后一个参数设置有效触发电平
|
||||
|
||||
button_init(&button1, read_button_pin, 0, 0);
|
||||
3.注册按键事件
|
||||
|
||||
button_attach(&button1, SINGLE_CLICK, Callback_SINGLE_CLICK_Handler);
|
||||
button_attach(&button1, DOUBLE_CLICK, Callback_DOUBLE_Click_Handler);
|
||||
...
|
||||
4.启动按键
|
||||
|
||||
button_start(&button1);
|
||||
5.设置一个5ms间隔的定时器循环调用后台处理函数
|
||||
|
||||
while(1) {
|
||||
...
|
||||
if(timer_ticks == 5) {
|
||||
timer_ticks = 0;
|
||||
|
||||
button_ticks();
|
||||
}
|
||||
}
|
||||
*/
|
||||
#ifndef _BTN_H_
|
||||
#define _BTN_H_
|
||||
|
||||
#ifdef STM32
|
||||
#include "lib.h"
|
||||
#else
|
||||
#include "stdint.h"
|
||||
#include "string.h"
|
||||
#endif
|
||||
|
||||
// 根据您的需求修改常量。
|
||||
#define TICKS_INTERVAL 10 // 按钮扫描间隔,单位ms
|
||||
#define DEBOUNCE_TICKS 20 / TICKS_INTERVAL // 按键去抖动时间,单位ms
|
||||
#define SHORT_TICKS (100 / TICKS_INTERVAL) // 短按时间阈值,单位ms
|
||||
#define LONG_TICKS (500 / TICKS_INTERVAL) // 长按时间阈值,单位ms
|
||||
|
||||
typedef void (*BtnCallback)(void *);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ACTIVE_LEVEL_LOW = 0, // 低电平有效
|
||||
ACTIVE_LEVEL_HIGH, // 高电平有效
|
||||
} active_level_e;
|
||||
|
||||
/***
|
||||
* @brief
|
||||
事件 说明
|
||||
PRESS_DOWN 按键按下,每次按下都触发
|
||||
PRESS_UP 按键弹起,每次松开都触发
|
||||
PRESS_REPEAT 重复按下触发,变量repeat计数连击次数
|
||||
SINGLE_CLICK 单击按键事件
|
||||
DOUBLE_CLICK 双击按键事件
|
||||
LONG_PRESS_START 达到长按时间阈值时触发一次
|
||||
LONG_PRESS_HOLD 长按期间一直触发
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
PRESS_DOWN = 0, // 按下
|
||||
PRESS_UP, // 弹起
|
||||
PRESS_REPEAT, // 重复按下
|
||||
SINGLE_CLICK, // 单击
|
||||
DOUBLE_CLICK, // 双击
|
||||
LONG_PRESS_START, // 长按开始
|
||||
LONG_PRESS_HOLD, // 长按保持
|
||||
number_of_event, // 事件数量
|
||||
NONE_PRESS // 无按键
|
||||
} PressEvent;
|
||||
|
||||
/***
|
||||
* @brief 按钮设备结构体
|
||||
*/
|
||||
typedef struct Button
|
||||
{
|
||||
uint16_t ticks; // 计时器
|
||||
uint8_t repeat : 4; // 重复计数
|
||||
uint8_t event : 4; // 事件类型
|
||||
uint8_t state : 3; // 状态
|
||||
uint8_t debounce_cnt : 3; // 去抖计数
|
||||
uint8_t active_level : 1; // 激活电平
|
||||
uint8_t button_level : 1; // 按钮电平
|
||||
uint8_t button_id; // 按钮ID
|
||||
uint8_t button_id_reverse; // 按钮ID反转
|
||||
uint8_t (*hal_button_Level)(uint8_t button_id_); // 获取按钮引脚电平函数
|
||||
BtnCallback cb[number_of_event]; // 回调函数数组
|
||||
struct Button *next; // 下一个按钮句柄
|
||||
} Button;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 初始化按钮设备
|
||||
* @param handle: 按钮句柄结构体
|
||||
* @param pin_level: 获取按钮引脚电平函数
|
||||
* @param active_level: 按钮激活电平
|
||||
* @param button_id: 按钮ID
|
||||
* @retval 0: 成功
|
||||
* @retval -1: 失败
|
||||
*/
|
||||
void button_init(struct Button *handle, uint8_t (*pin_level)(uint8_t), active_level_e active_level, uint8_t button_id, uint8_t button_id_reverse);
|
||||
|
||||
/**
|
||||
* @brief 附加按钮事件处理函数
|
||||
* @param handle: 按钮句柄结构体
|
||||
* @param event: 按钮事件
|
||||
* @param cb: 回调函数
|
||||
* @retval 0: 成功
|
||||
* @retval -1: 失败
|
||||
*/
|
||||
void button_attach(struct Button *handle, PressEvent event, BtnCallback cb);
|
||||
|
||||
/**
|
||||
* @brief 获取按钮事件
|
||||
* @param handle: 按钮句柄结构体
|
||||
* @retval 按钮事件
|
||||
*/
|
||||
PressEvent get_button_event(struct Button *handle);
|
||||
|
||||
/**
|
||||
* @brief 启动按钮工作
|
||||
* @param handle: 按钮句柄结构体
|
||||
* @retval 0: 成功
|
||||
* @retval -1: 已存在
|
||||
*/
|
||||
int button_start(struct Button *handle);
|
||||
|
||||
/**
|
||||
* @brief 停止按钮工作
|
||||
* @param handle: 按钮句柄结构体
|
||||
* @retval None
|
||||
*/
|
||||
void button_stop(struct Button *handle);
|
||||
|
||||
/**
|
||||
* @brief 后台计时,定时器重复调用间隔为5ms
|
||||
* @param None
|
||||
* @retval None
|
||||
*/
|
||||
void button_ticks(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
/*
|
||||
* @Author:
|
||||
* @Date: 2023-04-11 18:31:07
|
||||
* @LastEditors: xxx
|
||||
* @LastEditTime: 2023-08-25 11:27:12
|
||||
* @Description:
|
||||
* email:
|
||||
* Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include "delay.h"
|
||||
|
||||
#if USE_OS == 1
|
||||
#include "os.h"
|
||||
// 确保包含了 vPortSetupTimerInterrupt 函数的声明
|
||||
extern void vPortSetupTimerInterrupt(void);
|
||||
#endif
|
||||
|
||||
// static uint16_t g_fac_ms = 0; // ms延时倍乘数,在os下,代表每个节拍的ms数
|
||||
static uint32_t g_fac_us = 0; /* us延时倍乘数 */
|
||||
|
||||
/**
|
||||
* @brief 初始化延迟函数
|
||||
* @param sysclk: 系统时钟频率, 即CPU频率(rcc_c_ck)
|
||||
* @retval 无
|
||||
*/
|
||||
void delay_init(uint16_t sysclk)
|
||||
{
|
||||
#if SYS_SUPPORT_OS /* 如果需要支持OS */
|
||||
uint32_t reload;
|
||||
#endif
|
||||
g_fac_us = sysclk; /* 不论是否使用OS,g_fac_us都需要使用 */
|
||||
// Remove the redundant assignment statement
|
||||
#if SYS_SUPPORT_OS /* 如果需要支持OS. */
|
||||
reload = sysclk; /* 每秒钟的计数次数 单位为M */
|
||||
reload *= 1000000 / configTICK_RATE_HZ; /* 根据delay_ostickspersec设定溢出时间,reload为24位
|
||||
* 寄存器,最大值:16777216,在168M下,约合0.099s左右
|
||||
*/
|
||||
g_fac_ms = 1000 / configTICK_RATE_HZ; // 代表OS可以延时的最少单位
|
||||
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; /* 开启SYSTICK中断 */
|
||||
SysTick->LOAD = reload; /* 每1/delay_ostickspersec秒中断一次 */
|
||||
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; /* 开启SYSTICK */
|
||||
#endif
|
||||
}
|
||||
|
||||
#if USE_OS == 1
|
||||
void delay_us(uint32_t nus)
|
||||
{
|
||||
uint32_t ticks;
|
||||
uint32_t told, tnow, reload, tcnt = 0;
|
||||
|
||||
if ((0x0001 & (SysTick->CTRL)) == 0)
|
||||
{
|
||||
// 定时器未工作,初始化定时器
|
||||
vPortSetupTimerInterrupt();
|
||||
}
|
||||
|
||||
reload = SysTick->LOAD; // 获取重装载寄存器值
|
||||
ticks = nus * g_fac_us; // 计算延时所需的SysTick计数
|
||||
|
||||
vTaskSuspendAll(); // 阻止OS调度,防止打断us延时
|
||||
told = SysTick->VAL; // 获取当前数值寄存器值(开始时数值)
|
||||
|
||||
while (1)
|
||||
{
|
||||
tnow = SysTick->VAL; // 获取当前数值寄存器值
|
||||
if (tnow != told)
|
||||
{ // 当前值不等于开始值说明已在计数
|
||||
if (tnow < told)
|
||||
{
|
||||
tcnt += told - tnow; // 计数值 = 开始值 - 当前值
|
||||
}
|
||||
else
|
||||
{
|
||||
tcnt += reload - tnow + told; // 计数值 = 重装载值 - 当前值 + 开始值
|
||||
}
|
||||
told = tnow; // 更新开始值
|
||||
if (tcnt >= ticks)
|
||||
{
|
||||
break; // 时间超过/等于要延迟的时间,则退出
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xTaskResumeAll(); // 恢复OS调度
|
||||
}
|
||||
|
||||
#else
|
||||
/**
|
||||
* @brief 延时nus
|
||||
* @param nus: 要延时的us数.
|
||||
* @note 注意: nus的值,不要大于34952us(最大值即2^24 / g_fac_us @g_fac_us = 168)
|
||||
* @retval 无
|
||||
*/
|
||||
void delay_us(uint32_t nus)
|
||||
{
|
||||
uint32_t ticks;
|
||||
uint32_t told, tnow, tcnt = 0;
|
||||
uint32_t reload = SysTick->LOAD; /* LOAD的值 */
|
||||
ticks = nus * g_fac_us; /* 需要的节拍数 */
|
||||
told = SysTick->VAL; /* 刚进入时的计数器值 */
|
||||
while (1)
|
||||
{
|
||||
tnow = SysTick->VAL;
|
||||
if (tnow != told)
|
||||
{
|
||||
if (tnow < told)
|
||||
{
|
||||
tcnt += told - tnow; /* 这里注意一下SYSTICK是一个递减的计数器就可以了 */
|
||||
}
|
||||
else
|
||||
{
|
||||
tcnt += reload - tnow + told;
|
||||
}
|
||||
told = tnow;
|
||||
if (tcnt >= ticks)
|
||||
{
|
||||
// __NOP();
|
||||
break; /* 时间超过/等于要延迟的时间,则退出 */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 对指定的硬件定时器进行微秒级延时
|
||||
*
|
||||
* 使用指定的硬件定时器进行延时操作,延时时间为微秒级。
|
||||
*
|
||||
* @param timer_us 指向硬件定时器的指针
|
||||
* @param us 需要延时的微秒数
|
||||
*/
|
||||
void delay_hardware_us(TIM_TypeDef *timer_us, uint32_t us)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 延迟指定微秒数的硬件延迟函数
|
||||
*
|
||||
* 使用指定的定时器实现微秒级的硬件延迟。
|
||||
*
|
||||
* @param timer_us 指向定时器的指针,用于实现延迟功能
|
||||
* @param us 需要延迟的微秒数
|
||||
*/
|
||||
void delay_hardware_ms(TIM_TypeDef *timer_us, uint32_t ms)
|
||||
{
|
||||
while (ms--)
|
||||
{
|
||||
delay_hardware_us(timer_us, 1000); // 每毫秒延时1000微秒
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 延时nms
|
||||
* @param nms: 要延时的ms数 (0< nms <= 65535)
|
||||
* @retval 无
|
||||
*/
|
||||
void delay_ms(uint16_t nms)
|
||||
{
|
||||
uint32_t repeat = nms / 30; /* 这里用30,是考虑到可能有超频应用 */
|
||||
uint32_t remain = nms % 30;
|
||||
|
||||
while (repeat)
|
||||
{
|
||||
delay_us(30 * 1000); /* 利用delay_us 实现 1000ms 延时 */
|
||||
repeat--;
|
||||
}
|
||||
|
||||
if (remain)
|
||||
{
|
||||
delay_us(remain * 1000); /* 利用delay_us, 把尾数延时(remain ms)给做了 */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 延时函数,用于模拟硬件延时。
|
||||
* @param {uint32_t} ticks
|
||||
* @return {*}
|
||||
* @note: 请注意,这个函数仅用于模拟硬件延时,实际应用中可能需要使用其他延时函数,如HAL_Delay或rt_delay。
|
||||
*/
|
||||
void delay_tick(uint32_t ticks)
|
||||
{
|
||||
while (ticks--)
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/***
|
||||
* @Author:
|
||||
* @Date: 2023-04-11 18:31:07
|
||||
* @LastEditors: xxx
|
||||
* @LastEditTime: 2023-04-11 18:31:20
|
||||
* @Description:
|
||||
* @email:
|
||||
* @Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef __DELAY_H
|
||||
#define __DELAY_H
|
||||
|
||||
#include "sys.h"
|
||||
#include "tims.h"
|
||||
|
||||
#define USE_OS 0
|
||||
|
||||
void delay_init(uint16_t sysclk); /* 初始化延迟函数 */
|
||||
void delay_ms(uint16_t nms); /* 延时nms */
|
||||
void delay_us(uint32_t nus); /* 延时nus */
|
||||
void delay_hardware_us(TIM_TypeDef *timer_us, uint32_t us); /* 硬件延时us */
|
||||
void delay_hardware_ms(TIM_TypeDef *timer_us, uint32_t ms); /* 硬件延时ms */
|
||||
void delay_tick(uint32_t ticks); /* 延时ticks */
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
#include "dac161p997.h"
|
||||
#include "delay.h"
|
||||
static dac161p997_t _handle;
|
||||
|
||||
static void _delay_us(uint32_t us)
|
||||
{
|
||||
delay_hardware_us(_handle.timer_us, us);
|
||||
}
|
||||
|
||||
static void dac161p997_output_0()
|
||||
{
|
||||
_handle.io->set(*_handle.io);
|
||||
_delay_us(DUTY_CYCLE_25);
|
||||
_handle.io->reset(*_handle.io);
|
||||
_delay_us(DUTY_CYCLE_75);
|
||||
}
|
||||
|
||||
static void dac161p997_output_1()
|
||||
{
|
||||
_handle.io->set(*_handle.io);
|
||||
_delay_us(DUTY_CYCLE_75);
|
||||
_handle.io->reset(*_handle.io);
|
||||
_delay_us(DUTY_CYCLE_25);
|
||||
}
|
||||
|
||||
static void dac161p997_output_d()
|
||||
{
|
||||
_handle.io->set(*_handle.io);
|
||||
_delay_us(DUTY_CYCLE_50);
|
||||
_handle.io->reset(*_handle.io);
|
||||
_delay_us(DUTY_CYCLE_50);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 输出DAC161符号
|
||||
*
|
||||
* 根据传入的符号类型,调用相应的DAC161输出函数。
|
||||
*
|
||||
* @param sym 符号类型,可以是ZERO_SYM、ONE_SYM或其他值。
|
||||
*/
|
||||
static void dac161p997_output_symbol(uint8_t sym)
|
||||
{
|
||||
switch (sym)
|
||||
{
|
||||
case ZERO_SYM:
|
||||
dac161p997_output_0();
|
||||
break;
|
||||
case ONE_SYM:
|
||||
dac161p997_output_1();
|
||||
break;
|
||||
default:
|
||||
dac161p997_output_d();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向DAC161写入寄存器
|
||||
*
|
||||
* 向DAC161写入一个16位的数据,并附加一个8位的标签。
|
||||
* 芯片会回复一个应答信号,此处忽略应答信号。可以通过设置寄存器关闭应答信号。
|
||||
* @param data 要写入的数据,16位。
|
||||
* @param tag 附加的标签,8位。tag = 0时,写DAC寄存器;tag = 1时,写配置寄存器。
|
||||
*/
|
||||
void dac161p997_swif_write_reg(uint16_t data, uint8_t tag)
|
||||
{
|
||||
uint8_t plow, phigh;
|
||||
uint16_t i, tmp;
|
||||
|
||||
/* compute parity low */
|
||||
tmp = data & 0x00ff; // get least significant byte
|
||||
for (plow = 0; tmp != 0; tmp >>= 1)
|
||||
{
|
||||
if (tmp & 0x1) // test if lsb is 1
|
||||
plow++; // count number of bits equal to 1
|
||||
}
|
||||
|
||||
if (plow & 0x1) // check if number of 1s is odd
|
||||
plow = 0; // set even parity
|
||||
else
|
||||
plow = 1; // else set odd parity
|
||||
|
||||
/* compute parity high */
|
||||
tmp = (data & 0xff00) >> 8; // get most significant byte
|
||||
for (phigh = 0; tmp != 0; tmp >>= 1)
|
||||
{
|
||||
if (tmp & 0x1) // test if lsb is 1
|
||||
phigh++; // count number of bits equal to 1
|
||||
}
|
||||
|
||||
if (phigh & 0x1) // check if number of 1s is odd
|
||||
phigh = 0; // set even parity
|
||||
else
|
||||
phigh = 1; // set odd parity
|
||||
|
||||
phigh = phigh ^ tag; // parity high is for high slice = tag + 1 byte
|
||||
|
||||
dac161p997_output_symbol(IDLE_SYM); // Frame start: send an idle symbol first
|
||||
dac161p997_output_symbol(tag);
|
||||
|
||||
tmp = data;
|
||||
for (i = 0; i < 16; i++) // send 16 data bits msb to lsb
|
||||
{
|
||||
if (tmp & 0x8000)
|
||||
{
|
||||
dac161p997_output_symbol(ONE_SYM); // send 1
|
||||
}
|
||||
else
|
||||
{
|
||||
dac161p997_output_symbol(ZERO_SYM); // send 0
|
||||
}
|
||||
|
||||
tmp = tmp << 1; // move next data bit to msb
|
||||
}
|
||||
|
||||
dac161p997_output_symbol(phigh); // send parity high bit
|
||||
dac161p997_output_symbol(plow); // send parity low bit
|
||||
|
||||
dac161p997_output_symbol(IDLE_SYM); // send idle
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置DAC161输出电流
|
||||
*
|
||||
* 通过向DAC161写入指定的电流值来设置其输出电流,输出电流持续时间100ms。
|
||||
*
|
||||
* @param current 要设置的电流值
|
||||
*/
|
||||
void dac161p997_output_current(float32 current)
|
||||
{
|
||||
uint16_t adc = (uint16_t)(current * DAC161P997_CURRENT_SLOPE);
|
||||
dac161p997_swif_write_reg(adc, DACCODE_WRITE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化DAC161设备
|
||||
*
|
||||
* 此函数用于初始化DAC161设备,进行必要的配置,以便设备正常工作。
|
||||
* 写寄存器之前要先解锁,具体配置寄存器根据需要配置的寄存器而定
|
||||
* 具体步骤包括:
|
||||
* 1. 解锁DAC161的配置寄存器
|
||||
* 2. 配置DAC161的错误下限寄存器
|
||||
* 3. 锁定DAC161的配置寄存器
|
||||
*/
|
||||
void dac161p997_init(TIM_TypeDef *timer_us)
|
||||
{
|
||||
DBG_ASSERT(timer_us != NULL __DBG_LINE);
|
||||
_handle.timer_us = timer_us;
|
||||
ENABLE_TIM_COUNT(_handle.timer_us);
|
||||
_handle.io = gpio_create(DAC161P997_IO_PORT, DAC161P997_IO_PIN);
|
||||
dac161p997_swif_write_reg(DAC161P997_LCK_REG + DAC161P997_LCK_REG_UNLOCK, CONFIG_WRITE);
|
||||
dac161p997_swif_write_reg(DAC161P997_CONFIG2_REG, CONFIG_WRITE);
|
||||
dac161p997_swif_write_reg(DAC161P997_LCK_REG + DAC161P997_LCK_REG_LOCK, CONFIG_WRITE);
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#ifndef __DAC161P997_H__
|
||||
#define __DAC161P997_H__
|
||||
#include "main.h"
|
||||
#include "gpios.h"
|
||||
|
||||
#define DAC161P997_IO_PORT (DAC161P997_GPIO_Port)
|
||||
#define DAC161P997_IO_PIN (DAC161P997_Pin)
|
||||
|
||||
#define DAC161P997_CURRENT_SLOPE 2730.625f // adc = (current / 24) * 0xffff
|
||||
|
||||
// Symbol Periods
|
||||
#define DUTY_CYCLE_100 (1000U) // (CPU_CLK / BAUD_RATE)
|
||||
#define DUTY_CYCLE_75 (DUTY_CYCLE_100 * 0.75f)
|
||||
#define DUTY_CYCLE_50 (DUTY_CYCLE_100 * 0.50f)
|
||||
#define DUTY_CYCLE_25 (DUTY_CYCLE_100 * 0.25f)
|
||||
|
||||
/************************************************************
|
||||
* TI DAC161P997 REGISTER SET ADDRESSES
|
||||
************************************************************/
|
||||
#define DAC161P997_LCK_REG (0x0000)
|
||||
#define DAC161P997_CONFIG1_REG (0x0100)
|
||||
#define DAC161P997_CONFIG2_REG (0x0200)
|
||||
#define DAC161P997_CONFIG3_REG (0x0300)
|
||||
#define DAC161P997_ERR_LOW_REG (0x0400)
|
||||
#define DAC161P997_ERR_HIGH_REG (0x0500)
|
||||
|
||||
// TI DAC161P997 Register Bits
|
||||
#define DAC161P997_LCK_REG_LOCK (0x00AA) // any value other than 0x95
|
||||
#define DAC161P997_LCK_REG_UNLOCK (0x0095)
|
||||
#define DAC161P997_CONFIG1_REG_RST (0x0001)
|
||||
#define DAC161P997_CONFIG1_REG_NOP (0 * 0x08u)
|
||||
#define DAC161P997_CONFIG1_REG_SET_ERR (1 * 0x08u)
|
||||
#define DAC161P997_CONFIG1_REG_CLEAR_ERR (2 * 0x08u)
|
||||
#define DAC161P997_CONFIG1_REG_NOP3 (3 * 0x08u)
|
||||
#define DAC161P997_CONFIG2_REG_LOOP (0x0001)
|
||||
#define DAC161P997_CONFIG2_REG_CHANNEL (0x0002)
|
||||
#define DAC161P997_CONFIG2_REG_PARITY (0x0004)
|
||||
#define DAC161P997_CONFIG2_REG_FRAME (0x0008)
|
||||
#define DAC161P997_CONFIG2_REG_ACK_EN (0x0010)
|
||||
#define DAC161P997_CONFIG3_REG_RX_ERR_CNT_16 (0x000F)
|
||||
#define DAC161P997_CONFIG3_REG_RX_ERR_CNT_8 (0x0007)
|
||||
#define DAC161P997_CONFIG3_REG_RX_ERR_CNT_1 (0x0000)
|
||||
|
||||
// Tags
|
||||
#define DACCODE_WRITE (0x00)
|
||||
#define CONFIG_WRITE (0x01)
|
||||
|
||||
// Valid Symbols
|
||||
#define ZERO_SYM (0x00)
|
||||
#define ONE_SYM (0x01)
|
||||
#define IDLE_SYM (0x02)
|
||||
#define STATIC_LOW_SYM (0x03)
|
||||
|
||||
// DAC Codes for different currents
|
||||
#define DACCODE_0mA (0x0000)
|
||||
#define DACCODE_4mA (0x2AAA)
|
||||
#define DACCODE_8mA (0x5555)
|
||||
#define DACCODE_12mA (0x7FFF)
|
||||
#define DACCODE_16mA (0xAAAA)
|
||||
#define DACCODE_20mA (0xD555)
|
||||
#define DACCODE_24mA (0xFFFF)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
DAC161P997_IDLE_SYM,
|
||||
|
||||
} dac161p997_e;
|
||||
typedef struct
|
||||
{
|
||||
TIM_TypeDef *timer_us;
|
||||
gpio_t *io;
|
||||
uint8_t count;
|
||||
} dac161p997_t;
|
||||
|
||||
void dac161p997_output_0(void);
|
||||
void dac161p997_output_1(void);
|
||||
void dac161p997_output_d(void);
|
||||
void dac161p997_output_symbol(uint8_t sym);
|
||||
void dac161p997_swif_write_reg(uint16_t data, uint8_t tag);
|
||||
|
||||
extern void dac161p997_output_current(float32 current);
|
||||
extern void dac161p997_init(TIM_TypeDef *timer_us);
|
||||
#endif
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/**
|
||||
* @file eeprom_fm24.c
|
||||
* @author xxx
|
||||
* @date 2023-08-29 07:58:27
|
||||
* @brief 用于实现FM24 EEPROM相关的读写操作
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#include "entity.h"
|
||||
#include "board.h"
|
||||
#include "eeprom_fm24.h"
|
||||
#include "delay.h"
|
||||
|
||||
#define FM24_SPI SPI1
|
||||
|
||||
#define EEPROM_FM24_CS_PORT EE3_CS_GPIO_Port
|
||||
#define EEPROM_FM24_CS_PIN EE3_CS_Pin
|
||||
#define EEPROM_FM24_MOSI_PORT SPI_MOSI_GPIO_Port
|
||||
#define EEPROM_FM24_MOSI_PIN SPI_MOSI_Pin
|
||||
#define EEPROM_FM24_MISO_PORT SPI_MISO_GPIO_Port
|
||||
#define EEPROM_FM24_MISO_PIN SPI_MISO_Pin
|
||||
#define EEPROM_FM24_SCK_PORT SPI_CLK_GPIO_Port
|
||||
#define EEPROM_FM24_SCK_PIN SPI_CLK_Pin
|
||||
|
||||
static fm24_t _eeprom_fm24;
|
||||
|
||||
void eeprom_fm24_init(void)
|
||||
{
|
||||
spi_gpio_group_t gpios;
|
||||
spi_normal_config_t cfg;
|
||||
osel_memset((uint8_t *)&cfg, 0, sizeof(spi_normal_config_t));
|
||||
// 创建CS引脚
|
||||
gpios.cs = gpio_create(EEPROM_FM24_CS_PORT, EEPROM_FM24_CS_PIN);
|
||||
gpios.mosi = gpio_create(EEPROM_FM24_MOSI_PORT, EEPROM_FM24_MOSI_PIN);
|
||||
gpios.sck = gpio_create(EEPROM_FM24_SCK_PORT, EEPROM_FM24_SCK_PIN);
|
||||
gpios.miso = gpio_create(EEPROM_FM24_MISO_PORT, EEPROM_FM24_MISO_PIN);
|
||||
gpios.rst = gpio_create(NULL, 0);
|
||||
gpios.rdy = gpio_create(NULL, 0);
|
||||
|
||||
// 创建SPI对象
|
||||
eeprom_fm24_get()->spi = spi_create(SPI_TYPE_NORMAL, gpios, 0);
|
||||
DBG_ASSERT(eeprom_fm24_get() != NULL __DBG_LINE);
|
||||
cfg.cmd_rdsr = FM24_CMD_RDSR;
|
||||
cfg.cmd_wrsr = FM24_CMD_WRSR;
|
||||
cfg.cmd_wren = FM24_CMD_WREN;
|
||||
cfg.cmd_wrdi = FM24_CMD_WRDI;
|
||||
cfg.cmd_write = FM24_CMD_WRITE;
|
||||
cfg.cmd_read = FM24_CMD_READ;
|
||||
cfg.dummy_byte = FM24_DUMMY_BYTE;
|
||||
cfg.address_bytes = 2;
|
||||
cfg.page_size = FM24_PAGE_SIZE;
|
||||
cfg.total_size = FM24_SIZE;
|
||||
cfg.continuous_write = FALSE;
|
||||
osel_memcpy((uint8_t *)&eeprom_fm24_get()->spi->cfg, (uint8_t *)&cfg, sizeof(spi_normal_config_t));
|
||||
// 使能SPI
|
||||
eeprom_fm24_get()->spi->interface.hardware_enable(eeprom_fm24_get()->spi, FM24_SPI);
|
||||
// 这里需要复位下SPI,否则读出的数据不对
|
||||
eeprom_fm24_get()->spi->interface.u.normal.spi_reset(eeprom_fm24_get()->spi);
|
||||
|
||||
eeprom_fm24_write_protection_close();
|
||||
// eeprom_fm24_test();
|
||||
}
|
||||
|
||||
fm24_t *eeprom_fm24_get(void)
|
||||
{
|
||||
return &_eeprom_fm24;
|
||||
}
|
||||
|
||||
void eeprom_fm24_dinit(void)
|
||||
{
|
||||
LL_SPI_Disable(FM24_SPI);
|
||||
GPIO_SET_ANALOG(eeprom_fm24_get()->spi->gpios.mosi->port, eeprom_fm24_get()->spi->gpios.mosi->pin);
|
||||
GPIO_SET_ANALOG(eeprom_fm24_get()->spi->gpios.miso->port, eeprom_fm24_get()->spi->gpios.miso->pin);
|
||||
GPIO_SET_ANALOG(eeprom_fm24_get()->spi->gpios.sck->port, eeprom_fm24_get()->spi->gpios.sck->pin);
|
||||
GPIO_SET_ANALOG(eeprom_fm24_get()->spi->gpios.cs->port, eeprom_fm24_get()->spi->gpios.cs->pin);
|
||||
}
|
||||
|
||||
void eeprom_fm24_enable(void)
|
||||
{
|
||||
uint16_t count = 100;
|
||||
LL_SPI_Enable(FM24_SPI);
|
||||
// 判断SPI是否使能成功
|
||||
while (LL_SPI_IsEnabled(FM24_SPI) != 1)
|
||||
{
|
||||
if (count-- == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void eeprom_fm24_disable(void)
|
||||
{
|
||||
uint16_t count = 100;
|
||||
LL_SPI_Disable(FM24_SPI);
|
||||
// 判断SPI是否关闭成功
|
||||
while (LL_SPI_IsEnabled(FM24_SPI) != 0)
|
||||
{
|
||||
if (count-- == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭EEPROM FM24的写保护
|
||||
*
|
||||
* 此函数用于关闭EEPROM FM24的写保护功能,允许对其进行写操作。
|
||||
*
|
||||
* 调用此函数前,应确保EEPROM FM24的SPI接口已正确初始化。
|
||||
*/
|
||||
void eeprom_fm24_write_protection_close(void)
|
||||
{
|
||||
DBG_ASSERT(eeprom_fm24_get()->spi != NULL __DBG_LINE);
|
||||
eeprom_fm24_enable();
|
||||
eeprom_fm24_get()->spi->interface.u.normal.spi_write_reg(eeprom_fm24_get()->spi, eeprom_fm24_get()->spi->cfg.cmd_wrsr, 0);
|
||||
eeprom_fm24_disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取EEPROM FM24的写保护状态
|
||||
*
|
||||
* 获取EEPROM FM24的写保护状态。
|
||||
*
|
||||
* @return BOOL 返回TRUE表示没有写保护,返回FALSE表示有写保护
|
||||
*/
|
||||
BOOL eeprom_fm24_write_protection_state(void)
|
||||
{
|
||||
DBG_ASSERT(eeprom_fm24_get()->spi != NULL __DBG_LINE);
|
||||
eeprom_fm24_enable();
|
||||
eeprom_fm24_get()->write_protection.data = eeprom_fm24_get()->spi->interface.u.normal.spi_read_reg(eeprom_fm24_get()->spi, eeprom_fm24_get()->spi->cfg.cmd_rdsr);
|
||||
eeprom_fm24_disable();
|
||||
|
||||
if (eeprom_fm24_get()->write_protection.bits.bp0 == 1 ||
|
||||
eeprom_fm24_get()->write_protection.bits.bp1 == 1 ||
|
||||
eeprom_fm24_get()->write_protection.bits.wpen == 1)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL eeprom_fm24_write(uint32_t write_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
if (length == 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
// 开启和关闭SPI对读写实时性有影响
|
||||
eeprom_fm24_enable();
|
||||
ret = eeprom_fm24_get()->spi->interface.u.normal.spi_write(eeprom_fm24_get()->spi, write_addr, data, length);
|
||||
eeprom_fm24_disable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOL eeprom_fm24_read(uint32_t read_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
if (length == 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
// 开启和关闭SPI对读写实时性有影响
|
||||
eeprom_fm24_enable();
|
||||
ret = eeprom_fm24_get()->spi->interface.u.normal.spi_read(eeprom_fm24_get()->spi, read_addr, data, length);
|
||||
eeprom_fm24_disable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOL eeprom_fm24_test(void)
|
||||
{
|
||||
const uint8_t buf_size = 5;
|
||||
uint16_t test_address = FM24_TEST_PAGE * FM24_PAGE_SIZE;
|
||||
uint8_t buf[buf_size];
|
||||
uint8_t rbuf[buf_size];
|
||||
osel_memset(buf, 0, buf_size);
|
||||
osel_memset(rbuf, 0, buf_size);
|
||||
buf[0] = 0xD5;
|
||||
buf[1] = 0xC8;
|
||||
buf[2] = 0x00;
|
||||
buf[3] = 0x01;
|
||||
buf[4] = 0x02;
|
||||
buf[buf_size - 1] = 0xfe;
|
||||
eeprom_fm24_write(test_address, buf, buf_size);
|
||||
__NOP();
|
||||
eeprom_fm24_read(test_address, rbuf, buf_size);
|
||||
if (osel_memcmp(buf, rbuf, buf_size) == 0)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取FM24 EEPROM的状态
|
||||
*
|
||||
* 获取FM24 EEPROM的当前状态。该函数始终返回TRUE,表示EEPROM正常工作。
|
||||
*
|
||||
* @return BOOL 始终返回TRUE
|
||||
*/
|
||||
BOOL eeprom_fm24_status_get(void)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* @file eeprom_fm24.h
|
||||
* @author xxx
|
||||
* @date 2023-08-30 14:05:55
|
||||
* @brief FM24系列EEPROM驱动 https://zhuanlan.zhihu.com/p/598934638
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef __EEPROM_FM24_H__
|
||||
#define __EEPROM_FM24_H__
|
||||
#include "main.h"
|
||||
#include "spis.h"
|
||||
// High-endurance 100 trillion (1014) read/writes
|
||||
|
||||
//========在此设定芯片地址=============
|
||||
|
||||
#define W_ADD_COM 0xa0 // 写字节命令及器件地址(根据地址实际情况改变), 1010 A2 A1 A0 0
|
||||
|
||||
#define R_ADD_COM 0xa1 // 读命令字节及器件地址(根据地址实际情况改变), 1010 A2 A1 A0 1
|
||||
|
||||
//=======在此设定芯片型号, 1代表24C01; 16代表24C16; 512代表24C512
|
||||
|
||||
//=======在此设定芯片型号, 1代表24C01; 16代表24C16; 512代表24C512
|
||||
|
||||
#define e2prom 256 //
|
||||
|
||||
#if e2prom == 1
|
||||
#define FM24_PAGE_SIZE 16
|
||||
#define FM24_SIZE (128 * 8)
|
||||
#elif e2prom == 2
|
||||
#define FM24_PAGE_SIZE 16
|
||||
#define FM24_SIZE (256 * 8)
|
||||
#elif e2prom == 4
|
||||
#define FM24_PAGE_SIZE 32
|
||||
#define FM24_SIZE (512 * 8)
|
||||
#elif e2prom == 8
|
||||
#define FM24_PAGE_SIZE 64
|
||||
#define FM24_SIZE (1024 * 8)
|
||||
#elif e2prom == 16
|
||||
#define FM24_PAGE_SIZE 128
|
||||
#define FM24_SIZE (2048 * 8)
|
||||
#elif e2prom == 32
|
||||
#define FM24_PAGE_SIZE 128
|
||||
#define FM24_SIZE (4096 * 8)
|
||||
#elif e2prom == 64
|
||||
#define FM24_PAGE_SIZE 256
|
||||
#define FM24_SIZE (8192 * 8)
|
||||
#elif e2prom == 128
|
||||
#define FM24_PAGE_SIZE 256
|
||||
#define FM24_SIZE (16384)
|
||||
#elif e2prom == 256
|
||||
#define FM24_PAGE_SIZE 256
|
||||
#define FM24_SIZE (32768) // 32K 128页
|
||||
#elif e2prom == 512
|
||||
#define FM24_PAGE_SIZE 512
|
||||
#define FM24_SIZE (65536)
|
||||
#endif
|
||||
|
||||
#define FM24_CMD_RDSR 0x05 /*!< Read Status Register instruction */
|
||||
#define FM24_CMD_WRSR 0x01 /*!< Write Status Register instruction */
|
||||
|
||||
#define FM24_CMD_WREN 0x06 /*!< Write enable instruction */
|
||||
#define FM24_CMD_WRDI 0x04 /*!< Write disable instruction */
|
||||
|
||||
#define FM24_CMD_READ 0x03 /*!< Read from Memory instruction */
|
||||
#define FM24_CMD_WRITE 0x02 /*!< Write to Memory instruction */
|
||||
|
||||
#define FM24_DUMMY_BYTE 0x00 ///< 无用数据
|
||||
|
||||
#define FM24_TEST_PAGE 0 ///< 测试页地址
|
||||
|
||||
typedef union
|
||||
{
|
||||
uint8_t data;
|
||||
struct
|
||||
{
|
||||
uint8_t reserve1 : 1;
|
||||
uint8_t wel : 1; ///< Write enable latch
|
||||
uint8_t bp0 : 1; ///< Block protect 0
|
||||
uint8_t bp1 : 1; ///< Block protect 1
|
||||
uint8_t reserve2 : 3;
|
||||
uint8_t wpen : 1; ///< Write protect enable
|
||||
} bits;
|
||||
} fm24_write_protection_u;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
fm24_write_protection_u write_protection;
|
||||
spi_t *spi;
|
||||
} fm24_t;
|
||||
|
||||
/**
|
||||
* @brief Initializes the FM24 EEPROM module.
|
||||
*/
|
||||
extern void eeprom_fm24_init(void);
|
||||
|
||||
/**
|
||||
* @brief Deinitializes the FM24 EEPROM module.
|
||||
*/
|
||||
extern void eeprom_fm24_dinit(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the fm24_t handle of the FM24 EEPROM module.
|
||||
* @return The fm24_t handle of the FM24 EEPROM module.
|
||||
*/
|
||||
extern fm24_t *eeprom_fm24_get(void);
|
||||
|
||||
/**
|
||||
* @brief Enables the FM24 EEPROM module.
|
||||
*/
|
||||
extern void eeprom_fm24_enable(void);
|
||||
|
||||
/**
|
||||
* @brief Disables the FM24 EEPROM module.
|
||||
*/
|
||||
extern void eeprom_fm24_disable(void);
|
||||
|
||||
/**
|
||||
* @brief Reads data from the FM24 EEPROM module.
|
||||
* @param read_addr The starting address to read from.
|
||||
* @param data Pointer to the buffer to store the read data.
|
||||
* @param length The number of bytes to read.
|
||||
* @return TRUE if the read operation is successful, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL eeprom_fm24_read(uint32_t read_addr, uint8_t *data, uint16_t length);
|
||||
|
||||
/**
|
||||
* @brief Writes data to the FM24 EEPROM module.
|
||||
* @param write_addr The starting address to write to.
|
||||
* @param data Pointer to the data to be written.
|
||||
* @param length The number of bytes to write.
|
||||
* @return TRUE if the write operation is successful, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL eeprom_fm24_write(uint32_t write_addr, uint8_t *data, uint16_t length);
|
||||
|
||||
/**
|
||||
* @brief Closes the write protection of the FM24 EEPROM module.
|
||||
*/
|
||||
extern void eeprom_fm24_write_protection_close(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the write protection state of the FM24 EEPROM module.
|
||||
* @return TRUE if the FM24 EEPROM module is not write-protected, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL eeprom_fm24_write_protection_state(void);
|
||||
|
||||
/**
|
||||
* @brief Performs a test on the FM24 EEPROM module.
|
||||
*/
|
||||
extern BOOL eeprom_fm24_test(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the status of the FM24 EEPROM module.
|
||||
* @return TRUE if the FM24 EEPROM module is ready, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL eeprom_fm24_status_get(void);
|
||||
|
||||
#endif // __EEPROM_FM24_H__
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
#include "eeprom_lc02b.h"
|
||||
#include "delay.h"
|
||||
|
||||
#define W_ADD_COM 0xa8 // 写字节命令及器件地址(根据地址实际情况改变), 1010 A2 A1 A0 0
|
||||
#define R_ADD_COM 0xa9 // 读命令字节及器件地址(根据地址实际情况改变), 1010 A2 A1 A0 1
|
||||
#define PAGE_SIZE 8U
|
||||
#define SIZE 256U
|
||||
|
||||
#define EEPROM_LC02B_SDA_PORT I2C1_SDA_GPIO_Port
|
||||
#define EEPROM_LC02B_SDA_PIN I2C1_SDA_Pin
|
||||
#define EEPROM_LC02B_SCL_PORT I2C1_SCL_GPIO_Port
|
||||
#define EEPROM_LC02B_SCL_PIN I2C1_SCL_Pin
|
||||
|
||||
static i2c_t *_eeprom_24lc028bt_i2c;
|
||||
static TIM_TypeDef *_timer_us;
|
||||
|
||||
static void _delay_us(uint32_t us)
|
||||
{
|
||||
if (_timer_us != NULL)
|
||||
{
|
||||
delay_hardware_us(_timer_us, us);
|
||||
}
|
||||
else
|
||||
{
|
||||
delay_us(us);
|
||||
}
|
||||
}
|
||||
|
||||
void eeprom_lc02b_test(void)
|
||||
{
|
||||
#define TEST_SIZE 15
|
||||
uint16_t test_address = SIZE - TEST_SIZE;
|
||||
uint8_t buf[TEST_SIZE];
|
||||
for (uint8_t i = 0; i < TEST_SIZE; i++)
|
||||
{
|
||||
buf[i] = i + 1;
|
||||
}
|
||||
|
||||
eeprom_lc02b_write(test_address, buf, TEST_SIZE);
|
||||
_delay_us(10000);
|
||||
osel_memset(buf, 0, ARRAY_LEN(buf));
|
||||
eeprom_lc02b_read(test_address, buf, TEST_SIZE);
|
||||
|
||||
__NOP();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取EEPROM LC02B的状态
|
||||
*
|
||||
* 此函数用于获取EEPROM LC02B的当前状态。
|
||||
*
|
||||
* @return 如果EEPROM LC02B处于正常状态,则返回TRUE;否则返回FALSE。
|
||||
* 注意:在本实现中,总是返回TRUE,因为这是一个简单的示例函数。
|
||||
*/
|
||||
BOOL eeprom_lc02b_status_get(void)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief EEPROM LC02B初始化
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void eeprom_lc02b_init(TIM_TypeDef *timer_us)
|
||||
{
|
||||
if (timer_us != NULL)
|
||||
{
|
||||
_timer_us = timer_us;
|
||||
ENABLE_TIM_COUNT(_timer_us);
|
||||
}
|
||||
|
||||
i2c_gpio_group_t gpios;
|
||||
gpios.scl = gpio_create(EEPROM_LC02B_SCL_PORT, EEPROM_LC02B_SCL_PIN);
|
||||
gpios.sda = gpio_create(EEPROM_LC02B_SDA_PORT, EEPROM_LC02B_SDA_PIN);
|
||||
|
||||
_eeprom_24lc028bt_i2c = i2c_create(gpios, 10);
|
||||
// eeprom_lc02b_test();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief EEPROM LC02B反初始化
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void eeprom_lc02b_dinit(void)
|
||||
{
|
||||
GPIO_SET_ANALOG(EEPROM_LC02B_SDA_PORT, EEPROM_LC02B_SDA_PIN);
|
||||
GPIO_SET_ANALOG(EEPROM_LC02B_SCL_PORT, EEPROM_LC02B_SCL_PIN);
|
||||
}
|
||||
/**
|
||||
* @brief 写入数据
|
||||
* @param {uint32_t} write_addr
|
||||
* @param {uint8_t} *data
|
||||
* @param {uint16_t} length
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void eeprom_lc02b_write(uint32_t write_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
// 发送开始信号
|
||||
_eeprom_24lc028bt_i2c->interface.start(_eeprom_24lc028bt_i2c);
|
||||
// 发送写入地址命令
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, W_ADD_COM);
|
||||
// 等待写入地址命令响应
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
// 发送要写入的地址
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, (uint8_t)write_addr);
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
|
||||
// 循环写入数据
|
||||
|
||||
for (uint16_t i = 0; i < length; i++)
|
||||
{
|
||||
// 写入一个字节数据
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, *data++);
|
||||
// 等待响应
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
write_addr++;
|
||||
if (write_addr % PAGE_SIZE == 0)
|
||||
{
|
||||
_eeprom_24lc028bt_i2c->interface.stop(_eeprom_24lc028bt_i2c);
|
||||
_delay_us(10000); // 延时10ms,等待写入完成
|
||||
_eeprom_24lc028bt_i2c->interface.start(_eeprom_24lc028bt_i2c);
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, W_ADD_COM);
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, (uint8_t)write_addr);
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
}
|
||||
}
|
||||
// 写入完成,停止I2C总线
|
||||
_eeprom_24lc028bt_i2c->interface.stop(_eeprom_24lc028bt_i2c);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取数据
|
||||
* @param {uint32_t} read_addr
|
||||
* @param {uint8_t} *data
|
||||
* @param {uint16_t} length
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void eeprom_lc02b_read(uint32_t read_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
// 发送开始信号
|
||||
_eeprom_24lc028bt_i2c->interface.start(_eeprom_24lc028bt_i2c);
|
||||
// 发送写入地址命令
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, W_ADD_COM);
|
||||
// 等待写入地址命令响应
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
|
||||
// 发送要读取的地址
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, (uint8_t)read_addr);
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
|
||||
// 发送开始信号
|
||||
_eeprom_24lc028bt_i2c->interface.start(_eeprom_24lc028bt_i2c);
|
||||
// 发送读取地址命令
|
||||
_eeprom_24lc028bt_i2c->interface.write_byte(_eeprom_24lc028bt_i2c, R_ADD_COM);
|
||||
// 等待读取地址命令响应
|
||||
_eeprom_24lc028bt_i2c->interface.wait_ack(_eeprom_24lc028bt_i2c);
|
||||
// 循环读取数据
|
||||
for (uint16_t i = 0; i < length - 1; i++)
|
||||
{
|
||||
// 读取一个字节数据
|
||||
*data++ = _eeprom_24lc028bt_i2c->interface.read_byte(_eeprom_24lc028bt_i2c, TRUE);
|
||||
}
|
||||
*data++ = _eeprom_24lc028bt_i2c->interface.read_byte(_eeprom_24lc028bt_i2c, FALSE);
|
||||
// 停止I2C总线
|
||||
_eeprom_24lc028bt_i2c->interface.stop(_eeprom_24lc028bt_i2c);
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* @file eeprom_lc02b.h
|
||||
* @author xxx
|
||||
* @date 2023-12-27 14:44:02
|
||||
* @brief
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __EEPROM_LC02B_H
|
||||
#define __EEPROM_LC02B_H
|
||||
#include "main.h"
|
||||
#include "entity.h"
|
||||
|
||||
#define PRRESSURE_CALIBRATION_ADDRESS 0x00 ///< 压力校准地址
|
||||
|
||||
/**
|
||||
* @brief Initializes the LC02B EEPROM module.
|
||||
*/
|
||||
void eeprom_lc02b_init(TIM_TypeDef *timer_us);
|
||||
|
||||
/**
|
||||
* @brief Deinitializes the LC02B EEPROM module.
|
||||
*/
|
||||
void eeprom_lc02b_dinit(void);
|
||||
|
||||
/**
|
||||
* @brief Writes data to the LC02B EEPROM module.
|
||||
*
|
||||
* @param write_addr The starting address to write the data.
|
||||
* @param data The pointer to the data to be written.
|
||||
* @param length The length of the data to be written.
|
||||
*/
|
||||
void eeprom_lc02b_write(uint32_t write_addr, uint8_t *data, uint16_t length);
|
||||
|
||||
/**
|
||||
* @brief Reads data from the LC02B EEPROM module.
|
||||
*
|
||||
* @param read_addr The starting address to read the data.
|
||||
* @param data The pointer to store the read data.
|
||||
* @param length The length of the data to be read.
|
||||
*/
|
||||
void eeprom_lc02b_read(uint32_t read_addr, uint8_t *data, uint16_t length);
|
||||
|
||||
/**
|
||||
* @brief Performs a test on the LC02B EEPROM module.
|
||||
*/
|
||||
void eeprom_lc02b_test(void);
|
||||
|
||||
/**
|
||||
* @brief Gets the LC02B EEPROM status.
|
||||
*
|
||||
* This function is used to get the current status of the LC02B EEPROM.
|
||||
*
|
||||
* @return TRUE if the LC02B EEPROM is in normal status, FALSE otherwise.
|
||||
*/
|
||||
BOOL eeprom_lc02b_status_get(void);
|
||||
#endif ///< !__EEPROM_LC02B_H
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
/**
|
||||
* @file eeprom_m95.c
|
||||
* @author xxx
|
||||
* @date 2023-08-30 08:58:43
|
||||
* @brief 用于实现M95 EEPROM相关的读写操作
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include "eeprom_m95.h"
|
||||
#include "spis.h"
|
||||
#include "delay.h"
|
||||
#include "entity.h"
|
||||
#include "board.h"
|
||||
#include "diagnosis.h"
|
||||
#include "storage.h"
|
||||
|
||||
#define M95_SPI SPI1
|
||||
|
||||
#define EEPROM_M95_1_CS_PORT EE1_CS_GPIO_Port
|
||||
#define EEPROM_M95_1_CS_PIN EE1_CS_Pin
|
||||
#define EEPROM_M95_2_CS_PORT EE2_CS_GPIO_Port
|
||||
#define EEPROM_M95_2_CS_PIN EE2_CS_Pin
|
||||
|
||||
// 下面宏定义为2个EEPROM_M95的引脚定义
|
||||
#define EEPROM_M95_MOSI_PORT SPI_MOSI_GPIO_Port
|
||||
#define EEPROM_M95_MOSI_PIN SPI_MOSI_Pin
|
||||
#define EEPROM_M95_MISO_PORT SPI_MISO_GPIO_Port
|
||||
#define EEPROM_M95_MISO_PIN SPI_MISO_Pin
|
||||
#define EEPROM_M95_SCK_PORT SPI_CLK_GPIO_Port
|
||||
#define EEPROM_M95_SCK_PIN SPI_CLK_Pin
|
||||
|
||||
m95_number_t eeprom_m95s[M95_MAX];
|
||||
|
||||
/**
|
||||
* @brief 初始化EEPROM_M95eeprom_m95s
|
||||
* @param {m95_number_e} num
|
||||
* @return {*}
|
||||
* @note 初始化函数对板卡上不同的芯片定义了块大小
|
||||
*/
|
||||
void eeprom_m95_init(m95_number_e num)
|
||||
{
|
||||
DBG_ASSERT(num < M95_MAX __DBG_LINE);
|
||||
spi_gpio_group_t gpios;
|
||||
spi_t *eeprom_m95_spi;
|
||||
spi_normal_config_t cfg;
|
||||
osel_memset((uint8_t *)&cfg, 0, sizeof(spi_normal_config_t));
|
||||
cfg.cmd_rdsr = M95_CMD_RDSR;
|
||||
cfg.cmd_wrsr = M95_CMD_WRSR;
|
||||
cfg.cmd_wren = M95_CMD_WREN;
|
||||
cfg.cmd_wrdi = M95_CMD_WRDI;
|
||||
cfg.cmd_write = M95_CMD_WRITE;
|
||||
cfg.cmd_read = M95_CMD_READ;
|
||||
cfg.dummy_byte = M95_DUMMY_BYTE;
|
||||
cfg.continuous_write = FALSE;
|
||||
// 128 byte
|
||||
if (num == M95_1)
|
||||
{
|
||||
// 创建CS引脚
|
||||
gpios.cs = gpio_create(EEPROM_M95_1_CS_PORT, EEPROM_M95_1_CS_PIN);
|
||||
|
||||
cfg.address_bytes = 3;
|
||||
cfg.page_size = M95_PAGE_SIZE_256;
|
||||
cfg.total_size = _M95M02_;
|
||||
}
|
||||
// 256 byte
|
||||
else if (num == M95_2)
|
||||
{
|
||||
// 创建CS引脚
|
||||
gpios.cs = gpio_create(EEPROM_M95_2_CS_PORT, EEPROM_M95_2_CS_PIN);
|
||||
|
||||
cfg.address_bytes = 3;
|
||||
cfg.page_size = M95_PAGE_SIZE_256;
|
||||
cfg.total_size = _M95M02_;
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG_ASSERT(FALSE __DBG_LINE);
|
||||
}
|
||||
|
||||
gpios.mosi = gpio_create(EEPROM_M95_MOSI_PORT, EEPROM_M95_MOSI_PIN);
|
||||
gpios.sck = gpio_create(EEPROM_M95_SCK_PORT, EEPROM_M95_SCK_PIN);
|
||||
gpios.miso = gpio_create(EEPROM_M95_MISO_PORT, EEPROM_M95_MISO_PIN);
|
||||
gpios.rst = gpio_create(NULL, 0);
|
||||
gpios.rdy = gpio_create(NULL, 0);
|
||||
|
||||
// 创建SPI对象
|
||||
eeprom_m95_spi = spi_create(SPI_TYPE_NORMAL, gpios, 10);
|
||||
DBG_ASSERT(eeprom_m95_spi != NULL __DBG_LINE);
|
||||
osel_memcpy((uint8_t *)&eeprom_m95_spi->cfg, (uint8_t *)&cfg, sizeof(spi_normal_config_t));
|
||||
// 使能SPI
|
||||
eeprom_m95_spi->interface.hardware_enable(eeprom_m95_spi, M95_SPI);
|
||||
eeprom_m95s[num].num = num;
|
||||
eeprom_m95s[num].spi = eeprom_m95_spi;
|
||||
// 这里需要设置,否则读出的数据不对
|
||||
eeprom_m95_spi->interface.u.normal.spi_reset(eeprom_m95_spi);
|
||||
eeprom_m95_write_protection_close(num); // 关闭写保护
|
||||
|
||||
// eeprom_m95_test(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 反初始化EEPROM_M95
|
||||
* @param {m95_number_e} num
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void eeprom_m95_dinit(m95_number_e num)
|
||||
{
|
||||
LL_SPI_Disable(M95_SPI);
|
||||
GPIO_SET_ANALOG(eeprom_m95s[num].spi->gpios.mosi->port, eeprom_m95s[num].spi->gpios.mosi->pin);
|
||||
GPIO_SET_ANALOG(eeprom_m95s[num].spi->gpios.miso->port, eeprom_m95s[num].spi->gpios.miso->pin);
|
||||
GPIO_SET_ANALOG(eeprom_m95s[num].spi->gpios.sck->port, eeprom_m95s[num].spi->gpios.sck->pin);
|
||||
GPIO_SET_ANALOG(eeprom_m95s[num].spi->gpios.cs->port, eeprom_m95s[num].spi->gpios.cs->pin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief M95 EEPROM使能
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void eeprom_m95_enable(void)
|
||||
{
|
||||
uint16_t count = 100;
|
||||
LL_SPI_Enable(M95_SPI);
|
||||
// 判断SPI是否使能成功
|
||||
while (LL_SPI_IsEnabled(M95_SPI) != 1)
|
||||
{
|
||||
if (count-- == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief M95 EEPROM失能
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void eeprom_m95_disable(void)
|
||||
{
|
||||
uint16_t count = 100;
|
||||
LL_SPI_Disable(M95_SPI);
|
||||
// 判断SPI是否关闭成功
|
||||
while (LL_SPI_IsEnabled(M95_SPI) != 0)
|
||||
{
|
||||
if (count-- == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭EEPROM M95写保护
|
||||
*
|
||||
* 关闭指定M95 EEPROM的写保护功能。
|
||||
*
|
||||
* @param num 指定M95 EEPROM的编号
|
||||
*/
|
||||
void eeprom_m95_write_protection_close(m95_number_e num)
|
||||
{
|
||||
spi_t *handle = eeprom_m95s[num].spi;
|
||||
DBG_ASSERT(handle != NULL __DBG_LINE);
|
||||
eeprom_m95_enable();
|
||||
handle->interface.u.normal.spi_write_reg(handle, handle->cfg.cmd_wrsr, 0);
|
||||
eeprom_m95_disable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取M95 EEPROM写保护状态(软件)
|
||||
*
|
||||
* 根据传入的M95编号获取其写保护状态。
|
||||
*
|
||||
* @param num M95编号
|
||||
* @return 如果M95 EEPROM处于写保护状态,则返回FALSE;否则返回TRUE。
|
||||
*/
|
||||
BOOL eeprom_m95_write_protection_state(m95_number_e num)
|
||||
{
|
||||
spi_t *handle = eeprom_m95s[num].spi;
|
||||
DBG_ASSERT(handle != NULL __DBG_LINE);
|
||||
eeprom_m95_enable();
|
||||
eeprom_m95s[num].write_protection.data = handle->interface.u.normal.spi_read_reg(handle, handle->cfg.cmd_rdsr);
|
||||
eeprom_m95_disable();
|
||||
|
||||
if (eeprom_m95s[num].write_protection.bits.bp0 == 1 ||
|
||||
eeprom_m95s[num].write_protection.bits.bp1 == 1 ||
|
||||
eeprom_m95s[num].write_protection.bits.srwd == 1)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取M95 EEPROM内存数据
|
||||
* @param num EEPROM模块编号(0或1)
|
||||
* @param read_addr 要读取的地址
|
||||
* @param data 存储读取数据的缓冲区
|
||||
* @param length 要读取的数据长度
|
||||
* @return {*}
|
||||
*/
|
||||
BOOL eeprom_m95_read(m95_number_e num, uint32_t read_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
if (length == 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
|
||||
spi_t *eeprom_m95_spi = eeprom_m95s[num].spi; // 获取EEPROM模块的SPI配置
|
||||
DBG_ASSERT(eeprom_m95_spi != NULL __DBG_LINE);
|
||||
// 开启和关闭SPI对读写实时性有影响
|
||||
eeprom_m95_enable();
|
||||
ret = eeprom_m95_spi->interface.u.normal.spi_read(eeprom_m95_spi, read_addr, data, length);
|
||||
eeprom_m95_disable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向M95 EEPROM内存写入数据
|
||||
* @param num EEPROM模块编号(0或1)
|
||||
* @param write_addr 要写入的地址
|
||||
* @param data 包含要写入数据的缓冲区
|
||||
* @param length 要写入的数据长度
|
||||
* @return {*}
|
||||
*/
|
||||
BOOL eeprom_m95_write(m95_number_e num, uint32_t write_addr, uint8_t *data, uint16_t length)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
if (length == 0)
|
||||
{
|
||||
return ret;
|
||||
}
|
||||
spi_t *eeprom_m95_spi = eeprom_m95s[num].spi;
|
||||
DBG_ASSERT(eeprom_m95_spi != NULL __DBG_LINE);
|
||||
// 开启和关闭SPI对读写实时性有影响
|
||||
eeprom_m95_enable();
|
||||
ret = eeprom_m95_spi->interface.u.normal.spi_write(eeprom_m95_spi, write_addr, data, length);
|
||||
eeprom_m95_disable();
|
||||
return ret;
|
||||
}
|
||||
|
||||
BOOL eeprom_m95_1_read(uint32_t addr, uint8_t *buf, uint16_t size)
|
||||
{
|
||||
return eeprom_m95_read(M95_1, addr, buf, size);
|
||||
}
|
||||
|
||||
BOOL eeprom_m95_1_write(uint32_t addr, uint8_t *buf, uint16_t size)
|
||||
{
|
||||
return eeprom_m95_write(M95_1, addr, (uint8_t *)buf, size);
|
||||
}
|
||||
|
||||
BOOL eeprom_m95_2_read(uint32_t addr, uint8_t *buf, uint16_t size)
|
||||
{
|
||||
return eeprom_m95_read(M95_2, addr, buf, size);
|
||||
}
|
||||
|
||||
BOOL eeprom_m95_2_write(uint32_t addr, uint8_t *buf, uint16_t size)
|
||||
{
|
||||
return eeprom_m95_write(M95_2, addr, (uint8_t *)buf, size);
|
||||
}
|
||||
/**
|
||||
* @brief 用于M95 EEPROM测试
|
||||
* @param {m95_number_e} num
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
BOOL eeprom_m95_test(m95_number_e num)
|
||||
{
|
||||
const uint8_t buf_size = 5;
|
||||
storage_t *st = storage_init(M95_TEST_PAGE * M95_PAGE_SIZE_256, M95_PAGE_SIZE_256);
|
||||
DBG_ASSERT(st != NULL __DBG_LINE);
|
||||
|
||||
if (num == M95_1)
|
||||
{
|
||||
st->ops.read = eeprom_m95_1_read;
|
||||
st->ops.write = eeprom_m95_1_write;
|
||||
}
|
||||
else
|
||||
{
|
||||
st->ops.read = eeprom_m95_2_read;
|
||||
st->ops.write = eeprom_m95_2_write;
|
||||
}
|
||||
uint8_t buf[buf_size];
|
||||
uint8_t rbuf[buf_size];
|
||||
storage_add_node(st, 0, buf_size);
|
||||
osel_memset(buf, 0, buf_size);
|
||||
buf[0] = 0xD5;
|
||||
buf[1] = 0xC8;
|
||||
buf[2] = num;
|
||||
buf[3] = 0xaa;
|
||||
buf[4] = 0xbb;
|
||||
storage_write(st, 0, buf);
|
||||
__NOP();
|
||||
storage_read(st, 0, rbuf);
|
||||
storage_destroy(st);
|
||||
if (osel_memcmp(buf, rbuf, buf_size) == 0)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取EEPROM M95状态
|
||||
*
|
||||
* 获取EEPROM M95设备的工作状态。
|
||||
*
|
||||
* @param num EEPROM M95设备编号
|
||||
*
|
||||
* @return 如果EEPROM M95设备正常工作,返回TRUE;否则返回FALSE
|
||||
*/
|
||||
BOOL eeprom_m95_status_get(m95_number_e num)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
* @file eeprom_m95.h
|
||||
* @author xxx
|
||||
* @date 2023-08-30 14:05:55
|
||||
* @brief 头文件 eeprom_m95.h
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef __EEPROM_M95_H
|
||||
#define __EEPROM_M95_H
|
||||
#include "main.h"
|
||||
#include "spis.h"
|
||||
|
||||
#define _M95010_ 128
|
||||
#define _M95020_ 256
|
||||
#define _M95040_ 512
|
||||
|
||||
#define _M95080_ 1024
|
||||
#define _M95160_ 2048
|
||||
#define _M95320_ 4096
|
||||
#define _M95640_ 8192
|
||||
|
||||
#define _M95128_ 16384
|
||||
#define _M95256_ 32768
|
||||
|
||||
#define _M95512_ 65536 ///< 65K
|
||||
#define _M95M02_ 262144 ///< 256K
|
||||
|
||||
#define _M95_SIZE _M95512_
|
||||
|
||||
#define M95_CMD_RDSR 0x05 /*!< Read Status Register instruction */
|
||||
#define M95_CMD_WRSR 0x01 /*!< Write Status Register instruction */
|
||||
|
||||
#define M95_CMD_WREN 0x06 /*!< Write enable instruction */
|
||||
#define M95_CMD_WRDI 0x04 /*!< Write disable instruction */
|
||||
|
||||
#define M95_CMD_READ 0x03 /*!< Read from Memory instruction */
|
||||
#define M95_CMD_WRITE 0x02 /*!< Write to Memory instruction */
|
||||
|
||||
///< Instruction available only for the M95_2-D device.
|
||||
#define M95_CMD_RDID 0x83 /*!< Read identification page*/
|
||||
#define M95_CMD_WRID 0x82 /*!< Write identification page*/
|
||||
#define M95_CMD_RDLS 0x83 /*!< Reads the Identification page lock status*/
|
||||
#define M95_CMD_LID 0x82 /*!< Locks the Identification page in read-only mode*/
|
||||
|
||||
#define M95_DUMMY_BYTE 0xA5 ///< 虚拟字节
|
||||
|
||||
#define M95_TEST_PAGE 0 ///< 测试页地址
|
||||
|
||||
///< 定义存储器大小(Bytes)
|
||||
typedef enum
|
||||
{
|
||||
M95_PAGE_SIZE_16 = 16, ///< _M95010_ 、_M95020_ 、_M95040_
|
||||
M95_PAGE_SIZE_32 = 32, ///< _M95080_ 、_M95160_、_M95320_、_M95640_
|
||||
M95_PAGE_SIZE_64 = 64, ///< _M95128_、_M95256_
|
||||
M95_PAGE_SIZE_128 = 128, ///< _M95512_
|
||||
M95_PAGE_SIZE_256 = 256, ///< _M95M02_
|
||||
} m95_page_size_e;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
M95_1,
|
||||
M95_2,
|
||||
M95_MAX,
|
||||
} m95_number_e; ///< 板卡上2块m95芯片定义
|
||||
|
||||
typedef union
|
||||
{
|
||||
uint8_t data;
|
||||
struct
|
||||
{
|
||||
uint8_t wip : 1; ///< Write in progress
|
||||
uint8_t wel : 1; ///< Write enable latch
|
||||
uint8_t bp0 : 1; ///< Block protect 0
|
||||
uint8_t bp1 : 1; ///< Block protect 1
|
||||
uint8_t reserve : 3;
|
||||
uint8_t srwd : 1; ///< Status register write protect
|
||||
} bits;
|
||||
} m95_write_protection_u;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
m95_number_e num;
|
||||
m95_write_protection_u write_protection;
|
||||
spi_t *spi;
|
||||
} m95_number_t;
|
||||
|
||||
extern m95_number_t eeprom_m95s[M95_MAX]; ///< m95芯片数组
|
||||
|
||||
/**
|
||||
* @brief Initializes the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
*/
|
||||
extern void eeprom_m95_init(m95_number_e num);
|
||||
|
||||
/**
|
||||
* @brief Deinitializes the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
*/
|
||||
extern void eeprom_m95_dinit(m95_number_e num);
|
||||
|
||||
/**
|
||||
* @brief Enables the M95 EEPROM module.
|
||||
*/
|
||||
extern void eeprom_m95_enable(void);
|
||||
|
||||
/**
|
||||
* @brief Disables the M95 EEPROM module.
|
||||
*/
|
||||
extern void eeprom_m95_disable(void);
|
||||
|
||||
/**
|
||||
* @brief Closes the write protection of the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
*/
|
||||
extern void eeprom_m95_write_protection_close(m95_number_e num);
|
||||
|
||||
/**
|
||||
* @brief write protection state of the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
*/
|
||||
extern BOOL eeprom_m95_write_protection_state(m95_number_e num);
|
||||
|
||||
/**
|
||||
* @brief Reads data from the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
* @param read_addr The address to read from.
|
||||
* @param data The buffer to store the read data.
|
||||
* @param length The number of bytes to read.
|
||||
*/
|
||||
extern BOOL eeprom_m95_read(m95_number_e num, uint32_t read_addr, uint8_t *data, uint16_t length);
|
||||
|
||||
/**
|
||||
* @brief Writes data to the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
* @param write_addr The address to write to.
|
||||
* @param data The data to write.
|
||||
* @param length The number of bytes to write.
|
||||
*/
|
||||
extern BOOL eeprom_m95_write(m95_number_e num, uint32_t write_addr, uint8_t *data, uint16_t length);
|
||||
|
||||
/**
|
||||
* @brief Performs a test on the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
*/
|
||||
extern BOOL eeprom_m95_test(m95_number_e num);
|
||||
|
||||
/**
|
||||
* @brief Gets the status of the M95 EEPROM module.
|
||||
*
|
||||
* @param num The M95 EEPROM number.
|
||||
*/
|
||||
extern BOOL eeprom_m95_status_get(m95_number_e num);
|
||||
|
||||
extern BOOL eeprom_m95_1_read(uint32_t addr, uint8_t *buf, uint16_t size);
|
||||
extern BOOL eeprom_m95_1_write(uint32_t addr, uint8_t *buf, uint16_t size);
|
||||
extern BOOL eeprom_m95_2_read(uint32_t addr, uint8_t *buf, uint16_t size);
|
||||
extern BOOL eeprom_m95_2_write(uint32_t addr, uint8_t *buf, uint16_t size);
|
||||
#endif ///< __EEPROM_M95_H
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
/**
|
||||
* @file ntc_3950.c
|
||||
* @author xxx
|
||||
* @date 2023-08-30 08:58:43
|
||||
* @brief 用于实现NTC的应用功能
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include "ntc_3950.h"
|
||||
#define CPU_VREF 2.5
|
||||
#define NTC_VREF 2.5
|
||||
#define TABLE_SIZE 185
|
||||
#define NTC_SERIES_RESISTOR 200000 ///< 200K
|
||||
#define BASE_TEMP -55
|
||||
|
||||
const uint32_t _table[TABLE_SIZE] = {
|
||||
8989000, 8242680, 7592960, 7021380, 6513750, // -55,-54,-53,-52,-51
|
||||
6059060, 5648680, 5275800, 4935020, 4621990, 4333220, 4065840, 3817520, 3586310, 3370600, // -50,-49,-48,-47,-46,-45,-44,-43,-42,-41
|
||||
3169000, 2980330, 2803600, 2637910, 2482470, 2336580, 2199620, 2071020, 1950230, 1836790, // -40,-39,-38,-37,-36,-35,-34,-33,-32,-31
|
||||
1730230, 1630150, 1536140, 1447840, 1364900, 1287000, 1213820, 1145090, 1080530, 1019890, // -30,-29,-28,-27,-26,-25,-24,-23,-22,-21
|
||||
962912, 909379, 859074, 811797, 767359, 725581, 686296, 649348, 614590, 581883, // -20,-19,-18,-17,-16,-15,-14,-13,-12,-11
|
||||
551100, 522117, 494824, 469113, 444886, 422050, 400518, 380209, 361048, 342963, // -10,-9,-8,-7,-6,-5,-4,-3,-2,-1
|
||||
326560, 309764, 294529, 280131, 266520, 253647, 241470, 229946, 219036, 208706, // 0,1, 2, 3,4,5,6,7,8,9
|
||||
198920, 189647, 180857, 172523, 164618, 157118, 150000, 143243, 136827, 130731, // 10,11 ,12, 13,14,15,16,17,18,19
|
||||
124940, 119435, 114202, 109225, 104491, 100000, 95699, 91617, 87731, 84028, // 20,21, 22, 23,24,25,26,27,28,29
|
||||
80501, 77140, 73936, 70881, 67968, 65188, 62537, 60006, 57590, 55283, // 30,31, 32, 33,34,35,36,37,38,39
|
||||
53080, 50976, 48965, 47044, 45207, 43451, 41771, 40165, 38628, 37157, // 40,41, 42, 43,34,35,36,37,38,39
|
||||
35750, 34402, 33112, 31876, 30692, 29558, 28471, 27429, 26430, 25472, // 50,51, 52, 53,54,55,56,57,58,59
|
||||
24554, 23672, 22827, 22016, 21237, 20489, 19771, 19082, 18420, 17784, // 60,61, 62, 63,64,65,66,67,68,69
|
||||
17172, 16585, 16020, 15477, 14955, 14453, 13970, 13505, 13058, 12628, // 70,71, 72, 73,74,75,76,77,78,79
|
||||
12213, 11815, 11431, 11061, 10705, 10362, 10031, 9712, 9405, 9110, // 80,81, 82, 83,84,85,86,87,88,89
|
||||
8824, 8549, 8284, 8028, 7782, 7544, 7314, 7093, 6879, 6673, // 90,91, 92, 93,94,95,96,97,98,99
|
||||
6474, 6281, 6096, 5916, 5743, 5576, 5415, 5259, 5108, 4963, // 101,102, 103,104,105,106,107,108,109
|
||||
4822, 4687, 4555, 4428, 4306, 4187, 4073, 3962, 3855, 3751, // 111,112, 113,114,115,116,117,118,119
|
||||
3651, 3555, 3461, 3371, 3283, 3199, 3100, 3099, 2899, 2799, // 121,122, 123,124,125,126,127,128,129
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 温度查表
|
||||
* @param {uint32_t} *list
|
||||
* @param {uint32_t} rt
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static uint8_t ntc_lookup(const uint32_t *list, uint32_t rt)
|
||||
{
|
||||
uint8_t middle = 0;
|
||||
uint8_t indexL = 0;
|
||||
uint8_t indexR = TABLE_SIZE - 1;
|
||||
if (rt >= *(list + 0))
|
||||
return 0;
|
||||
if (rt <= *(list + TABLE_SIZE - 1))
|
||||
return TABLE_SIZE - 1;
|
||||
|
||||
while ((indexR - indexL) > 1)
|
||||
{
|
||||
middle = (indexL + indexR) >> 1;
|
||||
if (rt == *(list + middle))
|
||||
return middle;
|
||||
else if (rt > *(list + middle))
|
||||
indexR = middle;
|
||||
else if (rt < *(list + middle))
|
||||
indexL = middle;
|
||||
}
|
||||
return indexL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取温度值,单位为0.1摄氏度
|
||||
* @param {uint16_t} adc采集值
|
||||
* @return {float32_u} 温度值
|
||||
* @note
|
||||
*/
|
||||
float32_u ntc_get_temp(uint16_t adc)
|
||||
{
|
||||
|
||||
uint8_t index = 0;
|
||||
int16_t data = 0;
|
||||
int16_t t = 0;
|
||||
int16_t result = 0;
|
||||
uint32_t rt = 0;
|
||||
const int16_t base = BASE_TEMP * 10;
|
||||
float32_u res;
|
||||
res.f = BASE_TEMP;
|
||||
|
||||
/**
|
||||
* ad = (4095*rt)/(rt+10000)
|
||||
*
|
||||
* rt = (ad*NTC_SERIES_RESISTOR*CPU_VREF)/(NTC_VREF*4095-CPU_VREF*ad)
|
||||
*/
|
||||
rt = (adc * NTC_SERIES_RESISTOR * CPU_VREF) / (NTC_VREF * 4095 - CPU_VREF * adc);
|
||||
index = ntc_lookup(_table, rt);
|
||||
if (rt >= _table[0])
|
||||
return res;
|
||||
if (rt <= *(_table + TABLE_SIZE - 1))
|
||||
{
|
||||
result = (TABLE_SIZE - 1) * 10 + base;
|
||||
}
|
||||
else
|
||||
{
|
||||
data = _table[index] - _table[index + 1];
|
||||
t = 10 * (_table[index] - rt) / data;
|
||||
result = base + index * 10 + t;
|
||||
}
|
||||
res.f = result / 10.0;
|
||||
return res;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @file ntc_3950.h
|
||||
* @author xxx
|
||||
* @date 2023-08-30 14:05:55
|
||||
* @brief 头文件 ntc_3950.h
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef __NTC_B3950_H__
|
||||
#define __NTC_B3950_H__
|
||||
#include "main.h"
|
||||
|
||||
extern float32_u ntc_get_temp(uint16_t adc); ///< 获取温度值
|
||||
|
||||
#endif ///< __NTC_B3950_H__
|
||||
|
|
@ -0,0 +1,560 @@
|
|||
/**
|
||||
* @file rtc_rx8010.c
|
||||
* @author xxx
|
||||
* @date 2023-08-30 08:58:43
|
||||
* @brief 用于实现RTC芯片RX8010的应用功能
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include "rtc_rx8010.h"
|
||||
#include "i2cs.h"
|
||||
#include "delay.h"
|
||||
|
||||
#define RTC_RX8010_SDA_PORT RTC_SDA_GPIO_Port
|
||||
#define RTC_RX8010_SDA_PIN RTC_SDA_Pin
|
||||
#define RTC_RX8010_SCL_PORT RTC_SCL_GPIO_Port
|
||||
#define RTC_RX8010_SCL_PIN RTC_SCL_Pin
|
||||
|
||||
static i2c_t *rtc;
|
||||
static TIM_TypeDef *_timer_us;
|
||||
|
||||
static void _delay_us(uint32_t us)
|
||||
{
|
||||
if (_timer_us != NULL)
|
||||
{
|
||||
delay_hardware_us(_timer_us, us);
|
||||
}
|
||||
else
|
||||
{
|
||||
delay_us(us);
|
||||
}
|
||||
}
|
||||
|
||||
/* sec, min, hour, week, day, month, year */
|
||||
// static uint8_t calendar[7] = {0, 0, 0, 1, 29, 2, 98};
|
||||
|
||||
/**
|
||||
* @brief 从RTC芯片的指定地址读取一个字节数据
|
||||
* @param {uint8_t} *read_buf
|
||||
* @param {uint8_t} addr
|
||||
* @return {*}
|
||||
*/
|
||||
static BOOL rtc_read_byte(uint8_t *read_buf, uint8_t addr)
|
||||
{
|
||||
uint8_t *p = read_buf;
|
||||
|
||||
/* 发送起始信号 */
|
||||
rtc->interface.start(rtc);
|
||||
|
||||
/* 发送从机地址 + 读写方向 */
|
||||
rtc->interface.write_byte(rtc, RTC_WR_ADDR); /* 此处是写方向,因为要发送读地址 */
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 发送读地址 */
|
||||
rtc->interface.write_byte(rtc, addr);
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 重新发送起始信号。前面的代码的目的向RTC传送地址,下面开始读取数据 */
|
||||
rtc->interface.start(rtc);
|
||||
|
||||
/* 发送从机地址 + 读写方向 */
|
||||
rtc->interface.write_byte(rtc, RTC_RD_ADDR); /* 此处是读方向,因为要开始读数据了 */
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
/* 读取数据 */
|
||||
*p = rtc->interface.read_byte(rtc, FALSE); /* 读1个字节 */
|
||||
|
||||
/* 命令执行成功,发送I2C总线停止信号 */
|
||||
rtc->interface.stop(rtc);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从RTC芯片的指定地址读取若干数据
|
||||
* @param {uint8_t} *read_buf
|
||||
* @param {uint8_t} addr
|
||||
* @param {int} size
|
||||
* @return {*}
|
||||
*/
|
||||
static BOOL rtc_read_bytes(uint8_t *read_buf, uint8_t addr, int size)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
/* 发送起始信号 */
|
||||
rtc->interface.start(rtc);
|
||||
|
||||
/* 发送从机地址 + 读写方向 */
|
||||
rtc->interface.write_byte(rtc, RTC_WR_ADDR); /* 此处是写方向,因为要发送读地址 */
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 发送读地址 */
|
||||
rtc->interface.write_byte(rtc, addr);
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 重新发送起始信号。前面的代码的目的向RTC传送地址,下面开始读取数据 */
|
||||
rtc->interface.start(rtc);
|
||||
|
||||
/* 发送从机地址 + 读写方向 */
|
||||
rtc->interface.write_byte(rtc, RTC_RD_ADDR); /* 此处是读方向,因为要开始读数据了 */
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 循环读取数据,RTC芯片地址自动自增 */
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
/* 每读完1个字节后,需要发送Ack, 最后一个字节需要发Nack */
|
||||
if (i != (size - 1))
|
||||
{
|
||||
read_buf[i] = rtc->interface.read_byte(rtc, TRUE); /* 读1个字节 */
|
||||
}
|
||||
else
|
||||
{
|
||||
read_buf[i] = rtc->interface.read_byte(rtc, FALSE); /* 读1个字节 */
|
||||
}
|
||||
}
|
||||
|
||||
/* 命令执行成功,发送I2C总线停止信号 */
|
||||
rtc->interface.stop(rtc);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向RTC芯片的指定地址写入一个数据
|
||||
* @param {uint8_t} data
|
||||
* @param {uint8_t} addr
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static BOOL rtc_write_byte(uint8_t data, uint8_t addr)
|
||||
{
|
||||
int retry = 0;
|
||||
|
||||
/* 尝试与RTC芯片建立I2C通讯 */
|
||||
for (retry = 0; retry < 100; retry++)
|
||||
{
|
||||
rtc->interface.start(rtc); /* 发送起始信号 */
|
||||
rtc->interface.write_byte(rtc, RTC_WR_ADDR); /* 发送从机地址 + 读写方向 */
|
||||
if (rtc->interface.wait_ack(rtc) == TRUE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (retry == 100)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 发送起始写地址 */
|
||||
rtc->interface.write_byte(rtc, addr);
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 写入数据 */
|
||||
rtc->interface.write_byte(rtc, data);
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 命令执行成功,发送I2C总线停止信号 */
|
||||
rtc->interface.stop(rtc);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向RTC芯片的指定地址写入若干数据
|
||||
* @param {uint8_t} *write_buf
|
||||
* @param {uint8_t} addr
|
||||
* @param {int} size
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static BOOL rtc_write_bytes(uint8_t *write_buf, uint8_t addr, int size)
|
||||
{
|
||||
int i = 0;
|
||||
int retry = 0;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
/* 尝试与RTC芯片建立I2C通讯 */
|
||||
for (retry = 0; retry < 100; retry++)
|
||||
{
|
||||
rtc->interface.start(rtc); /* 发送起始信号 */
|
||||
rtc->interface.write_byte(rtc, RTC_WR_ADDR); /* 发送从机地址 + 读写方向 */
|
||||
if (rtc->interface.wait_ack(rtc) == TRUE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (retry == 100)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* 发送起始写地址 */
|
||||
rtc->interface.write_byte(rtc, addr);
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* 循环写入数据,RTC芯片地址自动自增 */
|
||||
rtc->interface.write_byte(rtc, write_buf[i]);
|
||||
if (rtc->interface.wait_ack(rtc) != TRUE)
|
||||
{
|
||||
rtc->interface.stop(rtc);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* 命令执行成功,发送I2C总线停止信号 */
|
||||
rtc->interface.stop(rtc);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 假读RTC芯片,任意读地址,不判断RTC响应
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void rtc_dummy_read(void)
|
||||
{
|
||||
rtc->interface.start(rtc);
|
||||
rtc->interface.write_byte(rtc, RTC_WR_ADDR);
|
||||
rtc->interface.write_byte(rtc, 0x20);
|
||||
|
||||
rtc->interface.start(rtc);
|
||||
rtc->interface.write_byte(rtc, RTC_RD_ADDR);
|
||||
rtc->interface.read_byte(rtc, FALSE);
|
||||
|
||||
rtc->interface.stop(rtc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 用于检查VLF,寄存器地址:0x1e bit[1]
|
||||
* @return {uint8_t} 0 = VLF位为0,1 = VLF位为1
|
||||
* @note
|
||||
*/
|
||||
static uint8_t rtc_check_vlf(void)
|
||||
{
|
||||
uint8_t flag_register = 1;
|
||||
uint8_t vlf = 0;
|
||||
|
||||
rtc_read_byte(&flag_register, RTC_FLAG_ADDR);
|
||||
|
||||
vlf = (flag_register & 0x02);
|
||||
if (vlf == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 等待VLF位清除,寄存器地址:0x1e bit[1]
|
||||
* @return {uint8_t} 0 = 清零成功,1 = 清零失败,2 = 无需清零
|
||||
*/
|
||||
static uint8_t rtc_wait_vlf_clear(void)
|
||||
{
|
||||
uint8_t ret = 1;
|
||||
uint8_t i = 0;
|
||||
uint8_t vlf;
|
||||
|
||||
for (i = 0; i < 10; i++)
|
||||
{
|
||||
vlf = rtc_check_vlf();
|
||||
|
||||
if (vlf == 0)
|
||||
{
|
||||
ret = ((i > 0) ? 0 : 2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* 清除VLF */
|
||||
rtc_write_byte(0, RTC_FLAG_ADDR);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 复位
|
||||
* @return {BOOL} FALSE = 复位成功,TRUE = 复位失败
|
||||
*/
|
||||
static BOOL rtc_soft_reset(void)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
|
||||
ret = rtc_write_byte(0x00, 0x1f);
|
||||
ret = rtc_write_byte(0x80, 0x1f);
|
||||
ret = rtc_write_byte(0xd3, 0x60);
|
||||
ret = rtc_write_byte(0x03, 0x66);
|
||||
ret = rtc_write_byte(0x02, 0x6b);
|
||||
ret = rtc_write_byte(0x01, 0x6b);
|
||||
|
||||
if (ret == 0)
|
||||
{
|
||||
_delay_us(2000);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 时钟寄存器初始化函数
|
||||
* @return {BOOL} TRUE = 初始化成功,FALSE = 初始化失败
|
||||
*/
|
||||
static BOOL rtc_clock_reginit(void)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
|
||||
/* set reserve register */
|
||||
ret = rtc_write_byte(RTC_REG17_DATA, RTC_REG17_ADDR);
|
||||
ret = rtc_write_byte(RTC_REG30_DATA, RTC_REG30_ADDR);
|
||||
ret = rtc_write_byte(RTC_REG31_DATA, RTC_REG31_ADDR);
|
||||
ret = rtc_write_byte(RTC_IRQ_DATA, RTC_IRQ_ADDR);
|
||||
/* write 0x04 to reg_0x1d */
|
||||
ret = rtc_write_byte(0x04, 0x1d);
|
||||
/* write 0x00 to reg_0x1e */
|
||||
ret = rtc_write_byte(0x00, 0x1e);
|
||||
/* stop clock */
|
||||
ret = rtc_write_byte(0x40, RTC_CONTROL_ADDR);
|
||||
/* set the present time */
|
||||
// ret = rtc_write_bytes(calendar, RTC_CLOCK_ADDR, sizeof(calendar));
|
||||
/* start clock */
|
||||
ret = rtc_write_byte(0x00, RTC_CONTROL_ADDR);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RTC芯片初始化
|
||||
* @return {BOOL} TRUE = 成功,FALSE = 失败
|
||||
* @note
|
||||
*/
|
||||
BOOL rtc_init(TIM_TypeDef *timer_us)
|
||||
{
|
||||
if (timer_us != NULL)
|
||||
{
|
||||
_timer_us = timer_us;
|
||||
ENABLE_TIM_COUNT(_timer_us);
|
||||
}
|
||||
|
||||
i2c_gpio_group_t gpios;
|
||||
int ret = 1;
|
||||
gpios.scl = gpio_create(RTC_RX8010_SCL_PORT, RTC_RX8010_SCL_PIN);
|
||||
gpios.sda = gpio_create(RTC_RX8010_SDA_PORT, RTC_RX8010_SDA_PIN);
|
||||
|
||||
rtc = i2c_create(gpios, 10);
|
||||
|
||||
rtc_dummy_read();
|
||||
/* wait for VLF bit clear */
|
||||
ret = rtc_wait_vlf_clear();
|
||||
if (ret == 0)
|
||||
{
|
||||
/* software reset */
|
||||
ret = rtc_soft_reset();
|
||||
if (ret == FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else if (ret == 1)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* register initialize */
|
||||
|
||||
return rtc_clock_reginit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RTC芯片反初始化
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
BOOL rtc_dinit(void)
|
||||
{
|
||||
GPIO_SET_ANALOG(RTC_RX8010_SCL_PORT, RTC_RX8010_SCL_PIN);
|
||||
GPIO_SET_ANALOG(RTC_RX8010_SDA_PORT, RTC_RX8010_SDA_PIN);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从RTC芯片读取时间
|
||||
* @param {uint8_t} *read_buf - 接收缓存指针,用于接收读取到的数据
|
||||
* @return {BOOL} TRUE = 成功,FALSE = 失败
|
||||
* @note
|
||||
*/
|
||||
BOOL rtc_get_clock_time(uint8_t *read_buf)
|
||||
{
|
||||
return rtc_read_bytes(read_buf, RTC_CLOCK_ADDR, 7);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 向RTC芯片写入时间
|
||||
* @param {rtc_date} *data - 发送缓存指针,用于存储要发送的数据
|
||||
* @return {BOOL} TRUE = 成功,FALSE = 失败
|
||||
* @note
|
||||
*/
|
||||
BOOL rtc_set_clock_time(rtc_date *data)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
uint8_t tmp[7];
|
||||
tmp[0] = data->second;
|
||||
tmp[1] = data->minute;
|
||||
tmp[2] = data->hour;
|
||||
tmp[3] = data->weekday;
|
||||
tmp[4] = data->day;
|
||||
tmp[5] = data->month;
|
||||
tmp[6] = data->year;
|
||||
|
||||
tmp[3] = (rtc_week_e)tmp[3]; // 改成星期几
|
||||
|
||||
/* stop clock */
|
||||
ret = rtc_write_byte(0x40, RTC_CONTROL_ADDR);
|
||||
/* set the present time */
|
||||
ret = rtc_write_bytes(tmp, RTC_CLOCK_ADDR, sizeof(tmp));
|
||||
/* start clock */
|
||||
ret = rtc_write_byte(0x00, RTC_CONTROL_ADDR);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实时时钟(RTC)的时间戳
|
||||
*
|
||||
* 该函数从RTC设备中获取当前的日期和时间,并将其转换为自1970年1月1日以来的秒数(时间戳)。
|
||||
* 如果无法从RTC设备中获取时间,则返回0。
|
||||
*
|
||||
* @return 返回从1970年1月1日以来的秒数(时间戳),如果无法获取时间则返回0。
|
||||
*/
|
||||
uint32_t rtc_timestamp(void)
|
||||
{
|
||||
BOOL ret = FALSE;
|
||||
uint8_t tmp[7];
|
||||
rtc_date_t date;
|
||||
rtc_time_t time;
|
||||
ret = rtc_get_clock_time(tmp);
|
||||
if (ret == FALSE)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
date.year = hex_format_dec(tmp[6]);
|
||||
date.month = hex_format_dec(tmp[5]);
|
||||
date.day = hex_format_dec(tmp[4]);
|
||||
time.hour = hex_format_dec(tmp[2]);
|
||||
time.minute = hex_format_dec(tmp[1]);
|
||||
time.second = hex_format_dec(tmp[0]);
|
||||
return time2stamp(&date, &time);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将星期转为星期码
|
||||
* @param {uint8_t} *weekday 星期几
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void rtc_weekday_convert(uint8_t *weekday)
|
||||
{
|
||||
switch (*weekday)
|
||||
{
|
||||
case 1:
|
||||
*weekday = MON;
|
||||
break;
|
||||
case 2:
|
||||
*weekday = TUE;
|
||||
break;
|
||||
case 3:
|
||||
*weekday = WED;
|
||||
break;
|
||||
case 4:
|
||||
*weekday = THUR;
|
||||
break;
|
||||
case 5:
|
||||
*weekday = FRI;
|
||||
break;
|
||||
case 6:
|
||||
*weekday = SAT;
|
||||
break;
|
||||
case 7:
|
||||
*weekday = SUN;
|
||||
break;
|
||||
default:
|
||||
*weekday = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将星期码转为星期
|
||||
* @param {uint8_t} *weekday 星期码
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void rtc_weekday_rconvert(uint8_t *weekday)
|
||||
{
|
||||
switch (*weekday)
|
||||
{
|
||||
case MON:
|
||||
*weekday = 1;
|
||||
break;
|
||||
case TUE:
|
||||
*weekday = 2;
|
||||
break;
|
||||
case WED:
|
||||
*weekday = 3;
|
||||
break;
|
||||
case THUR:
|
||||
*weekday = 4;
|
||||
break;
|
||||
case FRI:
|
||||
*weekday = 5;
|
||||
break;
|
||||
case SAT:
|
||||
*weekday = 6;
|
||||
break;
|
||||
case SUN:
|
||||
*weekday = 7;
|
||||
break;
|
||||
default:
|
||||
*weekday = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @file rtc_rx8010.h
|
||||
* @author xxx
|
||||
* @date 2023-08-30 14:05:55
|
||||
* @brief 头文件 rtc_rx8010.h
|
||||
* @copyright Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef __RTC_RX8010_H__
|
||||
#define __RTC_RX8010_H__
|
||||
#include "main.h"
|
||||
|
||||
#define RTC_DEVICE_ADDR 0x32
|
||||
|
||||
#define RTC_WR_ADDR ((RTC_DEVICE_ADDR << 1) | 0)
|
||||
#define RTC_RD_ADDR ((RTC_DEVICE_ADDR << 1) | 1)
|
||||
|
||||
#define RTC_FLAG_ADDR 0x1e
|
||||
#define RTC_CLOCK_ADDR 0x10
|
||||
#define RTC_CONTROL_ADDR 0x1f
|
||||
|
||||
#define RTC_REG17_ADDR 0x17
|
||||
#define RTC_REG17_DATA 0xd8
|
||||
|
||||
#define RTC_REG30_ADDR 0x30
|
||||
#define RTC_REG30_DATA 0x00
|
||||
|
||||
#define RTC_REG31_ADDR 0x31
|
||||
#define RTC_REG31_DATA 0x08
|
||||
|
||||
#define RTC_IRQ_ADDR 0x32
|
||||
#define RTC_IRQ_DATA 0x00
|
||||
|
||||
typedef enum
|
||||
{
|
||||
SUN = BIT0,
|
||||
MON = BIT1,
|
||||
TUE = BIT2,
|
||||
WED = BIT3,
|
||||
THUR = BIT4,
|
||||
FRI = BIT5,
|
||||
SAT = BIT6
|
||||
} rtc_week_e; ///< 星期码
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t year; ///< 7 bit - 1 63
|
||||
uint8_t month; ///< 4 bit
|
||||
uint8_t day; ///< 5 bit
|
||||
uint8_t weekday; ///< rtc_week_e
|
||||
uint8_t hour; ///< 5 bit
|
||||
uint8_t minute; ///< 6 bit
|
||||
uint8_t second; ///< 6 bit
|
||||
} rtc_date;
|
||||
|
||||
/**
|
||||
* @brief Initializes the RTC module.
|
||||
* @return TRUE if the initialization is successful, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL rtc_init(TIM_TypeDef *timer_us);
|
||||
|
||||
/**
|
||||
* @brief Deinitializes the RTC module.
|
||||
* @return TRUE if the deinitialization is successful, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL rtc_dinit(void);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the current clock time from the RTC module.
|
||||
* @param read_buf Pointer to the buffer to store the clock time.
|
||||
* @return TRUE if the clock time is successfully retrieved, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL rtc_get_clock_time(uint8_t *read_buf);
|
||||
|
||||
/**
|
||||
* @brief Sets the clock time in the RTC module.
|
||||
* @param data Pointer to the RTC date structure containing the new clock time.
|
||||
* @return TRUE if the clock time is successfully set, FALSE otherwise.
|
||||
*/
|
||||
extern BOOL rtc_set_clock_time(rtc_date *data);
|
||||
|
||||
/**
|
||||
* @brief Retrieves the current alarm time from the RTC module.
|
||||
* @param read_buf Pointer to the buffer to store the alarm time.
|
||||
* @return TRUE if the alarm time is successfully retrieved, FALSE otherwise.
|
||||
*/
|
||||
extern uint32_t rtc_timestamp(void);
|
||||
/**
|
||||
* @brief Converts the weekday value to a human-readable format.
|
||||
* @param weekday Pointer to the weekday value to be converted.
|
||||
*/
|
||||
extern void rtc_weekday_convert(uint8_t *weekday);
|
||||
|
||||
/**
|
||||
* @brief Converts the weekday value from a human-readable format to the RTC format.
|
||||
* @param weekday Pointer to the weekday value to be converted.
|
||||
*/
|
||||
extern void rtc_weekday_rconvert(uint8_t *weekday);
|
||||
#endif ///< !__RTC_RX8010_H__
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
#include "sht40.h"
|
||||
#include "i2cs.h"
|
||||
#include "delay.h"
|
||||
#define SHT40_SDA_PORT I2C1_SDA_GPIO_Port
|
||||
#define SHT40_SDA_PIN I2C1_SDA_Pin
|
||||
#define SHT40_SCL_PORT I2C1_SCL_GPIO_Port
|
||||
#define SHT40_SCL_PIN I2C1_SCL_Pin
|
||||
|
||||
#define SHT40_I2C_ADDRESS 0x44
|
||||
#define SHT40_MEASURE_CMD 0xFD // 2*8-bit T-data:8-bit CRC:2*8-bit RH-data: 8-bit CRC
|
||||
|
||||
static i2c_t *_sht40_i2c;
|
||||
static TIM_TypeDef *_timer_us;
|
||||
static uint8_t crc8(uint8_t *data, uint8_t len);
|
||||
|
||||
static void _delay_us(uint32_t us)
|
||||
{
|
||||
if (_timer_us != NULL)
|
||||
{
|
||||
delay_hardware_us(_timer_us, us);
|
||||
}
|
||||
else
|
||||
{
|
||||
delay_us(us);
|
||||
}
|
||||
}
|
||||
|
||||
void sht40_test(void)
|
||||
{
|
||||
float32 temperature = 0;
|
||||
float32 humidity = 0;
|
||||
sht40_read(&temperature, &humidity);
|
||||
|
||||
__NOP();
|
||||
}
|
||||
/**
|
||||
* @brief 初始化 SHT40 传感器
|
||||
*
|
||||
* 该函数用于初始化 SHT40 传感器,包括创建 I2C 通讯所需的 GPIO 引脚和 I2C 通讯对象。
|
||||
*
|
||||
* 初始化完成后,sht40_i2c 变量将用于后续的 SHT40 传感器通信。
|
||||
*/
|
||||
void sht40_init(TIM_TypeDef *timer_us)
|
||||
{
|
||||
DBG_ASSERT(timer_us != NULL __DBG_LINE);
|
||||
if (timer_us != NULL)
|
||||
{
|
||||
_timer_us = timer_us;
|
||||
ENABLE_TIM_COUNT(_timer_us);
|
||||
}
|
||||
|
||||
i2c_gpio_group_t gpios;
|
||||
gpios.scl = gpio_create(SHT40_SCL_PORT, SHT40_SCL_PIN);
|
||||
gpios.sda = gpio_create(SHT40_SDA_PORT, SHT40_SDA_PIN);
|
||||
|
||||
_sht40_i2c = i2c_create(gpios, 10);
|
||||
DBG_ASSERT(_sht40_i2c != NULL __DBG_LINE);
|
||||
|
||||
// sht40_test(); // 测试 SHT40
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化 SHT40 传感器
|
||||
*
|
||||
* 此函数用于初始化 SHT40 温湿度传感器,以便进行后续的温度和湿度测量。
|
||||
*
|
||||
* 注意:该函数没有返回值,也不接受任何参数。
|
||||
*/
|
||||
void sht40_dinit(void)
|
||||
{
|
||||
GPIO_SET_ANALOG(SHT40_SDA_PORT, SHT40_SDA_PIN);
|
||||
GPIO_SET_ANALOG(SHT40_SCL_PORT, SHT40_SCL_PIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取温湿度传感器数据
|
||||
*
|
||||
* 从温湿度传感器读取温度和湿度数据,并将读取到的数据存储在传入的浮点数指针指向的位置。
|
||||
*
|
||||
* @param temperature 用于存储读取到的温度值的浮点数指针
|
||||
* @param humidity 用于存储读取到的湿度值的浮点数指针
|
||||
*/
|
||||
BOOL sht40_read(float32 *temperature, float32 *humidity)
|
||||
{
|
||||
uint8_t data[6];
|
||||
|
||||
osel_memset(data, 0, ARRAY_LEN(data));
|
||||
// 发送开始信号
|
||||
_sht40_i2c->interface.start(_sht40_i2c);
|
||||
// 发送写入地址命令
|
||||
_sht40_i2c->interface.write_byte(_sht40_i2c, SHT40_I2C_ADDRESS << 1);
|
||||
// 等待写入地址命令响应
|
||||
if (_sht40_i2c->interface.wait_ack(_sht40_i2c) == FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
// 发送测量命令
|
||||
_sht40_i2c->interface.write_byte(_sht40_i2c, SHT40_MEASURE_CMD);
|
||||
// 等待测量命令响应
|
||||
if (_sht40_i2c->interface.wait_ack(_sht40_i2c) == FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
// 停止I2C总线
|
||||
_sht40_i2c->interface.stop(_sht40_i2c);
|
||||
|
||||
_delay_us(10000); // 根据 SHT40 数据手册,等待至少 10ms
|
||||
|
||||
// 发送开始信号
|
||||
_sht40_i2c->interface.start(_sht40_i2c);
|
||||
// 发送写入地址命令
|
||||
_sht40_i2c->interface.write_byte(_sht40_i2c, (SHT40_I2C_ADDRESS << 1) | 1);
|
||||
// 等待写入地址命令响应
|
||||
if (_sht40_i2c->interface.wait_ack(_sht40_i2c) == FALSE)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < ARRAY_LEN(data); i++)
|
||||
{
|
||||
if (i == 5)
|
||||
{
|
||||
data[i] = _sht40_i2c->interface.read_byte(_sht40_i2c, FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
data[i] = _sht40_i2c->interface.read_byte(_sht40_i2c, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
// 停止I2C总线
|
||||
_sht40_i2c->interface.stop(_sht40_i2c);
|
||||
|
||||
*temperature = 0;
|
||||
*humidity = 0;
|
||||
if (crc8(&data[0], 2) != data[2] || crc8(&data[3], 2) != data[5])
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
*temperature = (float32)((uint16_t)data[0] << 8 | data[1]) * 175 / 65535 - 45;
|
||||
*humidity = (float32)((uint16_t)data[3] << 8 | data[4]) * 125 / 65535 - 6;
|
||||
if (*humidity > 100)
|
||||
{
|
||||
*humidity = 100;
|
||||
}
|
||||
if (*humidity < 0)
|
||||
{
|
||||
*humidity = 0;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief crc8校验函数,多项式为 x^8 + x^5 + x^4 + 1
|
||||
* @param data 要校验的数据
|
||||
* @param len 要校验的数据的字节数
|
||||
* @retval 校验结果
|
||||
* @note 该校验适合SHT3温湿度传感器的数据校验
|
||||
*/
|
||||
static uint8_t crc8(uint8_t *data, uint8_t len)
|
||||
{
|
||||
const uint8_t polynomial = 0x31;
|
||||
uint8_t crc = 0xFF;
|
||||
int i, j;
|
||||
|
||||
for (i = 0; i < len; ++i)
|
||||
{
|
||||
crc ^= *data++;
|
||||
|
||||
for (j = 0; j < 8; ++j)
|
||||
{
|
||||
crc = (crc & 0x80) ? (crc << 1) ^ polynomial : (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef __SHT40_H
|
||||
#define __SHT40_H
|
||||
#include "main.h"
|
||||
|
||||
void sht40_init(TIM_TypeDef *timer_us); ///< 初始化 SHT40 传感器
|
||||
void sht40_dinit(void); ///< 反初始化 SHT40 传感器
|
||||
BOOL sht40_read(float32 *temperature, float32 *humidity); ///< 读取温湿度传感器数据
|
||||
|
||||
#endif ///< !__SHT40_H
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef __SSD1306_OLED_H
|
||||
#define __SSD1306_OLED_H
|
||||
|
||||
#include "main.h"
|
||||
|
||||
// OLED引脚定义
|
||||
#define SSD1306_SDA_PORT OLED_SDA_GPIO_Port
|
||||
#define SSD1306_SDA_PIN OLED_SDA_Pin
|
||||
#define SSD1306_SCK_PORT OLDE_SCK_GPIO_Port
|
||||
#define SSD1306_SCK_PIN OLDE_SCK_Pin
|
||||
|
||||
// I2C地址
|
||||
#define SSD1306_I2C_ADDRESS 0x78
|
||||
// OLED显示参数
|
||||
#define SSD1306_WIDTH 128
|
||||
#define SSD1306_HEIGHT 64
|
||||
// OLED颜色
|
||||
#define SSD1306_WHITE 1
|
||||
#define SSD1306_BLACK 0
|
||||
|
||||
// OLED命令定义
|
||||
#define SSD1306_CMD_DISPLAY_OFF 0xAE
|
||||
#define SSD1306_CMD_DISPLAY_ON 0xAF
|
||||
#define SSD1306_CMD_SET_CONTRAST 0x81
|
||||
#define SSD1306_CMD_DISPLAY_ALL_ON_RESUME 0xA4
|
||||
#define SSD1306_CMD_DISPLAY_ALL_ON 0xA5
|
||||
#define SSD1306_CMD_NORMAL_DISPLAY 0xA6
|
||||
#define SSD1306_CMD_INVERT_DISPLAY 0xA7
|
||||
#define SSD1306_CMD_SET_DISPLAY_OFFSET 0xD3
|
||||
#define SSD1306_CMD_SET_COM_PINS 0xDA
|
||||
#define SSD1306_CMD_SET_VCOM_DETECT 0xDB
|
||||
#define SSD1306_CMD_SET_DISPLAY_CLOCK_DIV 0xD5
|
||||
#define SSD1306_CMD_SET_PRECHARGE 0xD9
|
||||
#define SSD1306_CMD_SET_MULTIPLEX 0xA8
|
||||
#define SSD1306_CMD_SET_LOW_COLUMN 0x00
|
||||
#define SSD1306_CMD_SET_HIGH_COLUMN 0x10
|
||||
#define SSD1306_CMD_SET_START_LINE 0x40
|
||||
#define SSD1306_CMD_MEMORY_MODE 0x20
|
||||
#define SSD1306_CMD_COLUMN_ADDR 0x21
|
||||
#define SSD1306_CMD_PAGE_ADDR 0x22
|
||||
#define SSD1306_CMD_COM_SCAN_INC 0xC0
|
||||
#define SSD1306_CMD_COM_SCAN_DEC 0xC8
|
||||
#define SSD1306_CMD_SEG_REMAP 0xA0
|
||||
#define SSD1306_CMD_CHARGE_PUMP 0x8D
|
||||
#define SSD1306_CMD_SET_DC_DC_ENABLE 0x14
|
||||
|
||||
#define SDA_OUT() \
|
||||
{ \
|
||||
GPIO_SET_OUTPUT(SSD1306_SDA_PORT, SSD1306_SDA_PIN); \
|
||||
}
|
||||
|
||||
#define SDA_IN() \
|
||||
{ \
|
||||
GPIO_SET_INPUT(SSD1306_SDA_PORT, SSD1306_SDA_PIN); \
|
||||
}
|
||||
|
||||
// 函数声明
|
||||
void ssd1306_init(void);
|
||||
void ssd1306_logo(void);
|
||||
void ssd1306_display_on(void);
|
||||
void ssd1306_display_off(void);
|
||||
void ssd1306_update_screen(void);
|
||||
|
||||
void ssd1306_fill(uint8_t color);
|
||||
void ssd1306_clear(void);
|
||||
void ssd1306_clear_buffer(void);
|
||||
void ssd1306_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, const uint8_t *bmp);
|
||||
void ssd1306_f6x8_string(uint8_t x, uint8_t y, const uint8_t *ch);
|
||||
void ssd1306_f6x8_number(uint8_t x, uint8_t y, float32 num, uint8_t dot_num);
|
||||
void ssd1306_f6x8_string_number(uint8_t x, uint8_t y, const uint8_t *ch, uint8_t unit, float32 num);
|
||||
void ssd1306_f8x16_string(uint8_t x, uint8_t y, const uint8_t *ch);
|
||||
void ssd1306_f8x16_number(uint8_t x, uint8_t y, float32 num, uint8_t dot_num);
|
||||
|
||||
void ssd1306_draw_text_center(uint8_t y, const char *text);
|
||||
void ssd1306_draw_progress_bar(uint8_t progress);
|
||||
void ssd1306_fill_rect_angle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color);
|
||||
void ssd1306_draw_rect_angle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color);
|
||||
void ssd1306_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t color);
|
||||
void ssd1306_draw_pixel(uint8_t x, uint8_t y, uint8_t color);
|
||||
#endif // __SSD1306_OLED_H
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
#include "tmc2240.h"
|
||||
|
||||
tmc2240_t _tmc2240[TMC2240_MAX];
|
||||
|
||||
static void tmc2240_write(tmc2240_index_e index, uint8_t *data)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
DBG_ASSERT(tmc->spi != NULL __DBG_LINE);
|
||||
|
||||
tmc->spi->gpios.cs->reset(*tmc->spi->gpios.cs);
|
||||
for (uint16_t i = 0; i < 5; i++)
|
||||
{
|
||||
tmc->spi->interface.u.normal.spi_send(tmc->spi, data[i]);
|
||||
}
|
||||
tmc->spi->gpios.cs->set(*tmc->spi->gpios.cs);
|
||||
}
|
||||
|
||||
static void tmc2240_read(tmc2240_index_e index, uint8_t *wdata, uint8_t *rdata)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
DBG_ASSERT(tmc->spi != NULL __DBG_LINE);
|
||||
|
||||
tmc->spi->gpios.cs->reset(*tmc->spi->gpios.cs);
|
||||
for (uint16_t i = 0; i < 5; i++)
|
||||
{
|
||||
rdata[i] = tmc->spi->interface.u.normal.spi_send(tmc->spi, wdata[i]);
|
||||
}
|
||||
tmc->spi->gpios.cs->set(*tmc->spi->gpios.cs);
|
||||
|
||||
__NOP();
|
||||
__NOP();
|
||||
__NOP();
|
||||
|
||||
tmc->spi->gpios.cs->reset(*tmc->spi->gpios.cs);
|
||||
for (uint16_t i = 0; i < 5; i++)
|
||||
{
|
||||
rdata[i] = tmc->spi->interface.u.normal.spi_send(tmc->spi, wdata[i]);
|
||||
}
|
||||
tmc->spi->gpios.cs->set(*tmc->spi->gpios.cs);
|
||||
}
|
||||
|
||||
static void tmc2240_reg_write(tmc2240_index_e index, uint8_t reg, uint32_t data)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
DBG_ASSERT(tmc->spi != NULL __DBG_LINE);
|
||||
|
||||
uint8_t wdata[5] = {0};
|
||||
wdata[0] = TMC2240_HIGHT_BIT | reg;
|
||||
wdata[1] = (data >> 24) & 0xFF;
|
||||
wdata[2] = (data >> 16) & 0xFF;
|
||||
wdata[3] = (data >> 8) & 0xFF;
|
||||
wdata[4] = data & 0xFF;
|
||||
tmc2240_write(index, wdata);
|
||||
}
|
||||
|
||||
static uint32_t tmc2240_reg_read(tmc2240_index_e index, uint8_t reg)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
DBG_ASSERT(tmc->spi != NULL __DBG_LINE);
|
||||
|
||||
uint8_t wdata[5] = {0};
|
||||
uint8_t rdata[5] = {0};
|
||||
wdata[0] = reg;
|
||||
tmc2240_read(index, wdata, rdata);
|
||||
return (rdata[1] << 24) | (rdata[2] << 16) | (rdata[3] << 8) | rdata[4];
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置TMC2240步进电机驱动器
|
||||
*
|
||||
* 该函数用于配置TMC2240步进电机驱动器的各种参数,包括步进电机的工作模式、加减速曲线等。
|
||||
*
|
||||
* @param index TMC2240步进电机驱动器的索引,用于指定要配置的驱动器。
|
||||
*/
|
||||
static void tmc2240_config_write(tmc2240_index_e index)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
tmc->config.gconf.data = 0x00000000;
|
||||
|
||||
tmc->config.chopconf.data = 0x00410153;
|
||||
tmc->config.chopconf.bits.mres = TMC2240_MRES_8;
|
||||
|
||||
tmc->config.drvconf.data = 0x00000021;
|
||||
tmc->config.global_scaler.data = 0x00000000;
|
||||
|
||||
tmc->config.ihold_irun.bits.ihold = 31;
|
||||
tmc->config.ihold_irun.bits.irun = 31;
|
||||
tmc->config.ihold_irun.bits.iholddelay = 0;
|
||||
tmc->config.ihold_irun.bits.irundelay = 0;
|
||||
|
||||
tmc->config.pwmconf.data = 0xC44C261E;
|
||||
tmc->config.gstat.data = 0x00000007;
|
||||
tmc2240_reg_write(index, TMC2240_GCONF, tmc->config.gconf.data);
|
||||
tmc2240_reg_write(index, TMC2240_CHOPCONF, tmc->config.chopconf.data);
|
||||
tmc2240_reg_write(index, TMC2240_DRV_CONF, tmc->config.drvconf.data);
|
||||
tmc2240_reg_write(index, TMC2240_GLOBAL_SCALER, tmc->config.global_scaler.data);
|
||||
tmc2240_reg_write(index, TMC2240_IHOLD_IRUN, tmc->config.ihold_irun.data);
|
||||
tmc2240_reg_write(index, TMC2240_PWMCONF, tmc->config.pwmconf.data);
|
||||
tmc2240_reg_write(index, TMC2240_GSTAT, tmc->config.gstat.data);
|
||||
}
|
||||
|
||||
static void _tmc2240_motor_update(tmc2240_index_e index)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
switch (tmc->config.chopconf.bits.mres)
|
||||
{
|
||||
case TMC2240_MRES_256:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 256;
|
||||
break;
|
||||
case TMC2240_MRES_128:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 128;
|
||||
break;
|
||||
case TMC2240_MRES_64:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 64;
|
||||
break;
|
||||
case TMC2240_MRES_32:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 32;
|
||||
break;
|
||||
case TMC2240_MRES_16:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 16;
|
||||
break;
|
||||
case TMC2240_MRES_8:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 8;
|
||||
break;
|
||||
case TMC2240_MRES_4:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 4;
|
||||
break;
|
||||
case TMC2240_MRES_2:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE / 2;
|
||||
break;
|
||||
case TMC2240_MRES_1:
|
||||
tmc->motor.step_angle = MOTOR_42_STEP_ANGLE;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
tmc->motor.circle_pulse = 360 / tmc->motor.step_angle;
|
||||
}
|
||||
|
||||
static void _tmc2240_enable(tmc2240_index_e index, BOOL enable)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
BOOL state = tmc->en->read(*tmc->en) == 0 ? TRUE : FALSE;
|
||||
|
||||
if (state == enable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable == TRUE)
|
||||
{
|
||||
PWM_START(tmc->timer, tmc->time_ch);
|
||||
tmc->en->reset(*tmc->en);
|
||||
tmc2240_reg_write(index, TMC2240_CHOPCONF, tmc->config.chopconf.data);
|
||||
}
|
||||
else
|
||||
{
|
||||
PWM_STOP(tmc->timer, tmc->time_ch);
|
||||
tmc->en->set(*tmc->en);
|
||||
chopconf_u chopconf;
|
||||
osel_memset((uint8_t *)&chopconf, 0, sizeof(chopconf_u));
|
||||
chopconf.bits.mres = TMC2240_MRES_1;
|
||||
tmc2240_reg_write(index, TMC2240_CHOPCONF, chopconf.data);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tmc2240_direction(tmc2240_index_e index, tmc2240_direction_e dir)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
if (dir == TMC2240_FORWARD)
|
||||
{
|
||||
tmc->dir->reset(*tmc->dir);
|
||||
}
|
||||
else
|
||||
{
|
||||
tmc->dir->set(*tmc->dir);
|
||||
}
|
||||
}
|
||||
|
||||
void tmc2240_init(tmc2240_index_e index, SPI_TypeDef *SPIx, TIM_TypeDef *timer, uint32_t time_ch, spi_gpio_group_t *gpios)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
osel_memset((uint8_t *)tmc, 0, sizeof(tmc2240_t));
|
||||
|
||||
tmc->timer = timer;
|
||||
tmc->time_ch = time_ch;
|
||||
tmc->spi = spi_create(SPI_TYPE_NORMAL, *gpios, 0);
|
||||
DBG_ASSERT(tmc->spi != NULL __DBG_LINE);
|
||||
tmc->spi->interface.hardware_enable(tmc->spi, SPIx);
|
||||
{
|
||||
tmc->default_tm.sysclk = SystemCoreClock / 1000;
|
||||
tmc->default_tm.psc = PWM_GET_PSC(tmc->timer);
|
||||
tmc->default_tm.arr = PWM_GET_ARR(tmc->timer);
|
||||
tmc->default_tm.freq = PWM_GET_FREQ(tmc->timer);
|
||||
}
|
||||
tmc->params.percent = TMC2240_PWM_DUTY_DEFAULT;
|
||||
tmc->params.arr = 0;
|
||||
tmc->params.freq = 0;
|
||||
tmc->params.enable = FALSE;
|
||||
tmc->params.direction = TMC2240_FORWARD;
|
||||
tmc2240_config_write(index);
|
||||
_tmc2240_motor_update(index);
|
||||
}
|
||||
|
||||
tmc2240_t *tmc2240_get(tmc2240_index_e index)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
return &_tmc2240[index];
|
||||
}
|
||||
|
||||
void tmc2240_percent(tmc2240_index_e index, float32 percent)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
PWM_SET_DUTY(tmc->timer, tmc->time_ch, ABS(percent));
|
||||
}
|
||||
|
||||
void tmc2240_config_read(tmc2240_index_e index)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
tmc->read_config.gconf.data = tmc2240_reg_read(index, TMC2240_GCONF);
|
||||
tmc->read_config.chopconf.data = tmc2240_reg_read(index, TMC2240_CHOPCONF);
|
||||
tmc->read_config.drvconf.data = tmc2240_reg_read(index, TMC2240_DRV_CONF);
|
||||
tmc->read_config.global_scaler.data = tmc2240_reg_read(index, TMC2240_GLOBAL_SCALER);
|
||||
tmc->read_config.ihold_irun.data = tmc2240_reg_read(index, TMC2240_IHOLD_IRUN);
|
||||
tmc->read_config.pwmconf.data = tmc2240_reg_read(index, TMC2240_PWMCONF);
|
||||
tmc->read_config.gstat.data = tmc2240_reg_read(index, TMC2240_GSTAT);
|
||||
tmc->data.tmc2240_adc_temp = tmc2240_reg_read(index, TMC2240_ADC_TEMP);
|
||||
tmc->data.tmc2240_temperature = (float32)(tmc->data.tmc2240_adc_temp - 2038) / 7.7;
|
||||
}
|
||||
|
||||
void tmc2240_test(tmc2240_index_e index)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
_tmc2240_enable(index, tmc->params.enable);
|
||||
|
||||
if (tmc->params.enable == TRUE)
|
||||
{
|
||||
_tmc2240_direction(index, tmc->params.direction);
|
||||
|
||||
if (PWM_GET_ARR(tmc->timer) != tmc->params.arr)
|
||||
{
|
||||
if (tmc->params.arr == 0)
|
||||
{
|
||||
tmc->params.arr = tmc->default_tm.arr;
|
||||
}
|
||||
PWM_SET_ARR(tmc->timer, tmc->params.arr);
|
||||
tmc->params.freq = PWM_GET_FREQ(tmc->timer);
|
||||
}
|
||||
|
||||
tmc2240_percent(index, tmc->params.percent);
|
||||
|
||||
if (tmc->config.chopconf.bits.mres != tmc->read_config.chopconf.bits.mres)
|
||||
{
|
||||
tmc2240_reg_write(index, TMC2240_CHOPCONF, tmc->config.chopconf.data);
|
||||
_tmc2240_motor_update(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tmc2240_motor_set_angle(tmc2240_index_e index, int32_t angle)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
if (angle == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tmc->params.enable = FALSE;
|
||||
_tmc2240_enable(index, tmc->params.enable);
|
||||
tmc->motor.pulse_count = 0;
|
||||
|
||||
if (angle > 0)
|
||||
{
|
||||
tmc->params.direction = TMC2240_FORWARD;
|
||||
}
|
||||
else
|
||||
{
|
||||
tmc->params.direction = TMC2240_BACKWARD;
|
||||
}
|
||||
|
||||
tmc->motor.pulse_count = ABS(angle) / tmc->motor.step_angle;
|
||||
tmc->motor.step_angle_count = 0;
|
||||
|
||||
tmc->params.enable = TRUE;
|
||||
|
||||
_tmc2240_direction(index, tmc->params.direction);
|
||||
if (PWM_GET_ARR(tmc->timer) != tmc->params.arr)
|
||||
{
|
||||
if (tmc->params.arr == 0)
|
||||
{
|
||||
tmc->params.arr = tmc->default_tm.arr;
|
||||
}
|
||||
PWM_SET_ARR(tmc->timer, tmc->params.arr);
|
||||
tmc->params.freq = PWM_GET_FREQ(tmc->timer);
|
||||
}
|
||||
|
||||
tmc2240_percent(index, tmc->params.percent);
|
||||
|
||||
if (tmc->config.chopconf.bits.mres != tmc->read_config.chopconf.bits.mres)
|
||||
{
|
||||
tmc2240_reg_write(index, TMC2240_CHOPCONF, tmc->config.chopconf.data);
|
||||
_tmc2240_motor_update(index);
|
||||
}
|
||||
_tmc2240_enable(index, tmc->params.enable);
|
||||
}
|
||||
|
||||
void tmc2240_motor_update(tmc2240_index_e index)
|
||||
{
|
||||
DBG_ASSERT(index < TMC2240_MAX __DBG_LINE);
|
||||
tmc2240_t *tmc = &_tmc2240[index];
|
||||
DBG_ASSERT(tmc != NULL __DBG_LINE);
|
||||
|
||||
if (tmc->motor.pulse_count > 0)
|
||||
{
|
||||
tmc->motor.pulse_count--; // 脉冲数
|
||||
tmc->motor.step_angle_count++; // 步距个数
|
||||
if (tmc->params.direction == TMC2240_FORWARD)
|
||||
{
|
||||
tmc->motor.add_pulse_count++; /* 绝对位置++ */
|
||||
}
|
||||
else
|
||||
{
|
||||
tmc->motor.add_pulse_count--; /* 绝对位置-- */
|
||||
}
|
||||
}
|
||||
|
||||
/* 当脉冲数等于0的时候 代表需要发送的脉冲个数已完成,停止输出 */
|
||||
|
||||
if (tmc->motor.pulse_count <= 0)
|
||||
{
|
||||
tmc->params.enable = FALSE;
|
||||
_tmc2240_enable(index, tmc->params.enable);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,372 @@
|
|||
/**
|
||||
* @file tmc2240.h
|
||||
* @author xushenghao
|
||||
* @brief TMC2240驱动头文件
|
||||
* @version 0.1
|
||||
* @note
|
||||
* 1. 芯片VM需要供电,否则SPI无法正常通信
|
||||
* 2. 42步进电机每步1.8度,1圈200步。1/8细分,1步=0.225度,电机转一周需要1600个脉冲
|
||||
*/
|
||||
|
||||
#ifndef __TMC2240_H
|
||||
#define __TMC2240_H
|
||||
#include "main.h"
|
||||
#include "spis.h"
|
||||
|
||||
#define MOTOR_42_STEP_ANGLE 1.8f // 42步进电机每步1.8度
|
||||
#define TMC2240_PWM_DUTY_DEFAULT 50 // PWM默认占空比
|
||||
|
||||
/*
|
||||
0x00 = 0x00002108 ;; writing GCONF @ address 0=0x00 with 0x00002108=8456=0.0
|
||||
0x03 = 0x00000000 ;; writing SLAVECONF @ address 1=0x03 with 0x00000000=0=0.0
|
||||
0x04 = 0x4001682C ;; writing IOIN @ address 2=0x04 with 0x4001682C=1073834028=0.0
|
||||
0x0A = 0x00000021 ;; writing DRV_CONF @ address 3=0x0A with 0x00000021=33=0.0
|
||||
0x0B = 0x00000000 ;; writing GLOBAL_SCALER @ address 4=0x0B with 0x00000000=0=0.0
|
||||
0x10 = 0x00001208 ;; writing IHOLD_IRUN @ address 5=0x10 with 0x00001208=4616=0.0
|
||||
0x11 = 0x00000000 ;; writing TPOWERDOWN @ address 6=0x11 with 0x00000000=0=0.0
|
||||
0x13 = 0x00000000 ;; writing TPWMTHRS @ address 7=0x13 with 0x00000000=0=0.0
|
||||
0x14 = 0x000003BE ;; writing TCOOLTHRS @ address 8=0x14 with 0x000003BE=958=0.0
|
||||
0x15 = 0x00000000 ;; writing THIGH @ address 9=0x15 with 0x00000000=0=0.0
|
||||
0x2D = 0x00000000 ;; writing DIRECT_MODE @ address 10=0x2D with 0x00000000=0=0.0
|
||||
0x38 = 0x00000000 ;; writing ENCMODE @ address 11=0x38 with 0x00000000=0=0.0
|
||||
0x39 = 0x00000000 ;; writing X_ENC @ address 12=0x39 with 0x00000000=0=0.0
|
||||
0x3A = 0x00010000 ;; writing ENC_CONST @ address 13=0x3A with 0x00010000=65536=0.0
|
||||
0x52 = 0x0B920F25 ;; writing OTW_OV_VTH @ address 14=0x52 with 0x0B920F25=194121509=0.0
|
||||
0x60 = 0xAAAAB554 ;; writing MSLUT[0] @ address 15=0x60 with 0xAAAAB554=0=0.0
|
||||
0x61 = 0x4A9554AA ;; writing MSLUT[1] @ address 16=0x61 with 0x4A9554AA=1251300522=0.0
|
||||
0x62 = 0x24492929 ;; writing MSLUT[2] @ address 17=0x62 with 0x24492929=608774441=0.0
|
||||
0x63 = 0x10104222 ;; writing MSLUT[3] @ address 18=0x63 with 0x10104222=269500962=0.0
|
||||
0x64 = 0xFBFFFFFF ;; writing MSLUT[4] @ address 19=0x64 with 0xFBFFFFFF=0=0.0
|
||||
0x65 = 0xB5BB777D ;; writing MSLUT[5] @ address 20=0x65 with 0xB5BB777D=0=0.0
|
||||
0x66 = 0x49295556 ;; writing MSLUT[6] @ address 21=0x66 with 0x49295556=1227445590=0.0
|
||||
0x67 = 0x00404222 ;; writing MSLUT[7] @ address 22=0x67 with 0x00404222=4211234=0.0
|
||||
0x68 = 0xFFFF8056 ;; writing MSLUTSEL @ address 23=0x68 with 0xFFFF8056=0=0.0
|
||||
0x69 = 0x00F70000 ;; writing MSLUTSTART @ address 24=0x69 with 0x00F70000=16187392=0.0
|
||||
0x6C = 0x00410153 ;; writing CHOPCONF @ address 25=0x6C with 0x00410153=4260179=0.0
|
||||
0x6D = 0x00040000 ;; writing COOLCONF @ address 26=0x6D with 0x00040000=262144=0.0
|
||||
0x70 = 0xC44C001E ;; writing PWMCONF @ address 27=0x70 with 0xC44C001E=0=0.0
|
||||
0x74 = 0x00000000 ;; writing SG4_THRS @ address 28=0x74 with 0x00000000=0=0.0
|
||||
*/
|
||||
|
||||
#define TMC2240_GCONF 0x00
|
||||
#define TMC2240_GSTAT 0x01
|
||||
#define TMC2240_IFCNT 0x02
|
||||
#define TMC2240_SLAVECONF 0x03
|
||||
#define TMC2240_IOIN 0x04
|
||||
#define TMC2240_DRV_CONF 0x0A
|
||||
#define TMC2240_GLOBAL_SCALER 0x0B
|
||||
|
||||
#define TMC2240_IHOLD_IRUN 0x10
|
||||
#define TMC2240_TPOWERDOWN 0x11
|
||||
#define TMC2240_TSTEP 0x12
|
||||
#define TMC2240_TPWMTHRS 0x13
|
||||
#define TMC2240_TCOOLTHRS 0x14
|
||||
#define TMC2240_THIGH 0x15
|
||||
|
||||
#define TMC2240_DIRECT_MODE 0x2D
|
||||
|
||||
#define TMC2240_ENCMODE 0x38
|
||||
#define TMC2240_XENC 0x39
|
||||
#define TMC2240_ENC_CONST 0x3A
|
||||
#define TMC2240_ENC_STATUS 0x3B
|
||||
#define TMC2240_ENC_LATCH 0x3C
|
||||
|
||||
#define TMC2240_ADC_VSUPPLY_AIN 0x50
|
||||
#define TMC2240_ADC_TEMP 0x51
|
||||
#define TMC2240_OTW_OV_VTH 0x52
|
||||
|
||||
#define TMC2240_MSLUT0 0x60
|
||||
#define TMC2240_MSLUT1 0x61
|
||||
#define TMC2240_MSLUT2 0x62
|
||||
#define TMC2240_MSLUT3 0x63
|
||||
#define TMC2240_MSLUT4 0x64
|
||||
#define TMC2240_MSLUT5 0x65
|
||||
#define TMC2240_MSLUT6 0x66
|
||||
#define TMC2240_MSLUT7 0x67
|
||||
#define TMC2240_MSLUTSEL 0x68
|
||||
#define TMC2240_MSLUTSTART 0x69
|
||||
#define TMC2240_MSCNT 0x6A
|
||||
#define TMC2240_MSCURACT 0x6B
|
||||
#define TMC2240_CHOPCONF 0x6C
|
||||
#define TMC2240_COOLCONF 0x6D
|
||||
#define TMC2240_DCCTRL 0x6E
|
||||
#define TMC2240_DRVSTATUS 0x6F
|
||||
|
||||
#define TMC2240_PWMCONF 0x70
|
||||
#define TMC2240_PWMSCALE 0x71
|
||||
#define TMC2240_PWM_AUTO 0x72
|
||||
#define TMC2240_SG4_THRS 0x74
|
||||
#define TMC2240_SG4_RESULT 0x75
|
||||
#define TMC2240_SG4_IND 0x76
|
||||
|
||||
#define TMC2240_HIGHT_BIT 0x80
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TMC2240_1,
|
||||
TMC2240_MAX,
|
||||
} tmc2240_index_e;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TMC2240_FORWARD, // 正转
|
||||
TMC2240_BACKWARD, // 反转
|
||||
} tmc2240_direction_e;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TMC2240_MRES_256,
|
||||
TMC2240_MRES_128,
|
||||
TMC2240_MRES_64,
|
||||
TMC2240_MRES_32,
|
||||
TMC2240_MRES_16,
|
||||
TMC2240_MRES_8,
|
||||
TMC2240_MRES_4,
|
||||
TMC2240_MRES_2,
|
||||
TMC2240_MRES_1, // FULL STEP
|
||||
} tmc2240_mres_e;
|
||||
|
||||
// 0x00 GCONF
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
|
||||
uint32_t reserved1 : 1;
|
||||
/**
|
||||
* 停止之前的步骤执行超时检测。
|
||||
* 0x0:正常时间:2^20个时钟
|
||||
* 0x1:短时间:2^18个时钟
|
||||
*/
|
||||
uint32_t fast_standstill : 1;
|
||||
/**
|
||||
* 启用StealthChop2模式。
|
||||
* 0x0:无StealthChop2
|
||||
* 0x1:StealthChop2电压PWM模式使能(取决于速度阈值)。从关闭状态切换
|
||||
在静止状态下和在lHOLD = 时为开状态 仅限额定电流。
|
||||
*/
|
||||
uint32_t en_pwm_mode : 1;
|
||||
/**
|
||||
* 启用StealthChop2的步进输入筛选
|
||||
* 0x0:无StealthChop2
|
||||
* 0x1:StealthChop2电压PWM模式使能
|
||||
(取决于速度阈值)。从关闭状态切换
|
||||
在静止状态下和在lHOLD=时为开状态
|
||||
仅限额定电流。
|
||||
*/
|
||||
uint32_t multistep_filt : 1;
|
||||
/**
|
||||
* 更改电机方向/方向标志
|
||||
* 0x0:默认电机方向
|
||||
* 0x1:电机方向相反
|
||||
*/
|
||||
uint32_t shaft : 1;
|
||||
uint32_t diag0_error : 1;
|
||||
uint32_t diag0_otpw : 1;
|
||||
uint32_t diag0_stall : 1;
|
||||
uint32_t diag1_stall : 1;
|
||||
uint32_t diag1_index : 1;
|
||||
uint32_t diag1_onstate : 1;
|
||||
uint32_t reserved2 : 1;
|
||||
uint32_t diag0_pushpull : 1;
|
||||
uint32_t diag1_pushpull : 1;
|
||||
uint32_t small_hysteresis : 1;
|
||||
/**
|
||||
* 电机硬停止功能启用。
|
||||
* 0x0:正常运行
|
||||
* 0x1:紧急停止:ENCA停止定序器
|
||||
当绑得很高时(不执行任何步骤
|
||||
定序器、电机进入停顿状态)。
|
||||
*/
|
||||
uint32_t stop_enable : 1;
|
||||
/**
|
||||
* 通过以下方式启用直接motpr相电流控制
|
||||
* 0x0:正常运行
|
||||
* 0x1:电机线圈电流和极性直接
|
||||
通过串口编程:寄存器
|
||||
直接模式(0x2D)指定带符号线圈A
|
||||
电流(位8..0)和线圈B电流(位24..16)。在……里面
|
||||
在此模式下,电流按lHOLD设置进行定标。
|
||||
基于速度的StealthChop2电流调节
|
||||
在此模式下不可用。自动的
|
||||
StealthChop2电流调节仅适用于
|
||||
步进电机速度低。
|
||||
*/
|
||||
uint32_t direct_mode : 1;
|
||||
} bits;
|
||||
} gconf_u;
|
||||
|
||||
// 0x01 GSTAT
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
uint32_t reset : 1;
|
||||
uint32_t drv_err : 1;
|
||||
uint32_t uv_cp : 1;
|
||||
uint32_t register_reset : 1;
|
||||
uint32_t vm_uvlo : 1;
|
||||
} bits;
|
||||
} gstat_u;
|
||||
|
||||
// 0x0A DRVCONF
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
uint32_t current_range : 2; // 0-1
|
||||
uint32_t reserved1 : 2; // 2-3
|
||||
uint32_t slope_control : 2; // 4-5
|
||||
} bits;
|
||||
} drvconf_u;
|
||||
|
||||
// 0x0B GLOBAL_SCALER
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
uint32_t global_scale : 8; // 0-7
|
||||
} bits;
|
||||
} global_scaler_u;
|
||||
|
||||
// 0x10 IHOLD_IRUN
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
uint32_t ihold : 5; // 0-4
|
||||
uint32_t reserved1 : 3; // 5-7
|
||||
uint32_t irun : 5; // 8-12
|
||||
uint32_t reserved2 : 3; // 13-15
|
||||
uint32_t iholddelay : 4; // 16-19
|
||||
uint32_t reserved3 : 4; // 20-23
|
||||
uint32_t irundelay : 4; // 24-27
|
||||
} bits;
|
||||
} ihold_irun_u;
|
||||
|
||||
// 0x6C CHOPCONF
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
uint32_t toff : 4; // 0-3
|
||||
uint32_t hstrt : 3; // 4-6
|
||||
uint32_t hend : 4; // 7-10
|
||||
uint32_t fd3 : 1; // 11
|
||||
uint32_t disfdcc : 1; // 12
|
||||
uint32_t reserved1 : 1;
|
||||
uint32_t chm : 1; // 14
|
||||
uint32_t tbl : 2; // 15-16
|
||||
uint32_t reserved2 : 1;
|
||||
uint32_t vhighfs : 1; // 18
|
||||
uint32_t vhighchm : 1; // 19
|
||||
uint32_t tpfd : 4; // 20-23
|
||||
uint32_t mres : 4; // 24-27
|
||||
uint32_t intpol : 1; // 28
|
||||
uint32_t dedge : 1; // 29
|
||||
uint32_t diss2g : 1; // 30
|
||||
uint32_t diss2vs : 1; // 31
|
||||
} bits;
|
||||
} chopconf_u;
|
||||
|
||||
// 0x70 PWMCONF
|
||||
typedef union
|
||||
{
|
||||
uint32_t data;
|
||||
struct
|
||||
{
|
||||
/**
|
||||
* 用户定义的 PWM 幅度偏移(0-255)与静止状态下的全电机电流(CS_ACTUAL=31)相关。 (重置默认值=30) 使用 PWM_OFS 作为自动缩放的初始值,
|
||||
* 以加快自动调谐过程。为此,请将 PWM_OFS 设置为确定的、特定于应用的值,并将 pwm_autoscale 设置为 0。之后,将 pwm_autoscale 设置为 1。
|
||||
* 完成后启用 StealthChop2。 PWM_OFS = 0 将禁用将电机电流缩放到低于电机特定的较低测量阈值。此设置应仅在某些条件下使用,
|
||||
* 例如当电源电压可以上下变化两倍或更多时。它可以防止电机超出调节范围,但也会防止电流降到调节限制以下。 PWM_OFS > 0 允许自动缩放到低 PWM 占空比,
|
||||
* 甚至低于较低的调节阈值。这允许基于实际(保持)电流比例(寄存器 IHOLD_IRUN)的低(静止)电流设置。
|
||||
*/
|
||||
uint32_t pwm_ofs : 8;
|
||||
/**
|
||||
* PWM 幅度的速度依赖梯度: PWM_GRAD x 256 / TSTEP 此值加到 PWM_OFS 以补偿速度依赖的电机反电动势。 使用 PWM_GRAD 作为自动缩放的初始值,
|
||||
* 以加快自动调谐过程。为此,请将 PWM_GRAD 设置为确定的、特定于应用的值,并将 pwm_autoscale 设置为 0。之后,将 pwm_autoscale 设置为 1。
|
||||
* 完成后启用 StealthChop2。 提示: 初始调谐后,可以从 PWM_GRAD_AUTO 中读取所需的初始值。
|
||||
*/
|
||||
uint32_t pwm_grad : 8;
|
||||
uint32_t pwm_freq : 2;
|
||||
uint32_t pwm_autoscale : 1;
|
||||
uint32_t pwm_autograd : 1;
|
||||
} bits;
|
||||
} pwmconf_u;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gconf_u gconf; // 0x00 GCONF
|
||||
gstat_u gstat; // 0x01 GSTAT
|
||||
drvconf_u drvconf; // 0x0A DRVCONF
|
||||
global_scaler_u global_scaler; // 0x0B GLOBAL_SCALER
|
||||
ihold_irun_u ihold_irun; // 0x10 IHOLD_IRUN
|
||||
chopconf_u chopconf; // 0x6C CHOPCONF
|
||||
pwmconf_u pwmconf; // 0x70 PWMCONF
|
||||
} tmc2240_config_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gpio_t *en; ///< EN_PIN
|
||||
gpio_t *dir; ///< DIR_PIN
|
||||
TIM_TypeDef *timer;
|
||||
uint32_t time_ch;
|
||||
spi_t *spi;
|
||||
|
||||
tmc2240_config_t config;
|
||||
tmc2240_config_t read_config;
|
||||
|
||||
uint32_t step;
|
||||
__IO uint32_t step_count;
|
||||
|
||||
// PRIVATE
|
||||
struct
|
||||
{
|
||||
uint32_t sysclk; // 系统时钟
|
||||
uint32_t psc; // 预分频系数
|
||||
uint16_t arr; // 自动重装值 auto reload value
|
||||
uint32_t freq; // 频率
|
||||
} default_tm;
|
||||
struct
|
||||
{
|
||||
BOOL enable; // 使能
|
||||
tmc2240_direction_e direction; // 方向
|
||||
float32 percent; // 占空比
|
||||
uint16_t arr; // 自动重装值(改变速度)
|
||||
uint32_t freq; // 频率
|
||||
} params;
|
||||
|
||||
struct
|
||||
{
|
||||
float32 step_angle; // 步进角度
|
||||
uint16_t circle_pulse; // 一圈脉冲数
|
||||
|
||||
__IO int32_t add_pulse_count; /* 脉冲个数累计 */
|
||||
__IO uint32_t pulse_count; /* 脉冲个数记录 */
|
||||
__IO uint32_t step_angle_count; /* 步距个数 */
|
||||
} motor;
|
||||
|
||||
struct
|
||||
{
|
||||
uint16_t tmc2240_adc_temp; // 温度ADC值
|
||||
float32 tmc2240_temperature; // 温度
|
||||
} data;
|
||||
|
||||
} tmc2240_t;
|
||||
|
||||
void tmc2240_init(tmc2240_index_e index, SPI_TypeDef *SPIx, TIM_TypeDef *timer, uint32_t time_ch, spi_gpio_group_t *gpios);
|
||||
tmc2240_t *tmc2240_get(tmc2240_index_e index);
|
||||
|
||||
void tmc2240_motor_set_angle(tmc2240_index_e index, int32_t angle);
|
||||
void tmc2240_motor_update(tmc2240_index_e index);
|
||||
|
||||
void tmc2240_test(tmc2240_index_e index);
|
||||
void tmc2240_config_read(tmc2240_index_e index);
|
||||
#endif // __TMC2240_H
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
#include "bootload.h"
|
||||
#include "ymodem.h"
|
||||
#include "sys.h"
|
||||
#include "delay.h"
|
||||
#include "cmac.h"
|
||||
#define AES_CMAC_DIGEST_LENGTH 16
|
||||
typedef void (*fnc_ptr)(void); // 用于跳转到应用程序的函数指针
|
||||
static bootload_transmit_callback transmit_callback = NULL;
|
||||
static bootload_end_callback end_callback = NULL;
|
||||
uint8_t upgrade_key[] =
|
||||
{
|
||||
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6,
|
||||
0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; // 密钥
|
||||
|
||||
static AES_CMAC_CTX upgrade_ctx; /**< AES CMAC context for upgrade process */
|
||||
static uint8_t pre_upgrade_mic[AES_CMAC_DIGEST_LENGTH]; /**< MIC (Message Integrity Code) before upgrade */
|
||||
static uint8_t upgrade_mic[AES_CMAC_DIGEST_LENGTH]; /**< MIC (Message Integrity Code) after upgrade */
|
||||
static uint32_t flashdestination = BOOTLOAD_APP_BACKUP_ADDR_START; /**< Flash destination address for bootloading */
|
||||
static uint32_t upgrade_size = 0; /**< Size of the upgrade */
|
||||
static uint8_t read_cache[LL_FLASH_PAGE_SIZE]; /**< Read cache for flash pages */
|
||||
static uint8_t data_src_from; /**< Data source for bootloading */
|
||||
|
||||
/**
|
||||
* @brief Perform bootload inspection.
|
||||
*
|
||||
* This function calls the `rym_process()` function to perform bootload inspection.
|
||||
*/
|
||||
void bootload_inspection(void)
|
||||
{
|
||||
rym_process();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the bootload timeout has occurred.
|
||||
*
|
||||
* This function calls the `rym_timeout()` function to determine if the bootload timeout has occurred.
|
||||
*
|
||||
* @return TRUE if the bootload timeout has occurred, FALSE otherwise.
|
||||
*/
|
||||
BOOL bootload_timeout(void)
|
||||
{
|
||||
return rym_timeout();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the beginning of the bootloading process.
|
||||
*
|
||||
* This function is responsible for handling the initial steps of the bootloading process.
|
||||
* It checks if the length of the data to be bootloaded is greater than the end address of the application area.
|
||||
* If the length is greater, it returns an error code indicating that the bootloading cannot proceed.
|
||||
* Otherwise, it sets the flash destination address to the start address of the backup area,
|
||||
* erases the bank of the flash memory where the application is stored,
|
||||
* and initializes the AES-CMAC context for the upgrade process.
|
||||
*
|
||||
* @param p Pointer to the data to be bootloaded.
|
||||
* @param len Length of the data to be bootloaded.
|
||||
* @return Error code indicating the result of the operation.
|
||||
* - RYM_CODE_CAN: If the length of the data is greater than the end address of the application area.
|
||||
* - RYM_CODE_NONE: If the operation is successful.
|
||||
*/
|
||||
static rym_code_e on_begin(uint8_t *p, uint32_t len)
|
||||
{
|
||||
if (len > BOOTLOAD_APP_END_ADDRESS)
|
||||
{
|
||||
return RYM_CODE_CAN;
|
||||
}
|
||||
|
||||
flashdestination = BOOTLOAD_APP_BACKUP_ADDR_START;
|
||||
LL_FLASH_Unlock(FLASH);
|
||||
LL_FLASH_EraseBank(LL_FLASH_BANK2);
|
||||
LL_FLASH_Lock(FLASH);
|
||||
|
||||
AES_CMAC_Init(&upgrade_ctx);
|
||||
AES_CMAC_SetKey(&upgrade_ctx, upgrade_key);
|
||||
return RYM_CODE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the received data and programs it into the flash memory.
|
||||
*
|
||||
* This function unlocks the flash memory, programs the received data into the specified flash destination,
|
||||
* locks the flash memory again, updates the AES-CMAC context with the received data, and increments the flash destination.
|
||||
*
|
||||
* @param p Pointer to the data buffer.
|
||||
* @param len Length of the data buffer.
|
||||
* @return The result code indicating the success or failure of the operation.
|
||||
*/
|
||||
static rym_code_e on_data(uint8_t *p, uint32_t len)
|
||||
{
|
||||
LL_FLASH_Unlock(FLASH);
|
||||
LL_FLASH_Program(flashdestination, p, len);
|
||||
LL_FLASH_Lock(FLASH);
|
||||
AES_CMAC_Update(&upgrade_ctx, p, len);
|
||||
flashdestination += len;
|
||||
return RYM_CODE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate the MIC (Message Integrity Code) for the firmware upgrade.
|
||||
*
|
||||
* This function calculates the MIC using AES-CMAC algorithm for the firmware upgrade data.
|
||||
* It reads the firmware data from flash memory in pages and updates the MIC calculation.
|
||||
* Finally, it compares the calculated MIC with the pre-upgrade MIC to determine the success of the upgrade.
|
||||
*
|
||||
* @param p Pointer to the firmware data.
|
||||
* @param len Length of the firmware data.
|
||||
* @return The result code indicating the success or failure of the upgrade process.
|
||||
*/
|
||||
static rym_code_e on_end(uint8_t *p, uint32_t len)
|
||||
{
|
||||
AES_CMAC_Final(pre_upgrade_mic, &upgrade_ctx);
|
||||
upgrade_size = flashdestination - BOOTLOAD_APP_BACKUP_ADDR_START;
|
||||
|
||||
AES_CMAC_Init(&upgrade_ctx);
|
||||
AES_CMAC_SetKey(&upgrade_ctx, upgrade_key);
|
||||
uint32_t start = BOOTLOAD_APP_BACKUP_ADDR_START;
|
||||
uint16_t num = (upgrade_size / LL_FLASH_PAGE_SIZE);
|
||||
uint16_t remain = (upgrade_size % LL_FLASH_PAGE_SIZE);
|
||||
|
||||
// STM32L476RG的flash页大小为2K,先读取整数页,再读取余数
|
||||
for (uint16_t i = 0; i < num; i++)
|
||||
{
|
||||
LL_FLASH_Read((start + i * (LL_FLASH_PAGE_SIZE)), read_cache, LL_FLASH_PAGE_SIZE);
|
||||
AES_CMAC_Update(&upgrade_ctx, read_cache, LL_FLASH_PAGE_SIZE);
|
||||
}
|
||||
|
||||
if (remain)
|
||||
{
|
||||
osel_memset(read_cache, 0, LL_FLASH_PAGE_SIZE);
|
||||
LL_FLASH_Read((start + num * (LL_FLASH_PAGE_SIZE)), read_cache, remain);
|
||||
AES_CMAC_Update(&upgrade_ctx, read_cache, remain);
|
||||
}
|
||||
|
||||
AES_CMAC_Final(upgrade_mic, &upgrade_ctx);
|
||||
|
||||
// 比较mic,相同可以写入标志位告知应用程序升级成功
|
||||
if (osel_memcmp(upgrade_mic, pre_upgrade_mic, AES_CMAC_DIGEST_LENGTH) == 0)
|
||||
{
|
||||
end_callback(TRUE);
|
||||
}
|
||||
else
|
||||
{
|
||||
end_callback(FALSE);
|
||||
}
|
||||
return RYM_CODE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles the transmission of data.
|
||||
*
|
||||
* This function calls the transmit_callback function to transmit the data from the specified source.
|
||||
*
|
||||
* @param p Pointer to the data buffer.
|
||||
* @param len Length of the data buffer.
|
||||
* @return The return code indicating the status of the transmission.
|
||||
*/
|
||||
static rym_code_e on_transmit(uint8_t *p, uint32_t len)
|
||||
{
|
||||
transmit_callback(data_src_from, p, (uint16_t)len);
|
||||
return RYM_CODE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the bootload module.
|
||||
*
|
||||
* This function initializes the bootload module by setting the transmit callback
|
||||
* and configuring the RYM module. It asserts if the initialization fails.
|
||||
*
|
||||
* @param transmit The transmit callback function.
|
||||
*/
|
||||
void bootload_init(bootload_transmit_callback transmit, bootload_end_callback end)
|
||||
{
|
||||
BOOL res = FALSE;
|
||||
|
||||
transmit_callback = transmit;
|
||||
end_callback = end;
|
||||
|
||||
res = rym_init();
|
||||
DBG_ASSERT(res == TRUE __DBG_LINE);
|
||||
|
||||
res = rym_config(on_begin, on_data, on_end, on_transmit, BOOTLOAD_TIMEOUT);
|
||||
DBG_ASSERT(res == TRUE __DBG_LINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the data source index for bootload transmission.
|
||||
*
|
||||
* This function sets the data source index for bootload transmission. The data source index
|
||||
* determines the starting point from which the data will be transmitted.
|
||||
*
|
||||
* @param to_index The index of the data source.
|
||||
*/
|
||||
void bootload_transmit_from(const uint8_t to_index)
|
||||
{
|
||||
data_src_from = to_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Jump to the specified address and execute the bootloader.
|
||||
*
|
||||
* @param address The entry address of the bootloader.
|
||||
*/
|
||||
void bootload_jump(uint32_t address)
|
||||
{
|
||||
// Get the entry address of the application program
|
||||
fnc_ptr jump_to_bootload;
|
||||
jump_to_bootload = (fnc_ptr)(*(__IO uint32_t *)(address + 4));
|
||||
|
||||
// Disable RCC
|
||||
RCC->APB1ENR1 = 0;
|
||||
RCC->APB1ENR2 = 0;
|
||||
RCC->APB2ENR = 0;
|
||||
RCC->AHB1ENR = 0;
|
||||
RCC->AHB2ENR = 0;
|
||||
RCC->AHB3ENR = 0;
|
||||
|
||||
// Disable SysTick
|
||||
SysTick->CTRL = 0;
|
||||
// 清空SysTick
|
||||
SysTick->LOAD = 0;
|
||||
// 清空SysTick
|
||||
SysTick->VAL = 0;
|
||||
// 设置向量表偏移地址
|
||||
SCB->VTOR = address;
|
||||
// 设置堆栈指针
|
||||
sys_msr_msp(*(__IO uint32_t *)address);
|
||||
// 跳转
|
||||
jump_to_bootload();
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
#ifndef __BOOTLOAD_H
|
||||
#define __BOOTLOAD_H
|
||||
#include "lib.h"
|
||||
#include "flash.h"
|
||||
#include "flow.h"
|
||||
|
||||
#define BOOTLOAD_SET_FLAG 0xbb01
|
||||
#define BOOTLOAD_UNSET_FLAG 0xbb02
|
||||
/**
|
||||
* @brief Defines the start address of the application in flash memory.
|
||||
*/
|
||||
#define BOOTLOAD_APP_START_ADDRESS ((uint32_t)0x08000000u)
|
||||
|
||||
/**
|
||||
* @brief Defines the end address of the application in flash memory.
|
||||
*/
|
||||
#define BOOTLOAD_APP_END_ADDRESS (BOOTLOAD_APP_START_ADDRESS + 220 * LL_FLASH_PAGE_SIZE) // 220*2048 = 450560
|
||||
|
||||
/**
|
||||
* @brief Defines the start address of the bootloader in flash memory.
|
||||
*/
|
||||
#define BOOTLOAD_START_ADDRESS BOOTLOAD_APP_END_ADDRESS
|
||||
|
||||
/**
|
||||
* @brief Defines the start address of the backup area for the application in flash memory.
|
||||
*/
|
||||
#define BOOTLOAD_APP_BACKUP_ADDR_START (BOOTLOAD_APP_START_ADDRESS + 256 * LL_FLASH_PAGE_SIZE)
|
||||
|
||||
/**
|
||||
* @brief Defines the timeout for the bootloading process.
|
||||
*
|
||||
* This macro defines the timeout for the bootloading process, in seconds. The default value is 10 seconds.
|
||||
*/
|
||||
#define BOOTLOAD_TIMEOUT 10
|
||||
|
||||
/**
|
||||
* @brief A function pointer type for a bootload transmit callback function.
|
||||
*
|
||||
* This function pointer type is used for a bootload transmit callback function, which is
|
||||
* called when data needs to be transmitted to the bootloader. The function is passed the
|
||||
* source of the data (the index of the data packet), a pointer to the data buffer, and the
|
||||
* length of the data buffer.
|
||||
*
|
||||
* @param data_src The index of the data packet that is being transmitted.
|
||||
* @param buf A pointer to the data buffer.
|
||||
* @param len The length of the data buffer.
|
||||
*/
|
||||
typedef void (*bootload_transmit_callback)(const uint8_t data_src, const uint8_t *buf, const uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief A function pointer type for a bootload end callback function.
|
||||
*
|
||||
* This function pointer type is used for a bootload end callback function, which is called
|
||||
* when the bootloading process is complete. The function takes no parameters and returns no
|
||||
* value.
|
||||
*/
|
||||
typedef void (*bootload_end_callback)(BOOL);
|
||||
|
||||
/**
|
||||
* @brief initializes the bootloader
|
||||
*
|
||||
* This function initializes the bootloader, including setting up the communication
|
||||
* with the host and configuring the flash memory for bootloading.
|
||||
*
|
||||
* @param transmit a pointer to the function that will be called to transmit data to the host
|
||||
*/
|
||||
void bootload_init(bootload_transmit_callback transmit, bootload_end_callback end);
|
||||
|
||||
/**
|
||||
* @brief Transmits data from the specified index.
|
||||
*
|
||||
* This function transmits data from the specified index to the bootloader.
|
||||
*
|
||||
* @param to_index The index from which the data should be transmitted.
|
||||
*/
|
||||
void bootload_transmit_from(const uint8_t to_index);
|
||||
|
||||
/**
|
||||
* @brief Jumps to the specified address.
|
||||
*
|
||||
* This function jumps to the specified address, which is typically the start address of the bootloader.
|
||||
*
|
||||
* @param address The address to jump to.
|
||||
*/
|
||||
void bootload_jump(uint32_t address);
|
||||
|
||||
/**
|
||||
* @brief Performs inspection of the bootloader.
|
||||
*
|
||||
* This function performs inspection of the bootloader, such as checking the version or integrity of the bootloader.
|
||||
*/
|
||||
void bootload_inspection(void);
|
||||
|
||||
/**
|
||||
* @brief Checks if a timeout has occurred.
|
||||
*
|
||||
* This function checks if a timeout has occurred during the bootloading process.
|
||||
*
|
||||
* @return TRUE if a timeout has occurred, FALSE otherwise.
|
||||
*/
|
||||
BOOL bootload_timeout(void);
|
||||
#endif // __BOOTLOAD_H
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
# BOOTLOADER介绍
|
||||
|
||||
|
||||
STM32的BOOTLOADER是在芯片复位或从停机模式唤醒时执行的一段小程序,它负责将用户代码加载到内存中并启动它。STM32F1、F4、H7等不同系列的MCU可能会有不同的BOOTLOADER程序。
|
||||
|
||||
BOOTLOADER通常用于以下几种情况:
|
||||
|
||||
1. 在应用程序无法正常启动时,提供一个后备启动方式。
|
||||
2. 在系统需要进行固件更新时,可以先通过BOOTLOADER加载新的用户代码。
|
||||
3. 用于在线调试或调试无法通过JTAG/SWD接口访问时,可以通过BOOTLOADER加载调试工具。
|
||||
|
||||
BOOTLOADER的设计和实现通常依赖于芯片的内部结构和特性,以及用户代码存储的介质(如内部FLASH,外部SPI FLASH等)。
|
||||
|
||||
一个简单的BOOTLOADER示例可能包括以下步骤:
|
||||
|
||||
1. 复位后,芯片开始执行内部的BOOTLOADER程序。
|
||||
2. 通过某种通信接口(如USART,I2C,SPI)接收新的用户程序代码。
|
||||
3. 将接收到的代码写入用户代码存储区(如内部FLASH)。
|
||||
4. 设置启动引脚或者配置BOOT引导模式寄存器,选择启动用户代码。
|
||||
5. 重启芯片,这次不再执行BOOTLOADER,而是加载并运行新的用户程序代码。
|
||||
|
||||
注意:实际的BOOTLOADER实现可能会更加复杂,包括错误检查和处理、加密解密、固件完整性校验等安全措施。
|
||||
|
||||
# bootload.c 文件说明
|
||||
|
||||
`bootload.c`是一个实现引导加载程序(bootloader)功能的源代码文件。引导加载程序是一段在系统启动时运行的代码,负责初始化硬件设备、建立内存空间映射图,然后加载操作系统内核并将控制权转交给它。
|
||||
|
||||
以下是 `bootload.c`文件中可能包含的主要函数和其功能:
|
||||
|
||||
1. **系统启动函数** :这个函数是引导加载程序的入口点,它负责启动整个引导加载过程。
|
||||
2. **硬件初始化函数** :这些函数负责初始化系统的硬件设备,包括CPU、内存、IO设备等。
|
||||
3. **内存映射设置函数** :这些函数负责建立内存空间的映射图,包括物理内存、虚拟内存的映射关系。
|
||||
4. **操作系统内核加载函数** :这些函数负责加载操作系统内核,包括从存储设备读取内核镜像,加载到内存中,然后跳转到内核的入口点。
|
||||
5. **错误处理函数** :这些函数负责处理在引导加载过程中可能出现的各种错误,包括硬件错误、内核加载错误等。
|
||||
|
||||
|
||||
|
||||
# ymodem.c 文件说明
|
||||
|
||||
`ymodem.c`是一个实现YMODEM协议的源代码文件。YMODEM是一种用于文件传输的协议,它在XMODEM协议的基础上增加了一些新的特性,例如支持更大的文件和文件名传输。
|
||||
|
||||
以下是 `ymodem.c`文件中可能包含的主要函数和其功能:
|
||||
|
||||
1. **初始化和结束传输的函数** :这些函数负责设置传输的开始和结束,包括打开和关闭必要的硬件接口,设置传输参数等。
|
||||
2. **发送和接收数据包的函数** :这些函数负责实际的数据传输,包括将数据打包成YMODEM格式的数据包,通过硬件接口发送和接收数据包,处理数据包的确认和重传等。
|
||||
3. **CRC校验的函数** :这些函数负责计算和检查数据包的CRC(循环冗余校验)值,以确保数据的完整性。
|
||||
4. **处理错误和重试的函数** :这些函数负责处理在传输过程中可能出现的各种错误,包括数据包丢失、CRC校验失败等,并在必要时进行重试。
|
||||
|
||||
此外,`ymodem.c`文件还可能包含一些辅助函数,用于处理如超时、缓冲区管理等问题。
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#include "unity.h"
|
||||
#include "ymodem.c"
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
// 这里可以进行每个测试用例开始前的设置
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
// 这里可以进行每个测试用例结束后的清理
|
||||
}
|
||||
|
||||
void test_CRC16(void)
|
||||
{
|
||||
unsigned char data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
|
||||
TEST_ASSERT_EQUAL_HEX16(EXPECTED_CRC_VALUE, CRC16(data, sizeof(data)));
|
||||
}
|
||||
|
||||
void test_IS_CAP_LETTER(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(IS_CAP_LETTER('A'));
|
||||
TEST_ASSERT_FALSE(IS_CAP_LETTER('a'));
|
||||
}
|
||||
|
||||
void test_IS_LC_LETTER(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(IS_LC_LETTER('a'));
|
||||
TEST_ASSERT_FALSE(IS_LC_LETTER('A'));
|
||||
}
|
||||
|
||||
void test_IS_09(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(IS_09('0'));
|
||||
TEST_ASSERT_FALSE(IS_09('A'));
|
||||
}
|
||||
|
||||
void test_ISVALIDHEX(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ISVALIDHEX('A'));
|
||||
TEST_ASSERT_TRUE(ISVALIDHEX('a'));
|
||||
TEST_ASSERT_TRUE(ISVALIDHEX('0'));
|
||||
TEST_ASSERT_FALSE(ISVALIDHEX('G'));
|
||||
}
|
||||
|
||||
void test_ISVALIDDEC(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(ISVALIDDEC('0'));
|
||||
TEST_ASSERT_FALSE(ISVALIDDEC('A'));
|
||||
}
|
||||
|
||||
void test_CONVERTDEC(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(0, CONVERTDEC('0'));
|
||||
TEST_ASSERT_EQUAL_HEX8(9, CONVERTDEC('9'));
|
||||
}
|
||||
|
||||
void test_CONVERTHEX(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_HEX8(10, CONVERTHEX('A'));
|
||||
TEST_ASSERT_EQUAL_HEX8(10, CONVERTHEX('a'));
|
||||
TEST_ASSERT_EQUAL_HEX8(0, CONVERTHEX('0'));
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(test_CRC16);
|
||||
RUN_TEST(test_IS_CAP_LETTER);
|
||||
RUN_TEST(test_IS_LC_LETTER);
|
||||
RUN_TEST(test_IS_09);
|
||||
RUN_TEST(test_ISVALIDHEX);
|
||||
RUN_TEST(test_ISVALIDDEC);
|
||||
RUN_TEST(test_CONVERTDEC);
|
||||
RUN_TEST(test_CONVERTHEX);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
@ -0,0 +1,633 @@
|
|||
/**
|
||||
* @file ymodem.c
|
||||
* @author xxx
|
||||
* @date 2024-02-18 19:32:40
|
||||
* @brief
|
||||
* 该模块实现了YMODEM协议的核心功能,包括初始化、配置、数据接收、握手、数据传输和结束处理。
|
||||
* 它使用了回调函数来处理不同阶段的事件,并使用信号量来同步不同的流程。CRC校验是用来确保数据传输的完整性和准确性
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#include "ymodem.h"
|
||||
#include "sys.h"
|
||||
#include "delay.h"
|
||||
#include "flow.h"
|
||||
|
||||
sqqueue_ctrl_t rym_sqqueue; // 一个接收队列控制结构体,用于管理接收到的数据。
|
||||
static uint32_t tm_sec = 0; // 握手超时时间,用于握手阶段的超时计时
|
||||
static enum rym_stage stage = RYM_STAGE_NONE; // 当前的阶段
|
||||
static int32_t rym_tm_sec = 0; // YMODEM超时计时器
|
||||
|
||||
static uint8_t aPacketData[_RYM_PKG_SZ]; // 数据包缓冲区
|
||||
|
||||
// 回调函数,用于处理不同阶段的事件
|
||||
static rym_callback rym_on_begin = NULL;
|
||||
static rym_callback rym_on_data = NULL;
|
||||
static rym_callback rym_on_end = NULL;
|
||||
static rym_callback rym_transmit = NULL;
|
||||
|
||||
static struct flow handshake_fw; // 握手流程
|
||||
static struct flow trans_fw; // 传输流程
|
||||
static struct flow finsh_fw; // 结束流程
|
||||
static struct flow_sem msg_sem; // 消息信号量,用于同步
|
||||
|
||||
static const uint16_t ccitt_table[256] =
|
||||
{
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
|
||||
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
|
||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
|
||||
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
|
||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
|
||||
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
|
||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
|
||||
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
|
||||
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
|
||||
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
|
||||
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
|
||||
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
|
||||
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
|
||||
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
|
||||
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
|
||||
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
|
||||
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
|
||||
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
|
||||
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
|
||||
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
|
||||
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
|
||||
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
||||
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
|
||||
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
|
||||
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
|
||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
|
||||
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
|
||||
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
|
||||
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
|
||||
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
|
||||
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
|
||||
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
|
||||
|
||||
uint16_t CRC16(unsigned char *q, int len)
|
||||
{
|
||||
uint16_t crc = 0;
|
||||
|
||||
while (len-- > 0)
|
||||
crc = (crc << 8) ^ ccitt_table[((crc >> 8) ^ *q++) & 0xff];
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* Exported macro ------------------------------------------------------------*/
|
||||
#define IS_CAP_LETTER(c) (((c) >= 'A') && ((c) <= 'F'))
|
||||
#define IS_LC_LETTER(c) (((c) >= 'a') && ((c) <= 'f'))
|
||||
#define IS_09(c) (((c) >= '0') && ((c) <= '9'))
|
||||
#define ISVALIDHEX(c) (IS_CAP_LETTER(c) || IS_LC_LETTER(c) || IS_09(c))
|
||||
#define ISVALIDDEC(c) IS_09(c)
|
||||
#define CONVERTDEC(c) (c - '0')
|
||||
|
||||
#define CONVERTHEX_ALPHA(c) (IS_CAP_LETTER(c) ? ((c) - 'A' + 10) : ((c) - 'a' + 10))
|
||||
#define CONVERTHEX(c) (IS_09(c) ? ((c) - '0') : CONVERTHEX_ALPHA(c))
|
||||
/**
|
||||
* @brief Convert a string to an integer
|
||||
* @param p_inputstr: The string to be converted
|
||||
* @param p_intnum: The integer value
|
||||
* @retval 1: Correct
|
||||
* 0: Error
|
||||
*/
|
||||
uint32_t Str2Int(uint8_t *p_inputstr, uint32_t *p_intnum)
|
||||
{
|
||||
uint32_t i = 0, res = 0;
|
||||
uint32_t val = 0;
|
||||
|
||||
if ((p_inputstr[0] == '0') && ((p_inputstr[1] == 'x') || (p_inputstr[1] == 'X')))
|
||||
{
|
||||
i = 2;
|
||||
while ((i < 11) && (p_inputstr[i] != '\0'))
|
||||
{
|
||||
if (ISVALIDHEX(p_inputstr[i]))
|
||||
{
|
||||
val = (val << 4) + CONVERTHEX(p_inputstr[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Return 0, Invalid input */
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
/* valid result */
|
||||
if (p_inputstr[i] == '\0')
|
||||
{
|
||||
*p_intnum = val;
|
||||
res = 1;
|
||||
}
|
||||
}
|
||||
else /* max 10-digit decimal input */
|
||||
{
|
||||
while ((i < 11) && (res != 1))
|
||||
{
|
||||
if (p_inputstr[i] == '\0')
|
||||
{
|
||||
*p_intnum = val;
|
||||
/* return 1 */
|
||||
res = 1;
|
||||
}
|
||||
else if (((p_inputstr[i] == 'k') || (p_inputstr[i] == 'K')) && (i > 0))
|
||||
{
|
||||
val = val << 10;
|
||||
*p_intnum = val;
|
||||
res = 1;
|
||||
}
|
||||
else if (((p_inputstr[i] == 'm') || (p_inputstr[i] == 'M')) && (i > 0))
|
||||
{
|
||||
val = val << 20;
|
||||
*p_intnum = val;
|
||||
res = 1;
|
||||
}
|
||||
else if (ISVALIDDEC(p_inputstr[i]))
|
||||
{
|
||||
val = val * 10 + CONVERTDEC(p_inputstr[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* return 0, Invalid input */
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read data from the circular queue.
|
||||
*
|
||||
* This function reads the specified length of data from the circular queue and stores it in the given buffer.
|
||||
* If the length of data in the queue is greater than or equal to the specified length, it directly reads the specified length of data.
|
||||
* If the length of data in the queue is less than the specified length, it reads all the data in the queue.
|
||||
*
|
||||
* @param buffer The buffer to store the data.
|
||||
* @param len The length of data to read.
|
||||
*
|
||||
* @return The actual length of data read.
|
||||
*/
|
||||
static uint16_t rym_sqqueue_read(void *buffer, uint16_t len)
|
||||
{
|
||||
uint16_t i = 0;
|
||||
uint8_t *buf = buffer;
|
||||
if (rym_sqqueue.get_len(&rym_sqqueue) >= len)
|
||||
{
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
buf[i] = *((uint8_t *)rym_sqqueue.del(&rym_sqqueue));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while ((rym_sqqueue.get_len(&rym_sqqueue) != 0) && (i < len))
|
||||
{
|
||||
buf[i] = *((uint8_t *)rym_sqqueue.del(&rym_sqqueue));
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the parameters for the YMODEM process.
|
||||
*
|
||||
* This function sets the stage variable to the specified stage and initializes the rym_tm_sec variable with the value of tm_sec.
|
||||
* If the stage is RYM_STAGE_ESTABLISHING, it also clears the rym_sqqueue.
|
||||
*
|
||||
* @param st The stage of the YMODEM process.
|
||||
*/
|
||||
static void rym_process_params_init(enum rym_stage st)
|
||||
{
|
||||
stage = st;
|
||||
rym_tm_sec = tm_sec;
|
||||
if (st == RYM_STAGE_ESTABLISHING)
|
||||
{
|
||||
rym_sqqueue.clear_sqq(&rym_sqqueue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs the handshake process for the YMODEM protocol.
|
||||
*
|
||||
* This function reads packets from the input queue and performs the necessary checks and actions
|
||||
* to establish a connection and initiate file transfer using the YMODEM protocol.
|
||||
*
|
||||
* @param fl The flow structure containing the necessary variables and synchronization mechanisms.
|
||||
* @return The result of the handshake process.
|
||||
* - 0: Handshake process completed successfully.
|
||||
* - Non-zero: Handshake process failed.
|
||||
*/
|
||||
static uint8_t rym_do_handshake_process(struct flow *fl)
|
||||
{
|
||||
uint8_t index = 0;
|
||||
FL_HEAD(fl);
|
||||
static uint16_t rym_recv_len = 0;
|
||||
static uint16_t recv_crc, cal_crc;
|
||||
static uint8_t *file_ptr = NULL;
|
||||
static uint8_t file_name[FILE_NAME_LENGTH];
|
||||
static uint8_t file_size[FILE_SIZE_LENGTH];
|
||||
static uint32_t filesize;
|
||||
for (;;)
|
||||
{
|
||||
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
|
||||
if (FL_SEM_IS_RELEASE(fl, &msg_sem))
|
||||
{
|
||||
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
|
||||
if (rym_recv_len == 1)
|
||||
{
|
||||
if (aPacketData[PACKET_START_INDEX] != RYM_CODE_SOH && aPacketData[PACKET_START_INDEX] != RYM_CODE_STX)
|
||||
continue;
|
||||
|
||||
/* SOH/STX + seq + payload + crc */
|
||||
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_NUMBER_INDEX],
|
||||
_RYM_PKG_SZ - 1);
|
||||
if (rym_recv_len != (_RYM_PKG_SZ - 1))
|
||||
continue;
|
||||
/* sanity check */
|
||||
if ((aPacketData[PACKET_NUMBER_INDEX] != 0) || (aPacketData[PACKET_CNUMBER_INDEX] != 0xFF))
|
||||
continue;
|
||||
recv_crc = (uint16_t)(*(&aPacketData[PACKET_START_INDEX] + _RYM_PKG_SZ - 2) << 8) |
|
||||
*(&aPacketData[PACKET_START_INDEX] + _RYM_PKG_SZ - 1);
|
||||
cal_crc = CRC16(aPacketData + PACKET_DATA_INDEX, _RYM_PKG_SZ - 5);
|
||||
if (recv_crc != cal_crc)
|
||||
continue;
|
||||
|
||||
if (rym_on_begin != NULL)
|
||||
{
|
||||
file_ptr = aPacketData + PACKET_DATA_INDEX;
|
||||
while ((*file_ptr != 0) && (index < FILE_NAME_LENGTH))
|
||||
{
|
||||
file_name[index++] = *file_ptr++;
|
||||
}
|
||||
file_name[index++] = '\0';
|
||||
index = 0;
|
||||
file_ptr++;
|
||||
while ((*file_ptr != ' ') && (index < FILE_SIZE_LENGTH))
|
||||
{
|
||||
file_size[index++] = *file_ptr++;
|
||||
}
|
||||
file_size[index++] = '\0';
|
||||
Str2Int(file_size, &filesize);
|
||||
|
||||
if (RYM_CODE_NONE != rym_on_begin(file_name, filesize))
|
||||
{
|
||||
for (uint8_t i = 0; i < RYM_END_SESSION_SEND_CAN_NUM; i++)
|
||||
{
|
||||
aPacketData[0] = RYM_CODE_CAN;
|
||||
rym_transmit(aPacketData, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aPacketData[0] = RYM_CODE_ACK;
|
||||
rym_transmit(aPacketData, 1);
|
||||
FL_LOCK_DELAY(fl, FL_CLOCK_100MSEC * 5);
|
||||
aPacketData[0] = RYM_CODE_C;
|
||||
rym_transmit(aPacketData, 1);
|
||||
rym_process_params_init(RYM_STAGE_TRANSMITTING);
|
||||
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
aPacketData[0] = RYM_CODE_C;
|
||||
rym_transmit(aPacketData, 1);
|
||||
rym_tm_sec--;
|
||||
}
|
||||
}
|
||||
FL_TAIL(fl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Transfers data using the YMODEM protocol.
|
||||
*
|
||||
* This function is responsible for transferring data using the YMODEM protocol.
|
||||
* It receives the size of the data to be transferred and a pointer to the code
|
||||
* that will be returned. It performs various checks on the received data and
|
||||
* calculates the CRC to ensure data integrity. If all checks pass, it sets the
|
||||
* code to RYM_CODE_ACK and returns 0. Otherwise, it returns an error code.
|
||||
*
|
||||
* @param data_size The size of the data to be transferred.
|
||||
* @param code Pointer to the code that will be returned.
|
||||
* @return 0 if successful, otherwise an error code.
|
||||
*/
|
||||
static int8_t rym_tran_data(uint16_t data_size, uint8_t *code)
|
||||
{
|
||||
DBG_ASSERT(NULL != code __DBG_LINE);
|
||||
uint16_t recv_len = 0;
|
||||
uint16_t recv_crc, cal_crc;
|
||||
const uint16_t tran_size = PACKET_HEADER_SIZE - 1 + data_size + PACKET_TRAILER_SIZE;
|
||||
|
||||
/* seq + data + crc */
|
||||
recv_len = rym_sqqueue_read(&aPacketData[PACKET_NUMBER_INDEX],
|
||||
tran_size);
|
||||
if (recv_len != tran_size)
|
||||
return -RYM_ERR_DSZ;
|
||||
/* sanity check */
|
||||
if ((aPacketData[PACKET_NUMBER_INDEX] + aPacketData[PACKET_CNUMBER_INDEX]) != 0xFF)
|
||||
return -RYM_ERR_SEQ;
|
||||
/* As we are sending C continuously, there is a chance that the
|
||||
* sender(remote) receive an C after sending the first handshake package.
|
||||
* So the sender will interpret it as NAK and re-send the package. So we
|
||||
* just ignore it and proceed. */
|
||||
if (stage == RYM_STAGE_ESTABLISHED && aPacketData[PACKET_NUMBER_INDEX] == RYM_CODE_NONE)
|
||||
{
|
||||
*code = RYM_CODE_NONE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
stage = RYM_STAGE_TRANSMITTING;
|
||||
|
||||
recv_crc = (uint16_t)(*(&aPacketData[PACKET_START_INDEX] + tran_size - 1) << 8) |
|
||||
*(&aPacketData[PACKET_START_INDEX] + tran_size);
|
||||
cal_crc = CRC16(aPacketData + PACKET_DATA_INDEX, data_size);
|
||||
if (recv_crc != cal_crc)
|
||||
return -RYM_ERR_CRC;
|
||||
|
||||
*code = RYM_CODE_ACK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs the YMODEM transmission process.
|
||||
*
|
||||
* This function is responsible for handling the YMODEM transmission process.
|
||||
* It receives packets of data and performs the necessary operations based on the received data.
|
||||
* It handles timeouts and retransmissions if necessary.
|
||||
*
|
||||
* @param fl The flow structure pointer.
|
||||
* @return The status of the transmission process.
|
||||
*/
|
||||
static uint8_t rym_do_trans_process(struct flow *fl)
|
||||
{
|
||||
FL_HEAD(fl);
|
||||
static uint16_t data_size, rym_recv_len;
|
||||
static uint8_t rym_code;
|
||||
static uint16_t tran_timeout = 0;
|
||||
for (;;)
|
||||
{
|
||||
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
|
||||
if (FALSE == FL_SEM_IS_RELEASE(fl, &msg_sem))
|
||||
{
|
||||
if (tran_timeout++ >= 5)
|
||||
{
|
||||
tran_timeout = 0;
|
||||
rym_process_params_init(RYM_STAGE_ESTABLISHING);
|
||||
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tran_timeout = 0;
|
||||
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
|
||||
if (rym_recv_len == 1)
|
||||
{
|
||||
if (aPacketData[PACKET_START_INDEX] == RYM_CODE_SOH)
|
||||
data_size = PACKET_SIZE;
|
||||
else if (aPacketData[PACKET_START_INDEX] == RYM_CODE_STX)
|
||||
data_size = PACKET_1K_SIZE;
|
||||
else if (aPacketData[PACKET_START_INDEX] == RYM_CODE_EOT)
|
||||
{
|
||||
aPacketData[0] = RYM_CODE_NAK;
|
||||
rym_transmit(aPacketData, 1);
|
||||
rym_process_params_init(RYM_STAGE_FINISHING);
|
||||
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
rym_process_params_init(RYM_STAGE_ESTABLISHING);
|
||||
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rym_tran_data(data_size, &rym_code) == 0)
|
||||
{
|
||||
if (rym_on_data != NULL)
|
||||
rym_on_data(aPacketData + PACKET_DATA_INDEX, data_size);
|
||||
|
||||
if (rym_code == RYM_CODE_CAN)
|
||||
{
|
||||
for (uint8_t i = 0; i < RYM_END_SESSION_SEND_CAN_NUM; i++)
|
||||
{
|
||||
aPacketData[0] = RYM_CODE_CAN;
|
||||
rym_transmit(aPacketData, 1);
|
||||
}
|
||||
}
|
||||
else if (rym_code == RYM_CODE_ACK)
|
||||
{
|
||||
aPacketData[0] = RYM_CODE_ACK;
|
||||
rym_transmit(aPacketData, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FL_TAIL(fl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs the finishing process for the YMODEM protocol.
|
||||
*
|
||||
* This function is responsible for handling the final stage of the YMODEM protocol,
|
||||
* where the receiver receives the end of transmission (EOT) signal from the sender.
|
||||
* It verifies the received EOT signal, sends an acknowledgement (ACK) signal back to
|
||||
* the sender, and waits for the start of header (SOH) signal to receive the final
|
||||
* packet containing the payload and checksum. If the received packet is valid, it
|
||||
* calculates the checksum and compares it with the received checksum. If they match,
|
||||
* it sets the stage to RYM_STAGE_FINISHED and invokes the callback function if
|
||||
* available. This function also handles timeout conditions and reinitializes the
|
||||
* protocol parameters if necessary.
|
||||
*
|
||||
* @param fl The flow structure pointer.
|
||||
* @return The result of the finishing process.
|
||||
*/
|
||||
static uint8_t rym_do_finish_process(struct flow *fl)
|
||||
{
|
||||
FL_HEAD(fl);
|
||||
static uint16_t rym_recv_len;
|
||||
static uint16_t recv_crc, cal_crc;
|
||||
static uint16_t tran_timeout = 0;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
|
||||
if (FALSE == FL_SEM_IS_RELEASE(fl, &msg_sem))
|
||||
{
|
||||
if (tran_timeout++ >= 5)
|
||||
{
|
||||
tran_timeout = 0;
|
||||
rym_process_params_init(RYM_STAGE_ESTABLISHING);
|
||||
FL_LOCK_DELAY(fl, FL_CLOCK_SEC);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tran_timeout = 0;
|
||||
/* read the length of the packet */
|
||||
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
|
||||
if (rym_recv_len == 1)
|
||||
{
|
||||
if (aPacketData[PACKET_START_INDEX] != RYM_CODE_EOT)
|
||||
continue;
|
||||
|
||||
/* send an ACK */
|
||||
aPacketData[0] = RYM_CODE_ACK;
|
||||
rym_transmit(aPacketData, 1);
|
||||
FL_LOCK_DELAY(fl, FL_CLOCK_100MSEC * 5);
|
||||
/* send a C */
|
||||
aPacketData[0] = RYM_CODE_C;
|
||||
rym_transmit(aPacketData, 1);
|
||||
|
||||
FL_LOCK_WAIT_SEM_OR_TIMEOUT(fl, &msg_sem, FL_CLOCK_SEC);
|
||||
if (FALSE == FL_SEM_IS_RELEASE(fl, &msg_sem))
|
||||
continue;
|
||||
/* read the length of the packet */
|
||||
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_START_INDEX], 1);
|
||||
if (rym_recv_len == 1)
|
||||
{
|
||||
if (aPacketData[PACKET_START_INDEX] != RYM_CODE_SOH)
|
||||
continue;
|
||||
|
||||
/* SOH/STX + seq + payload + crc */
|
||||
rym_recv_len = rym_sqqueue_read(&aPacketData[PACKET_NUMBER_INDEX],
|
||||
_RYM_SOH_PKG_SZ - 1);
|
||||
if (rym_recv_len != (_RYM_SOH_PKG_SZ - 1))
|
||||
continue;
|
||||
/* sanity check */
|
||||
if ((aPacketData[PACKET_NUMBER_INDEX] != 0) || (aPacketData[PACKET_CNUMBER_INDEX] != 0xFF))
|
||||
continue;
|
||||
recv_crc = (uint16_t)(*(&aPacketData[PACKET_START_INDEX] + _RYM_SOH_PKG_SZ - 2) << 8) |
|
||||
*(&aPacketData[PACKET_START_INDEX] + _RYM_SOH_PKG_SZ - 1);
|
||||
cal_crc = CRC16(aPacketData + PACKET_DATA_INDEX, _RYM_SOH_PKG_SZ - 5);
|
||||
if (recv_crc != cal_crc)
|
||||
continue;
|
||||
|
||||
/* we got a valid packet. invoke the callback if there is one. */
|
||||
stage = RYM_STAGE_FINISHED;
|
||||
aPacketData[0] = RYM_CODE_ACK;
|
||||
rym_transmit(aPacketData, 1);
|
||||
/* we already got one EOT in the caller. invoke the callback if there is
|
||||
* one. */
|
||||
if (rym_on_end)
|
||||
rym_on_end(&aPacketData[PACKET_DATA_INDEX], PACKET_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
FL_TAIL(fl);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Process the Ymodem protocol stages.
|
||||
*
|
||||
* This function is responsible for handling the different stages of the Ymodem protocol.
|
||||
* It performs the necessary actions based on the current stage.
|
||||
*
|
||||
* @note The stages include establishing connection, transmitting data, finishing, and others.
|
||||
*
|
||||
* @param None
|
||||
* @return None
|
||||
*/
|
||||
void rym_process(void)
|
||||
{
|
||||
switch (stage)
|
||||
{
|
||||
case RYM_STAGE_ESTABLISHING: // 建立连接 (Establishing connection)
|
||||
rym_do_handshake_process(&handshake_fw);
|
||||
break;
|
||||
case RYM_STAGE_TRANSMITTING: // 传输中 (Transmitting)
|
||||
rym_do_trans_process(&trans_fw);
|
||||
break;
|
||||
case RYM_STAGE_FINISHING: // 结束 (Finishing)
|
||||
rym_do_finish_process(&finsh_fw);
|
||||
break;
|
||||
case RYM_STAGE_NONE:
|
||||
rym_process_params_init(RYM_STAGE_ESTABLISHING);
|
||||
break;
|
||||
case RYM_STAGE_FINISHED:
|
||||
rym_tm_sec = 0;
|
||||
break;
|
||||
default:
|
||||
// rym_process_params_init(RYM_STAGE_ESTABLISHING);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the YMODEM receive timeout has occurred.
|
||||
* @return TRUE if the timeout has occurred, FALSE otherwise.
|
||||
*/
|
||||
BOOL rym_timeout(void)
|
||||
{
|
||||
return rym_tm_sec == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initializes the YMODEM protocol for receiving files.
|
||||
*
|
||||
* This function initializes the necessary data structures and variables
|
||||
* for receiving files using the YMODEM protocol.
|
||||
*
|
||||
* @return TRUE if initialization is successful, FALSE otherwise.
|
||||
*/
|
||||
BOOL rym_init(void)
|
||||
{
|
||||
sqqueue_ctrl_init(&rym_sqqueue, sizeof(uint8_t), _RYM_PKG_SZ);
|
||||
FL_INIT(&handshake_fw);
|
||||
FL_INIT(&trans_fw);
|
||||
FL_INIT(&finsh_fw);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Receive data using the Ymodem protocol.
|
||||
*
|
||||
* This function is used to receive data transmitted using the Ymodem protocol and store it in the specified buffer.
|
||||
*
|
||||
* @param p Pointer to the data storage buffer.
|
||||
*/
|
||||
uint16_t rym_receive(void *p, uint16_t size)
|
||||
{
|
||||
rym_sqqueue.string_enter(&rym_sqqueue, p, size);
|
||||
FLOW_SEM_RELEASE(&msg_sem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configures the YMODEM protocol with the specified callbacks and handshake timeout.
|
||||
*
|
||||
* This function sets the callback functions for the YMODEM protocol, which will be called during different stages of the protocol.
|
||||
* The `on_begin` callback is called when the YMODEM transfer begins.
|
||||
* The `on_data` callback is called when a data packet is received during the transfer.
|
||||
* The `on_end` callback is called when the YMODEM transfer ends.
|
||||
* The `on_transmit` callback is called when a data packet needs to be transmitted during the transfer.
|
||||
* The `handshake_timeout` parameter specifies the timeout duration for the handshake phase of the YMODEM protocol.
|
||||
*
|
||||
* @param on_begin The callback function to be called when the YMODEM transfer begins.
|
||||
* @param on_data The callback function to be called when a data packet is received during the transfer.
|
||||
* @param on_end The callback function to be called when the YMODEM transfer ends.
|
||||
* @param on_transmit The callback function to be called when a data packet needs to be transmitted during the transfer.
|
||||
* @param handshake_timeout The timeout duration for the handshake phase of the YMODEM protocol.
|
||||
*
|
||||
* @return TRUE if the configuration was successful, FALSE otherwise.
|
||||
*/
|
||||
BOOL rym_config(rym_callback on_begin, rym_callback on_data,
|
||||
rym_callback on_end, rym_callback on_transmit,
|
||||
int handshake_timeout)
|
||||
{
|
||||
rym_on_begin = on_begin;
|
||||
rym_on_data = on_data;
|
||||
rym_on_end = on_end;
|
||||
rym_transmit = on_transmit;
|
||||
tm_sec = handshake_timeout;
|
||||
return TRUE;
|
||||
}
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/**
|
||||
* @file ymodem.h
|
||||
* @author xsh
|
||||
* @date 2024-02-18 19:32:46
|
||||
* @brief
|
||||
* @copyright Copyright (c) 2024 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __YMODEM_H__
|
||||
#define __YMODEM_H__
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
enum rym_code
|
||||
{
|
||||
RYM_CODE_NONE = 0x00,
|
||||
RYM_CODE_SOH = 0x01, /* start of 128-byte data packet */
|
||||
RYM_CODE_STX = 0x02, /* start of 1024-byte data packet */
|
||||
RYM_CODE_EOT = 0x04, /* end of transmission */
|
||||
RYM_CODE_ACK = 0x06, /* acknowledge */
|
||||
RYM_CODE_NAK = 0x15, /* negative acknowledge */
|
||||
RYM_CODE_CAN = 0x18, /* two of these in succession aborts transfer */
|
||||
RYM_CODE_C = 0x43, /* 'C' == 0x43, request 16-bit CRC */
|
||||
};
|
||||
typedef enum rym_code rym_code_e;
|
||||
|
||||
/* RYM error code
|
||||
*
|
||||
* We use the rt_err_t to return error values. We take use of current error
|
||||
* codes available in RTT and append ourselves.
|
||||
*/
|
||||
/* timeout on handshake */
|
||||
#define RYM_ERR_TMO 0x70
|
||||
/* wrong code, wrong SOH, STX etc. */
|
||||
#define RYM_ERR_CODE 0x71
|
||||
/* wrong sequence number */
|
||||
#define RYM_ERR_SEQ 0x72
|
||||
/* wrong CRC checksum */
|
||||
#define RYM_ERR_CRC 0x73
|
||||
/* not enough data received */
|
||||
#define RYM_ERR_DSZ 0x74
|
||||
/* the transmission is aborted by user */
|
||||
#define RYM_ERR_CAN 0x75
|
||||
|
||||
/* how many ticks wait for chars between packet. */
|
||||
#ifndef RYM_WAIT_CHR_TICK
|
||||
#define RYM_WAIT_CHR_TICK (OSEL_TICK_RATE_HZ * 3)
|
||||
#endif
|
||||
/* how many ticks wait for between packet. */
|
||||
#ifndef RYM_WAIT_PKG_TICK
|
||||
#define RYM_WAIT_PKG_TICK (OSEL_TICK_RATE_HZ * 3)
|
||||
#endif
|
||||
/* how many ticks between two handshake code. */
|
||||
#ifndef RYM_CHD_INTV_TICK
|
||||
#define RYM_CHD_INTV_TICK (OSEL_TICK_RATE_HZ * 3)
|
||||
#endif
|
||||
|
||||
/* how many CAN be sent when user active end the session. */
|
||||
#ifndef RYM_END_SESSION_SEND_CAN_NUM
|
||||
#define RYM_END_SESSION_SEND_CAN_NUM 0x03
|
||||
#endif
|
||||
|
||||
/* Exported constants --------------------------------------------------------*/
|
||||
/* Packet structure defines */
|
||||
#define PACKET_HEADER_SIZE ((uint32_t)3)
|
||||
#define PACKET_DATA_INDEX ((uint32_t)4)
|
||||
#define PACKET_START_INDEX ((uint32_t)1)
|
||||
#define PACKET_NUMBER_INDEX ((uint32_t)2)
|
||||
#define PACKET_CNUMBER_INDEX ((uint32_t)3)
|
||||
#define PACKET_TRAILER_SIZE ((uint32_t)2)
|
||||
#define PACKET_OVERHEAD_SIZE (PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE - 1)
|
||||
#define PACKET_SIZE ((uint32_t)128)
|
||||
#define PACKET_1K_SIZE ((uint32_t)1024)
|
||||
#define _RYM_SOH_PKG_SZ (PACKET_SIZE + PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE)
|
||||
#define _RYM_STX_PKG_SZ (PACKET_1K_SIZE + PACKET_HEADER_SIZE + PACKET_TRAILER_SIZE)
|
||||
#define _RYM_PKG_SZ _RYM_STX_PKG_SZ // 这里定义的是数据包的大小
|
||||
|
||||
/* 因为data是需要写入到flash里面,如果不对齐,会出现UNALIGNED异常
|
||||
* /-------- Packet in IAP memory ------------------------------------------\
|
||||
* | 0 | 1 | 2 | 3 | 4 | ... | n+4 | n+5 | n+6 |
|
||||
* |------------------------------------------------------------------------|
|
||||
* | unused | start | number | !num | data[0] | ... | data[n] | crc0 | crc1 |
|
||||
* \------------------------------------------------------------------------/
|
||||
* the first byte is left unused for memory alignment reasons */
|
||||
|
||||
#define FILE_NAME_LENGTH ((uint32_t)64)
|
||||
#define FILE_SIZE_LENGTH ((uint32_t)16)
|
||||
|
||||
#define NEGATIVE_BYTE ((uint8_t)0xFF)
|
||||
|
||||
#define ABORT1 ((uint8_t)0x41) /* 'A' == 0x41, abort by user */
|
||||
#define ABORT2 ((uint8_t)0x61) /* 'a' == 0x61, abort by user */
|
||||
|
||||
#define MAX_ERRORS ((uint32_t)5)
|
||||
|
||||
enum rym_stage
|
||||
{
|
||||
RYM_STAGE_NONE,
|
||||
/* set when C is send */
|
||||
RYM_STAGE_ESTABLISHING,
|
||||
/* set when we've got the packet 0 and sent ACK and second C */
|
||||
RYM_STAGE_ESTABLISHED,
|
||||
/* set when the sender respond to our second C and recviever got a real
|
||||
* data packet. */
|
||||
RYM_STAGE_TRANSMITTING,
|
||||
/* set when the sender send a EOT */
|
||||
RYM_STAGE_FINISHING,
|
||||
/* set when transmission is really finished, i.e., after the NAK, C, final
|
||||
* NULL packet stuff. */
|
||||
RYM_STAGE_FINISHED,
|
||||
};
|
||||
|
||||
/* when receiving files, the buf will be the data received from ymodem protocol
|
||||
* and the len is the data size.
|
||||
*
|
||||
* TODO:
|
||||
* When sending files, the len is the buf size in RYM. The callback need to
|
||||
* fill the buf with data to send. Returning RYM_CODE_EOT will terminate the
|
||||
* transfer and the buf will be discarded. Any other return values will cause
|
||||
* the transfer continue.
|
||||
*/
|
||||
typedef enum rym_code (*rym_callback)(uint8_t *buf, uint32_t len);
|
||||
|
||||
/** recv a file on device dev with ymodem session ctx.
|
||||
*
|
||||
* If an error happens, you can get where it is failed from ctx->stage.
|
||||
*
|
||||
* @param on_begin The callback will be invoked when the first packet arrived.
|
||||
* This packet often contain file names and the size of the file, if the sender
|
||||
* support it. So if you want to save the data to a file, you may need to
|
||||
* create the file on need. It is the on_begin's responsibility to parse the
|
||||
* data content. The on_begin can be NULL, in which case the transmission will
|
||||
* continue without any side-effects.
|
||||
*
|
||||
* @param on_data The callback will be invoked on the packets received. The
|
||||
* callback should save the data to the destination. The return value will be
|
||||
* sent to the sender and in turn, only RYM_{ACK,CAN} is valid. When on_data is
|
||||
* NULL, RYM will barely send ACK on every packet and have no side-effects.
|
||||
*
|
||||
* @param on_end The callback will be invoked when one transmission is
|
||||
* finished. The data should be 128 bytes of NULL. You can do some cleaning job
|
||||
* in this callback such as closing the file. The return value of this callback
|
||||
* is ignored. As above, this parameter can be NULL if you don't need such
|
||||
* function.
|
||||
*
|
||||
* @param handshake_timeout the timeout when hand shaking. The unit is in
|
||||
* second.
|
||||
*/
|
||||
BOOL rym_config(rym_callback on_begin, rym_callback on_data,
|
||||
rym_callback on_end, rym_callback on_transmit,
|
||||
int handshake_timeout);
|
||||
|
||||
/**
|
||||
* @brief Initializes the YMODEM protocol for receiving data.
|
||||
*
|
||||
* @return BOOL Returns TRUE if initialization is successful, FALSE otherwise.
|
||||
*/
|
||||
BOOL rym_init(void);
|
||||
|
||||
/**
|
||||
* @brief Receives data using the YMODEM protocol.
|
||||
*
|
||||
* @param p Pointer to the buffer where the received data will be stored.
|
||||
* @param size The size of the buffer.
|
||||
* @return uint16_t The number of bytes received.
|
||||
*/
|
||||
uint16_t rym_receive(void *p, uint16_t size);
|
||||
|
||||
/**
|
||||
* @brief Processes the received data using the YMODEM protocol.
|
||||
*/
|
||||
void rym_process(void);
|
||||
|
||||
/**
|
||||
* @brief Checks if a timeout has occurred during the YMODEM protocol.
|
||||
*
|
||||
* @return BOOL Returns TRUE if a timeout has occurred, FALSE otherwise.
|
||||
*/
|
||||
BOOL rym_timeout(void);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
#include "pid_c.h"
|
||||
|
||||
/**
|
||||
* @brief 设置PID控制器参数
|
||||
* @param {PID_C} *self - PID控制器结构体指针
|
||||
* @param {float32} kp - 比例系数
|
||||
* @param {float32} ki - 积分系数
|
||||
* @param {float32} kd - 微分系数
|
||||
* @param {float32} out_min - 最小输出
|
||||
* @param {float32} out_max - 最大输出
|
||||
* @return {*} - 空
|
||||
*/
|
||||
static void _set_ctrl_prm(struct PID_C *self, float32 kp, float32 ki, float32 kd, float32 out_min, float32 out_max)
|
||||
{
|
||||
self->pri.kp = kp; /*比例系数*/
|
||||
self->pri.ki = ki; /*积分系数*/
|
||||
self->pri.kd = kd; /*微分系数*/
|
||||
|
||||
self->pri.deadband = 0.5; /*死区*/
|
||||
self->pri.maximum = out_max; /*最大输出*/
|
||||
self->pri.minimum = out_min; /*最小输出*/
|
||||
self->pri.last_error = 0; /*上一次误差*/
|
||||
self->pri.prev_error = 0; /*上上次误差*/
|
||||
}
|
||||
|
||||
static float32 _PID(struct PID_C *self, float32 target, float32 feedback)
|
||||
{
|
||||
/**
|
||||
* 实现PID算法
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pid_c_constructor(struct PID_C *self)
|
||||
{
|
||||
self->set_ctrl_prm = _set_ctrl_prm;
|
||||
self->PID = _PID;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef __PID_C_H__
|
||||
#define __PID_C_H__
|
||||
#include "lib.h"
|
||||
|
||||
typedef struct PID_C
|
||||
{
|
||||
/* 设置PID三个参数 */
|
||||
void (*set_ctrl_prm)(struct PID_C *self, float32 kp, float32 ki, float32 kd, float32 out_min, float32 out_max);
|
||||
/* 控制接口 */
|
||||
float32 (*PID)(struct PID_C *self, float32 target, float32 feedback);
|
||||
|
||||
// 自定义参数
|
||||
/* 实际值与目标值之间的误差 */
|
||||
float32 err;
|
||||
/* 输出值 */
|
||||
float32 out;
|
||||
/* private */
|
||||
struct
|
||||
{
|
||||
float32 kp; /*比例学习速度*/
|
||||
float32 ki; /*积分学习速度*/
|
||||
float32 kd; /*微分学习速度*/
|
||||
float32 ki_error; /*积分误差*/
|
||||
float32 last_error; /*前一拍偏差*/
|
||||
float32 prev_error; /*前两拍偏差*/
|
||||
float32 deadband; /*死区*/
|
||||
float32 maximum; /*输出值的上限*/
|
||||
float32 minimum; /*输出值的下限*/
|
||||
} pri;
|
||||
} pid_c_t;
|
||||
|
||||
extern void pid_c_constructor(struct PID_C *self);
|
||||
|
||||
#endif // __PID_C_H__
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
#include "pid_g.h"
|
||||
#include <math.h>
|
||||
|
||||
/**
|
||||
* @brief 复位PID积分及微分控制数据
|
||||
* @param {PID_G} *self
|
||||
* @return {*}
|
||||
*/
|
||||
static void _restctrl(struct PID_G *self)
|
||||
{
|
||||
self->pri.pre_error = 0;
|
||||
self->pri.sum_iterm = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新控制区间
|
||||
* @param {PID_G} *self
|
||||
* @param {float32} out_min
|
||||
* @param {float32} out_max
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_range(struct PID_G *self, float32 out_min, float32 out_max)
|
||||
{
|
||||
self->pri.out_max = out_max;
|
||||
self->pri.out_min = out_min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新kp
|
||||
* @param {PID_G} *self
|
||||
* @param {float32} kp
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_kp(struct PID_G *self, float32 kp)
|
||||
{
|
||||
self->pri.kp = kp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新ki
|
||||
* @param {PID_G} *self
|
||||
* @param {float32} ki
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_ki(struct PID_G *self, float32 ki)
|
||||
{
|
||||
self->pri.ki = ki;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新kd
|
||||
* @param {PID_G} *self
|
||||
* @param {float32} kd
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_kd(struct PID_G *self, float32 kd)
|
||||
{
|
||||
self->pri.kd = kd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使能积分控制
|
||||
* @param {PID_G} *self
|
||||
* @param {BOOL} enable
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_ki_enable(struct PID_G *self, BOOL enable)
|
||||
{
|
||||
self->pri.ki_enable = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使能微分控制
|
||||
* @param {PID_G} *self
|
||||
* @param {BOOL} enable
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_kd_enable(struct PID_G *self, BOOL enable)
|
||||
{
|
||||
self->pri.kd_enable = enable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化控制参数
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_ctrl_prm(struct PID_G *self, float32 kp, float32 ki, float32 kd, float32 err_dead, float32 out_min, float32 out_max)
|
||||
{
|
||||
g_param_t *pri = &self->pri;
|
||||
osel_memset((uint8_t *)pri, 0, sizeof(pid_g_t));
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
pri->detach = FALSE;
|
||||
}
|
||||
|
||||
static void _update_ctrl_prm(struct PID_G *self, float32 kp, float32 ki, float32 kd, float32 err_dead, float32 out_min, float32 out_max)
|
||||
{
|
||||
g_param_t *pri = &self->pri;
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
pri->detach = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 非0时配置为积分分离+抗积分饱和PID,否则为普通抗积分饱和PID
|
||||
* @param {PID_G} *self
|
||||
* @param {float32} max_err
|
||||
* @param {BOOL} mode
|
||||
* @return {*}
|
||||
*/
|
||||
static void _set_cfg(struct PID_G *self, float32 max_err, BOOL mode)
|
||||
{
|
||||
self->pri.err_limit = max_err;
|
||||
self->pri.detach = mode == FALSE ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置积分权重
|
||||
* @param {PID_G} *self
|
||||
* @param {float32} max_weight
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_weight(struct PID_G *self, float32 max_ratio, BOOL mode)
|
||||
{
|
||||
self->pri.ui_ratio = max_ratio;
|
||||
self->pri.weight = mode == FALSE ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PID算法函数
|
||||
* @param {PID_G} *self
|
||||
* @param {float32} target 目标位置
|
||||
* @param {float32} feedback 实际位置
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static float32 _PID(struct PID_G *self, float32 target, float32 feedback)
|
||||
{
|
||||
float32 error = 0.0f;
|
||||
float32 insert = 0.0f; ///< 该值为0时积分不介入计算
|
||||
float32 temp_iterm = 0.0f;
|
||||
float32 temp_kd = 0.0f;
|
||||
|
||||
g_param_t *pri = &self->pri;
|
||||
|
||||
pri->ref = target; ///< 目标位置
|
||||
pri->feed_back = feedback; ///< 实际位置
|
||||
pri->error = pri->ref - pri->feed_back; /// 误差
|
||||
error = pri->error;
|
||||
if (fabs(pri->error) <= pri->err_dead) ///< 误差小于死区,不计算
|
||||
{
|
||||
error = 0;
|
||||
}
|
||||
|
||||
/*根据PID配置的模式,获取积分数据,进行积分累加*/
|
||||
if (pri->out >= pri->out_max) ///< 到达输出上限
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach) ///< 误差大于积分介入区间,积分不介入计算
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
if (error < 0)
|
||||
{
|
||||
temp_iterm = pri->ki * error;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pri->out <= pri->out_min) ///< 到达输出下限
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach) ///< 误差大于积分介入区间,积分不介入计算
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
if (error > 0)
|
||||
{
|
||||
temp_iterm = pri->ki * error;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach)
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
temp_iterm = pri->ki * error;
|
||||
}
|
||||
}
|
||||
if (pri->ki_enable == FALSE)
|
||||
{
|
||||
temp_iterm = 0;
|
||||
insert = 0;
|
||||
}
|
||||
|
||||
/* integral */
|
||||
pri->sum_iterm += temp_iterm;
|
||||
|
||||
if (pri->weight == TRUE)
|
||||
{
|
||||
if (pri->sum_iterm > pri->ui_ratio)
|
||||
{
|
||||
pri->sum_iterm = pri->ui_ratio;
|
||||
}
|
||||
else if (pri->sum_iterm < -pri->ui_ratio)
|
||||
{
|
||||
pri->sum_iterm = -pri->ui_ratio;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pri->sum_iterm > pri->out_max)
|
||||
{
|
||||
pri->sum_iterm = pri->out_max;
|
||||
}
|
||||
else if (pri->sum_iterm < pri->out_min)
|
||||
{
|
||||
pri->sum_iterm = pri->out_min;
|
||||
}
|
||||
}
|
||||
|
||||
/* differential */
|
||||
if (pri->kd_enable == TRUE)
|
||||
{
|
||||
temp_kd = pri->kd;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp_kd = 0;
|
||||
}
|
||||
|
||||
pri->out = pri->kp * pri->error + pri->sum_iterm * insert + (pri->error - pri->pre_error) * temp_kd;
|
||||
pri->pre_error = pri->error; ///< 记录这次误差,为下次微分计算做准备
|
||||
pri->pre_feed_back = pri->feed_back;
|
||||
|
||||
/*limt pid output*/
|
||||
pri->out = RANGE(pri->out, pri->out_min, pri->out_max); ///< 限制输出
|
||||
return pri->out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PID接口
|
||||
* @param {PID_G} *self
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
void pid_g_constructor(struct PID_G *self)
|
||||
{
|
||||
self->set_ctrl_prm = _set_ctrl_prm;
|
||||
self->update_ctrl_prm = _update_ctrl_prm;
|
||||
self->set_cfg = _set_cfg;
|
||||
self->set_kp = _set_kp;
|
||||
self->set_ki_enable = _set_ki_enable;
|
||||
self->set_ki = _set_ki;
|
||||
self->set_kd_enable = _set_kd_enable;
|
||||
self->set_kd = _set_kd;
|
||||
self->set_range = _set_range;
|
||||
self->restctrl = _restctrl;
|
||||
self->PID = _PID;
|
||||
self->set_weight = _set_weight;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
#ifndef __PID_G_H__
|
||||
#define __PID_G_H__
|
||||
#include "lib.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float32 ref; /*目标*/
|
||||
float32 feed_back; /*实际*/
|
||||
float32 pre_feed_back; /*上一次实际*/
|
||||
float32 kp; /*比例学习速度*/
|
||||
float32 ki; /*积分学习速度*/
|
||||
float32 kd; /*微分学习速度*/
|
||||
float32 ki_error; /*积分误差*/
|
||||
float32 error; /*误差*/
|
||||
float32 pre_error; /*前一拍偏差*/
|
||||
float32 prev_error; /*前两拍偏差*/
|
||||
float32 err_dead; /*死区*/
|
||||
float32 err_limit; /*积分分离上限*/
|
||||
float32 maximum; /*输出值的上限*/
|
||||
float32 minimum; /*输出值的下限*/
|
||||
float32 out; /*输出值*/
|
||||
float32 sum_iterm; /*积分累加*/
|
||||
float32 ui_ratio; /*积分权重*/
|
||||
BOOL ki_enable; /*积分使能*/
|
||||
BOOL kd_enable; /*微分使能*/
|
||||
BOOL detach; /*积分分离标志*/
|
||||
BOOL weight; /*积分权重标志*/
|
||||
float32 out_max; /*输出最大值*/
|
||||
float32 out_min; /*输出最小值*/
|
||||
} g_param_t;
|
||||
|
||||
typedef struct PID_G
|
||||
{
|
||||
/* 设置PID三个参数 */
|
||||
void (*set_ctrl_prm)(struct PID_G *self, float32 kp, float32 ki, float32 kd, float32 err_dead, float32 out_min, float32 out_max);
|
||||
/* 更新PID参数 */
|
||||
void (*update_ctrl_prm)(struct PID_G *self, float32 kp, float32 ki, float32 kd, float32 err_dead, float32 out_min, float32 out_max);
|
||||
/* 控制接口 */
|
||||
float32 (*PID)(struct PID_G *self, float32 target, float32 feedback);
|
||||
/* 更新控制区间 */
|
||||
void (*set_range)(struct PID_G *self, float32 out_min, float32 out_max);
|
||||
/* 设置积分分离 */
|
||||
void (*set_cfg)(struct PID_G *self, float32 max_err, BOOL mode);
|
||||
/* 设置积分权重 */
|
||||
void (*set_weight)(struct PID_G *self, float32 max_ratio, BOOL mode);
|
||||
/* 更新kp */
|
||||
void (*set_kp)(struct PID_G *self, float32 kp);
|
||||
/* 使能ki */
|
||||
void (*set_ki_enable)(struct PID_G *self, BOOL enable);
|
||||
/* 更新ki */
|
||||
void (*set_ki)(struct PID_G *self, float32 ki);
|
||||
/* 使能kd */
|
||||
void (*set_kd_enable)(struct PID_G *self, BOOL enable);
|
||||
/* 更新kd */
|
||||
void (*set_kd)(struct PID_G *self, float32 kd);
|
||||
/* 复位PID积分及微分控制数据 */
|
||||
void (*restctrl)(struct PID_G *self);
|
||||
|
||||
/* private */
|
||||
g_param_t pri;
|
||||
|
||||
} pid_g_t;
|
||||
|
||||
extern void pid_g_constructor(struct PID_G *self);
|
||||
#endif // __PID_G_H__
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
#include "pid_hd.h"
|
||||
#include <math.h>
|
||||
#include "sys.h"
|
||||
#include "app.h"
|
||||
|
||||
#if INCOMPLETE_DIFFEREN_HD == 1 // 积分分离
|
||||
/*计算微分项,使用追随误差微分项*/
|
||||
static float32 td_derivative(struct PID_HD *self, float32 current_err, float32 pre_err, float32 dt)
|
||||
{
|
||||
pid_hd_position_t *pri = &self->pri_u.position;
|
||||
float32 derivative = (current_err - pre_err) / dt; // 计算积分项
|
||||
derivative = pri->td_alpha * derivative + (1 - pri->td_alpha) * pri->td_beta * pri->pre_derivative; // 追随误差微分器平滑输出
|
||||
pri->pre_derivative = derivative; // 更新上一次误差
|
||||
return derivative;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*杭电:设置位置式PID参数*/
|
||||
static void _set_ctrl_prm_position(struct PID_HD *self, float32 kp, float32 ki, float32 kd)
|
||||
{
|
||||
pid_hd_position_t *pri = &self->pri_u.position;
|
||||
osel_memset((uint8_t *)pri, 0, sizeof(pid_hd_position_t));
|
||||
|
||||
/*观测传进来的Kp、Ki、Kd*/
|
||||
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->ki_limit = 10; // 积分分离界限
|
||||
pri->err_dead = 0.5; // 控制死区范围
|
||||
#if INCOMPLETE_DIFFEREN_HD == 1
|
||||
/*不完全微分系数*/
|
||||
pri->td_alpha = 0.5;
|
||||
pri->td_beta = 0.5;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*杭电:设置输出限幅参数*/
|
||||
static void _set_out_prm_position(struct PID_HD *self, float32 maximum, float32 minimum)
|
||||
{
|
||||
self->pri_u.position.out_max = maximum;
|
||||
self->pri_u.position.out_min = minimum;
|
||||
}
|
||||
|
||||
/*杭电:位置式PID控制算法*/
|
||||
static float32 _pid_position(struct PID_HD *self, float32 err)
|
||||
{
|
||||
/*计算控制的运行时间*/
|
||||
// sys_millis_reset();
|
||||
self->pri_u.position.control_time = sys_millis();
|
||||
self->pri_u.position.tmp_time = 0;
|
||||
|
||||
/*测试:4.18*/
|
||||
if (fabs(err) < 0.1)
|
||||
{
|
||||
err = 0;
|
||||
}
|
||||
|
||||
float32 x[3];
|
||||
self->pri_u.position.err = err;
|
||||
|
||||
/*抗积分饱和*/
|
||||
#if INTEGRAL_SEPARATION == 1 // 积分分离
|
||||
if (self->pri_u.position.out > self->pri_u.position.out_max)
|
||||
{
|
||||
if (self->pri_u.position.err > self->pri_u.position.ki_limit) // 积分分离
|
||||
{
|
||||
self->pri_u.position.ki_error += 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self->pri_u.position.err < 0) // 若偏差为负值,执行负偏差的累加
|
||||
{
|
||||
self->pri_u.position.ki_error += self->pri_u.position.err;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (self->pri_u.position.out < self->pri_u.position.out_min)
|
||||
{
|
||||
if (self->pri_u.position.err > self->pri_u.position.ki_limit) // 若偏差为负值,停止积分
|
||||
{
|
||||
self->pri_u.position.ki_error += 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (self->pri_u.position.err > 0) // 若偏差为正值,执行正偏差的累加
|
||||
{
|
||||
self->pri_u.position.ki_error += self->pri_u.position.err;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fabs(err) > self->pri_u.position.ki_limit || fabs(err) < 0.5)
|
||||
{
|
||||
self->pri_u.position.ki_error += 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->pri_u.position.ki_error += self->pri_u.position.err;
|
||||
}
|
||||
}
|
||||
#else /*无积分分离操作*/
|
||||
if (fabs(err) < 0.4)
|
||||
{
|
||||
self->pri_u.position.ki_error += 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->pri_u.position.ki_error += self->pri_u.position.err;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*输出*/
|
||||
if (fabs(err) < self->pri_u.position.err_dead)
|
||||
{
|
||||
/*输出上一次的值*/
|
||||
// self->pri_u.position.out = self->pri_u.position.pre_out;
|
||||
x[0] = self->pri_u.position.err;
|
||||
x[1] = self->pri_u.position.ki_error;
|
||||
self->pri_u.position.out = self->pri_u.position.kp * x[0] + self->pri_u.position.ki * x[1] + self->pri_u.position.kd * x[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
x[0] = self->pri_u.position.err;
|
||||
x[1] = self->pri_u.position.ki_error;
|
||||
|
||||
#if INCOMPLETE_DIFFEREN_HD == 1
|
||||
/*不完全微分项,为了解决普通PID为微分环节容易振荡的问题*/
|
||||
self->pri_u.position.tmp_time = sys_millis();
|
||||
self->pri_u.position.control_time -= self->pri_u.position.tmp_time;
|
||||
self->pri_u.position.control_time /= 1000.0; // 将单位转换为秒
|
||||
x[2] = td_derivative(&_pid.pid_u.hd, err, self->pri_u.position.pre_error, self->pri_u.position.control_time);
|
||||
#else
|
||||
// 普通的微分环节
|
||||
x[2] = self->pri_u.position.err - self->pri_u.position.pre_error;
|
||||
#endif
|
||||
|
||||
self->pri_u.position.out = self->pri_u.position.kp * x[0] + self->pri_u.position.ki * x[1] + self->pri_u.position.kd * x[2];
|
||||
}
|
||||
|
||||
/*输出限幅*/
|
||||
if (self->pri_u.position.out > self->pri_u.position.out_max)
|
||||
{
|
||||
self->pri_u.position.out = self->pri_u.position.out_max;
|
||||
}
|
||||
if (self->pri_u.position.out < self->pri_u.position.out_min)
|
||||
{
|
||||
self->pri_u.position.out = self->pri_u.position.out_min;
|
||||
}
|
||||
|
||||
// 更新误差历史
|
||||
self->pri_u.position.pre_error = self->pri_u.position.err; /*上一次误差值*/
|
||||
// 更新输出历史
|
||||
self->pri_u.position.pre_out = self->pri_u.position.out; /*上一次输出值*/
|
||||
|
||||
return self->pri_u.position.out;
|
||||
}
|
||||
|
||||
/*杭电:参数控制器*/
|
||||
void pid_hd_constructor(struct PID_HD *self)
|
||||
{
|
||||
self->set_ctrl_prm_position = _set_ctrl_prm_position;
|
||||
self->set_out_prm_position = _set_out_prm_position;
|
||||
self->pid_position = _pid_position;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef __PID_HD__
|
||||
#define __PID_HD__
|
||||
#include "lib.h"
|
||||
|
||||
#define INTEGRAL_SEPARATION 1 // 积分分离
|
||||
#define INCOMPLETE_DIFFEREN_HD 1 // 不完全微分
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float32 ref;
|
||||
float32 feed_back;
|
||||
float32 pre_feed_back;
|
||||
float32 pre_error;
|
||||
float32 ki_error; /*积分误差*/
|
||||
float32 ki_limit; /*积分分离界限*/
|
||||
float32 ki_alpha; /*变积分的系数*/
|
||||
float32 err;
|
||||
float32 sum_iterm;
|
||||
float32 kp;
|
||||
float32 kp_small; /*在接近稳态时的Kp*/
|
||||
float32 kp_big; /*在大范围时的Kp*/
|
||||
float32 ki;
|
||||
float32 kd;
|
||||
float32 err_limit;
|
||||
BOOL detach;
|
||||
float32 err_dead;
|
||||
#if INCOMPLETE_DIFFEREN_HD == 1
|
||||
float32 td_alpha; /*不完全微分系数*/
|
||||
float32 td_beta; /*不完全微分系数beta*/
|
||||
float32 pre_derivative; /*上一次微分值*/
|
||||
#endif
|
||||
float32 out;
|
||||
float32 pre_out;
|
||||
float32 out_max;
|
||||
float32 out_min;
|
||||
BOOL sm;
|
||||
float32 sv_range;
|
||||
uint32_t control_time; /*控制算法运行一次花费的时间*/
|
||||
uint32_t tmp_time; /*临时用来记录控制的运行时间*/
|
||||
} pid_hd_position_t; // 位置式PID
|
||||
|
||||
typedef struct PID_HD
|
||||
{
|
||||
/* 设置PID三个参数 */
|
||||
void (*set_ctrl_prm_position)(struct PID_HD *self, float32 kp, float32 ki, float32 kd);
|
||||
/* 设置输出范围 */
|
||||
void (*set_out_prm_position)(struct PID_HD *self, float32 maximum, float32 minimum);
|
||||
|
||||
/* 控制接口 */
|
||||
float32 (*pid_position)(struct PID_HD *self, float32 err);
|
||||
|
||||
// 自定义参数
|
||||
/* 实际值与目标值之间的误差 */
|
||||
float32 err;
|
||||
/* 输出值 */
|
||||
float32 out;
|
||||
/* private */
|
||||
struct
|
||||
{
|
||||
pid_hd_position_t position;
|
||||
} pri_u;
|
||||
} pid_hd_t;
|
||||
|
||||
extern void pid_hd_constructor(struct PID_HD *self);
|
||||
|
||||
#endif // __PID_HD__
|
||||
|
|
@ -0,0 +1,481 @@
|
|||
#include "pid_x.h"
|
||||
#include "math.h"
|
||||
#define LAG_PHASE (6) // 迟滞相位,单位:拍
|
||||
|
||||
#ifndef PI
|
||||
#define PI 3.14159265358979f
|
||||
#endif
|
||||
// 注1:自适应模糊pid最重要的就是论域的选择,要和你应该控制的对象相切合
|
||||
// 注2:以下各阀值、限幅值、输出值均需要根据具体的使用情况进行更改
|
||||
// 注3:因为我的控制对象惯性比较大,所以以下各部分取值较小
|
||||
// 论域e:[-5,5] ec:[-0.5,0.5]
|
||||
|
||||
// 误差的阀值,小于这个数值的时候,不做PID调整,避免误差较小时频繁调节引起震荡
|
||||
#define Emin 0.3f
|
||||
#define Emid 1.0f
|
||||
#define Emax 5.0f
|
||||
// 调整值限幅,防止积分饱和
|
||||
#define Umax 1
|
||||
#define Umin -1
|
||||
|
||||
#define NB 0
|
||||
#define NM 1
|
||||
#define NS 2
|
||||
#define ZO 3
|
||||
#define PS 4
|
||||
#define PM 5
|
||||
#define PB 6
|
||||
|
||||
int32_t kp[7][7] = {{PB, PB, PM, PM, PS, ZO, ZO},
|
||||
{PB, PB, PM, PS, PS, ZO, ZO},
|
||||
{PM, PM, PM, PS, ZO, NS, NS},
|
||||
{PM, PM, PS, ZO, NS, NM, NM},
|
||||
{PS, PS, ZO, NS, NS, NM, NM},
|
||||
{PS, ZO, NS, NM, NM, NM, NB},
|
||||
{ZO, ZO, NM, NM, NM, NB, NB}};
|
||||
|
||||
int32_t kd[7][7] = {{PS, NS, NB, NB, NB, NM, PS},
|
||||
{PS, NS, NB, NM, NM, NS, ZO},
|
||||
{ZO, NS, NM, NM, NS, NS, ZO},
|
||||
{ZO, NS, NS, NS, NS, NS, ZO},
|
||||
{ZO, ZO, ZO, ZO, ZO, ZO, ZO},
|
||||
{PB, NS, PS, PS, PS, PS, PB},
|
||||
{PB, PM, PM, PM, PS, PS, PB}};
|
||||
|
||||
int32_t ki[7][7] = {{NB, NB, NM, NM, NS, ZO, ZO},
|
||||
{NB, NB, NM, NS, NS, ZO, ZO},
|
||||
{NB, NM, NS, NS, ZO, PS, PS},
|
||||
{NM, NM, NS, ZO, PS, PM, PM},
|
||||
{NM, NS, ZO, PS, PS, PM, PB},
|
||||
{ZO, ZO, PS, PS, PM, PB, PB},
|
||||
{ZO, ZO, PS, PM, PM, PB, PB}};
|
||||
|
||||
static float32 ec; // 误差变化率
|
||||
/**************求隶属度(三角形)***************/
|
||||
float32 FTri(float32 x, float32 a, float32 b, float32 c) // FuzzyTriangle
|
||||
{
|
||||
if (x <= a)
|
||||
return 0;
|
||||
else if ((a < x) && (x <= b))
|
||||
return (x - a) / (b - a);
|
||||
else if ((b < x) && (x <= c))
|
||||
return (c - x) / (c - b);
|
||||
else if (x > c)
|
||||
return 0;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
/*****************求隶属度(梯形左)*******************/
|
||||
float32 FTraL(float32 x, float32 a, float32 b) // FuzzyTrapezoidLeft
|
||||
{
|
||||
if (x <= a)
|
||||
return 1;
|
||||
else if ((a < x) && (x <= b))
|
||||
return (b - x) / (b - a);
|
||||
else if (x > b)
|
||||
return 0;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
/*****************求隶属度(梯形右)*******************/
|
||||
float32 FTraR(float32 x, float32 a, float32 b) // FuzzyTrapezoidRight
|
||||
{
|
||||
if (x <= a)
|
||||
return 0;
|
||||
if ((a < x) && (x < b))
|
||||
return (x - a) / (b - a);
|
||||
if (x >= b)
|
||||
return 1;
|
||||
else
|
||||
return 1;
|
||||
}
|
||||
/****************三角形反模糊化处理**********************/
|
||||
float32 uFTri(float32 x, float32 a, float32 b, float32 c)
|
||||
{
|
||||
float32 y, z;
|
||||
z = (b - a) * x + a;
|
||||
y = c - (c - b) * x;
|
||||
return (y + z) / 2;
|
||||
}
|
||||
/*******************梯形(左)反模糊化***********************/
|
||||
float32 uFTraL(float32 x, float32 a, float32 b)
|
||||
{
|
||||
return b - (b - a) * x;
|
||||
}
|
||||
/*******************梯形(右)反模糊化***********************/
|
||||
float32 uFTraR(float32 x, float32 a, float32 b)
|
||||
{
|
||||
return (b - a) * x + a;
|
||||
}
|
||||
/**************************求交集****************************/
|
||||
float32 fand(float32 a, float32 b)
|
||||
{
|
||||
return (a < b) ? a : b;
|
||||
}
|
||||
/**************************求并集****************************/
|
||||
float32 forr(float32 a, float32 b)
|
||||
{
|
||||
return (a < b) ? b : a;
|
||||
}
|
||||
|
||||
static float32 _PID(struct PID_X *self, float32 target, float32 feedback)
|
||||
{
|
||||
float32 pwm_var; // pwm调整量
|
||||
float32 iError; // 当前误差
|
||||
float32 set, input;
|
||||
CLASSICPID *pri = &self->pri;
|
||||
// 计算隶属度表
|
||||
float32 es[7], ecs[7], e;
|
||||
float32 form[7][7];
|
||||
int i = 0, j = 0;
|
||||
int MaxX = 0, MaxY = 0;
|
||||
|
||||
// 记录隶属度最大项及相应推理表的p、i、d值
|
||||
float32 lsd;
|
||||
int temp_p, temp_d, temp_i;
|
||||
float32 detkp, detkd, detki; // 推理后的结果
|
||||
|
||||
// 输入格式的转化及偏差计算
|
||||
set = target;
|
||||
input = feedback;
|
||||
iError = set - input; // 偏差
|
||||
|
||||
e = iError;
|
||||
ec = iError - pri->lasterror;
|
||||
|
||||
// 当温度差的绝对值小于Emax时,对pid的参数进行调整
|
||||
if (fabs(iError) <= Emax)
|
||||
{
|
||||
// 计算iError在es与ecs中各项的隶属度
|
||||
es[NB] = FTraL(e * 5, -3, -1); // e
|
||||
es[NM] = FTri(e * 5, -3, -2, 0);
|
||||
es[NS] = FTri(e * 5, -3, -1, 1);
|
||||
es[ZO] = FTri(e * 5, -2, 0, 2);
|
||||
es[PS] = FTri(e * 5, -1, 1, 3);
|
||||
es[PM] = FTri(e * 5, 0, 2, 3);
|
||||
es[PB] = FTraR(e * 5, 1, 3);
|
||||
|
||||
ecs[NB] = FTraL(ec * 30, -3, -1); // ec
|
||||
ecs[NM] = FTri(ec * 30, -3, -2, 0);
|
||||
ecs[NS] = FTri(ec * 30, -3, -1, 1);
|
||||
ecs[ZO] = FTri(ec * 30, -2, 0, 2);
|
||||
ecs[PS] = FTri(ec * 30, -1, 1, 3);
|
||||
ecs[PM] = FTri(ec * 30, 0, 2, 3);
|
||||
ecs[PB] = FTraR(ec * 30, 1, 3);
|
||||
|
||||
// 计算隶属度表,确定e和ec相关联后表格各项隶属度的值
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
for (j = 0; j < 7; j++)
|
||||
{
|
||||
form[i][j] = fand(es[i], ecs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
// 取出具有最大隶属度的那一项
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
for (j = 0; j < 7; j++)
|
||||
{
|
||||
if (form[MaxX][MaxY] < form[i][j])
|
||||
{
|
||||
MaxX = i;
|
||||
MaxY = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 进行模糊推理,并去模糊
|
||||
lsd = form[MaxX][MaxY];
|
||||
temp_p = kp[MaxX][MaxY];
|
||||
temp_d = kd[MaxX][MaxY];
|
||||
temp_i = ki[MaxX][MaxY];
|
||||
|
||||
if (temp_p == NB)
|
||||
detkp = uFTraL(lsd, -0.3, -0.1);
|
||||
else if (temp_p == NM)
|
||||
detkp = uFTri(lsd, -0.3, -0.2, 0);
|
||||
else if (temp_p == NS)
|
||||
detkp = uFTri(lsd, -0.3, -0.1, 0.1);
|
||||
else if (temp_p == ZO)
|
||||
detkp = uFTri(lsd, -0.2, 0, 0.2);
|
||||
else if (temp_p == PS)
|
||||
detkp = uFTri(lsd, -0.1, 0.1, 0.3);
|
||||
else if (temp_p == PM)
|
||||
detkp = uFTri(lsd, 0, 0.2, 0.3);
|
||||
else if (temp_p == PB)
|
||||
detkp = uFTraR(lsd, 0.1, 0.3);
|
||||
|
||||
if (temp_d == NB)
|
||||
detkd = uFTraL(lsd, -3, -1);
|
||||
else if (temp_d == NM)
|
||||
detkd = uFTri(lsd, -3, -2, 0);
|
||||
else if (temp_d == NS)
|
||||
detkd = uFTri(lsd, -3, 1, 1);
|
||||
else if (temp_d == ZO)
|
||||
detkd = uFTri(lsd, -2, 0, 2);
|
||||
else if (temp_d == PS)
|
||||
detkd = uFTri(lsd, -1, 1, 3);
|
||||
else if (temp_d == PM)
|
||||
detkd = uFTri(lsd, 0, 2, 3);
|
||||
else if (temp_d == PB)
|
||||
detkd = uFTraR(lsd, 1, 3);
|
||||
|
||||
if (temp_i == NB)
|
||||
detki = uFTraL(lsd, -0.06, -0.02);
|
||||
else if (temp_i == NM)
|
||||
detki = uFTri(lsd, -0.06, -0.04, 0);
|
||||
else if (temp_i == NS)
|
||||
detki = uFTri(lsd, -0.06, -0.02, 0.02);
|
||||
else if (temp_i == ZO)
|
||||
detki = uFTri(lsd, -0.04, 0, 0.04);
|
||||
else if (temp_i == PS)
|
||||
detki = uFTri(lsd, -0.02, 0.02, 0.06);
|
||||
else if (temp_i == PM)
|
||||
detki = uFTri(lsd, 0, 0.04, 0.06);
|
||||
else if (temp_i == PB)
|
||||
detki = uFTraR(lsd, 0.02, 0.06);
|
||||
|
||||
// pid三项系数的修改
|
||||
pri->pKp += detkp;
|
||||
pri->pKi += detki;
|
||||
if (pri->kd_e)
|
||||
{
|
||||
pri->pKd += detkd;
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->pKd = 0; // 取消微分作用
|
||||
}
|
||||
|
||||
// 对Kp,Ki进行限幅
|
||||
if (pri->pKp < 0)
|
||||
{
|
||||
pri->pKp = 0;
|
||||
}
|
||||
if (pri->pKi < 0)
|
||||
{
|
||||
pri->pKi = 0;
|
||||
}
|
||||
|
||||
// 计算新的K1,nKi,nKd
|
||||
pri->nKp = pri->pKp + pri->pKi + pri->pKd;
|
||||
pri->nKi = -(pri->pKp + 2 * pri->pKd);
|
||||
pri->nKd = pri->pKd;
|
||||
}
|
||||
|
||||
if (iError > Emax)
|
||||
{
|
||||
pri->out = pri->max;
|
||||
pwm_var = 0;
|
||||
pri->flag = 1; // 设定标志位,如果误差超过了门限值,则认为当控制量第一次到达给定值时,应该采取下面的 抑制超调 的措施
|
||||
}
|
||||
else if (iError < -Emax)
|
||||
{
|
||||
pri->out = pri->min;
|
||||
pwm_var = 0;
|
||||
}
|
||||
else if (fabsf(iError) <= Emin)
|
||||
{
|
||||
pwm_var = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (iError < Emid && pri->flag == 1) // 第一次超过(设定值-Emid(-0.08)摄氏度),是输出为零,防止超调,也可以输出其他值,不至于太小而引起震荡
|
||||
{
|
||||
pri->out = 0;
|
||||
pri->flag = 0;
|
||||
}
|
||||
else if (-iError > Emid) // 超过(设定+Emid(+0.08)摄氏度)
|
||||
{
|
||||
pwm_var = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 增量计算
|
||||
pwm_var = (pri->nKp * iError // e[k]
|
||||
+ pri->nKi * pri->lasterror // e[k-1]
|
||||
+ pri->nKd * pri->preverror); // e[k-2]
|
||||
}
|
||||
if (pwm_var >= Umax)
|
||||
pwm_var = Umax; // 调整值限幅,防止积分饱和
|
||||
if (pwm_var <= Umin)
|
||||
pwm_var = Umin; // 调整值限幅,防止积分饱和
|
||||
}
|
||||
pri->preverror = pri->lasterror;
|
||||
pri->lasterror = iError;
|
||||
|
||||
pri->out += pwm_var; // 调整PWM输出
|
||||
|
||||
if (pri->out > pri->max)
|
||||
pri->out = pri->max; // 输出值限幅
|
||||
if (pri->out < pri->min)
|
||||
pri->out = pri->min; // 输出值限幅
|
||||
|
||||
return pri->out;
|
||||
}
|
||||
|
||||
/*整定开始前的预处理,判断状态及初始化变量*/
|
||||
static void tune_pretreatment(struct PID_X *self)
|
||||
{
|
||||
CLASSIC_AUTOTUNE *tune = &self->tune;
|
||||
CLASSICPID *vPID = &self->pri;
|
||||
|
||||
tune->tuneTimer = 0;
|
||||
tune->startTime = 0;
|
||||
tune->endTime = 0;
|
||||
tune->outputStep = 100;
|
||||
|
||||
if (*vPID->pSV >= *vPID->pPV)
|
||||
{
|
||||
tune->initialStatus = 1;
|
||||
tune->outputStatus = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
tune->initialStatus = 0;
|
||||
tune->outputStatus = 1;
|
||||
}
|
||||
tune->tuneEnable = 1;
|
||||
tune->preEnable = 0;
|
||||
tune->zeroAcrossCounter = 0;
|
||||
tune->riseLagCounter = 0;
|
||||
tune->fallLagCounter = 0;
|
||||
}
|
||||
|
||||
/*计算PID参数值*/
|
||||
static void calculation_parameters(struct PID_X *self)
|
||||
{
|
||||
CLASSIC_AUTOTUNE *tune = &self->tune;
|
||||
CLASSICPID *vPID = &self->pri;
|
||||
float32 kc = 0.0f;
|
||||
float32 tc = 0.0f;
|
||||
float32 zn[3][3] = {{0.5f, 100000.0f, 0.0f}, {0.45f, 0.8f, 0.0f}, {0.6f, 0.5f, 0.125f}};
|
||||
|
||||
tc = (tune->endTime - tune->startTime) * tune->tunePeriod / 1000.0;
|
||||
kc = (8.0f * tune->outputStep) / (PI * (tune->maxPV - tune->minPV));
|
||||
|
||||
vPID->pKp = zn[tune->controllerType][0] * kc; // 比例系数
|
||||
vPID->pKi = vPID->pKp * tune->tunePeriod / (zn[tune->controllerType][1] * tc); // 积分系数
|
||||
vPID->pKd = vPID->pKp * zn[tune->controllerType][2] * tc / tune->tunePeriod; // 微分系数
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 自整定函数
|
||||
* @param {PID_X} *self
|
||||
* @return {*}
|
||||
* @note 成员变量tuneEnable、preEnable和controllerType需要提前赋值。tuneEnable变量值为0时是使用PID控制器,而tuneEnable变量值为1时是开启整定过程,当tuneEnable变量值为2时是指示整定失败。preEnable变量在整定前赋值为1,表示先做预处理。而controllerType则根据所整定的控制器的类型来定,主要用于参数的计算。
|
||||
*/
|
||||
static uint8_t _auto_tune(struct PID_X *self)
|
||||
{
|
||||
CLASSIC_AUTOTUNE *tune = &self->tune;
|
||||
CLASSICPID *vPID = &self->pri;
|
||||
/*整定开始前的预处理,只执行一次*/
|
||||
if (tune->preEnable == 1)
|
||||
{
|
||||
tune_pretreatment(self);
|
||||
}
|
||||
|
||||
uint32_t tuneDuration = 0;
|
||||
tune->tuneTimer++;
|
||||
tuneDuration = (tune->tuneTimer * tune->tunePeriod) / 1000;
|
||||
if (tuneDuration > (10 * 60)) // 整定过程持续超过10分钟,未能形成有效振荡,整定失败
|
||||
{
|
||||
tune->tuneEnable = 2;
|
||||
tune->preEnable = 1;
|
||||
return tune->tuneEnable;
|
||||
}
|
||||
|
||||
if (*vPID->pSV >= *vPID->pPV) // 设定值大于测量值,则开执行单元
|
||||
{
|
||||
tune->riseLagCounter++;
|
||||
tune->fallLagCounter = 0;
|
||||
|
||||
if (tune->riseLagCounter > LAG_PHASE)
|
||||
{
|
||||
*vPID->pMV = vPID->max;
|
||||
if (tune->outputStatus == 0)
|
||||
{
|
||||
tune->outputStatus = 1;
|
||||
tune->zeroAcrossCounter++;
|
||||
|
||||
if (tune->zeroAcrossCounter == 3)
|
||||
{
|
||||
tune->startTime = tune->tuneTimer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tune->riseLagCounter = 0;
|
||||
tune->fallLagCounter++;
|
||||
|
||||
if (tune->fallLagCounter > LAG_PHASE)
|
||||
{
|
||||
*vPID->pMV = vPID->min;
|
||||
if (tune->outputStatus == 1)
|
||||
{
|
||||
tune->outputStatus = 0;
|
||||
tune->zeroAcrossCounter++;
|
||||
|
||||
if (tune->zeroAcrossCounter == 3)
|
||||
{
|
||||
tune->startTime = tune->tuneTimer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tune->zeroAcrossCounter == 3) // 已经两次过零,可以记录波形数据
|
||||
{
|
||||
if (tune->initialStatus == 1) // 初始设定值大于测量值,则区域3出现最小值
|
||||
{
|
||||
if (*vPID->pPV < tune->minPV)
|
||||
{
|
||||
tune->minPV = *vPID->pPV;
|
||||
}
|
||||
}
|
||||
else if (tune->initialStatus == 0) // 初始设定值小于测量值,则区域3出现最大值
|
||||
{
|
||||
if (*vPID->pPV > tune->maxPV)
|
||||
{
|
||||
tune->maxPV = *vPID->pPV;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tune->zeroAcrossCounter == 4) // 已经三次过零,记录另半波的数据
|
||||
{
|
||||
if (tune->initialStatus == 1) // 初始设定值大于测量值,则区域4出现最大值
|
||||
{
|
||||
if (*vPID->pPV > tune->maxPV)
|
||||
{
|
||||
tune->maxPV = *vPID->pPV;
|
||||
}
|
||||
}
|
||||
else if (tune->initialStatus == 0) // 初始设定值小于测量值,则区域4出现最小值
|
||||
{
|
||||
if (*vPID->pPV < tune->minPV)
|
||||
{
|
||||
tune->minPV = *vPID->pPV;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (tune->zeroAcrossCounter == 5) // 已经四次过零,振荡已形成可以整定参数
|
||||
{
|
||||
calculation_parameters(self);
|
||||
|
||||
tune->tuneEnable = 0;
|
||||
tune->preEnable = 1;
|
||||
}
|
||||
|
||||
return tune->tuneEnable;
|
||||
}
|
||||
|
||||
void pid_x_constructor(struct PID_X *self)
|
||||
{
|
||||
self->PID = _PID;
|
||||
self->AUTO_TUNE = _auto_tune;
|
||||
self->pri.flag = 0;
|
||||
self->pri.out = 0;
|
||||
self->tune.preEnable = 1;
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
#ifndef __PID_X_H__
|
||||
#define __PID_X_H__
|
||||
#include "lib.h"
|
||||
|
||||
/*定义PID对象类型*/
|
||||
typedef struct CLASSIC
|
||||
{
|
||||
float32 *pPV; // 测量值指针
|
||||
float32 *pSV; // 设定值指针
|
||||
float32 *pMV; // 输出值指针
|
||||
BOOL *pMA; // 手自动操作指针
|
||||
|
||||
float32 out; // 输出值
|
||||
float32 setpoint; // 设定值
|
||||
float32 lasterror; // 前一拍偏差
|
||||
float32 preverror; // 前两拍偏差
|
||||
float32 max; // 输出值上限
|
||||
float32 min; // 输出值下限
|
||||
|
||||
uint16_t flag; // 状态标志位
|
||||
|
||||
float32 pKp; // 比例系数
|
||||
float32 pKi; // 积分系数
|
||||
float32 pKd; // 微分系数
|
||||
|
||||
float32 nKp; // 比例系数
|
||||
float32 nKi; // 积分系数
|
||||
float32 nKd; // 微分系数
|
||||
|
||||
BOOL direct; // 正反作用
|
||||
BOOL sm; // 设定值平滑
|
||||
BOOL cas; // 串级设定
|
||||
BOOL pac; // 输出防陡变
|
||||
BOOL kd_e; // 微分使能
|
||||
} CLASSICPID;
|
||||
|
||||
// 定义整定参数
|
||||
typedef struct
|
||||
{
|
||||
uint8_t tuneEnable : 2; // 整定与PID控制开关,0:PID控制;1:参数整定;2:整定失败
|
||||
uint8_t preEnable : 2; // 预处理使能,在开始整定前置位
|
||||
uint8_t initialStatus : 1; // 记录开始整定前偏差的初始状态
|
||||
uint8_t outputStatus : 1; // 记录输出的初始状态,0允许上升过零计数;1允许下降过零计数
|
||||
uint8_t controllerType : 2; // 控制器类型:0,P控制器;1,PI控制器;2,PID控制器
|
||||
|
||||
uint8_t zeroAcrossCounter; // 过零点计数器,每次输出改变加1,比实际过零次数多1
|
||||
uint8_t riseLagCounter; // 上升迟滞时间计数器
|
||||
uint8_t fallLagCounter; // 下降迟滞时间计数器
|
||||
|
||||
uint16_t tunePeriod; // 整定采样周期
|
||||
uint32_t tuneTimer; // 整定计时器
|
||||
uint32_t startTime; // 记录波形周期起始时间
|
||||
uint32_t endTime; // 记录波形周期结束时间
|
||||
|
||||
float32 outputStep; // 输出阶跃d
|
||||
float32 maxPV; // 振荡波形中测量值的最大值
|
||||
float32 minPV; // 振荡波形中测量值的最小值
|
||||
} CLASSIC_AUTOTUNE;
|
||||
|
||||
typedef struct PID_X
|
||||
{
|
||||
/* 控制接口 */
|
||||
float32 (*PID)(struct PID_X *self, float32 target, float32 feedback);
|
||||
uint8_t (*AUTO_TUNE)(struct PID_X *self);
|
||||
/* private */
|
||||
CLASSICPID pri;
|
||||
CLASSIC_AUTOTUNE tune;
|
||||
} pid_x_t;
|
||||
|
||||
extern void pid_x_constructor(struct PID_X *self);
|
||||
#endif // __PID_X_H__
|
||||
|
|
@ -0,0 +1,467 @@
|
|||
#include "pid_zh.h"
|
||||
#include "sys.h"
|
||||
#include <math.h>
|
||||
|
||||
// 定义输出量比例因子
|
||||
#ifdef GPS3000
|
||||
#define KUP 0.0f // #define KUP 3.0f
|
||||
#define KUI 0.00f
|
||||
#define KUD 0.0f // #define KUP 3.0f
|
||||
#else
|
||||
#define KUP 3.0f
|
||||
#define KUI 0.0f
|
||||
#define KUD 0.0f
|
||||
#endif
|
||||
|
||||
// 模糊集合
|
||||
#define NL -3
|
||||
#define NM -2
|
||||
#define NS -1
|
||||
#define ZE 0
|
||||
#define PS 1
|
||||
#define PM 2
|
||||
#define PL 3
|
||||
|
||||
// 定义偏差E的范围,因为设置了非线性区间,误差在10时才开始进行PID调节,这里E的范围为10
|
||||
#define MAXE (10)
|
||||
#define MINE (-MAXE)
|
||||
// 定义EC的范围,因为变化非常缓慢!,每次的EC都非常小,这里可以根据实际需求来调整,
|
||||
#define MAXEC (10)
|
||||
#define MINEC (-MAXEC)
|
||||
// 定义e,ec的量化因子
|
||||
#define KE 3 / MAXE
|
||||
#define KEC 3 / MAXEC
|
||||
|
||||
static const float32 fuzzyRuleKp[7][7] = {
|
||||
PL, PL, PM, PL, PS, PM, PL,
|
||||
PL, PM, PM, PM, PS, PM, PL,
|
||||
PM, PS, PS, PS, PS, PS, PM,
|
||||
PM, PS, ZE, ZE, ZE, PS, PM,
|
||||
PS, PS, PS, PS, PS, PM, PM,
|
||||
PM, PM, PM, PM, PL, PL, PL,
|
||||
PM, PL, PL, PL, PL, PL, PL};
|
||||
|
||||
static const float32 fuzzyRuleKi[7][7] = {
|
||||
PL, PL, PL, PL, PM, PL, PL,
|
||||
PL, PL, PM, PM, PM, PL, PL,
|
||||
PM, PM, PS, PS, PS, PM, PM,
|
||||
PM, PS, ZE, ZE, ZE, PS, PM,
|
||||
PM, PS, PS, PS, PS, PM, PM,
|
||||
PM, PM, PS, PM, PM, PL, PL,
|
||||
PM, PL, PM, PL, PL, PL, PL};
|
||||
|
||||
/*
|
||||
static const float32 fuzzyRuleKi[7][7] = {
|
||||
NL, NL, NL, NL, NM, NL, NL,
|
||||
NL, NL, NM, NM, NM, NL, NL,
|
||||
NM, NM, NS, NS, NS, NM, NM,
|
||||
NM, NS, ZE, ZE, ZE, NS, NM,
|
||||
NM, NS, NS, NS, NS, NM, NM,
|
||||
NM, NM, NS, NM, NM, NL, NL,
|
||||
NM, NL, NM, NL, NL, NL, NL};
|
||||
*/
|
||||
|
||||
static const float32 fuzzyRuleKd[7][7] = {
|
||||
PS, PS, ZE, ZE, ZE, PL, PL,
|
||||
PS, PS, PS, PS, ZE, PS, PM,
|
||||
PL, PL, PM, PS, ZE, PS, PM,
|
||||
PL, PM, PM, PS, ZE, PS, PM,
|
||||
PL, PM, PS, PS, ZE, PS, PS,
|
||||
PM, PS, PS, PS, ZE, PS, PS,
|
||||
PS, ZE, ZE, ZE, ZE, PL, PL};
|
||||
|
||||
/*
|
||||
static const float32 fuzzyRuleKp[7][7] = {
|
||||
PL, PL, PM, PM, PS, ZE, ZE,
|
||||
PL, PL, PM, PS, PS, ZE, NS,
|
||||
PM, PM, PM, PS, ZE, NS, NS,
|
||||
PM, PM, PS, ZE, NS, NM, NM,
|
||||
PS, PS, ZE, NS, NS, NM, NM,
|
||||
PS, ZE, NS, NM, NM, NM, NL,
|
||||
ZE, ZE, NM, NM, NM, NL, NL};
|
||||
|
||||
static const float32 fuzzyRuleKi[7][7] = {
|
||||
NL, NL, NM, NM, NS, ZE, ZE,
|
||||
NL, NL, NM, NS, NS, ZE, ZE,
|
||||
NL, NM, NS, NS, ZE, PS, PS,
|
||||
NM, NM, NS, ZE, PS, PM, PM,
|
||||
NM, NS, ZE, PS, PS, NM, PL,
|
||||
ZE, ZE, PS, PS, PM, PL, PL,
|
||||
ZE, ZE, PS, PM, PM, PL, PL};
|
||||
|
||||
static const float32 fuzzyRuleKd[7][7] = {
|
||||
PS, NS, NL, NL, NL, NM, PS,
|
||||
PS, NS, NL, NM, NM, NS, ZE,
|
||||
ZE, NS, NM, NM, NS, NS, ZE,
|
||||
ZE, NS, NS, NS, NS, NS, ZE,
|
||||
|
||||
ZE, ZE, ZE, ZE, ZE, ZE, ZE,
|
||||
PL, NS, PS, PS, PS, PS, PL,
|
||||
PL, PM, PM, PM, PS, PS, PL};
|
||||
*/
|
||||
|
||||
static void fuzzy(float32 e, float32 ec, FUZZY_PID_ZH_t *fuzzy_pid)
|
||||
{
|
||||
|
||||
float32 etemp, ectemp;
|
||||
float32 eLefttemp, ecLefttemp; // ec,e,左隶属度
|
||||
float32 eRighttemp, ecRighttemp;
|
||||
|
||||
int eLeftIndex, ecLeftIndex; // 模糊位置标号
|
||||
int eRightIndex, ecRightIndex;
|
||||
e = RANGE(e, MINE, MAXE);
|
||||
ec = RANGE(ec, MINEC, MAXEC);
|
||||
e = e * KE;
|
||||
ec = ec * KEC;
|
||||
|
||||
etemp = e > 3.0f ? 0.0f : (e < -3.0f ? 0.0f : (e >= 0.0f ? (e >= 2.0f ? 2.5f : (e >= 1.0f ? 1.5f : 0.5f)) : (e >= -1.0f ? -0.5f : (e >= -2.0f ? -1.5f : (e >= -3.0f ? -2.5f : 0.0f)))));
|
||||
eLeftIndex = (int)((etemp - 0.5f) + 3); //[-3,2] -> [0,5]
|
||||
eRightIndex = (int)((etemp + 0.5f) + 3);
|
||||
eLefttemp = etemp == 0.0f ? 0.0f : ((etemp + 0.5f) - e);
|
||||
eRighttemp = etemp == 0.0f ? 0.0f : (e - (etemp - 0.5f));
|
||||
|
||||
ectemp = ec > 3.0f ? 0.0f : (ec < -3.0f ? 0.0f : (ec >= 0.0f ? (ec >= 2.0f ? 2.5f : (ec >= 1.0f ? 1.5f : 0.5f)) : (ec >= -1.0f ? -0.5f : (ec >= -2.0f ? -1.5f : (ec >= -3.0f ? -2.5f : 0.0f)))));
|
||||
ecLeftIndex = (int)((ectemp - 0.5f) + 3); //[-3,2] -> [0,5]
|
||||
ecRightIndex = (int)((ectemp + 0.5f) + 3);
|
||||
ecLefttemp = ectemp == 0.0f ? 0.0f : ((ectemp + 0.5f) - ec);
|
||||
ecRighttemp = ectemp == 0.0f ? 0.0f : (ec - (ectemp - 0.5f));
|
||||
|
||||
/*************************************反模糊*************************************/
|
||||
|
||||
fuzzy_pid->kp = (eLefttemp * ecLefttemp * fuzzyRuleKp[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKp[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKp[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKp[eRightIndex][ecRightIndex]);
|
||||
|
||||
fuzzy_pid->ki = (eLefttemp * ecLefttemp * fuzzyRuleKi[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKi[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKi[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKi[eRightIndex][ecRightIndex]);
|
||||
|
||||
fuzzy_pid->kd = (eLefttemp * ecLefttemp * fuzzyRuleKd[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKd[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKd[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKd[eRightIndex][ecRightIndex]);
|
||||
// 对解算出的KP,KI,KD进行量化映射
|
||||
fuzzy_pid->kp = fuzzy_pid->kp * fuzzy_pid->kup;
|
||||
fuzzy_pid->ki = fuzzy_pid->ki * fuzzy_pid->kui;
|
||||
fuzzy_pid->kd = fuzzy_pid->kd * fuzzy_pid->kud;
|
||||
}
|
||||
|
||||
static void smoothSetpoint(struct PID_FUZZY_ZH *self, float32 target_sv)
|
||||
{
|
||||
#if FUZZY_SUB_TYPE == PID_SUB_TYPE_POSITION
|
||||
pid_zh_position_t *pri = &self->pri;
|
||||
#else
|
||||
pid_common_increment_t *pri = &self->pri;
|
||||
#endif
|
||||
float32 stepIn = (pri->sv_range) * 0.1f;
|
||||
float32 kFactor = 0.0f;
|
||||
if (fabs(pri->ref - target_sv) <= stepIn)
|
||||
{
|
||||
pri->ref = target_sv;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pri->ref - target_sv > 0)
|
||||
{
|
||||
kFactor = -1.0f;
|
||||
}
|
||||
else if (pri->ref - target_sv < 0)
|
||||
{
|
||||
kFactor = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
kFactor = 0.0f;
|
||||
}
|
||||
pri->ref = pri->ref + kFactor * stepIn;
|
||||
}
|
||||
}
|
||||
|
||||
/*封装模糊接口*/
|
||||
static void compensate(float32 e, float32 ec, FUZZY_PID_ZH_t *fuzzy_d)
|
||||
{
|
||||
fuzzy(e, ec, fuzzy_d);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 复位PID积分及微分控制数据
|
||||
* @param {PID_FUZZY_ZH} *self
|
||||
* @return {*}
|
||||
*/
|
||||
static void _restctrl(struct PID_FUZZY_ZH *self)
|
||||
{
|
||||
self->pri.pre_error = 0;
|
||||
self->pri.sum_iterm = 0;
|
||||
#if INCOMPLETE_DIFFEREN == 1
|
||||
self->pri.lastdev = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新最大最小值
|
||||
* @param {PID_FUZZY_ZH} *self
|
||||
* @param {float32} out_min
|
||||
* @param {float32} out_max
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_range(struct PID_FUZZY_ZH *self, float32 out_min, float32 out_max)
|
||||
{
|
||||
self->pri.out_max = out_max;
|
||||
self->pri.out_min = out_min;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使能积分控制
|
||||
* @param {PID_FUZZY_ZH} *self
|
||||
* @param {BOOL} enable
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
// static void _set_ki_enable(struct PID_FUZZY_ZH *self, BOOL enable)
|
||||
// {
|
||||
// self->pri.ki_enable = enable;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief 使能微分控制
|
||||
* @param {PID_FUZZY_ZH} *self
|
||||
* @param {BOOL} enable
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_kd_enable(struct PID_FUZZY_ZH *self, BOOL enable)
|
||||
{
|
||||
self->pri.kd_enable = enable;
|
||||
}
|
||||
|
||||
/*
|
||||
* Function:使能平滑控制
|
||||
* parameter:*pid需要配,PID参数结构指针,sv_range控制范围sv的范围
|
||||
* return:无
|
||||
*/
|
||||
static void _set_smooth_enable(struct PID_FUZZY_ZH *self, BOOL enable, float32 sv_range)
|
||||
{
|
||||
#if FUZZY_SUB_TYPE == PID_SUB_TYPE_POSITION
|
||||
pid_zh_position_t *pri = &self->pri;
|
||||
#else
|
||||
pid_common_increment_t *pri = &self->pri;
|
||||
#endif
|
||||
pri->sm = enable;
|
||||
pri->sv_range = sv_range;
|
||||
}
|
||||
|
||||
// 设置控制参数
|
||||
static void _set_ctrl_prm(struct PID_FUZZY_ZH *self, float32 kp, float32 ki, float32 kd, float32 err_dead,
|
||||
float32 out_min, float32 out_max)
|
||||
{
|
||||
self->open = TRUE;
|
||||
self->fuzzy.kup = KUP;
|
||||
self->fuzzy.kui = KUI;
|
||||
self->fuzzy.kud = KUD;
|
||||
#if FUZZY_SUB_TYPE == PID_SUB_TYPE_POSITION
|
||||
pid_zh_position_t *pri = &self->pri;
|
||||
osel_memset((uint8_t *)pri, 0, sizeof(pid_zh_position_t));
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
pri->detach = FALSE;
|
||||
pri->sm = FALSE;
|
||||
#else
|
||||
pid_common_increment_t *pri = &self->pri;
|
||||
osel_memset((uint8_t *)pri, 0, sizeof(pid_common_increment_t));
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
#endif
|
||||
|
||||
if (kd > 0)
|
||||
{
|
||||
pri->kd_enable = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->kd_enable = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void _update_ctrl_prm(struct PID_FUZZY_ZH *self, float32 kp, float32 ki, float32 kd, float32 err_dead,
|
||||
float32 out_min, float32 out_max)
|
||||
{
|
||||
#if FUZZY_SUB_TYPE == PID_SUB_TYPE_POSITION
|
||||
pid_zh_position_t *pri = &self->pri;
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
pri->detach = FALSE;
|
||||
pri->sm = FALSE;
|
||||
#else
|
||||
pid_common_increment_t *pri = &self->pri;
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
#endif
|
||||
|
||||
if (kd > 0)
|
||||
{
|
||||
pri->kd_enable = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->kd_enable = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 非0时配置为积分分离+抗积分饱和PID,否则为普通抗积分饱和PID
|
||||
* @param {PID_FUZZY_ZH} *self
|
||||
* @param {float32} max_err
|
||||
* @param {BOOL} mode
|
||||
* @return {*}
|
||||
*/
|
||||
static void _set_cfg(struct PID_FUZZY_ZH *self, float32 max_err, BOOL mode)
|
||||
{
|
||||
self->pri.err_limit = max_err;
|
||||
self->pri.detach = mode == FALSE ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
#if FUZZY_SUB_TYPE == PID_SUB_TYPE_POSITION
|
||||
static float32 _PID(struct PID_FUZZY_ZH *self, float32 target, float32 feedback)
|
||||
{
|
||||
float32 error = 0;
|
||||
float32 insert = 0;
|
||||
float32 ec = 0;
|
||||
float32 kd = 0;
|
||||
#if INCOMPLETE_DIFFEREN == 1
|
||||
float32 thisdev = 0;
|
||||
#else
|
||||
// float32 dinput = 0.0f;
|
||||
#endif
|
||||
float32 temp_iterm = 0.0f;
|
||||
pid_zh_position_t *pri = &self->pri;
|
||||
|
||||
/*获取期望值与实际值,进行偏差计算*/
|
||||
if (pri->sm == 1)
|
||||
{
|
||||
smoothSetpoint(self, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->ref = target;
|
||||
}
|
||||
|
||||
pri->feed_back = feedback;
|
||||
error = pri->ref - pri->feed_back;
|
||||
if (fabs(error) <= pri->err_dead)
|
||||
error = 0;
|
||||
|
||||
/* fuzzy control caculate */
|
||||
ec = error - pri->pre_error;
|
||||
if (self->open == TRUE)
|
||||
{
|
||||
compensate(error, ec, &self->fuzzy);
|
||||
}
|
||||
|
||||
/*根据PID配置的模式,获取积分数据,进行积分累加*/
|
||||
if (pri->out >= pri->out_max)
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach)
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
if (error < 0)
|
||||
{
|
||||
temp_iterm = (pri->ki + self->fuzzy.ki) * error;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pri->out <= pri->out_min)
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach)
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
if (error > 0)
|
||||
{
|
||||
temp_iterm = (pri->ki + self->fuzzy.ki) * error;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach)
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
temp_iterm = (pri->ki + self->fuzzy.ki) * error;
|
||||
}
|
||||
}
|
||||
pri->sum_iterm += temp_iterm;
|
||||
/* limt integral */
|
||||
pri->sum_iterm = RANGE(pri->sum_iterm, pri->out_min, pri->out_max);
|
||||
/*
|
||||
if (pri->sum_ref)
|
||||
pri->sum_iterm = RANGE(pri->sum_iterm, pri->sum_ref - 1.0f, pri->sum_ref + 1.0f);
|
||||
else
|
||||
pri->sum_iterm = RANGE(pri->sum_iterm, pri->out_min, pri->out_max);
|
||||
*/
|
||||
#if INCOMPLETE_DIFFEREN == 1
|
||||
/*不完全微分*/
|
||||
thisdev = (pri->kd + self->fuzzy.kd) * (1.0 - pri->alpha) * (error - pri->pre_error) + pri->alpha * pri->lastdev;
|
||||
/*caculate pid out*/
|
||||
pri->out = (pri->kp + self->fuzzy.kp) * error + pri->sum_iterm * insert + thisdev;
|
||||
|
||||
/*record last dev result*/
|
||||
pri->lastdev = thisdev;
|
||||
#else
|
||||
|
||||
if (pri->kd_enable == TRUE)
|
||||
{
|
||||
kd = pri->kd + self->fuzzy.kd;
|
||||
}
|
||||
else
|
||||
{
|
||||
kd = 0;
|
||||
}
|
||||
|
||||
pri->out = (pri->kp + self->fuzzy.kp) * error + pri->sum_iterm * insert + (error - pri->pre_error) * (kd);
|
||||
// pri->out += pri->sum_ref;
|
||||
|
||||
#endif
|
||||
|
||||
pri->pre_error = error;
|
||||
/*record last feedback sensor result*/
|
||||
pri->pre_feed_back = pri->feed_back;
|
||||
/*limt pid output*/
|
||||
pri->out = RANGE(pri->out, pri->out_min, pri->out_max);
|
||||
return pri->out;
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
|
||||
void pid_zh_constructor(struct PID_FUZZY_ZH *self)
|
||||
{
|
||||
self->set_ctrl_prm = _set_ctrl_prm;
|
||||
self->update_ctrl_prm = _update_ctrl_prm;
|
||||
self->set_cfg = _set_cfg;
|
||||
self->set_smooth_enable = _set_smooth_enable;
|
||||
// self->set_ki_enable = _set_ki_enable;
|
||||
self->set_kd_enable = _set_kd_enable;
|
||||
self->set_range = _set_range;
|
||||
self->restctrl = _restctrl;
|
||||
self->PID = _PID;
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef __PID_ZH_H__
|
||||
#define __PID_ZH_H__
|
||||
#include "lib.h"
|
||||
#include "pid_auto_tune.h"
|
||||
|
||||
#define GPS2000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float32 ref;
|
||||
float32 feed_back;
|
||||
float32 pre_feed_back;
|
||||
float32 pre_error;
|
||||
float32 sum_ref;
|
||||
float32 sum_iterm;
|
||||
float32 kp;
|
||||
float32 ki;
|
||||
float32 kd;
|
||||
float32 err_limit;
|
||||
BOOL detach;
|
||||
float32 err_dead;
|
||||
#if INCOMPLETE_DIFFEREN == 1
|
||||
float32 alpha;
|
||||
float32 lastdev;
|
||||
#endif
|
||||
float32 out;
|
||||
float32 out_max;
|
||||
float32 out_min;
|
||||
float32 sv_range;
|
||||
BOOL sm;
|
||||
BOOL ki_enable;
|
||||
BOOL kd_enable;
|
||||
} pid_zh_position_t; // 位置式PID
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float32 kp;
|
||||
float32 ki;
|
||||
float32 kd;
|
||||
|
||||
float32 kup;
|
||||
float32 kui;
|
||||
float32 kud;
|
||||
} FUZZY_PID_ZH_t;
|
||||
|
||||
// 模糊PID
|
||||
typedef struct PID_FUZZY_ZH
|
||||
{
|
||||
/* 设置PID三个参数 */
|
||||
void (*set_ctrl_prm)(struct PID_FUZZY_ZH *self, float32 kp, float32 ki, float32 kd, float32 err_dead,
|
||||
float32 out_min, float32 out_max); // 设置PID参数
|
||||
void (*update_ctrl_prm)(struct PID_FUZZY_ZH *self, float32 kp, float32 ki, float32 kd, float32 err_dead,
|
||||
float32 out_min, float32 out_max); // 更新PID参数
|
||||
void (*set_range)(struct PID_FUZZY_ZH *self, float32 out_min, float32 out_max); // 更新最大最小值
|
||||
void (*set_cfg)(struct PID_FUZZY_ZH *self, float32 max_err, BOOL mode); // 配置PID模式,默认不使用积分分离
|
||||
void (*set_smooth_enable)(struct PID_FUZZY_ZH *self, BOOL enable, float32 sv_range); // 设置平滑范围
|
||||
// void (*set_ki_enable)(struct PID_FUZZY *self, BOOL enable);
|
||||
// 微分开启使能
|
||||
void (*set_kd_enable)(struct PID_FUZZY_ZH *self, BOOL enable);
|
||||
void (*restctrl)(struct PID_FUZZY_ZH *self); // 复位PID积分及微分控制数据
|
||||
/* 控制接口 */
|
||||
float32 (*PID)(struct PID_FUZZY_ZH *self, float32 target, float32 feedback);
|
||||
|
||||
pid_zh_position_t pri;
|
||||
|
||||
BOOL open; // 是否使用模糊PID控制
|
||||
|
||||
FUZZY_PID_ZH_t fuzzy;
|
||||
|
||||
} pid_zh_t; // 模糊PID
|
||||
|
||||
extern void pid_zh_constructor(struct PID_FUZZY_ZH *self);
|
||||
#endif
|
||||
|
|
@ -0,0 +1,619 @@
|
|||
|
||||
#include <math.h>
|
||||
#include "pid_zh1.h"
|
||||
|
||||
// 定义输出量比例因子
|
||||
#define KUP 1.0f
|
||||
#define KUI 1.0f
|
||||
#define KUD 1.0f
|
||||
|
||||
// 模糊集合
|
||||
#define NL -3
|
||||
#define NM -2
|
||||
#define NS -1
|
||||
#define ZE 0
|
||||
#define PS 1
|
||||
#define PM 2
|
||||
#define PL 3
|
||||
|
||||
// 定义偏差E的范围,因为设置了非线性区间,误差在10时才开始进行PID调节,这里E的范围为10
|
||||
#define MAXE (10)
|
||||
#define MINE (-MAXE)
|
||||
// 定义EC的范围,因为变化非常缓慢!,每次的EC都非常小,这里可以根据实际需求来调整,
|
||||
#define MAXEC (10)
|
||||
#define MINEC (-MAXEC)
|
||||
// 定义e,ec的量化因子
|
||||
#define KE 3 / MAXE
|
||||
#define KEC 3 / MAXEC
|
||||
/*
|
||||
static const float fuzzyRuleKp[7][7] = {
|
||||
PL, PL, PM, PL, PS, PM, PL,
|
||||
PL, PM, PM, PM, PS, PM, PL,
|
||||
PM, PS, PS, PS, PS, PS, PM,
|
||||
PM, PS, ZE, ZE, ZE, PS, PM,
|
||||
PS, PS, PS, PS, PS, PM, PM,
|
||||
PM, PM, PM, PM, PL, PL, PL,
|
||||
PM, PL, PL, PL, PL, PL, PL};
|
||||
|
||||
static const float fuzzyRuleKi[7][7] = {
|
||||
PL, PL, PL, PL, PM, PL, PL,
|
||||
PL, PL, PM, PM, PM, PL, PL,
|
||||
PM, PM, PS, PS, PS, PM, PM,
|
||||
PM, PS, ZE, ZE, ZE, PS, PM,
|
||||
PM, PS, PS, PS, PS, PM, PM,
|
||||
PM, PM, PS, PM, PM, PL, PL,
|
||||
PM, PL, PM, PL, PL, PL, PL};
|
||||
|
||||
static const float fuzzyRuleKd[7][7] = {
|
||||
PS, PS, ZE, ZE, ZE, PL, PL,
|
||||
PS, PS, PS, PS, ZE, PS, PM,
|
||||
PL, PL, PM, PS, ZE, PS, PM,
|
||||
PL, PM, PM, PS, ZE, PS, PM,
|
||||
PL, PM, PS, PS, ZE, PS, PS,
|
||||
PM, PS, PS, PS, ZE, PS, PS,
|
||||
PS, ZE, ZE, ZE, ZE, PL, PL};
|
||||
*/
|
||||
static const float fuzzyRuleKp[7][7] = {
|
||||
PL, PL, PM, PL, PS, PM, PL,
|
||||
PL, PM, PM, PM, PS, PM, PL,
|
||||
PM, PS, PS, PS, PS, PS, PM,
|
||||
PM, PS, ZE, ZE, ZE, PS, PM,
|
||||
PS, PS, PS, PS, PS, PM, PM,
|
||||
PM, PM, PM, PM, PL, PL, PL,
|
||||
PM, PL, PL, PL, PL, PL, PL};
|
||||
|
||||
static const float fuzzyRuleKi[7][7] = {
|
||||
PL, PL, PL, PL, PM, PL, PL,
|
||||
PL, PL, PM, PM, PM, PL, PL,
|
||||
PM, PM, PS, PS, PS, PM, PM,
|
||||
PM, PS, ZE, ZE, ZE, PS, PM,
|
||||
PM, PS, PS, PS, PS, PM, PM,
|
||||
PM, PM, PS, PM, PM, PL, PL,
|
||||
PM, PL, PM, PL, PL, PL, PL};
|
||||
|
||||
static const float fuzzyRuleKd[7][7] = {
|
||||
PS, PS, ZE, ZE, ZE, PL, PL,
|
||||
PS, PS, PS, PS, ZE, PS, PM,
|
||||
PL, PL, PM, PS, ZE, PS, PM,
|
||||
PL, PM, PM, PS, ZE, PS, PM,
|
||||
PL, PM, PS, PS, ZE, PS, PS,
|
||||
PM, PS, PS, PS, ZE, PS, PS,
|
||||
PS, ZE, ZE, ZE, ZE, PL, PL};
|
||||
|
||||
static void fuzzy(float e, float ec, fuzzy_pid_t *fuzzy_pid)
|
||||
{
|
||||
|
||||
float etemp, ectemp;
|
||||
float eLefttemp, ecLefttemp; // 左隶属度
|
||||
float eRighttemp, ecRighttemp; // 右隶属度
|
||||
|
||||
int eLeftIndex, ecLeftIndex; // 模糊位置标号
|
||||
int eRightIndex, ecRightIndex;
|
||||
e = RANGE(e, MINE, MAXE);
|
||||
ec = RANGE(ec, MINEC, MAXEC);
|
||||
e = e * KE;
|
||||
ec = ec * KEC;
|
||||
|
||||
etemp = e > 3.0f ? 0.0f : (e < -3.0f ? 0.0f : (e >= 0.0f ? (e >= 2.0f ? 2.5f : (e >= 1.0f ? 1.5f : 0.5f)) : (e >= -1.0f ? -0.5f : (e >= -2.0f ? -1.5f : (e >= -3.0f ? -2.5f : 0.0f)))));
|
||||
eLeftIndex = (int)((etemp - 0.5f) + 3);
|
||||
eRightIndex = (int)((etemp + 0.5f) + 3);
|
||||
eLefttemp = etemp == 0.0f ? 0.0f : ((etemp + 0.5f) - e);
|
||||
eRighttemp = etemp == 0.0f ? 0.0f : (e - (etemp - 0.5f));
|
||||
|
||||
ectemp = ec > 3.0f ? 0.0f : (ec < -3.0f ? 0.0f : (ec >= 0.0f ? (ec >= 2.0f ? 2.5f : (ec >= 1.0f ? 1.5f : 0.5f)) : (ec >= -1.0f ? -0.5f : (ec >= -2.0f ? -1.5f : (ec >= -3.0f ? -2.5f : 0.0f)))));
|
||||
ecLeftIndex = (int)((ectemp - 0.5f) + 3);
|
||||
ecRightIndex = (int)((ectemp + 0.5f) + 3);
|
||||
ecLefttemp = ectemp == 0.0f ? 0.0f : ((ectemp + 0.5f) - ec);
|
||||
ecRighttemp = ectemp == 0.0f ? 0.0f : (ec - (ectemp - 0.5f));
|
||||
|
||||
/*************************************反模糊*************************************/
|
||||
|
||||
fuzzy_pid->kp = (eLefttemp * ecLefttemp * fuzzyRuleKp[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKp[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKp[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKp[eRightIndex][ecRightIndex]);
|
||||
|
||||
fuzzy_pid->ki = (eLefttemp * ecLefttemp * fuzzyRuleKi[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKi[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKi[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKi[eRightIndex][ecRightIndex]);
|
||||
|
||||
fuzzy_pid->kd = (eLefttemp * ecLefttemp * fuzzyRuleKd[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKd[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKd[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKd[eRightIndex][ecRightIndex]);
|
||||
// 对解算出的KP,KI,KD进行量化映射
|
||||
fuzzy_pid->kp = fuzzy_pid->kp * fuzzy_pid->kup;
|
||||
fuzzy_pid->ki = fuzzy_pid->ki * fuzzy_pid->kui;
|
||||
fuzzy_pid->kd = fuzzy_pid->kd * fuzzy_pid->kud;
|
||||
}
|
||||
|
||||
static void smooth_init(smart_pid_t *pid, BOOL sm_open, float maxTarget)
|
||||
{
|
||||
if (!pid)
|
||||
return;
|
||||
|
||||
pid->sm_open = sm_open;
|
||||
pid->maxTarget = maxTarget;
|
||||
}
|
||||
|
||||
static void smooth_target(smart_pid_t *pid, float *target)
|
||||
{
|
||||
|
||||
if ((!pid) || (!target))
|
||||
return;
|
||||
|
||||
if (!pid->maxTarget)
|
||||
return;
|
||||
|
||||
float sm_step = (pid->maxTarget) * 0.1f;
|
||||
float k = 0.0f;
|
||||
|
||||
if (fabs(pid->target - *target) <= sm_step)
|
||||
{
|
||||
pid->target = *target;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pid->target - *target > 0)
|
||||
{
|
||||
k = -1.0f;
|
||||
}
|
||||
else if (pid->target - *target < 0)
|
||||
{
|
||||
k = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
k = 0.0f;
|
||||
}
|
||||
pid->target += k * sm_step;
|
||||
}
|
||||
}
|
||||
|
||||
static void smart_pid_init(smart_pid_t *pid, float *duty, float *kp, float *ki, float *kd, float *errorDead, float *iDetachCondation, float *maxOut)
|
||||
{
|
||||
if ((!pid) || (!duty) || (!kp) || (!ki) || (!kd) || (!iDetachCondation) || (!maxOut))
|
||||
return;
|
||||
|
||||
pid->duty = *duty;
|
||||
pid->kp = *kp;
|
||||
pid->ki = *ki;
|
||||
pid->kd = *kd;
|
||||
pid->errorDead = *errorDead;
|
||||
pid->iDetachCondation = *iDetachCondation;
|
||||
pid->maxOutput = *maxOut;
|
||||
}
|
||||
|
||||
void cascade_pid_init(cascade_pid_t *pid, smart_pid_t *pid_outer, smart_pid_t *pid_inner)
|
||||
{
|
||||
|
||||
smooth_init(&pid->outer, pid_outer->sm_open, pid_outer->maxTarget);
|
||||
smooth_init(&pid->inner, pid_inner->sm_open, pid_inner->maxTarget);
|
||||
|
||||
smart_pid_init(&pid->outer, &pid_outer->duty, &pid_outer->kp, &pid_outer->ki, &pid_outer->kd, &pid_outer->errorDead, &pid_outer->iDetachCondation, &pid_outer->maxOutput);
|
||||
smart_pid_init(&pid->inner, &pid_inner->duty, &pid_inner->kp, &pid_inner->ki, &pid_inner->kd, &pid_inner->errorDead, &pid_inner->iDetachCondation, &pid_inner->maxOutput);
|
||||
}
|
||||
|
||||
void smart_pid_calc(smart_pid_t *pid, float *target, float *feedback)
|
||||
{
|
||||
// 将旧error存起来
|
||||
pid->lastError = pid->error;
|
||||
// 平滑处理目标值
|
||||
if (pid->sm_open == TRUE)
|
||||
smooth_target(pid, target);
|
||||
else
|
||||
pid->target = *target;
|
||||
// 计算新error
|
||||
pid->error = pid->target - *feedback;
|
||||
if (fabs(pid->error) <= pid->errorDead)
|
||||
pid->error = 0.0f;
|
||||
// 计算误差变化
|
||||
pid->dError = pid->error - pid->lastError;
|
||||
// 选用模糊规则
|
||||
if (pid->fuzzy_open == TRUE)
|
||||
{
|
||||
fuzzy(pid->error, pid->dError, &pid->fuzzy_pid);
|
||||
}
|
||||
// 计算微分
|
||||
float dout = pid->dError * (pid->kd + pid->fuzzy_pid.kd);
|
||||
// 计算比例
|
||||
float pout = pid->error * (pid->kp + pid->fuzzy_pid.kp);
|
||||
// 积分分离
|
||||
if (fabs(pid->error) <= pid->iDetachCondation)
|
||||
{
|
||||
pid->integral += pid->error * (pid->ki + pid->fuzzy_pid.ki);
|
||||
pid->iDetach = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid->iDetach = TRUE;
|
||||
pid->integral = 0;
|
||||
}
|
||||
// 积分限幅
|
||||
if (pid->integral > pid->maxOutput)
|
||||
pid->integral = pid->maxOutput;
|
||||
else if (pid->integral < -pid->maxOutput)
|
||||
pid->integral = -pid->maxOutput;
|
||||
// 计算输出
|
||||
pid->output = pout + dout + pid->integral * (!pid->iDetach);
|
||||
// 输出限幅
|
||||
if (pid->output > pid->maxOutput)
|
||||
pid->output = pid->maxOutput;
|
||||
else if (pid->output < -pid->maxOutput)
|
||||
pid->output = -pid->maxOutput;
|
||||
}
|
||||
|
||||
void cascade_pid_calc(struct CascadePID *pid, float *outerRef, float *outerFdb, float *innerFdb)
|
||||
{
|
||||
// 外环位置控制
|
||||
smart_pid_calc(&pid->cascade_pid.outer, outerRef, outerFdb);
|
||||
// 内环速度控制
|
||||
smart_pid_calc(&pid->cascade_pid.inner, &pid->cascade_pid.outer.output, innerFdb);
|
||||
// 内环输出就是串级PID的输出
|
||||
// pid->cascade_pid.output = pid->cascade_pid.inner.output;
|
||||
pid->cascade_pid.output = pid->cascade_pid.outer.output;
|
||||
}
|
||||
|
||||
void pid_zh_constructor1(struct CascadePID *pid)
|
||||
{
|
||||
pid->smart_pid_init = smart_pid_init;
|
||||
pid->smart_pid_calc = smart_pid_calc;
|
||||
pid->cascade_pid_init = cascade_pid_init;
|
||||
pid->cascade_pid_calc = cascade_pid_calc;
|
||||
}
|
||||
|
||||
/*
|
||||
典型输入信号
|
||||
L[1] -> 1/s
|
||||
L[t] -> 1/s^2
|
||||
L[1/2t^2] -> 1/s^3
|
||||
系统传递函数
|
||||
G(s) = k/(TS+1)
|
||||
G(s) = k/S^i(TS+1)
|
||||
i = 0 0型系统 i = 1 I 型系统 I = 2 II 型系统
|
||||
系统开环传递函数: G(s)
|
||||
系统闭环传递函数: G闭(s) = G(s) / 1 + G(s)
|
||||
系统静差: R(s) = 1/1+G(s)
|
||||
终值定理:limf(t) = lim s*G(s)
|
||||
t->无穷 s->0
|
||||
o型系统,单位输入:r/s
|
||||
e = lim f(t) = lim s*G(s) = G(s) = k/1+G(s) = k/ 1 + (Ts+1+k)..
|
||||
t->无穷 s->0 s->0 s->0 = s->0
|
||||
*系统误差有给定和扰动引起的
|
||||
*线性系统符合叠加原理
|
||||
*动态性能指标
|
||||
超调 、过度过程时间、上升时间 tr 延迟时间 td 峰值指标tp
|
||||
误差积分指标大 好小 好?
|
||||
典型二阶系统
|
||||
G(s) = 1/ s^2 *(2ξwn)s + wn^2
|
||||
ξ:阻尼系统
|
||||
wn = 1/T:震荡频率
|
||||
如果有一个极点和零点很接近,可以忽略该极点。
|
||||
主导极点用二阶系统定义的
|
||||
开环传递函数 K T
|
||||
复变函数
|
||||
频率特性
|
||||
阻容RC电路 一阶低通滤波器 一阶惰性系统
|
||||
Gs = 1/Ts+1
|
||||
正弦稳态
|
||||
|
||||
控制系统设计依据:
|
||||
考虑到被控对象包含 弹簧储能、填料阻尼、EPM气压控制等主导环节,忽略其它非主导惯性、阻尼环节,将被控系统模型归纳为典型二阶阻尼震荡系统:
|
||||
被控对象开环传递函数:G(s) = wn^2/ s*( s + 2ξwn)
|
||||
未加控制器被控对象闭环传递函数:G(s) = wn^2/ (s^2 + (2ξwn)s + wn^2)
|
||||
ξ :为系统阻尼比。跟阀座填料材、压力正相关(忽略其他非主导阻尼环节)
|
||||
Wn :无阻尼自然震荡角频率。跟弹簧的固有振荡频率正相关(忽略其他非主导惯性环节)
|
||||
|
||||
阀杆填料阻尼比: 0 < ξ < 1
|
||||
|
||||
系统特征方程s^2 + (2ξwn)s + wn^2 = 0 时,有两个共轭复根,要想系统稳定,根的实部要远离虚轴,且在允许情况下,越远越稳定。
|
||||
标准二阶系统是零型系统,存在静差。而且,考虑气体压缩比大,导致系统的惯性环节增大。
|
||||
因此需要控制器将系统变为I型系统来消除静差(增加积分环节),而且需要在控制器传递函数的零点增加最小项来克服系统的大惯性(增加微分环节)。因此,设计控制器类型为PID控制器。
|
||||
|
||||
※ 难点在于系统的无阻尼自然震荡角频率和阀座阻尼系数无法准确获得,整定算法计算的特性参数不一定合理。
|
||||
※ 另一个难点在于EPM本身的控制特性线性度差,会导致控制器的矫正环节达不到理想效果或在部分区间达不到理想效果,因此,
|
||||
系统会不稳定或在部分区间不稳定。
|
||||
※ EMP本身的控制线性度差,通过控制器弥补EPM性能的不足。
|
||||
(因为阀杆速度变化快慢,直接由EPM IP开度大小决定,因此考虑增加阀杆速度环控制来弥补EPM本身的不足)
|
||||
|
||||
算法核心原理说明 :
|
||||
核心算法: PID
|
||||
u(t) = k*(e(t) +∫e(t)dt + de(t)dt)
|
||||
u(n) = k*(e(n) + ∑e(n)/Ti + Td*(e(n) - e(n-1)))
|
||||
|
||||
位置控制:PID + PD控制
|
||||
误差在[-5%,5%] 内 PID 控制,控制静差 < 0.3%
|
||||
误差在[-100%,5%] 或 [5%,100%] 时,PD控制,防止积分引起的系统不稳定
|
||||
|
||||
积分分离:当误差<= 5% 时,加入积分
|
||||
当误差> 5%时,积分分离、积分项清零
|
||||
积分限幅:
|
||||
当积分项 >= 100% 积分项 = 100%
|
||||
当积分项 <= -100% 积分项 = -100%
|
||||
|
||||
整定算法: 继电反馈整定
|
||||
设定目标位置:额定行程 60%
|
||||
控制初始位置: < 60%
|
||||
施加最大控制量 ->检测实时位置->过零,计t0、上冲峰值 -> 施加最小控制量 -> 检测实时位置 -> 过零,计t1、下冲峰值— ↓
|
||||
↑ <—————————————————————————————————————————————————————————————————————————————————————————————————|
|
||||
|
||||
循环三次,记录系统稳态下波峰、波谷,和震荡周期Tu
|
||||
计算: Ku = (控制量最大值 - 控制量最小值)/ (位置波峰 - 位置波谷)
|
||||
查表:Kp = 0.8 * Ku
|
||||
Ti = 0.6 * Tu
|
||||
Td = 0.125 * Tu
|
||||
Ki = Kp / Ti = Kp / (0.6 * Tu)
|
||||
Kd = Kp * Td = Kp * 0.125 * Td
|
||||
|
||||
速度控制算法:
|
||||
s1:初始位置AD值
|
||||
s2:单位时间位置移动后AD值
|
||||
v1:当前速度
|
||||
v2: 前速度
|
||||
v: 实时速度
|
||||
a:实时加速度
|
||||
t: 速度计算周期
|
||||
v = (s2 - s1) / t
|
||||
a = (v2 - v1) / t
|
||||
|
||||
整定过程,分别整定上升过程平均速度和下降过程平均速度。
|
||||
分别以上升和下降速度为PID 内环参考速度,计算速度控制量。
|
||||
速度偏差 = 当前速度 - 参考速度
|
||||
加速度 = 速度偏差变化(即位置的二阶导数)
|
||||
速度控制量 = Kp*速度偏差 + Ki * ∑速度偏差 + Kd * 加速度
|
||||
|
||||
串级控制
|
||||
外环位置检测,进行速度控制
|
||||
内环速度检测,进行位置控制
|
||||
位置偏差 = 位置给定 - 位置反馈
|
||||
位置偏差变化 = 位置偏差 - 上次位置偏差
|
||||
速度控制量 = Kp1*位置偏差 + Ki1 * ∑位置偏差 + Kd1 * 位置偏差变化
|
||||
速度偏差 = 速度控制量 - 速度反馈
|
||||
速度偏差变化 = 速度偏差 - 上次速度偏差
|
||||
位置控制量 = Kp2*速度偏差 + Ki2 * ∑速度偏差 + Kd2 * 速度偏差变化
|
||||
串级速度整定:???
|
||||
|
||||
automatic control theory
|
||||
control system
|
||||
linear and nonlinear system
|
||||
线性连续系统
|
||||
离散系统
|
||||
modelling
|
||||
analysis
|
||||
矫正
|
||||
分析 时域分析方法 微分方程 传递函数 一阶系统 二阶系统
|
||||
频域分析方法 闭环特性 低频特性 高频特性
|
||||
根轨迹
|
||||
建模 分析 矫正
|
||||
离散系统
|
||||
非线性控制理论
|
||||
L变换 Z变换
|
||||
采样控制系统 数字控制系统
|
||||
条件稳定系统
|
||||
problems
|
||||
time consuming
|
||||
行列式计算
|
||||
相对稳定性
|
||||
劳斯准则只能判断系统是否稳定,不能判断稳定程度
|
||||
伯德图
|
||||
nyquist 图
|
||||
相位裕度
|
||||
闭环特征方程的根闭,开环增益0到无穷 特征方程根的变化规律
|
||||
root locus
|
||||
单位负反馈
|
||||
G(s) = k/s(s+1)
|
||||
复角条件复值条件
|
||||
渐近线与实轴交点
|
||||
出射角p-n/n-m
|
||||
|
||||
lambda
|
||||
大纯滞后
|
||||
积分对象
|
||||
lambda
|
||||
纯滞后 tao/t
|
||||
斯密斯预估 闭环传递函数 整定方法
|
||||
阶跃响应 鲁棒性 积分过程 输入干扰
|
||||
执行机构偏离,不确定不准确,调整速度
|
||||
PI 为主 D
|
||||
两自由度PID 比例积分先行
|
||||
鲁棒性 性能不完全微分
|
||||
负载扰
|
||||
|
||||
makefile 工作原理:
|
||||
1、可执行文件生成过程:预处理->编译->链接
|
||||
#source object target
|
||||
SRC := *.c
|
||||
OBJ := *.o
|
||||
TARGET := mqtt_test
|
||||
|
||||
#compile library include
|
||||
CC := cc
|
||||
LIB:= -lpthread
|
||||
LDFLAG := -L. libmosquitto.so
|
||||
CFLAG :=
|
||||
CXXFLAG :=
|
||||
#link
|
||||
$(TARGET):$(OBJ)
|
||||
$(CC) -o $(TARGET) $(BOJ) $(LIB) $(LDFLAG)
|
||||
$(CC) -o $@ $^ $(LIB) $(LDFLAG)
|
||||
$(OBJ):$(SRC)
|
||||
$(CC) -c $(SRC)
|
||||
#all
|
||||
all:$(TARGET)
|
||||
$(CC) -o $(TARGET) $(SRC) $(LIB) $(LDFLAG)
|
||||
#clean
|
||||
clean:
|
||||
rm -rf *.o $(TARGET)
|
||||
*/
|
||||
// ————————————————————————————————————
|
||||
|
||||
/*
|
||||
typedef struct
|
||||
{
|
||||
int argc;
|
||||
char **argv;
|
||||
int sock;
|
||||
}st_app_param;
|
||||
|
||||
const char *g_version = "V1.0.0 24.08.16";
|
||||
int child_mian_ini()
|
||||
{
|
||||
int i_re;
|
||||
i_re = gw_support_init();
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("解析支持文件错误.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
i_re = gw_config_init();
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("解析配置文件错误.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
i_re = gw_data_init();
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("数据初始化错误.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
i_re = gw_modol_init();
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("解析模型文件错误.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
i_re = gw_stat_init();
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("数据统计初始化错误.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
i_re = gw_process_init();
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("规约初始化错误.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
i_re = gw_manage_init();
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("界面通讯初始化错误.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
pthread_create(tid,thread_task1,p_param);
|
||||
|
||||
while(1)
|
||||
{
|
||||
get_sys_time();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
int child_main(int argc,char **argv)
|
||||
{
|
||||
|
||||
if((argc == 2)&&(strcasecmp(argv[1],"-version") == 0))
|
||||
{
|
||||
printf("version [%s]",g_version);
|
||||
}
|
||||
|
||||
child_mian_ini();
|
||||
|
||||
}
|
||||
|
||||
static int run_child_process_thread((void *)param)
|
||||
{
|
||||
st_app_param *p_param;
|
||||
|
||||
p_param = param;
|
||||
chlid_main(p_parm->argc,p_param->argv);
|
||||
}
|
||||
|
||||
static int run_child_process(int argc,char **argv,int sock)
|
||||
{
|
||||
int i_re;
|
||||
pthread_t *tid;
|
||||
st_app_param p_param;
|
||||
|
||||
p_param.argc = argc;
|
||||
p_param.argv = argv;
|
||||
p_param.sock = sock;
|
||||
|
||||
pthread_creat(tid,run_child_process_thread,(void *)p_param);
|
||||
i_re = recv(sock,..,0);//阻塞接收
|
||||
send(sock,'q',0);
|
||||
}
|
||||
|
||||
int main(int argc,char**argv)
|
||||
{
|
||||
int i_re,fd,pid;
|
||||
char lock_file[16];
|
||||
socketpair sockpaire[2];
|
||||
|
||||
sprintf(lock_file,"%s.lock",argv[0]);
|
||||
|
||||
fd = open(lock_file,_O_WRONLY|_EXC,0777);
|
||||
if(fd < 0)
|
||||
{
|
||||
printf("应用:[%s]已运行.\n",argv[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sockpaire[0] = -1;
|
||||
|
||||
while(1)
|
||||
{
|
||||
i_re = socketpaire(sockpaire,AN_UNIX,..);
|
||||
if(i_re < 0)
|
||||
{
|
||||
printf("[%d]创建sockpaire失败.\n",getpid());
|
||||
return 0;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
|
||||
if(pid < 0)
|
||||
{
|
||||
printf("[%d]创建进程失败.\n",getpid());
|
||||
close(sockpaire[0]);
|
||||
sockpaire[0] = -1
|
||||
close(sockpaire[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(pid == 0)//child process
|
||||
{
|
||||
close(sockpair[0]);
|
||||
return run_child_process(argc,argv,sockpair[1]);
|
||||
}
|
||||
|
||||
printf("[%d]创建子进程成功.\n",getpid());
|
||||
close(sockpair[1]);
|
||||
while(1)//father process
|
||||
{
|
||||
i_re = recv(sockpaire[0],....,0);//阻塞接收
|
||||
if(i_re <= 0)
|
||||
{
|
||||
//连接中断,重连
|
||||
sockpaire[0] = -1;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
//正常退出
|
||||
goto EXIT:
|
||||
}
|
||||
}
|
||||
|
||||
EXIT:
|
||||
//退出操作
|
||||
if(sockpaire[0] == -1)
|
||||
{
|
||||
//有子进程,关闭子进程
|
||||
}
|
||||
close(fd);
|
||||
unlink(file_lock);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* @Author: 张小明 zxm5337@163.com
|
||||
* @Date: 2024-06-25 10:26:36
|
||||
* @LastEditors: DaMingSY zxm5337@163.com
|
||||
* @LastEditTime: 2024-09-03 09:19:23
|
||||
* @FilePath: \controller-v2\User\lib\control\custom\cascade_pid_zh.h
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
#ifndef __CASCADE_PID_ZH__
|
||||
#define __CASCADE_PID_ZH__
|
||||
|
||||
#include "data_type_def.h"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
TARGET_DIR_ADD,
|
||||
TARGET_DIR_SUB,
|
||||
} target_dir_e;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float kp;
|
||||
float ki;
|
||||
float kd;
|
||||
|
||||
float kup;
|
||||
float kui;
|
||||
float kud;
|
||||
} fuzzy_pid_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
BOOL iDetach, sm_open, fuzzy_open;
|
||||
float feedBack;
|
||||
float target, maxTarget;
|
||||
float duty, kp, ki, kd;
|
||||
float error, dError, lastError, errorDead;
|
||||
float integral, iDetachCondation;
|
||||
float output, maxOutput;
|
||||
fuzzy_pid_t fuzzy_pid;
|
||||
} smart_pid_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
smart_pid_t inner;
|
||||
smart_pid_t outer;
|
||||
float output;
|
||||
} cascade_pid_t;
|
||||
|
||||
typedef struct CascadePID
|
||||
{
|
||||
void (*smart_pid_init)(smart_pid_t *pid, float *duty, float *kp, float *ki, float *kd, float *errorDead, float *iDetachCondation, float *maxOut);
|
||||
void (*cascade_pid_init)(cascade_pid_t *pid, smart_pid_t *pid_outer, smart_pid_t *pid_inner);
|
||||
void (*smart_pid_calc)(smart_pid_t *pid, float *target, float *feedback);
|
||||
void (*cascade_pid_calc)(struct CascadePID *pid, float *outerRef, float *outerFdb, float *innerFdb);
|
||||
|
||||
smart_pid_t smart_pid;
|
||||
cascade_pid_t cascade_pid;
|
||||
} pid_zh1_t; // PID_t;
|
||||
|
||||
extern void pid_zh_constructor1(struct CascadePID *pid);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# 架构
|
||||
|文件|路径| <div style="width:300px">说明</div>|
|
||||
|:--|:--|:--|
|
||||
|pid|User\lib\control\src|PID算法模块|
|
||||
|execute|User\application\src|执行器|
|
||||
|app_flow|User|任务流程控制|
|
||||
|
||||
## APP_FLOW任务流程控制
|
||||
> adjust_inspection 中在没有检测到调试信号时执行<b style="color:blue">execute_dac,EXECUTE_PLAN</b>定义了需要执行的算法任务计划
|
||||
|
||||
## PID算法模块
|
||||
文件内容:
|
||||
|文件| <div style="width:300px">说明</div>|
|
||||
|:--|:--|
|
||||
|pid.c|构造算法的入口|
|
||||
|pid_common.c|普通算法|
|
||||
|pid_neural.c|神经网络算法|
|
||||
|pid_fuzzy.c|模糊算法|
|
||||
|
||||
<b style="color:blue">custom 目录下为各自算法实现</b>
|
||||
|
||||
|
||||
## EXECUTE执行器
|
||||
> execute_pid_init中定义了初始化参数
|
||||
> execute_dac中定义了执行器
|
||||
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
#ifndef __PID_H__
|
||||
#define __PID_H__
|
||||
#include "lib.h"
|
||||
|
||||
#include "pid_auto_tune.h"
|
||||
|
||||
#include "pid_c.h"
|
||||
#include "pid_g.h"
|
||||
#include "pid_x.h"
|
||||
#include "pid_zh.h"
|
||||
#include "pid_zh1.h"
|
||||
#include "pid_hd.h"
|
||||
|
||||
#define INCOMPLETE_DIFFEREN 1 // 不完全微分
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PID_SUB_TYPE_POSITION = 1, // 位置式
|
||||
PID_SUB_TYPE_INCREMENT = 2, // 增量式
|
||||
} pid_sub_type_e;
|
||||
typedef enum
|
||||
{
|
||||
DEAD_ZONE_BOTH = 0, // 正负都可以
|
||||
DEAD_ZONE_POSITIVE = 1, // 正数
|
||||
DEAD_ZONE_NEGATIVE = 2, // 负数
|
||||
} deadzone_e;
|
||||
typedef enum
|
||||
{
|
||||
// PID自整定
|
||||
PID_TYPE_AUTO_TUNE,
|
||||
// 通用PID
|
||||
PID_TYPE_COMMON,
|
||||
// 神经PID
|
||||
PID_TYPE_NEURAL,
|
||||
// 模糊PID
|
||||
PID_TYPE_FUZZY,
|
||||
|
||||
// 以下是自定义PID
|
||||
|
||||
// cj PID
|
||||
PID_TYPE_CUSTOM_CAO,
|
||||
// gp jPID
|
||||
PID_TYPE_CUSTOM_GAO,
|
||||
// xsh PID
|
||||
PID_TYPE_CUSTOM_XU,
|
||||
// zxm PID
|
||||
PID_TYPE_CUSTOM_ZHANG,
|
||||
// hangdian PID
|
||||
PID_TYPE_CUSTOM_HANGDIAN,
|
||||
} pid_type_e;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float32 ref;
|
||||
float32 feedback;
|
||||
float32 pre_feedback;
|
||||
float32 e_0; // 当前误差
|
||||
float32 e_1; // 上一次误差
|
||||
float32 kp;
|
||||
float32 ki;
|
||||
float32 kd;
|
||||
float32 err_limit;
|
||||
BOOL detach;
|
||||
float32 err_dead;
|
||||
// 不完全微分方式在微分环节采用了低通滤波有效地提高了微分项的特性。其中α的取值是一个0~1之间的数。两个极限值,在0时其实就是没有滤波的普通微分环节;而取1时,则没有微分作用。所以α的取值对不完全微分的效果是至关重要的,一般要根据被控对象的特性来确定
|
||||
float32 alpha;
|
||||
float32 lastdev;
|
||||
float32 out;
|
||||
float32 out_max;
|
||||
float32 out_min;
|
||||
float32 sv_range;
|
||||
float32 iout; // 积分输出
|
||||
BOOL sm;
|
||||
BOOL ki_enable;
|
||||
BOOL kd_enable;
|
||||
float32 deviation; // 纠正系统误差造成的影响,作用于死区,大于0需要补偿,小于0需要反向补偿
|
||||
BOOL in_dead_zone; // 是否在死区内
|
||||
} pid_common_position_t; // 位置式PID
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float32 ref; // 目标设定值
|
||||
float32 feedback; // 传感器采集值
|
||||
float32 out; // PID计算结果
|
||||
float32 kp;
|
||||
float32 ki;
|
||||
float32 kd;
|
||||
float32 e_0; // 当前误差
|
||||
float32 e_1; // 上一次误差
|
||||
float32 e_2; // 上上次误差
|
||||
float32 err_dead;
|
||||
float32 deviation; // 纠正系统误差造成的影响,作用于死区
|
||||
float32 out_max; // 输出限幅
|
||||
float32 out_min; // 输出限幅
|
||||
float32 sum_iterm;
|
||||
float32 iout; // 积分输出
|
||||
float32 alpha; // 不完全微分参数
|
||||
float32 lastdev; // 不完全微分参数
|
||||
|
||||
BOOL sm;
|
||||
BOOL ki_enable;
|
||||
BOOL kd_enable;
|
||||
float32 sv_range;
|
||||
// 没有用的
|
||||
float32 err_limit;
|
||||
BOOL detach;
|
||||
} pid_common_increment_t; // 增量式PID
|
||||
|
||||
typedef struct PID_COMMON
|
||||
{
|
||||
uint8_t type;
|
||||
|
||||
/* 设置PID三个参数 */
|
||||
void (*set_ctrl_prm)(struct PID_COMMON *self, float32 kp, float32 ki, float32 kd);
|
||||
/* 设置积分范围 */
|
||||
void (*set_integral_prm)(struct PID_COMMON *self, float32 integral_up, float32 integral_low);
|
||||
|
||||
/* 控制接口 */
|
||||
float32 (*PID)(struct PID_COMMON *self, float32 err);
|
||||
|
||||
/* in value */
|
||||
float32 err;
|
||||
/* out value */
|
||||
float32 out;
|
||||
|
||||
union
|
||||
{
|
||||
pid_common_position_t position;
|
||||
pid_common_increment_t increment;
|
||||
} pri_u;
|
||||
|
||||
} pid_common_t; // 通用PID
|
||||
|
||||
typedef struct PID_NEURAL
|
||||
{
|
||||
uint8_t type;
|
||||
/* 设置PID三个参数 */
|
||||
void (*set_ctrl_prm)(struct PID_NEURAL *self, float32 minimum, float32 maximum);
|
||||
/* 设置输出范围 */
|
||||
void (*set_out_prm)(struct PID_NEURAL *self, float32 minimum, float32 maximum);
|
||||
/* 控制接口 */
|
||||
float32 (*PID)(struct PID_NEURAL *self, float32 target, float32 feedback);
|
||||
|
||||
struct
|
||||
{
|
||||
float32 setpoint; /*设定值*/
|
||||
float32 kcoef; /*神经元输出比例*/
|
||||
float32 kp; /*比例学习速度*/
|
||||
float32 ki; /*积分学习速度*/
|
||||
float32 kd; /*微分学习速度*/
|
||||
float32 lasterror; /*前一拍偏差*/
|
||||
float32 preerror; /*前两拍偏差*/
|
||||
float32 deadband; /*死区*/
|
||||
float32 result; /*输出值*/
|
||||
float32 output; /*百分比输出值*/
|
||||
float32 maximum; /*输出值的上限*/
|
||||
float32 minimum; /*输出值的下限*/
|
||||
float32 wp; /*比例加权系数*/
|
||||
float32 wi; /*积分加权系数*/
|
||||
float32 wd; /*微分加权系数*/
|
||||
} pri;
|
||||
} pid_neural_t; // 神经PID
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float32 kp;
|
||||
float32 ki;
|
||||
float32 kd;
|
||||
|
||||
float32 kup;
|
||||
float32 kui;
|
||||
float32 kud;
|
||||
|
||||
float32 maxe; // 非线性区间最大值
|
||||
float32 mine; // 非线性区间最小值
|
||||
} FUZZY_PID_t;
|
||||
|
||||
// 模糊PID
|
||||
typedef struct PID_FUZZY
|
||||
{
|
||||
/* 设置PID三个参数 */
|
||||
void (*set_ctrl_prm)(struct PID_FUZZY *self, float32 kp, float32 ki, float32 kd, float32 err_dead, float32 deviation,
|
||||
float32 out_min, float32 out_max); // 设置PID参数
|
||||
void (*set_error_max_min)(struct PID_FUZZY *self, float32 mine, float32 maxe); // 设置非线性区间值
|
||||
void (*update_ctrl_prm)(struct PID_FUZZY *self, float32 kp, float32 ki, float32 kd, float32 err_dead,
|
||||
float32 out_min, float32 out_max); // 更新PID参数
|
||||
void (*set_range)(struct PID_FUZZY *self, float32 out_min, float32 out_max); // 更新最大最小值
|
||||
void (*set_cfg)(struct PID_FUZZY *self, float32 max_err, BOOL mode); // 配置PID模式,默认不使用积分分离
|
||||
void (*set_smooth_enable)(struct PID_FUZZY *self, BOOL enable, float32 sv_range); // 设置平滑范围
|
||||
void (*set_iout)(struct PID_FUZZY *self, float32 iout); // 设置积分输出
|
||||
void (*set_err_dead)(struct PID_FUZZY *self, float32 err_dead); // 设置死区
|
||||
void (*set_kp)(struct PID_FUZZY *self, float32 kp);
|
||||
void (*set_ki_enable)(struct PID_FUZZY *self, BOOL enable);
|
||||
void (*set_ki)(struct PID_FUZZY *self, float32 ki);
|
||||
// 微分开启使能
|
||||
void (*set_kd_enable)(struct PID_FUZZY *self, BOOL enable);
|
||||
void (*set_kd)(struct PID_FUZZY *self, float32 kd);
|
||||
void (*set_kd_dev)(struct PID_FUZZY *self, float32 alpha);
|
||||
void (*restctrl)(struct PID_FUZZY *self, float32 out); // 复位PID积分及微分控制数据
|
||||
/* 控制接口 */
|
||||
float32 (*execute)(struct PID_FUZZY *self, float32 target, float32 feedback);
|
||||
|
||||
BOOL(*in_dead_zone)
|
||||
(struct PID_FUZZY *self);
|
||||
|
||||
union
|
||||
{
|
||||
pid_common_position_t position;
|
||||
pid_common_increment_t increment;
|
||||
} pri_u;
|
||||
|
||||
pid_sub_type_e sub_type; // 位置式PID,增量式PID
|
||||
BOOL open; // 是否使用模糊PID控制
|
||||
BOOL speed_integral_enable; // 变速积分,暂时没有验证成功
|
||||
deadzone_e deadzone_dir;
|
||||
FUZZY_PID_t pid_params;
|
||||
|
||||
} pid_fuzzy_t; // 模糊PID
|
||||
|
||||
// PID
|
||||
typedef struct
|
||||
{
|
||||
BOOL is_init; // 是否初始化
|
||||
pid_type_e type; // 不同的算法类型,模糊PID,神经PID,通用PID
|
||||
pid_sub_type_e sub_type; // 位置式PID,增量式PID
|
||||
union
|
||||
{
|
||||
pid_common_t common;
|
||||
pid_neural_t neural;
|
||||
pid_fuzzy_t fuzzy;
|
||||
|
||||
// 自定义PID
|
||||
pid_c_t cao;
|
||||
pid_g_t gao;
|
||||
pid_x_t xu;
|
||||
pid_zh_t zhang;
|
||||
pid_zh1_t zhang1;
|
||||
pid_hd_t hd;
|
||||
} pid_u;
|
||||
pid_auto_tune_t auto_tune;
|
||||
} pid_t;
|
||||
|
||||
// PID控制
|
||||
extern void pid_constructor(pid_t *self);
|
||||
|
||||
// private
|
||||
// 神经元PID
|
||||
extern void pid_neural_constructor(struct PID_NEURAL *self);
|
||||
// 模糊PID
|
||||
extern void pid_fuzzy_constructor(struct PID_FUZZY *self);
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
/***
|
||||
* @Author:
|
||||
* @Date: 2023-07-24 11:17:55
|
||||
* @LastEditors: xxx
|
||||
* @LastEditTime: 2023-07-24 11:19:06
|
||||
* @Description:pid自动调参,构建闭环回路 确定稳定极限 确定两个参数 极限值KP和震荡周期
|
||||
* @email:
|
||||
* @Copyright (c) 2023 by xxx, All Rights Reserved.
|
||||
*/
|
||||
#ifndef __PID_AUTO_TUNE_H__
|
||||
#define __PID_AUTO_TUNE_H__
|
||||
#include "lib.h"
|
||||
|
||||
typedef struct PID_AUTO_TUNE
|
||||
{
|
||||
// public:
|
||||
void (*set_ctrl_prm)(struct PID_AUTO_TUNE *self, float32 *input, float32 *output);
|
||||
int32_t (*runtime)(struct PID_AUTO_TUNE *self);
|
||||
void (*set_output_step)(struct PID_AUTO_TUNE *self, int32_t step);
|
||||
void (*set_control_type)(struct PID_AUTO_TUNE *self, int32_t type);
|
||||
void (*set_noise_band)(struct PID_AUTO_TUNE *self, int32_t band);
|
||||
void (*set_look_back)(struct PID_AUTO_TUNE *self, int32_t n);
|
||||
float32 (*get_kp)(struct PID_AUTO_TUNE *self);
|
||||
float32 (*get_ki)(struct PID_AUTO_TUNE *self);
|
||||
float32 (*get_kd)(struct PID_AUTO_TUNE *self);
|
||||
// private:
|
||||
struct
|
||||
{
|
||||
BOOL isMax, isMin; // 运算中出现最大、最小值标志
|
||||
float32 *input, *output;
|
||||
float32 setpoint; // 反向控制判断值,这个值需要根据对象的实际工作值确定!是通过第一次启动时对应的输入值带入的。
|
||||
int32_t noiseBand; // 判断回差,类似于施密特触发器,实际控制反向的比较值是 setpoint + noiseBand 或 setpoint - noiseBand
|
||||
int32_t controlType; // 计算 PID 参数时,选择 PI 或 PID 模式,输出 Kp Ki,或 Kp、Ki、Kd
|
||||
BOOL running;
|
||||
uint32_t peak1, peak2, lastTime; // 峰值对应的时间
|
||||
int32_t sampleTime;
|
||||
int32_t nLookBack;
|
||||
int32_t peakType;
|
||||
int32_t lastInputs[51]; // 保存的历史输入值, 改为 50 次。 by shenghao.xu
|
||||
int32_t peaks[13]; // 保存的历史峰值,最多存前 12 次,对应 6个最大、6个最小。20221124 by Embedream
|
||||
int32_t peakCount; // 峰值计数
|
||||
int32_t peakPeriod[7]; // 保存前 6 次的最大值间隔时间 by shenghao.xu
|
||||
int32_t peakMaxCount; // 最大峰值计数 by shenghao.xu
|
||||
BOOL justchanged;
|
||||
int32_t oStep; // 这个值是用于计算控制高低值的,以 outputStart 为中值,输出高值用 outputStart + oStep, 输出低值用 outputStart - oStep
|
||||
float32 outputStart; // 输出控制的基础值,这个需要结合对象特征确定,此值也是通过第一次启动时对应的输出值带入的。
|
||||
float32 Ku, Pu;
|
||||
} pri;
|
||||
|
||||
} pid_auto_tune_t;
|
||||
|
||||
extern void pid_auto_tune_constructor(struct PID_AUTO_TUNE *self);
|
||||
#endif // __PID_AUTO_TUNE_H__
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
#include "pid.h"
|
||||
#include <math.h>
|
||||
|
||||
// 构造函数将接口绑定
|
||||
void pid_constructor(pid_t *self)
|
||||
{
|
||||
switch (self->type)
|
||||
{
|
||||
case PID_TYPE_COMMON:
|
||||
/* code */
|
||||
break;
|
||||
case PID_TYPE_NEURAL:
|
||||
pid_neural_constructor(&self->pid_u.neural);
|
||||
break;
|
||||
case PID_TYPE_FUZZY:
|
||||
DBG_ASSERT(self->sub_type != 0 __DBG_LINE);
|
||||
self->pid_u.fuzzy.sub_type = self->sub_type;
|
||||
pid_fuzzy_constructor(&self->pid_u.fuzzy);
|
||||
break;
|
||||
case PID_TYPE_AUTO_TUNE:
|
||||
pid_auto_tune_constructor(&self->auto_tune);
|
||||
break;
|
||||
case PID_TYPE_CUSTOM_CAO:
|
||||
pid_c_constructor(&self->pid_u.cao);
|
||||
break;
|
||||
case PID_TYPE_CUSTOM_GAO:
|
||||
pid_g_constructor(&self->pid_u.gao);
|
||||
break;
|
||||
case PID_TYPE_CUSTOM_XU:
|
||||
pid_x_constructor(&self->pid_u.xu);
|
||||
break;
|
||||
case PID_TYPE_CUSTOM_ZHANG:
|
||||
// pid_zh_constructor(&self->pid_u.zhang);
|
||||
pid_zh_constructor1(&self->pid_u.zhang1);
|
||||
break;
|
||||
case PID_TYPE_CUSTOM_HANGDIAN:
|
||||
pid_hd_constructor(&self->pid_u.hd);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
self->is_init = TRUE;
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
#include "pid_auto_tune.h"
|
||||
#include "sys.h"
|
||||
|
||||
/*
|
||||
设置峰值回溯时间,单位 0.1 秒,最小 0.2秒, 最大 4 秒
|
||||
*/
|
||||
static void set_look_backsec(pid_auto_tune_t *self, int32_t value)
|
||||
{
|
||||
if (value < 2)
|
||||
value = 2;
|
||||
if (value > 40)
|
||||
value = 40;
|
||||
|
||||
if (value < 40)
|
||||
{
|
||||
self->pri.nLookBack = 12; // 按目前实际周期约300ms、采样周期 10ms 考虑,一个周期只有 30 点,回溯 12 点即可。
|
||||
self->pri.sampleTime = value * 10; // 改为 Value*10 ms, 20、30、40 ~ 200ms
|
||||
}
|
||||
else
|
||||
{
|
||||
self->pri.nLookBack = 50 + value;
|
||||
self->pri.sampleTime = 4000;
|
||||
}
|
||||
}
|
||||
|
||||
static void _set_ctrl_prm(struct PID_AUTO_TUNE *self, float32 *input, float32 *output)
|
||||
{
|
||||
self->pri.input = input;
|
||||
self->pri.output = output;
|
||||
self->pri.controlType = 0; // 默认为 PI 模式
|
||||
self->pri.noiseBand = 1;
|
||||
self->pri.running = FALSE;
|
||||
self->pri.oStep = 1;
|
||||
set_look_backsec(self, 8);
|
||||
self->pri.lastTime = sys_millis();
|
||||
}
|
||||
|
||||
static void _set_noise_band(struct PID_AUTO_TUNE *self, int32_t value)
|
||||
{
|
||||
self->pri.noiseBand = value;
|
||||
}
|
||||
|
||||
static void _set_output_step(struct PID_AUTO_TUNE *self, int32_t value)
|
||||
{
|
||||
self->pri.oStep = value;
|
||||
}
|
||||
|
||||
// * Determies if the tuning parameters returned will be PI (D=0)
|
||||
// or PID. (0=PI, 1=PID)
|
||||
static void _set_control_type(struct PID_AUTO_TUNE *self, int32_t value)
|
||||
{
|
||||
self->pri.controlType = value;
|
||||
}
|
||||
|
||||
static void _set_look_back(struct PID_AUTO_TUNE *self, int32_t value)
|
||||
{
|
||||
set_look_backsec(self, value);
|
||||
}
|
||||
|
||||
static float32 _get_kp(struct PID_AUTO_TUNE *self)
|
||||
{
|
||||
float32 kp = self->pri.controlType == 1 ? 0.6f * self->pri.Ku : 0.4f * self->pri.Ku;
|
||||
return kp;
|
||||
}
|
||||
|
||||
static float32 _get_ki(struct PID_AUTO_TUNE *self)
|
||||
{
|
||||
float32 ki = self->pri.controlType == 1 ? 1.2f * self->pri.Ku / self->pri.Pu : 0.48f * self->pri.Ku / self->pri.Pu;
|
||||
return ki;
|
||||
}
|
||||
|
||||
static float32 _get_kd(struct PID_AUTO_TUNE *self)
|
||||
{
|
||||
return self->pri.controlType == 1 ? 0.075f * self->pri.Ku * self->pri.Pu : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 修改返回值,0 - 执行计算,未完成整定, 1 - 执行计算,完成整定过程, 2 - 采样时间未到
|
||||
* @return {*}
|
||||
*/
|
||||
static int32_t _runtime(struct PID_AUTO_TUNE *self)
|
||||
{
|
||||
int32_t i, iSum;
|
||||
|
||||
uint32_t now = sys_millis();
|
||||
if ((now - self->pri.lastTime) < ((uint32_t)self->pri.sampleTime))
|
||||
{
|
||||
return 2; // 原来返回值为 FALSE 不符合函数定义,也无法区分,改为 2,by shenghao.xu
|
||||
}
|
||||
|
||||
// 开始整定计算
|
||||
self->pri.lastTime = now;
|
||||
float32 refVal = *(self->pri.input);
|
||||
if (FALSE == self->pri.running) // 首次进入,初始化参数
|
||||
{
|
||||
self->pri.peakType = 0;
|
||||
self->pri.peakCount = 0;
|
||||
self->pri.peakMaxCount = 0;
|
||||
self->pri.peak1 = 0;
|
||||
self->pri.peak2 = 0;
|
||||
self->pri.justchanged = FALSE;
|
||||
self->pri.setpoint = refVal; // 不变
|
||||
self->pri.running = TRUE;
|
||||
self->pri.outputStart = *self->pri.output;
|
||||
*self->pri.output = self->pri.outputStart + self->pri.oStep;
|
||||
}
|
||||
|
||||
// 根据输入与设定点的关系振荡输出
|
||||
if (refVal > (self->pri.setpoint + self->pri.noiseBand))
|
||||
*self->pri.output = self->pri.outputStart - self->pri.oStep;
|
||||
else if (refVal < (self->pri.setpoint - self->pri.noiseBand))
|
||||
*self->pri.output = self->pri.outputStart + self->pri.oStep;
|
||||
|
||||
// bool isMax=TRUE, isMin=TRUE;
|
||||
self->pri.isMax = TRUE;
|
||||
self->pri.isMin = TRUE;
|
||||
// id peaks
|
||||
/*
|
||||
以下循环完成,对回溯次数的输入缓存进行判断,如果输入值均大于或小于缓存值,则确定此次为峰值。
|
||||
峰值特征根据 isMax、isMin 哪个为真确定。
|
||||
同时完成输入缓存向后平移,腾出第一个单元存放新的输入值。
|
||||
这一段代码完成的噪声所产生的虚假峰值判断,应该没有问题!
|
||||
*/
|
||||
for (i = self->pri.nLookBack - 1; i >= 0; i--)
|
||||
{
|
||||
int32_t val = self->pri.lastInputs[i];
|
||||
if (self->pri.isMax)
|
||||
self->pri.isMax = (refVal > val); // 第一次是新输入和缓存最后一个值比较,如果大于,则前面的值均判是否大于
|
||||
if (self->pri.isMin)
|
||||
self->pri.isMin = (refVal < val); // 第一次是新输入和缓存最后一个值比较,如果小于,则前面的值均判是否小于
|
||||
self->pri.lastInputs[i + 1] = self->pri.lastInputs[i]; // 每采样一次,将输入缓存的数据向后挪一次
|
||||
}
|
||||
self->pri.lastInputs[0] = refVal; // 新采样的数据放置缓存第一个单元。
|
||||
|
||||
/*
|
||||
以下代码完成峰值的确定,以及对应峰值的时间纪录。
|
||||
因为上述代码只是去掉噪产生的波动峰值,但如果是连续超过 nLookBack 次数的的上升或下降,
|
||||
则上述算法所确定的最大或最小值,并非是峰值,只能是前 nLookBack 次中的最大或最小值。
|
||||
但逐句消化程序后,发现这段处理有几点疑惑:
|
||||
1、peaks[] 的纪录好像不对,在执行最小到最大值转换时,peakCount 也应该+1,否则应该把
|
||||
纪录的最小值覆盖了!所以后面的峰值判断总是满足条件。
|
||||
2、峰值对应时间似乎也应该多次存放,取平均值,因对象没有那么理想化,目前应该是取的最后一组峰值的周期。
|
||||
3、后续计算 Ku 用的是整个整定过程的最大、最小值,这对于非理想的对象而言也不是很合适。
|
||||
|
||||
考虑做如下改进:
|
||||
1)修改峰值纪录,设计12个峰值保存单元,存满12个峰值(6大、6小)后再计算。
|
||||
2)纪录 6 组最大值的间隔时间,作为最终计算 Pu 的数据。
|
||||
*/
|
||||
if (self->pri.isMax)
|
||||
{
|
||||
if (self->pri.peakType == 0)
|
||||
self->pri.peakType = 1; // 首次最大值,初始化
|
||||
|
||||
if (self->pri.peakType == -1) // 如果前一次为最小值,则标识目前进入最大值判断
|
||||
{
|
||||
self->pri.peakType = 1; // 开始最大值判断
|
||||
self->pri.peakCount++; // 峰值计数 by shenghao.xu
|
||||
self->pri.justchanged = TRUE; // 标识峰值转换
|
||||
if (self->pri.peak2 != 0) // 已经纪录一次最大峰值对应时间后,开始记录峰值周期 by shenghao.xu
|
||||
{
|
||||
self->pri.peakPeriod[self->pri.peakMaxCount] = (int32_t)(self->pri.peak1 - self->pri.peak2); // 最大峰值间隔时间(即峰值周期)
|
||||
self->pri.peakMaxCount++; // 最大峰值计数
|
||||
}
|
||||
self->pri.peak2 = self->pri.peak1; // 刷新上次最大值对应时间
|
||||
}
|
||||
self->pri.peak1 = now; // 保存最大值对应时间 peak1
|
||||
self->pri.peaks[self->pri.peakCount] = refVal; // 保存最大值
|
||||
} // 此段代码可以保证得到的是真正的最大值,因为peakType不变,则会不断刷新最大值
|
||||
else if (self->pri.isMin)
|
||||
{
|
||||
if (self->pri.peakType == 0)
|
||||
self->pri.peakType = -1; // 首次最小值,初始化
|
||||
|
||||
if (self->pri.peakType == 1) // 如果前一次是最大值判断,则转入最小值判断
|
||||
{
|
||||
self->pri.peakType = -1; // 开始最小值判断
|
||||
self->pri.peakCount++; // 峰值计数
|
||||
self->pri.justchanged = TRUE;
|
||||
}
|
||||
|
||||
if (self->pri.peakCount < 10)
|
||||
self->pri.peaks[self->pri.peakCount] = refVal; // 只要类型不变,就不断刷新最小值
|
||||
}
|
||||
|
||||
/* by shenghao.xu
|
||||
以下计算是作为判断采集数据是否合适的部分,如果 2 次峰值判断条件满足,就结束整定过程,感觉不甚合理。
|
||||
拟修改为:
|
||||
1)计满 12 次峰值后再计算(到第 13 次)。
|
||||
2)不再判断是否合理,因为对象如果特性好,自然已经稳定,如果不好,再长时间也无效果。
|
||||
3)将后面5次的数据作为素材,去掉第一组数据,因为考虑第一组时对象可能处于过渡过程。
|
||||
4)用后 10 点得到的 9 个峰值差平均值作为 Ku 计算值中的 A,取代原来的整个过程的最大、最小值差。
|
||||
5)用后 5 点峰值周期平均值作为 Pu 的计算值,取代原来用最后一组的值。
|
||||
*/
|
||||
if (self->pri.justchanged && self->pri.peakCount == 12)
|
||||
{
|
||||
// we've transitioned. check if we can autotune based on the last peaks
|
||||
iSum = 0;
|
||||
for (i = 2; i <= 10; i++)
|
||||
iSum += ABS(self->pri.peaks[i] - self->pri.peaks[i + 1]);
|
||||
iSum /= 9; // 取 9 次峰峰值平均值
|
||||
self->pri.Ku = (float32)(4 * (2 * self->pri.oStep)) / (iSum * 3.14159); // 用峰峰平均值计算 Ku
|
||||
|
||||
iSum = 0;
|
||||
for (i = 1; i <= 5; i++)
|
||||
iSum += self->pri.peakPeriod[i];
|
||||
iSum /= 5; // 计算峰值的所有周期平均值
|
||||
self->pri.Pu = (float32)(iSum) / 1000; // 用周期平均值作为 Pu,单位:秒
|
||||
|
||||
*self->pri.output = 0;
|
||||
self->pri.running = FALSE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
self->pri.justchanged = FALSE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pid_auto_tune_constructor(struct PID_AUTO_TUNE *self)
|
||||
{
|
||||
self->set_ctrl_prm = _set_ctrl_prm;
|
||||
self->runtime = _runtime;
|
||||
self->set_output_step = _set_output_step;
|
||||
self->set_control_type = _set_control_type;
|
||||
self->set_noise_band = _set_noise_band;
|
||||
self->set_look_back = _set_look_back;
|
||||
|
||||
self->get_kp = _get_kp;
|
||||
self->get_ki = _get_ki;
|
||||
self->get_kd = _get_kd;
|
||||
}
|
||||
|
|
@ -0,0 +1,896 @@
|
|||
#include "pid.h"
|
||||
#include <math.h>
|
||||
// 定义死区枚举
|
||||
|
||||
#define DEADZONE DEAD_ZONE_POSITIVE
|
||||
// 模糊集合
|
||||
#define NL -3
|
||||
#define NM -2
|
||||
#define NS -1
|
||||
#define ZE 0
|
||||
#define PS 1
|
||||
#define PM 2
|
||||
#define PL 3
|
||||
|
||||
// 定义偏差E的范围,因为设置了非线性区间,误差在10时才开始进行PID调节,这里E的范围为10
|
||||
#define MAXE (30)
|
||||
#define MINE (-MAXE)
|
||||
// 定义EC的范围,因为变化非常缓慢!,每次的EC都非常小,这里可以根据实际需求来调整,
|
||||
#define MAXEC (30)
|
||||
#define MINEC (-MAXEC)
|
||||
// 定义e,ec的量化因子
|
||||
#define KE 3 / MAXE
|
||||
#define KEC 3 / MAXEC
|
||||
|
||||
// 定义输出量比例因子
|
||||
#define KUP 3.0f // 这里只使用了模糊PID的比例增益
|
||||
#define KUI 0.0f
|
||||
#define KUD 0.0f
|
||||
|
||||
static const float32 fuzzyRuleKp[7][7] = {
|
||||
PL, PL, PM, PL, PS, PM, PL,
|
||||
PL, PM, PM, PM, PS, PM, PL,
|
||||
PM, PS, PS, PS, PS, PS, PM,
|
||||
PM, PS, ZE, ZE, ZE, PS, PM,
|
||||
PS, PS, PS, PS, PS, PM, PM,
|
||||
PM, PM, PM, PM, PL, PL, PL,
|
||||
PM, PL, PL, PL, PL, PL, PL};
|
||||
|
||||
static const float32 fuzzyRuleKi[7][7] = {
|
||||
NL, NL, NL, NL, NM, NL, NL,
|
||||
NL, NL, NM, NM, NM, NL, NL,
|
||||
NM, NM, NS, NS, NS, NM, NM,
|
||||
NM, NS, ZE, ZE, ZE, NS, NM,
|
||||
NM, NS, NS, NS, NS, NM, NM,
|
||||
NM, NM, NS, NM, NM, NL, NL,
|
||||
NM, NL, NM, NL, NL, NL, NL};
|
||||
|
||||
static const float32 fuzzyRuleKd[7][7] = {
|
||||
PS, PS, ZE, ZE, ZE, PL, PL,
|
||||
NS, NS, NS, NS, ZE, NS, PM,
|
||||
NL, NL, NM, NS, ZE, PS, PM,
|
||||
NL, NM, NM, NS, ZE, PS, PM,
|
||||
NL, NM, NS, NS, ZE, PS, PS,
|
||||
NM, NS, NS, NS, ZE, PS, PS,
|
||||
PS, ZE, ZE, ZE, ZE, PL, PL};
|
||||
|
||||
static void fuzzy(float32 e, float32 ec, FUZZY_PID_t *fuzzy_pid)
|
||||
{
|
||||
|
||||
float32 etemp, ectemp;
|
||||
float32 eLefttemp, ecLefttemp; // ec,e,左隶属度
|
||||
float32 eRighttemp, ecRighttemp;
|
||||
|
||||
int eLeftIndex, ecLeftIndex; // 模糊位置标号
|
||||
int eRightIndex, ecRightIndex;
|
||||
e = RANGE(e, fuzzy_pid->mine, fuzzy_pid->maxe);
|
||||
ec = RANGE(ec, MINEC, MAXEC);
|
||||
e = e * KE;
|
||||
ec = ec * KEC;
|
||||
|
||||
etemp = e > 3.0f ? 0.0f : (e < -3.0f ? 0.0f : (e >= 0.0f ? (e >= 2.0f ? 2.5f : (e >= 1.0f ? 1.5f : 0.5f)) : (e >= -1.0f ? -0.5f : (e >= -2.0f ? -1.5f : (e >= -3.0f ? -2.5f : 0.0f)))));
|
||||
eLeftIndex = (int)((etemp - 0.5f) + 3); //[-3,3] -> [0,6]
|
||||
eRightIndex = (int)((etemp + 0.5f) + 3);
|
||||
eLefttemp = etemp == 0.0f ? 0.0f : ((etemp + 0.5f) - e); //
|
||||
eRighttemp = etemp == 0.0f ? 0.0f : (e - (etemp - 0.5f));
|
||||
ectemp = ec > 3.0f ? 0.0f : (ec < -3.0f ? 0.0f : (ec >= 0.0f ? (ec >= 2.0f ? 2.5f : (ec >= 1.0f ? 1.5f : 0.5f)) : (ec >= -1.0f ? -0.5f : (ec >= -2.0f ? -1.5f : (ec >= -3.0f ? -2.5f : 0.0f)))));
|
||||
ecLeftIndex = (int)((ectemp - 0.5f) + 3); //[-3,3] -> [0,6]
|
||||
ecRightIndex = (int)((ectemp + 0.5f) + 3);
|
||||
|
||||
ecLefttemp = ectemp == 0.0f ? 0.0f : ((ectemp + 0.5f) - ec);
|
||||
ecRighttemp = ectemp == 0.0f ? 0.0f : (ec - (ectemp - 0.5f));
|
||||
|
||||
/*************************************反模糊*************************************/
|
||||
|
||||
fuzzy_pid->kp = (eLefttemp * ecLefttemp * fuzzyRuleKp[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKp[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKp[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKp[eRightIndex][ecRightIndex]);
|
||||
|
||||
fuzzy_pid->ki = (eLefttemp * ecLefttemp * fuzzyRuleKi[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKi[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKi[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKi[eRightIndex][ecRightIndex]);
|
||||
|
||||
fuzzy_pid->kd = (eLefttemp * ecLefttemp * fuzzyRuleKd[eLeftIndex][ecLeftIndex] + eLefttemp * ecRighttemp * fuzzyRuleKd[eLeftIndex][ecRightIndex] + eRighttemp * ecLefttemp * fuzzyRuleKd[eRightIndex][ecLeftIndex] + eRighttemp * ecRighttemp * fuzzyRuleKd[eRightIndex][ecRightIndex]);
|
||||
// 对解算出的KP,KI,KD进行量化映射
|
||||
|
||||
fuzzy_pid->kp = fuzzy_pid->kp * fuzzy_pid->kup;
|
||||
fuzzy_pid->ki = fuzzy_pid->ki * fuzzy_pid->kui;
|
||||
fuzzy_pid->kd = fuzzy_pid->kd * fuzzy_pid->kud;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SV平滑给定,步长默认为0.1,范围0-1之间,越大平滑性越差
|
||||
* @param {PID_FUZZY} *self
|
||||
* @param {float32} target_sv
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void smooth_setpoint(struct PID_FUZZY *self, float32 target_sv)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
float32 stepIn = (pri->sv_range) * 0.1f;
|
||||
float32 kFactor = 0.0f;
|
||||
if (fabs(pri->ref - target_sv) <= stepIn)
|
||||
{
|
||||
pri->ref = target_sv;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pri->ref - target_sv > 0)
|
||||
{
|
||||
kFactor = -1.0f;
|
||||
}
|
||||
else if (pri->ref - target_sv < 0)
|
||||
{
|
||||
kFactor = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
kFactor = 0.0f;
|
||||
}
|
||||
pri->ref = pri->ref + kFactor * stepIn;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
float32 stepIn = (pri->sv_range) * 0.1f;
|
||||
float32 kFactor = 0.0f;
|
||||
if (fabs(pri->ref - target_sv) <= stepIn)
|
||||
{
|
||||
pri->ref = target_sv;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pri->ref - target_sv > 0)
|
||||
{
|
||||
kFactor = -1.0f;
|
||||
}
|
||||
else if (pri->ref - target_sv < 0)
|
||||
{
|
||||
kFactor = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
kFactor = 0.0f;
|
||||
}
|
||||
pri->ref = pri->ref + kFactor * stepIn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 变速积分
|
||||
static float32 changing_integral_rate(struct PID_FUZZY *self)
|
||||
{
|
||||
float32 err = 0, iout = 0;
|
||||
float32 err_1 = 1, // 误差下限
|
||||
err_2 = 10; // 误差上限
|
||||
float32 index = 0;
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
err = pri->e_0;
|
||||
iout = pri->iout;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
err = pri->e_0;
|
||||
iout = pri->iout;
|
||||
}
|
||||
|
||||
if (err * iout > 0) // 判断积分是否为积累趋势
|
||||
{
|
||||
if (ABS(err) <= err_1)
|
||||
{
|
||||
index = 1; // 完整积分
|
||||
}
|
||||
else if (ABS(err) <= (err_1 + err_2))
|
||||
{
|
||||
// 使用线性函数过渡
|
||||
index = (float)(err_2 - ABS(err) + err_1) / err_2;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/*封装模糊接口*/
|
||||
static void compensate(float32 e, float32 ec, FUZZY_PID_t *fuzzy_d)
|
||||
{
|
||||
fuzzy(e, ec, fuzzy_d);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新最大最小值
|
||||
* @param {PID_FUZZY} *self
|
||||
* @param {float32} out_min
|
||||
* @param {float32} out_max
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_range(struct PID_FUZZY *self, float32 out_min, float32 out_max)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置死区
|
||||
* @param {PID_FUZZY} *self
|
||||
* @param {float32} err_dead
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_err_dead(struct PID_FUZZY *self, float32 err_dead)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->err_dead = err_dead;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->err_dead = err_dead;
|
||||
}
|
||||
}
|
||||
|
||||
static void _set_iout(struct PID_FUZZY *self, float32 iout)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->iout = iout;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->iout = iout;
|
||||
}
|
||||
}
|
||||
|
||||
static void _set_kp(struct PID_FUZZY *self, float32 kp)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->kp = kp;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->kp = kp;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使能积分控制
|
||||
* @param {PID_FUZZY} *self
|
||||
* @param {BOOL} enable
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
// static void _set_ki_enable(struct PID_FUZZY *self, BOOL enable)
|
||||
// {
|
||||
// pri->ki_enable = enable;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief 使能微分控制
|
||||
* @param {PID_FUZZY} *self
|
||||
* @param {BOOL} enable
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void _set_kd_enable(struct PID_FUZZY *self, BOOL enable)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->kd_enable = enable;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->kd_enable = enable;
|
||||
}
|
||||
}
|
||||
|
||||
static void _set_kd(struct PID_FUZZY *self, float32 kd)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->kd = kd;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->kd = kd;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 配置不完全微分系数
|
||||
* @param {PID_FUZZY} *self
|
||||
* @param {float32} alpha
|
||||
* @return {*}
|
||||
* @note alpha范围0-1,系数越大,不完全微分的作用越强
|
||||
*/
|
||||
static void _set_kd_dev(struct PID_FUZZY *self, float32 alpha)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->alpha = alpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
static void _set_ki_enable(struct PID_FUZZY *self, BOOL enable)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->ki_enable = enable;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->ki_enable = enable;
|
||||
}
|
||||
}
|
||||
|
||||
static void _set_ki(struct PID_FUZZY *self, float32 ki)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->ki = ki;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->ki = ki;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Function:使能平滑控制
|
||||
* parameter:*pid需要配,PID参数结构指针,sv_range控制范围sv的范围
|
||||
* return:无
|
||||
*/
|
||||
static void _set_smooth_enable(struct PID_FUZZY *self, BOOL enable, float32 sv_range)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->sm = enable;
|
||||
pri->sv_range = sv_range;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->sm = enable;
|
||||
pri->sv_range = sv_range;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置控制参数
|
||||
static void _set_ctrl_prm(struct PID_FUZZY *self, float32 kp, float32 ki, float32 kd, float32 err_dead, float32 deviation,
|
||||
float32 out_min, float32 out_max)
|
||||
{
|
||||
self->open = TRUE;
|
||||
self->pid_params.kup = KUP;
|
||||
self->pid_params.kui = KUI;
|
||||
self->pid_params.kud = KUD;
|
||||
self->pid_params.mine = MINE;
|
||||
self->pid_params.maxe = MAXE;
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
osel_memset((uint8_t *)pri, 0, sizeof(pid_common_position_t));
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->deviation = deviation;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
pri->detach = FALSE;
|
||||
pri->sm = FALSE;
|
||||
pri->ki_enable = TRUE;
|
||||
pri->alpha = 0.25;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
osel_memset((uint8_t *)pri, 0, sizeof(pid_common_increment_t));
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->deviation = deviation;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
pri->ki_enable = TRUE;
|
||||
pri->alpha = 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
static void _set_error_max_min(struct PID_FUZZY *self, float32 mine, float32 maxe)
|
||||
{
|
||||
self->pid_params.mine = mine;
|
||||
self->pid_params.maxe = maxe;
|
||||
}
|
||||
|
||||
static void _update_ctrl_prm(struct PID_FUZZY *self, float32 kp, float32 ki, float32 kd, float32 err_dead,
|
||||
float32 out_min, float32 out_max)
|
||||
{
|
||||
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
pri->detach = FALSE;
|
||||
pri->sm = FALSE;
|
||||
|
||||
if (kd > 0)
|
||||
{
|
||||
pri->kd_enable = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->kd_enable = FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->kp = kp;
|
||||
pri->ki = ki;
|
||||
pri->kd = kd;
|
||||
pri->err_dead = err_dead;
|
||||
pri->out_max = out_max;
|
||||
pri->out_min = out_min;
|
||||
|
||||
if (kd > 0)
|
||||
{
|
||||
pri->kd_enable = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->kd_enable = FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 非0时配置为积分分离+抗积分饱和PID,否则为普通抗积分饱和PID
|
||||
* @param {PID_FUZZY} *self
|
||||
* @param {float32} max_err
|
||||
* @param {BOOL} mode
|
||||
* @return {*}
|
||||
*/
|
||||
static void _set_cfg(struct PID_FUZZY *self, float32 max_err, BOOL mode)
|
||||
{
|
||||
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->err_limit = max_err;
|
||||
pri->detach = mode == FALSE ? FALSE : TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->err_limit = max_err;
|
||||
pri->detach = mode == FALSE ? FALSE : TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 判断是否处于死区范围内
|
||||
*
|
||||
* 根据给定的 PID_FUZZY 结构体,判断当前值是否处于死区范围内。
|
||||
*
|
||||
* @param self PID_FUZZY 结构体指针
|
||||
*
|
||||
* @return 如果处于死区范围内,返回 TRUE;否则返回 FALSE
|
||||
*/
|
||||
static BOOL _in_dead_zone(struct PID_FUZZY *self)
|
||||
{
|
||||
float32 deviation = 0.0f;
|
||||
float32 err_dead = 0.0f;
|
||||
float32 err = 0.0f;
|
||||
float32 offset = 0.0f;
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
deviation = pri->deviation;
|
||||
err_dead = pri->err_dead;
|
||||
err = pri->feedback - pri->ref;
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
deviation = pri->deviation;
|
||||
err_dead = pri->err_dead;
|
||||
err = pri->feedback - pri->ref;
|
||||
}
|
||||
|
||||
offset = err + deviation;
|
||||
|
||||
if (self->deadzone_dir == DEAD_ZONE_POSITIVE)
|
||||
{
|
||||
if (offset >= 0 && offset <= ABS(err_dead))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else if (self->deadzone_dir == DEAD_ZONE_NEGATIVE)
|
||||
{
|
||||
if (offset <= 0 && offset >= err_dead)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ABS(offset) <= ABS(err_dead))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static float32 position_pid(struct PID_FUZZY *self, float32 target, float32 feedback)
|
||||
{
|
||||
float32 error = 0;
|
||||
float32 ec = 0;
|
||||
float32 kd = 0;
|
||||
float32 thisdev = 0;
|
||||
|
||||
pid_common_position_t *pri = &self->pri_u.position;
|
||||
kd = pri->kd;
|
||||
/*获取期望值与实际值,进行偏差计算*/
|
||||
if (pri->sm == 1)
|
||||
{
|
||||
smooth_setpoint(self, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->ref = target;
|
||||
}
|
||||
|
||||
pri->feedback = feedback;
|
||||
error = pri->ref - pri->feedback;
|
||||
|
||||
if (self->in_dead_zone(self) == TRUE)
|
||||
{
|
||||
error = 0;
|
||||
pri->in_dead_zone = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->in_dead_zone = FALSE;
|
||||
}
|
||||
|
||||
pri->e_0 = error;
|
||||
|
||||
/* fuzzy control caculate */
|
||||
ec = error - pri->e_1;
|
||||
if (self->open == TRUE)
|
||||
{
|
||||
compensate(error, ec, &self->pid_params);
|
||||
}
|
||||
|
||||
/*根据PID配置的模式,获取积分数据,进行积分累加*/
|
||||
if (self->speed_integral_enable == TRUE)
|
||||
{
|
||||
pri->iout = (pri->ki + self->pid_params.ki) * error * changing_integral_rate(self);
|
||||
}
|
||||
else
|
||||
{
|
||||
float32 temp_iterm = 0.0f;
|
||||
float32 insert = 0;
|
||||
if (pri->out >= pri->out_max)
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach)
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
if (error < 0)
|
||||
{
|
||||
temp_iterm = (pri->ki + self->pid_params.ki) * error;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pri->out <= pri->out_min)
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach)
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
if (error > 0)
|
||||
{
|
||||
temp_iterm = (pri->ki + self->pid_params.ki) * error;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (fabs(error) > pri->err_limit && pri->detach)
|
||||
{
|
||||
insert = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
insert = 1;
|
||||
temp_iterm = (pri->ki + self->pid_params.ki) * error;
|
||||
}
|
||||
}
|
||||
|
||||
pri->iout += temp_iterm;
|
||||
|
||||
/* limt integral */
|
||||
if (pri->iout > pri->out_max)
|
||||
{
|
||||
pri->iout = pri->out_max;
|
||||
}
|
||||
else if (pri->iout < pri->out_min)
|
||||
{
|
||||
pri->iout = pri->out_min;
|
||||
}
|
||||
pri->iout = pri->iout * insert;
|
||||
}
|
||||
|
||||
#if INCOMPLETE_DIFFEREN == 1
|
||||
/*不完全微分*/
|
||||
thisdev = kd * (1.0 - pri->alpha) * (error - pri->e_1) + pri->alpha * pri->lastdev;
|
||||
/*record last dev result*/
|
||||
pri->lastdev = thisdev;
|
||||
#else
|
||||
thisdev = (error - pri->e_1) * (kd);
|
||||
#endif
|
||||
|
||||
if (pri->kd_enable == FALSE)
|
||||
{
|
||||
thisdev = 0;
|
||||
}
|
||||
|
||||
if (pri->ki_enable == FALSE)
|
||||
{
|
||||
pri->iout = 0;
|
||||
}
|
||||
|
||||
pri->out = (pri->kp + self->pid_params.kp) * error + pri->iout + thisdev;
|
||||
pri->e_1 = error;
|
||||
/*record last feedback sensor result*/
|
||||
pri->pre_feedback = pri->feedback;
|
||||
/*limt pid output*/
|
||||
pri->out = RANGE(pri->out, pri->out_min, pri->out_max);
|
||||
return pri->out;
|
||||
}
|
||||
|
||||
static float32 increment_pid(struct PID_FUZZY *self, float32 target, float32 feedback)
|
||||
{
|
||||
float32 ep, ei, ed;
|
||||
float32 inc_out;
|
||||
float32 thisdev = 0;
|
||||
pid_common_increment_t *pri = &self->pri_u.increment;
|
||||
|
||||
pri->feedback = feedback;
|
||||
pri->e_0 = pri->ref - pri->feedback;
|
||||
|
||||
if (pri->e_0 >= MAXE)
|
||||
{
|
||||
return pri->out_max;
|
||||
}
|
||||
else if (pri->e_0 <= MINE)
|
||||
{
|
||||
return pri->out_min;
|
||||
}
|
||||
|
||||
if (fabs(pri->e_0) <= pri->err_dead)
|
||||
{
|
||||
pri->e_0 = 0;
|
||||
}
|
||||
|
||||
ep = pri->e_0 - pri->e_1;
|
||||
ei = pri->e_0;
|
||||
ed = pri->e_0 - 2 * pri->e_1 + pri->e_2;
|
||||
if (self->open == TRUE)
|
||||
{
|
||||
compensate(pri->e_0, ep, &self->pid_params);
|
||||
}
|
||||
|
||||
if (pri->sm == 1)
|
||||
{
|
||||
smooth_setpoint(self, target);
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->ref = target;
|
||||
}
|
||||
|
||||
#if INCOMPLETE_DIFFEREN == 1
|
||||
/*不完全微分*/
|
||||
thisdev = (1.0 - pri->alpha) * (pri->kd + self->pid_params.kd) * ed + pri->alpha * pri->lastdev;
|
||||
#else
|
||||
ed = ed;
|
||||
#endif
|
||||
|
||||
if (self->speed_integral_enable == TRUE)
|
||||
{
|
||||
|
||||
if (ABS(pri->e_0) > MAXE)
|
||||
{
|
||||
pri->iout = (pri->ki + self->pid_params.ki) * ei;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 变速积分
|
||||
pri->iout = (pri->ki + self->pid_params.ki) * ei * changing_integral_rate(self);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pri->iout = (pri->ki + self->pid_params.ki) * ei;
|
||||
}
|
||||
|
||||
if (pri->kd_enable == FALSE)
|
||||
{
|
||||
thisdev = 0;
|
||||
}
|
||||
|
||||
if (pri->ki_enable == FALSE)
|
||||
{
|
||||
pri->iout = 0;
|
||||
}
|
||||
|
||||
inc_out = (pri->kp + self->pid_params.kp) * ep + pri->iout + thisdev;
|
||||
pri->e_2 = pri->e_1;
|
||||
pri->e_1 = pri->e_0;
|
||||
pri->lastdev = thisdev;
|
||||
pri->out = pri->out + inc_out;
|
||||
pri->out = RANGE(pri->out, pri->out_min, pri->out_max);
|
||||
return pri->out;
|
||||
}
|
||||
|
||||
static float32 _pid(struct PID_FUZZY *self, float32 target, float32 feedback)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
return position_pid(self, target, feedback);
|
||||
}
|
||||
else
|
||||
{
|
||||
return increment_pid(self, target, feedback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 复位PID积分及微分控制数据
|
||||
* @param {PID_FUZZY} *self
|
||||
* @return {*}
|
||||
*/
|
||||
static void _restctrl(struct PID_FUZZY *self, float32 out)
|
||||
{
|
||||
if (self->sub_type == PID_SUB_TYPE_POSITION)
|
||||
{
|
||||
pid_common_position_t *pri = NULL;
|
||||
pri = &self->pri_u.position;
|
||||
pri->e_1 = 0;
|
||||
pri->iout = 0;
|
||||
pri->out = out;
|
||||
pri->iout = out;
|
||||
#if INCOMPLETE_DIFFEREN == 1
|
||||
pri->lastdev = 0;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
pid_common_increment_t *pri = NULL;
|
||||
pri = &self->pri_u.increment;
|
||||
pri->e_0 = 0;
|
||||
pri->e_1 = 0;
|
||||
pri->e_2 = 0;
|
||||
pri->lastdev = 0;
|
||||
pri->out = out;
|
||||
pri->iout = out;
|
||||
}
|
||||
}
|
||||
|
||||
void pid_fuzzy_constructor(struct PID_FUZZY *self)
|
||||
{
|
||||
self->set_ctrl_prm = _set_ctrl_prm;
|
||||
self->set_error_max_min = _set_error_max_min;
|
||||
self->update_ctrl_prm = _update_ctrl_prm;
|
||||
self->set_cfg = _set_cfg;
|
||||
self->set_smooth_enable = _set_smooth_enable;
|
||||
self->set_err_dead = _set_err_dead;
|
||||
self->set_kp = _set_kp;
|
||||
self->set_ki_enable = _set_ki_enable;
|
||||
self->set_ki = _set_ki;
|
||||
self->set_kd_enable = _set_kd_enable;
|
||||
self->set_kd = _set_kd;
|
||||
self->set_kd_dev = _set_kd_dev;
|
||||
self->set_range = _set_range;
|
||||
self->restctrl = _restctrl;
|
||||
self->set_iout = _set_iout;
|
||||
self->in_dead_zone = _in_dead_zone;
|
||||
self->execute = _pid;
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#include "pid.h"
|
||||
#include <math.h>
|
||||
// 设置控制参数
|
||||
static void _set_ctrl_prm(struct PID_NEURAL *self, float32 minimum, float32 maximum)
|
||||
{
|
||||
self->pri.setpoint = minimum; /*设定值*/
|
||||
|
||||
self->pri.kcoef = 0.12; /*神经元输出比例*/
|
||||
self->pri.kp = 0.45; /*比例学习速度*/
|
||||
self->pri.ki = 0.05; /*积分学习速度*/
|
||||
self->pri.kd = 0; /*微分学习速度*/
|
||||
|
||||
self->pri.lasterror = 0.0; /*前一拍偏差*/
|
||||
self->pri.preerror = 0.0; /*前两拍偏差*/
|
||||
self->pri.result = minimum; /*PID控制器结果*/
|
||||
self->pri.output = 0.0; /*输出值,百分比*/
|
||||
|
||||
self->pri.maximum = maximum; /*输出值上限*/
|
||||
self->pri.minimum = minimum; /*输出值下限*/
|
||||
self->pri.deadband = (maximum - minimum) * 0.0005f; /*死区*/
|
||||
|
||||
self->pri.wp = 0.10; /*比例加权系数*/
|
||||
self->pri.wi = 0.10; /*积分加权系数*/
|
||||
self->pri.wd = 0.10; /*微分加权系数*/
|
||||
}
|
||||
|
||||
// 设置输出参数
|
||||
static void _set_out_prm(struct PID_NEURAL *self, float32 minimum, float32 maximum)
|
||||
{
|
||||
self->pri.maximum = maximum;
|
||||
self->pri.minimum = minimum;
|
||||
}
|
||||
|
||||
/*单神经元学习规则函数*/
|
||||
static void NeureLearningRules(struct PID_NEURAL *self, float32 zk, float32 uk, float32 *xi)
|
||||
{
|
||||
self->pri.wi = self->pri.wi + self->pri.ki * zk * uk * xi[0];
|
||||
self->pri.wp = self->pri.wp + self->pri.kp * zk * uk * xi[1];
|
||||
self->pri.wd = self->pri.wd + self->pri.kd * zk * uk * xi[2];
|
||||
}
|
||||
|
||||
static float32 _PID(struct PID_NEURAL *self, float32 target, float32 feedback)
|
||||
{
|
||||
float32 x[3];
|
||||
float32 w[3];
|
||||
float32 sabs;
|
||||
float32 error;
|
||||
float32 result;
|
||||
float32 deltaResult;
|
||||
self->pri.setpoint = target;
|
||||
error = self->pri.setpoint - feedback;
|
||||
result = self->pri.result;
|
||||
if (fabs(error) > self->pri.deadband)
|
||||
{
|
||||
x[0] = error;
|
||||
x[1] = error - self->pri.lasterror;
|
||||
x[2] = error - self->pri.lasterror * 2 + self->pri.preerror;
|
||||
|
||||
sabs = fabs(self->pri.wi) + fabs(self->pri.wp) + fabs(self->pri.wd);
|
||||
w[0] = self->pri.wi / sabs;
|
||||
w[1] = self->pri.wp / sabs;
|
||||
w[2] = self->pri.wd / sabs;
|
||||
|
||||
deltaResult = (w[0] * x[0] + w[1] * x[1] + w[2] * x[2]) * self->pri.kcoef;
|
||||
}
|
||||
else
|
||||
{
|
||||
deltaResult = 0;
|
||||
}
|
||||
|
||||
result = result + deltaResult;
|
||||
if (result > self->pri.maximum)
|
||||
{
|
||||
result = self->pri.maximum;
|
||||
}
|
||||
if (result < self->pri.minimum)
|
||||
{
|
||||
result = self->pri.minimum;
|
||||
}
|
||||
self->pri.result = result;
|
||||
self->pri.output = self->pri.result;
|
||||
|
||||
// 单神经元学习
|
||||
NeureLearningRules(self, error, result, x);
|
||||
|
||||
self->pri.preerror = self->pri.lasterror;
|
||||
self->pri.lasterror = error;
|
||||
|
||||
return self->pri.output;
|
||||
}
|
||||
|
||||
void pid_neural_constructor(struct PID_NEURAL *self)
|
||||
{
|
||||
self->set_ctrl_prm = _set_ctrl_prm;
|
||||
self->set_out_prm = _set_out_prm;
|
||||
self->PID = _PID;
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# 模糊PID控制器设计文档
|
||||
|
||||
# 模糊PID控制器详细设计文档
|
||||
|
||||
## 1. 引言
|
||||
|
||||
### 1.1 目的
|
||||
|
||||
本文档旨在详细介绍模糊PID控制器的设计理念、实现方法和使用指南,为开发者提供一套完整的模糊PID控制解决方案。
|
||||
|
||||
### 1.2 背景
|
||||
|
||||
PID控制器因其结构简单、稳定性好、易于实现等优点,在工业控制系统中得到了广泛应用。然而,传统PID控制器在面对复杂或非线性系统时,性能表现不佳。模糊PID控制器通过引入模糊逻辑,动态调整PID参数,以适应系统在不同工作状态下的控制需求,从而提高控制性能。
|
||||
|
||||
## 2. 设计概述
|
||||
|
||||
### 2.1 设计目标
|
||||
|
||||
- **适应性**:能够适应不同类型和不同工作状态的控制系统。
|
||||
- **稳定性**:保证控制系统在各种工作条件下的稳定运行。
|
||||
- **易用性**:提供简单易懂的接口,便于开发者快速实现和调试。
|
||||
|
||||
### 2.2 功能模块
|
||||
|
||||
模糊PID控制器主要包括以下几个功能模块:
|
||||
|
||||
1. **模糊控制模块**:负责根据输入的误差和误差变化率,通过模糊逻辑计算出PID参数。
|
||||
2. **SV平滑给定模块**:负责平滑控制目标值,减少控制过程中的突变。
|
||||
3. **变速积分模块**:根据误差的大小调整积分速率,提高控制效率。
|
||||
4. **参数设置模块**:提供接口函数,用于设置和调整PID参数。
|
||||
|
||||
## 3. 功能模块详细设计
|
||||
|
||||
### 3.1 模糊控制模块
|
||||
|
||||
#### 3.1.1 输入处理
|
||||
|
||||
- **误差处理**:将实时误差 `e`限制在预定的范围内,并进行模糊化处理。
|
||||
- **误差变化率处理**:将误差变化率 `ec`进行相同的处理。
|
||||
|
||||
#### 3.1.2 模糊规则库
|
||||
|
||||
- **规则定义**:根据系统的具体需求,定义一套模糊规则,用于计算PID参数。
|
||||
- **规则应用**:根据输入的误差和误差变化率的模糊化值,通过模糊规则库计算出 `kp`、`ki`、`kd`。
|
||||
|
||||
### 3.2 SV平滑给定模块
|
||||
|
||||
- **平滑策略**:根据当前目标值与新目标值之间的差值,动态调整目标值变化的步长,实现平滑过渡。
|
||||
|
||||
### 3.3 变速积分模块
|
||||
|
||||
- **积分策略**:根据误差的大小,调整积分速率。误差较小时,使用完整积分;误差较大时,减小或停止积分。
|
||||
|
||||
### 3.4 参数设置模块
|
||||
|
||||
- **接口设计**:提供一系列接口函数,用于设置PID控制器的参数,如输出限制、死区误差等。
|
||||
|
||||
## 4. 使用说明
|
||||
|
||||
### 4.1 初始化
|
||||
|
||||
- **控制器初始化**:根据控制对象的特性,初始化模糊PID控制器的相关参数和模糊规则库。
|
||||
|
||||
### 4.2 实时控制
|
||||
|
||||
- **参数调整**:在控制循环中,根据实时误差和误差变化率,动态调整PID参数。
|
||||
- **控制执行**:根据调整后的PID参数,执行PID控制算法,输出控制信号。
|
||||
|
||||
### 4.3 参数调整
|
||||
|
||||
- **动态调整**:根据系统运行情况,通过参数设置模块调整PID参数,优化控制效果。
|
||||
|
||||
## 5. 结论
|
||||
|
||||
模糊PID控制器通过动态调整PID参数,提高了控制系统的适应性和稳定性,特别适用于复杂或非线性系统的控制。本文档提供了模糊PID控制器的详细设计方案,旨在帮助开发者更好地理解和应用模糊PID控制技术
|
||||
|
||||
## 概述
|
||||
|
||||
本文档旨在详细介绍模糊PID控制器的设计与实现。模糊PID控制器结合了传统PID控制和模糊逻辑控制的优点,通过模糊逻辑对PID参数进行动态调整,以适应控制系统在不同工作状态下的需求。
|
||||
|
||||
## 功能模块
|
||||
|
||||
### 1. 模糊控制模块
|
||||
|
||||
- **功能描述**:根据误差 `e`和误差变化率 `ec`的模糊化值,通过模糊规则库计算出模糊PID控制器的三个参数:比例系数 `kp`、积分系数 `ki`、微分系数 `kd`。
|
||||
- **实现方法**:首先将输入的误差 `e`和误差变化率 `ec`限制在预定范围内,然后通过模糊化处理得到其隶属度和模糊位置标号,最后根据模糊规则库计算出 `kp`、`ki`、`kd`的值。
|
||||
|
||||
### 2. SV平滑给定模块
|
||||
|
||||
- **功能描述**:平滑控制目标值(Setpoint Value, SV),以减少控制过程中的突变,提高系统的稳定性。
|
||||
- **实现方法**:根据当前目标值与新目标值之间的差值,动态调整目标值的变化步长,以实现平滑过渡。
|
||||
|
||||
### 3. 变速积分模块
|
||||
|
||||
- **功能描述**:根据误差的大小调整积分速率,以提高控制系统的快速性和稳定性。
|
||||
- **实现方法**:当误差较小时,使用完整积分;当误差在一定范围内变化时,通过线性函数调整积分速率;当误差较大时,减小或停止积分,以避免积分饱和。
|
||||
|
||||
### 4. 参数设置模块
|
||||
|
||||
- **功能描述**:提供接口函数,用于设置PID控制器的各项参数,包括输出限制范围、死区误差、积分输出值、PID参数等。
|
||||
- **实现方法**:根据控制器的子类型(位置型或增量型),分别设置相应参数的值。
|
||||
|
||||
## 使用说明
|
||||
|
||||
1. **初始化**:根据控制对象的具体情况,初始化模糊PID控制器的结构体,包括最大误差、最小误差、PID参数的模糊规则库等。
|
||||
2. **实时控制**:在控制循环中,根据当前的误差 `e`和误差变化率 `ec`调用模糊控制模块,计算出动态调整的PID参数,然后根据这些参数进行PID控制。
|
||||
3. **参数调整**:根据系统运行情况,通过参数设置模块调整PID控制器的参数,以优化控制效果。
|
||||
|
||||
## 结论
|
||||
|
||||
模糊PID控制器通过引入模糊逻辑,使得PID参数能够根据控制系统的实时状态动态调整,从而提高了控制系统的适应性和稳定性。通过本文档的设计与实现,开发者可以更好地理解和应用模糊PID控制器。
|
||||
|
|
@ -0,0 +1 @@
|
|||
https://www.cnblogs.com/foxclever/p/16299063.html
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
# 变量BIN: 给定的是我们想要生成的可执行文件的名称
|
||||
BIN = run.exe
|
||||
SO = lib.dll
|
||||
|
||||
# 变量SRC中给的是所有的想要编译的.c源文件,与makefile在同一目录下可直接写(如这里的main.c),否则需要写明相对路径(如这里的其余源文件都在目录src下)。
|
||||
# 多文件时,选择用"\"进行分行处理
|
||||
SRC = \
|
||||
../src/malloc.c \
|
||||
../src/sqqueue.c \
|
||||
../src/mlist.c \
|
||||
../src/debug.c \
|
||||
../src/data_analysis.c \
|
||||
../src/filter.c \
|
||||
../src/clist.c \
|
||||
../src/aes.c \
|
||||
../src/cmac.c \
|
||||
../src/lib.c
|
||||
|
||||
EXAMPLE = \
|
||||
./simple_clist.c \
|
||||
./simple_data_analysis.c \
|
||||
./simple_sqqueue.c \
|
||||
./simple_aes.c \
|
||||
./simple_cmac.c
|
||||
|
||||
CPLUS_INCLUDE_PATH= -I ../inc
|
||||
|
||||
# 变量CC:给定编译器名gcc
|
||||
# 变量CFLAGS:传给编译器的某些编译参数,看需求添加
|
||||
CC = gcc
|
||||
CFLAGS = -m32 -std=c99
|
||||
# 变量GDB:给定debugger名gdb
|
||||
# 变量RM:给定删除文件方式,用于后面删除所有编译所得的.o文件,linux下使用rm -rf
|
||||
GDB = gdb
|
||||
RM = rm -rf
|
||||
# 变量OBJS:将变量SRC中所有的.c文件替换成以.o结尾,即将.c源文件编译成.o文件
|
||||
OBJS = $(SRC:%.c=%.o)
|
||||
EXAPMLES = $(EXAMPLE:%.c=%.o)
|
||||
|
||||
$(SO): $(OBJS) $(EXAPMLES)
|
||||
|
||||
|
||||
# pull in dependencies for .o files
|
||||
-include $(OBJS:.o=.d)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CPLUS_INCLUDE_PATH) $(CFLAGS) -c $< -o $@
|
||||
|
||||
.PHONY: all clean clist data_analysis
|
||||
|
||||
all: $(SO)
|
||||
|
||||
rm:
|
||||
$(RM) $(OBJS)
|
||||
|
||||
#简单链表
|
||||
clist: $(SO)
|
||||
$(CC) $(CPLUS_INCLUDE_PATH) $(CFLAGS) $(OBJS) ./simple_clist.o -o $(BIN)
|
||||
$(RM) $(OBJS) $(EXAPMLES)
|
||||
|
||||
#数据分析器
|
||||
data_analysis: $(SO)
|
||||
$(CC) $(CPLUS_INCLUDE_PATH) $(CFLAGS) $(OBJS) ./simple_data_analysis.o -o $(BIN)
|
||||
$(RM) $(OBJS) $(EXAPMLES)
|
||||
|
||||
#队列
|
||||
sqqueue: $(SO)
|
||||
$(CC) $(CPLUS_INCLUDE_PATH) $(CFLAGS) $(OBJS) ./simple_sqqueue.o -o $(BIN)
|
||||
$(RM) $(OBJS) $(EXAPMLES)
|
||||
|
||||
#aes加密
|
||||
aes: $(SO)
|
||||
$(CC) $(CPLUS_INCLUDE_PATH) $(CFLAGS) $(OBJS) ./simple_aes.o -o $(BIN)
|
||||
$(RM) $(OBJS) $(EXAPMLES)
|
||||
|
||||
#cmac类CRC
|
||||
cmac: $(SO)
|
||||
$(CC) $(CPLUS_INCLUDE_PATH) $(CFLAGS) $(OBJS) ./simple_cmac.o -o $(BIN)
|
||||
$(RM) $(OBJS) $(EXAPMLES)
|
||||
|
||||
#运行程序
|
||||
run:
|
||||
./run.exe
|
||||
|
||||
clean:
|
||||
$(RM) $(OBJS) $(EXAPMLES) $(BIN)
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#include "../inc/data_type_def.h"
|
||||
#include "../inc/log.h"
|
||||
#include "../inc/osel_arch.h"
|
||||
#include "../inc/aes.h"
|
||||
|
||||
// 全局变量
|
||||
static aes_context AesContext; // 密钥表
|
||||
static uint8_t aBlock[] = {0x00, 0x00, 0x00, 0xcc, 0xff, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 数据块
|
||||
static uint8_t sBlock[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 存放输出结果
|
||||
|
||||
int32_t main(void)
|
||||
{
|
||||
uint8_t buf[16] = {0x00};
|
||||
uint8_t size = ARRAY_LEN(buf);
|
||||
uint8_t key[] = {
|
||||
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6,
|
||||
0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; // 密钥
|
||||
|
||||
// 初始化密文
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
buf[i] = i;
|
||||
}
|
||||
|
||||
// 设置预密钥
|
||||
osel_memset(AesContext.ksch, 0, ARRAY_LEN(AesContext.ksch));
|
||||
aes_set_key(key, 16, &AesContext);
|
||||
|
||||
// 加密
|
||||
osel_memcpy(aBlock, buf, size);
|
||||
aes_encrypt(aBlock, sBlock, &AesContext);
|
||||
LOG_HEX(sBlock, ARRAY_LEN(sBlock)); // 打印加密结果:50 fe 67 cc 99 6d 32 b6 da 09 37 e9 9b af ec 60
|
||||
|
||||
// 解密
|
||||
osel_memcpy(aBlock, sBlock, size);
|
||||
aes_decrypt(aBlock, sBlock, &AesContext);
|
||||
LOG_HEX(sBlock, ARRAY_LEN(sBlock)); // 打印解密结果:00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
#include "../inc/data_type_def.h"
|
||||
#include "../inc/clist.h"
|
||||
|
||||
int32_t main(void)
|
||||
{
|
||||
clist_node_t *head = NULL; // 创建头指针,初始化为NULL
|
||||
clist_init(&head); // 初始化指针(可有可无)
|
||||
|
||||
// 1:添加数据
|
||||
for (int32_t i = 0; i < 30; i++)
|
||||
{
|
||||
if (i > 10)
|
||||
clist_push_front(&head, (cnode)i); // 头部插入
|
||||
else
|
||||
clist_push_back(&head, (cnode)i); // 尾部插入
|
||||
}
|
||||
|
||||
LOG_PRINT("\n 1: count:%d \n", clist_node_count(head)); // 获取链表节点数,打印
|
||||
clist_print(head); // 打印链表
|
||||
|
||||
// 2:删除数据
|
||||
for (int32_t i = 0; i < 10; i++)
|
||||
{
|
||||
if (i > 5)
|
||||
clist_pop_back(&head); // 删除尾部
|
||||
else
|
||||
clist_pop_front(&head); // 头部删除
|
||||
}
|
||||
LOG_PRINT("\n 2: count:%d \n", clist_node_count(head));
|
||||
clist_print(head);
|
||||
|
||||
// 3:插入数据
|
||||
clist_insert(&head, 5, (cnode)1111);
|
||||
clist_insert_for_node(&head, head->Next->Next->Next->Next->Next, (cnode)10000);
|
||||
clist_insert(&head, 1000, (cnode)2222); // 无效插入
|
||||
LOG_PRINT("\n 3: count:%d \n", clist_node_count(head));
|
||||
clist_print(head);
|
||||
|
||||
// 4:删除指定节点
|
||||
clist_remove(&head, (cnode)5);
|
||||
clist_erase_for_node(&head, head->Next->Next);
|
||||
clist_remove(&head, (cnode)1000); // 无效删除
|
||||
clist_print(head);
|
||||
LOG_PRINT("\n 4: count:%d \n", clist_node_count(head));
|
||||
clist_print(head);
|
||||
|
||||
// 5:删除所有节点
|
||||
clist_destroy(&head);
|
||||
LOG_PRINT("\n 5: count:%d ", clist_node_count(head));
|
||||
clist_print(head);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
#include "../inc/data_type_def.h"
|
||||
#include "../inc/log.h"
|
||||
#include "../inc/osel_arch.h"
|
||||
#include "../inc/cmac.h"
|
||||
|
||||
static uint8_t key[] = {
|
||||
0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6,
|
||||
0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C}; // 密钥
|
||||
int32_t main(void)
|
||||
{
|
||||
uint8_t *p;
|
||||
uint8_t buffer[16] = {0x00};
|
||||
uint32_t size = ARRAY_LEN(buffer);
|
||||
// 初始化需要校验的数据
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
buffer[i] = i;
|
||||
}
|
||||
uint8_t mic[16]; // 存放生成校验数据的数组
|
||||
AES_CMAC_CTX AesCmacCtx[1]; // 密钥扩展表
|
||||
AES_CMAC_Init(AesCmacCtx); // 完成密钥扩展表的初始化
|
||||
|
||||
AES_CMAC_SetKey(AesCmacCtx, key); // 完成密钥扩展表数据
|
||||
|
||||
AES_CMAC_Update(AesCmacCtx, buffer, size & 0xFF); // 完成数据的奇偶校验
|
||||
|
||||
AES_CMAC_Final(mic, AesCmacCtx); // 生成16个字节的校验表
|
||||
|
||||
uint32_t xor_vol = (uint32_t)((uint32_t)mic[3] << 24 | (uint32_t)mic[2] << 16 | (uint32_t)mic[1] << 8 | (uint32_t)mic[0]); // 取表4个字节作为校验码
|
||||
|
||||
p = (uint8_t *)&xor_vol;
|
||||
LOG_HEX(p, 4); // 打印结果:5c 7e fb 43
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#include "../inc/data_type_def.h"
|
||||
#include "../inc/log.h"
|
||||
#include "../inc/cmd.h"
|
||||
|
||||
void at_name_req(void)
|
||||
{
|
||||
LOG_PRINT("name:cmd\n");
|
||||
}
|
||||
|
||||
void at_version_req(void)
|
||||
{
|
||||
LOG_PRINT("version:1.0\n");
|
||||
}
|
||||
|
||||
REGISTER_CMD(NAME, at_name_req, at name);
|
||||
REGISTER_CMD(VERSION, at_version_req, at version);
|
||||
|
||||
int32_t main(void)
|
||||
{
|
||||
cmd_init();
|
||||
|
||||
cmd_parsing("TEST");
|
||||
cmd_parsing("NAME");
|
||||
cmd_parsing("VERSION");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
#include "../inc/data_type_def.h"
|
||||
#include "../inc/log.h"
|
||||
#include "../inc/osel_arch.h"
|
||||
#include "../inc/data_analysis.h"
|
||||
#define UART_RXSIZE (254U)
|
||||
#define UART_DATA_ANALYSIS_PORT_1 DATA_1
|
||||
#define UART_DATA_ANALYSIS_PORT_2 DATA_2
|
||||
|
||||
static data_interupt_cb_t uart_data_analysis_cb = NULL; // 数据源中断回调函数
|
||||
|
||||
static void data_analysis_event1(void)
|
||||
{
|
||||
uint8_t frame[UART_RXSIZE];
|
||||
uint8_t data_head[3];
|
||||
uint8_t crc[2];
|
||||
uint16_t frame_len, out_frame_len;
|
||||
data_read(UART_DATA_ANALYSIS_PORT_1, &data_head[0], 3);
|
||||
osel_memcpy((uint8_t *)&frame_len, &data_head[1], 2);
|
||||
|
||||
frame_len = B2S_UINT16(frame_len) - 2; // 报文长度包含帧长,这里需要减2
|
||||
if (frame_len > UART_RXSIZE)
|
||||
{
|
||||
lock_data(UART_DATA_ANALYSIS_PORT_1);
|
||||
unlock_data(UART_DATA_ANALYSIS_PORT_1);
|
||||
return;
|
||||
}
|
||||
|
||||
out_frame_len = data_read(UART_DATA_ANALYSIS_PORT_1, frame, (uint16_t)frame_len);
|
||||
if (out_frame_len != frame_len)
|
||||
{
|
||||
return;
|
||||
}
|
||||
out_frame_len = out_frame_len - 1; // 报文中包含帧尾,这里需要减1
|
||||
|
||||
// 校验CRC_16
|
||||
uint16_t crc_16 = 0;
|
||||
uint16_t crc16 = crc16_compute(&frame[0], out_frame_len - 2);
|
||||
osel_memcpy(&crc[0], &frame[out_frame_len - 2], 2);
|
||||
crc_16 = BUILD_UINT16(crc[1], crc[0]);
|
||||
if (crc16 != crc_16)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// CRC校验通过后将数据长度-2
|
||||
out_frame_len -= 2;
|
||||
|
||||
LOG_PRINT("data_analysis_event1 ok:");
|
||||
LOG_HEX(frame, out_frame_len);
|
||||
}
|
||||
|
||||
static void data_analysis_event2(void)
|
||||
{
|
||||
uint8_t frame[UART_RXSIZE];
|
||||
uint8_t data_head[4];
|
||||
uint8_t crc[2];
|
||||
uint16_t frame_len, out_frame_len;
|
||||
data_read(UART_DATA_ANALYSIS_PORT_2, &data_head[0], 4);
|
||||
osel_memcpy((uint8_t *)&frame_len, &data_head[2], 2);
|
||||
frame_len = B2S_UINT16(frame_len);
|
||||
if (frame_len > UART_RXSIZE)
|
||||
{
|
||||
lock_data(UART_DATA_ANALYSIS_PORT_2);
|
||||
unlock_data(UART_DATA_ANALYSIS_PORT_2);
|
||||
return;
|
||||
}
|
||||
|
||||
out_frame_len = data_read(UART_DATA_ANALYSIS_PORT_2, frame, (uint16_t)frame_len);
|
||||
if (out_frame_len != frame_len)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 校验CRC_16
|
||||
uint16_t crc_16 = 0;
|
||||
uint16_t crc16 = crc16_compute(&frame[0], out_frame_len - 2);
|
||||
osel_memcpy(&crc[0], &frame[out_frame_len - 2], 2);
|
||||
crc_16 = BUILD_UINT16(crc[1], crc[0]);
|
||||
if (crc16 != crc_16)
|
||||
{
|
||||
LOG_PRINT("crc error crc16:%x, crc_16:%x\n");
|
||||
return;
|
||||
}
|
||||
|
||||
out_frame_len -= 2; // 去掉CRC_16
|
||||
|
||||
LOG_PRINT("data_analysis_event2 ok:");
|
||||
LOG_HEX(frame, out_frame_len);
|
||||
}
|
||||
/**
|
||||
* @brief 需要识别帧头和帧尾的数据协议
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void data_register1(void)
|
||||
{
|
||||
/**
|
||||
* 帧头 帧长度 源地址 目标地址 报文类型 报文体 校验 帧尾
|
||||
1 2 2 2 1 n 2 1
|
||||
*/
|
||||
#define FRAME_HEAD 0x05 // 帧头
|
||||
#define FRAME_TAIL 0x1b // 帧尾
|
||||
|
||||
// 注册数据解析
|
||||
data_reg_t reg;
|
||||
reg.sd.valid = true; // 数据头部验证有效标志位
|
||||
reg.sd.len = 1; // 数据头部长度
|
||||
reg.sd.pos = 0; // 数据头部偏移量
|
||||
reg.sd.data[0] = FRAME_HEAD; // 数据头部数据
|
||||
reg.ld.len = 2; // 数据长度
|
||||
reg.ld.pos = 2; // 报文长度包含帧长,这里需要设置偏移2
|
||||
reg.ld.valid = true; // 数据长度有效标志位
|
||||
reg.ld.little_endian = false; // 数据长度是否小端模式
|
||||
reg.argu.len_max = UART_RXSIZE; // 数据最大长度
|
||||
reg.argu.len_min = 2; // 数据最小长度
|
||||
reg.ed.valid = true; // 数据尾部有效标志位
|
||||
reg.ed.len = 1; // 数据尾部长度
|
||||
reg.ed.data[0] = FRAME_TAIL; // 数据尾部数据
|
||||
reg.echo_en = false; // 是否回显
|
||||
reg.func_ptr = data_analysis_event1; // 数据解析回调函数 data_analysis模块处理完数据后,会调用这个函数继续数据协议的处理
|
||||
uart_data_analysis_cb = data_fsm_init(UART_DATA_ANALYSIS_PORT_1); // 注册数据处理函数 data_analysis模块会调用这个函数,将数据写入到data_analysis模块
|
||||
data_reg(UART_DATA_ANALYSIS_PORT_1, reg); // 注册数据解析
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 需要识别帧头和没有帧尾的数据协议
|
||||
* @return {*}
|
||||
* @note
|
||||
*/
|
||||
static void data_register2(void)
|
||||
{
|
||||
/**
|
||||
* 帧头 帧长度 源地址 目标地址 报文类型 报文体 校验
|
||||
2 2 2 2 1 n 2
|
||||
*/
|
||||
#define FRAME_HEAD1 0xD5 // 帧头
|
||||
#define FRAME_HEAD2 0xC8 // 帧尾
|
||||
|
||||
// 注册数据解析
|
||||
data_reg_t reg;
|
||||
reg.sd.valid = true; // 数据头部验证有效标志位
|
||||
reg.sd.len = 2; // 数据头部长度
|
||||
reg.sd.pos = 0; // 数据头部偏移量
|
||||
reg.sd.data[0] = FRAME_HEAD1; // 数据头部数据
|
||||
reg.sd.data[1] = FRAME_HEAD2; // 数据头部数据
|
||||
reg.ld.len = 2; // 数据长度
|
||||
reg.ld.pos = 2; // 报文长度包含帧长,这里需要设置偏移2
|
||||
reg.ld.valid = true; // 数据长度有效标志位
|
||||
reg.ld.little_endian = false; // 数据长度是否小端模式
|
||||
reg.argu.len_max = UART_RXSIZE; // 数据最大长度
|
||||
reg.argu.len_min = 2; // 数据最小长度
|
||||
reg.ed.valid = false; // 数据尾部有效标志位
|
||||
reg.echo_en = false; // 是否回显
|
||||
reg.func_ptr = data_analysis_event2; // 数据解析回调函数 data_analysis模块处理完数据后,会调用这个函数继续数据协议的处理
|
||||
uart_data_analysis_cb = data_fsm_init(UART_DATA_ANALYSIS_PORT_2); // 注册数据处理函数 data_analysis模块会调用这个函数,将数据写入到data_analysis模块
|
||||
data_reg(UART_DATA_ANALYSIS_PORT_2, reg); // 注册数据解析
|
||||
}
|
||||
|
||||
int32_t main(void)
|
||||
{
|
||||
data_register1();
|
||||
data_register2();
|
||||
|
||||
// 模拟串口数据
|
||||
uint8_t data1[] = {0x05, 0x00, 0x0a, 0xff, 0xff, 0x00, 0x01, 0x00, 0x55, 0x40, 0x1b};
|
||||
for (uint16_t i = 0; i < ARRAY_LEN(data1); i++)
|
||||
{
|
||||
uart_data_analysis_cb(UART_DATA_ANALYSIS_PORT_1, *(data1 + i));
|
||||
}
|
||||
|
||||
// 模拟串口数据
|
||||
uint8_t data2[] = {0xD5, 0xC8, 0x00, 0x07, 0xff, 0xff, 0x00, 0x01, 0x00, 0x55, 0x40};
|
||||
for (uint16_t i = 0; i < ARRAY_LEN(data2); i++)
|
||||
{
|
||||
uart_data_analysis_cb(UART_DATA_ANALYSIS_PORT_2, *(data2 + i));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
#include "../inc/data_type_def.h"
|
||||
#include "../inc/log.h"
|
||||
#include "../inc/osel_arch.h"
|
||||
#include "../inc/sqqueue.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
} element_t;
|
||||
|
||||
sqqueue_ctrl_t queue; // 创建队列对象
|
||||
|
||||
void traverse_cb(const void *e)
|
||||
{
|
||||
element_t *p = (element_t *)e;
|
||||
LOG_PRINT("x = %d, y = %d", p->x, p->y);
|
||||
}
|
||||
|
||||
int32_t main(void)
|
||||
{
|
||||
int size = 10;
|
||||
// 初始化队列
|
||||
if (FALSE == sqqueue_ctrl_init(&queue, sizeof(element_t), size))
|
||||
{
|
||||
LOG_ERR("queue init failed!");
|
||||
return -1; // 创建失败
|
||||
}
|
||||
|
||||
// 添加测试元素
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
element_t element;
|
||||
element.x = i * 10;
|
||||
element.y = i * 10;
|
||||
queue.enter(&queue, &element); // 将成员插入到队列中
|
||||
}
|
||||
LOG_PRINT("add queue len = %d", queue.get_len(&queue)); // 获取队列长度
|
||||
|
||||
queue.del(&queue); // 移除首元素
|
||||
LOG_PRINT("del queue len = %d", queue.get_len(&queue)); // 获取队列长度
|
||||
queue.revoke(&queue); // 移除尾元素
|
||||
LOG_PRINT("revoke queue len = %d", queue.get_len(&queue)); // 获取队列长度
|
||||
queue.remove(&queue, 3); // 删除相对队头指定偏移位置的元素
|
||||
LOG_PRINT("remove queue len = %d", queue.get_len(&queue)); // 获取队列长度
|
||||
|
||||
LOG_PRINT("queue traverse:");
|
||||
queue.traverse(&queue, traverse_cb); // 遍历队列
|
||||
|
||||
queue.clear_sqq(&queue); // 清空队列
|
||||
LOG_PRINT("clear queue len = %d", queue.get_len(&queue)); // 获取队列长度
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief basic KV samples.
|
||||
*
|
||||
* basic Key-Value Database KV feature samples
|
||||
* get and show currnet boot counts
|
||||
*/
|
||||
|
||||
#include <flashdb.h>
|
||||
|
||||
#ifdef FDB_USING_KVDB
|
||||
|
||||
#define FDB_LOG_TAG "[sample][kvdb][basic]"
|
||||
|
||||
void kvdb_basic_sample(fdb_kvdb_t kvdb)
|
||||
{
|
||||
struct fdb_blob blob;
|
||||
int boot_count = 0;
|
||||
|
||||
FDB_INFO("==================== kvdb_basic_sample ====================\n");
|
||||
|
||||
{ /* GET the KV value */
|
||||
/* get the "boot_count" KV value */
|
||||
fdb_kv_get_blob(kvdb, "boot_count", fdb_blob_make(&blob, &boot_count, sizeof(boot_count)));
|
||||
/* the blob.saved.len is more than 0 when get the value successful */
|
||||
if (blob.saved.len > 0) {
|
||||
FDB_INFO("get the 'boot_count' value is %d\n", boot_count);
|
||||
} else {
|
||||
FDB_INFO("get the 'boot_count' failed\n");
|
||||
}
|
||||
}
|
||||
|
||||
{ /* CHANGE the KV value */
|
||||
/* increase the boot count */
|
||||
boot_count ++;
|
||||
/* change the "boot_count" KV's value */
|
||||
fdb_kv_set_blob(kvdb, "boot_count", fdb_blob_make(&blob, &boot_count, sizeof(boot_count)));
|
||||
FDB_INFO("set the 'boot_count' value to %d\n", boot_count);
|
||||
}
|
||||
|
||||
FDB_INFO("===========================================================\n");
|
||||
}
|
||||
|
||||
#endif /* FDB_USING_KVDB */
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief blob KV samples.
|
||||
*
|
||||
* Key-Value Database blob type KV feature samples
|
||||
*/
|
||||
|
||||
#include <flashdb.h>
|
||||
|
||||
#ifdef FDB_USING_KVDB
|
||||
|
||||
#define FDB_LOG_TAG "[sample][kvdb][blob]"
|
||||
|
||||
void kvdb_type_blob_sample(fdb_kvdb_t kvdb)
|
||||
{
|
||||
struct fdb_blob blob;
|
||||
|
||||
FDB_INFO("==================== kvdb_type_blob_sample ====================\n");
|
||||
|
||||
{ /* CREATE new Key-Value */
|
||||
int temp_data = 36;
|
||||
|
||||
/* It will create new KV node when "temp" KV not in database.
|
||||
* fdb_blob_make: It's a blob make function, and it will return the blob when make finish.
|
||||
*/
|
||||
fdb_kv_set_blob(kvdb, "temp", fdb_blob_make(&blob, &temp_data, sizeof(temp_data)));
|
||||
FDB_INFO("create the 'temp' blob KV, value is: %d\n", temp_data);
|
||||
}
|
||||
|
||||
{ /* GET the KV value */
|
||||
int temp_data = 0;
|
||||
|
||||
/* get the "temp" KV value */
|
||||
fdb_kv_get_blob(kvdb, "temp", fdb_blob_make(&blob, &temp_data, sizeof(temp_data)));
|
||||
/* the blob.saved.len is more than 0 when get the value successful */
|
||||
if (blob.saved.len > 0) {
|
||||
FDB_INFO("get the 'temp' value is: %d\n", temp_data);
|
||||
}
|
||||
}
|
||||
|
||||
{ /* CHANGE the KV value */
|
||||
int temp_data = 38;
|
||||
|
||||
/* change the "temp" KV's value to 38 */
|
||||
fdb_kv_set_blob(kvdb, "temp", fdb_blob_make(&blob, &temp_data, sizeof(temp_data)));
|
||||
FDB_INFO("set 'temp' value to %d\n", temp_data);
|
||||
}
|
||||
|
||||
{ /* DELETE the KV by name */
|
||||
fdb_kv_del(kvdb, "temp");
|
||||
FDB_INFO("delete the 'temp' finish\n");
|
||||
}
|
||||
|
||||
FDB_INFO("===========================================================\n");
|
||||
}
|
||||
|
||||
#endif /* FDB_USING_KVDB */
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief string KV samples.
|
||||
*
|
||||
* Key-Value Database string type KV feature samples source file.
|
||||
*/
|
||||
|
||||
#include <flashdb.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef FDB_USING_KVDB
|
||||
|
||||
#define FDB_LOG_TAG "[sample][kvdb][string]"
|
||||
|
||||
void kvdb_type_string_sample(fdb_kvdb_t kvdb)
|
||||
{
|
||||
FDB_INFO("==================== kvdb_type_string_sample ====================\n");
|
||||
|
||||
{ /* CREATE new Key-Value */
|
||||
char temp_data[10] = "36C";
|
||||
|
||||
/* It will create new KV node when "temp" KV not in database. */
|
||||
fdb_kv_set(kvdb, "temp", temp_data);
|
||||
FDB_INFO("create the 'temp' string KV, value is: %s\n", temp_data);
|
||||
}
|
||||
|
||||
{ /* GET the KV value */
|
||||
char *return_value, temp_data[10] = { 0 };
|
||||
|
||||
/* Get the "temp" KV value.
|
||||
* NOTE: The return value saved in fdb_kv_get's buffer. Please copy away as soon as possible.
|
||||
*/
|
||||
return_value = fdb_kv_get(kvdb, "temp");
|
||||
/* the return value is NULL when get the value failed */
|
||||
if (return_value != NULL) {
|
||||
strncpy(temp_data, return_value, sizeof(temp_data));
|
||||
FDB_INFO("get the 'temp' value is: %s\n", temp_data);
|
||||
}
|
||||
}
|
||||
|
||||
{ /* CHANGE the KV value */
|
||||
char temp_data[10] = "38C";
|
||||
|
||||
/* change the "temp" KV's value to "38.1" */
|
||||
fdb_kv_set(kvdb, "temp", temp_data);
|
||||
FDB_INFO("set 'temp' value to %s\n", temp_data);
|
||||
}
|
||||
|
||||
{ /* DELETE the KV by name */
|
||||
fdb_kv_del(kvdb, "temp");
|
||||
FDB_INFO("delete the 'temp' finish\n");
|
||||
}
|
||||
|
||||
FDB_INFO("===========================================================\n");
|
||||
}
|
||||
|
||||
#endif /* FDB_USING_KVDB */
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief TSDB samples.
|
||||
*
|
||||
* Time series log (like TSDB) feature samples source file.
|
||||
*
|
||||
* TSL is time series log, the TSDB saved many TSLs.
|
||||
*/
|
||||
|
||||
#include <flashdb.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef FDB_USING_TSDB
|
||||
|
||||
#define FDB_LOG_TAG "[sample][tsdb]"
|
||||
|
||||
#ifdef FDB_USING_TIMESTAMP_64BIT
|
||||
#define __PRITS "ld"
|
||||
#else
|
||||
#define __PRITS "d"
|
||||
#endif
|
||||
|
||||
struct env_status {
|
||||
int temp;
|
||||
int humi;
|
||||
};
|
||||
|
||||
static bool query_cb(fdb_tsl_t tsl, void *arg);
|
||||
static bool query_by_time_cb(fdb_tsl_t tsl, void *arg);
|
||||
static bool set_status_cb(fdb_tsl_t tsl, void *arg);
|
||||
|
||||
void tsdb_sample(fdb_tsdb_t tsdb)
|
||||
{
|
||||
struct fdb_blob blob;
|
||||
|
||||
FDB_INFO("==================== tsdb_sample ====================\n");
|
||||
|
||||
{ /* APPEND new TSL (time series log) */
|
||||
struct env_status status;
|
||||
|
||||
/* append new log to TSDB */
|
||||
status.temp = 36;
|
||||
status.humi = 85;
|
||||
fdb_tsl_append(tsdb, fdb_blob_make(&blob, &status, sizeof(status)));
|
||||
FDB_INFO("append the new status.temp (%d) and status.humi (%d)\n", status.temp, status.humi);
|
||||
|
||||
status.temp = 38;
|
||||
status.humi = 90;
|
||||
fdb_tsl_append(tsdb, fdb_blob_make(&blob, &status, sizeof(status)));
|
||||
FDB_INFO("append the new status.temp (%d) and status.humi (%d)\n", status.temp, status.humi);
|
||||
}
|
||||
|
||||
{ /* QUERY the TSDB */
|
||||
/* query all TSL in TSDB by iterator */
|
||||
fdb_tsl_iter(tsdb, query_cb, tsdb);
|
||||
}
|
||||
|
||||
{ /* QUERY the TSDB by time */
|
||||
/* prepare query time (from 1970-01-01 00:00:00 to 2020-05-05 00:00:00) */
|
||||
struct tm tm_from = { .tm_year = 1970 - 1900, .tm_mon = 0, .tm_mday = 1, .tm_hour = 0, .tm_min = 0, .tm_sec = 0 };
|
||||
struct tm tm_to = { .tm_year = 2020 - 1900, .tm_mon = 4, .tm_mday = 5, .tm_hour = 0, .tm_min = 0, .tm_sec = 0 };
|
||||
time_t from_time = mktime(&tm_from), to_time = mktime(&tm_to);
|
||||
size_t count;
|
||||
/* query all TSL in TSDB by time */
|
||||
fdb_tsl_iter_by_time(tsdb, from_time, to_time, query_by_time_cb, tsdb);
|
||||
/* query all FDB_TSL_WRITE status TSL's count in TSDB by time */
|
||||
count = fdb_tsl_query_count(tsdb, from_time, to_time, FDB_TSL_WRITE);
|
||||
FDB_INFO("query count is: %zu\n", count);
|
||||
}
|
||||
|
||||
{ /* SET the TSL status */
|
||||
/* Change the TSL status by iterator or time iterator
|
||||
* set_status_cb: the change operation will in this callback
|
||||
*
|
||||
* NOTE: The actions to modify the state must be in order.
|
||||
* like: FDB_TSL_WRITE -> FDB_TSL_USER_STATUS1 -> FDB_TSL_DELETED -> FDB_TSL_USER_STATUS2
|
||||
* The intermediate states can also be ignored.
|
||||
* such as: FDB_TSL_WRITE -> FDB_TSL_DELETED
|
||||
*/
|
||||
fdb_tsl_iter(tsdb, set_status_cb, tsdb);
|
||||
}
|
||||
|
||||
FDB_INFO("===========================================================\n");
|
||||
}
|
||||
|
||||
static bool query_cb(fdb_tsl_t tsl, void *arg)
|
||||
{
|
||||
struct fdb_blob blob;
|
||||
struct env_status status;
|
||||
fdb_tsdb_t db = arg;
|
||||
|
||||
fdb_blob_read((fdb_db_t) db, fdb_tsl_to_blob(tsl, fdb_blob_make(&blob, &status, sizeof(status))));
|
||||
FDB_INFO("[query_cb] queried a TSL: time: %" __PRITS ", temp: %d, humi: %d\n", tsl->time, status.temp, status.humi);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool query_by_time_cb(fdb_tsl_t tsl, void *arg)
|
||||
{
|
||||
struct fdb_blob blob;
|
||||
struct env_status status;
|
||||
fdb_tsdb_t db = arg;
|
||||
|
||||
fdb_blob_read((fdb_db_t) db, fdb_tsl_to_blob(tsl, fdb_blob_make(&blob, &status, sizeof(status))));
|
||||
FDB_INFO("[query_by_time_cb] queried a TSL: time: %" __PRITS ", temp: %d, humi: %d\n", tsl->time, status.temp, status.humi);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool set_status_cb(fdb_tsl_t tsl, void *arg)
|
||||
{
|
||||
fdb_tsdb_t db = arg;
|
||||
|
||||
FDB_INFO("set the TSL (time %" __PRITS ") status from %d to %d\n", tsl->time, tsl->status, FDB_TSL_USER_STATUS1);
|
||||
fdb_tsl_set_status(db, tsl, FDB_TSL_USER_STATUS1);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* FDB_USING_TSDB */
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-05-17 armink the first version
|
||||
*/
|
||||
|
||||
#include <fal.h>
|
||||
|
||||
static uint8_t init_ok = 0;
|
||||
|
||||
/**
|
||||
* FAL (Flash Abstraction Layer) initialization.
|
||||
* It will initialize all flash device and all flash partition.
|
||||
*
|
||||
* @return >= 0: partitions total number
|
||||
*/
|
||||
int fal_init(void)
|
||||
{
|
||||
extern int fal_flash_init(void);
|
||||
extern int fal_partition_init(void);
|
||||
|
||||
int result;
|
||||
|
||||
/* initialize all flash device on FAL flash table */
|
||||
result = fal_flash_init();
|
||||
|
||||
if (result < 0) {
|
||||
goto __exit;
|
||||
}
|
||||
|
||||
/* initialize all flash partition on FAL partition table */
|
||||
result = fal_partition_init();
|
||||
|
||||
__exit:
|
||||
|
||||
if ((result > 0) && (!init_ok))
|
||||
{
|
||||
init_ok = 1;
|
||||
log_i("Flash Abstraction Layer (V%s) initialize success.", FAL_SW_VERSION);
|
||||
}
|
||||
else if(result <= 0)
|
||||
{
|
||||
init_ok = 0;
|
||||
log_e("Flash Abstraction Layer (V%s) initialize failed.", FAL_SW_VERSION);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the FAL is initialized successfully
|
||||
*
|
||||
* @return 0: not init or init failed; 1: init success
|
||||
*/
|
||||
int fal_init_check(void)
|
||||
{
|
||||
return init_ok;
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-05-17 armink the first version
|
||||
*/
|
||||
|
||||
#ifndef _FAL_H_
|
||||
#define _FAL_H_
|
||||
|
||||
#include <fal_cfg.h>
|
||||
#include "fal_def.h"
|
||||
|
||||
/**
|
||||
* FAL (Flash Abstraction Layer) initialization.
|
||||
* It will initialize all flash device and all flash partition.
|
||||
*
|
||||
* @return >= 0: partitions total number
|
||||
*/
|
||||
int fal_init(void);
|
||||
|
||||
/* =============== flash device operator API =============== */
|
||||
/**
|
||||
* find flash device by name
|
||||
*
|
||||
* @param name flash device name
|
||||
*
|
||||
* @return != NULL: flash device
|
||||
* NULL: not found
|
||||
*/
|
||||
const struct fal_flash_dev *fal_flash_device_find(const char *name);
|
||||
|
||||
/* =============== partition operator API =============== */
|
||||
/**
|
||||
* find the partition by name
|
||||
*
|
||||
* @param name partition name
|
||||
*
|
||||
* @return != NULL: partition
|
||||
* NULL: not found
|
||||
*/
|
||||
const struct fal_partition *fal_partition_find(const char *name);
|
||||
|
||||
/**
|
||||
* get the partition table
|
||||
*
|
||||
* @param len return the partition table length
|
||||
*
|
||||
* @return partition table
|
||||
*/
|
||||
const struct fal_partition *fal_get_partition_table(size_t *len);
|
||||
|
||||
/**
|
||||
* set partition table temporarily
|
||||
* This setting will modify the partition table temporarily, the setting will be lost after restart.
|
||||
*
|
||||
* @param table partition table
|
||||
* @param len partition table length
|
||||
*/
|
||||
void fal_set_partition_table_temp(struct fal_partition *table, size_t len);
|
||||
|
||||
/**
|
||||
* read data from partition
|
||||
*
|
||||
* @param part partition
|
||||
* @param addr relative address for partition
|
||||
* @param buf read buffer
|
||||
* @param size read size
|
||||
*
|
||||
* @return >= 0: successful read data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size);
|
||||
|
||||
/**
|
||||
* write data to partition
|
||||
*
|
||||
* @param part partition
|
||||
* @param addr relative address for partition
|
||||
* @param buf write buffer
|
||||
* @param size write size
|
||||
*
|
||||
* @return >= 0: successful write data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size);
|
||||
|
||||
/**
|
||||
* erase partition data
|
||||
*
|
||||
* @param part partition
|
||||
* @param addr relative address for partition
|
||||
* @param size erase size
|
||||
*
|
||||
* @return >= 0: successful erased data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size);
|
||||
|
||||
/**
|
||||
* erase partition all data
|
||||
*
|
||||
* @param part partition
|
||||
*
|
||||
* @return >= 0: successful erased data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_erase_all(const struct fal_partition *part);
|
||||
|
||||
/**
|
||||
* print the partition table
|
||||
*/
|
||||
void fal_show_part_table(void);
|
||||
|
||||
/* =============== API provided to RT-Thread =============== */
|
||||
/**
|
||||
* create RT-Thread block device by specified partition
|
||||
*
|
||||
* @param parition_name partition name
|
||||
*
|
||||
* @return != NULL: created block device
|
||||
* NULL: created failed
|
||||
*/
|
||||
struct rt_device *fal_blk_device_create(const char *parition_name);
|
||||
|
||||
#if defined(RT_USING_MTD_NOR)
|
||||
/**
|
||||
* create RT-Thread MTD NOR device by specified partition
|
||||
*
|
||||
* @param parition_name partition name
|
||||
*
|
||||
* @return != NULL: created MTD NOR device
|
||||
* NULL: created failed
|
||||
*/
|
||||
struct rt_device *fal_mtd_nor_device_create(const char *parition_name);
|
||||
#endif /* defined(RT_USING_MTD_NOR) */
|
||||
|
||||
/**
|
||||
* create RT-Thread char device by specified partition
|
||||
*
|
||||
* @param parition_name partition name
|
||||
*
|
||||
* @return != NULL: created char device
|
||||
* NULL: created failed
|
||||
*/
|
||||
struct rt_device *fal_char_device_create(const char *parition_name);
|
||||
|
||||
#endif /* _FAL_H_ */
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-05-17 armink the first version
|
||||
*/
|
||||
|
||||
#ifndef _FAL_CFG_H_
|
||||
#define _FAL_CFG_H_
|
||||
#include "eeprom_m95.h"
|
||||
#include "eeprom_fm24.h"
|
||||
#include "flash.h"
|
||||
|
||||
#define FAL_PART_HAS_TABLE_CFG
|
||||
#define FAL_ERASE_SIZE 100
|
||||
// 需要块大小为2的N次方
|
||||
#define EEPROM_M95_1_BLOCK_SIZE M95_PAGE_SIZE_256 * 32
|
||||
#define EEPROM_M95_2_BLOCK_SIZE M95_PAGE_SIZE_256 * 32
|
||||
#define EEPROM_FM24_BLOCK_SIZE FM24_PAGE_SIZE * 16
|
||||
|
||||
#define EEPROM_M95_1_SIZE _M95M02_
|
||||
#define EEPROM_M95_2_SIZE _M95M02_
|
||||
#define EEPROM_FM24_SIZE FM24_SIZE
|
||||
|
||||
#define EEPROM_M95_1_DEV_NAME "eeprom_m95_1"
|
||||
#define EEPROM_M95_2_DEV_NAME "eeprom_m95_2"
|
||||
#define EEPROM_FM24_DEV_NAME "eeprom_fm24"
|
||||
|
||||
/* ===================== Flash device Configuration ========================= */
|
||||
extern struct fal_flash_dev eeprom_m95_1;
|
||||
extern struct fal_flash_dev eeprom_m95_2;
|
||||
extern struct fal_flash_dev eeprom_fm24;
|
||||
|
||||
/* flash device table */
|
||||
#define FAL_FLASH_DEV_TABLE \
|
||||
{ \
|
||||
&eeprom_m95_1, \
|
||||
&eeprom_m95_2, \
|
||||
}
|
||||
|
||||
/* ====================== Partition Configuration ========================== */
|
||||
#ifdef FAL_PART_HAS_TABLE_CFG
|
||||
/* partition table */
|
||||
// issues :https://github.com/armink/FlashDB/issues/40 ;epprom的扇区太小
|
||||
#define FAL_PART_TABLE \
|
||||
{ \
|
||||
{FAL_PART_MAGIC_WORD, "KVDB", EEPROM_M95_1_DEV_NAME, 0, EEPROM_M95_1_SIZE, 0}, \
|
||||
{FAL_PART_MAGIC_WORD, "TSDB", EEPROM_M95_2_DEV_NAME, 0, EEPROM_M95_2_SIZE, 0}, \
|
||||
}
|
||||
|
||||
#endif /* FAL_PART_HAS_TABLE_CFG */
|
||||
|
||||
#endif /* _FAL_CFG_H_ */
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-05-17 armink the first version
|
||||
*/
|
||||
|
||||
#ifndef _FAL_DEF_H_
|
||||
#define _FAL_DEF_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#ifdef FDB_USING_NATIVE_ASSERT
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
#define FAL_SW_VERSION "0.5.99"
|
||||
|
||||
#ifdef __RTTHREAD__ /* for RT-Thread platform */
|
||||
#include <rtthread.h>
|
||||
#define FAL_PRINTF rt_kprintf
|
||||
#define FAL_MALLOC rt_malloc
|
||||
#define FAL_CALLOC rt_calloc
|
||||
#define FAL_REALLOC rt_realloc
|
||||
#define FAL_FREE rt_free
|
||||
#endif
|
||||
|
||||
#ifndef FAL_MALLOC
|
||||
#define FAL_MALLOC malloc
|
||||
#endif
|
||||
|
||||
#ifndef FAL_CALLOC
|
||||
#define FAL_CALLOC calloc
|
||||
#endif
|
||||
|
||||
#ifndef FAL_REALLOC
|
||||
#define FAL_REALLOC realloc
|
||||
#endif
|
||||
|
||||
#ifndef FAL_FREE
|
||||
#define FAL_FREE free
|
||||
#endif
|
||||
|
||||
#ifndef FAL_PRINTF
|
||||
#define FAL_PRINTF printf
|
||||
#endif
|
||||
|
||||
#ifndef FAL_DEBUG
|
||||
#define FAL_DEBUG 0
|
||||
#endif
|
||||
|
||||
#if FAL_DEBUG
|
||||
#ifndef FDB_USING_NATIVE_ASSERT
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif
|
||||
#define assert(EXPR) \
|
||||
if (!(EXPR)) \
|
||||
{ \
|
||||
FAL_PRINTF("(%s) has assert failed at %s.\n", #EXPR, __func__ ); \
|
||||
while (1); \
|
||||
}
|
||||
#endif
|
||||
|
||||
/* debug level log */
|
||||
#ifdef log_d
|
||||
#undef log_d
|
||||
#endif
|
||||
#include <inttypes.h>
|
||||
#define log_d(...) FAL_PRINTF("[D/FAL] (%s:%" PRIdLEAST16 ") ", __func__, __LINE__); FAL_PRINTF(__VA_ARGS__);FAL_PRINTF("\n")
|
||||
|
||||
#else
|
||||
|
||||
#ifndef FDB_USING_NATIVE_ASSERT
|
||||
#ifdef assert
|
||||
#undef assert
|
||||
#endif
|
||||
#define assert(EXPR) ((void)0);
|
||||
#endif
|
||||
|
||||
/* debug level log */
|
||||
#ifdef log_d
|
||||
#undef log_d
|
||||
#endif
|
||||
#define log_d(...)
|
||||
#endif /* FAL_DEBUG */
|
||||
|
||||
/* error level log */
|
||||
#ifdef log_e
|
||||
#undef log_e
|
||||
#endif
|
||||
#define log_e(...) FAL_PRINTF("\033[31;22m[E/FAL] (%s:%d) ", __func__, __LINE__);FAL_PRINTF(__VA_ARGS__);FAL_PRINTF("\033[0m\n")
|
||||
|
||||
/* info level log */
|
||||
#ifdef log_i
|
||||
#undef log_i
|
||||
#endif
|
||||
#define log_i(...) FAL_PRINTF("\033[32;22m[I/FAL] "); FAL_PRINTF(__VA_ARGS__);FAL_PRINTF("\033[0m\n")
|
||||
|
||||
/* FAL flash and partition device name max length */
|
||||
#ifndef FAL_DEV_NAME_MAX
|
||||
#define FAL_DEV_NAME_MAX 24
|
||||
#endif
|
||||
|
||||
struct fal_flash_dev
|
||||
{
|
||||
char name[FAL_DEV_NAME_MAX];
|
||||
|
||||
/* flash device start address and len */
|
||||
uint32_t addr;
|
||||
size_t len;
|
||||
/* the block size in the flash for erase minimum granularity */
|
||||
size_t blk_size;
|
||||
|
||||
struct
|
||||
{
|
||||
int (*init)(void);
|
||||
int (*read)(long offset, uint8_t *buf, size_t size);
|
||||
int (*write)(long offset, const uint8_t *buf, size_t size);
|
||||
int (*erase)(long offset, size_t size);
|
||||
} ops;
|
||||
|
||||
/* write minimum granularity, unit: bit.
|
||||
1(nor flash)/ 8(stm32f2/f4)/ 32(stm32f1)/ 64(stm32l4)
|
||||
0 will not take effect. */
|
||||
size_t write_gran;
|
||||
};
|
||||
typedef struct fal_flash_dev *fal_flash_dev_t;
|
||||
|
||||
/**
|
||||
* FAL partition
|
||||
*/
|
||||
struct fal_partition
|
||||
{
|
||||
uint32_t magic_word;
|
||||
|
||||
/* partition name */
|
||||
char name[FAL_DEV_NAME_MAX];
|
||||
/* flash device name for partition */
|
||||
char flash_name[FAL_DEV_NAME_MAX];
|
||||
|
||||
/* partition offset address on flash device */
|
||||
long offset;
|
||||
size_t len;
|
||||
|
||||
uint32_t reserved;
|
||||
};
|
||||
typedef struct fal_partition *fal_partition_t;
|
||||
|
||||
#endif /* _FAL_DEF_H_ */
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-05-17 armink the first version
|
||||
*/
|
||||
|
||||
#include <fal.h>
|
||||
#include <string.h>
|
||||
|
||||
/* flash device table, must defined by user */
|
||||
#if !defined(FAL_FLASH_DEV_TABLE)
|
||||
#error "You must defined flash device table (FAL_FLASH_DEV_TABLE) on 'fal_cfg.h'"
|
||||
#endif
|
||||
|
||||
static const struct fal_flash_dev * const device_table[] = FAL_FLASH_DEV_TABLE;
|
||||
static const size_t device_table_len = sizeof(device_table) / sizeof(device_table[0]);
|
||||
static uint8_t init_ok = 0;
|
||||
|
||||
/**
|
||||
* Initialize all flash device on FAL flash table
|
||||
*
|
||||
* @return result
|
||||
*/
|
||||
int fal_flash_init(void)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (init_ok)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (i = 0; i < device_table_len; i++)
|
||||
{
|
||||
assert(device_table[i]->ops.read);
|
||||
assert(device_table[i]->ops.write);
|
||||
assert(device_table[i]->ops.erase);
|
||||
/* init flash device on flash table */
|
||||
if (device_table[i]->ops.init)
|
||||
{
|
||||
device_table[i]->ops.init();
|
||||
}
|
||||
log_d("Flash device | %*.*s | addr: 0x%08lx | len: 0x%08x | blk_size: 0x%08x |initialized finish.",
|
||||
FAL_DEV_NAME_MAX, FAL_DEV_NAME_MAX, device_table[i]->name, device_table[i]->addr, device_table[i]->len,
|
||||
device_table[i]->blk_size);
|
||||
}
|
||||
|
||||
init_ok = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* find flash device by name
|
||||
*
|
||||
* @param name flash device name
|
||||
*
|
||||
* @return != NULL: flash device
|
||||
* NULL: not found
|
||||
*/
|
||||
const struct fal_flash_dev *fal_flash_device_find(const char *name)
|
||||
{
|
||||
assert(init_ok);
|
||||
assert(name);
|
||||
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < device_table_len; i++)
|
||||
{
|
||||
if (!strncmp(name, device_table[i]->name, FAL_DEV_NAME_MAX)) {
|
||||
return device_table[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-05-17 armink the first version
|
||||
*/
|
||||
|
||||
#include <fal.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* partition magic word */
|
||||
#define FAL_PART_MAGIC_WORD 0x45503130
|
||||
#define FAL_PART_MAGIC_WORD_H 0x4550L
|
||||
#define FAL_PART_MAGIC_WORD_L 0x3130L
|
||||
|
||||
struct part_flash_info
|
||||
{
|
||||
const struct fal_flash_dev *flash_dev;
|
||||
};
|
||||
|
||||
/**
|
||||
* FAL partition table config has defined on 'fal_cfg.h'.
|
||||
* When this option is disable, it will auto find the partition table on a specified location in flash partition.
|
||||
*/
|
||||
#ifdef FAL_PART_HAS_TABLE_CFG
|
||||
|
||||
/* check partition table definition */
|
||||
#if !defined(FAL_PART_TABLE)
|
||||
#error "You must defined FAL_PART_TABLE on 'fal_cfg.h'"
|
||||
#endif
|
||||
|
||||
#ifdef __CC_ARM /* ARM Compiler */
|
||||
#define SECTION(x) __attribute__((section(x)))
|
||||
#define USED __attribute__((used))
|
||||
#elif defined (__IAR_SYSTEMS_ICC__) /* for IAR Compiler */
|
||||
#define SECTION(x) @ x
|
||||
#define USED __root
|
||||
#elif defined (__GNUC__) /* GNU GCC Compiler */
|
||||
#define SECTION(x) __attribute__((section(x)))
|
||||
#define USED __attribute__((used))
|
||||
#else
|
||||
#error not supported tool chain
|
||||
#endif /* __CC_ARM */
|
||||
//USED static const struct fal_partition partition_table_def[] SECTION("FalPartTable") = FAL_PART_TABLE;
|
||||
static const struct fal_partition partition_table_def[] = FAL_PART_TABLE;
|
||||
static const struct fal_partition *partition_table = NULL;
|
||||
/* partition and flash object information cache table */
|
||||
static struct part_flash_info part_flash_cache[sizeof(partition_table_def) / sizeof(partition_table_def[0])] = { 0 };
|
||||
|
||||
#else /* FAL_PART_HAS_TABLE_CFG */
|
||||
|
||||
#if !defined(FAL_PART_TABLE_FLASH_DEV_NAME)
|
||||
#error "You must defined FAL_PART_TABLE_FLASH_DEV_NAME on 'fal_cfg.h'"
|
||||
#endif
|
||||
|
||||
/* check partition table end offset address definition */
|
||||
#if !defined(FAL_PART_TABLE_END_OFFSET)
|
||||
#error "You must defined FAL_PART_TABLE_END_OFFSET on 'fal_cfg.h'"
|
||||
#endif
|
||||
|
||||
static struct fal_partition *partition_table = NULL;
|
||||
static struct part_flash_info *part_flash_cache = NULL;
|
||||
#endif /* FAL_PART_HAS_TABLE_CFG */
|
||||
|
||||
static uint8_t init_ok = 0;
|
||||
static size_t partition_table_len = 0;
|
||||
|
||||
/**
|
||||
* print the partition table
|
||||
*/
|
||||
void fal_show_part_table(void)
|
||||
{
|
||||
char *item1 = "name", *item2 = "flash_dev";
|
||||
size_t i, part_name_max = strlen(item1), flash_dev_name_max = strlen(item2);
|
||||
const struct fal_partition *part;
|
||||
|
||||
if (partition_table_len)
|
||||
{
|
||||
for (i = 0; i < partition_table_len; i++)
|
||||
{
|
||||
part = &partition_table[i];
|
||||
if (strlen(part->name) > part_name_max)
|
||||
{
|
||||
part_name_max = strlen(part->name);
|
||||
}
|
||||
if (strlen(part->flash_name) > flash_dev_name_max)
|
||||
{
|
||||
flash_dev_name_max = strlen(part->flash_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
log_i("==================== FAL partition table ====================");
|
||||
log_i("| %-*.*s | %-*.*s | offset | length |", part_name_max, FAL_DEV_NAME_MAX, item1, flash_dev_name_max,
|
||||
FAL_DEV_NAME_MAX, item2);
|
||||
log_i("-------------------------------------------------------------");
|
||||
for (i = 0; i < partition_table_len; i++)
|
||||
{
|
||||
|
||||
#ifdef FAL_PART_HAS_TABLE_CFG
|
||||
part = &partition_table[i];
|
||||
#else
|
||||
part = &partition_table[partition_table_len - i - 1];
|
||||
#endif
|
||||
|
||||
log_i("| %-*.*s | %-*.*s | 0x%08lx | 0x%08x |", part_name_max, FAL_DEV_NAME_MAX, part->name, flash_dev_name_max,
|
||||
FAL_DEV_NAME_MAX, part->flash_name, part->offset, part->len);
|
||||
}
|
||||
log_i("=============================================================");
|
||||
}
|
||||
|
||||
static int check_and_update_part_cache(const struct fal_partition *table, size_t len)
|
||||
{
|
||||
const struct fal_flash_dev *flash_dev = NULL;
|
||||
size_t i;
|
||||
|
||||
#ifndef FAL_PART_HAS_TABLE_CFG
|
||||
if (part_flash_cache)
|
||||
{
|
||||
FAL_FREE(part_flash_cache);
|
||||
}
|
||||
part_flash_cache = FAL_MALLOC(len * sizeof(struct part_flash_info));
|
||||
if (part_flash_cache == NULL)
|
||||
{
|
||||
log_e("Initialize failed! No memory for partition table cache");
|
||||
return -2;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
flash_dev = fal_flash_device_find(table[i].flash_name);
|
||||
if (flash_dev == NULL)
|
||||
{
|
||||
log_d("Warning: Do NOT found the flash device(%s).", table[i].flash_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (table[i].offset >= (long)flash_dev->len)
|
||||
{
|
||||
log_e("Initialize failed! Partition(%s) offset address(%ld) out of flash bound(<%d).",
|
||||
table[i].name, table[i].offset, flash_dev->len);
|
||||
partition_table_len = 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
part_flash_cache[i].flash_dev = flash_dev;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize all flash partition on FAL partition table
|
||||
*
|
||||
* @return partitions total number
|
||||
*/
|
||||
int fal_partition_init(void)
|
||||
{
|
||||
|
||||
if (init_ok)
|
||||
{
|
||||
return partition_table_len;
|
||||
}
|
||||
|
||||
#ifdef FAL_PART_HAS_TABLE_CFG
|
||||
partition_table = &partition_table_def[0];
|
||||
partition_table_len = sizeof(partition_table_def) / sizeof(partition_table_def[0]);
|
||||
#else
|
||||
/* load partition table from the end address FAL_PART_TABLE_END_OFFSET, error return 0 */
|
||||
long part_table_offset = FAL_PART_TABLE_END_OFFSET;
|
||||
size_t table_num = 0, table_item_size = 0;
|
||||
uint8_t part_table_find_ok = 0;
|
||||
uint32_t read_magic_word;
|
||||
fal_partition_t new_part = NULL;
|
||||
size_t i;
|
||||
const struct fal_flash_dev *flash_dev = NULL;
|
||||
|
||||
flash_dev = fal_flash_device_find(FAL_PART_TABLE_FLASH_DEV_NAME);
|
||||
if (flash_dev == NULL)
|
||||
{
|
||||
log_e("Initialize failed! Flash device (%s) NOT found.", FAL_PART_TABLE_FLASH_DEV_NAME);
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
/* check partition table offset address */
|
||||
if (part_table_offset < 0 || part_table_offset >= (long) flash_dev->len)
|
||||
{
|
||||
log_e("Setting partition table end offset address(%ld) out of flash bound(<%d).", part_table_offset, flash_dev->len);
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
table_item_size = sizeof(struct fal_partition);
|
||||
new_part = (fal_partition_t)FAL_MALLOC(table_item_size);
|
||||
if (new_part == NULL)
|
||||
{
|
||||
log_e("Initialize failed! No memory for table buffer.");
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
/* find partition table location */
|
||||
{
|
||||
uint8_t read_buf[64];
|
||||
|
||||
part_table_offset -= sizeof(read_buf);
|
||||
while (part_table_offset >= 0)
|
||||
{
|
||||
if (flash_dev->ops.read(part_table_offset, read_buf, sizeof(read_buf)) > 0)
|
||||
{
|
||||
/* find magic word in read buf */
|
||||
for (i = 0; i < sizeof(read_buf) - sizeof(read_magic_word) + 1; i++)
|
||||
{
|
||||
read_magic_word = read_buf[0 + i] + (read_buf[1 + i] << 8) + (read_buf[2 + i] << 16) + (read_buf[3 + i] << 24);
|
||||
if (read_magic_word == ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L))
|
||||
{
|
||||
part_table_find_ok = 1;
|
||||
part_table_offset += i;
|
||||
log_d("Find the partition table on '%s' offset @0x%08lx.", FAL_PART_TABLE_FLASH_DEV_NAME,
|
||||
part_table_offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* read failed */
|
||||
break;
|
||||
}
|
||||
|
||||
if (part_table_find_ok)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* calculate next read buf position */
|
||||
if (part_table_offset >= (long)sizeof(read_buf))
|
||||
{
|
||||
part_table_offset -= sizeof(read_buf);
|
||||
part_table_offset += (sizeof(read_magic_word) - 1);
|
||||
}
|
||||
else if (part_table_offset != 0)
|
||||
{
|
||||
part_table_offset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* find failed */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* load partition table */
|
||||
while (part_table_find_ok)
|
||||
{
|
||||
memset(new_part, 0x00, table_num);
|
||||
if (flash_dev->ops.read(part_table_offset - table_item_size * (table_num), (uint8_t *) new_part,
|
||||
table_item_size) < 0)
|
||||
{
|
||||
log_e("Initialize failed! Flash device (%s) read error!", flash_dev->name);
|
||||
table_num = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (new_part->magic_word != ((FAL_PART_MAGIC_WORD_H << 16) + FAL_PART_MAGIC_WORD_L))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
partition_table = (fal_partition_t) FAL_REALLOC(partition_table, table_item_size * (table_num + 1));
|
||||
if (partition_table == NULL)
|
||||
{
|
||||
log_e("Initialize failed! No memory for partition table");
|
||||
table_num = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(partition_table + table_num, new_part, table_item_size);
|
||||
|
||||
table_num++;
|
||||
};
|
||||
|
||||
if (table_num == 0)
|
||||
{
|
||||
log_e("Partition table NOT found on flash: %s (len: %d) from offset: 0x%08x.", FAL_PART_TABLE_FLASH_DEV_NAME,
|
||||
FAL_DEV_NAME_MAX, FAL_PART_TABLE_END_OFFSET);
|
||||
goto _exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
partition_table_len = table_num;
|
||||
}
|
||||
#endif /* FAL_PART_HAS_TABLE_CFG */
|
||||
|
||||
/* check the partition table device exists */
|
||||
if (check_and_update_part_cache(partition_table, partition_table_len) != 0)
|
||||
{
|
||||
goto _exit;
|
||||
}
|
||||
|
||||
init_ok = 1;
|
||||
|
||||
_exit:
|
||||
|
||||
#if FAL_DEBUG
|
||||
fal_show_part_table();
|
||||
#endif
|
||||
|
||||
#ifndef FAL_PART_HAS_TABLE_CFG
|
||||
if (new_part)
|
||||
{
|
||||
FAL_FREE(new_part);
|
||||
}
|
||||
#endif /* !FAL_PART_HAS_TABLE_CFG */
|
||||
|
||||
return partition_table_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* find the partition by name
|
||||
*
|
||||
* @param name partition name
|
||||
*
|
||||
* @return != NULL: partition
|
||||
* NULL: not found
|
||||
*/
|
||||
const struct fal_partition *fal_partition_find(const char *name)
|
||||
{
|
||||
assert(init_ok);
|
||||
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < partition_table_len; i++)
|
||||
{
|
||||
if (!strcmp(name, partition_table[i].name))
|
||||
{
|
||||
return &partition_table[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const struct fal_flash_dev *flash_device_find_by_part(const struct fal_partition *part)
|
||||
{
|
||||
assert(part >= partition_table);
|
||||
assert(part <= &partition_table[partition_table_len - 1]);
|
||||
|
||||
return part_flash_cache[part - partition_table].flash_dev;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the partition table
|
||||
*
|
||||
* @param len return the partition table length
|
||||
*
|
||||
* @return partition table
|
||||
*/
|
||||
const struct fal_partition *fal_get_partition_table(size_t *len)
|
||||
{
|
||||
assert(init_ok);
|
||||
assert(len);
|
||||
|
||||
*len = partition_table_len;
|
||||
|
||||
return partition_table;
|
||||
}
|
||||
|
||||
/**
|
||||
* set partition table temporarily
|
||||
* This setting will modify the partition table temporarily, the setting will be lost after restart.
|
||||
*
|
||||
* @param table partition table
|
||||
* @param len partition table length
|
||||
*/
|
||||
void fal_set_partition_table_temp(struct fal_partition *table, size_t len)
|
||||
{
|
||||
assert(init_ok);
|
||||
assert(table);
|
||||
|
||||
check_and_update_part_cache(table, len);
|
||||
|
||||
partition_table_len = len;
|
||||
partition_table = table;
|
||||
}
|
||||
|
||||
/**
|
||||
* read data from partition
|
||||
*
|
||||
* @param part partition
|
||||
* @param addr relative address for partition
|
||||
* @param buf read buffer
|
||||
* @param size read size
|
||||
*
|
||||
* @return >= 0: successful read data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_read(const struct fal_partition *part, uint32_t addr, uint8_t *buf, size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct fal_flash_dev *flash_dev = NULL;
|
||||
|
||||
assert(part);
|
||||
assert(buf);
|
||||
|
||||
if (addr + size > part->len)
|
||||
{
|
||||
log_e("Partition read error! Partition address out of bound.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
flash_dev = flash_device_find_by_part(part);
|
||||
if (flash_dev == NULL)
|
||||
{
|
||||
log_e("Partition read error! Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = flash_dev->ops.read(part->offset + addr, buf, size);
|
||||
if (ret < 0)
|
||||
{
|
||||
log_e("Partition read error! Flash device(%s) read error!", part->flash_name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* write data to partition
|
||||
*
|
||||
* @param part partition
|
||||
* @param addr relative address for partition
|
||||
* @param buf write buffer
|
||||
* @param size write size
|
||||
*
|
||||
* @return >= 0: successful write data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_write(const struct fal_partition *part, uint32_t addr, const uint8_t *buf, size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct fal_flash_dev *flash_dev = NULL;
|
||||
|
||||
assert(part);
|
||||
assert(buf);
|
||||
|
||||
if (addr + size > part->len)
|
||||
{
|
||||
log_e("Partition write error! Partition address out of bound.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
flash_dev = flash_device_find_by_part(part);
|
||||
if (flash_dev == NULL)
|
||||
{
|
||||
log_e("Partition write error! Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = flash_dev->ops.write(part->offset + addr, buf, size);
|
||||
if (ret < 0)
|
||||
{
|
||||
log_e("Partition write error! Flash device(%s) write error!", part->flash_name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* erase partition data
|
||||
*
|
||||
* @param part partition
|
||||
* @param addr relative address for partition
|
||||
* @param size erase size
|
||||
*
|
||||
* @return >= 0: successful erased data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_erase(const struct fal_partition *part, uint32_t addr, size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
const struct fal_flash_dev *flash_dev = NULL;
|
||||
|
||||
assert(part);
|
||||
|
||||
if (addr + size > part->len)
|
||||
{
|
||||
log_e("Partition erase error! Partition address out of bound.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
flash_dev = flash_device_find_by_part(part);
|
||||
if (flash_dev == NULL)
|
||||
{
|
||||
log_e("Partition erase error! Don't found flash device(%s) of the partition(%s).", part->flash_name, part->name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = flash_dev->ops.erase(part->offset + addr, size);
|
||||
if (ret < 0)
|
||||
{
|
||||
log_e("Partition erase error! Flash device(%s) erase error!", part->flash_name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* erase partition all data
|
||||
*
|
||||
* @param part partition
|
||||
*
|
||||
* @return >= 0: successful erased data size
|
||||
* -1: error
|
||||
*/
|
||||
int fal_partition_erase_all(const struct fal_partition *part)
|
||||
{
|
||||
return fal_partition_erase(part, 0, part->len);
|
||||
}
|
||||
|
|
@ -0,0 +1,934 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-06-23 armink the first version
|
||||
* 2019-08-22 MurphyZhao adapt to none rt-thread case
|
||||
*/
|
||||
|
||||
#include <fal.h>
|
||||
|
||||
#ifdef RT_VER_NUM
|
||||
#include <rtthread.h>
|
||||
#include <rtdevice.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ========================== block device ======================== */
|
||||
struct fal_blk_device
|
||||
{
|
||||
struct rt_device parent;
|
||||
struct rt_device_blk_geometry geometry;
|
||||
const struct fal_partition *fal_part;
|
||||
};
|
||||
|
||||
/* RT-Thread device interface */
|
||||
#if RTTHREAD_VERSION >= 30000
|
||||
static rt_err_t blk_dev_control(rt_device_t dev, int cmd, void *args)
|
||||
#else
|
||||
static rt_err_t blk_dev_control(rt_device_t dev, rt_uint8_t cmd, void *args)
|
||||
#endif
|
||||
{
|
||||
struct fal_blk_device *part = (struct fal_blk_device*) dev;
|
||||
|
||||
assert(part != RT_NULL);
|
||||
|
||||
if (cmd == RT_DEVICE_CTRL_BLK_GETGEOME)
|
||||
{
|
||||
struct rt_device_blk_geometry *geometry;
|
||||
|
||||
geometry = (struct rt_device_blk_geometry *) args;
|
||||
if (geometry == RT_NULL)
|
||||
{
|
||||
return -RT_ERROR;
|
||||
}
|
||||
|
||||
memcpy(geometry, &part->geometry, sizeof(struct rt_device_blk_geometry));
|
||||
}
|
||||
else if (cmd == RT_DEVICE_CTRL_BLK_ERASE)
|
||||
{
|
||||
rt_uint32_t *addrs = (rt_uint32_t *) args, start_addr = addrs[0], end_addr = addrs[1], phy_start_addr;
|
||||
rt_size_t phy_size;
|
||||
|
||||
if (addrs == RT_NULL || start_addr > end_addr)
|
||||
{
|
||||
return -RT_ERROR;
|
||||
}
|
||||
|
||||
if (end_addr == start_addr)
|
||||
{
|
||||
end_addr++;
|
||||
}
|
||||
|
||||
phy_start_addr = start_addr * part->geometry.bytes_per_sector;
|
||||
phy_size = (end_addr - start_addr) * part->geometry.bytes_per_sector;
|
||||
|
||||
if (fal_partition_erase(part->fal_part, phy_start_addr, phy_size) < 0)
|
||||
{
|
||||
return -RT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static rt_size_t blk_dev_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_blk_device *part = (struct fal_blk_device*) dev;
|
||||
|
||||
assert(part != RT_NULL);
|
||||
|
||||
ret = fal_partition_read(part->fal_part, pos * part->geometry.block_size, buffer, size * part->geometry.block_size);
|
||||
|
||||
if (ret != (int)(size * part->geometry.block_size))
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static rt_size_t blk_dev_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_blk_device *part;
|
||||
rt_off_t phy_pos;
|
||||
rt_size_t phy_size;
|
||||
|
||||
part = (struct fal_blk_device*) dev;
|
||||
assert(part != RT_NULL);
|
||||
|
||||
/* change the block device's logic address to physical address */
|
||||
phy_pos = pos * part->geometry.bytes_per_sector;
|
||||
phy_size = size * part->geometry.bytes_per_sector;
|
||||
|
||||
ret = fal_partition_erase(part->fal_part, phy_pos, phy_size);
|
||||
|
||||
if (ret == (int) phy_size)
|
||||
{
|
||||
ret = fal_partition_write(part->fal_part, phy_pos, buffer, phy_size);
|
||||
}
|
||||
|
||||
if (ret != (int) phy_size)
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = size;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef RT_USING_DEVICE_OPS
|
||||
const static struct rt_device_ops blk_dev_ops =
|
||||
{
|
||||
RT_NULL,
|
||||
RT_NULL,
|
||||
RT_NULL,
|
||||
blk_dev_read,
|
||||
blk_dev_write,
|
||||
blk_dev_control
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* create RT-Thread block device by specified partition
|
||||
*
|
||||
* @param parition_name partition name
|
||||
*
|
||||
* @return != NULL: created block device
|
||||
* NULL: created failed
|
||||
*/
|
||||
struct rt_device *fal_blk_device_create(const char *parition_name)
|
||||
{
|
||||
struct fal_blk_device *blk_dev;
|
||||
const struct fal_partition *fal_part = fal_partition_find(parition_name);
|
||||
const struct fal_flash_dev *fal_flash = NULL;
|
||||
|
||||
if (!fal_part)
|
||||
{
|
||||
log_e("Error: the partition name (%s) is not found.", parition_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((fal_flash = fal_flash_device_find(fal_part->flash_name)) == NULL)
|
||||
{
|
||||
log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
blk_dev = (struct fal_blk_device*) rt_malloc(sizeof(struct fal_blk_device));
|
||||
if (blk_dev)
|
||||
{
|
||||
blk_dev->fal_part = fal_part;
|
||||
blk_dev->geometry.bytes_per_sector = fal_flash->blk_size;
|
||||
blk_dev->geometry.block_size = fal_flash->blk_size;
|
||||
blk_dev->geometry.sector_count = fal_part->len / fal_flash->blk_size;
|
||||
|
||||
/* register device */
|
||||
blk_dev->parent.type = RT_Device_Class_Block;
|
||||
|
||||
#ifdef RT_USING_DEVICE_OPS
|
||||
blk_dev->parent.ops = &blk_dev_ops;
|
||||
#else
|
||||
blk_dev->parent.init = NULL;
|
||||
blk_dev->parent.open = NULL;
|
||||
blk_dev->parent.close = NULL;
|
||||
blk_dev->parent.read = blk_dev_read;
|
||||
blk_dev->parent.write = blk_dev_write;
|
||||
blk_dev->parent.control = blk_dev_control;
|
||||
#endif
|
||||
|
||||
/* no private */
|
||||
blk_dev->parent.user_data = RT_NULL;
|
||||
|
||||
log_i("The FAL block device (%s) created successfully", fal_part->name);
|
||||
rt_device_register(RT_DEVICE(blk_dev), fal_part->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_STANDALONE);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_e("Error: no memory for create FAL block device");
|
||||
}
|
||||
|
||||
return RT_DEVICE(blk_dev);
|
||||
}
|
||||
|
||||
/* ========================== MTD nor device ======================== */
|
||||
#if defined(RT_USING_MTD_NOR)
|
||||
|
||||
struct fal_mtd_nor_device
|
||||
{
|
||||
struct rt_mtd_nor_device parent;
|
||||
const struct fal_partition *fal_part;
|
||||
};
|
||||
|
||||
static rt_size_t mtd_nor_dev_read(struct rt_mtd_nor_device* device, rt_off_t offset, rt_uint8_t* data, rt_uint32_t length)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_mtd_nor_device *part = (struct fal_mtd_nor_device*) device;
|
||||
|
||||
assert(part != RT_NULL);
|
||||
|
||||
ret = fal_partition_read(part->fal_part, offset, data, length);
|
||||
|
||||
if (ret != (int)length)
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = length;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static rt_size_t mtd_nor_dev_write(struct rt_mtd_nor_device* device, rt_off_t offset, const rt_uint8_t* data, rt_uint32_t length)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_mtd_nor_device *part;
|
||||
|
||||
part = (struct fal_mtd_nor_device*) device;
|
||||
assert(part != RT_NULL);
|
||||
|
||||
ret = fal_partition_write(part->fal_part, offset, data, length);
|
||||
|
||||
if (ret != (int) length)
|
||||
{
|
||||
ret = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = length;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static rt_err_t mtd_nor_dev_erase(struct rt_mtd_nor_device* device, rt_off_t offset, rt_uint32_t length)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_mtd_nor_device *part;
|
||||
|
||||
part = (struct fal_mtd_nor_device*) device;
|
||||
assert(part != RT_NULL);
|
||||
|
||||
ret = fal_partition_erase(part->fal_part, offset, length);
|
||||
|
||||
if (ret != length)
|
||||
{
|
||||
return -RT_ERROR;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RT_EOK;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct rt_mtd_nor_driver_ops _ops =
|
||||
{
|
||||
RT_NULL,
|
||||
mtd_nor_dev_read,
|
||||
mtd_nor_dev_write,
|
||||
mtd_nor_dev_erase,
|
||||
};
|
||||
|
||||
/**
|
||||
* create RT-Thread MTD NOR device by specified partition
|
||||
*
|
||||
* @param parition_name partition name
|
||||
*
|
||||
* @return != NULL: created MTD NOR device
|
||||
* NULL: created failed
|
||||
*/
|
||||
struct rt_device *fal_mtd_nor_device_create(const char *parition_name)
|
||||
{
|
||||
struct fal_mtd_nor_device *mtd_nor_dev;
|
||||
const struct fal_partition *fal_part = fal_partition_find(parition_name);
|
||||
const struct fal_flash_dev *fal_flash = NULL;
|
||||
|
||||
if (!fal_part)
|
||||
{
|
||||
log_e("Error: the partition name (%s) is not found.", parition_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((fal_flash = fal_flash_device_find(fal_part->flash_name)) == NULL)
|
||||
{
|
||||
log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mtd_nor_dev = (struct fal_mtd_nor_device*) rt_malloc(sizeof(struct fal_mtd_nor_device));
|
||||
if (mtd_nor_dev)
|
||||
{
|
||||
mtd_nor_dev->fal_part = fal_part;
|
||||
|
||||
mtd_nor_dev->parent.block_start = 0;
|
||||
mtd_nor_dev->parent.block_end = fal_part->len / fal_flash->blk_size;
|
||||
mtd_nor_dev->parent.block_size = fal_flash->blk_size;
|
||||
|
||||
/* set ops */
|
||||
mtd_nor_dev->parent.ops = &_ops;
|
||||
|
||||
log_i("The FAL MTD NOR device (%s) created successfully", fal_part->name);
|
||||
rt_mtd_nor_register_device(fal_part->name, &mtd_nor_dev->parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
log_e("Error: no memory for create FAL MTD NOR device");
|
||||
}
|
||||
|
||||
return RT_DEVICE(&mtd_nor_dev->parent);
|
||||
}
|
||||
|
||||
#endif /* defined(RT_USING_MTD_NOR) */
|
||||
|
||||
|
||||
/* ========================== char device ======================== */
|
||||
struct fal_char_device
|
||||
{
|
||||
struct rt_device parent;
|
||||
const struct fal_partition *fal_part;
|
||||
};
|
||||
|
||||
/* RT-Thread device interface */
|
||||
static rt_size_t char_dev_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_char_device *part = (struct fal_char_device *) dev;
|
||||
|
||||
assert(part != RT_NULL);
|
||||
|
||||
if (pos + size > part->fal_part->len)
|
||||
size = part->fal_part->len - pos;
|
||||
|
||||
ret = fal_partition_read(part->fal_part, pos, buffer, size);
|
||||
|
||||
if (ret != (int)(size))
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static rt_size_t char_dev_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_char_device *part;
|
||||
|
||||
part = (struct fal_char_device *) dev;
|
||||
assert(part != RT_NULL);
|
||||
|
||||
if (pos == 0)
|
||||
{
|
||||
fal_partition_erase_all(part->fal_part);
|
||||
}
|
||||
else if (pos + size > part->fal_part->len)
|
||||
{
|
||||
size = part->fal_part->len - pos;
|
||||
}
|
||||
|
||||
ret = fal_partition_write(part->fal_part, pos, buffer, size);
|
||||
|
||||
if (ret != (int) size)
|
||||
ret = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef RT_USING_DEVICE_OPS
|
||||
const static struct rt_device_ops char_dev_ops =
|
||||
{
|
||||
RT_NULL,
|
||||
RT_NULL,
|
||||
RT_NULL,
|
||||
char_dev_read,
|
||||
char_dev_write,
|
||||
RT_NULL
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef RT_USING_POSIX
|
||||
#include <dfs_posix.h>
|
||||
|
||||
/* RT-Thread device filesystem interface */
|
||||
static int char_dev_fopen(struct dfs_fd *fd)
|
||||
{
|
||||
struct fal_char_device *part = (struct fal_char_device *) fd->data;
|
||||
|
||||
assert(part != RT_NULL);
|
||||
|
||||
switch (fd->flags & O_ACCMODE)
|
||||
{
|
||||
case O_RDONLY:
|
||||
break;
|
||||
case O_WRONLY:
|
||||
case O_RDWR:
|
||||
/* erase partition when device file open */
|
||||
fal_partition_erase_all(part->fal_part);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
fd->pos = 0;
|
||||
|
||||
return RT_EOK;
|
||||
}
|
||||
|
||||
static int char_dev_fread(struct dfs_fd *fd, void *buf, size_t count)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_char_device *part = (struct fal_char_device *) fd->data;
|
||||
|
||||
assert(part != RT_NULL);
|
||||
|
||||
if (fd->pos + count > part->fal_part->len)
|
||||
count = part->fal_part->len - fd->pos;
|
||||
|
||||
ret = fal_partition_read(part->fal_part, fd->pos, buf, count);
|
||||
|
||||
if (ret != (int)(count))
|
||||
return 0;
|
||||
|
||||
fd->pos += ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int char_dev_fwrite(struct dfs_fd *fd, const void *buf, size_t count)
|
||||
{
|
||||
int ret = 0;
|
||||
struct fal_char_device *part = (struct fal_char_device *) fd->data;
|
||||
|
||||
assert(part != RT_NULL);
|
||||
|
||||
if (fd->pos + count > part->fal_part->len)
|
||||
count = part->fal_part->len - fd->pos;
|
||||
|
||||
ret = fal_partition_write(part->fal_part, fd->pos, buf, count);
|
||||
|
||||
if (ret != (int) count)
|
||||
return 0;
|
||||
|
||||
fd->pos += ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dfs_file_ops char_dev_fops =
|
||||
{
|
||||
char_dev_fopen,
|
||||
RT_NULL,
|
||||
RT_NULL,
|
||||
char_dev_fread,
|
||||
char_dev_fwrite,
|
||||
RT_NULL, /* flush */
|
||||
RT_NULL, /* lseek */
|
||||
RT_NULL, /* getdents */
|
||||
RT_NULL,
|
||||
};
|
||||
#endif /* defined(RT_USING_POSIX) */
|
||||
|
||||
/**
|
||||
* create RT-Thread char device by specified partition
|
||||
*
|
||||
* @param parition_name partition name
|
||||
*
|
||||
* @return != NULL: created char device
|
||||
* NULL: created failed
|
||||
*/
|
||||
struct rt_device *fal_char_device_create(const char *parition_name)
|
||||
{
|
||||
struct fal_char_device *char_dev;
|
||||
const struct fal_partition *fal_part = fal_partition_find(parition_name);
|
||||
|
||||
if (!fal_part)
|
||||
{
|
||||
log_e("Error: the partition name (%s) is not found.", parition_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ((fal_flash_device_find(fal_part->flash_name)) == NULL)
|
||||
{
|
||||
log_e("Error: the flash device name (%s) is not found.", fal_part->flash_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char_dev = (struct fal_char_device *) rt_malloc(sizeof(struct fal_char_device));
|
||||
if (char_dev)
|
||||
{
|
||||
char_dev->fal_part = fal_part;
|
||||
|
||||
/* register device */
|
||||
char_dev->parent.type = RT_Device_Class_Char;
|
||||
|
||||
#ifdef RT_USING_DEVICE_OPS
|
||||
char_dev->parent.ops = &char_dev_ops;
|
||||
#else
|
||||
char_dev->parent.init = NULL;
|
||||
char_dev->parent.open = NULL;
|
||||
char_dev->parent.close = NULL;
|
||||
char_dev->parent.read = char_dev_read;
|
||||
char_dev->parent.write = char_dev_write;
|
||||
char_dev->parent.control = NULL;
|
||||
/* no private */
|
||||
char_dev->parent.user_data = NULL;
|
||||
#endif
|
||||
|
||||
rt_device_register(RT_DEVICE(char_dev), fal_part->name, RT_DEVICE_FLAG_RDWR);
|
||||
log_i("The FAL char device (%s) created successfully", fal_part->name);
|
||||
|
||||
#ifdef RT_USING_POSIX
|
||||
/* set fops */
|
||||
char_dev->parent.fops = &char_dev_fops;
|
||||
#endif
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
log_e("Error: no memory for create FAL char device");
|
||||
}
|
||||
|
||||
return RT_DEVICE(char_dev);
|
||||
}
|
||||
|
||||
#if defined(RT_USING_FINSH) && defined(FINSH_USING_MSH)
|
||||
|
||||
#include <finsh.h>
|
||||
extern int fal_init_check(void);
|
||||
|
||||
static void fal(uint8_t argc, char **argv) {
|
||||
|
||||
#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ')
|
||||
#define HEXDUMP_WIDTH 16
|
||||
#define CMD_PROBE_INDEX 0
|
||||
#define CMD_READ_INDEX 1
|
||||
#define CMD_WRITE_INDEX 2
|
||||
#define CMD_ERASE_INDEX 3
|
||||
#define CMD_BENCH_INDEX 4
|
||||
|
||||
int result;
|
||||
static const struct fal_flash_dev *flash_dev = NULL;
|
||||
static const struct fal_partition *part_dev = NULL;
|
||||
size_t i = 0, j = 0;
|
||||
|
||||
const char* help_info[] =
|
||||
{
|
||||
[CMD_PROBE_INDEX] = "fal probe [dev_name|part_name] - probe flash device or partition by given name",
|
||||
[CMD_READ_INDEX] = "fal read addr size - read 'size' bytes starting at 'addr'",
|
||||
[CMD_WRITE_INDEX] = "fal write addr data1 ... dataN - write some bytes 'data' starting at 'addr'",
|
||||
[CMD_ERASE_INDEX] = "fal erase addr size - erase 'size' bytes starting at 'addr'",
|
||||
[CMD_BENCH_INDEX] = "fal bench <blk_size> - benchmark test with per block size",
|
||||
};
|
||||
|
||||
if (fal_init_check() != 1)
|
||||
{
|
||||
rt_kprintf("\n[Warning] FAL is not initialized or failed to initialize!\n\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (argc < 2)
|
||||
{
|
||||
rt_kprintf("Usage:\n");
|
||||
for (i = 0; i < sizeof(help_info) / sizeof(char*); i++)
|
||||
{
|
||||
rt_kprintf("%s\n", help_info[i]);
|
||||
}
|
||||
rt_kprintf("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
const char *operator = argv[1];
|
||||
uint32_t addr, size;
|
||||
|
||||
if (!strcmp(operator, "probe"))
|
||||
{
|
||||
if (argc >= 3)
|
||||
{
|
||||
char *dev_name = argv[2];
|
||||
if ((flash_dev = fal_flash_device_find(dev_name)) != NULL)
|
||||
{
|
||||
part_dev = NULL;
|
||||
}
|
||||
else if ((part_dev = fal_partition_find(dev_name)) != NULL)
|
||||
{
|
||||
flash_dev = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Device %s NOT found. Probe failed.\n", dev_name);
|
||||
flash_dev = NULL;
|
||||
part_dev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (flash_dev)
|
||||
{
|
||||
rt_kprintf("Probed a flash device | %s | addr: %ld | len: %d |.\n", flash_dev->name,
|
||||
flash_dev->addr, flash_dev->len);
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
rt_kprintf("Probed a flash partition | %s | flash_dev: %s | offset: %ld | len: %d |.\n",
|
||||
part_dev->name, part_dev->flash_name, part_dev->offset, part_dev->len);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("No flash device or partition was probed.\n");
|
||||
rt_kprintf("Usage: %s.\n", help_info[CMD_PROBE_INDEX]);
|
||||
fal_show_part_table();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!flash_dev && !part_dev)
|
||||
{
|
||||
rt_kprintf("No flash device or partition was probed. Please run 'fal probe'.\n");
|
||||
return;
|
||||
}
|
||||
if (!rt_strcmp(operator, "read"))
|
||||
{
|
||||
if (argc < 4)
|
||||
{
|
||||
rt_kprintf("Usage: %s.\n", help_info[CMD_READ_INDEX]);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = strtol(argv[2], NULL, 0);
|
||||
size = strtol(argv[3], NULL, 0);
|
||||
uint8_t *data = rt_malloc(size);
|
||||
if (data)
|
||||
{
|
||||
if (flash_dev)
|
||||
{
|
||||
result = flash_dev->ops.read(addr, data, size);
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
result = fal_partition_read(part_dev, addr, data, size);
|
||||
}
|
||||
if (result >= 0)
|
||||
{
|
||||
rt_kprintf("Read data success. Start from 0x%08X, size is %ld. The data is:\n", addr,
|
||||
size);
|
||||
rt_kprintf("Offset (h) 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n");
|
||||
for (i = 0; i < size; i += HEXDUMP_WIDTH)
|
||||
{
|
||||
rt_kprintf("[%08X] ", addr + i);
|
||||
/* dump hex */
|
||||
for (j = 0; j < HEXDUMP_WIDTH; j++)
|
||||
{
|
||||
if (i + j < size)
|
||||
{
|
||||
rt_kprintf("%02X ", data[i + j]);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf(" ");
|
||||
}
|
||||
}
|
||||
/* dump char for hex */
|
||||
for (j = 0; j < HEXDUMP_WIDTH; j++)
|
||||
{
|
||||
if (i + j < size)
|
||||
{
|
||||
rt_kprintf("%c", __is_print(data[i + j]) ? data[i + j] : '.');
|
||||
}
|
||||
}
|
||||
rt_kprintf("\n");
|
||||
}
|
||||
rt_kprintf("\n");
|
||||
}
|
||||
rt_free(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Low memory!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!strcmp(operator, "write"))
|
||||
{
|
||||
if (argc < 4)
|
||||
{
|
||||
rt_kprintf("Usage: %s.\n", help_info[CMD_WRITE_INDEX]);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = strtol(argv[2], NULL, 0);
|
||||
size = argc - 3;
|
||||
uint8_t *data = rt_malloc(size);
|
||||
if (data)
|
||||
{
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
data[i] = strtol(argv[3 + i], NULL, 0);
|
||||
}
|
||||
if (flash_dev)
|
||||
{
|
||||
result = flash_dev->ops.write(addr, data, size);
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
result = fal_partition_write(part_dev, addr, data, size);
|
||||
}
|
||||
if (result >= 0)
|
||||
{
|
||||
rt_kprintf("Write data success. Start from 0x%08X, size is %ld.\n", addr, size);
|
||||
rt_kprintf("Write data: ");
|
||||
for (i = 0; i < size; i++)
|
||||
{
|
||||
rt_kprintf("%d ", data[i]);
|
||||
}
|
||||
rt_kprintf(".\n");
|
||||
}
|
||||
rt_free(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Low memory!\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!rt_strcmp(operator, "erase"))
|
||||
{
|
||||
if (argc < 4)
|
||||
{
|
||||
rt_kprintf("Usage: %s.\n", help_info[CMD_ERASE_INDEX]);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = strtol(argv[2], NULL, 0);
|
||||
size = strtol(argv[3], NULL, 0);
|
||||
if (flash_dev)
|
||||
{
|
||||
result = flash_dev->ops.erase(addr, size);
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
result = fal_partition_erase(part_dev, addr, size);
|
||||
}
|
||||
if (result >= 0)
|
||||
{
|
||||
rt_kprintf("Erase data success. Start from 0x%08X, size is %ld.\n", addr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!strcmp(operator, "bench"))
|
||||
{
|
||||
if (argc < 3)
|
||||
{
|
||||
rt_kprintf("Usage: %s.\n", help_info[CMD_BENCH_INDEX]);
|
||||
return;
|
||||
}
|
||||
else if ((argc > 3 && strcmp(argv[3], "yes")) || argc < 4)
|
||||
{
|
||||
rt_kprintf("DANGER: It will erase full chip or partition! Please run 'fal bench %d yes'.\n", strtol(argv[2], NULL, 0));
|
||||
return;
|
||||
}
|
||||
/* full chip benchmark test */
|
||||
uint32_t start_time, time_cast;
|
||||
size_t write_size = strtol(argv[2], NULL, 0), read_size = strtol(argv[2], NULL, 0), cur_op_size;
|
||||
uint8_t *write_data = (uint8_t *)rt_malloc(write_size), *read_data = (uint8_t *)rt_malloc(read_size);
|
||||
|
||||
if (write_data && read_data)
|
||||
{
|
||||
for (i = 0; i < write_size; i ++) {
|
||||
write_data[i] = i & 0xFF;
|
||||
}
|
||||
if (flash_dev)
|
||||
{
|
||||
size = flash_dev->len;
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
size = part_dev->len;
|
||||
}
|
||||
/* benchmark testing */
|
||||
rt_kprintf("Erasing %ld bytes data, waiting...\n", size);
|
||||
start_time = rt_tick_get();
|
||||
if (flash_dev)
|
||||
{
|
||||
result = flash_dev->ops.erase(0, size);
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
result = fal_partition_erase(part_dev, 0, size);
|
||||
}
|
||||
if (result >= 0)
|
||||
{
|
||||
time_cast = rt_tick_get() - start_time;
|
||||
rt_kprintf("Erase benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND,
|
||||
time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Erase benchmark has an error. Error code: %d.\n", result);
|
||||
}
|
||||
/* write test */
|
||||
rt_kprintf("Writing %ld bytes data, waiting...\n", size);
|
||||
start_time = rt_tick_get();
|
||||
for (i = 0; i < size; i += write_size)
|
||||
{
|
||||
if (i + write_size <= size)
|
||||
{
|
||||
cur_op_size = write_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
cur_op_size = size - i;
|
||||
}
|
||||
if (flash_dev)
|
||||
{
|
||||
result = flash_dev->ops.write(i, write_data, cur_op_size);
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
result = fal_partition_write(part_dev, i, write_data, cur_op_size);
|
||||
}
|
||||
if (result < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result >= 0)
|
||||
{
|
||||
time_cast = rt_tick_get() - start_time;
|
||||
rt_kprintf("Write benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND,
|
||||
time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Write benchmark has an error. Error code: %d.\n", result);
|
||||
}
|
||||
/* read test */
|
||||
rt_kprintf("Reading %ld bytes data, waiting...\n", size);
|
||||
start_time = rt_tick_get();
|
||||
for (i = 0; i < size; i += read_size)
|
||||
{
|
||||
if (i + read_size <= size)
|
||||
{
|
||||
cur_op_size = read_size;
|
||||
}
|
||||
else
|
||||
{
|
||||
cur_op_size = size - i;
|
||||
}
|
||||
if (flash_dev)
|
||||
{
|
||||
result = flash_dev->ops.read(i, read_data, cur_op_size);
|
||||
}
|
||||
else if (part_dev)
|
||||
{
|
||||
result = fal_partition_read(part_dev, i, read_data, cur_op_size);
|
||||
}
|
||||
/* data check */
|
||||
for (int index = 0; index < cur_op_size; index ++)
|
||||
{
|
||||
if (write_data[index] != read_data[index])
|
||||
{
|
||||
rt_kprintf("%d %d %02x %02x.\n", i, index, write_data[index], read_data[index]);
|
||||
}
|
||||
}
|
||||
|
||||
if (memcmp(write_data, read_data, cur_op_size))
|
||||
{
|
||||
result = -RT_ERROR;
|
||||
rt_kprintf("Data check ERROR! Please check you flash by other command.\n");
|
||||
}
|
||||
/* has an error */
|
||||
if (result < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (result >= 0)
|
||||
{
|
||||
time_cast = rt_tick_get() - start_time;
|
||||
rt_kprintf("Read benchmark success, total time: %d.%03dS.\n", time_cast / RT_TICK_PER_SECOND,
|
||||
time_cast % RT_TICK_PER_SECOND / ((RT_TICK_PER_SECOND * 1 + 999) / 1000));
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Read benchmark has an error. Error code: %d.\n", result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Low memory!\n");
|
||||
}
|
||||
rt_free(write_data);
|
||||
rt_free(read_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
rt_kprintf("Usage:\n");
|
||||
for (i = 0; i < sizeof(help_info) / sizeof(char*); i++)
|
||||
{
|
||||
rt_kprintf("%s\n", help_info[i]);
|
||||
}
|
||||
rt_kprintf("\n");
|
||||
return;
|
||||
}
|
||||
if (result < 0) {
|
||||
rt_kprintf("This operate has an error. Error code: %d.\n", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MSH_CMD_EXPORT(fal, FAL (Flash Abstraction Layer) operate.);
|
||||
|
||||
#endif /* defined(RT_USING_FINSH) && defined(FINSH_USING_MSH) */
|
||||
#endif /* RT_VER_NUM */
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-01-26 armink the first version
|
||||
*/
|
||||
#include "eeprom_fm24.h"
|
||||
#include <fal.h>
|
||||
|
||||
static int init(void);
|
||||
static int erase(long offset, size_t size);
|
||||
|
||||
static int read(long offset, uint8_t *buf, size_t size);
|
||||
static int write(long offset, const uint8_t *buf, size_t size);
|
||||
|
||||
// 1.定义 flash 设备
|
||||
struct fal_flash_dev eeprom_fm24 =
|
||||
{
|
||||
.name = EEPROM_FM24_DEV_NAME,
|
||||
.addr = 0,
|
||||
.len = FM24_PAGE_SIZE,
|
||||
.blk_size = EEPROM_FM24_BLOCK_SIZE,
|
||||
.ops = {init, read, write, erase},
|
||||
.write_gran = 1};
|
||||
|
||||
static int init(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int erase(long offset, size_t size)
|
||||
{
|
||||
// uint8_t erase_size = size > FAL_ERASE_SIZE ? FAL_ERASE_SIZE : size;
|
||||
// uint8_t buf[FAL_ERASE_SIZE];
|
||||
// osel_memset(buf, 0xFF, FAL_ERASE_SIZE);
|
||||
|
||||
// for (uint8_t i = 0; i < (size / FM24_PAGE_SIZE); i++)
|
||||
// {
|
||||
// uint32_t addr = eeprom_fm24.addr + offset + i * FM24_PAGE_SIZE;
|
||||
// eeprom_fm24_write(addr, (uint8_t *)buf, erase_size);
|
||||
// }
|
||||
return size;
|
||||
}
|
||||
|
||||
static int read(long offset, uint8_t *buf, size_t size)
|
||||
{
|
||||
/* You can add your code under here. */
|
||||
if (size == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint32_t addr = eeprom_fm24.addr + offset;
|
||||
BOOL res = eeprom_fm24_read(addr, buf, size);
|
||||
return res == TRUE ? size : 0;
|
||||
}
|
||||
|
||||
static int write(long offset, const uint8_t *buf, size_t size)
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
uint32_t addr = eeprom_fm24.addr + offset;
|
||||
BOOL res = eeprom_fm24_write(addr, (uint8_t *)buf, size);
|
||||
return res == TRUE ? (int)size : -1;
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2018, RT-Thread Development Team
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Change Logs:
|
||||
* Date Author Notes
|
||||
* 2018-01-26 armink the first version
|
||||
*/
|
||||
#include "eeprom_m95.h"
|
||||
#include <fal.h>
|
||||
|
||||
static int init(void);
|
||||
static int erase(long offset, size_t size);
|
||||
|
||||
static int read1(long offset, uint8_t *buf, size_t size);
|
||||
static int read2(long offset, uint8_t *buf, size_t size);
|
||||
static int write1(long offset, const uint8_t *buf, size_t size);
|
||||
static int write2(long offset, const uint8_t *buf, size_t size);
|
||||
|
||||
// 1.定义 flash 设备
|
||||
struct fal_flash_dev eeprom_m95_1 =
|
||||
{
|
||||
.name = EEPROM_M95_1_DEV_NAME,
|
||||
.addr = 10 * M95_PAGE_SIZE_256,
|
||||
.len = EEPROM_M95_1_SIZE - 10 * M95_PAGE_SIZE_256,
|
||||
.blk_size = EEPROM_M95_1_BLOCK_SIZE,
|
||||
.ops = {init, read1, write1, erase},
|
||||
.write_gran = 1}; // 设置写粒度,单位 bit,EPPROM写粒度为1bit
|
||||
|
||||
struct fal_flash_dev eeprom_m95_2 =
|
||||
{
|
||||
.name = EEPROM_M95_2_DEV_NAME,
|
||||
.addr = 0x000000,
|
||||
.len = EEPROM_M95_2_SIZE,
|
||||
.blk_size = EEPROM_M95_2_BLOCK_SIZE,
|
||||
.ops = {init, read2, write2, erase},
|
||||
.write_gran = 1};
|
||||
|
||||
static int init(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int erase(long offset, size_t size)
|
||||
{
|
||||
// uint8_t erase_size = size > FAL_ERASE_SIZE ? FAL_ERASE_SIZE : size;
|
||||
// uint8_t buf[FAL_ERASE_SIZE];
|
||||
// osel_memset(buf, 0xFF, FAL_ERASE_SIZE);
|
||||
|
||||
// for (uint8_t i = 0; i < (size / M95_PAGE_SIZE_256); i++)
|
||||
// {
|
||||
// uint32_t addr = eeprom_m95_1.addr + offset + i * M95_PAGE_SIZE_256;
|
||||
// eeprom_m95_write(M95_1, addr, (uint8_t *)buf, erase_size);
|
||||
// }
|
||||
return size;
|
||||
}
|
||||
|
||||
static int read1(long offset, uint8_t *buf, size_t size)
|
||||
{
|
||||
/* You can add your code under here. */
|
||||
uint32_t addr = eeprom_m95_1.addr + offset;
|
||||
BOOL res = eeprom_m95_read(M95_1, addr, buf, size);
|
||||
return res == TRUE ? size : 0;
|
||||
}
|
||||
|
||||
static int write1(long offset, const uint8_t *buf, size_t size)
|
||||
{
|
||||
uint32_t addr = eeprom_m95_1.addr + offset;
|
||||
BOOL res = eeprom_m95_write(M95_1, addr, (uint8_t *)buf, size);
|
||||
return res == TRUE ? (int)size : -1;
|
||||
}
|
||||
|
||||
static int read2(long offset, uint8_t *buf, size_t size)
|
||||
{
|
||||
/* You can add your code under here. */
|
||||
uint32_t addr = eeprom_m95_2.addr + offset;
|
||||
BOOL res = eeprom_m95_read(M95_2, addr, buf, size);
|
||||
return res == TRUE ? size : 0;
|
||||
}
|
||||
|
||||
static int write2(long offset, const uint8_t *buf, size_t size)
|
||||
{
|
||||
uint32_t addr = eeprom_m95_2.addr + offset;
|
||||
BOOL res = eeprom_m95_write(M95_2, addr, (uint8_t *)buf, size);
|
||||
return res == TRUE ? (int)size : -1;
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Initialize interface.
|
||||
*
|
||||
* Some initialize interface for this library.
|
||||
*/
|
||||
|
||||
#include <flashdb.h>
|
||||
#include <fdb_low_lvl.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifdef FDB_USING_FILE_POSIX_MODE
|
||||
#if !defined(_MSC_VER)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#endif /* FDB_USING_FILE_POSIX_MODE */
|
||||
|
||||
#define FDB_LOG_TAG ""
|
||||
|
||||
#if !defined(FDB_USING_FAL_MODE) && !defined(FDB_USING_FILE_MODE)
|
||||
#error "Please defined the FDB_USING_FAL_MODE or FDB_USING_FILE_MODE macro"
|
||||
#endif
|
||||
|
||||
fdb_err_t _fdb_init_ex(fdb_db_t db, const char *name, const char *path, fdb_db_type type, void *user_data)
|
||||
{
|
||||
FDB_ASSERT(db);
|
||||
FDB_ASSERT(name);
|
||||
FDB_ASSERT(path);
|
||||
|
||||
if (db->init_ok) {
|
||||
return FDB_NO_ERR;
|
||||
}
|
||||
|
||||
db->name = name;
|
||||
db->type = type;
|
||||
db->user_data = user_data;
|
||||
|
||||
if (db->file_mode) {
|
||||
#ifdef FDB_USING_FILE_MODE
|
||||
memset(db->cur_file_sec, FDB_FAILED_ADDR, FDB_FILE_CACHE_TABLE_SIZE * sizeof(db->cur_file_sec[0]));
|
||||
/* must set when using file mode */
|
||||
FDB_ASSERT(db->sec_size != 0);
|
||||
FDB_ASSERT(db->max_size != 0);
|
||||
#ifdef FDB_USING_FILE_POSIX_MODE
|
||||
memset(db->cur_file, -1, FDB_FILE_CACHE_TABLE_SIZE * sizeof(db->cur_file[0]));
|
||||
#else
|
||||
memset(db->cur_file, 0, FDB_FILE_CACHE_TABLE_SIZE * sizeof(db->cur_file[0]));
|
||||
#endif
|
||||
db->storage.dir = path;
|
||||
FDB_ASSERT(strlen(path) != 0)
|
||||
#endif
|
||||
} else {
|
||||
#ifdef FDB_USING_FAL_MODE
|
||||
size_t block_size;
|
||||
|
||||
/* FAL (Flash Abstraction Layer) initialization */
|
||||
fal_init();
|
||||
/* check the flash partition */
|
||||
if ((db->storage.part = fal_partition_find(path)) == NULL) {
|
||||
FDB_INFO("Error: Partition (%s) not found.\n", path);
|
||||
return FDB_PART_NOT_FOUND;
|
||||
}
|
||||
|
||||
block_size = fal_flash_device_find(db->storage.part->flash_name)->blk_size;
|
||||
if (db->sec_size == 0) {
|
||||
db->sec_size = block_size;
|
||||
} else {
|
||||
/* must be aligned with block size */
|
||||
if (db->sec_size % block_size != 0) {
|
||||
FDB_INFO("Error: db sector size (%" PRIu32 ") MUST align with block size (%zu).\n", db->sec_size, block_size);
|
||||
return FDB_INIT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
db->max_size = db->storage.part->len;
|
||||
#endif /* FDB_USING_FAL_MODE */
|
||||
}
|
||||
|
||||
/* the block size MUST to be the Nth power of 2 */
|
||||
FDB_ASSERT((db->sec_size & (db->sec_size - 1)) == 0);
|
||||
/* must align with sector size */
|
||||
if (db->max_size % db->sec_size != 0) {
|
||||
FDB_INFO("Error: db total size (%" PRIu32 ") MUST align with sector size (%" PRIu32 ").\n", db->max_size, db->sec_size);
|
||||
return FDB_INIT_FAILED;
|
||||
}
|
||||
/* must has more than or equal 2 sectors */
|
||||
if (db->max_size / db->sec_size < 2) {
|
||||
FDB_INFO("Error: db MUST has more than or equal 2 sectors, current has %" PRIu32 " sector(s)\n", db->max_size / db->sec_size);
|
||||
return FDB_INIT_FAILED;
|
||||
}
|
||||
|
||||
return FDB_NO_ERR;
|
||||
}
|
||||
|
||||
void _fdb_init_finish(fdb_db_t db, fdb_err_t result)
|
||||
{
|
||||
static bool log_is_show = false;
|
||||
if (result == FDB_NO_ERR) {
|
||||
db->init_ok = true;
|
||||
if (!log_is_show) {
|
||||
FDB_INFO("FlashDB V%s is initialize success.\n", FDB_SW_VERSION);
|
||||
FDB_INFO("You can get the latest version on https://github.com/armink/FlashDB .\n");
|
||||
log_is_show = true;
|
||||
}
|
||||
} else if (!db->not_formatable) {
|
||||
FDB_INFO("Error: %s (%s@%s) is initialize fail (%d).\n", db->type == FDB_DB_TYPE_KV ? "KVDB" : "TSDB",
|
||||
db->name, _fdb_db_path(db), (int)result);
|
||||
}
|
||||
}
|
||||
|
||||
void _fdb_deinit(fdb_db_t db)
|
||||
{
|
||||
FDB_ASSERT(db);
|
||||
|
||||
if (db->init_ok) {
|
||||
#ifdef FDB_USING_FILE_MODE
|
||||
for (int i = 0; i < FDB_FILE_CACHE_TABLE_SIZE; i++) {
|
||||
#ifdef FDB_USING_FILE_POSIX_MODE
|
||||
if (db->cur_file[i] > 0) {
|
||||
close(db->cur_file[i]);
|
||||
}
|
||||
#else
|
||||
if (db->cur_file[i] != 0) {
|
||||
fclose(db->cur_file[i]);
|
||||
}
|
||||
#endif /* FDB_USING_FILE_POSIX_MODE */
|
||||
}
|
||||
#endif /* FDB_USING_FILE_MODE */
|
||||
}
|
||||
|
||||
db->init_ok = false;
|
||||
}
|
||||
|
||||
const char *_fdb_db_path(fdb_db_t db)
|
||||
{
|
||||
if (db->file_mode) {
|
||||
#ifdef FDB_USING_FILE_MODE
|
||||
return db->storage.dir;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
#ifdef FDB_USING_FAL_MODE
|
||||
return db->storage.part->name;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief configuration file
|
||||
*/
|
||||
|
||||
#ifndef _FDB_CFG_H_
|
||||
#define _FDB_CFG_H_
|
||||
|
||||
/* using KVDB feature */
|
||||
#define FDB_USING_KVDB
|
||||
|
||||
#ifdef FDB_USING_KVDB
|
||||
/* Auto update KV to latest default when current KVDB version number is changed. @see fdb_kvdb.ver_num */
|
||||
#define FDB_KV_AUTO_UPDATE
|
||||
#endif
|
||||
|
||||
/* using TSDB (Time series database) feature */
|
||||
#define FDB_USING_TSDB
|
||||
|
||||
/* Using FAL storage mode */
|
||||
#define FDB_USING_FAL_MODE
|
||||
|
||||
#ifdef FDB_USING_FAL_MODE
|
||||
/* the flash write granularity, unit: bit
|
||||
* only support 1(nor flash)/ 8(stm32f2/f4)/ 32(stm32f1)/ 64(stm32f7)/ 128(stm32h5) */
|
||||
#define FDB_WRITE_GRAN 1 /* @note you must define it for a value */
|
||||
#endif
|
||||
|
||||
/* Using file storage mode by LIBC file API, like fopen/fread/fwrte/fclose */
|
||||
/* #define FDB_USING_FILE_LIBC_MODE */
|
||||
|
||||
/* Using file storage mode by POSIX file API, like open/read/write/close */
|
||||
/* #define FDB_USING_FILE_POSIX_MODE */
|
||||
|
||||
/* MCU Endian Configuration, default is Little Endian Order. */
|
||||
/* #define FDB_BIG_ENDIAN */
|
||||
|
||||
/* log print macro. default EF_PRINT macro is printf() */
|
||||
/* #define FDB_PRINT(...) my_printf(__VA_ARGS__) */
|
||||
|
||||
/* print debug information */
|
||||
// #define FDB_DEBUG_ENABLE
|
||||
|
||||
#endif /* _FDB_CFG_H_ */
|
||||
|
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2023, Armink, <armink.ztl@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Public definition.
|
||||
*/
|
||||
|
||||
#ifndef _FDB_DEF_H_
|
||||
#define _FDB_DEF_H_
|
||||
|
||||
#ifdef FDB_USING_NATIVE_ASSERT
|
||||
#include <assert.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/* software version number */
|
||||
#define FDB_SW_VERSION "2.1.1"
|
||||
#define FDB_SW_VERSION_NUM 0x20101
|
||||
|
||||
/* the KV max name length must less then it */
|
||||
#ifndef FDB_KV_NAME_MAX
|
||||
#define FDB_KV_NAME_MAX 64
|
||||
#endif
|
||||
|
||||
/* the KV cache table size, it will improve KV search speed when using cache */
|
||||
#ifndef FDB_KV_CACHE_TABLE_SIZE
|
||||
#define FDB_KV_CACHE_TABLE_SIZE 64
|
||||
#endif
|
||||
|
||||
/* the sector cache table size, it will improve KV save speed when using cache */
|
||||
#ifndef FDB_SECTOR_CACHE_TABLE_SIZE
|
||||
#define FDB_SECTOR_CACHE_TABLE_SIZE 8
|
||||
#endif
|
||||
|
||||
#if (FDB_KV_CACHE_TABLE_SIZE > 0) && (FDB_SECTOR_CACHE_TABLE_SIZE > 0)
|
||||
#define FDB_KV_USING_CACHE
|
||||
#endif
|
||||
|
||||
#if defined(FDB_USING_FILE_LIBC_MODE) || defined(FDB_USING_FILE_POSIX_MODE)
|
||||
#define FDB_USING_FILE_MODE
|
||||
#endif
|
||||
|
||||
/* the file cache table size, it will improve GC speed in file mode when using cache */
|
||||
#ifndef FDB_FILE_CACHE_TABLE_SIZE
|
||||
#define FDB_FILE_CACHE_TABLE_SIZE 4
|
||||
#endif
|
||||
|
||||
#ifndef FDB_WRITE_GRAN
|
||||
#define FDB_WRITE_GRAN 1
|
||||
#endif
|
||||
|
||||
/* log function. default FDB_PRINT macro is printf() */
|
||||
// #ifndef FDB_PRINT
|
||||
// #define FDB_PRINT(...) printf(__VA_ARGS__)
|
||||
// #endif
|
||||
#ifndef FDB_PRINT
|
||||
#define FDB_PRINT(...)
|
||||
#endif
|
||||
#define FDB_LOG_PREFIX1() FDB_PRINT("[FlashDB]" FDB_LOG_TAG)
|
||||
#define FDB_LOG_PREFIX2() FDB_PRINT(" ")
|
||||
#define FDB_LOG_PREFIX() \
|
||||
FDB_LOG_PREFIX1(); \
|
||||
FDB_LOG_PREFIX2()
|
||||
#ifdef FDB_DEBUG_ENABLE
|
||||
#define FDB_DEBUG(...) \
|
||||
FDB_LOG_PREFIX(); \
|
||||
FDB_PRINT("(%s:%d) ", __FILE__, __LINE__); \
|
||||
FDB_PRINT(__VA_ARGS__)
|
||||
#else
|
||||
#define FDB_DEBUG(...)
|
||||
#endif
|
||||
/* routine print function. Must be implement by user. */
|
||||
#define FDB_INFO(...) \
|
||||
FDB_LOG_PREFIX(); \
|
||||
FDB_PRINT(__VA_ARGS__)
|
||||
/* assert for developer. */
|
||||
#ifdef FDB_USING_NATIVE_ASSERT
|
||||
#define FDB_ASSERT(EXPR) assert(EXPR);
|
||||
#else
|
||||
#ifndef FDB_ASSERT
|
||||
#define FDB_ASSERT(EXPR) \
|
||||
if (!(EXPR)) \
|
||||
{ \
|
||||
FDB_INFO("(%s) has assert failed at %s.\n", #EXPR, __func__); \
|
||||
while (1) \
|
||||
; \
|
||||
}
|
||||
#endif /* FDB_ASSERT */
|
||||
#endif /* FDB_USING_NATIVE_ASSERT */
|
||||
|
||||
#define FDB_KVDB_CTRL_SET_SEC_SIZE 0x00 /**< set sector size control command, this change MUST before database initialization */
|
||||
#define FDB_KVDB_CTRL_GET_SEC_SIZE 0x01 /**< get sector size control command */
|
||||
#define FDB_KVDB_CTRL_SET_LOCK 0x02 /**< set lock function control command */
|
||||
#define FDB_KVDB_CTRL_SET_UNLOCK 0x03 /**< set unlock function control command */
|
||||
#define FDB_KVDB_CTRL_SET_FILE_MODE 0x09 /**< set file mode control command, this change MUST before database initialization */
|
||||
#define FDB_KVDB_CTRL_SET_MAX_SIZE 0x0A /**< set database max size in file mode control command, this change MUST before database initialization */
|
||||
#define FDB_KVDB_CTRL_SET_NOT_FORMAT 0x0B /**< set database NOT format mode control command, this change MUST before database initialization */
|
||||
|
||||
#define FDB_TSDB_CTRL_SET_SEC_SIZE 0x00 /**< set sector size control command, this change MUST before database initialization */
|
||||
#define FDB_TSDB_CTRL_GET_SEC_SIZE 0x01 /**< get sector size control command */
|
||||
#define FDB_TSDB_CTRL_SET_LOCK 0x02 /**< set lock function control command */
|
||||
#define FDB_TSDB_CTRL_SET_UNLOCK 0x03 /**< set unlock function control command */
|
||||
#define FDB_TSDB_CTRL_SET_ROLLOVER 0x04 /**< set rollover control command, this change MUST after database initialization */
|
||||
#define FDB_TSDB_CTRL_GET_ROLLOVER 0x05 /**< get rollover control command */
|
||||
#define FDB_TSDB_CTRL_GET_LAST_TIME 0x06 /**< get last save time control command */
|
||||
#define FDB_TSDB_CTRL_SET_FILE_MODE 0x09 /**< set file mode control command, this change MUST before database initialization */
|
||||
#define FDB_TSDB_CTRL_SET_MAX_SIZE 0x0A /**< set database max size in file mode control command, this change MUST before database initialization */
|
||||
#define FDB_TSDB_CTRL_SET_NOT_FORMAT 0x0B /**< set database NOT formatable mode control command, this change MUST before database initialization */
|
||||
|
||||
#ifdef FDB_USING_TIMESTAMP_64BIT
|
||||
typedef int64_t fdb_time_t;
|
||||
#else
|
||||
typedef int32_t fdb_time_t;
|
||||
#endif /* FDB_USING_TIMESTAMP_64BIT */
|
||||
|
||||
typedef fdb_time_t (*fdb_get_time)(void);
|
||||
|
||||
struct fdb_default_kv_node
|
||||
{
|
||||
char *key;
|
||||
void *value;
|
||||
size_t value_len;
|
||||
};
|
||||
|
||||
struct fdb_default_kv
|
||||
{
|
||||
struct fdb_default_kv_node *kvs;
|
||||
size_t num;
|
||||
};
|
||||
|
||||
/* error code */
|
||||
typedef enum
|
||||
{
|
||||
FDB_NO_ERR,
|
||||
FDB_ERASE_ERR,
|
||||
FDB_READ_ERR,
|
||||
FDB_WRITE_ERR,
|
||||
FDB_PART_NOT_FOUND,
|
||||
FDB_KV_NAME_ERR,
|
||||
FDB_KV_NAME_EXIST,
|
||||
FDB_SAVED_FULL,
|
||||
FDB_INIT_FAILED,
|
||||
} fdb_err_t;
|
||||
|
||||
enum fdb_kv_status
|
||||
{
|
||||
FDB_KV_UNUSED,
|
||||
FDB_KV_PRE_WRITE,
|
||||
FDB_KV_WRITE,
|
||||
FDB_KV_PRE_DELETE,
|
||||
FDB_KV_DELETED,
|
||||
FDB_KV_ERR_HDR,
|
||||
#define FDB_KV_STATUS_NUM 6
|
||||
};
|
||||
typedef enum fdb_kv_status fdb_kv_status_t;
|
||||
|
||||
enum fdb_tsl_status
|
||||
{
|
||||
FDB_TSL_UNUSED,
|
||||
FDB_TSL_PRE_WRITE,
|
||||
FDB_TSL_WRITE,
|
||||
FDB_TSL_USER_STATUS1,
|
||||
FDB_TSL_DELETED,
|
||||
FDB_TSL_USER_STATUS2,
|
||||
#define FDB_TSL_STATUS_NUM 6
|
||||
};
|
||||
typedef enum fdb_tsl_status fdb_tsl_status_t;
|
||||
|
||||
/* key-value node object */
|
||||
struct fdb_kv
|
||||
{
|
||||
fdb_kv_status_t status; /**< node status, @see fdb_kv_status_t */
|
||||
bool crc_is_ok; /**< node CRC32 check is OK */
|
||||
uint8_t name_len; /**< name length */
|
||||
uint32_t magic; /**< magic word(`K`, `V`, `4`, `0`) */
|
||||
uint32_t len; /**< node total length (header + name + value), must align by FDB_WRITE_GRAN */
|
||||
uint32_t value_len; /**< value length */
|
||||
char name[FDB_KV_NAME_MAX]; /**< name */
|
||||
struct
|
||||
{
|
||||
uint32_t start; /**< node start address */
|
||||
uint32_t value; /**< value start address */
|
||||
} addr;
|
||||
};
|
||||
typedef struct fdb_kv *fdb_kv_t;
|
||||
|
||||
struct fdb_kv_iterator
|
||||
{
|
||||
struct fdb_kv curr_kv; /**< Current KV we get from the iterator */
|
||||
uint32_t iterated_cnt; /**< How many KVs have we iterated already */
|
||||
size_t iterated_obj_bytes; /**< Total storage size of KVs we have iterated. */
|
||||
size_t iterated_value_bytes; /**< Total value size of KVs we have iterated. */
|
||||
uint32_t sector_addr; /**< Current sector address we're iterating. DO NOT touch it. */
|
||||
uint32_t traversed_len; /**< Traversed sector total length. */
|
||||
};
|
||||
typedef struct fdb_kv_iterator *fdb_kv_iterator_t;
|
||||
|
||||
/* time series log node object */
|
||||
struct fdb_tsl
|
||||
{
|
||||
fdb_tsl_status_t status; /**< node status, @see fdb_log_status_t */
|
||||
fdb_time_t time; /**< node timestamp */
|
||||
uint32_t log_len; /**< log length, must align by FDB_WRITE_GRAN */
|
||||
struct
|
||||
{
|
||||
uint32_t index; /**< node index address */
|
||||
uint32_t log; /**< log data address */
|
||||
} addr;
|
||||
};
|
||||
typedef struct fdb_tsl *fdb_tsl_t;
|
||||
typedef bool (*fdb_tsl_cb)(fdb_tsl_t tsl, void *arg);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FDB_DB_TYPE_KV,
|
||||
FDB_DB_TYPE_TS,
|
||||
} fdb_db_type;
|
||||
|
||||
/* the flash sector store status */
|
||||
enum fdb_sector_store_status
|
||||
{
|
||||
FDB_SECTOR_STORE_UNUSED,
|
||||
FDB_SECTOR_STORE_EMPTY,
|
||||
FDB_SECTOR_STORE_USING,
|
||||
FDB_SECTOR_STORE_FULL,
|
||||
#define FDB_SECTOR_STORE_STATUS_NUM 4
|
||||
};
|
||||
typedef enum fdb_sector_store_status fdb_sector_store_status_t;
|
||||
|
||||
/* the flash sector dirty status */
|
||||
enum fdb_sector_dirty_status
|
||||
{
|
||||
FDB_SECTOR_DIRTY_UNUSED,
|
||||
FDB_SECTOR_DIRTY_FALSE,
|
||||
FDB_SECTOR_DIRTY_TRUE,
|
||||
FDB_SECTOR_DIRTY_GC,
|
||||
#define FDB_SECTOR_DIRTY_STATUS_NUM 4
|
||||
};
|
||||
typedef enum fdb_sector_dirty_status fdb_sector_dirty_status_t;
|
||||
|
||||
/* KVDB section information */
|
||||
struct kvdb_sec_info
|
||||
{
|
||||
bool check_ok; /**< sector header check is OK */
|
||||
struct
|
||||
{
|
||||
fdb_sector_store_status_t store; /**< sector store status @see fdb_sector_store_status_t */
|
||||
fdb_sector_dirty_status_t dirty; /**< sector dirty status @see sector_dirty_status_t */
|
||||
} status;
|
||||
uint32_t addr; /**< sector start address */
|
||||
uint32_t magic; /**< magic word(`E`, `F`, `4`, `0`) */
|
||||
uint32_t combined; /**< the combined next sector number, 0xFFFFFFFF: not combined */
|
||||
size_t remain; /**< remain size */
|
||||
uint32_t empty_kv; /**< the next empty KV node start address */
|
||||
};
|
||||
typedef struct kvdb_sec_info *kv_sec_info_t;
|
||||
|
||||
/* TSDB section information */
|
||||
struct tsdb_sec_info
|
||||
{
|
||||
bool check_ok; /**< sector header check is OK */
|
||||
fdb_sector_store_status_t status; /**< sector store status @see fdb_sector_store_status_t */
|
||||
uint32_t addr; /**< sector start address */
|
||||
uint32_t magic; /**< magic word(`T`, `S`, `L`, `0`) */
|
||||
fdb_time_t start_time; /**< the first start node's timestamp, 0xFFFFFFFF: unused */
|
||||
fdb_time_t end_time; /**< the last end node's timestamp, 0xFFFFFFFF: unused */
|
||||
uint32_t end_idx; /**< the last end node's index, 0xFFFFFFFF: unused */
|
||||
fdb_tsl_status_t end_info_stat[2]; /**< the last end node's info status */
|
||||
size_t remain; /**< remain size */
|
||||
uint32_t empty_idx; /**< the next empty node index address */
|
||||
uint32_t empty_data; /**< the next empty node's data end address */
|
||||
};
|
||||
typedef struct tsdb_sec_info *tsdb_sec_info_t;
|
||||
|
||||
struct kv_cache_node
|
||||
{
|
||||
uint16_t name_crc; /**< KV name's CRC32 low 16bit value */
|
||||
uint16_t active; /**< KV node access active degree */
|
||||
uint32_t addr; /**< KV node address */
|
||||
};
|
||||
typedef struct kv_cache_node *kv_cache_node_t;
|
||||
|
||||
/* database structure */
|
||||
typedef struct fdb_db *fdb_db_t;
|
||||
struct fdb_db
|
||||
{
|
||||
const char *name; /**< database name */
|
||||
fdb_db_type type; /**< database type */
|
||||
union
|
||||
{
|
||||
#ifdef FDB_USING_FAL_MODE
|
||||
const struct fal_partition *part; /**< flash partition for saving database */
|
||||
#endif
|
||||
#ifdef FDB_USING_FILE_MODE
|
||||
const char *dir; /**< directory path for saving database */
|
||||
#endif
|
||||
} storage;
|
||||
uint32_t sec_size; /**< flash section size. It's a multiple of block size */
|
||||
uint32_t max_size; /**< database max size. It's a multiple of section size */
|
||||
uint32_t oldest_addr; /**< the oldest sector start address */
|
||||
bool init_ok; /**< initialized successfully */
|
||||
bool file_mode; /**< is file mode, default is false */
|
||||
bool not_formatable; /**< is can NOT be formated mode, default is false */
|
||||
#ifdef FDB_USING_FILE_MODE
|
||||
uint32_t cur_file_sec[FDB_FILE_CACHE_TABLE_SIZE]; /**< last operate sector address */
|
||||
#if defined(FDB_USING_FILE_POSIX_MODE)
|
||||
int cur_file[FDB_FILE_CACHE_TABLE_SIZE]; /**< current file object */
|
||||
#elif defined(FDB_USING_FILE_LIBC_MODE)
|
||||
FILE *cur_file[FDB_FILE_CACHE_TABLE_SIZE]; /**< current file object */
|
||||
#endif /* FDB_USING_FILE_MODE */
|
||||
uint32_t cur_sec; /**< current operate sector address */
|
||||
#endif
|
||||
void (*lock)(fdb_db_t db); /**< lock the database operate */
|
||||
void (*unlock)(fdb_db_t db); /**< unlock the database operate */
|
||||
|
||||
void *user_data;
|
||||
};
|
||||
|
||||
/* KVDB structure */
|
||||
struct fdb_kvdb
|
||||
{
|
||||
struct fdb_db parent; /**< inherit from fdb_db */
|
||||
struct fdb_default_kv default_kvs; /**< default KV */
|
||||
bool gc_request; /**< request a GC check */
|
||||
bool in_recovery_check; /**< is in recovery check status when first reboot */
|
||||
struct fdb_kv cur_kv;
|
||||
struct kvdb_sec_info cur_sector;
|
||||
bool last_is_complete_del;
|
||||
|
||||
#ifdef FDB_KV_USING_CACHE
|
||||
/* KV cache table */
|
||||
struct kv_cache_node kv_cache_table[FDB_KV_CACHE_TABLE_SIZE];
|
||||
/* sector cache table, it caching the sector info which status is current using */
|
||||
struct kvdb_sec_info sector_cache_table[FDB_SECTOR_CACHE_TABLE_SIZE];
|
||||
#endif /* FDB_KV_USING_CACHE */
|
||||
|
||||
#ifdef FDB_KV_AUTO_UPDATE
|
||||
uint32_t ver_num; /**< setting version number for update */
|
||||
#endif
|
||||
|
||||
void *user_data;
|
||||
};
|
||||
typedef struct fdb_kvdb *fdb_kvdb_t;
|
||||
|
||||
/* TSDB structure */
|
||||
struct fdb_tsdb
|
||||
{
|
||||
struct fdb_db parent; /**< inherit from fdb_db */
|
||||
struct tsdb_sec_info cur_sec; /**< current using sector */
|
||||
fdb_time_t last_time; /**< last TSL timestamp */
|
||||
fdb_get_time get_time; /**< the current timestamp get function */
|
||||
size_t max_len; /**< the maximum length of each log */
|
||||
bool rollover; /**< the oldest data will rollover by newest data, default is true */
|
||||
|
||||
void *user_data;
|
||||
};
|
||||
typedef struct fdb_tsdb *fdb_tsdb_t;
|
||||
|
||||
/* blob structure */
|
||||
struct fdb_blob
|
||||
{
|
||||
void *buf; /**< blob data buffer */
|
||||
size_t size; /**< blob data buffer size */
|
||||
struct
|
||||
{
|
||||
uint32_t meta_addr; /**< saved KV or TSL index address */
|
||||
uint32_t addr; /**< blob data saved address */
|
||||
size_t len; /**< blob data saved length */
|
||||
} saved;
|
||||
};
|
||||
typedef struct fdb_blob *fdb_blob_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* _FDB_DEF_H_ */
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Armink, <armink.ztl@gmail.com>
|
||||
* Copyright (c) 2020, enkiller, <462747508@qq.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <flashdb.h>
|
||||
#include <fdb_low_lvl.h>
|
||||
|
||||
#define FDB_LOG_TAG "[file]"
|
||||
|
||||
#ifdef FDB_USING_FILE_MODE
|
||||
|
||||
#define DB_PATH_MAX 256
|
||||
|
||||
static void get_db_file_path(fdb_db_t db, uint32_t addr, char *path, size_t size)
|
||||
{
|
||||
#define DB_NAME_MAX 8
|
||||
|
||||
/* from db_name.fdb.0 to db_name.fdb.n */
|
||||
char file_name[DB_NAME_MAX + 4 + 10];
|
||||
uint32_t sec_addr = FDB_ALIGN_DOWN(addr, db->sec_size);
|
||||
int index = sec_addr / db->sec_size;
|
||||
|
||||
snprintf(file_name, sizeof(file_name), "%.*s.fdb.%d", DB_NAME_MAX, db->name, index);
|
||||
if (strlen(db->storage.dir) + 1 + strlen(file_name) >= size) {
|
||||
/* path is too long */
|
||||
FDB_INFO("Error: db (%s) file path (%s) is too log.\n", file_name, db->storage.dir);
|
||||
FDB_ASSERT(0)
|
||||
}
|
||||
snprintf(path, size, "%s/%s", db->storage.dir, file_name);
|
||||
}
|
||||
|
||||
#if defined(FDB_USING_FILE_POSIX_MODE)
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#if !defined(_MSC_VER)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static int get_file_from_cache(fdb_db_t db, uint32_t sec_addr)
|
||||
{
|
||||
for (int i = 0; i < FDB_FILE_CACHE_TABLE_SIZE; i++) {
|
||||
if (db->cur_file_sec[i] == sec_addr)
|
||||
return db->cur_file[i];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void update_file_cache(fdb_db_t db, uint32_t sec_addr, int fd)
|
||||
{
|
||||
int free_index = FDB_FILE_CACHE_TABLE_SIZE;
|
||||
|
||||
for (int i = 0; i < FDB_FILE_CACHE_TABLE_SIZE; i++) {
|
||||
if (db->cur_file_sec[i] == sec_addr) {
|
||||
db->cur_file[i] = fd;
|
||||
return;
|
||||
} else if (db->cur_file[i] == -1) {
|
||||
free_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (fd > 0) {
|
||||
if (free_index < FDB_FILE_CACHE_TABLE_SIZE) {
|
||||
db->cur_file[free_index] = fd;
|
||||
db->cur_file_sec[free_index] = sec_addr;
|
||||
} else {
|
||||
/* cache is full, move to end */
|
||||
for (int i = FDB_FILE_CACHE_TABLE_SIZE - 1; i > 0; i--) {
|
||||
close(db->cur_file[i]);
|
||||
memcpy(&db->cur_file[i], &db->cur_file[i - 1], sizeof(db->cur_file[0]));
|
||||
memcpy(&db->cur_file_sec[i], &db->cur_file_sec[i - 1], sizeof(db->cur_file_sec[0]));
|
||||
}
|
||||
/* add to head */
|
||||
db->cur_file[0] = fd;
|
||||
db->cur_file_sec[0] = sec_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int open_db_file(fdb_db_t db, uint32_t addr, bool clean)
|
||||
{
|
||||
uint32_t sec_addr = FDB_ALIGN_DOWN(addr, db->sec_size);
|
||||
int fd = get_file_from_cache(db, sec_addr);
|
||||
char path[DB_PATH_MAX];
|
||||
|
||||
if (fd <= 0 || clean) {
|
||||
get_db_file_path(db, addr, path, DB_PATH_MAX);
|
||||
|
||||
if (fd > 0) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
update_file_cache(db, sec_addr, fd);
|
||||
}
|
||||
if (clean) {
|
||||
/* clean the old file */
|
||||
int clean_fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0777);
|
||||
if (clean_fd <= 0) {
|
||||
FDB_INFO("Error: open (%s) file failed.\n", path);
|
||||
}
|
||||
else {
|
||||
close(clean_fd);
|
||||
clean_fd = -1;
|
||||
}
|
||||
}
|
||||
if (get_file_from_cache(db, sec_addr) < 0) {
|
||||
/* open the database file */
|
||||
fd = open(path, O_RDWR, 0777);
|
||||
update_file_cache(db, sec_addr, fd);
|
||||
}
|
||||
db->cur_sec = sec_addr;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
fdb_err_t _fdb_file_read(fdb_db_t db, uint32_t addr, void *buf, size_t size)
|
||||
{
|
||||
fdb_err_t result = FDB_NO_ERR;
|
||||
int fd = open_db_file(db, addr, false);
|
||||
if (fd > 0) {
|
||||
/* get the offset address is relative to the start of the current file */
|
||||
addr = addr % db->sec_size;
|
||||
|
||||
if ((lseek(fd, addr, SEEK_SET) != (int32_t)addr) || (read(fd, buf, size) != (ssize_t)size))
|
||||
result = FDB_READ_ERR;
|
||||
} else {
|
||||
result = FDB_READ_ERR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fdb_err_t _fdb_file_write(fdb_db_t db, uint32_t addr, const void *buf, size_t size, bool sync)
|
||||
{
|
||||
fdb_err_t result = FDB_NO_ERR;
|
||||
int fd = open_db_file(db, addr, false);
|
||||
if (fd > 0) {
|
||||
/* get the offset address is relative to the start of the current file */
|
||||
addr = addr % db->sec_size;
|
||||
|
||||
if ((lseek(fd, addr, SEEK_SET) != (int32_t)addr) || (write(fd, buf, size) != (ssize_t)size))
|
||||
result = FDB_WRITE_ERR;
|
||||
if(sync) {
|
||||
fsync(fd);
|
||||
}
|
||||
} else {
|
||||
result = FDB_WRITE_ERR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fdb_err_t _fdb_file_erase(fdb_db_t db, uint32_t addr, size_t size)
|
||||
{
|
||||
fdb_err_t result = FDB_NO_ERR;
|
||||
int fd = open_db_file(db, addr, true);
|
||||
if (fd > 0) {
|
||||
#define BUF_SIZE 32
|
||||
uint8_t buf[BUF_SIZE];
|
||||
size_t i;
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
for (i = 0; i * BUF_SIZE < size; i++)
|
||||
{
|
||||
memset(buf, 0xFF, BUF_SIZE);
|
||||
write(fd, buf, BUF_SIZE);
|
||||
}
|
||||
memset(buf, 0xFF, BUF_SIZE);
|
||||
write(fd, buf, size - i * BUF_SIZE);
|
||||
fsync(fd);
|
||||
} else {
|
||||
result = FDB_ERASE_ERR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#elif defined(FDB_USING_FILE_LIBC_MODE)
|
||||
|
||||
static FILE *get_file_from_cache(fdb_db_t db, uint32_t sec_addr)
|
||||
{
|
||||
for (int i = 0; i < FDB_FILE_CACHE_TABLE_SIZE; i++) {
|
||||
if (db->cur_file_sec[i] == sec_addr)
|
||||
return db->cur_file[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void update_file_cache(fdb_db_t db, uint32_t sec_addr, FILE *fd)
|
||||
{
|
||||
int free_index = FDB_FILE_CACHE_TABLE_SIZE;
|
||||
|
||||
for (int i = 0; i < FDB_FILE_CACHE_TABLE_SIZE; i++) {
|
||||
if (db->cur_file_sec[i] == sec_addr) {
|
||||
db->cur_file[i] = fd;
|
||||
return;
|
||||
}
|
||||
else if (db->cur_file[i] == 0) {
|
||||
free_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (fd) {
|
||||
if (free_index < FDB_FILE_CACHE_TABLE_SIZE) {
|
||||
db->cur_file[free_index] = fd;
|
||||
db->cur_file_sec[free_index] = sec_addr;
|
||||
}
|
||||
else {
|
||||
/* cache is full, move to end */
|
||||
for (int i = FDB_FILE_CACHE_TABLE_SIZE - 1; i > 0; i--) {
|
||||
fclose(db->cur_file[i]);
|
||||
memcpy(&db->cur_file[i], &db->cur_file[i - 1], sizeof(db->cur_file[0]));
|
||||
memcpy(&db->cur_file_sec[i], &db->cur_file_sec[i - 1], sizeof(db->cur_file_sec[0]));
|
||||
}
|
||||
/* add to head */
|
||||
db->cur_file[0] = fd;
|
||||
db->cur_file_sec[0] = sec_addr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static FILE *open_db_file(fdb_db_t db, uint32_t addr, bool clean)
|
||||
{
|
||||
uint32_t sec_addr = FDB_ALIGN_DOWN(addr, db->sec_size);
|
||||
FILE *fd = get_file_from_cache(db, sec_addr);
|
||||
char path[DB_PATH_MAX];
|
||||
|
||||
if (fd == NULL || clean) {
|
||||
get_db_file_path(db, addr, path, DB_PATH_MAX);
|
||||
|
||||
if (fd) {
|
||||
fclose(fd);
|
||||
fd = NULL;
|
||||
update_file_cache(db, sec_addr, fd);
|
||||
}
|
||||
|
||||
if (clean) {
|
||||
/* clean the old file */
|
||||
FILE *clean_fd = fopen(path, "wb+");
|
||||
if (clean_fd == NULL) {
|
||||
FDB_INFO("Error: open (%s) file failed.\n", path);
|
||||
} else {
|
||||
fclose(clean_fd);
|
||||
clean_fd = NULL;
|
||||
}
|
||||
}
|
||||
if (get_file_from_cache(db, sec_addr) == NULL) {
|
||||
/* open the database file */
|
||||
fd = fopen(path, "rb+");
|
||||
update_file_cache(db, sec_addr, fd);
|
||||
}
|
||||
db->cur_sec = sec_addr;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
fdb_err_t _fdb_file_read(fdb_db_t db, uint32_t addr, void *buf, size_t size)
|
||||
{
|
||||
fdb_err_t result = FDB_NO_ERR;
|
||||
FILE *fp = open_db_file(db, addr, false);
|
||||
if (fp) {
|
||||
addr = addr % db->sec_size;
|
||||
if ((fseek(fp, addr, SEEK_SET) != 0) || (fread(buf, size, 1, fp) != size))
|
||||
result = FDB_READ_ERR;
|
||||
} else {
|
||||
result = FDB_READ_ERR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
fdb_err_t _fdb_file_write(fdb_db_t db, uint32_t addr, const void *buf, size_t size, bool sync)
|
||||
{
|
||||
fdb_err_t result = FDB_NO_ERR;
|
||||
FILE *fp = open_db_file(db, addr, false);
|
||||
if (fp) {
|
||||
addr = addr % db->sec_size;
|
||||
if ((fseek(fp, addr, SEEK_SET) != 0) || (fwrite(buf, size, 1, fp) != size))
|
||||
result = FDB_READ_ERR;
|
||||
if(sync) {
|
||||
fflush(fp);
|
||||
}
|
||||
} else {
|
||||
result = FDB_READ_ERR;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fdb_err_t _fdb_file_erase(fdb_db_t db, uint32_t addr, size_t size)
|
||||
{
|
||||
fdb_err_t result = FDB_NO_ERR;
|
||||
|
||||
FILE *fp = open_db_file(db, addr, true);
|
||||
if (fp != NULL) {
|
||||
#define BUF_SIZE 32
|
||||
uint8_t buf[BUF_SIZE];
|
||||
size_t i;
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
for (i = 0; i * BUF_SIZE < size; i++)
|
||||
{
|
||||
memset(buf, 0xFF, BUF_SIZE);
|
||||
fwrite(buf, BUF_SIZE, 1, fp);
|
||||
}
|
||||
memset(buf, 0xFF, BUF_SIZE);
|
||||
fwrite(buf, size - i * BUF_SIZE, 1, fp);
|
||||
fflush(fp);
|
||||
} else {
|
||||
result = FDB_ERASE_ERR;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif /* defined(FDB_USING_FILE_LIBC_MODE) */
|
||||
|
||||
#endif /* FDB_USING_FILE_MODE */
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue