Files
VK035_Template/MDK-ARM/Core/App/adc.c

589 lines
23 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_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
}