'Porting' Arduino PulseLn function to general AVRs

Go To Last Post
2 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello

 

Ive been working with Arduino and it has a very convinient function PulseLn that allows user to measure pulse frequency on any digital pin of MCU. I could use it in my project thats why im attempting to "port" it to Atmel Studio AVR.

 

I know that Arduino PulseLn function is "not pretty" because it stops the code, but in my project i cant spare any of my timers to measure pulses in non blocking way. And in worst case scenario the pulseln function will block main loop for not longer than 100ms in my particular device.

 

First thing i did was to locate pulseln source in Arduino files and extract it and then locate every single definition in all other files.

The original pulseln function is as follow:

    /*
      wiring_pulse.c - pulseIn() function
      Part of Arduino - http://www.arduino.cc/

      Copyright (c) 2005-2006 David A. Mellis

      This library is free software; you can redistribute it and/or
      modify it under the terms of the GNU Lesser General Public
      License as published by the Free Software Foundation; either
      version 2.1 of the License, or (at your option) any later version.

      This library is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      Lesser General Public License for more details.

      You should have received a copy of the GNU Lesser General
      Public License along with this library; if not, write to the
      Free Software Foundation, Inc., 59 Temple Place, Suite 330,
      Boston, MA  02111-1307  USA
    */

    #include "wiring_private.h"
    #include "pins_arduino.h"

    /* Measures the length (in microseconds) of a pulse on the pin; state is HIGH
     * or LOW, the type of pulse to measure.  Works on pulses from 2-3 microseconds
     * to 3 minutes in length, but must be called at least a few dozen microseconds
     * before the start of the pulse.
     *
     * This function performs better with short pulses in noInterrupt() context
     */
    unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
    {
        // cache the port and bit of the pin in order to speed up the
        // pulse width measuring loop and achieve finer resolution.  calling
        // digitalRead() instead yields much coarser resolution.
        uint8_t bit = digitalPinToBitMask(pin);
        uint8_t port = digitalPinToPort(pin);
        uint8_t stateMask = (state ? bit : 0);

        // convert the timeout from microseconds to a number of times through
        // the initial loop; it takes approximately 16 clock cycles per iteration
        unsigned long maxloops = microsecondsToClockCycles(timeout)/16;

        unsigned long width = countPulseASM(portInputRegister(port), bit, stateMask, maxloops);

        // prevent clockCyclesToMicroseconds to return bogus values if countPulseASM timed out
        if (width)
            return clockCyclesToMicroseconds(width * 16 + 16);
        else
            return 0;
    }

    /* Measures the length (in microseconds) of a pulse on the pin; state is HIGH
     * or LOW, the type of pulse to measure.  Works on pulses from 2-3 microseconds
     * to 3 minutes in length, but must be called at least a few dozen microseconds
     * before the start of the pulse.
     *
     * ATTENTION:
     * this function relies on micros() so cannot be used in noInterrupt() context
     */
    unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout)
    {
        // cache the port and bit of the pin in order to speed up the
        // pulse width measuring loop and achieve finer resolution.  calling
        // digitalRead() instead yields much coarser resolution.
        uint8_t bit = digitalPinToBitMask(pin);
        uint8_t port = digitalPinToPort(pin);
        uint8_t stateMask = (state ? bit : 0);

        unsigned long startMicros = micros();

        // wait for any previous pulse to end
        while ((*portInputRegister(port) & bit) == stateMask) {
            if (micros() - startMicros > timeout)
                return 0;
        }

        // wait for the pulse to start
        while ((*portInputRegister(port) & bit) != stateMask) {
            if (micros() - startMicros > timeout)
                return 0;
        }

        unsigned long start = micros();
        // wait for the pulse to stop
        while ((*portInputRegister(port) & bit) == stateMask) {
            if (micros() - startMicros > timeout)
                return 0;
        }
        return micros() - start;
    }

Searching trough all arduino files i have put together all defintions and macros into one file

unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
	// cache the port and bit of the pin in order to speed up the
	// pulse width measuring loop and achieve finer resolution.  calling
	// digitalRead() instead yields much coarser resolution.
	uint8_t bit = digitalPinToBitMask(pin);
	uint8_t port = digitalPinToPort(pin);
	uint8_t stateMask = (state ? bit : 0);

	// convert the timeout from microseconds to a number of times through
	// the initial loop; it takes approximately 16 clock cycles per iteration
	unsigned long maxloops = microsecondsToClockCycles(timeout)/16;

	unsigned long width = countPulseASM(portInputRegister(port), bit, stateMask, maxloops);

	// prevent clockCyclesToMicroseconds to return bogus values if countPulseASM timed out
	if (width)
		return clockCyclesToMicroseconds(width * 16 + 16);
	else
		return 0;
}

#define _BV(bit)	(1 << (bit))
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) )
#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
#define portInputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_input_PGM + (P))) )

#define PA 1
#define PB 2
#define PC 3
#define PD 4
#define PE 5
#define PF 6
#define PG 7
#define PH 8
#define PJ 10
#define PK 11
#define PL 12

uint32_t countPulseASM(volatile uint8_t *port, uint8_t bit, uint8_t stateMask, unsigned long maxloops);

const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[] = {
	_BV(0), /* 0, port D */
	_BV(1),
	_BV(2),
	_BV(3),
	_BV(4),
	_BV(5),
	_BV(6),
	_BV(7),
	_BV(0), /* 8, port B */
	_BV(1),
	_BV(2),
	_BV(3),
	_BV(4),
	_BV(5),
	_BV(0), /* 14, port C */
	_BV(1),
	_BV(2),
	_BV(3),
	_BV(4),
	_BV(5),
};

const uint8_t PROGMEM digital_pin_to_port_PGM[] = {
	PD, /* 0 */
	PD,
	PD,
	PD,
	PD,
	PD,
	PD,
	PD,
	PB, /* 8 */
	PB,
	PB,
	PB,
	PB,
	PB,
	PC, /* 14 */
	PC,
	PC,
	PC,
	PC,
	PC,
};

const uint16_t PROGMEM port_to_input_PGM[] = {
	NOT_A_PORT,
	NOT_A_PORT,
	(uint16_t) &PINB,
	(uint16_t) &PINC,
	(uint16_t) &PIND,
};

And from that i created a condensate function. Right now its meant to measure pulse only on ATmega328 PORTB pin . Below is the code of pulseln AVR function

#define _BV(bit) (1 << (bit))
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

uint32_t countPulseASM(volatile uint8_t *port, uint8_t bit, uint8_t stateMask, unsigned long maxloops);

unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
	// cache the port and bit of the pin in order to speed up the
	// pulse width measuring loop and achieve finer resolution.  calling
	// digitalRead() instead yields much coarser resolution.
	uint8_t bit = _BV(1);
	uint8_t port = 8;

	uint8_t stateMask = (state ? bit : 0);

	// convert the timeout from microseconds to a number of times through
	// the initial loop; it takes approximately 16 clock cycles per iteration
	unsigned long maxloops = microsecondsToClockCycles(timeout)/16;

	unsigned long width = countPulseASM(PINB, bit, stateMask, maxloops);

	// prevent clockCyclesToMicroseconds to return bogus values if countPulseASM timed out
	if (width)
	return clockCyclesToMicroseconds(width * 16 + 16);
	else
	return 0;
}

the function countPulseASM is located in wiring_pulse.S and the whole file as it is below is attached to AVR studio project libraries

/*
  wiring_pulse.s - pulseInASM() function in different flavours
  Part of Arduino - http://www.arduino.cc/

  Copyright (c) 2014 Martino Facchin

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General
  Public License along with this library; if not, write to the
  Free Software Foundation, Inc., 59 Temple Place, Suite 330,
  Boston, MA  02111-1307  USA
*/

/*
 * The following routine was generated by avr-gcc 4.8.3 with the following parameters
 * -gstabs -Wa,-ahlmsd=output.lst -dp -fverbose-asm -O2
 * on the original C function
 *
 * unsigned long pulseInSimpl(volatile uint8_t *port, uint8_t bit, uint8_t stateMask, unsigned long maxloops)
 * {
 *     unsigned long width = 0;
 *     // wait for any previous pulse to end
 *     while ((*port & bit) == stateMask)
 *         if (--maxloops == 0)
 *             return 0;
 *
 *     // wait for the pulse to start
 *     while ((*port & bit) != stateMask)
 *         if (--maxloops == 0)
 *             return 0;
 *
 *     // wait for the pulse to stop
 *     while ((*port & bit) == stateMask) {
 *         if (++width == maxloops)
 *             return 0;
 *     }
 *     return width;
 * }
 *
 * some compiler outputs were removed but the rest of the code is untouched
 */

#include <avr/io.h>

.section .text

.global countPulseASM

countPulseASM:

.LM0:
.LFBB1:
    push r12   ;   ;  130 pushqi1/1 [length = 1]
    push r13   ;   ;  131 pushqi1/1 [length = 1]
    push r14   ;   ;  132 pushqi1/1 [length = 1]
    push r15   ;   ;  133 pushqi1/1 [length = 1]
    push r16   ;   ;  134 pushqi1/1 [length = 1]
    push r17   ;   ;  135 pushqi1/1 [length = 1]
/* prologue: function */
/* frame size = 0 */
/* stack size = 6 */
.L__stack_usage = 6
    mov r30,r24  ;  port, port   ;  2 *movhi/1  [length = 2]
    mov r31,r25  ;  port, port
/*     unsigned long width = 0;
***     // wait for any previous pulse to end
***     while ((*port & bit) == stateMask)
*/
.LM1:
    rjmp .L2   ;   ;  181 jump  [length = 1]
.L4:
/*         if (--maxloops == 0) */
.LM2:
    subi r16,1   ;  maxloops,  ;  17  addsi3/2  [length = 4]
    sbc r17, r1   ;  maxloops
    sbc r18, r1   ;  maxloops
    sbc r19, r1   ;  maxloops
    breq .L13  ; ,   ;  19  branch  [length = 1]
.L2:
/*         if (--maxloops == 0) */
.LM3:
    ld r25,Z   ;  D.1554, *port_7(D)   ;  22  movqi_insn/4  [length = 1]
    and r25,r22  ;  D.1554, bit  ;  24  andqi3/1  [length = 1]
    cp r25,r20   ;  D.1554, stateMask  ;  25  *cmpqi/2  [length = 1]
    breq .L4   ; ,   ;  26  branch  [length = 1]
    rjmp .L6   ;   ;  184 jump  [length = 1]
.L7:
/*             return 0;
***
***     // wait for the pulse to start
***     while ((*port & bit) != stateMask)
***         if (--maxloops == 0)
*/
.LM4:
    subi r16,1   ;  maxloops,  ;  31  addsi3/2  [length = 4]
    sbc r17, r1   ;  maxloops
    sbc r18, r1   ;  maxloops
    sbc r19, r1   ;  maxloops
    breq .L13  ; ,   ;  33  branch  [length = 1]
.L6:
/*         if (--maxloops == 0) */
.LM5:
    ld r25,Z   ;  D.1554, *port_7(D)   ;  41  movqi_insn/4  [length = 1]
    and r25,r22  ;  D.1554, bit  ;  43  andqi3/1  [length = 1]
    cpse r25,r20   ;  D.1554, stateMask  ;  44  enable_interrupt-3  [length = 1]
    rjmp .L7   ;
    mov r12, r1   ;  width  ;  7 *movsi/2  [length = 4]
    mov r13, r1   ;  width
    mov r14, r1   ;  width
    mov r15, r1   ;  width
    rjmp .L9   ;   ;  186 jump  [length = 1]
.L10:
/*             return 0;
***
***     // wait for the pulse to stop
***     while ((*port & bit) == stateMask) {
***         if (++width == maxloops)
*/
.LM6:
    ldi r24,-1   ; ,   ;  50  addsi3/3  [length = 5]
    sub r12,r24  ;  width,
    sbc r13,r24  ;  width,
    sbc r14,r24  ;  width,
    sbc r15,r24  ;  width,
    cp r16,r12   ;  maxloops, width  ;  51  *cmpsi/2  [length = 4]
    cpc r17,r13  ;  maxloops, width
    cpc r18,r14  ;  maxloops, width
    cpc r19,r15  ;  maxloops, width
    breq .L13  ; ,   ;  52  branch  [length = 1]
.L9:
/*         if (++width == maxloops) */
.LM7:
    ld r24,Z   ;  D.1554, *port_7(D)   ;  60  movqi_insn/4  [length = 1]
    and r24,r22  ;  D.1554, bit  ;  62  andqi3/1  [length = 1]
    cp r24,r20   ;  D.1554, stateMask  ;  63  *cmpqi/2  [length = 1]
    breq .L10  ; ,   ;  64  branch  [length = 1]
/*             return 0;
***     }
***     return width;
*/
.LM8:
    mov r22,r12  ;  D.1553, width  ;  108 movqi_insn/1  [length = 1]
    mov r23,r13  ;  D.1553, width  ;  109 movqi_insn/1  [length = 1]
    mov r24,r14  ;  D.1553, width  ;  110 movqi_insn/1  [length = 1]
    mov r25,r15  ;  D.1553, width  ;  111 movqi_insn/1  [length = 1]
/* epilogue start */
.LM9:
    pop r17  ;   ;  171 popqi [length = 1]
    pop r16  ;   ;  172 popqi [length = 1]
    pop r15  ;   ;  173 popqi [length = 1]
    pop r14  ;   ;  174 popqi [length = 1]
    pop r13  ;   ;  175 popqi [length = 1]
    pop r12  ;   ;  176 popqi [length = 1]
    ret  ;  177 return_from_epilogue  [length = 1]
.L13:
.LM10:
    ldi r22,0  ;  D.1553   ;  120 movqi_insn/1  [length = 1]
    ldi r23,0  ;  D.1553   ;  121 movqi_insn/1  [length = 1]
    ldi r24,0  ;  D.1553   ;  122 movqi_insn/1  [length = 1]
    ldi r25,0  ;  D.1553   ;  123 movqi_insn/1  [length = 1]
/* epilogue start */
.LM11:
    pop r17  ;   ;  138 popqi [length = 1]
    pop r16  ;   ;  139 popqi [length = 1]
    pop r15  ;   ;  140 popqi [length = 1]
    pop r14  ;   ;  141 popqi [length = 1]
    pop r13  ;   ;  142 popqi [length = 1]
    pop r12  ;   ;  143 popqi [length = 1]
    ret  ;  144 return_from_epilogue  [length = 1]

 

So far the result is that Atmel studio errors out while compiling with:

expected 'volatile uint8_t *' but argument is of type 'uint8_t'    LLLCCD    I:\Elektronika\LLLCCD\LLLCCD\main.c    21

passing argument 1 of 'countPulseASM' makes pointer from integer without a cast    LLLCCD    I:\Elektronika\LLLCCD\LLLCCD\main.c    37

 

Both line 21 and 37 are within the newly created funcion at the beginning of my file:

/*
 * LLLCCD.c
 *
 * Created: 2015-11-02 12:24:31
 * Author : CIC
 */
#define F_CPU 8000000UL

#include <avr/io.h>
#include <stdlib.h>
#include <util/delay.h>
#include "lcd.h"
#define ADCIN PC5   //definicja ADCIN (wejście ADC) 

#define _BV(bit) (1 << (bit))
#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

uint32_t countPulseASM(volatile uint8_t *port, uint8_t bit, uint8_t stateMask, unsigned long maxloops);

unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout)
{
	// cache the port and bit of the pin in order to speed up the
	// pulse width measuring loop and achieve finer resolution.  calling
	// digitalRead() instead yields much coarser resolution.
	uint8_t bit = _BV(1);
	uint8_t port = 8;

	uint8_t stateMask = (state ? bit : 0);

	// convert the timeout from microseconds to a number of times through
	// the initial loop; it takes approximately 16 clock cycles per iteration
	unsigned long maxloops = microsecondsToClockCycles(timeout)/16;

	unsigned long width = countPulseASM(PINB, bit, stateMask, maxloops);

	// prevent clockCyclesToMicroseconds to return bogus values if countPulseASM timed out
	if (width)
	return clockCyclesToMicroseconds(width * 16 + 16);
	else
	return 0;
}

There is no calls to this function yet, the first thing i do is to recreate it and then ill be testing if it actually works. But it seems that i either missed some definition or converted it in wrong way like cut out too much out of it.

Last Edited: Thu. Nov 19, 2015 - 09:13 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't quite see why you're bothering with a direct translation of the arduino code; once you figure out that that it's building parameters (port, bitmask, timeout) for a busy loop, and converting loop iterations to microseconds, it ought to be easier to just write that from scratch.

 

In this case, the fact that the Arduino code is using asm is probably to avoid unexpected changes between compiler versions, you could keep that, or not...

 

As for the actual error:

unsigned long width = countPulseASM(PINB, bit, stateMask, maxloops);

the PINB definition includes a dereference; it's not an address.   So the code needs to be:

unsigned long width = countPulseASM(&PINB, bit, stateMask, maxloops);