简体   繁体   English

从没有中断引脚的传感器读取数据的最佳方法,并且在测量准备好之前需要一些时间

[英]Best way to read from a sensor that doesn't have interrupt pin and requires some time before the measurement is ready

I'm trying to interface a pressure sensor (MS5803-14BA) with my board (NUCLEO-STM32L073RZ).我正在尝试将压力传感器 (MS5803-14BA) 与我的板 (NUCLEO-STM32L073RZ) 连接起来。

According to the datasheet (page 3), the pressure sensor requires some milliseconds before the measurement is ready to be read.根据数据表(第 3 页),压力传感器需要几毫秒才能准备好读取测量值。 For my project, I would be interested in the highest resolution that requires around 10 ms for the conversion of the raw data.对于我的项目,我对转换原始数据需要大约 10 毫秒的最高分辨率感兴趣。

Unfortunately, this pressure sensor doesn't have any interrupt pin that can be exploited to see when the measurement is ready, and therefore I temporarily solved the problem putting a delay after the request of new data.不幸的是,这个压力传感器没有任何中断引脚可以用来查看测量何时准备就绪,因此我暂时解决了在请求新数据后延迟的问题。

I don't like my current solution, since in those 10 ms I could put the mcu working on something else (I have several other sensors attached to my board), but without any interrupt pin, I'm not sure about what is the best way to solve this problem.我不喜欢我当前的解决方案,因为在这 10 毫秒内我可以让 mcu 处理其他事情(我的板上还有其他几个传感器),但是没有任何中断引脚,我不确定是什么解决这个问题的最好方法。

Another solution came into my mind: Using a timer that triggers every say 20 ms and performs the following operations:我想到了另一个解决方案:使用一个每隔 20 毫秒触发一次并执行以下操作的计时器:

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

In this way, at the next iteration I would just need to read the value requested at the end of the previous iteration.这样,在下一次迭代中,我只需要读取上一次迭代结束时请求的值。

What I don't like is that my measurement would be always 20 ms old.我不喜欢的是我的测量结果总是 20 毫秒。 Until the delay remains 20 ms, it should be still fine, but if I need to reduce the rate, the "age" of the reading with my solution would increase.直到延迟保持 20 毫秒,它应该仍然可以,但是如果我需要降低速率,使用我的解决方案读取的“年龄”会增加。

Do you have any other idea about how to deal with this?你对如何处理这个问题有任何其他想法吗?

Thank you.谢谢你。

Note: Please let me know if you would need to see my current implementation.注意:如果您需要查看我当前的实现,请告诉我。

This isn't a "how to read a sensor" problem, this is a "how to do non-blocking cooperative multi-tasking" problem.这不是“如何读取传感器”的问题,而是“如何进行非阻塞协作多任务”的问题。 Assuming you are running bare-metal (no operating system, such as FreeRTOS), you have two good options.假设您运行的是裸机(没有操作系统,例如 FreeRTOS),您有两个不错的选择。

First, the datasheet shows you need to wait up to 9.04 ms, or 9040 us.首先,数据表显示您最多需要等待 9.04 毫秒,即 9040 us。 在此处输入图片说明

Now, here are your cooperative multi-tasking options:现在,这是您的协作式多任务处理选项:

  1. Send a command to tell the device to do an ADC conversion (ie: to take an analog measurement), then configure a hardware timer to interrupt you exactly 9040 us later.发送命令告诉设备进行 ADC 转换(即:进行模拟测量),然后配置一个硬件定时器,在 9040 秒后准确地中断您。 In your ISR you then can either set a flag to tell your main loop to send a read command to read the result, OR you can just send the read command right inside the ISR.在您的 ISR 中,您可以设置一个标志来告诉您的主循环发送读取命令来读取结果,或者您可以直接在 ISR 内发送读取命令。

  2. Use non-blocking time-stamp-based cooperative multi-tasking in your main loop.在主循环中使用基于非阻塞时间戳的协作多任务处理。 This will likely require a basic state machine.这可能需要一个基本的状态机。 Send the conversion command, then move on, doing other things.发送转换命令,然后继续做其他事情。 When your time stamp indicates it's been long enough, send the read command to read the converted result from the sensor.当您的时间戳表明它已经足够长时,发送读取命令以从传感器读取转换结果。

Number 1 above is my preferred approach for time-critical tasks.上面的第 1 条是我处理时间紧迫任务的首选方法。 This isn't time-critical, however, and a little jitter won't make any difference, so Number 2 above is my preferred approach for general, bare-metal cooperative multi-tasking, so let's do that.然而,这不是时间紧迫的,一点点抖动也不会产生任何影响,所以上面的数字 2 是我首选的通用裸机协作多任务处理方法,所以让我们这样做。

Here's a sample program to demonstrate the principle of time-stamp-based bare-metal cooperative multi-tasking for your specific case where you need to:这是一个示例程序,用于演示基于时间戳的裸机协作多任务处理的原理,适用于您需要的特定情况:

  1. request a data sample (start ADC conversion in your external sensor)请求数据样本(在外部传感器中启动 ADC 转换)
  2. wait 9040 us for the conversion to complete等待 9040 us 以完成转换
  3. read in the data sample from your external sensor (now that the ADC conversion is complete)从外部传感器读取数据样本(现在 ADC 转换已完成)

Code:代码:

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

For another really simple timestamp-based multi-tasking example see Arduino's "Blink Without Delay" example here .对于另一个非常简单的基于时间戳的多任务示例,请参阅Arduino 的“无延迟闪烁”示例

General time-stamp-based bare-metal cooperative multi-tasking architecture notes:一般基于时间戳的裸机协同多任务架构注意事项:

Depending on how you do it all, in the end, you basically end up with this type of code layout, which simply runs each task at fixed time intervals.取决于你如何做这一切,最终,你基本上会得到这种类型的代码布局,它只是以固定的时间间隔运行每个任务。 Each task should be non-blocking to ensure it does not conflict with the run intervals of the other tasks.每个任务都应该是非阻塞的,以确保它不会与其他任务的运行间隔发生冲突。 Non-blocking on bare metal means simply "do not use clock-wasting delays, busy-loops, or other types of polling, repeating, counting, or busy delays!".裸机上的非阻塞意味着“不要使用浪费时钟的延迟、忙循环或其他类型的轮询、重复、计数或忙延迟!”。 (This is opposed to "blocking" on an operating-system-based (OS-based) system, which means "giving the clock back to the scheduler to let it run another thread while this task 'sleeps'." Remember: bare metal means no operating system !). (这与基于操作系统(OS-based)的系统上的“阻塞”相反,这意味着“将时钟返回给调度程序,让它在此任务“休眠”时运行另一个线程。”记住:裸机意味着没有操作系统!)。 Instead, if something isn't quite ready to run yet, simply save your state via a state machine, exit this task's code (this is the "cooperative" part, as your task must voluntarily give up the processor by returning), and let another task run!相反,如果某些东西还没有准备好运行,只需通过状态机保存您的状态,退出此任务的代码(这是“合作”部分,因为您的任务必须通过返回自愿放弃处理器),然后让另一个任务运行!

Here's the basic architecture, showing a simple timestamp-based way to get 3 Tasks to run at independent, fixed frequencies withOUT relying on any interrupts, and with minimal jitter , due to the thorough and methodical approach I take to check the timestamps and update the start time at each run time.这是基本架构,展示了一种简单的基于时间戳的方法,可以让 3 个任务以独立的固定频率运行,而不依赖于任何中断,并且抖动最小,这是由于我采用彻底和有条理的方法来检查时间戳并更新每次运行时的开始时间。

1st, the definition for the main() function and main loop: 1、 main()函数和主循环的定义:

int main(void)
{
    doSetupStuff();
    configureHardwareTimer();

    while (1)
    {
        doTask1();
        doTask2();
        doTask3();
    }
}

2nd, the definitions for the doTask() functions: 2、 doTask()函数的定义:

// Task 1: Let's run this one at 100 Hz (every 10ms)
void doTask1(void)
{
    const uint32_t DT_DESIRED_US = 10000; // 10000us = 10ms, or 100Hz run freq
    static uint32_t t_start_us = getMicros();
    uint32_t t_now_us = getMicros();
    uint32_t dt_us = t_now_us - t_start_us;

    // See if it's time to run this Task
    if (dt_us >= DT_DESIRED_US)
    {
        // 1. Add DT_DESIRED_US to t_start_us rather than setting t_start_us to t_now_us (which many 
        // people do) in order to ***avoid introducing artificial jitter into the timing!***
        t_start_us += DT_DESIRED_US;
        // 2. Handle edge case where it's already time to run again because just completing one of the main
        // "scheduler" loops in the main() function takes longer than DT_DESIRED_US; in other words, here
        // we are seeing that t_start_us is lagging too far behind (more than one DT_DESIRED_US time width
        // from t_now_us), so we are "fast-forwarding" t_start_us up to the point where it is exactly 
        // 1 DT_DESIRED_US time width back now, thereby causing this task to instantly run again the 
        // next time it is called (trying as hard as we can to run at the specified frequency) while 
        // at the same time protecting t_start_us from lagging farther and farther behind, as that would
        // eventually cause buggy and incorrect behavior when the (unsigned) timestamps start to roll over
        // back to zero.
        dt_us = t_now_us - t_start_us; // calculate new time delta with newly-updated t_start_us
        if (dt_us >= DT_DESIRED_US)
        {
            t_start_us = t_now_us - DT_DESIRED_US;
        }

        // PERFORM THIS TASK'S OPERATIONS HERE!

    }
}

// Task 2: Let's run this one at 1000 Hz (every 1ms)
void doTask2(void)
{
    const uint32_t DT_DESIRED_US = 1000; // 1000us = 1ms, or 1000Hz run freq
    static uint32_t t_start_us = getMicros();
    uint32_t t_now_us = getMicros();
    uint32_t dt_us = t_now_us - t_start_us;

    // See if it's time to run this Task
    if (dt_us >= DT_DESIRED_US)
    {
        t_start_us += DT_DESIRED_US;
        dt_us = t_now_us - t_start_us; // calculate new time delta with newly-updated t_start_us
        if (dt_us >= DT_DESIRED_US)
        {
            t_start_us = t_now_us - DT_DESIRED_US;
        }

        // PERFORM THIS TASK'S OPERATIONS HERE!

    }
}

// Task 3: Let's run this one at 10 Hz (every 100ms)
void doTask3(void)
{
    const uint32_t DT_DESIRED_US = 100000; // 100000us = 100ms, or 10Hz run freq
    static uint32_t t_start_us = getMicros();
    uint32_t t_now_us = getMicros();
    uint32_t dt_us = t_now_us - t_start_us;

    // See if it's time to run this Task
    if (dt_us >= DT_DESIRED_US)
    {
        t_start_us += DT_DESIRED_US;
        dt_us = t_now_us - t_start_us; // calculate new time delta with newly-updated t_start_us
        if (dt_us >= DT_DESIRED_US)
        {
            t_start_us = t_now_us - DT_DESIRED_US;
        }

        // PERFORM THIS TASK'S OPERATIONS HERE!

    }
}

The code above works perfectly but as you can see is pretty redundant and a bit irritating to set up new tasks.上面的代码运行良好,但正如您所见,它非常多余,并且设置新任务有点烦人。 This job can be a bit more automated and much much easier to do by simply defining a macro, CREATE_TASK_TIMER() , as follows, to do all of the redundant timing stuff and timestamp variable creation for us:通过简单地定义一个宏CREATE_TASK_TIMER() ,这项工作可以更加自动化,并且更容易完成,如下所示,为我们完成所有冗余的计时内容和时间戳变量创建:

/// @brief      A function-like macro to get a certain set of events to run at a desired, fixed 
///             interval period or frequency.
/// @details    This is a timestamp-based time polling technique frequently used in bare-metal
///             programming as a basic means of achieving cooperative multi-tasking. Note 
///             that getting the timing details right is difficult, hence one reason this macro 
///             is so useful. The other reason is that this maro significantly reduces the number of
///             lines of code you need to write to introduce a new timestamp-based cooperative
///             task. The technique used herein achieves a perfect desired period (or freq) 
///             on average, as it centers the jitter inherent in any polling technique around 
///             the desired time delta set-point, rather than always lagging as many other 
///             approaches do.
///             
///             USAGE EX:
///             ```
///             // Create a task timer to run at 500 Hz (every 2000 us, or 2 ms; 1/0.002 sec = 500 Hz)
///             const uint32_t PERIOD_US = 2000; // 2000 us pd --> 500 Hz freq
///             bool time_to_run;
///             CREATE_TASK_TIMER(PERIOD_US, time_to_run);
///             if (time_to_run)
///             {
///                 run_task_2();
///             }
///             ```
///
///             Source: Gabriel Staples 
///             https://stackoverflow.com/questions/50028821/best-way-to-read-from-a-sensors-that-doesnt-have-interrupt-pin-and-require-some/50032992#50032992
/// @param[in]  dt_desired_us   The desired delta time period, in microseconds; note: pd = 1/freq;
///                             the type must be `uint32_t`
/// @param[out] time_to_run     A `bool` whose scope will enter *into* the brace-based scope block
///                             below; used as an *output* flag to the caller: this variable will 
///                             be set to true if it is time to run your code, according to the 
///                             timestamps, and will be set to false otherwise
/// @return     NA--this is not a true function
#define CREATE_TASK_TIMER(dt_desired_us, time_to_run)                                                                  \
{ /* Use scoping braces to allow multiple calls of this macro all in one outer scope while */                          \
  /* allowing each variable created below to be treated as unique to its own scope */                                  \
    time_to_run = false;                                                                                               \
                                                                                                                       \
    /* set the desired run pd / freq */                                                                                \
    const uint32_t DT_DESIRED_US = dt_desired_us;                                                                      \
    static uint32_t t_start_us = getMicros();                                                                          \
    uint32_t t_now_us = getMicros();                                                                                   \
    uint32_t dt_us = t_now_us - t_start_us;                                                                            \
                                                                                                                       \
    /* See if it's time to run this Task */                                                                            \
    if (dt_us >= DT_DESIRED_US)                                                                                        \
    {                                                                                                                  \
        /* 1. Add DT_DESIRED_US to t_start_us rather than setting t_start_us to t_now_us (which many */                \
        /* people do) in order to ***avoid introducing artificial jitter into the timing!*** */                        \
        t_start_us += DT_DESIRED_US;                                                                                   \
        /* 2. Handle edge case where it's already time to run again because just completing one of the main */         \
        /* "scheduler" loops in the main() function takes longer than DT_DESIRED_US; in other words, here */           \
        /* we are seeing that t_start_us is lagging too far behind (more than one DT_DESIRED_US time width */          \
        /* from t_now_us), so we are "fast-forwarding" t_start_us up to the point where it is exactly */               \
        /* 1 DT_DESIRED_US time width back now, thereby causing this task to instantly run again the */                \
        /* next time it is called (trying as hard as we can to run at the specified frequency) while */                \
        /* at the same time protecting t_start_us from lagging farther and farther behind, as that would */            \
        /* eventually cause buggy and incorrect behavior when the (unsigned) timestamps start to roll over */          \
        /* back to zero. */                                                                                            \
        dt_us = t_now_us - t_start_us; /* calculate new time delta with newly-updated t_start_us */                    \
        if (dt_us >= DT_DESIRED_US)                                                                                    \
        {                                                                                                              \
            t_start_us = t_now_us - DT_DESIRED_US;                                                                     \
        }                                                                                                              \
                                                                                                                       \
        time_to_run = true;                                                                                            \
    }                                                                                                                  \
}

Now, there are multiple ways to use it, but for the sake of this demo, in order to keep the really clean main() loop code which looks like this:现在,有多种方法可以使用它,但为了这个演示,为了保持真正干净的main()循环代码,如下所示:

int main(void)
{
    doSetupStuff();
    configureHardwareTimer();

    while (1)
    {
        doTask1();
        doTask2();
        doTask3();
    }
}

Let's use the CREATE_TASK_TIMER() macro like this.让我们像这样使用CREATE_TASK_TIMER()宏。 As you can see, the code is now much cleaner and easier to set up a new task.如您所见,代码现在更清晰,更容易设置新任务。 This is my preferred approach, because it creates the really clean main loop shown just above, with just the various doTask() calls, which are also easy to write and maintain:这是我的首选方法,因为它创建了上面显示的非常干净的主循环,只有各种doTask()调用,这些调用也很容易编写和维护:

// Task 1: Let's run this one at 100 Hz (every 10ms, or 10000us)
void doTask1(void)
{
    bool time_to_run;
    const uint32_t DT_DESIRED_US = 10000; // 10000us = 10ms, or 100Hz run freq
    CREATE_TASK_TIMER(DT_DESIRED_US, time_to_run);
    if (time_to_run)
    {
        // PERFORM THIS TASK'S OPERATIONS HERE!
    }
}

// Task 2: Let's run this one at 1000 Hz (every 1ms)
void doTask2(void)
{
    bool time_to_run;
    const uint32_t DT_DESIRED_US = 1000; // 1000us = 1ms, or 1000Hz run freq
    CREATE_TASK_TIMER(DT_DESIRED_US, time_to_run);
    if (time_to_run)
    {
        // PERFORM THIS TASK'S OPERATIONS HERE!
    }
}

// Task 3: Let's run this one at 10 Hz (every 100ms)
void doTask3(void)
{
    bool time_to_run;
    const uint32_t DT_DESIRED_US = 100000; // 100000us = 100ms, or 10Hz run freq
    CREATE_TASK_TIMER(DT_DESIRED_US, time_to_run);
    if (time_to_run)
    {
        // PERFORM THIS TASK'S OPERATIONS HERE!
    }
}

Alternatively, however, you could structure the code more like this, which works equally as well and produces the same effect, just in a slightly different way:但是,或者,您可以更像这样构建代码,它同样工作并产生相同的效果,只是方式略有不同:

#include <stdbool.h>
#include <stdint.h>

#define TASK1_PD_US (10000)     // 10ms pd, or 100 Hz run freq 
#define TASK2_PD_US (1000)      // 1ms pd, or 1000 Hz run freq 
#define TASK3_PD_US (100000)    // 100ms pd, or 10 Hz run freq 

// Task 1: Let's run this one at 100 Hz (every 10ms, or 10000us)
void doTask1(void)
{
    // PERFORM THIS TASK'S OPERATIONS HERE!
}

// Task 2: Let's run this one at 1000 Hz (every 1ms)
void doTask2(void)
{
    // PERFORM THIS TASK'S OPERATIONS HERE!
}

// Task 3: Let's run this one at 10 Hz (every 100ms)
void doTask3(void)
{
    // PERFORM THIS TASK'S OPERATIONS HERE!
}

int main(void)
{
    doSetupStuff();
    configureHardwareTimer();

    while (1)
    {
        bool time_to_run;

        CREATE_TASK_TIMER(TASK1_PD_US, time_to_run);
        if (time_to_run)
        {
            doTask1();
        }

        CREATE_TASK_TIMER(TASK2_PD_US, time_to_run);
        if (time_to_run)
        {
            doTask2();
        }

        CREATE_TASK_TIMER(TASK3_PD_US, time_to_run);
        if (time_to_run)
        {
            doTask3();
        }
    }
}

Part of the art (and fun!) of embedded bare-metal microcontroller programming is the skill and ingenuity involved in deciding exactly how you want to interleave each task and get them to run together, all as though they were running in parallel.嵌入式裸机微控制器编程的艺术(和乐趣!)的一部分在于确定您希望如何交错每个任务并使它们一起运行,就好像它们并行运行一样所涉及的技巧和独创性。 Use one of the above formats as a starting point, and adapt to your particular circumstances.使用上述格式之一作为起点,并适应您的特定情况。 Message-passing can be added between tasks or between tasks and interrupts, tasks and a user, etc, as desired, and as required for your particular application.可以根据需要以及特定应用程序的需要在任务之间或任务与中断、任务与用户等之间添加消息传递。

Here's an example of how to configure a timer for use as a timestamp-generator on an STM32F2 microcontroller.以下是如何配置定时器以用作 STM32F2 微控制器上的时间戳生成器的示例。

This shows functions for configureHardwareTimer() and getMicros() , used above:这显示了上面使用的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);
}

References:参考:

  1. https://www.arduino.cc/en/tutorial/BlinkWithoutDelay https://www.arduino.cc/en/tutorial/BlinkWithoutDelay
  2. Doxygen: What's the right way to reference a parameter in Doxygen? Doxygen: 在 Doxygen 中引用参数的正确方法是什么?
  3. Enum-based error codes for error handling: Error handling in C code用于错误处理的基于枚举的错误代码: C 代码中的错误处理
  4. Other architectural styles in C, such as "object-based" C via opaque pointers: Opaque C structs: how should they be declared? C 中的其他架构风格,例如通过不透明指针的“基于对象的”C: 不透明的 C 结构:它们应该如何声明?

First of all thank you for your suggestions.首先感谢您的建议。 I tried to analyze every single possible solution you proposed.我试图分析你提出的每一个可能的解决方案。

The solution proposed by Peter seemed very interesting but I have to say that, after having gone through the datasheet several times, I don't believe that is feasible. Peter 提出的解决方案看起来很有趣,但我不得不说,在多次浏览数据表后,我认为这是不可行的。 My consideration is based on the following facts.我的考虑是基于以下事实。

Using a scope I see that the acknowledge is received right after sending the command for doing conversion.使用范围我看到在发送进行转换的命令后立即收到确认。 See following image concerning the temperature conversion:有关温度转换,请参见下图:

在此处输入图片说明

It seems quite clear to me the acknowledge bit right after the command.命令之后的确认位对我来说似乎很清楚。 After that the SDA line (yellow) goes high, therefore I don't see how it is possible that I can exploit that for detecting when the conversion is ready.在那之后,SDA 线(黄色)变高,因此我看不出如何利用它来检测转换何时准备就绪。

Concerning the solution when using SPI, yes, the SDO remains low during the conversion, but I cannot use it: I need to stick with I2C.关于使用 SPI 时的解决方案,是的,SDO 在转换过程中保持低电平,但我无法使用它:我需要坚持使用 I2C。 Furthermore, I have other sensors attached to that SPI bus and I agree with what Gabriel Staples says.此外,我在该 SPI 总线上连接了其他传感器,我同意 Gabriel Staples 所说的。

After my consideration I went for the solution proposed by Gabriel Staples (considering that, in order to read pressure value, I also need to read and convert temperature).经过我的考虑,我选择了 Gabriel Staples 提出的解决方案(考虑到,为了读取压力值,我还需要读取和转换温度)。

My current solution is based on a state machine with 6 states.我当前的解决方案基于具有 6 个状态的状态机。 In my solution, I distinguish between the wait time for the pressure conversion and the wait time for the temperature conversion with the idea the I could try to see how much the pressure reading degrades if I use a less precise temperature reading.在我的解决方案中,我区分了压力转换的等待时间和温度转换的等待时间,我的想法是如果我使用不太精确的温度读数,我可以尝试查看压力读数下降了多少。

Here is my current solution.这是我目前的解决方案。 The following function is called inside the main while:下面的函数在 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;
    }
  }
}

I don't pretend that my solution is better.我不会假装我的解决方案更好。 I just post it in order to have an opinion from you guys.我只是为了得到大家的意见而发布它。 Note: I'm still working on it.注意:我仍在努力。 Therefore I cannot guarantee is bug-free!因此我不能保证没有错误!

For PeterJ_01: I could agree that this is not strictly a teaching portal, but I believe that everybody around here asks questions to learn something new or to improve theirselves.对于 PeterJ_01:我同意严格来说这不是一个教学门户,但我相信这里的每个人都会提出问题来学习新事物或提高自己。 Therefore, if you believe that the solution using the ack is better, it would be great if you could show us a draft of your idea.因此,如果您认为使用 ack 的解决方案更好,那么如果您能向我们展示您的想法草稿,那就太好了。 For me it would be something new to learn.对我来说,这将是学习的新东西。

Any further comment is appreciated.任何进一步的评论表示赞赏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM