User:Brendlefly62/Radxa x4 N100 sbc with RP2040/Use the RP2040 Microcontroller/Validate serial and PWM with a new pwm fan control program
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
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
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
Now create the script
user $
nano /home/joe/script/Temperature.py
/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
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
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
/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
/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
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
/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"
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'
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.