Skip to content

drivers/srf04: range finder sensor#9035

Merged
haukepetersen merged 2 commits intomasterfrom
unknown repository
Sep 20, 2018
Merged

drivers/srf04: range finder sensor#9035
haukepetersen merged 2 commits intomasterfrom
unknown repository

Conversation

@ghost
Copy link
Copy Markdown

@ghost ghost commented Apr 26, 2018

This is an initial driver implementation for the srf04 ultra sonic range finder. The driver provides functions for initializing the gpio, triggering a measurement and reading out the measured data. This PR also provides a very simple test.

@ghost ghost changed the title Driver sensor srf04 drivers/srf04: range finder sensor Apr 26, 2018
Copy link
Copy Markdown
Member

@jnohlgard jnohlgard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have the hardware to test this, but it looks like a reasonable draft at first glance. There are a few issues with the design that should be addressed however, see my comments below. There are also a few copy paste mistakes in the documentation where there are references to another driver or the wrong docs, and some missing docs

* @brief GPIO pins for hc-sr04_t device
*/
typedef struct {
srf04_params_t p;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand you copied the structure for this driver from one of the existing drivers, but this modifiable params struct is what I would call an antipattern in RIOT. A better solution is to change this to const srf04_params_t * to let the parameters reside in ROM.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this page, this is a recommended pattern. But your concern sounds valid. We have the same question in #7067. @haukepetersen what's your opinion ?

gpio_t trigger;
gpio_t echo;
uint32_t distance;
uint32_t time;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest moving the time, distance, and state to the srf04_params_t struct and keep only the hardware configuration parameters in srf04_params_t

* @return SRF04_OK on success
* @return SRF04_GPIO on gpio init failure
*/
int srf04_init(srf04_t *dev, const gpio_t trigger, const gpio_t echo);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Get rid of the gpio arguments here and place the configuration in srf04_params.h

} srf04_params_t;

/**
* @brief GPIO pins for hc-sr04_t device
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong docs

SRF04_IDLE,
} srf04_state_t;

typedef struct {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing docs

SRF04_INT = -2, /**< error initializing gpio interrupt*/
};

typedef enum {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing docs

*
* @param[in] dev device descriptor of sensor
*
* @return SRF04_OK
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it always return this value? Is there any reason to have a return value then?

/** @} */

/**
* @brief BMX055 configuration
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong docs

@aabadie aabadie added Type: new feature The issue requests / The PR implemements a new feature for RIOT Area: drivers Area: Device drivers labels Apr 27, 2018
Copy link
Copy Markdown
Contributor

@aabadie aabadie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great one! I have a few of these modules so I can test.

Otherwise, there a few things to change: style, internal implementation and I think the API should be extended with a function that directly returns the measured distance in mm.

*/
enum {
SRF04_OK = 0, /**< exit without error */
SRF04_GPIO = -1, /**< error initializing gpio*/
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would slightly update the name of the error enums by introducing ERR: SRF04_ERR_GPIO, etc

* @brief GPIO pins for hc-sr04_t device
*/
typedef struct {
srf04_params_t p;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to this page, this is a recommended pattern. But your concern sounds valid. We have the same question in #7067. @haukepetersen what's your opinion ?

{ .state = SRF04_IDLE, \
.trigger = SRF04_PARAM_TRIGGER, \
.echo = SRF04_PARAM_ECHO, \
.distance = (0U), \
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need distance in the initialization parameters ? Same question for time and I also saw below that state is also added in the parameter struct. I think those 3 should go in the device descriptor.

dev->p.time = 0;

if ((gpio_init(dev->p.trigger, GPIO_OUT) != 0) |
(gpio_init(dev->p.echo, GPIO_IN) != 0)){
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space before {


if ((gpio_init(dev->p.trigger, GPIO_OUT) != 0) |
(gpio_init(dev->p.echo, GPIO_IN) != 0)){
DEBUG("[srf04] Error: could not initialize GPIO\n");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would split this check in 2: one for the trigger pin, one for the echo pin

This example shows the usage of an srf04 module.
The application exerts a timer and two gpio.
The module is an ultrasonic range finder with a small protocoll,
hence an implementation can be kept simple.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this comment is very useful. Better detail the mentioned protocol ? or give a link to a datasheet.

Build, flash and start the application:
```
export BOARD=your_board
make -j 2 flash term
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing closing ```
Or just indent the block with 4 spaces (and remove the opening ```). I would put the command line on a single line and remove -j2


srf04_init(&dev, srf04_params[0].trigger, srf04_params[0].echo);

while(1) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing space after while


int main(void)
{
printf("SRF04 range finder example\n");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use puts without \n


xtimer_usleep(SAMPLE_PERIOD);

printf("D: %d mm\n", (((srf04_read(&dev) * 100) / 292) / 2));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I think it would be better to extend the driver API with a srf04_read_distance that return the mesure in mm. The computation seems weird (x * 100 / 292 / 2). And better define constants in the driver header instead of hard coded values.

Copy link
Copy Markdown
Contributor

@haukepetersen haukepetersen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some ideas for optimizing the mem usage and making the API functions more safe against miss-usage.

#define SRF04_DISTANCE (584U)
#endif

static bool state;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thie variable is not used, right?!

uint32_t t = xtimer_now_usec();

srf04_t* dev = (srf04_t*)arg;
if (dev->state == SRF04_IDLE) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could simply use dev->distance for keeping the state:

if (dev->distance > SRF04_MEASURING) {
    .... // start new measurement
}
else {
    ... // save actual distance time
} 

typedef enum {
SRF04_MEASURING,
SRF04_IDLE,
} srf04_state_t;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest a small change here, which would allow us to keep state using the distance variable, and using these values as return codes as well:

enum {
    SRF04_OK = 0,
    SRF04_INVALID = -1,
    SRF04_MEASURING = -2,
}; // no need for typing this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and simply append the error codes from above with -3, -4...

*
* @param[in] dev device descriptor of sensor
*
* @return time of flight in ms
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add something like

@return              time of flight in ms (>= SRF04_OK)
@return  SRF04_MEASURING if measurement is in progress
@return SRF04_INVALID if no valid measurement is available

*
* @param[in] dev device descriptor of sensor
*
* @return time of flight in mm
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here.


if (gpio_init_int(dev->p.echo, GPIO_IN, GPIO_BOTH, _cb, (void*)dev) != 0) {
DEBUG("[srf04] Error: could not initialize GPIO echo pin\n");
return SRF04_ERR_INT;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would simplify here and drop the explicit ERR_INT value in favor of returning SRF04_ERR_GPIO in both cases.

void srf04_trigger(const srf04_t* dev)
{
gpio_irq_enable(dev->p.echo);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it makes sense to add a check so we don't trigger a new measurement while one is in progress:

if (dev->distance == SRF04_MEASURING) {
    return;
}


int srf04_read_distance(const srf04_t* dev)
{
return ((dev->distance * 100) / SRF04_DISTANCE);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we should also add a check:

if (dev->distance >= SRF04_OK) {
    return ((dev->distance * 100) / SRF04_DISTANCE);
}
return dev->distance;


xtimer_usleep(SAMPLE_PERIOD);

printf("D: %d mm\n", srf04_read_distance(&dev));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with my proposed changes, it could be nice to check the return value of srf04_read_distance() and print an explicit error message in case the value is not valid (val < SRF04_OK).

* @return time of flight in mm
*/
int srf04_read_distance(const srf04_t* dev);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

last proposal: how about a convenience function that combines _trigger() and read_distance() together with the mandatory wait time?! This could be useful for very simple use cases (as e.g. the example application below...).

@PeterKietzmann
Copy link
Copy Markdown
Member

@SemjonKerner I guess you 'll ping the reviewers once you addressed their comments?

@ghost
Copy link
Copy Markdown
Author

ghost commented Jun 20, 2018

@PeterKietzmann as far as i recall, all comments are implemented. Since i am on parental leave i have no option to check if my changes are working on the real hardware. I will update and inform everyone when i get back in August, if this is ok with you. If someone needs the driver as fast as possible, then i can make it happen sooner.

@PeterKietzmann
Copy link
Copy Markdown
Member

No stress from my side. In general it makes sense to inform or even ping reviewers after you addressed their comments. @haukepetersen, @gebart, @aabadie if your comments were addressed, please remove the change request.


int srf04_init(srf04_t* dev)
{
dev->p = srf04_params[0];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This limits the driver to a single hardware sensor. I suggest adding an argument to the init function, passing a pointer to a params struct. See other device drivers in the tree, for example https://github.com/RIOT-OS/RIOT/blob/master/drivers/io1_xplained/io1_xplained.c#L37-L39

Copy link
Copy Markdown
Contributor

@aabadie aabadie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found minor things remaining: improvement in driver dependencies, test application makefile, documentation.


RIOTBASE ?= $(CURDIR)/../..

FEATURES_REQUIRED = periph_gpio
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed anymore with this added in the driver module dependencies.

@@ -0,0 +1,11 @@
APPLICATION = driver_srf04
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use include ../Makefile.tests_common instead of that line.

* @brief GPIO pins for srf04 device
*/
typedef struct {
gpio_t trigger;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The attributes of this struct are not documented, same for the srf04_t struct below.

* @brief Status and error return codes
*/
enum {
SRF04_OK = 0, /**< exit without error */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe align a bit the values and comments

@ghost
Copy link
Copy Markdown
Author

ghost commented Aug 15, 2018

Hey @haukepetersen , @gebart , @aabadie finally there is a new commit fixing your requested changes.

@ghost
Copy link
Copy Markdown
Author

ghost commented Aug 29, 2018

@haukepetersen @gebart @aabadi just a small reminder

Copy link
Copy Markdown
Contributor

@aabadie aabadie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comments are addressed but I didn't test. Just a minor and non blocking comment.
Untested-ACK


while (1) {
distance = srf04_get_distance(&dev);
if (distance < SRF04_OK) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is weird since the function can return a distance or error codes (negative values). Maybe just compare to 0 ? Or use a switch and address each potential error code ?

Copy link
Copy Markdown
Author

@ghost ghost Aug 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing to SRF04_OK is the same thing, since the enum defines it as zero and all errors would evaluate negative. I could distinguish between errors, but i deem it not necessary.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about your concern and now i believe to understand what you meant. Distance should only be SRF04_OK (zero), when srf04_init returns successfully. After the first get_distance (wich happens before) it will either have a valid distance (greater or equal to 0) or give an error. So comparing to SRF04_OK is just meant to be a clean way of checking for new data.
As the sensor only measures values from a minimum distance of i think 3 cm it should always be greater then zero when successfull.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say the check is fine. Maybe more intuitive would simply be to test for distance < 0, semantically considering the 0 as distance value instead of some arbitrarily defined named value. But I'd say this is up to the creative freedom of the author :-)

Copy link
Copy Markdown
Contributor

@haukepetersen haukepetersen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some tiny details, else ok.

* @note for inch define "SRF04_DISTANCE (1480U)"
*/
#ifndef SRF04_DISTANCE
#define SRF04_DISTANCE (584U)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed offline: drop the support for inches and move this define into the source file

/**
* @brief defines sensor required sample time
*/
#define SRF04_SAMPLE_PERIOD (50U * US_PER_MS)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is static and has nothing to do with the interface -> move into source file


/**
* @brief Convenience function triggers a measurement and returns distance
*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a comment about the blocking nature of this function (will only return after the new value is acquired, which takes ~50ms..).

}
return dev->distance;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate newline :-)


srf04_t dev;
srf04_init(&dev, &srf04_params[0]);
int distance;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scope: cppcheck will probably complain that distance should be define inside the while loop below

puts("SRF04 range finder example");

srf04_t dev;
srf04_init(&dev, &srf04_params[0]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for demonstration purposes, it is good practice to check the return value of the init function, e.g.

if (srf04_init(dev, params) != SRF04_OK) {
  puts("error initializing...");
  return 1;
}

or similar..


while (1) {
distance = srf04_get_distance(&dev);
if (distance < SRF04_OK) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say the check is fine. Maybe more intuitive would simply be to test for distance < 0, semantically considering the 0 as distance value instead of some arbitrarily defined named value. But I'd say this is up to the creative freedom of the author :-)

@ghost
Copy link
Copy Markdown
Author

ghost commented Sep 20, 2018

@haukepetersen fixes are commited.
@gebart are you still looking into this?

Copy link
Copy Markdown
Contributor

@haukepetersen haukepetersen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept, arch, code, and style are good, also tested the PR and it functions as expected -> ACK

please squash

@haukepetersen haukepetersen added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Sep 20, 2018
@jnohlgard
Copy link
Copy Markdown
Member

My comments seem to have been addressed

@haukepetersen
Copy link
Copy Markdown
Contributor

all green -> lets go

@SemjonKerner nice job, thanks!

@haukepetersen haukepetersen merged commit ef4bd75 into RIOT-OS:master Sep 20, 2018
@ghost ghost deleted the driver_sensor_srf04 branch September 20, 2018 14:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: drivers Area: Device drivers CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Type: new feature The issue requests / The PR implemements a new feature for RIOT

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants