#include "segment.h" #include "main.h" // ==================== ТАЙМЕР ДЛЯ ПРЕРЫВАНИЙ ==================== #define SEGMENT_PROCESS_TIMER htim1 // ==================== КОНФИГУРАЦИЯ ДИСПЛЕЯ ==================== #define DIGITS_COUNT 6 #define MULTIPLEX_FREQ_HZ 10000 #define PWM_FREQUENCY_HZ 10000 #define PWM_RESOLUTION 100 #define TIMER_BUS_FREQ_MHZ 72 #define SWAP_BIT5_BIT6(x) (((x) & 0x9F) | (((x) & 0x20) << 1) | (((x) & 0x40) >> 1)) // ==================== ИНИЦИАЛИЗАЦИЯ СЕГМЕНТОВ ==================== SegCtrl_t segments[7] = { SEG_A_CONFIG, SEG_B_CONFIG, SEG_C_CONFIG, SEG_D_CONFIG, SEG_E_CONFIG, SEG_F_CONFIG, SEG_G_CONFIG }; // ==================== ТАБЛИЦА СЕГМЕНТОВ ==================== // 1 - сегмент включен, 0 - выключен static const uint8_t segmentTable[10] = { 0x3F, // 0: 0011 1111 - A,B,C,D,E,F 0x06, // 1: 0000 0110 - B,C 0x5B, // 2: 0101 1011 - A,B,D,E,G 0x4F, // 3: 0100 1111 - A,B,C,D,G 0x66, // 4: 0110 0110 - B,C,F,G 0x6D, // 5: 0110 1101 - A,C,D,F,G 0x7D, // 6: 0111 1101 - A,C,D,E,F,G 0x07, // 7: 0000 0111 - A,B,C 0x7F, // 8: 0111 1111 - все сегменты 0x6F // 9: 0110 1111 - A,B,C,D,F,G }; static const uint8_t activeSegmentsCount[10] = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 }; // ==================== ПЕРЕМЕННЫЕ ==================== static uint8_t displayBuffer[DIGITS_COUNT]; static uint8_t currentPos = 0; static TimeStruct currentTime; static uint8_t globalBrightness = 100; static uint8_t digitCompensation[10]; static uint32_t switchIntervalTicks; static uint32_t tickCounter = 0; // ==================== ФУНКЦИИ УПРАВЛЕНИЯ РАЗРЯДАМИ ==================== static void DisableAllDigits(void) { DIGIT_HOUR_H_GPIO_Port->BSRR = DIGIT_HOUR_H_Pin << 16; DIGIT_HOUR_L_GPIO_Port->BSRR = DIGIT_HOUR_L_Pin << 16; DIGIT_MIN_H_GPIO_Port->BSRR = DIGIT_MIN_H_Pin << 16; DIGIT_MIN_L_GPIO_Port->BSRR = DIGIT_MIN_L_Pin << 16; DIGIT_SEC_H_GPIO_Port->BSRR = DIGIT_SEC_H_Pin << 16; DIGIT_SEC_L_GPIO_Port->BSRR = DIGIT_SEC_L_Pin << 16; } static void EnableDigit(uint8_t pos) { switch(pos) { case 0: DIGIT_HOUR_H_GPIO_Port->BSRR = DIGIT_HOUR_H_Pin; break; case 1: DIGIT_HOUR_L_GPIO_Port->BSRR = DIGIT_HOUR_L_Pin; break; case 2: DIGIT_MIN_H_GPIO_Port->BSRR = DIGIT_MIN_H_Pin; break; case 3: DIGIT_MIN_L_GPIO_Port->BSRR = DIGIT_MIN_L_Pin; break; case 4: DIGIT_SEC_H_GPIO_Port->BSRR = DIGIT_SEC_H_Pin; break; case 5: DIGIT_SEC_L_GPIO_Port->BSRR = DIGIT_SEC_L_Pin; break; } } // ==================== ИНИЦИАЛИЗАЦИЯ CCMR ДЛЯ КАЖДОГО СЕГМЕНТА ==================== static void InitChannel(SegCtrl_t *seg) { // Определяем указатель на CCMR регистр и сдвиг в зависимости от канала if (seg->channel == TIM_CHANNEL_1) { seg->ccmr_ptr = (uint32_t*)&seg->htim->Instance->CCMR1; seg->ccmr_shift = 0; if(!seg->isComplementary) seg->htim->Instance->CCER &= ~TIM_CCER_CC1P; // Сброс бита CC1P else seg->htim->Instance->CCER |= TIM_CCER_CC1P; // Установка бита CC1P } else if (seg->channel == TIM_CHANNEL_2) { seg->ccmr_ptr = (uint32_t*)&seg->htim->Instance->CCMR1; seg->ccmr_shift = 8; if(!seg->isComplementary) seg->htim->Instance->CCER &= ~TIM_CCER_CC2P; // Сброс бита CC2P else seg->htim->Instance->CCER |= TIM_CCER_CC2P; // Установка бита CC2P } else if (seg->channel == TIM_CHANNEL_3) { seg->ccmr_ptr = (uint32_t*)&seg->htim->Instance->CCMR2; seg->ccmr_shift = 0; if(!seg->isComplementary) seg->htim->Instance->CCER &= ~TIM_CCER_CC3P; // Сброс бита CC3P else seg->htim->Instance->CCER |= TIM_CCER_CC3P; // Установка бита CC3P } else if (seg->channel == TIM_CHANNEL_4) { seg->ccmr_ptr = (uint32_t*)&seg->htim->Instance->CCMR2; seg->ccmr_shift = 8; if(!seg->isComplementary) seg->htim->Instance->CCER &= ~TIM_CCER_CC4P; // Сброс бита CC4P else seg->htim->Instance->CCER |= TIM_CCER_CC4P; // Установка бита CC4P } } // ==================== ФУНКЦИИ УПРАВЛЕНИЯ ШИМ ==================== static inline void PWM_StartChannel(SegCtrl_t *seg) { if (seg->isComplementary) { HAL_TIMEx_PWMN_Start(seg->htim, seg->channel); } else { HAL_TIM_PWM_Start(seg->htim, seg->channel); } } static inline void PWM_SetMode(SegCtrl_t *seg, uint32_t mode) { uint32_t mask = 0x7 << (seg->ccmr_shift+4); *seg->ccmr_ptr &= ~mask; *seg->ccmr_ptr |= (mode << seg->ccmr_shift); } static inline void PWM_SetDuty(SegCtrl_t *seg, uint32_t duty) { uint32_t final_duty = duty; if (duty > PWM_RESOLUTION) duty = PWM_RESOLUTION; if (final_duty == 0) { PWM_SetMode(seg, TIM_OCMODE_FORCED_INACTIVE); } else { PWM_SetMode(seg, TIM_OCMODE_PWM1); __HAL_TIM_SET_COMPARE(seg->htim, seg->channel, final_duty); } } // ==================== АВТОНАСТРОЙКА ТАЙМЕРА ==================== static void TimerAutoConfig(TIM_HandleTypeDef *htim) { uint32_t timer_clock_hz = TIMER_BUS_FREQ_MHZ * 1000000; uint32_t arr = PWM_RESOLUTION - 1; uint32_t prescaler_plus1 = timer_clock_hz / (PWM_FREQUENCY_HZ * PWM_RESOLUTION); if (prescaler_plus1 < 1) prescaler_plus1 = 1; if (prescaler_plus1 > 65535) prescaler_plus1 = 65535; uint32_t prescaler = prescaler_plus1 - 1; __HAL_TIM_SET_PRESCALER(htim, prescaler); __HAL_TIM_SET_AUTORELOAD(htim, arr); } // ==================== ФУНКЦИИ УПРАВЛЕНИЯ СЕГМЕНТАМИ ==================== static void SetSegment(uint8_t segIndex, uint8_t state) { SegCtrl_t *seg = &segments[segIndex]; seg->isActive = state; if (state) { PWM_SetDuty(seg, seg->Duty); } else { PWM_SetDuty(seg, 0); } } static void SetSegmentBrightness(uint8_t segIndex, uint8_t percent) { SegCtrl_t *seg = &segments[segIndex]; if (percent > 100) percent = 100; seg->Duty = (percent * PWM_RESOLUTION) / 100; } static void DisplayDigit(uint8_t digit, uint8_t pos) { if (digit > 9) digit = 0; uint8_t segmentMask = segmentTable[digit]; if (pos == 5) { segmentMask = SWAP_BIT5_BIT6(segmentMask); } // Применяем компенсацию для текущей цифры uint8_t comp = digitCompensation[digit]; uint8_t finalBrightness = (globalBrightness * comp) / 100; for (int i = 0; i < 7; i++) { SetSegmentBrightness(i, finalBrightness); if ((segmentMask >> i) & 1) { SetSegment(i, 1); } else { SetSegment(i, 0); } } } // ==================== ФУНКЦИИ ОБНОВЛЕНИЯ БУФЕРА ==================== static void UpdateDisplayBuffer(void) { uint8_t hours = currentTime.hours; uint8_t minutes = currentTime.minutes; uint8_t seconds = currentTime.seconds; static const uint8_t div10[100] = { 0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5, 6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7, 8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9 }; static const uint8_t mod10[100] = { 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9, 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9, 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9, 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9, 0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9 }; displayBuffer[0] = div10[hours]; displayBuffer[1] = mod10[hours]; displayBuffer[2] = div10[minutes]; displayBuffer[3] = mod10[minutes]; displayBuffer[4] = div10[seconds]; displayBuffer[5] = mod10[seconds]; } static void NextDigit(void) { DisableAllDigits(); currentPos++; if (currentPos >= DIGITS_COUNT) { currentPos = 0; } DisplayDigit(displayBuffer[currentPos], currentPos); EnableDigit(currentPos); } // ==================== ПУБЛИЧНЫЕ ФУНКЦИИ ==================== void Segment_Init(void) { TIM_HandleTypeDef* configuredTimers[10] = {0}; int timerCount = 0; // Инициализируем CCMR для каждого сегмента for (int i = 0; i < 7; i++) { InitChannel(&segments[i]); } // Настраиваем и запускаем все уникальные таймеры для ШИМ for (int i = 0; i < 7; i++) { TIM_HandleTypeDef *htim = segments[i].htim; int alreadyConfigured = 0; for (int j = 0; j < timerCount; j++) { if (configuredTimers[j] == htim) { alreadyConfigured = 1; break; } } if (!alreadyConfigured) { TimerAutoConfig(htim); configuredTimers[timerCount++] = htim; } PWM_StartChannel(&segments[i]); } // Инициализация сегментов for (int i = 0; i < 7; i++) { segments[i].Duty = 0; segments[i].isActive = 0; PWM_SetDuty(&segments[i], 0); } // Рассчитываем компенсацию яркости для каждой цифры // Чем меньше сегментов у цифры, тем ярче должен гореть каждый сегмент for (int i = 0; i < 10; i++) { // Максимальное количество сегментов = 7 (цифра 8) // Коэффициент = (7 / количество_сегментов_у_цифры) * 100 digitCompensation[i] = (activeSegmentsCount[i] * 100) / 7; if (digitCompensation[i] > 200) digitCompensation[i] = 200; // Ограничиваем } currentTime.hours = 0; currentTime.minutes = 0; currentTime.seconds = 0; UpdateDisplayBuffer(); switchIntervalTicks = PWM_FREQUENCY_HZ / MULTIPLEX_FREQ_HZ; if (switchIntervalTicks < 1) switchIntervalTicks = 1; tickCounter = 0; DisplayDigit(displayBuffer[0], 0); EnableDigit(0); // Запускаем таймер прерываний HAL_TIM_Base_Start_IT(&SEGMENT_PROCESS_TIMER); } void Segment_SetBrightness(uint8_t percent) { if (percent > 100) percent = 100; globalBrightness = percent; DisplayDigit(displayBuffer[currentPos], currentPos); } void Segment_SetTime(uint8_t hours, uint8_t minutes, uint8_t seconds) { if (hours > 23) hours = 23; if (minutes > 59) minutes = 59; if (seconds > 59) seconds = 59; currentTime.hours = hours; currentTime.minutes = minutes; currentTime.seconds = seconds; UpdateDisplayBuffer(); } void Segment_Process(void) { tickCounter++; if (tickCounter >= switchIntervalTicks) { tickCounter = 0; NextDigit(); } }