Files
VK035_Template/Core/App/adc.c
Razvalyaev f3e76e105a Добавлено:
- рефакторинг названий:
	- маленькие буквы - инит периферии
	- ТакойСтильФункций - API для использования
- сделаны шапки с инструкциями к функциями
- доработан ацп секвенсора
- доработан систем тики. можно настроить его на разные частоты и подключить коллбеки на разный период
- в gpio добавлены функции для кнопок и диодов
- генерация бинарника
2025-12-27 23:20:03 +03:00

485 lines
18 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
******************************************************************************
* @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:
a) Инициализация:
(+) adc_init_first() — обязательный вызов перед использованием ADC
b) Работа с каналами:
(+) ADC_Get_ChannelValue(&hadc, channel) — получение текущего значения канала
2. Секвенсоры (SEQ):
a) Настройка периферии (periph_config.h):
(+) Определить структуры ADC_SEQ_ExtInit_TypeDef для нужных секвенсоров:
adc_seq0_config, adc_seq1_config
(+) Настроить последовательность каналов, режимы работы
(+) Настроить прерывания (IT) и ITCount
(+) Определить callback-функции (можно NULL)
b) Инициализация:
(+) adc_init_first() — первичная настройка тактирования, сброс ADC, инициализация хендла hadc
(+) adc_seq_init(&hadc, ADC_SEQ_Num_0, &adc_seq0_config) — инициализация конкретного секвенсора
c) Callback-функции (опционально):
(+) ADC_SEQ_Set_Callback(&hadc, ADC_SEQ_Num_0, ADC_Callback_SeqCplt, Callback) — завершение секвенсора
(+) ADC_SEQ_Set_Callback(&hadc, ADC_SEQ_Num_0, ADC_Callback_BuffHalf, Callback) — половина буфера
(+) ADC_SEQ_Set_Callback(&hadc, ADC_SEQ_Num_0, ADC_Callback_BuffFull, Callback) — полный буфер
(+) ADC_SEQ_Set_Callback(&hadc, ADC_SEQ_Num_0, ADC_Callback_Error, Callback) — ошибки
d) Запуск и остановка:
(+) ADC_SEQ_Start(&hadc, ADC_SEQ_Num_0, data_buffer, buffer_size) — запуск с буфером
(+) ADC_SEQ_Stop(&hadc, ADC_SEQ_Num_0) — остановка секвенсора
e) Работа с данными:
(+) ADC_Get_ChannelValue(&hadc, channel) — чтение текущего значения канала из хендла
(+) ADC_SEQ_SoftwareStart() — программный запуск преобразования
f) Обработка прерываний:
(+) adc_seq_irq_handler(&hadc, ADC_SEQ_Num_0) — обработчик прерываний секвенсора
- В обработчике автоматически читаются данные из FIFO, обновляются каналы и буфер
- В обработчиках автоматически вызываются соответствующие callback-функции
и сбрасываются флаги
g) Особенности работы:
(+) Данные автоматически читаются из FIFO в прерывании
(+) Поддерживается кольцевой буфер (BufferCircular)
(+) Автоматический вызов callback при заполнении половины/всего буфера
3. Цифровые компараторы (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);
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_DC0==1)
#endif
#if (USE_ADC_DC1==1)
#endif
#if (USE_ADC_DC2==1)
#endif
#if (USE_ADC_DC3==1)
#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
}
//-- 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 Получение текущего значения канала
* @param hadc указатель на хендл АЦП
* @param channel номер канала
* @retval значение ADC (0 если данные невалидны)
*/
uint16_t ADC_Get_ChannelValue(ADC_HandleTypeDef *hadc, ADC_CH_Num_TypeDef channel)
{
if (!hadc || (int)channel >= ADC_CH_Total)
return 0;
return hadc->ChannelData[channel];
}
/**
* @brief Программный запуск преобразования
*/
void ADC_SEQ_SoftwareStart(void)
{
ADC_SEQ_SwStartCmd();
}
int itcnt;
/**
* @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);
itcnt++;
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 -------------------------------------