Лучший способ считывать данные с датчиков, которые не имеют выводов прерывания и требуют некоторого времени, прежде чем измерение будет готово - PullRequest
0 голосов
/ 25 апреля 2018

Я пытаюсь подключить датчик давления (MS5803-14BA) к моей плате (NUCLEO-STM32L073RZ).

В соответствии с таблицей (стр. 3), датчик давлениятребуется несколько миллисекунд, прежде чем измерение будет готово к считыванию.Для моего проекта меня заинтересует самое высокое разрешение, для преобразования необработанных данных которого требуется около 10 мс.

К сожалению, этот датчик давления не имеет никакого контакта прерывания, который можно использовать, чтобы увидеть, когдаизмерение готово, и поэтому я временно решил проблему, поставив задержку после запроса новых данных.

Мне не нравится мое текущее решение, поскольку в те 10 мс я мог заставить mcu работать на чем-тоиначе (у меня есть несколько других датчиков, прикрепленных к моей плате), но без какого-либо контакта прерывания, я не уверен, что является лучшим способом решения этой проблемы.

Другое решение пришло мне в голову: Использованиетаймер, который запускает каждое, скажем, 20 мс и выполняет следующие операции:

1.a Read the current value stored in the registers (discarding the first value)
1.b Ask for a new value

Таким образом, на следующей итерации мне просто нужно прочитать значение, запрошенное в конце предыдущей итерации.

Что мне не нравится, так это то, что моему измерению всегда будет 20 мс.До тех пор, пока задержка не будет составлять 20 мс, все равно будет в порядке, но если мне нужно будет уменьшить скорость, «возраст» чтения с моим решением увеличится.

Есть ли у вас какие-либо другие идеи о том, какиметь дело с этим?

Спасибо.

Примечание: Пожалуйста, дайте мне знать, если вам нужно будет увидеть мою текущую реализацию.

Ответы [ 2 ]

0 голосов
/ 28 апреля 2018

Прежде всего, спасибо за ваши предложения.Я пытался проанализировать каждое возможное решение, которое вы предложили.

Решение, предложенное Питером, показалось очень интересным, но я должен сказать, что, пройдя через таблицу несколько раз, я не думаю, что это осуществимо.Мое соображение основано на следующих фактах:

Используя область действия, я вижу, что подтверждение получено сразу после отправки команды для выполнения преобразования.См. Следующее изображение, касающееся преобразования температуры:

enter image description here

Мне кажется, что бит подтверждения сразу после команды совершенно ясен.После этого линия SDA (желтая) становится высокой, поэтому я не понимаю, как это возможно, чтобы использовать это для определения того, когда преобразование готово.

Что касается решения при использовании SPI, да,SDO остается низким во время преобразования, но я не могу его использовать: мне нужно придерживаться I2C.Кроме того, у меня есть другие датчики, подключенные к этой шине SPI, и я согласен с тем, что говорит Габриэль Стейплз.

После моего рассмотрения я остановился на решении, предложенном Габриэлем Стейплсом (учитывая, что для считывания значения давления ятакже необходимо прочитать и преобразовать температуру).

Мое текущее решение основано на автомате с 6 состояниями.В моем решении я различаю время ожидания для преобразования давления и время ожидания для преобразования температуры с идеей, что я мог бы попытаться увидеть, насколько ухудшается показание давления, если я использую менее точное показание температуры.

Вот мое текущее решение.Следующая функция вызывается внутри main while:

void MS5803_update()
{
  static uint32_t tStart; // us; start time

  switch (sensor_state)
  {
    case MS5803_REQUEST_TEMPERATURE:
    {
        MS5803_send_command(MS5803_CMD_ADC_CONV + TEMPERATURE + baro.resolution);
        tStart = HAL_GetTick();
        sensor_state = MS5803_WAIT_RAW_TEMPERATURE;
        break;
    }

    case MS5803_WAIT_RAW_TEMPERATURE:
    {
        uint32_t tNow = HAL_GetTick();
        if (tNow - tStart >= conversion_time)
        {
            sensor_state = MS5803_CONVERTING_TEMPERATURE;
        }
        break;
    }

    case MS5803_CONVERTING_TEMPERATURE:
    {
        MS5803_send_command(MS5803_CMD_ADC_READ);
        uint8_t raw_value[3]; // Read 24 bit
        MS5803_read_value(raw_value,3);
        temperature_raw = ((uint32_t)raw_value[0] << 16) + ((uint32_t)raw_value[1] << 8) + raw_value[2];
        sensor_state = MS5803_REQUEST_PRESSURE;
        break;
    }

    case MS5803_REQUEST_PRESSURE:
    {
        MS5803_send_command(MS5803_CMD_ADC_CONV + PRESSURE + baro.resolution);
        tStart = HAL_GetTick();
        sensor_state = MS5803_WAIT_RAW_PRESSURE;
        break;
    }

    case MS5803_WAIT_RAW_PRESSURE:
    {
        uint32_t tNow = HAL_GetTick();
        if (tNow - tStart >= conversion_time)
        {
            sensor_state = MS5803_CONVERTING_PRESSURE;
        }
        break;
    }

    case MS5803_CONVERTING_PRESSURE:
    {
        MS5803_send_command(MS5803_CMD_ADC_READ);
        uint8_t raw_value[3]; // Read 24 bit
        MS5803_read_value(raw_value,3);
        pressure_raw = ((uint32_t)raw_value[0] << 16) + ((uint32_t)raw_value[1] << 8) + raw_value[2];

        // Now I have both temperature and pressure raw and I can convert them
        MS5803_updateMeasurements();

        // Reset the state machine to perform a new measurement
        sensor_state = MS5803_REQUEST_TEMPERATURE;
        break;
    }
  }
}

Я не претендую на то, что мое решение лучше.Я просто отправляю это, чтобы узнать ваше мнение, ребята.Примечание: я все еще работаю над этим.Поэтому я не могу гарантировать отсутствие ошибок!

Для PeterJ_01: я мог бы согласиться, что это не просто учебный портал, но я считаю, что все здесь задают вопросы, чтобы узнать что-то новое или улучшить себя.Поэтому, если вы считаете, что решение, использующее ack, лучше, было бы здорово, если бы вы могли показать нам черновик вашей идеи.Для меня это было бы что-то новое для изучения.

Любые дальнейшие комментарии приветствуются.

0 голосов
/ 26 апреля 2018

Это не проблема «как прочитать датчик», это проблема «как сделать неблокирующую совместную многозадачность».Предполагая, что вы работаете на голом железе (без операционной системы, такой как FreeRTOS), у вас есть два хороших варианта.

Во-первых, таблица данных показывает, что вам нужно подождать до 9,04 мс или 9040 мкс.enter image description here

Теперь, вот ваши совместные варианты многозадачности:

  1. Отправьте команду, чтобы сообщить устройству выполнить преобразование АЦП.(то есть: провести аналоговое измерение), затем настройте аппаратный таймер, чтобы прервать вас ровно на 9040 минут позже.В вашем ISR вы можете либо установить флаг, чтобы сообщить основному циклу отправку команды чтения для чтения результата, либо вы можете просто отправить команду чтения прямо внутри ISR.

  2. Используйте неблокирующую кооперативную многозадачность на основе временных меток в основном цикле.Это, вероятно, потребует базового конечного автомата.Отправьте команду преобразования, затем продолжайте, занимаясь другими делами.Когда ваша отметка времени показывает, что она была достаточно длинной, отправьте команду чтения, чтобы прочитать преобразованный результат с датчика.

Номер 1 выше - мой предпочтительный подход для срочных задач.Однако это не является критичным по времени, и небольшое дрожание не будет иметь никакого значения, поэтому номер 2, указанный выше, является моим предпочтительным подходом для общей многозадачности, основанной на кооперации, так что давайте сделаем это.

Вот пример программы, демонстрирующей принцип совместной многозадачности на основе меток времени для конкретного случая, когда вам необходимо:

  1. запросить образец данных (запустите преобразование АЦП в вашем внешнем датчике)
  2. подождите 9040 долларов США для завершения преобразования
  3. считайте образец данных с вашего внешнего датчика (теперь, когда преобразование АЦП завершено)

Код:

enum sensorState_t 
{
    SENSOR_START_CONVERSION,
    SENSOR_WAIT,
    SENSOR_GET_CONVERSION
}

int main(void)
{
    doSetupStuff();
    configureHardwareTimer(); // required for getMicros() to work

    while (1)
    {
        //
        // COOPERATIVE TASK #1
        // Read the under-water pressure sensor as fast as permitted by the datasheet
        //
        static sensorState_t sensorState = SENSOR_START_CONVERSION; // initialize state machine
        static uint32_t task1_tStart; // us; start time
        static uint32_t sensorVal; // the sensor value you are trying to obtain 
        static bool newSensorVal = false; // set to true whenever a new value arrives
        switch (sensorState)
        {
            case SENSOR_START_CONVERSION:
            {
                startConversion(); // send command to sensor to start ADC conversion
                task1_tStart = getMicros(); // get a microsecond time stamp
                sensorState = SENSOR_WAIT; // next state 
                break;
            }
            case SENSOR_WAIT:
            {
                const uint32_t DESIRED_WAIT_TIME = 9040; // us
                uint32_t tNow = getMicros();
                if (tNow - task1_tStart >= DESIRED_WAIT_TIME)
                {
                    sensorState = SENSOR_GET_CONVERSION; // next state
                }
                break;
            }
            case SENSOR_GET_CONVERSION:
            {
                sensorVal = readConvertedResult(); // send command to read value from the sensor
                newSensorVal = true;
                sensorState = SENSOR_START_CONVERSION; // next state 
                break;
            }
        }

        //
        // COOPERATIVE TASK #2
        // use the under-water pressure sensor data right when it comes in (this will be an event-based task
        // whose running frequency depends on the rate of new data coming in, for example)
        //
        if (newSensorVal == true)
        {
            newSensorVal = false; // reset this flag 

            // use the sensorVal data here now for whatever you need it for
        }


        //
        // COOPERATIVE TASK #3
        //


        //
        // COOPERATIVE TASK #4
        //


        // etc etc

    } // end of while (1)
} // end of main

Еще один очень простой пример многозадачности на основе временных меток см. Пример Arduino "Blink Without Delay" здесь .

Вот пример того, как настроить таймер для использования в качестве генератора меток времени на микроконтроллере STM32F2.

Здесь показаны функции для configureHardwareTimer() и getMicros(), использованные выше:

// Timer handle to be used for Timer 2 below
TIM_HandleTypeDef TimHandle;

// Configure Timer 2 to be used as a free-running 32-bit hardware timer for general-purpose use as a 1-us-resolution
// timestamp source
void configureHardwareTimer()
{
    // Timer clock must be enabled before you can configure it
    __HAL_RCC_TIM2_CLK_ENABLE();

    // Calculate prescaler
    // Here are some references to show how this is done:
    // 1) "STM32Cube_FW_F2_V1.7.0/Projects/STM32F207ZG-Nucleo/Examples/TIM/TIM_OnePulse/Src/main.c" shows the
    //    following (slightly modified) equation on line 95: `Prescaler = (TIMxCLK/TIMx_counter_clock) - 1`
    // 2) "STM32F20x and STM32F21x Reference Manual" states the following on pg 419: "14.4.11 TIMx prescaler (TIMx_PSC)"
    //    "The counter clock frequency CK_CNT is equal to fCK_PSC / (PSC[15:0] + 1)"
    //    This means that TIMx_counter_clock_freq = TIMxCLK/(prescaler + 1). Now, solve for prescaler and you
    //    get the exact same equation as above: `prescaler = TIMxCLK/TIMx_counter_clock_freq - 1`
    // Calculating TIMxCLK:
    // - We must divide SystemCoreClock (returned by HAL_RCC_GetHCLKFreq()) by 2 because TIM2 uses clock APB1
    // as its clock source, and on my board this is configured to be 1/2 of the SystemCoreClock.
    // - Note: To know which clock source each peripheral and timer uses, you can look at
    //  "Table 25. Peripheral current consumption" in the datasheet, p86-88.
    const uint32_t DESIRED_TIMER_FREQ = 1e6; // 1 MHz clock freq --> 1 us pd per tick, which is what I want
    uint32_t Tim2Clk = HAL_RCC_GetHCLKFreq() / 2;
    uint32_t prescaler = Tim2Clk / DESIRED_TIMER_FREQ - 1; // Don't forget the minus 1!

    // Configure timer
    // TIM2 is a 32-bit timer; See datasheet "Table 4. Timer feature comparison", p30-31
    TimHandle.Instance               = TIM2;
    TimHandle.Init.Period            = 0xFFFFFFFF; // Set pd to max possible for a 32-bit timer
    TimHandle.Init.Prescaler         = prescaler;
    TimHandle.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;
    TimHandle.Init.CounterMode       = TIM_COUNTERMODE_UP;
    TimHandle.Init.RepetitionCounter = 0; // NA (has no significance) for this timer

    // Initialize the timer
    if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
    {
        // handle error condition
    }

    // Start the timer
    if (HAL_TIM_Base_Start(&TimHandle) != HAL_OK)
    {
        // handle error condition
    }
}

// Get the 1 us count value on Timer 2.
// This timer will be used for general purpose hardware timing that does NOT rely on interrupts.
// Therefore, the counter will continue to increment even with interrupts disabled.
// The count value increments every 1 microsecond.
// Since it is a 32-bit counter it overflows every 2^32 counts, which means the highest value it can
// store is 2^32 - 1 = 4294967295. Overflows occur every 2^32 counts / 1 count/us / 1e6us/sec
// = ~4294.97 sec = ~71.6 min.
uint32_t getMicros()
{
    return __HAL_TIM_GET_COUNTER(&TimHandle);
}
...