From a4e935692a6a4a1922be4aafb5a2dcd7c390416c Mon Sep 17 00:00:00 2001 From: Thomas Kolb Date: Thu, 9 May 2024 23:56:32 +0200 Subject: [PATCH] Initial commit: most infrastructure already done - 7-segment display controlled via PIO and DMA - buzzer works with PWM - millisecond trigger interrupt works --- .gitignore | 2 + CMakeLists.txt | 33 ++++++++++ compile_commands.json | 1 + doc/sseg_codes.txt | 16 +++++ make.sh | 8 +++ pico_sdk_import.cmake | 73 +++++++++++++++++++++ src/main.c | 118 ++++++++++++++++++++++++++++++++++ src/pinout.h | 22 +++++++ src/sseg.c | 145 ++++++++++++++++++++++++++++++++++++++++++ src/sseg.h | 12 ++++ src/sseg.pio | 10 +++ 11 files changed, 440 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 120000 compile_commands.json create mode 100644 doc/sseg_codes.txt create mode 100755 make.sh create mode 100644 pico_sdk_import.cmake create mode 100644 src/main.c create mode 100644 src/pinout.h create mode 100644 src/sseg.c create mode 100644 src/sseg.h create mode 100644 src/sseg.pio diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4fb4fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +.cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..aab2c38 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required (VERSION 3.13) + +include(pico_sdk_import.cmake) + +project (zam_power_timer VERSION 0.1 LANGUAGES C CXX ASM) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +pico_sdk_init() + +add_executable(${CMAKE_PROJECT_NAME} + src/main.c + src/sseg.c + src/sseg.h + src/pinout.h + src/sseg.pio + ) + +pico_generate_pio_header(zam_power_timer ${CMAKE_CURRENT_LIST_DIR}/src/sseg.pio) + + +pico_enable_stdio_usb(${CMAKE_PROJECT_NAME} 1) +pico_enable_stdio_uart(${CMAKE_PROJECT_NAME} 0) +pico_add_extra_outputs(${CMAKE_PROJECT_NAME}) + +target_link_libraries(${CMAKE_PROJECT_NAME} + pico_stdlib + hardware_pwm + hardware_timer + hardware_irq + hardware_pio + hardware_dma) diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000..25eb4b2 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +build/compile_commands.json \ No newline at end of file diff --git a/doc/sseg_codes.txt b/doc/sseg_codes.txt new file mode 100644 index 0000000..fb86ea9 --- /dev/null +++ b/doc/sseg_codes.txt @@ -0,0 +1,16 @@ + 7654 3210 Hex + ABCDEFGP PCBA DEFG +Z: 11011010 0011 1101 0x3C +A: 11101110 0111 0111 0x77 +M: 11101100 0111 0110 0x76 + +0: 11111100 0111 1110 0x7E +1: 01100000 0110 0000 0x60 +2: 11011010 0011 1101 0x3D +3: 11110010 0111 1001 0x79 +4: 01100110 0110 0011 0x63 +5: 10110110 0101 1011 0x5B +6: 10111110 0101 1111 0x5F +7: 11100000 0111 0000 0x70 +8: 11111110 0111 1111 0x7F +9: 11110110 0111 1011 0x7B diff --git a/make.sh b/make.sh new file mode 100755 index 0000000..0a3392a --- /dev/null +++ b/make.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +export PICO_SDK_PATH=/home/thomas/Software/rpi-pico/pico-sdk + +mkdir -p build +cd build +cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .. +make $@ diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..65f8a6f --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,73 @@ +# This is a copy of /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}) diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..4a2bd6c --- /dev/null +++ b/src/main.c @@ -0,0 +1,118 @@ +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "hardware/pwm.h" +#include "hardware/irq.h" + +#include "sseg.h" +#include "pinout.h" + +#include + +const uint8_t all_gpios[] = { + OUTPUT_ENABLE_PIN, +}; + +static volatile bool m_ms_triggered; +static uint32_t m_next_ms_trigger; + +#define MS_TRIG_ALARM_NUM 0 +#define MS_TRIG_ALARM_IRQ TIMER_IRQ_0 + +static void arm_ms_trig_alarm(void) +{ + m_next_ms_trigger += 1000; // microseconds + timer_hw->alarm[MS_TRIG_ALARM_NUM] = m_next_ms_trigger; +} + +static void alarm_irq(void) +{ + // Clear the alarm irq + hw_clear_bits(&timer_hw->intr, 1u << MS_TRIG_ALARM_NUM); + + // set the next alarm + arm_ms_trig_alarm(); + + // Assume alarm 0 has fired + m_ms_triggered = true; +} + +static void configure_ms_trig_alarm(void) +{ + m_ms_triggered = false; + hw_set_bits(&timer_hw->inte, 1u << MS_TRIG_ALARM_NUM); + irq_set_exclusive_handler(MS_TRIG_ALARM_IRQ, alarm_irq); + irq_set_enabled(MS_TRIG_ALARM_IRQ, true); + m_next_ms_trigger = timer_hw->timelr + 10000; +} + +int main() +{ + //stdio_init_all(); + //printf("Hello, world!\n"); + + bool power_switch_state = false; + + for(size_t i = 0; i < sizeof(all_gpios); i++) { + gpio_init(all_gpios[i]); + gpio_put(all_gpios[i], false); + gpio_set_dir(all_gpios[i], true); + } + + gpio_put(OUTPUT_ENABLE_PIN, true); + + // set up PWM for 50 kHz @ 512 steps resolution + uint slice_num = pwm_gpio_to_slice_num(BUZZER_PIN); + + pwm_set_clkdiv_int_frac(slice_num, 244, (14 << 4) / 100); // 125 MHz / 244.14 / 512 = 1 kHz + gpio_set_function(BUZZER_PIN, GPIO_FUNC_PWM); + pwm_set_wrap(slice_num, 512); + pwm_set_chan_level(slice_num, PWM_CHAN_B, 1 * 512 / 100); + pwm_set_enabled(slice_num, true); + + sleep_ms(200); + + pwm_set_enabled(slice_num, false); + pwm_set_clkdiv_int_frac(slice_num, 122, (7 << 4) / 100); // 125 MHz / 122.07 / 512 = 2 kHz + pwm_set_enabled(slice_num, true); + + + sleep_ms(200); + + pwm_set_enabled(slice_num, false); + pwm_set_clkdiv_int_frac(slice_num, 61, (3 << 4) / 100); // 125 MHz / 61.03 / 512 = 4 kHz + pwm_set_enabled(slice_num, true); + + sleep_ms(200); + + pwm_set_enabled(slice_num, false); + gpio_init(BUZZER_PIN); + gpio_put(BUZZER_PIN, false); + gpio_set_dir(BUZZER_PIN, true); + + sleep_ms(2400); + + pwm_set_enabled(slice_num, false); + gpio_init(BUZZER_PIN); + + gpio_put(OUTPUT_ENABLE_PIN, false); + + sseg_init(); + + configure_ms_trig_alarm(); + arm_ms_trig_alarm(); + + uint32_t ms_counter = 0; + while (true) { + sseg_loop(); + + if(m_ms_triggered) { + m_ms_triggered = false; + + ms_counter++; + sseg_set_char(2, '0' + (ms_counter / 100) % 10, false); + sseg_set_char(1, '0' + (ms_counter / 1000) % 10, true); + sseg_set_char(0, '0' + (ms_counter / 10000) % 10, false); + } + } + return 0; +} diff --git a/src/pinout.h b/src/pinout.h new file mode 100644 index 0000000..061e316 --- /dev/null +++ b/src/pinout.h @@ -0,0 +1,22 @@ +#ifndef PINOUT_H +#define PINOUT_H + +#define OUTPUT_ENABLE_PIN 22 + +#define SSEG_MUX_1_PIN 8 +#define SSEG_MUX_2_PIN 9 +#define SSEG_MUX_3_PIN 10 + +#define SSEG_A_PIN 4 +#define SSEG_B_PIN 5 +#define SSEG_C_PIN 6 +#define SSEG_D_PIN 3 +#define SSEG_E_PIN 2 +#define SSEG_F_PIN 1 +#define SSEG_G_PIN 0 +#define SSEG_DP_PIN 7 + +#define BUZZER_PIN 11 + + +#endif // PINOUT_H diff --git a/src/sseg.c b/src/sseg.c new file mode 100644 index 0000000..ecd90ad --- /dev/null +++ b/src/sseg.c @@ -0,0 +1,145 @@ +#include "hardware/gpio.h" +#include "hardware/pio.h" +#include "hardware/dma.h" + +#include "sseg.pio.h" + +#include "sseg.h" +#include "pinout.h" + +// Segment Layout: +// +// .--A--. +// | | +// F B +// | | +// +--G--+ +// | | +// E C +// | | +// .--D--. .DP. + + +#define SSEG_START_PIN 0 + +static volatile uint32_t m_dma_buffer[4] __attribute__((aligned(4*sizeof(uint32_t *)))) = { + 0x0000013D, // segment 0 = 'Z' = '2' + 0x00000277, // segment 1 = A + 0x00000476, // segment 2 = M + 0x00000000, // dummy entry +}; + +static PIO m_pio; +static uint m_sm; + +static int m_dma_chan; + +static uint8_t ascii_to_sseg(char c) +{ + switch(c) { + case '0': + return 0x7E; + + case '1': + return 0x60; + + case 'Z': + case '2': + return 0x3D; + + case '3': + return 0x79; + + case '4': + return 0x63; + + case '5': + return 0x5B; + + case '6': + return 0x5F; + + case '7': + return 0x70; + + case '8': + return 0x7F; + + case '9': + return 0x7B; + + case 'M': + return 0x76; + + case 'A': + return 0x77; + + default: // '-' + return 0x01; + } +} + +void sseg_init(void) +{ + // set up PIO for 7-segment display + m_pio = pio0; + uint offset = pio_add_program(m_pio, &sseg_program); + m_sm = pio_claim_unused_sm(m_pio, true); + pio_sm_config c = sseg_program_get_default_config(offset); + + sm_config_set_clkdiv_int_frac(&c, 2000, 0); + + sm_config_set_out_pins(&c, SSEG_START_PIN, 11); + sm_config_set_set_pins(&c, SSEG_START_PIN+8, 3); + + pio_gpio_init(m_pio, SSEG_A_PIN); + pio_gpio_init(m_pio, SSEG_B_PIN); + pio_gpio_init(m_pio, SSEG_C_PIN); + pio_gpio_init(m_pio, SSEG_D_PIN); + pio_gpio_init(m_pio, SSEG_E_PIN); + pio_gpio_init(m_pio, SSEG_F_PIN); + pio_gpio_init(m_pio, SSEG_G_PIN); + pio_gpio_init(m_pio, SSEG_DP_PIN); + pio_gpio_init(m_pio, SSEG_MUX_1_PIN); + pio_gpio_init(m_pio, SSEG_MUX_2_PIN); + pio_gpio_init(m_pio, SSEG_MUX_3_PIN); + + pio_sm_set_consecutive_pindirs(m_pio, m_sm, SSEG_START_PIN, 11, true); + + pio_sm_init(m_pio, m_sm, offset, &c); + + m_dma_chan = dma_claim_unused_channel(true); + dma_channel_config dma_config = dma_channel_get_default_config(m_dma_chan); + + channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32); + channel_config_set_read_increment(&dma_config, true); + channel_config_set_write_increment(&dma_config, false); + channel_config_set_ring(&dma_config, false, 4); // read ringbuffer with 2^4 = 16 bytes = 4 entries + channel_config_set_dreq(&dma_config, DREQ_PIO0_TX0); + + dma_channel_configure( + m_dma_chan, + &dma_config, + &pio0_hw->txf[0], // Write address (only need to set this once) + m_dma_buffer, // Read address + 1<<14, // Write the buffer many times, then halt and interrupt + true // Start right now + ); + + pio_sm_set_enabled(m_pio, m_sm, true); +} + +void sseg_loop(void) +{ + // restart the DMA channel if it has completed the transfer + if((dma_hw->intr & (1u << m_dma_chan)) != 0) { + dma_hw->ints0 = 1u << m_dma_chan; + dma_channel_start(m_dma_chan); + } +} + +void sseg_set_char(uint8_t seg, char c, bool with_dot) +{ + uint dot_bit = with_dot ? 0x80 : 0x00; + m_dma_buffer[seg] = (m_dma_buffer[seg] & 0xFFFFFF00) | dot_bit | ascii_to_sseg(c); +} diff --git a/src/sseg.h b/src/sseg.h new file mode 100644 index 0000000..56edfcf --- /dev/null +++ b/src/sseg.h @@ -0,0 +1,12 @@ +#ifndef SSEG_H +#define SSEG_H + +#include +#include + +void sseg_init(void); +void sseg_loop(void); + +void sseg_set_char(uint8_t seg, char c, bool with_dot); + +#endif // SSEG_H diff --git a/src/sseg.pio b/src/sseg.pio new file mode 100644 index 0000000..bc2a66e --- /dev/null +++ b/src/sseg.pio @@ -0,0 +1,10 @@ +.program sseg + +; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is +; empty. Write the least significant bit to the OUT pin group. + +loop: + set pins, 0x00 ; set MUX pins to 0 + pull + out pins, 11 [31] ; write the data from the FIFO to the outputs and wait some time + jmp loop [31] ; repeat and wait some extra time