SPL: настройка и работа с АЦП

 Краткое описание: Для работы с этим примером необходимы плата расширения 1 и Индикатор (t°C). Подключаем индикационную плату Индикатор (t°C) к плате CodeIN через плату расширения 1 рисунок 1. 

     Задача: Требуется написать с использованием библиотеки StdPeriph код. Программа должна производить настойку аппаратного ADC, DMA и SPI. ADC по ножке PA0 подключить к Датчику LMT88. Значения от ADC перевести в показания температуры с помощью формулы приведенной в документации на датчик. Так же необходимо реализовать фильтр скользящего среднего, для усреднения значений температуры. На индикационную плату Индикатор (t°C) вывести отфильтрованные показания температуры. 

Рисунок 1

     Содержимое файла main.c

#include "stm32f30x_gpio.h"
#include "stm32f30x_spi.h"
#include "stm32f30x_dma.h"
#include "stm32f30x_adc.h"
#include "stm32f30x_rcc.h"
#include "stm32f30x_tim.h"
#include "stm32f30x_misc.h"
#include "stm32f30x.h"
#include "math.h"
#include "stm32f30x_syscfg.h"

GPIO_InitTypeDef gpio; //Объявляем переменную gpio типа GPIO_InitTypeDef
SPI_InitTypeDef spi; //Объявляем переменную spi типа SPI_InitTypeDef
DMA_InitTypeDef dma; //Объявляем переменную dma типа DMA_InitTypeDef
ADC_InitTypeDef adc; //Объявляем переменную adc типа ADC_InitTypeDef
ADC_CommonInitTypeDef adc_common; //Объявляем переменную adc_common типа ADC_CommonInitTypeDef
TIM_TimeBaseInitTypeDef timer; //Объявляем переменную timer типа TIM_TimeBaseInitTypeDef

#define PRESCALER_TIM2 720 //Пределитель частоты для таймера 2
#define PERIOD_TIM2 1000 //Период таймера 10 миллисекунд (1 единица значения равна 0,01 миллисекунде (10 микросекунд))

//Значения для работы с индикатором
#define Zero 63 //Соответствует цифре <0> на индикаторе
#define One 6 //Соответствует цифре <1> на индикаторе
#define Two 91 //Соответствует цифре <2> на индикаторе
#define Three 79 //Соответствует цифре <3> на индикаторе
#define Four 102 //Соответствует цифре <4> на индикаторе
#define Five 109 //Соответствует цифре <5> на индикаторе
#define Six 125 //Соответствует цифре <6> на индикаторе
#define Seven 7 //Соответствует цифре <7> на индикаторе
#define Eight 127 //Соответствует цифре <8> на индикаторе
#define Nine 111 //Соответствует цифре <9> на индикаторе
#define Point 128 //Соответствует точке на индикаторе

//Значения для работы с индикационными светодиодами
#define Led_ALL_Good 1 //Соответствует зеленому цвету
#define Led_Warning 2 //Соответствует красному цвету

volatile uint32_t ADC_Result; //Переменная для хранения значений полученных от ADC
const short average_sample = 6; //Выборка среднего

int temp_calculated = 0; //Содержит расcчитанное значение температуры
int temp_filtered = 0; //Содержит отфильтрованные значения температуры
int new_temp_value = 0; //Содержит температуры для работы с фильтром

int Indikator_1 = Zero; //Присваиваем переменной индикатора 1 значение 0
int Indikator_2 = Zero; //Присваиваем переменной индикатора 2 значение 0
int Indikator_3 = Zero; //Присваиваем переменной индикатора 3 значение 0

int Led_conf = Led_ALL_Good; //Присваиваем переменной светодиода значение соответствующее зеленному цвету

int k = 0; //Переменная для условной операции изменения значения индикатора 1
int m = 0; //Переменная для условной операции изменения значения индикатора 2
int n = 0; //Переменная для условной операции изменения значения индикатора 3

void initAll()
{
    RCC_HSICmd(ENABLE); //Включаем тактирование от внутреннего источника
    RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI); //Подаем тактовый сигнал на системную шину

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //Включаем тактирование DMA
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_ADC12, ENABLE); //Включаем тактирование ADC1
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //Включаем тактирование SPI1
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //Тактирование таймера 2
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); //Включаем тактирование порта A
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE); //Включаем тактирование порта B

    gpio.GPIO_Mode = GPIO_Mode_OUT; //Выбираем режим работы пинов: ВЫХОД
    gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //Выбираем пин PB0( <LE> нужен для вывода данных на индикаторы после загрузки посылки в микросхему MBI), пин PB1 <nOE> Для включения микросхемы MBI
    GPIO_Init(GPIOB, &gpio); //Инициализируем структуру с настройками

    //Настраиваем пины SPI1 для работы в режиме альтернативной функции
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource5, GPIO_AF_5); //Будет использоваться как SCK
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_5); //Будет использоваться как SDO
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_5); //Будет использоваться как SDI

    gpio.GPIO_Mode = GPIO_Mode_AF; //Выбираем режим работы пинов: Альтернативная функция
    gpio.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; //Выбираем нужные пины
    GPIO_Init(GPIOA, &gpio); //Инициализируем структуру с настройками

    //Заполняем структуру с параметрами SPI модуля
    spi.SPI_Direction = SPI_Direction_1Line_Tx; //Полный дуплекс
    spi.SPI_DataSize = SPI_DataSize_8b; //Передаем по 8 бит
    spi.SPI_CPOL = SPI_CPOL_High; //Полярность и
    spi.SPI_CPHA = SPI_CPHA_2Edge; //Фаза тактового сигнала
    spi.SPI_NSS = SPI_NSS_Soft; //Управлять состоянием сигнала NSS программно
    spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; //Пределитель SCK
    spi.SPI_FirstBit = SPI_FirstBit_MSB; //Первым отправляется старший бит
    spi.SPI_Mode = SPI_Mode_Master; //Режим - мастер
    SPI_Init(SPI1, &spi); //Настраиваем SPI1
    SPI_Cmd(SPI1, ENABLE); //Включаем модуль SPI1....

    //Поскольку сигнал NSS контролируется программно, установим его в единицу
    //Если сбросить его в ноль, то наш SPI модуль подумает, что
    //у нас мультимастерная топология и его лишили полномочий мастера.
    SPI_NSSInternalSoftwareConfig(SPI1, SPI_NSSInternalSoft_Set);

    //Настраиваем пин на работу в режиме аналогового входа
    gpio.GPIO_Pin = GPIO_Pin_0; //Выбираем нужный пин с которого будим читать данные
    gpio.GPIO_Mode = GPIO_Mode_AN; //Выбираем режим работы пинa
    GPIO_Init(GPIOA, &gpio); //Инициализируем структуру с настройками

    DMA_DeInit(DMA1_Channel1); //Деинициализирует регистры DMA: настройки по умолчанию
    dma.DMA_PeripheralBaseAddr = (uint32_t) & (ADC1->DR); //Данные будем брать из регистра данных ADC1
    dma.DMA_MemoryBaseAddr = (uint32_t)&ADC_Result; //Переправлять данные будем в переменную ADC_Result
    dma.DMA_DIR = DMA_DIR_PeripheralSRC; //Передача данных из периферии в память
    dma.DMA_BufferSize = 1; //Размер буфера
    dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //Адрес источника данных не инкрементируем
    dma.DMA_MemoryInc = DMA_MemoryInc_Disable; //Аналогично, и с памятью
    dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //Настройки размера данных периферии
    dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //Настройки размера данных памяти
    dma.DMA_Mode = DMA_Mode_Circular; //Циклическое считывание каналов DMA
    dma.DMA_Priority = DMA_Priority_High; //Указывает приоритет для обработки каналов DMA
    dma.DMA_M2M = DMA_M2M_Disable; //Указывает, будет ли DMA использоваться в режиме передачи: память/память
    DMA_Init(DMA1_Channel1, &dma); //Настраиваем DMA
    DMA_Cmd(DMA1_Channel1, ENABLE); //Включаем первый канал DMA1

    RCC_ADCCLKConfig(RCC_ADC12PLLCLK_Div2); //Настраиваем тактирование ADC1

    ADC_StructInit(&adc); //Заполняем регистры настройки ADC1 значениями по умолчанию

    //Калибровка ADC1
    ADC_VoltageRegulatorCmd(ADC1, ENABLE); //Включаем/отключаем регулятор напряжения ADC1
    ADC_SelectCalibrationMode(ADC1, ADC_CalibrationMode_Single); //Выбираем режим калибровки ADC1
    ADC_StartCalibration(ADC1); //Запускаем процесс калибровки ADC1

    // Настраиваем непрерывные преобразования
    adc_common.ADC_Mode = ADC_Mode_Independent; //Настраиваем ADC для работы в независимом или режиме.
    adc_common.ADC_Clock = ADC_Clock_AsynClkMode; //Настраиваем Асинхронный режим тактирования
    adc_common.ADC_DMAAccessMode = ADC_DMAAccessMode_1; //Включен режим DMA для разрешения 12 и 10 бит
    adc_common.ADC_DMAMode = ADC_DMAMode_Circular; //Настраиваем режим передачи данных DMA от ADC1
    adc_common.ADC_TwoSamplingDelay = 0; //Указываем величину задержки между выборками.
    ADC_CommonInit(ADC1, &adc_common); //Настраиваем ADC1

    ADC_DMACmd(ADC1, ENABLE); //Включаем работу DMA через ADC1
    ADC_DMAConfig(ADC1, ADC_DMAMode_Circular); //Настраиваем режим запроса данных для DMA

    while (ADC_GetCalibrationStatus(ADC1) != RESET); //Получаем статус калибровки

    adc.ADC_ContinuousConvMode = ADC_ContinuousConvMode_Enable; //Режим одиночного/непрерывного преобразования
    adc.ADC_Resolution = ADC_Resolution_12b; //Настройка разрешения ADC1
    adc.ADC_ExternalTrigConvEvent = ADC_ExternalTrigConvEvent_0; //Внешние источники запуска ADC1 для преобразования обычных каналов
    adc.ADC_ExternalTrigEventEdge = ADC_ExternalTrigEventEdge_None; //Для обычного канала нет внешнего триггера преобразования
    adc.ADC_DataAlign = ADC_DataAlign_Right; //Выравнивание данных вправо
    adc.ADC_OverrunMode = ADC_OverrunMode_Disable; //Режим блокирования новых результатов если старые не считаны
    adc.ADC_AutoInjMode = ADC_AutoInjec_Disable; //Режим автоинжекции преобразований после Регулярной группы
    adc.ADC_NbrOfRegChannel = 1; //Количество каналов для преобразования
    ADC_Init(ADC1, &adc); //Настраиваем ADC1

    ADC_RegularChannelConfig(ADC1, 1, 1, ADC_SampleTime_1Cycles5); //Включаем третий канал первого модуля ADC1
    ADC_Cmd(ADC1, ENABLE); //Включаем АЦП

    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_RDY)); //Ожидаем флаг готовности ADC1
    ADC_StartConversion(ADC1); //Старт преобразований ADC1

    TIM_TimeBaseStructInit(&timer); //Инициализация структуры

    timer.TIM_Prescaler = PRESCALER_TIM2; //Выбор пределителя
    timer.TIM_Period = PERIOD_TIM2; //Период досчитав до которого произойдет прерывание
    TIM_TimeBaseInit(TIM2, &timer); //Инициализация выбранных значений

    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //Настраивает таймер на генерирования прерывания по переполнению
    NVIC_EnableIRQ(TIM2_IRQn); //Разрешаем соответствующие прерывания
    TIM_Cmd(TIM2, ENABLE); //Запускаем таймер
}

int main()
{

    initAll();
    ok_enable_irq(); //Разрешаются прерывания с линии IRQ на контроллере " disable_irq и enable_irq управляeт битом в регистре PRIMASK (запрет/разрешение)"

    GPIO_ResetBits(GPIOB, GPIO_Pin_1); //Включаем микросхему MBI (для этого подтягиваем ножку PB1 земле)

    while (1) {
    }
}

void TIM2_IRQHandler() //Функция прерывания по переполнению таймера 2
{
    TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //Функция очищает бит прерывания

    ADC_Result = ADC_GetConversionValue(ADC1); //Записываем данные от ADC в переменную ADC_Result

    new_temp_value = 0;

    temp_calculated = 0;

    temp_calculated = round(-1481.96 + (sqrt(2196200 + ((1.8639 - ((2.9 * ADC_Result) / 4096)) / 0.00000388)))); //Формула из документации на LMT88

    new_temp_value = temp_calculated; //Только что полученные данные температуры

    if (average_sample > 0) {
        temp_filtered = (temp_filtered * (average_sample - 1) + new_temp_value) / average_sample; //Усредняем значение температуры
    }

    else {
        temp_filtered = new_temp_value; //Оставляем значение температуры без изменений если коэффициент выборки среднего равен 0
    }

    //Выводим значения температуры на индикатор

    int a = temp_filtered;
    int b = a % 100;
    int n = (a - b) / 100;
    int k = b % 10;
    int m = (b - k) / 10;

    //Условия переключения значений индикатора 1
    if (k == 0) {
        Indikator_1 = Zero;
    }
    if (k == 1) {
        Indikator_1 = One;
    }
    if (k == 2) {
        Indikator_1 = Two;
    }
    if (k == 3) {
        Indikator_1 = Three;
    }
    if (k == 4) {
        Indikator_1 = Four;
    }
    if (k == 5) {
        Indikator_1 = Five;
    }
    if (k == 6) {
        Indikator_1 = Six;
    }
    if (k == 7) {
        Indikator_1 = Seven;
    }
    if (k == 8) {
        Indikator_1 = Eight;
    }
    if (k == 9) {
        Indikator_1 = Nine;
    }

    //Условия переключения значений индикатора 2
    if (m == 0) {
        Indikator_2 = Zero;
    }
    if (m == 1) {
        Indikator_2 = One;
    }
    if (m == 2) {
        Indikator_2 = Two;
    }
    if (m == 3) {
        Indikator_2 = Three;
    }
    if (m == 4) {
        Indikator_2 = Four;
    }
    if (m == 5) {
        Indikator_2 = Five;
    }
    if (m == 6) {
        Indikator_2 = Six;
    }
    if (m == 7) {
        Indikator_2 = Seven;
    }
    if (m == 8) {
        Indikator_2 = Eight;
    }
    if (m == 9) {
        Indikator_2 = Nine;
    }

    //Условия переключения значений индикатора 3
    if (n == 0) {
        Indikator_3 = Zero;
    }
    if (n == 1) {
        Indikator_3 = One;
    }
    if (n == 2) {
        Indikator_3 = Two;
    }
    if (n == 3) {
        Indikator_3 = Three;
    }
    if (n == 4) {
        Indikator_3 = Four;
    }
    if (n == 5) {
        Indikator_3 = Five;
    }
    if (n == 6) {
        Indikator_3 = Six;
    }
    if (n == 7) {
        Indikator_3 = Seven;
    }
    if (n == 8) {
        Indikator_3 = Eight;
    }
    if (n == 9) {
        Indikator_3 = Nine;
    }

    //Условия переключения цветов светодиода
    if (m < 5) Led_conf = Led_ALL_Good;
    if (m > 4) Led_conf = Led_Warning;

    GPIO_ResetBits(GPIOB, GPIO_Pin_0); //Сбрасываем LE

    SPI_SendData8(SPI1, Led_conf); //Передаем байт управляющий цветами светодиода
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); //Передатчик занят? значит ничего не делаем

    SPI_SendData8(SPI1, Indikator_1); //Передаем байт управляющий первым индикатором
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET) ; //Передатчик занят? значит ничего не делаем

    SPI_SendData8(SPI1, Indikator_2); //Передаем байт управляющий вторым индикатором
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); //Передатчик занят? значит ничего не делаем

    SPI_SendData8(SPI1, Indikator_3); //Передаем байт управляющий третьим индикатором
    while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) == SET); //Передатчик занят? значит ничего не делаем

    GPIO_SetBits(GPIOB, GPIO_Pin_0); //Устанавливаем LE для вывода переданных данных на индикаторы
}

     Скомпилируйте программу нажав кнопку Build, прошейте микроконтроллер, нажмите RESET. При запуске программы, произойдет считывание данных с датчика LMT88, вычисление температуры и фильтрация значений с последующим выводом на индикационную плату.

     ВНИМАНИЕ: Приведенный листинг кода не является единственной и оптимальной реализацией поставленной задачи. Пример опубликован с демонстрационной целью. Также автор во избежание переписывания чужих статей, пропускает теоретические основы необходимые для понимания примера, связывая это с тем что всю необходимую информацию можно найти в интернете.

Комментариев (0)

Написать комментарий

Имя *
E-mail
Введите комментарий *
Капча
33 + ? = 39