PWM control Error Number 30

Hello, I am trying to run the following pwm sample https://github.com/toradex/torizon-samples/tree/bullseye/pwm/pwm with ApolloX on VS Code to control PWM1 on Apalis iMX6 Dual with Ixora carrier board.
The only difference is that I have rewritten the code for C++ but I have not changed anything of the functionality. Basically only to use cout instead of printf.

The code compiles but I get the following errors when I run it:

Error Number 30
failed to export
[Inferior 1 (process 37) exited with code 01]

I checked and the /sys/class/pwm/pwmchip0/export exists on my system.
Is that a problem regarding permission of accessing the files or something else?

Also the Error Number 30, where can I check the error numbers to know what I am dealing with?

Greetings @Svetoslav,

How exactly did you port this sample into an ApolloX project? Did you just copy and modify the code, or did you do any other changes?

Best Regards,
Jeremias

I just copied and modified the code, as I didn’t find in the website describing the use of PWM any other changes that need to be made on Dockerfile or Makefile.

Hello @Svetoslav ,
Maybe our documentation on PWM might help you:

Best regards,
Josep

Hello,
I checked the documentation and I see they make some con figurations in the dockerfile.
I am fairly new to using docker and torizon but from what I understand they just make some basic configuration regarding the system and architecture that is used and mine is a bit different so I am not sure if I should add any of these configurations.

This is my docker file:

# ARG CROSS_SDK_BASE_TAG=2.7-bullseye
ARG CROSS_SDK_BASE_TAG=3.0.2-20230323-bookworm
ARG BASE_VERSION=2.5-bullseye

##
# Board architecture
# arm or arm64
##
ARG IMAGE_ARCH=

##
# Application Name
##
ARG APP_EXECUTABLE=pesho


# BUILD ------------------------------------------------------------------------
FROM torizon/debian-cross-toolchain-${IMAGE_ARCH}:${CROSS_SDK_BASE_TAG} As Build

ARG IMAGE_ARCH
ARG COMPILER_ARCH
ENV IMAGE_ARCH ${IMAGE_ARCH}

# __deps__
RUN apt-get -q -y update && \
    apt-get -q -y install libcurl4-gnutls-dev:armhf \
    apt-get -q -y install \
# DOES NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_dev_start__
    # __torizon_packages_dev_end__
# DOES NOT REMOVE THIS LABEL: this is used for VS Code automation
    libgpiod-dev:$CROSS_TC_IMAGE_ARCH \
    libgpiod2:$CROSS_TC_IMAGE_ARCH \
    && \
    apt-get clean && apt-get autoremove && \
    rm -rf /var/lib/apt/lists/*
# __deps__

COPY . /app
WORKDIR /app

# RUN echo  arm-linux-gnueabihf-g++  --version

RUN if [ "$IMAGE_ARCH" = "arm64" ] ; then \
        make ARCH=. CC=aarch64-linux-gnu-g++ ; \
    elif [ "$IMAGE_ARCH" = "arm" ] ; then \
        make ARCH=. CC=arm-linux-gnueabihf-g++ ; \
    elif [ "$IMAGE_ARCH" = "amd64" ] ; then \
        make ARCH=. CC=x86_64-linux-gnu-g++ ; \
    fi

# BUILD ------------------------------------------------------------------------


# DEPLOY ------------------------------------------------------------------------
FROM --platform=linux/${IMAGE_ARCH} torizonextras/debian:${BASE_VERSION} AS Deploy

ARG IMAGE_ARCH
ARG APP_EXECUTABLE
ENV APP_EXECUTABLE ${APP_EXECUTABLE}

RUN apt-get -y update && apt-get install -y --no-install-recommends \
# DOES NOT REMOVE THIS LABEL: this is used for VS Code automation
    # __torizon_packages_prod_start__
    # __torizon_packages_prod_end__
# DOES NOT REMOVE THIS LABEL: this is used for VS Code automation
	&& apt-get clean && apt-get autoremove && rm -rf /var/lib/apt/lists/*

# copy the build
COPY --from=Build /app/bin /app

# ADD YOUR ARGUMENTS HERE
CMD [ "./app/pesho" ]

# DEPLOY ------------------------------------------------------------------------

And I’m almost sure there is not problem in my code but this is the one I’m using for the PWM:

#include <iostream>
#include <string>
#include <cstring>
#include <cstdint>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include "pwm_utils.h"

#define MAX_BUF 50

int pwm_write_val(char *file, uint32_t val)
{
    int fd;
    char buf[MAX_BUF]; 
    
    fd = open(file, O_WRONLY);
    if(fd < 0)
    {
        std::cout << "Error Number " << errno << std::endl;
        perror("pwm");
        return -1;
    }        
    if( snprintf(buf, MAX_BUF, "%u", val) < 0)
        goto failed; 

    if( write(fd, buf, strlen(buf)) < 0)
    {
        std::cout << "Error Number " << errno << std::endl;
        perror("pwm");
        goto failed;
    }

    close(fd);    
    return 0;

failed:
    close(fd);
    return -1;
}

int pwm_write_str(char *file, char *val)
{
    int fd;
    
    fd = open(file, O_WRONLY);
    if(fd < 0)
    {
        std::cout << "Error Number " << errno << std::endl;
        perror("pwm");
        return -1;
    }        
    if( write(fd, val, strlen(val)) < 0)
    {
        std::cout << "Error Number " << errno << std::endl;
        perror("pwm");
        close(fd);
        return -1;
    }

    close(fd); 
    return 0;
}

int pwm_read(char *file, char *val, uint8_t size)
{
    int fd;
    
    fd = open(file, O_RDONLY);
    if(fd < 0)
    {
        std::cout << "Error Number " << errno << std::endl;
        perror("pwm");
        return -1;
    }
    if( read(fd, val, size) < 0)
    {
        std::cout << "Error Number " << errno << std::endl;
        perror("pwm");
        close(fd);
        return -1;    
    }
 
    close(fd);     
    return 0;
}

#include <iostream>
#include <cstring>
#include <cstdint>
#include <cstdlib>

#include "pwm_utils.h"

#define PERIOD 1000000000
#define DUTY_CYCLE 500000000
#define POLARITY "normal"

int main(int argc, char **argv)
{
    if (pwm_write_val("/sys/class/pwm/pwmchip0/export", 0) < 0)
    {
        std::cout << "failed to export" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    if (pwm_write_val("/sys/class/pwm/pwmchip0/pwm0/period", PERIOD) < 0)
    {
        std::cout << "failed to set period" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    if (pwm_write_val("/sys/class/pwm/pwmchip0/pwm0/duty_cycle", DUTY_CYCLE) < 0)
    {
        std::cout << "failed to set duty cycle" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    if (pwm_write_str("/sys/class/pwm/pwmchip0/pwm0/polarity", POLARITY) < 0)
    {
        std::cout << "failed to set polarity" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    if (pwm_write_val("/sys/class/pwm/pwmchip0/pwm0/enable", 1) < 0)
    {
        std::cout << "failed to enable pwm" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    char d_cycle[10] = {0};
    if (pwm_read("/sys/class/pwm/pwmchip0/pwm0/duty_cycle", d_cycle, 9) < 0)
        std::cout << "unable to read duty cycle" << std::endl;
    else
        std::cout << "duty cycle is " << d_cycle << std::endl;

    char polarity[10] = {0};
    if (pwm_read("/sys/class/pwm/pwmchip0/pwm0/polarity", polarity, 6) < 0)
        std::cout << "failed to read polarity" << std::endl;
    else
        std::cout << "polarity is " << polarity << std::endl;

    return 0;
}

The original code works fine for me if I follow the article. How are you launching your container? Are you bind-mounting /sys? If not then the container won’t have access to the PWM interfaces.

Best Regards,
Jeremias

I am trying to do that as I see in the sample they use the following command:

docker run -it --rm -v /sys:/sys pwm-sample

I am trying to add it to my Dockerfile so it will launch when I upload the code and I don’t need to do it every time by adding the following arguments at the end of the file that I sent previously:

# ADD YOUR ARGUMENTS HERE
CMD [ "./app/pesho" , "-it", "--rm", "-v", "/sys:/sys"]

I also tried like this:

# ADD YOUR ARGUMENTS HERE
CMD [ "./app/pesho" , "-it", "--rm", "-v", "/sys:/sys", "pesho_debeliq"]

and:

# ADD YOUR ARGUMENTS HERE
CMD [ "./app/pesho" , "-it", "--rm", "-v", "/sys:/sys" "pesho_debeliq-debug"]

But nothing of them works.
How do I add this to the dockerfile so it can be executed every time when I upload the command. I do not want to do it separately and don’t want to run the command trough the command line like the example but trough VS Code.

That’s not how that is supposed to be used, docker run is executed on the command line to launch your container image. Putting these arguments in the Dockerfile does not make sense.

For ApolloX the container is launched via docker-compose file, therefore you need to translate these arguments to the docker-compose file. To translate -v /sys:/sys into your compose-file look at this example: Volumes top-level element | Docker Documentation

services:
  backend:
    image: awesome/database
    volumes:
      - db-data:/etc/data

  backup:
    image: backup-service
    volumes:
      - db-data:/var/lib/backup/data

volumes:
  db-data:

Notice the volumes property, here you want to specify /sys:/sys, or whatever path you want to be available inside the container. Do that for the docker-compose file that is in your project in VSCode.

Best Regards,
Jeremias

Thank you for your help.

I added the lines the following way:

version: "3.9"
services:
  pesho_debeliq-debug:
    build:
      context: .
      dockerfile: Dockerfile.debug
    image: ${LOCAL_REGISTRY}:5002/pesho_debeliq-debug:${TAG}
    ports:
      - 2230:2230
    volumes:
      - /sys:/sys
      - type: bind
        source: /media
        target: /media

  pesho_debeliq:
    build:
      context: .
      dockerfile: Dockerfile
    image: ${DOCKER_LOGIN}/pesho_debeliq:${TAG}
    volumes:
      - /sys:/sys
      - type: bind
        source: /media
        target: /media

Now the export function is done and when I check the pwm0 with the sudo cat /sys/kernel/debug/pwm it shows sysfs as it should.

But then the error comes immediately on the next pwm_write function:

Error Number 13
failed to set period

Shouldn’t the command like this give access to all the functions available for the PWM?

Strange it works if I build and run it as documented in the README: torizon-samples/README.md at bullseye ¡ toradex/torizon-samples ¡ GitHub

Maybe it’s a side effect of trying to compile and run it as a C++ program? Or possibly the extension is doing something unexpected compared to when I build and run the example outside of the extension. I believe “Error 13” typically corresponds to a permission issue with the file involved. Though that’s strange since as I said I can get it to work outside of the extension. I may need to do more digging to figure out why the extension behaves differently here.

Best Regards,
Jeremias

I tried also runnning the C code as it is given here.
But I get the same error so it shouldn’t be from the fact that is C++.
The only thing I see in the C++ code is that I get a warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings] on the pwm_write() function. But as I said the C code gives me the same error.

Can it be a problem that I have to define something else also and not only - /sys:/sys in the volumes.
This is what I saw is in here.

I tried also runnning the C code as it is given here .
But I get the same error so it shouldn’t be from the fact that is C++.

Are you building and running it outside of the use of the extension as I am? Cause when I do this without the extension it works as expected. I only experience issues if I try to use it in the extension for some reason.

Best Regards,
Jeremias

After some more testing, it seems if you add a sleep in the code between writing to the export file and writing to the period file the code now works. Though the sleep is only required when running this code from the extension. Why this sleep is required it’s not quite clear but our team is looking into it.

Best Regards,
Jeremias

Hi @jeremias.tx ,

You are right, with a sleep function everything works normally, thanks for the tip.
It is a strange behaviour indeed.

But if the reason for that is found I’d like to know why this is the case.

While it’s not confirmed, our best hunch at the moment is that somehow running the debugger (gdb) in parallel with the application somehow affects the execution. It appears when not running the debugger then the execution is as one would expect. Though as I said this is mostly theory at the time.

Another of my coworkers created different PWM logic and has no issues:

#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main() {
    // Export the PWM device
    ofstream export_stream("/sys/class/pwm/pwmchip0/export");
    export_stream << "0" << endl; // Export PWM0
    export_stream.close();

    // Open the sysfs files for the PWM device
    string period_file = "/sys/class/pwm/pwmchip0/pwm0/period";
    string duty_cycle_file = "/sys/class/pwm/pwmchip0/pwm0/duty_cycle";
    string enable_file = "/sys/class/pwm/pwmchip0/pwm0/enable";
    string polarity_file = "/sys/class/pwm/pwmchip0/pwm0/polarity";

    ofstream period_stream(period_file);
    ofstream duty_cycle_stream(duty_cycle_file);
    ofstream enable_stream(enable_file);
    ofstream polarity_stream(polarity_file);

    // Set the period and duty cycle values
    period_stream << "1000000" << endl; // 1ms
    duty_cycle_stream << "500000" << endl; // 50% duty cycle

    // Enable the PWM device
    enable_stream << "1" << endl;

    // Set the polarity of the PWM device to high
    polarity_stream << "normal" << endl;

    // Close the sysfs files
    period_stream.close();
    duty_cycle_stream.close();
    enable_stream.close();
    polarity_stream.close();

    return 0;
}

So perhaps it’s a combination of the specific code and debugger, but honestly it’s hard to say.

Best Regards,
Jeremias

1 Like