PWM auf Pins 0 bis 20
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
# Pico 1 W: RP2040 + CYW43439.
|
||||||
|
set(PICO_BOARD pico_w)
|
||||||
|
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
|
||||||
|
project(ampel C CXX ASM)
|
||||||
|
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
add_executable(ampel
|
||||||
|
ampel.c
|
||||||
|
pwm.c
|
||||||
|
hw_pwm.c
|
||||||
|
pio_pwm.c
|
||||||
|
)
|
||||||
|
|
||||||
|
# PIO-Assembler: generiert pio_pwm.pio.h in den Build-Ordner.
|
||||||
|
pico_generate_pio_header(ampel ${CMAKE_CURRENT_LIST_DIR}/pio_pwm.pio)
|
||||||
|
|
||||||
|
target_link_libraries(ampel
|
||||||
|
pico_stdlib
|
||||||
|
pico_cyw43_arch_none
|
||||||
|
hardware_pwm
|
||||||
|
hardware_pio
|
||||||
|
hardware_clocks
|
||||||
|
)
|
||||||
|
|
||||||
|
# stdio ueber USB-CDC — praktisch fuers Debuggen ueber tio.
|
||||||
|
pico_enable_stdio_usb(ampel 1)
|
||||||
|
pico_enable_stdio_uart(ampel 0)
|
||||||
|
|
||||||
|
pico_add_extra_outputs(ampel)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/cyw43_arch.h"
|
||||||
|
#include "pwm.h"
|
||||||
|
|
||||||
|
// Alle 21 Kanaele synchron:
|
||||||
|
// 500 ms Ramp-Up, 500 ms voll an, 500 ms Ramp-Down, 500 ms aus.
|
||||||
|
static void set_all(uint16_t level) {
|
||||||
|
for (unsigned ch = 0; ch < PWM_TOTAL_COUNT; ch++) {
|
||||||
|
pwm_ch_set(ch, level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
stdio_init_all();
|
||||||
|
|
||||||
|
if (cyw43_arch_init()) {
|
||||||
|
printf("cyw43_arch_init fehlgeschlagen\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pwm_all_init();
|
||||||
|
|
||||||
|
printf("ampel: alle %u Kanaele synchron Ramp-Up / An / Ramp-Down / Aus\n",
|
||||||
|
PWM_TOTAL_COUNT);
|
||||||
|
|
||||||
|
const int ramp_steps = 50;
|
||||||
|
const int step_ms = 500 / ramp_steps;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
|
||||||
|
for (int i = 0; i <= ramp_steps; i++) {
|
||||||
|
set_all((uint16_t)((i * PWM_MAX_LEVEL) / ramp_steps));
|
||||||
|
sleep_ms(step_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_all(PWM_MAX_LEVEL);
|
||||||
|
sleep_ms(500);
|
||||||
|
|
||||||
|
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
|
||||||
|
for (int i = ramp_steps; i >= 0; i--) {
|
||||||
|
set_all((uint16_t)((i * PWM_MAX_LEVEL) / ramp_steps));
|
||||||
|
sleep_ms(step_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_all(0);
|
||||||
|
sleep_ms(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Baut die ampel-Firmware und flasht sie optional gleich auf den Pico.
|
||||||
|
#
|
||||||
|
# ./build.sh -> nur bauen
|
||||||
|
# ./build.sh -f -> bauen und flashen (Pico muss im BOOTSEL-Modus sein)
|
||||||
|
# ./build.sh --clean -> vorher build/ loeschen
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
SDK_PATH="/home/irrlicht/Projekte/pico/reference/pico-sdk"
|
||||||
|
FLASH_SCRIPT="$HERE/../flash.sh"
|
||||||
|
|
||||||
|
DO_FLASH=0
|
||||||
|
DO_CLEAN=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-f|--flash) DO_FLASH=1 ;;
|
||||||
|
--clean) DO_CLEAN=1 ;;
|
||||||
|
*) echo "Unbekanntes Argument: $arg" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$HERE"
|
||||||
|
|
||||||
|
if [ $DO_CLEAN -eq 1 ]; then
|
||||||
|
echo "Loesche build/ ..."
|
||||||
|
rm -rf build
|
||||||
|
fi
|
||||||
|
|
||||||
|
# cmake nur bei fehlendem Build-Ordner explizit aufrufen — sonst reicht ninja,
|
||||||
|
# das erkennt CMakeLists.txt-Aenderungen selbst.
|
||||||
|
if [ ! -f build/build.ninja ]; then
|
||||||
|
cmake -S . -B build -G Ninja -DPICO_SDK_PATH="$SDK_PATH"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ninja -C build
|
||||||
|
|
||||||
|
UF2="$HERE/build/ampel.uf2"
|
||||||
|
echo
|
||||||
|
echo "Fertig: $UF2 ($(stat -c%s "$UF2") Byte)"
|
||||||
|
|
||||||
|
if [ $DO_FLASH -eq 1 ]; then
|
||||||
|
echo
|
||||||
|
"$FLASH_SCRIPT" "$UF2"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#include "hw_pwm.h"
|
||||||
|
#include "pwm.h"
|
||||||
|
#include "hardware/pwm.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
|
||||||
|
void hw_pwm_init(void) {
|
||||||
|
// clkdiv so waehlen, dass (wrap+1) Ticks pro Periode 1 kHz ergeben.
|
||||||
|
// Bei 125 MHz sysclk und wrap=999 kommt exakt 125.0 raus.
|
||||||
|
float div = (float)clock_get_hz(clk_sys) / ((PWM_MAX_LEVEL + 1) * PWM_FREQ_HZ);
|
||||||
|
|
||||||
|
for (unsigned pin = PWM_HW_FIRST_PIN; pin < PWM_HW_FIRST_PIN + PWM_HW_COUNT; pin++) {
|
||||||
|
gpio_set_function(pin, GPIO_FUNC_PWM);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8 Slices decken GP0..GP15 komplett ab (je 2 Kanaele A/B pro Slice).
|
||||||
|
for (unsigned slice = 0; slice < 8; slice++) {
|
||||||
|
pwm_set_wrap(slice, PWM_MAX_LEVEL);
|
||||||
|
pwm_set_clkdiv(slice, div);
|
||||||
|
pwm_set_both_levels(slice, 0, 0);
|
||||||
|
pwm_set_enabled(slice, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_pwm_set(unsigned pin, uint16_t level) {
|
||||||
|
if (level > PWM_MAX_LEVEL) level = PWM_MAX_LEVEL;
|
||||||
|
pwm_set_chan_level(pwm_gpio_to_slice_num(pin), pwm_gpio_to_channel(pin), level);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void hw_pwm_init(void);
|
||||||
|
void hw_pwm_set(unsigned pin, uint16_t level);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||||
|
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG master
|
||||||
|
GIT_SUBMODULES_RECURSE FALSE
|
||||||
|
)
|
||||||
|
else ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG master
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
FetchContent_Populate(pico_sdk)
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Erste 16 Kanaele: HW-PWM auf GP0..GP15 (8 Slices, je 2 Kanaele).
|
||||||
|
#define PWM_HW_FIRST_PIN 0
|
||||||
|
#define PWM_HW_COUNT 16
|
||||||
|
|
||||||
|
// Kanaele 16..20: PIO-PWM auf GP16..GP20 (5 State Machines).
|
||||||
|
#define PWM_PIO_FIRST_PIN 16
|
||||||
|
#define PWM_PIO_COUNT 5
|
||||||
|
|
||||||
|
#define PWM_TOTAL_COUNT (PWM_HW_COUNT + PWM_PIO_COUNT)
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
#include "pio_pwm.h"
|
||||||
|
#include "pwm.h"
|
||||||
|
#include "pins.h"
|
||||||
|
#include "hardware/pio.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "pico/error.h"
|
||||||
|
#include "pio_pwm.pio.h"
|
||||||
|
|
||||||
|
// 5 State Machines koexistieren mit dem CYW43-Chip, der einen SM auf pio0 haelt.
|
||||||
|
// Wir claimen dynamisch: erst pio1 leerraeumen (4 SMs), dann eine SM aus pio0.
|
||||||
|
// Das Programm wird pro benutztem PIO-Block einmal geladen.
|
||||||
|
static struct {
|
||||||
|
PIO pio;
|
||||||
|
uint sm;
|
||||||
|
uint pin;
|
||||||
|
} g_channels[PWM_PIO_COUNT];
|
||||||
|
|
||||||
|
static int try_claim(PIO pio) {
|
||||||
|
// pio_claim_unused_sm(pio, false) liefert -1 statt panic bei Misserfolg.
|
||||||
|
return pio_claim_unused_sm(pio, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pio_pwm_init(void) {
|
||||||
|
int offset[2] = {-1, -1}; // Programm-Offset in pio0 / pio1, -1 = noch nicht geladen.
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < PWM_PIO_COUNT; i++) {
|
||||||
|
uint pin = PWM_PIO_FIRST_PIN + i;
|
||||||
|
|
||||||
|
// Erst pio1 abgrasen (kein CYW43 dort), dann pio0.
|
||||||
|
PIO pio = pio1;
|
||||||
|
int sm = try_claim(pio1);
|
||||||
|
if (sm < 0) {
|
||||||
|
pio = pio0;
|
||||||
|
sm = try_claim(pio0);
|
||||||
|
if (sm < 0) {
|
||||||
|
panic("Keine freie PIO-SM fuer PWM-Kanal %u", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned idx = (pio == pio0) ? 0 : 1;
|
||||||
|
if (offset[idx] < 0) {
|
||||||
|
if (!pio_can_add_program(pio, &pio_pwm_program)) {
|
||||||
|
panic("PIO-Programm passt nicht mehr auf PIO%u", idx);
|
||||||
|
}
|
||||||
|
offset[idx] = (int)pio_add_program(pio, &pio_pwm_program);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_channels[i].pio = pio;
|
||||||
|
g_channels[i].sm = (uint)sm;
|
||||||
|
g_channels[i].pin = pin;
|
||||||
|
|
||||||
|
// Cycle-Bilanz pro PWM-Periode:
|
||||||
|
// 3 Zyklen fuer pull/mov/mov (einmalig)
|
||||||
|
// + 3 Zyklen * (PWM_MAX_LEVEL + 1) fuer die Zaehlschleife
|
||||||
|
// Bei PWM_MAX_LEVEL=999 sind das 3003 PIO-Zyklen pro 1-kHz-Periode.
|
||||||
|
float cycles_per_period = 3.0f * (PWM_MAX_LEVEL + 1) + 3.0f;
|
||||||
|
float div = (float)clock_get_hz(clk_sys) / (cycles_per_period * PWM_FREQ_HZ);
|
||||||
|
|
||||||
|
pio_pwm_program_init(pio, (uint)sm, (uint)offset[idx], pin, div);
|
||||||
|
|
||||||
|
// Periode ins ISR laden: FIFO->OSR->ISR via SM-EXEC, ohne dass die
|
||||||
|
// Hauptschleife laeuft. Danach wird der eigentliche Level (Startwert 0)
|
||||||
|
// ins FIFO gelegt, den die Schleife via `pull noblock` in X uebernimmt.
|
||||||
|
pio_sm_set_enabled(pio, (uint)sm, false);
|
||||||
|
pio_sm_clear_fifos(pio, (uint)sm);
|
||||||
|
pio_sm_put_blocking(pio, (uint)sm, PWM_MAX_LEVEL);
|
||||||
|
pio_sm_exec(pio, (uint)sm, pio_encode_pull(false, false));
|
||||||
|
pio_sm_exec(pio, (uint)sm, pio_encode_out(pio_isr, 32));
|
||||||
|
|
||||||
|
pio_sm_put_blocking(pio, (uint)sm, 0);
|
||||||
|
|
||||||
|
pio_sm_set_enabled(pio, (uint)sm, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pio_pwm_set(unsigned index, uint16_t level) {
|
||||||
|
if (index >= PWM_PIO_COUNT) return;
|
||||||
|
if (level > PWM_MAX_LEVEL) level = PWM_MAX_LEVEL;
|
||||||
|
pio_sm_put_blocking(g_channels[index].pio, g_channels[index].sm, level);
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void pio_pwm_init(void);
|
||||||
|
void pio_pwm_set(unsigned index, uint16_t level);
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
; Klassisches PIO-PWM (Vorlage aus pico-examples).
|
||||||
|
; Pro Periode: pull noblock holt den neuen Level aus dem FIFO
|
||||||
|
; (oder recycelt den letzten aus X, wenn das FIFO leer ist).
|
||||||
|
; Y zaehlt von Periode runter auf 0; sobald X == Y wird der Pin per side-set hoch.
|
||||||
|
; Beim naechsten pull wird er wieder auf 0 gezogen.
|
||||||
|
|
||||||
|
.program pio_pwm
|
||||||
|
.side_set 1 opt
|
||||||
|
|
||||||
|
pull noblock side 0
|
||||||
|
mov x, osr
|
||||||
|
mov y, isr
|
||||||
|
countloop:
|
||||||
|
jmp x!=y noset
|
||||||
|
jmp skip side 1
|
||||||
|
noset:
|
||||||
|
nop
|
||||||
|
skip:
|
||||||
|
jmp y-- countloop
|
||||||
|
|
||||||
|
% c-sdk {
|
||||||
|
static inline void pio_pwm_program_init(PIO pio, uint sm, uint offset, uint pin, float clkdiv) {
|
||||||
|
pio_gpio_init(pio, pin);
|
||||||
|
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
|
||||||
|
pio_sm_config c = pio_pwm_program_get_default_config(offset);
|
||||||
|
sm_config_set_sideset_pins(&c, pin);
|
||||||
|
sm_config_set_clkdiv(&c, clkdiv);
|
||||||
|
pio_sm_init(pio, sm, offset, &c);
|
||||||
|
}
|
||||||
|
%}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#include "pwm.h"
|
||||||
|
#include "pins.h"
|
||||||
|
#include "hw_pwm.h"
|
||||||
|
#include "pio_pwm.h"
|
||||||
|
|
||||||
|
void pwm_all_init(void) {
|
||||||
|
hw_pwm_init();
|
||||||
|
pio_pwm_init();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pwm_ch_set(unsigned channel, uint16_t level) {
|
||||||
|
if (channel < PWM_HW_COUNT) {
|
||||||
|
hw_pwm_set(PWM_HW_FIRST_PIN + channel, level);
|
||||||
|
} else if (channel < PWM_TOTAL_COUNT) {
|
||||||
|
pio_pwm_set(channel - PWM_HW_COUNT, level);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "pins.h"
|
||||||
|
|
||||||
|
// Gemeinsame API fuer HW- und PIO-PWM. Kanalnummern:
|
||||||
|
// 0..15 -> HW-PWM auf GP0..GP15
|
||||||
|
// 16..20 -> PIO-PWM auf GP16..GP20
|
||||||
|
// Alle Kanaele laufen mit gleicher Frequenz und Aufloesung.
|
||||||
|
|
||||||
|
#define PWM_FREQ_HZ 1000u
|
||||||
|
#define PWM_MAX_LEVEL 999u
|
||||||
|
|
||||||
|
void pwm_all_init(void);
|
||||||
|
void pwm_ch_set(unsigned channel, uint16_t level);
|
||||||
Reference in New Issue
Block a user