Files
Lamps/Core/Src/segment_tim.c
2026-04-12 10:50:01 +03:00

331 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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();
}
}