C++ probleem

Ik will een class maken voor software pwm maar loop tegen een tweetal problemen aan. Wanneer ik de vector stl gebruik dan geeft de compiler direct een memory error. Ik dacht eerst dan er iets anders aan de hand was maar als ik de code compileer met gcc op een linux box dan is het resultaat 78kb groot. De geplande STM32 heeft maar 64k flash beschikbaar. Ik zoek dus een andere opslag methode die klein en van variabele grootte is, ipv. een vector of fixed size array.

Het tweede probleem, ik kan geen lijst van poorten meegeven aan de constructor van de class, zo dus:

c code:

class SoftPWM pwm( { PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9 } );

Heb ik dat verkeerd gecodeerd of is dit gewoon niet mogelijk?
Het werkt wel als ik het in meerdere stappen doe:

c code:

uint8_t PWM_ports[] = { PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9 };
class SoftPWM pwm( PWM_ports ); 

Hieronder de nog lang niet complete code, slechts een eerste aanzet om wat dingen te testen.

c code:


#include <cstdint>
//#include <vector>

#define MAX_PORTS 32

class SoftPWM {
    public:
        SoftPWM( uint8_t *PWM_ports );
        void AddPort( uint8_t PWM_port );
    private:
        int iPortCount=0;
        //vector<uint8_t> PWM_ports;    
        uint8_t PWM_ports[MAX_PORTS];
        //vector<uint32_t> PWM_dutycycle;  
        uint32_t PWM_dutycycle[MAX_PORTS];  
};

SoftPWM::SoftPWM( uint8_t *PWM_ports ) {
    for( int i=0; i<sizeof(PWM_ports); i++ ) {
        AddPort( PWM_ports[i] );
    }
}  

void SoftPWM::AddPort( uint8_t PWM_port ) {
    //PWM_ports.push_back(PWM_port);
    PWM_ports[iPortCount] = PWM_port;
    PWM_dutycycle[iPortCount] = 0;
    pinMode(PWM_port, OUTPUT);
    iPortCount++;
}

void setup() {    
    //class SoftPWM pwm( { PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9 } );   
    uint8_t PWM_ports[] = { PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9 };
    class SoftPWM pwm( PWM_ports ); 
    pwm.AddPort( PA0 );
}

void loop() {

}

[Bericht gewijzigd door Henry S. op 27 september 2019 00:10:22 (0%)]

Je moet wel bijhouden hoe groot je array wordt. Zo gaat dat niet.

c code:


uint8_t PWM_ports[] = { PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9 };

Deze heeft maar 9 elementen.

c code:


 uint8_t PWM_ports[MAX_PORTS];

En deze heeft er 32.

c code:


SoftPWM::SoftPWM( uint8_t *PWM_ports ) {
    for( int i=0; i<sizeof(PWM_ports); i++ ) {
        AddPort( PWM_ports[i] );
    }
}  

En hier met sizeof(PWM_ports) krijg je het aantal bytes in de pointer (4) en niet het aantal elementen in de array.

En het is nogal foutgevoelig om meerdere variabelen allemaal dezelfde naam te geven.

En de stl library is wel erg handig in gebruik, maar over het algemeen toch teveel gevraagd van een klein micro'tje. Ik was vorige week tamelijk verbaasd dat het op een ESP32 wel werkte, maar die heeft dan wel 4Mbyte flash en 512 kByte ram.

[Bericht gewijzigd door Henry S. op 27 september 2019 00:11:01 (0%)]

Ik zou eens kijken naar initializer lists.

Jouw oplossing kan niet werken, omdat (zelfs als je je array op deze wijze kon meegeven) het array niet meer bestaat na de constructor (hij is tijdelijk).

Een andere oplossing is het gebruik van templates, dan kan je een SoftPWM<9> klasse maken die dus expliciet 9 elementen verwacht.

Meep! Meep!

@deKees, ik had al geschreven dat ik iets anders wilde dan een vector of een array. die arraygrootte van 32 was dus gekozen om toch iets te kunnen testen.

@roadrunner84, Die initializer_list werkt perfect. Ik heb hem gelijk geadopteerd. Die ga ik beslist vaker gebruiken.

Voor de opslag heb ik een linked list gekozen. Ik moet nog wat tweaken. Er zit bijvoorbeeld nog een inline functie in maar ik kan al een reeks pwm pinnen gebruiken. Voor wie geinteresseerd is in het vervolg:

code:

#include <cstdint>
#include <initializer_list>

class SoftPWM {
    public:
        template <typename T> SoftPWM( std::initializer_list<T> a );
        ~SoftPWM( void );
        size_t GetPortCount() {
            return iPortCount;
        }
        void SetDutyCycle( uint8_t _port, uint32_t _dutycycle );
        void handler( void );
    private:
        size_t iPortCount;
        uint8_t port;
        uint32_t dutycycle; 
        uint32_t counter;
        uint32_t max_val;
        class SoftPWM *next = NULL;
};


class SoftPWM *pwm;

void IRQ_handler(void) {
    pwm->handler();
} 

template <typename T> SoftPWM::SoftPWM( std::initializer_list<T> a ) {
    iPortCount = 0;
    for( const T& _port : a ) {
        if( iPortCount == 0 ) {
            port = _port;
            counter = 0;
            max_val = 100;
            dutycycle = 0;
            next = NULL;
            pinMode( port, OUTPUT );
            Timer2.pause(); 
            Timer2.setMode(TIMER_CH1, TIMER_OUTPUTCOMPARE); 
            Timer2.setPeriod(100); // 100us, 10 us werkt niet !! 
            Timer2.setCompare(TIMER_CH1, 1);      
            Timer2.attachInterrupt(TIMER_CH1, IRQ_handler);
            Timer2.resume();            
            iPortCount++;
        } else {
            class SoftPWM *p = this;
            while( p->next != NULL ) {
                p = p->next;
            }
            p->next = new class SoftPWM( { _port } );     
        }
        
    }
}

SoftPWM::~SoftPWM( void ) { 
    Timer2.pause();
    pinMode( port, INPUT );
    delete next;
}

void SoftPWM::handler() {
    class SoftPWM *p = this;
    while( p != NULL ) {
        if( ++(p->counter) >= p->max_val ) {
            p->counter -= p->max_val;
            if( p->dutycycle == 0 ) {
                digitalWrite( p->port, LOW );
            } else {
                digitalWrite( p->port, HIGH );    
            }
        } else if( p->counter >= p->dutycycle ) {
            digitalWrite( p->port, LOW );
        }
        p = p->next;
    } 
}

void SoftPWM::SetDutyCycle( uint8_t _port, uint32_t _dutycycle ) {
    class SoftPWM *p = this;
    while( p != NULL ) {
        if( p->port == _port ) {
            p->dutycycle = _dutycycle;
            break;
        }
        p = p->next;
    }
}

void setup() {
    pwm = new class SoftPWM( { PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, LED_BUILTIN } );
}

void loop() {
    // LED_BUILTIN voor STM32 dev board is aan wanneer het bit laag is. Met onderstaande regel is de led dus 99% uit, 1% aan
    pwm->SetDutyCycle( LED_BUILTIN, 99 );

    // Nu geen scoop bij de hand, later testen
    pwm->SetDutyCycle( PA0, 25 );
    pwm->SetDutyCycle( PA1, 50 );
    pwm->SetDutyCycle( PA2, 75 );

    //TODO: timer instelbaar maken om timer frequentie voor de gebruiker toegankelijk te maken: SetTimerTicks(uint32_t us)
    //      maxval toegankelijk maken: SetMaxVal(uint32_t us)
    //      SetDutyPercent() om bij andere maxval dan 100 toch procenten in te kunnen stellen
    //      memory leaks testen
}

Geen bump, maar een vervolg. Ik ben 29 september voor 3 weken op vakantie gegaan en moest dit even laten liggen. Ik had tijdens de vakantie wel een laptop mee, maar geen oscilloscoop. Deze uitdaging moest dus even wachten. :-)
Zojuist even wat metingen gedaan en er gebeurden dingen die ik absoluut niet had verwacht. Oorzaak een te snelle timerinterrupt. Met een interrupt om 10 us ga je het niet redden als de IRQ handler tussen de 6 en de 11 us nodig heeft om 10 pwm pennen te bedienen. Vind ik overigens vrij veel tijd voor een controller die draait op 72MHz. Ik dacht dat de code efficienter was geschreven, nog iets om uit te zoeken dus.

Ik heb de tijd, nodig voor het sturen van 10 pennen, zo gemeten

code:


void IRQ_handler(void) {
    digitalWrite( PB0, HIGH );
    pwm->handler();
    digitalWrite( PB0, LOW );
} 

Op de scope kan ik zo prima zien hoe lang een routine draait, maar zou dit op een gemakkelijke manier te automatiseren zijn zodat ik de hoogste resolutie kan bepalen voor een wisselend aantal pwm kanalen?

Als je binnen de interrupt GEEN functies aanroept, dan zou het kunnen dat er minder registers op de stack hoeven.

C++ heeft m.i. soms wat rare "overhead". Dan zit je bijvoorbeeld ineens met 78k code waar je maar een paar regels source hebt. Of een interrupt routine die 11 us loopt terwijl ie maar weinig hoeft te doen.

Overweeg om eens te testen of een C interrupt handler sneller is. Als dat zo is kan je alsnog overwegen om de C++ versie te gebruiken.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Op 19 oktober 2019 23:07:57 schreef rew:
Als je binnen de interrupt GEEN functies aanroept, ...

Er worden dus een heleboel functies aangeroepen. En de belangrijkste, digitalWrite, kan ik echt niet missen, zie onderstaande.

De tijd nodig voor het uitvoeren van de IRQ is bij een enkele PWM-uitgang tussen 1.4 en 2.0 us, bij 50 PWM-uitgangen 45us

Even speuren leert dat de vertraging wordt veroorzaakt door digitalWrite. Ik heb even onder elkaar geplakt wat wordt aangeroepen:

code:


#define STM_LL_GPIO_PIN(X) (pin_map_ll[STM_PIN(X)])

#define STM_PORT(X) (((uint32_t)(X) >> 4) & 0xF)

#define get_GPIO_Port(p) ((p < MAX_NB_PORT) ? GPIOPort[p] : (GPIO_TypeDef *)NULL)

#define digitalPinToPinName(p)      (((uint32_t)p < NUM_DIGITAL_PINS) ? digitalPin[p] : NC)

static inline void digital_io_write(GPIO_TypeDef *port, uint32_t pin, uint32_t val) {
    if (val) {
        LL_GPIO_SetOutputPin(port, pin);
    } else {
        LL_GPIO_ResetOutputPin(port, pin);
    }
}

static inline void digitalWriteFast(PinName pn, uint32_t ulVal) {
    digital_io_write(get_GPIO_Port(STM_PORT(pn)), STM_LL_GPIO_PIN(pn), ulVal);
}

void digitalWrite(uint32_t ulPin, uint32_t ulVal) {
    digitalWriteFast(digitalPinToPinName(ulPin), ulVal);
}

Deze functies/defines zijn verdeeld over een aantal bestanden. Als ik daar iets aan wil versnellen moet ik of de STM32-code voor de Arduino gaan herschrijven, of naar een ontwikkelomgeving van ST overstappen om hun GPIO te gebruiken. Dat gaat het dus allebei niet worden.

Voorlopig neem ik genoegen met een wat langere periodetijd. Deze code wordt deze keer dan maar geen generieke module. :-)

Voor wat betreft:

Op 19 oktober 2019 23:07:57 schreef rew:
Als je binnen de interrupt GEEN functies aanroept, dan zou het kunnen dat er minder registers op de stack hoeven.

is jou digitalWriteFast waarschijnlijk geen functie.

Ik gebruik zelf:

code:

#define PORT(portpin) ((GPIO_TypeDef *)(portpin & 0xfffffff0))
#define PIN(portpin) (portpin & 0xf)

(Nu even uit m'n hoofd ingetikt, niet copy-paste uit m'n code).

De "foutcheck" met dat je een NULL returned als je pin ID fout is, dat lijkt me weinig zinnig. Als je dan dat als GPIO port probeert te gebruiken dan krijg je toch een exception en crasht de boel. En dat is ook wat er zonder foutcheck gebeurt.

Die pin_map vind ik onnodig. Je hebt in je code eerder ook al "PA0" als pin-identifier.

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

HO STOP, dat laatste is geen code van mezelf!

Ik heb de functies tussen code tags geplakt die achter de Arduino digitalWrite verstopt zitten voor de stm32 arduino maple.
Ik vind die code omslachtig in elkaar zitten. Maar dit komt waarschijnlijk omdat de omgeving nog niet echt door de massa gevonden is of geaccepteerd is. Daar speelt waarschijnlijk ook mee dat het stm32duino forum ter ziele is.

EDIT:
Welke ontwikkelomgeving gebruik jij (gebruiken jullie) voor stm32?

Op 20 oktober 2019 16:14:57 schreef hennep:
Welke ontwikkelomgeving gebruik jij (gebruiken jullie) voor stm32?

Eclipse (SW4STM32), dus onder water gewoon GCC.
Heb ook wel Keil MDK gebruikt, maar vind Eclipse fijner werken.
Bovendien heb je voor Keil MDK een licentie nodig vanaf een bepaalde code-size.

hier ook gewoon eclipse.
Om je STM32 gewoon efficient te gebruiken en er uit te halen wat die kan zou ik ook gewoon van arduino afstappen.
Als je CUBE MX gebruikt met de daarbij behorende HAL heb je ook gewoon al een hele goede schil als basis.
Gebruik zelf momenteel de Standard Peripheral Lib, ide vind ik toch ook wel lekker rechttoe rechtaan

Ik gebruik gewoon gcc en makefiles. (en chibios).

Op 20 oktober 2019 16:14:57 schreef hennep:
Ik heb de functies tussen code tags geplakt die achter de Arduino digitalWrite verstopt zitten voor de stm32 arduino maple.
Ik vind die code omslachtig in elkaar zitten.

Dit komt omdat in arduino-land er een "kleine integer" gebruikt wordt om een pin aan te duiden. Dan zijn er dat soort vertaal-tabellen nodig.

[Bericht gewijzigd door rew op 21 oktober 2019 14:03:55 (81%)]

four NANDS do make a NOR . Kijk ook eens in onze shop: http://www.bitwizard.nl/shop/

Ik heb gisteravond Cube MX geinstalleerd en onderstaande stukje code doet er precies net zolang over als de Arduino class die 1 poort aanstuurt. Mogelijk heeft de optimizer meer invloed dan ik had verwacht.

code:


while(1) {
    HAL_GPIO_WritePin(SCOPE_GPIO_Port, SCOPE_Pin, 0);
    HAL_GPIO_WritePin(SCOPE_GPIO_Port, SCOPE_Pin, 1);
}

Voorlopig kan ik, voor de huidige toepassing, voldoende snel PWM'en.

Ik moet even kwijt dat ik de "pinout" interface van Cube erg prettig vind. Wel jammer dat de main functie wordt overschreven zodra je iets aanpast in de instellingen van de pinnen. Alle code die je daar hebt ingezet wordt overschreven bij het opnieuw genereren. Voor nu heb ik bedacht een test project aan te maken waarmee ik de code genereer en dan kopieer ik de pin-settings naar mijn eigen project. Of dat gaat werken moet ik nog even testen.

Om even snel iets uit te proberen denk ik de Arduino interface toch nog niet helemaal los te laten.

Op 21 oktober 2019 19:00:07 schreef hennep:
Wel jammer dat de main functie wordt overschreven zodra je iets aanpast in de instellingen van de pinnen.

In de gegenereerde code vind je commentaarblokken waarin je je eigen code toe kunt voegen. Voorbeeld:

c code:

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

Als je hier netjes binnen blijft, wordt de code niet overschreven tijdens het hergenereren van je HAL laag. Ik zou je eigen code zoveel mogelijk in apparte files houden en vanaf de main alleen functies aanroepen in andere files. Zo kan je goed overzicht houden.