Проект перенесен в папку MDK-ARM

This commit is contained in:
2025-12-28 15:38:30 +03:00
parent 8b930ebe12
commit c63d98f431
75 changed files with 110 additions and 64 deletions

588
MDK-ARM/Core/App/adc.c Normal file
View File

@@ -0,0 +1,588 @@
/**
******************************************************************************
* @file adc.c
* @author Разваляев Алексей
* @brief Драйвер ADC на основе PLIB035.
* Этот файл содержит:
* + Инициализацию ADC и аналогового модуля (AM)
* + Управление секвенсорами (SEQ0, SEQ1):
* - Инициализацию и конфигурацию секвенсоров
* - Запуск и остановку преобразований с буфером
* - Обработку прерываний секвенсоров
* - Установку callback-функций для событий:
* - завершение секвенсора
* - половина буфера
* - полный буфера
* - ошибка
* + Управление цифровыми компараторами (DC0-DC3):
* - Инициализацию и конфигурацию компараторов
* - Запуск и остановку компараторов
* - Обработку прерываний компараторов
* - Установку callback-функций для событий:
* - срабатывание компаратора
* - ошибка
* + Функции для работы с каналами ADC
* + Программный запуск преобразований
*
******************************************************************************
* @attention
*
* Использование этого драйвера предполагает наличие корректных настроек:
* - Определены конфигурационные структуры adc_seqx_config и adc_dcx_config
* в periph_config.h
* - Настроено тактирование ADC через RCU
* - Для использования прерываний - включены соответствующие NVIC IRQ
*
******************************************************************************
* @verbatim
==============================================================================
##### Как использовать этот драйвер #####
==============================================================================
-------------------------- Общие функции АЦП --------------------------------
1. Инициализация АЦП:
(+) adc_init_first() - обязательный вызов перед использованием АЦП
(+) Настраивает тактирование, GPIO и ожидает готовности аналогового модуля
2. Калибровка каналов:
(+) ADC_Channel_Calibr(&hadc, channel, OFFSET, GAIN) - калибровка смещения и усиления
(+) Формула калибровки: Dc = Dr * (4096+GAIN)/4096 + OFFSET
3. Чтение значений каналов:
(+) ADC_Channel_GetValue(&hadc, channel) - получение текущего значения канала
(+) Данные обновляются автоматически при работе секвенсоров
4. Инициализация GPIO:
(+) adc_gpio_init(hadc.ChannelEnable) - настраивает пины выбранных каналов
(+) Вызывается автоматически в adc_init_first()
------------------------ Секвенсоры (SEQ0, SEQ1) ----------------------------
1. Настройка в periph_config.h:
(+) Определите adc_seq0_config, adc_seq1_config
(+) Настройте ADC_ClockSource и ADC_ClockMHz для тактирования АЦП
(+) Определите adc_ch_config для включения нужных каналов
2. Инициализация секвенсоров:
(+) adc_seq_init(&hadc, ADC_SEQ_Num_0, &adc_seq0_config) - инициализация SEQ0
(+) Аналогично для SEQ1 при необходимости
3. Работа с секвенсорами:
(+) ADC_SEQ_Start(&hadc, ADC_SEQ_Num_0, buffer, buffer_size) - запуск с буфером
(+) ADC_SEQ_Stop(&hadc, ADC_SEQ_Num_0) - остановка секвенсора
(+) ADC_SEQ_Set_Callback(&hadc, ADC_SEQ_Num_0, тип, func) - установка обработчика
(+) ADC_SEQ_SoftwareStart() - программный запуск преобразования
4. Обработка прерываний секвенсоров:
(+) ADC_SEQ0_IRQHandler - вызвать adc_seq_irq_handler(&hadc, ADC_SEQ_Num_0)
(+) ADC_SEQ1_IRQHandler - вызвать adc_seq_irq_handler(&hadc, ADC_SEQ_Num_1)
------------------------ Цифровые компараторы (DC) --------------------------
==============================================================================
##### Особенности работы #####
==============================================================================
-------------------------- Общие особенности --------------------------------
- Частота ADC:
- Задается через ADC_ClockMHz в periph_config.h
- Автоматически настраивается делитель от источника тактирования
- Аналоговый модуль (AM):
- Требует времени для старта после включения (до 1 мс)
- adc_init_first() ожидает готовности AM с таймаутом 1 секунда
- При неудаче вызывает Error_Handler()
- Калибровка:
- OFFSET: смещение в диапазоне [-255, 255] квантов АЦП
- GAIN: коэффициент усиления в диапазоне [-255, 255] квантов АЦП (4096+GAIN)
- Калибровка применяется аппаратно для каждого канала отдельно
------------------------ Секвенсоры (SEQ0, SEQ1) ----------------------------
- Буферизация данных:
- Данные в буфере хранятся в формате [канал][время]
- При buffer_size=100 для 2 каналов буфер будет 200 элементов
- Буфер может быть кольцевым (BufferCircular=ENABLE)
- Прерывания секвенсоров:
- Генерируются после заданного количества рестартов (ITCount)
- ITCount=0 означает прерывание после каждого прохода секвенсора
- Данные автоматически читаются из FIFO в прерывании
- Работа с усреднением:
- Аппаратное усреднение измерения:
- 2, 4, 8, 16, 32 или 64 выборок (SEQ_Init.ReqAverage)
- Включается через SEQ_Init.ReqAverageEn
- Усредненные значения помещаются в FIFO как единый результат
- Аппаратное усреднение рестартов
- 2, 4, 8, 16, 32, 64, 128, 256 выборок (SER_Init.RestartCount + 1)
- Включается через SEQ_Init.RestartAverageEn
- Усредненные значения помещаются в FIFO как единый результат
- Таймер рестартов:
- Период в тиках ADC_ClockMHz/2 (драйвер автоматически делит на 2)
- Используется для периодического запуска секвенсора
- При RestartTimer=0 рестарт происходит немедленно
- Чтение FIFO:
- FIFO буфер на 32 элемента автоматически читается в прерывании
- Данные распределяются по каналам согласно последовательности в Req[0..3]
- При переполнении FIFO (>32 элементов до прерывания) возможна потеря данных
------------------------ Цифровые компараторы (DC) --------------------------
@endverbatim
******************************************************************************
*/
//-- Includes ------------------------------------------------------------------
#include "periph_config.h"
ADC_HandleTypeDef hadc; /*!< Хендл ADC */
//-- Private function prototypes -----------------------------------------------
static void __adc_seq_fifo_read(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num);
static int __adc_seq_calc_fifo_load(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num);
//-- Defines -------------------------------------------------------------------
//-- ADC Init functions --------------------------------------------------------
/**
* @brief Первичная инициализация ADC
* @details Настройка ADC и хендла: тактирования, прерывания
* и секвенсоры с компараторами
*/
void adc_init_first(void)
{
#if (USE_ADC_SEQ0==1) || (USE_ADC_SEQ1==1) || (USE_ADC_DC0==1) || (USE_ADC_DC1==1) || (USE_ADC_DC2==1) || (USE_ADC_DC3==1)
// Настройка тактирования
if(rcu_set_clock_adc(ADC_ClockSource, ADC_ClockMHz, ENABLE) != OK)
{
Error_Handler();
}
RCU_ADCRstCmd(ENABLE);
// Включаем аналоговый модуль
ADC_AM_Cmd(ENABLE);
// Настройка пинов для ADC
hadc.ChannelEnable = &adc_ch_config;
adc_gpio_init(hadc.ChannelEnable);
hadc.Instance = ADC;
#endif
#if (USE_ADC_SEQ0==1)
adc_seq_init(&hadc, ADC_SEQ_Num_0, &adc_seq0_config);
if(hadc.SEQ[ADC_SEQ_Num_0].Config->IT == ENABLE)
{
NVIC_EnableIRQ(ADC_SEQ0_IRQn);
}
#endif
#if (USE_ADC_SEQ1==1)
adc_seq_init(&hadc, ADC_SEQ_Num_1, &adc_seq1_config);
if(hadc.SEQ[ADC_SEQ_Num_1].Config->IT == ENABLE)
{
NVIC_EnableIRQ(ADC_SEQ1_IRQn);
}
#endif
#if (USE_ADC_SEQ0==1) || (USE_ADC_SEQ1==1) || (USE_ADC_DC0==1) || (USE_ADC_DC1==1) || (USE_ADC_DC2==1) || (USE_ADC_DC3==1)
uint32_t starttick = millis();
while (!ADC_AM_ReadyStatus()) {
if((millis() - starttick) > 1000)
Error_Handler();
}
#endif
}
/**
* @brief Записать калибровочные значения канала
* @param hadc указатель на хендл АЦП
* @param channel номер канала
* @param OFFSET Коэффициент корректировки смещения нуля АЦП в квантах [-255:255]
* @param GAIN Коэффициент корректировки усиления зн АЦП в квантах [-255:255]
* @retval значение ADC (0 если данные невалидны)
* @details Результат преобразования передается на схему коррекции, которая нивелирует
* ошибку усиления и смещения нуля и работа которой описывается формулой
* Dc = Dr * (4096+GAIN)/4096 + OFFSET
*/
OperationStatus ADC_Channel_Calibr(ADC_HandleTypeDef *hadc, ADC_CH_Num_TypeDef channel, int OFFSET, int GAIN)
{
if (!hadc || (int)channel >= ADC_CH_Total)
return ERROR;
ADC_CH_SetOffsetTrim(channel, OFFSET);
ADC_CH_SetGainTrim(channel, GAIN);
return OK;
}
/**
* @brief Получение текущего значения канала
* @param hadc указатель на хендл АЦП
* @param channel номер канала
* @retval значение ADC (0 если данные невалидны)
*/
uint16_t ADC_Channel_GetValue(ADC_HandleTypeDef *hadc, ADC_CH_Num_TypeDef channel)
{
if (!hadc || (int)channel >= ADC_CH_Total)
return 0;
return hadc->ChannelData[channel];
}
//-- ADC Sequencers API functions ----------------------------------------------
/**
* @brief Инициализация секвенсора АЦП
* @param hadc указатель на хендл АЦП
* @param SEQ_Num номер секвенсора
* @param NewConfig указатель на новую конфигурацию ADC, иначе используется та, что в структуре
* @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
*/
OperationStatus adc_seq_init(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num, ADC_SEQ_ExtInit_TypeDef *NewConfig)
{
if(!hadc || !hadc->Instance)
return ERROR;
ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
if(NewConfig != NULL)
{
hseq->Config = NewConfig;
}
if(hseq->Config == NULL)
{
return ERROR;
}
ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
if(conf->SEQ_Init.RestartTimer) // не значю почему но этот таймер работает в 2 раза медленее чем AdcClk
{
conf->SEQ_Init.RestartTimer = (conf->SEQ_Init.RestartTimer+1)/2-1; // поэтому дополнительно делим всё на два чтобы работало
}
if(__adc_seq_calc_fifo_load(hadc, SEQ_Num) < 0)
return ERROR;
ADC_SEQ_Init(SEQ_Num, &hseq->Config->SEQ_Init);
return OK;
}
/**
* @brief Установка коллбека секвенсора АЦП
* @param hadc указатель на хендл АЦП
* @param CallbackType Тип коллбека
* @param Callback Функция коллбека
* @retval void
*/
OperationStatus ADC_SEQ_Set_Callback(ADC_HandleTypeDef* hadc, ADC_SEQ_Num_TypeDef SEQ_Num, ADC_CallbackTypeDef CallbackType, void (*Callback)(void))
{
if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
return ERROR;
ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
switch(CallbackType)
{
case ADC_Callback_SeqCplt:
hseq->Config->SEQCpltCallback = Callback;
break;
case ADC_Callback_Error:
hseq->Config->ErrorCallback = Callback;
break;
case ADC_Callback_BuffFull:
hseq->Config->BuffFullCallback = Callback;
break;
case ADC_Callback_BuffHalf:
hseq->Config->BuffFullCallback = Callback;
break;
default:
return ERROR;
}
return OK;
}
/**
* @brief Запуск секвенсора АЦП с буфером
* @param hadc указатель на хендл АЦП
* @param SEQ_Num номер секвенсора
* @param data_buffer указатель на буфер данных [ch][buffer_size] или NULL
* @param buffer_size размер буфера для каждого канала (0 если буфер не используется)
* @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
*/
OperationStatus ADC_SEQ_Start(ADC_HandleTypeDef *hadc,
ADC_SEQ_Num_TypeDef SEQ_Num,
uint16_t (*data_buffer)[],
uint32_t buffer_size)
{
if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
return ERROR;
ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
// Сохраняем буферные параметры
hseq->data_buffer = (uint16_t *)data_buffer;
hseq->buffer_size = buffer_size;
hseq->buffer_count = 0; // Сбрасываем счетчик
// DMA > IT
if(conf->SEQ_Init.DMAEn == ENABLE)
{
conf->IT = DISABLE;
}
// Настраиваем прерывания если нужно
if (conf->IT == ENABLE)
{
uint32_t it_real_cnt = (uint32_t)(conf->ITCount+1)*(conf->SEQ_Init.ReqMax+1);
ADC_SEQ_ITConfig(SEQ_Num, it_real_cnt-1, DISABLE);
ADC_SEQ_ITCmd(SEQ_Num, ENABLE);
}
// Включаем секвенсор
ADC_SEQ_Cmd(SEQ_Num, ENABLE);
return OK;
}
/**
* @brief Остановка секвенсора АЦП
* @param hadc указатель на хендл АЦП
* @param SEQ_Num номер секвенсора
* @retval OperationStatus OK - если всё хорошо, ERROR - если ошибка
*/
OperationStatus ADC_SEQ_Stop(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{
if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
return ERROR;
// ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
// Выключаем секвенсор
ADC_SEQ_Cmd(SEQ_Num, DISABLE);
// Выключаем прерывания
ADC_SEQ_ITCmd(SEQ_Num, DISABLE);
return OK;
}
/**
* @brief Программный запуск преобразования
*/
void ADC_SEQ_SoftwareStart(void)
{
ADC_SEQ_SwStartCmd();
}
/**
* @brief Обработчик прерываний секвенсора
* @param hadc указатель на хендл АЦП
* @param SEQ_Num номер секвенсора
* @retval void
*/
void adc_seq_irq_handler(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{
if (!hadc || !hadc->Instance || !hadc->SEQ[SEQ_Num].Config)
return;
// GPIO_SetBits(GPIOA, GPIO_Pin_7);
ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
// Проверяем флаг прерывания
if (ADC_SEQ_ITMaskedStatus(SEQ_Num) == SET)
{
// Очищаем флаг
ADC_SEQ_ITStatusClear(SEQ_Num);
__adc_seq_fifo_read(hadc, SEQ_Num);
}
// Обработка ошибок
if (ADC_SEQ_DMAErrorStatus(SEQ_Num) == SET)
{
ADC_SEQ_DMAErrorStatusClear(SEQ_Num);
if (conf->ErrorCallback)
{
conf->ErrorCallback();
}
}
// GPIO_ClearBits(GPIOA, GPIO_Pin_7);
}
/**
* @brief Внутренняя функция для расчета загрузки FIFO буфера
* @param hadc указатель на хендл АЦП
* @param SEQ_Num номер секвенсора
* @retval Кол-во данных FIFO буфера, или -1 если переполнение FIFO
* @details Рассчитывает сколько данных накопится в fifo буфере за одно прерывание
*/
static int __adc_seq_calc_fifo_load(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{
ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
int numb_of_data = 0;
uint32_t numb_of_req = ((uint32_t)conf->SEQ_Init.ReqMax+1);
uint32_t it_cnt = ((uint32_t)conf->ITCount+1);
if(conf->SEQ_Init.RestartAverageEn)
{
numb_of_data = numb_of_req;
}
else
{
numb_of_data = it_cnt*numb_of_req;
if(numb_of_data > 32) // Максимальный размер FIFO - 32 элемента, поэтому если слишком многшо данных за время работы перед прерыванием - то ошибка
return -1;
}
return numb_of_data;
}
/**
* @brief Внутренняя функция для чтения данных из FIFO
* @param hadc указатель на хендл АЦП
* @param SEQ_Num номер секвенсора
* @retval void
* @details Автоматически достает данные из FIFO и складывает их в заданный буфер
и в структуру hadc
*/
static void __adc_seq_fifo_read(ADC_HandleTypeDef *hadc, ADC_SEQ_Num_TypeDef SEQ_Num)
{
ADC_SEQ_HandleTypeDef *hseq = &hadc->SEQ[SEQ_Num];
_ADC_SEQ_TypeDef *SEQx = &hadc->Instance->SEQ[SEQ_Num];
ADC_SEQ_ExtInit_TypeDef *conf = hseq->Config;
// Последний запрос в секвенсоре
uint32_t last_request = SEQx->SRQCTL_bit.RQMAX;
uint32_t numb_of_request = last_request+1;
// Читаем все доступные данные из FIFO
uint32_t fifo_size = ADC_SEQ_GetFIFOLoad(SEQ_Num);
uint32_t req_num = ((int)ADC_SEQ_GetReqCurrent(SEQ_Num)-fifo_size)%(numb_of_request); // первая дата в fifo будет (следующий запрос - количество данных в буфере)
while (ADC_SEQ_GetFIFOLoad(SEQ_Num))
{
uint32_t data = ADC_SEQ_GetFIFOData(SEQ_Num);
if(ADC_SEQ_GetFIFOLoad(SEQ_Num) == 0)
{
__NOP();
}
if ((int)req_num < ADC_SEQ_Req_Total)
{
ADC_CH_Num_TypeDef channel = conf->SEQ_Init.Req[req_num];
if ((int)channel < ADC_CH_Total)
{
// 1. Обновляем текущее значение
hadc->ChannelData[channel] = (uint16_t)data;
// 2. Записываем в буфер если он есть
if (hseq->data_buffer &&
hseq->buffer_size > 0)
{
// Вычисляем offset для [ch][buffer_size]
uint32_t offset = req_num * hseq->buffer_size +
hseq->buffer_count;
hseq->data_buffer[offset] = (uint16_t)data;
}
}
// Если это последний канал в секвенсоре - увеличиваем счетчик буфера
if (req_num == last_request)
{
hseq->buffer_count++;
if(hseq->buffer_count == hseq->buffer_size/2)
{
if (conf->BuffHalfCallback)
{
conf->BuffHalfCallback();
}
}
if(hseq->buffer_count >= hseq->buffer_size)
{
// Кольцевой буфер
if(conf->BufferCircular)
hseq->buffer_count = 0;
else
ADC_SEQ_Stop(hadc, SEQ_Num);
if (conf->BuffFullCallback)
{
conf->BuffFullCallback();
}
}
}
}
// Переход на Следующий канал
req_num = (req_num+1)%numb_of_request;
}
// Вызываем коллбек пользователя
if (conf->SEQCpltCallback)
{
conf->SEQCpltCallback();
}
}
//-- ADC Digital Comparators API functions -------------------------------------
//-- ADC GPIO functions --------------------------------------------------------
/**
* @brief Инициализация GPIO для ADC
*/
void adc_gpio_init(ADC_ChannelEnableTypeDef *ChEn)
{
#if (USE_ADC_SEQ0==1) || (USE_ADC_SEQ1==1) || (USE_ADC_DC0==1) || (USE_ADC_DC1==1) || (USE_ADC_DC2==1) || (USE_ADC_DC3==1)
// Получаем структуру
GPIO_Init_TypeDef *ch0_config = gpio_get_init(ADC_Ch0_GPIO_Port, ADC_Ch0_Pin);
GPIO_Init_TypeDef *ch1_config = gpio_get_init(ADC_Ch1_GPIO_Port, ADC_Ch1_Pin);
GPIO_Init_TypeDef *ch2_config = gpio_get_init(ADC_Ch2_GPIO_Port, ADC_Ch2_Pin);
GPIO_Init_TypeDef *ch3_config = gpio_get_init(ADC_Ch3_GPIO_Port, ADC_Ch3_Pin);
if(ChEn->Ch0)
{
GPIO_StructInit(ch0_config);
ch0_config->Pin = ADC_Ch0_Pin;
GPIO_Init(ADC_Ch0_GPIO_Port, ch0_config);
}
if(ChEn->Ch1)
{
GPIO_StructInit(ch1_config);
ch1_config->Pin = ADC_Ch1_Pin;
GPIO_Init(ADC_Ch1_GPIO_Port, ch1_config);
}
if(ChEn->Ch2)
{
GPIO_StructInit(ch2_config);
ch2_config->Pin = ADC_Ch2_Pin;
GPIO_Init(ADC_Ch2_GPIO_Port, ch2_config);
}
if(ChEn->Ch3)
{
GPIO_StructInit(ch3_config);
ch3_config->Pin = ADC_Ch3_Pin;
GPIO_Init(ADC_Ch3_GPIO_Port, ch3_config);
}
#endif
}