User:Brendlefly62/Radxa x4 N100 sbc with RP2040/Use the RP2040 Microcontroller/Validate serial and PWM with a new pwm fan control program

From Gentoo Wiki
Jump to:navigation Jump to:search

In this example, Temperature.py on the host will send temp data to the microcontroller which will set the PWM level of a GPIO output (for a pwm_fan) depending on temperature relative to a programmed threshhold.

Create Temperature.py on the X4 host

This python script will require serial and psutils modules, so take care of that first

root #emerge -av dev-python/pyserial net-dialup/minicom dev-python/psutil app-benchmarks/stress

use pip to install pyserial and psutil

Warning
Gentoo's pip will warn that it is dangerous to use pip as root and that a virtual environment should be used
user $python -m venv --system-site-packages /home/joe/py_venv
user $. /home/joe/py_venv/bin/activate
user $pip3 install pyserial psutil

Prereauisite

Tip
To save time troubleshooting later, first identify the X4 serial port that connects the RP2040 to the X4 internally. If the script has this wrong, it will output error messages when it fails to configure the serial port. There may be other ways to do this, but the 'ttyS0' value provided in Radxa's example did not work on the board used in this project. Noting that "sudo cat /dev/ttyS0" generates the same error, the following technique works --
root #for i in $(seq 0 31); do echo -n "$i: "; eval cat /dev/ttyS$i; done
0: cat: /dev/ttyS0: Input/output error
1: cat: /dev/ttyS1: Input/output error
2: cat: /dev/ttyS2: Input/output error
3: cat: /dev/ttyS3: Input/output error
4: 5: cat: /dev/ttyS5: Input/output error
6: cat: /dev/ttyS6: Input/output error
7: cat: /dev/ttyS7: Input/output error
Note that no error was generated for /dev/ttyS4 -- this is the correct serial port.

Now create the script

user $nano /home/joe/script/Temperature.py
FILE /home/joe/script/Temperature.py
#!/usr/bin/python3
import serial
import psutil
import time

#SERIAL_PORT = '/dev/ttyS0'   (wrong port...)
SERIAL_PORT = '/dev/ttyS4'
BAUD_RATE = 115200

set = serial.Serial(SERIAL_PORT, BAUD_RATE)

def get_cpu_temperature():
    # get temperature from PC
    temps = psutil.sensors_temperatures()
    if 'coretemp' in temps:
        cpu_temp = temps['coretemp'][0].current
        return cpu_temp
    else:
        return None

try:
    while True:
        temp = get_cpu_temperature()
        if temp is not None:
            print(f"CPU Temperature: {temp}°C")
            set.write(f"{temp}\n".encode())
        else:
            print("Unable to read temperature.")
        time.sleep(1)
except KeyboardInterrupt:
    set.close()
    print("Program terminated.")

Add a directory for pwm_fan inside pico-examples/pwm/

user $mkdir /home/joe/pico-examples/pwm/pwm_fan/

update the parent directory's CMakeLists.txt

user $nano pico-examples/pwm/CMakeLists.txt
FILE pico-examples/pwm/CMakeLists.txt
if (TARGET hardware_pwm)
    add_subdirectory_exclude_platforms(hello_pwm)
    add_subdirectory_exclude_platforms(led_fade)
    add_subdirectory_exclude_platforms(measure_duty_cycle)
    add_subdirectory_exclude_platforms(pwm_fan)
else()
    message("Skipping PWM examples as hardware_pwm is unavailable on this platform")
endif()

Populate the new pwm_fan directory

Tip
Note: the default threshhold for activation of the pwm_fan in Radxa's example is 60 deg C. If experimentation reveals that even with use of app-benchmarks/stress the core temperature does not exceed the threshold, then the threshhold should be adjusted, as was done in the script below, for demonstration purposes.
user $nano /home/joe/pico-examples/pwm/pwm_fan/pwm_fan.c
FILE /home/joe/pico-examples/pwm/pwm_fan/pwm_fan.c
    #include <stdio.h>
    #include <stdlib.h>
    #include "pico/stdlib.h"
    #include "hardware/uart.h"
    #include "hardware/pwm.h"

    #define UART_ID uart0
    #define BAUD_RATE 115200
    #define UART_TX_PIN 0
    #define UART_RX_PIN 1
//    #define FAN_PWM_PIN 28     already using 28 for LED; use GPIO26 instead
    #define FAN_PWM_PIN 26
//    #define TEMP_THRESHOLD 60.0    // (set lower instead, for demonstration...)
    #define TEMP_THRESHOLD 45.0

    void set_pwm_duty_cycle(uint slice_num, uint channel, float duty_cycle) {
        if (duty_cycle < 0.0f) duty_cycle = 0.0f;
        if (duty_cycle > 100.0f) duty_cycle = 100.0f;
        uint16_t level = (uint16_t)(duty_cycle * (float)(1 << 16) / 100.0f);
        pwm_set_gpio_level(FAN_PWM_PIN, level);
    }

    int main() {
        stdio_init_all();

        uart_init(UART_ID, BAUD_RATE);
        gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
        gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);

        uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE);
        uart_set_fifo_enabled(UART_ID, false);

        gpio_set_function(FAN_PWM_PIN, GPIO_FUNC_PWM);
        uint slice_num = pwm_gpio_to_slice_num(FAN_PWM_PIN);
        pwm_config config = pwm_get_default_config();
        pwm_config_set_clkdiv(&config, 4.0f);
        pwm_init(slice_num, &config, true);

        char buffer[32];
        int index = 0;

        printf("Waiting for data...\n");

        while (1) {
            if (uart_is_readable(UART_ID)) {
                char c = uart_getc(UART_ID);
                if (c == '\n') {
                    buffer[index] = '\0';
                    float temperature = atof(buffer);
                    printf("Received temperature: %.2f°C\n", temperature);
                    if (temperature > TEMP_THRESHOLD) {
                        set_pwm_duty_cycle(slice_num, PWM_CHAN_A, 100.0f);
                    } else {
                        set_pwm_duty_cycle(slice_num, PWM_CHAN_A, 0.0f);
                    }
                    index = 0;
                } else {
                    buffer[index++] = c;
                    if (index >= sizeof(buffer)) {
                        index = sizeof(buffer) - 1;
                    }
                }
            }
        }

        return 0;
    }
user $nano /home/joe/pico-examples/pwm/pwm_fan/CMakeLists.txt
FILE /home/joe/pico-examples/pwm/pwm_fan/CMakeLists.txt
    add_executable(pwm_fan
        pwm_fan.c
        )

    # pull in common dependencies and additional pwm hardware support
    target_link_libraries(pwm_fan pico_stdlib hardware_pwm)

    # create map/bin/hex file etc.
    pico_add_extra_outputs(pwm_fan)

    # add url via pico_set_program_url
    example_auto_set_url(pwm_fan)

Compile

user $cd /home/joe/pico-examples/build
user $export PICO_SDK_PATH=/home/joe/pico-sdk/
user $cmake .. && make -j(nproc)

Now re-flash the RP2040 with the new program

Note
The joetoo git repository https://github.com/JosephBrendler/joetoo.git contains a package that automates this "reflash" process and includes an eselect module to select from among config files in /etc/reflash_rp2/ to identify the program to be loaded. See dev-sbc/reflash_rp2::joetoo

This time use a software method to "reboot" the microcontroller

user $nano /home/joe/script/rp2040_usb.sh
FILE /home/joe/script/rp2040_usb.sh
#! /bin/bash
gpioset gpiochip0 17=1
gpioset gpiochip0 7=1
sleep 1
gpioset gpiochip0 17=0
gpioset gpiochip0 7=0
user $sudo rp2040_usb.sh
user $sleep 2
user $sudo blkid {{|}} grep '/dev/sd'
/dev/sdb1: SEC_TYPE="msdos" LABEL_FATBOOT="RPI-RP2" LABEL="RPI-RP2" UUID="0009-C79E" BLOCK_SIZE="512" TYPE="vfat" PARTUUID="0009c79e-01"
Note
The rp2040 will show up in usb-storage mode after a couple of seconds. Use blkid to identify it; note that the label of the rp2040 device on the Radxa X4 should be "RPI-RP2" -- the UUID nad PARTUUID may change every time the device is rebooted, but the label will be constant, and this information can enable you to extend the script above to mount the device as is done manually below.

mount the device

user $sudo mkdir /mnt/rp2040
user $blkid
user $sudo mount LABEL=RPI-RP2 /mnt/rp2040
user $ls -al /mnt/rp2040
total 28
drwxr-xr-x  2 root root 16384 Dec 31  1969 .
drwxr-xr-x 13 root root  4096 Mar  8 12:54 ..
-r-xr-xr-x  1 root root   241 Sep  5  2008 INDEX.HTM
-r-xr-xr-x  1 root root    62 Sep  5  2008 INFO_UF2.TXT

Now flash the new program to the device

user $sudo cp -v /home/joe/pico-examples/build/pwm/pwm_fan/pwm_fan.uf2 /mnt/rp2040/
'/home/joe/pico-examples/build/pwm/pwm_fan/pwm_fan.uf2' -> '/mnt/rp2040/pwm_fan.uf2'
Important
Don't forget to un-mount the device...
user $sudo umount /mnt/rp2040

Now observe the new program running

Run app-benchmarks/stress in the background on the host X4 to get it to heat up temporarily, so the PWM signal transition will be visible, for demonstration purposes, and run Temperature.py to send system temperature to the rp2040 via internal serial connection --

user $stress -c 4 -i 4 -m 4 --timeout 30 & python3 Temperature.py
[1] 27166
stress: info: [27166] dispatching hogs: 4 cpu, 4 io, 4 vm, 0 hdd
CPU Temperature: 39.0°C
CPU Temperature: 64.0°C
CPU Temperature: 65.0°C
CPU Temperature: 65.0°C
CPU Temperature: 65.0°C
stress: info: [27166] successful run completed in 5s
CPU Temperature: 46.0°C
CPU Temperature: 42.0°C
CPU Temperature: 41.0°C
CPU Temperature: 41.0°C
CPU Temperature: 41.0°C
^CProgram terminated.
[1]+  Done                    stress -c 4 -i 4 -m 4 --timeout 5


Here is a short video (~4 seconds) centered around the end of the stress run shown in the above output. The red LED simulates a pwm fan, and it can be seen to be "on" when the temperature is up, due to stress, and it goes "off" when the temperature drops after the stress run is complete.