From 5cea1324ac4a34a21324c4bb50885ffacb6d29da Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Sat, 27 Jan 2024 16:03:35 -0800 Subject: [PATCH] altos/telelco-v3.0: Control LCD backlight with PWM Signed-off-by: Keith Packard --- src/stm32f1/ao_pwm_stm.c | 203 ++++++++++++++++++++++++++++++++++ src/stm32f1/stm32f1.h | 9 +- src/telelco-v3.0/Makefile | 1 + src/telelco-v3.0/ao_lco_v3.c | 64 +++++++++-- src/telelco-v3.0/ao_pins.h | 31 +++++- src/telelco-v3.0/ao_telelco.c | 5 +- 6 files changed, 289 insertions(+), 24 deletions(-) create mode 100644 src/stm32f1/ao_pwm_stm.c diff --git a/src/stm32f1/ao_pwm_stm.c b/src/stm32f1/ao_pwm_stm.c new file mode 100644 index 00000000..dcf9d0e7 --- /dev/null +++ b/src/stm32f1/ao_pwm_stm.c @@ -0,0 +1,203 @@ +/* + * Copyright © 2024 Keith Packard + * + * 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 \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]); +} diff --git a/src/stm32f1/stm32f1.h b/src/stm32f1/stm32f1.h index bf96fbbc..b77a0aef 100644 --- a/src/stm32f1/stm32f1.h +++ b/src/stm32f1/stm32f1.h @@ -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 diff --git a/src/telelco-v3.0/Makefile b/src/telelco-v3.0/Makefile index 5846a8ec..55de1b23 100644 --- a/src/telelco-v3.0/Makefile +++ b/src/telelco-v3.0/Makefile @@ -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 \ diff --git a/src/telelco-v3.0/ao_lco_v3.c b/src/telelco-v3.0/ao_lco_v3.c index 7c81a2e5..c991ae6e 100644 --- a/src/telelco-v3.0/ao_lco_v3.c +++ b/src/telelco-v3.0/ao_lco_v3.c @@ -23,6 +23,7 @@ #include #include #include +#include #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; diff --git a/src/telelco-v3.0/ao_pins.h b/src/telelco-v3.0/ao_pins.h index be253232..7a7ffae2 100644 --- a/src/telelco-v3.0/ao_pins.h +++ b/src/telelco-v3.0/ao_pins.h @@ -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 */ @@ -179,9 +179,9 @@ #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_ */ diff --git a/src/telelco-v3.0/ao_telelco.c b/src/telelco-v3.0/ao_telelco.c index 708352ca..faba96b0 100644 --- a/src/telelco-v3.0/ao_telelco.c +++ b/src/telelco-v3.0/ao_telelco.c @@ -29,6 +29,7 @@ #include #include #include +#include #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; } -- 2.30.2