/** ****************************************************************************** * @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 }