altos/telelco-v3.0: Control LCD backlight with PWM
authorKeith Packard <keithp@keithp.com>
Sun, 28 Jan 2024 00:03:35 +0000 (16:03 -0800)
committerKeith Packard <keithp@keithp.com>
Thu, 1 Feb 2024 01:50:19 +0000 (17:50 -0800)
Signed-off-by: Keith Packard <keithp@keithp.com>
src/stm32f1/ao_pwm_stm.c [new file with mode: 0644]
src/stm32f1/stm32f1.h
src/telelco-v3.0/Makefile
src/telelco-v3.0/ao_lco_v3.c
src/telelco-v3.0/ao_pins.h
src/telelco-v3.0/ao_telelco.c

diff --git a/src/stm32f1/ao_pwm_stm.c b/src/stm32f1/ao_pwm_stm.c
new file mode 100644 (file)
index 0000000..dcf9d0e
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright © 2024 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+#include "ao_pwm.h"
+
+static uint8_t pwm_running;
+
+static uint16_t        pwm_value[NUM_PWM];
+
+static void
+ao_pwm_up(void)
+{
+       if (pwm_running++ == 0) {
+               struct stm_tim234       *tim = &AO_PWM_TIMER;
+
+               tim->ccr1 = 0;
+               tim->ccr2 = 0;
+               tim->ccr3 = 0;
+               tim->ccr4 = 0;
+               tim->arr = PWM_MAX - 1; /* turn on the timer */
+               tim->cr1 = ((STM_TIM234_CR1_CKD_1 << STM_TIM234_CR1_CKD) |
+                           (0 << STM_TIM234_CR1_ARPE) |
+                           (STM_TIM234_CR1_CMS_EDGE << STM_TIM234_CR1_CMS) |
+                           (STM_TIM234_CR1_DIR_UP << STM_TIM234_CR1_DIR) |
+                           (0 << STM_TIM234_CR1_OPM) |
+                           (0 << STM_TIM234_CR1_URS) |
+                           (0 << STM_TIM234_CR1_UDIS) |
+                           (1 << STM_TIM234_CR1_CEN));
+
+               /* Set the timer running */
+               tim->egr = (1 << STM_TIM234_EGR_UG);
+       }
+}
+
+static void
+ao_pwm_down(void)
+{
+       if (--pwm_running == 0) {
+               struct stm_tim234       *tim = &AO_PWM_TIMER;
+
+               tim->arr = 0;
+               tim->cr1 = ((STM_TIM234_CR1_CKD_1 << STM_TIM234_CR1_CKD) |
+                           (0 << STM_TIM234_CR1_ARPE) |
+                           (STM_TIM234_CR1_CMS_EDGE << STM_TIM234_CR1_CMS) |
+                           (STM_TIM234_CR1_DIR_UP << STM_TIM234_CR1_DIR) |
+                           (0 << STM_TIM234_CR1_OPM) |
+                           (0 << STM_TIM234_CR1_URS) |
+                           (0 << STM_TIM234_CR1_UDIS) |
+                           (0 << STM_TIM234_CR1_CEN));
+
+               /* Stop the timer */
+               tim->egr = (1 << STM_TIM234_EGR_UG);
+       }
+}
+
+void
+ao_pwm_set(uint8_t pwm, uint16_t value)
+{
+       struct stm_tim234       *tim = &AO_PWM_TIMER;
+
+#if PWM_MAX < UINT16_MAX
+       if (value > PWM_MAX)
+               value = PWM_MAX;
+#endif
+       if (value != 0) {
+               if (pwm_value[pwm] == 0)
+                       ao_pwm_up();
+       }
+       switch (pwm) {
+       case 0:
+               tim->ccr1 = value;
+               break;
+       case 1:
+               tim->ccr2 = value;
+               break;
+       case 2:
+               tim->ccr3 = value;
+               break;
+       case 3:
+               tim->ccr4 = value;
+               break;
+       }
+       if (value == 0) {
+               if (pwm_value[pwm] != 0)
+                       ao_pwm_down();
+       }
+       pwm_value[pwm] = value;
+}
+
+static void
+ao_pwm_cmd(void)
+{
+       uint8_t ch;
+       uint16_t val;
+
+       ch = (uint8_t) ao_cmd_decimal();
+       val = (uint16_t) ao_cmd_decimal();
+       if (ao_cmd_status != ao_cmd_success)
+               return;
+
+       printf("Set channel %d to %d\n", ch, val);
+       ao_pwm_set(ch, val);
+}
+
+static const struct ao_cmds ao_pwm_cmds[] = {
+       { ao_pwm_cmd,   "P <ch> <val>\0Set PWM ch to val" },
+       { 0, NULL },
+};
+
+void
+ao_pwm_init(void)
+{
+       struct stm_tim234       *tim = &AO_PWM_TIMER;
+
+       stm_rcc.apb1enr |= (1 << AO_PWM_TIMER_ENABLE);
+
+       tim->cr1 = 0;
+       tim->psc = AO_PWM_TIMER_SCALE - 1;
+       tim->cnt = 0;
+       tim->ccer = ((1 << STM_TIM234_CCER_CC1E) |
+                    (0 << STM_TIM234_CCER_CC1P) |
+                    (1 << STM_TIM234_CCER_CC2E) |
+                    (0 << STM_TIM234_CCER_CC2P) |
+                    (1 << STM_TIM234_CCER_CC3E) |
+                    (0 << STM_TIM234_CCER_CC3P) |
+                    (1 << STM_TIM234_CCER_CC4E) |
+                    (0 << STM_TIM234_CCER_CC4P));
+
+       tim->ccmr1 = ((0 << STM_TIM234_CCMR1_OC2CE) |
+                     (STM_TIM234_CCMR1_OC2M_PWM_MODE_1 << STM_TIM234_CCMR1_OC2M) |
+                     (0 << STM_TIM234_CCMR1_OC2PE) |
+                     (0 << STM_TIM234_CCMR1_OC2FE) |
+                     (STM_TIM234_CCMR1_CC2S_OUTPUT << STM_TIM234_CCMR1_CC2S) |
+
+                     (0 << STM_TIM234_CCMR1_OC1CE) |
+                     (STM_TIM234_CCMR1_OC1M_PWM_MODE_1 << STM_TIM234_CCMR1_OC1M) |
+                     (0 << STM_TIM234_CCMR1_OC1PE) |
+                     (0 << STM_TIM234_CCMR1_OC1FE) |
+                     (STM_TIM234_CCMR1_CC1S_OUTPUT << STM_TIM234_CCMR1_CC1S));
+
+
+       tim->ccmr2 = ((0 << STM_TIM234_CCMR2_OC4CE) |
+                     (STM_TIM234_CCMR2_OC4M_PWM_MODE_1 << STM_TIM234_CCMR2_OC4M) |
+                     (0 << STM_TIM234_CCMR2_OC4PE) |
+                     (0 << STM_TIM234_CCMR2_OC4FE) |
+                     (STM_TIM234_CCMR2_CC4S_OUTPUT << STM_TIM234_CCMR2_CC4S) |
+
+                     (0 << STM_TIM234_CCMR2_OC3CE) |
+                     (STM_TIM234_CCMR2_OC3M_PWM_MODE_1 << STM_TIM234_CCMR2_OC3M) |
+                     (0 << STM_TIM234_CCMR2_OC3PE) |
+                     (0 << STM_TIM234_CCMR2_OC3FE) |
+                     (STM_TIM234_CCMR2_CC3S_OUTPUT << STM_TIM234_CCMR2_CC3S));
+       tim->egr = 0;
+
+       tim->sr = 0;
+       tim->dier = 0;
+       tim->smcr = 0;
+       tim->cr2 = ((0 << STM_TIM234_CR2_TI1S) |
+                   (STM_TIM234_CR2_MMS_RESET<< STM_TIM234_CR2_MMS) |
+                   (0 << STM_TIM234_CR2_CCDS));
+
+       stm_set_afio_mapr(AO_AFIO_PWM_REMAP,
+                         AO_AFIO_PWM_REMAP_VAL,
+                         AO_AFIO_PWM_REMAP_MASK);
+
+       ao_enable_port(AO_PWM_0_GPIO);
+
+       stm_gpio_conf(AO_PWM_0_GPIO, AO_PWM_0_PIN,
+                     STM_GPIO_CR_MODE_OUTPUT_2MHZ,
+                     STM_GPIO_CR_CNF_OUTPUT_AF_PUSH_PULL);
+#if NUM_PWM > 1
+       stm_gpio_conf(AO_PWM_1_GPIO, AO_PWM_1_PIN,
+                     STM_GPIO_CR_MODE_OUTPUT_2MHZ,
+                     STM_GPIO_CR_CNF_OUTPUT_AF_PUSH_PULL);
+#endif
+#if NUM_PWM > 2
+       stm_gpio_conf(AO_PWM_2_GPIO, AO_PWM_2_PIN,
+                     STM_GPIO_CR_MODE_OUTPUT_2MHZ,
+                     STM_GPIO_CR_CNF_OUTPUT_AF_PUSH_PULL);
+#endif
+#if NUM_PWM > 3
+       stm_gpio_conf(AO_PWM_3_GPIO, AO_PWM_3_PIN,
+                     STM_GPIO_CR_MODE_OUTPUT_2MHZ,
+                     STM_GPIO_CR_CNF_OUTPUT_AF_PUSH_PULL);
+#endif
+       ao_cmd_register(&ao_pwm_cmds[0]);
+}
index bf96fbbc8549c21382dbea5b9458268dbe8ec515..b77a0aef4d68f44029f37ef02fc9d6d4e85a9dcb 100644 (file)
@@ -187,9 +187,6 @@ extern struct stm_rcc stm_rcc;
 #define STM_RCC_APB1ENR_SPI3EN         15
 #define STM_RCC_APB1ENR_SPI2EN         14
 #define STM_RCC_APB1ENR_WWDGEN         11
-#define STM_RCC_APB1ENR_TIM14EN                8
-#define STM_RCC_APB1ENR_TIM13EN                7
-#define STM_RCC_APB1ENR_TIM12EN                6
 #define STM_RCC_APB1ENR_TIM7EN         5
 #define STM_RCC_APB1ENR_TIM6EN         4
 #define STM_RCC_APB1ENR_TIM5EN         3
@@ -530,19 +527,19 @@ extern struct stm_afio stm_afio;
 #define  STM_AFIO_MAPR_CAN_REMAP_PA11_PA12             0
 #define  STM_AFIO_MAPR_CAN_REMAP_PB8_PB9               2
 #define  STM_AFIO_MAPR_CAN_REMAP_PD0_PD1               3
-#define  STM_AFIO_MAPR_CAN_REMAP_MASK                  3
+#define  STM_AFIO_MAPR_CAN_REMAP_MASK                  3UL
 #define STM_AFIO_MAPR_TIM4_REMAP               12
 #define STM_AFIO_MAPR_TIM3_REMAP               10
 #define  STM_AFIO_MAPR_TIM3_REMAP_PA6_PA7_PB0_PB1      0
 #define  STM_AFIO_MAPR_TIM3_REMAP_PB4_PB5_PB0_PB1      2
 #define  STM_AFIO_MAPR_TIM3_REMAP_PC6_PC7_PC8_PC9      3
-#define  STM_AFIO_MAPR_TIM3_REMAP_MASK                 3
+#define  STM_AFIO_MAPR_TIM3_REMAP_MASK                 3UL
 #define STM_AFIO_MAPR_TIM2_REMAP               8
 #define  STM_AFIO_MAPR_TIM2_REMAP_PA0_PA1_PA2_PA3      0
 #define  STM_AFIO_MAPR_TIM2_REMAP_PA15_PB3_PA2_PA3     1
 #define  STM_AFIO_MAPR_TIM2_REMAP_PA0_PA1_PB10_PB11    2
 #define  STM_AFIO_MAPR_TIM2_REMAP_PA15_PB3_PB10_PB11   3
-#define  STM_AFIO_MAPR_TIM2_REMAP_MASK                 3
+#define  STM_AFIO_MAPR_TIM2_REMAP_MASK                 3UL
 #define STM_AFIO_MAPR_TIM1_REMAP               6
 #define  STM_AFIO_MAPR_TIM1_REMAP_PA12_PA8_PA9_PA10_PA11_PB12_PB13_PB14_PB15   0
 #define  STM_AFIO_MAPR_TIM1_REMAP_PA12_PA8_PA9_PA10_PA11_PA6_PA7_PB0_PB1       1
index 5846a8ece71f8c5bb7e14005f21db6684a01509c..55de1b231af0ddfa7845569627a1cabd4dcf5dc9 100644 (file)
@@ -54,6 +54,7 @@ ALTOS_SRC = \
        ao_beep_stm.c \
        ao_convert_volt.c \
        ao_fast_timer.c \
+       ao_pwm_stm.c \
        ao_eeprom.c \
        ao_flash_stm.c \
        ao_usb_stm.c \
index 7c81a2e5babd3ddaf4c1f5c69c00c88469e0ed1b..c991ae6e4822af80fe479b90fe955f657bb32704 100644 (file)
@@ -23,6 +23,7 @@
 #include <ao_radio_cmac.h>
 #include <ao_st7565.h>
 #include <ao_adc_single.h>
+#include <ao_pwm.h>
 
 #define WIDTH  AO_ST7565_WIDTH
 #define HEIGHT AO_ST7565_HEIGHT
@@ -45,7 +46,6 @@ static const struct ao_transform logo_transform = {
 
 #define BIG_FONT BitstreamVeraSans_Roman_58_font
 #define VOLT_FONT BitstreamVeraSans_Roman_58_font
-#define CONTRAST_FONT BitstreamVeraSans_Roman_58_font
 #define SMALL_FONT BitstreamVeraSans_Roman_12_font
 #define TINY_FONT BitstreamVeraSans_Roman_10_font
 #define LOGO_FONT BenguiatGothicStd_Bold_26_font
@@ -71,6 +71,12 @@ static const struct ao_transform logo_transform = {
 #define CONTRAST_Y     20
 #define CONTRAST_HEIGHT        20
 
+#define BACKLIGHT_LABEL_X      37
+#define BACKLIGHT_WIDTH        100
+#define BACKLIGHT_X    (WIDTH - BACKLIGHT_WIDTH) / 2
+#define BACKLIGHT_Y    20
+#define BACKLIGHT_HEIGHT       20
+
 #define AO_LCO_DRAG_RACE_START_TIME    AO_SEC_TO_TICKS(5)
 #define AO_LCO_DRAG_RACE_STOP_TIME     AO_SEC_TO_TICKS(2)
 
@@ -137,21 +143,40 @@ _ao_lco_show_contrast(void)
        ao_rect(&fb, CONTRAST_X, CONTRAST_Y, contrast, CONTRAST_HEIGHT, AO_BLACK, AO_COPY);
 }
 
+static void
+_ao_lco_show_backlight(void)
+{
+       int32_t backlight = ao_lco_get_backlight();
+       int16_t value = (int16_t) (backlight * BACKLIGHT_WIDTH / AO_LCO_MAX_BACKLIGHT);
+
+       ao_text(&fb, &SMALL_FONT, BACKLIGHT_LABEL_X, LABEL_Y, "Backlight", AO_BLACK, AO_COPY);
+       ao_rect(&fb, BACKLIGHT_X, BACKLIGHT_Y, value, BACKLIGHT_HEIGHT, AO_BLACK, AO_COPY);
+}
+
 void
 ao_lco_show(void)
 {
        ao_mutex_get(&ao_lco_display_mutex);
        ao_rect(&fb, 0, 0, WIDTH, HEIGHT, AO_WHITE, AO_COPY);
-       if (ao_lco_box == AO_LCO_LCO_VOLTAGE) {
+       switch (ao_lco_box) {
+       case AO_LCO_LCO_VOLTAGE:
                _ao_lco_batt_voltage();
-       } else if (ao_lco_box == AO_LCO_CONTRAST) {
+               break;
+       case AO_LCO_CONTRAST:
                _ao_lco_show_contrast();
-       } else if (ao_lco_pad == AO_LCO_PAD_VOLTAGE) {
-               _ao_lco_show_voltage(ao_pad_query.battery, "Pad battery");
-       } else {
-               _ao_lco_show_pad(ao_lco_pad);
-               _ao_lco_show_box(ao_lco_box);
-               ao_rect(&fb, SEP_X, 0, 2, HEIGHT, AO_BLACK, AO_COPY);
+               break;
+       case AO_LCO_BACKLIGHT:
+               _ao_lco_show_backlight();
+               break;
+       default:
+               if (ao_lco_pad == AO_LCO_PAD_VOLTAGE) {
+                       _ao_lco_show_voltage(ao_pad_query.battery, "Pad battery");
+               } else {
+                       _ao_lco_show_pad(ao_lco_pad);
+                       _ao_lco_show_box(ao_lco_box);
+                       ao_rect(&fb, SEP_X, 0, 2, HEIGHT, AO_BLACK, AO_COPY);
+               }
+               break;
        }
        ao_st7565_update(&fb);
        ao_mutex_put(&ao_lco_display_mutex);
@@ -181,15 +206,30 @@ ao_lco_set_select(void)
 
 
 void
-ao_lco_set_contrast(int16_t contrast)
+ao_lco_set_contrast(int32_t contrast)
 {
        ao_st7565_set_brightness((uint8_t) contrast);
 }
 
-int16_t
+int32_t
 ao_lco_get_contrast(void)
 {
-       return (int16_t) ao_st7565_get_brightness();
+       return (int32_t) ao_st7565_get_brightness();
+}
+
+static uint16_t ao_backlight;
+
+void
+ao_lco_set_backlight(int32_t backlight)
+{
+       ao_backlight = (uint16_t) backlight;
+       ao_pwm_set(AO_LCD_BL_PWM_CHAN, ao_backlight);
+}
+
+int32_t
+ao_lco_get_backlight(void)
+{
+       return (int32_t) ao_backlight;
 }
 
 static struct ao_task  ao_lco_drag_task;
index be253232ca26afa463982cdc582632e6944d4ab3..7a7ffae2acff8e78f565dc386dd15513133b7a0d 100644 (file)
@@ -95,7 +95,7 @@
 #define PACKET_HAS_SLAVE       0
 #define PACKET_HAS_MASTER      0
 
-#define AO_FAST_TIMER          2
+#define AO_FAST_TIMER          4
 #define FAST_TIMER_FREQ                10000   /* .1ms for debouncing */
 
 /* LCD module */
 #define LED_11_PORT            (&stm_gpioa)
 #define LED_11_PIN             0
 
-#define AO_LED_CONTINUITY_1    AO_LED_12       /* PA1 */
+#define AO_LED_CONTINUITY_1    AO_LED_12       /* PA6 */
 #define LED_12_PORT            (&stm_gpioa)
-#define LED_12_PIN             1
+#define LED_12_PIN             6
 
 #define AO_LED_CONTINUITY_0    AO_LED_13       /* PB1 */
 #define LED_13_PORT            (&stm_gpiob)
@@ -301,4 +301,29 @@ struct ao_adc {
 #define AO_LCO_MAX_CONTRAST    63
 #define AO_LCO_CONTRAST_STEP   1
 
+#define AO_LCO_HAS_BACKLIGHT   1
+#define AO_LCO_MIN_BACKLIGHT   0
+#define AO_LCO_MAX_BACKLIGHT   65535
+#define AO_LCO_BACKLIGHT_STEP  771
+
+/*
+ * LCD Backlight via PWM.
+ *
+ * Pin PA1, TIM2_CH2
+ */
+
+#define NUM_PWM                        1
+#define PWM_MAX                        65535
+#define AO_PWM_TIMER           stm_tim2
+#define AO_LCD_BL_PWM_CHAN     1
+#define AO_PWM_0_GPIO          (&stm_gpioa)
+#define AO_PWM_0_PIN           1
+#define AO_PWM_TIMER_ENABLE    STM_RCC_APB1ENR_TIM2EN
+#define AO_PWM_TIMER_SCALE     1
+
+#define AO_AFIO_PWM_REMAP      STM_AFIO_MAPR_TIM2_REMAP
+#define AO_AFIO_PWM_REMAP_VAL  STM_AFIO_MAPR_TIM2_REMAP_PA0_PA1_PA2_PA3
+#define AO_AFIO_PWM_REMAP_MASK STM_AFIO_MAPR_TIM2_REMAP_MASK
+
+
 #endif /* _AO_PINS_H_ */
index 708352ca4802eb92baf39b73a4191ffd891cec4f..faba96b0e0f5c28b930af0ad4a5d492b2d8af188 100644 (file)
@@ -29,6 +29,7 @@
 #include <ao_eeprom.h>
 #include <ao_adc_single.h>
 #include <ao_st7565.h>
+#include <ao_pwm.h>
 
 #define WIDTH  AO_ST7565_WIDTH
 #define HEIGHT AO_ST7565_HEIGHT
@@ -253,7 +254,6 @@ main(void)
        ao_clock_init();
 
        ao_led_init();
-       ao_led_on(LEDS_AVAILABLE);
        ao_task_init();
 
        ao_timer_init();
@@ -264,6 +264,7 @@ main(void)
        ao_adc_single_init();
 
        ao_beep_init();
+       ao_pwm_init();
        ao_cmd_init();
 
        ao_quadrature_init();
@@ -282,8 +283,6 @@ main(void)
 
 //     ao_cmd_register(ao_st7565_cmds);
 
-       ao_led_off(LEDS_AVAILABLE);
-
        ao_start_scheduler();
        return 0;
 }