апдейт части с дисплеем
This commit is contained in:
parent
9d5085c48b
commit
1362b58739
162
Projects/epwm_test_biss_c_cpu2/src/Peripherals/timersec.c
Normal file
162
Projects/epwm_test_biss_c_cpu2/src/Peripherals/timersec.c
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* timersec.c
|
||||||
|
*
|
||||||
|
* Created on: 23 авг. 2024 г.
|
||||||
|
* Author: sedov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "timersec.h"
|
||||||
|
|
||||||
|
struct CPUTIMER_VARS CpuTimer0;
|
||||||
|
struct CPUTIMER_VARS CpuTimer1;
|
||||||
|
struct CPUTIMER_VARS CpuTimer2;
|
||||||
|
|
||||||
|
unsigned long uptimeSeconds = 0; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
|
||||||
|
__interrupt void cpuTimer0ISR(void)
|
||||||
|
{
|
||||||
|
CpuTimer0.InterruptCount++;
|
||||||
|
uptimeSeconds++;
|
||||||
|
//
|
||||||
|
// Acknowledge this interrupt to receive more interrupts from group 1
|
||||||
|
//
|
||||||
|
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
|
||||||
|
}
|
||||||
|
void InitCpuTimers(void)
|
||||||
|
{
|
||||||
|
EALLOW;
|
||||||
|
PieVectTable.TIMER0_INT = &cpuTimer0ISR;
|
||||||
|
EDIS;
|
||||||
|
//
|
||||||
|
// CPU Timer 0
|
||||||
|
// Initialize address pointers to respective timer registers:
|
||||||
|
//
|
||||||
|
CpuTimer0.RegsAddr = &CpuTimer0Regs;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialize timer period to maximum:
|
||||||
|
//
|
||||||
|
CpuTimer0Regs.PRD.all = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialize pre-scale counter to divide by 1 (SYSCLKOUT):
|
||||||
|
//
|
||||||
|
CpuTimer0Regs.TPR.all = 0;
|
||||||
|
CpuTimer0Regs.TPRH.all = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Make sure timer is stopped:
|
||||||
|
//
|
||||||
|
CpuTimer0Regs.TCR.bit.TSS = 1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reload all counter register with period value:
|
||||||
|
//
|
||||||
|
CpuTimer0Regs.TCR.bit.TRB = 1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reset interrupt counters:
|
||||||
|
//
|
||||||
|
CpuTimer0.InterruptCount = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialize address pointers to respective timer registers:
|
||||||
|
//
|
||||||
|
CpuTimer1.RegsAddr = &CpuTimer1Regs;
|
||||||
|
CpuTimer2.RegsAddr = &CpuTimer2Regs;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialize timer period to maximum:
|
||||||
|
//
|
||||||
|
CpuTimer1Regs.PRD.all = 0xFFFFFFFF;
|
||||||
|
CpuTimer2Regs.PRD.all = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialize pre-scale counter to divide by 1 (SYSCLKOUT):
|
||||||
|
//
|
||||||
|
CpuTimer1Regs.TPR.all = 0;
|
||||||
|
CpuTimer1Regs.TPRH.all = 0;
|
||||||
|
CpuTimer2Regs.TPR.all = 0;
|
||||||
|
CpuTimer2Regs.TPRH.all = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Make sure timers are stopped:
|
||||||
|
//
|
||||||
|
CpuTimer1Regs.TCR.bit.TSS = 1;
|
||||||
|
CpuTimer2Regs.TCR.bit.TSS = 1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reload all counter register with period value:
|
||||||
|
//
|
||||||
|
CpuTimer1Regs.TCR.bit.TRB = 1;
|
||||||
|
CpuTimer2Regs.TCR.bit.TRB = 1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reset interrupt counters:
|
||||||
|
//
|
||||||
|
CpuTimer1.InterruptCount = 0;
|
||||||
|
CpuTimer2.InterruptCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// ConfigCpuTimer - This function initializes the selected timer to the period
|
||||||
|
// specified by the "Freq" and "Period" parameters. The "Freq"
|
||||||
|
// is entered as "MHz" and the period in "uSeconds". The timer
|
||||||
|
// is held in the stopped state after configuration.
|
||||||
|
//
|
||||||
|
void ConfigCpuTimer(struct CPUTIMER_VARS *Timer, float Freq, float Period)
|
||||||
|
{
|
||||||
|
Uint32 temp;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialize timer period:
|
||||||
|
//
|
||||||
|
Timer->CPUFreqInMHz = Freq;
|
||||||
|
Timer->PeriodInUSec = Period;
|
||||||
|
temp = (long) (Freq * Period);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Counter decrements PRD+1 times each period
|
||||||
|
//
|
||||||
|
Timer->RegsAddr->PRD.all = temp - 1;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Set pre-scale counter to divide by 1 (SYSCLKOUT):
|
||||||
|
//
|
||||||
|
Timer->RegsAddr->TPR.all = 0;
|
||||||
|
Timer->RegsAddr->TPRH.all = 0;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initialize timer control register:
|
||||||
|
//
|
||||||
|
Timer->RegsAddr->TCR.bit.TSS = 1; // 1 = Stop timer, 0 = Start/Restart
|
||||||
|
// Timer
|
||||||
|
Timer->RegsAddr->TCR.bit.TRB = 1; // 1 = reload timer
|
||||||
|
Timer->RegsAddr->TCR.bit.SOFT = 0;
|
||||||
|
Timer->RegsAddr->TCR.bit.FREE = 0; // Timer Free Run Disabled
|
||||||
|
Timer->RegsAddr->TCR.bit.TIE = 1; // 0 = Disable/ 1 = Enable Timer
|
||||||
|
// Interrupt
|
||||||
|
|
||||||
|
//
|
||||||
|
// Reset interrupt counter:
|
||||||
|
//
|
||||||
|
Timer->InterruptCount = 0;
|
||||||
|
//
|
||||||
|
// To ensure precise timing, use write-only instructions to write to the
|
||||||
|
// entire register. Therefore, if any of the configuration bits are changed
|
||||||
|
// in ConfigCpuTimer and InitCpuTimers, the below settings must also be
|
||||||
|
// be updated.
|
||||||
|
//
|
||||||
|
CpuTimer0Regs.TCR.all = 0x4000;
|
||||||
|
//
|
||||||
|
// Enable CPU int1 which is connected to CPU-Timer 0, CPU int13
|
||||||
|
// which is connected to CPU-Timer 1, and CPU int 14, which is connected
|
||||||
|
// to CPU-Timer 2
|
||||||
|
//
|
||||||
|
IER |= M_INT1;
|
||||||
|
//
|
||||||
|
// Enable TINT0 in the PIE: Group 1 interrupt 7
|
||||||
|
//
|
||||||
|
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
|
||||||
|
//
|
||||||
|
}
|
||||||
16
Projects/epwm_test_biss_c_cpu2/src/Peripherals/timersec.h
Normal file
16
Projects/epwm_test_biss_c_cpu2/src/Peripherals/timersec.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* timersec.h
|
||||||
|
*
|
||||||
|
* Created on: 23 авг. 2024 г.
|
||||||
|
* Author: sedov
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SRC_PERIPHERALS_TIMERSEC_H_
|
||||||
|
#define SRC_PERIPHERALS_TIMERSEC_H_
|
||||||
|
|
||||||
|
#include "f28x_project.h"
|
||||||
|
|
||||||
|
__interrupt void cpuTimer0ISR(void);// ежесекундное прерывание инкрементирует секунды
|
||||||
|
void InitCpuTimers(void);//Функция инициализации таймера
|
||||||
|
void ConfigCpuTimer(struct CPUTIMER_VARS *Timer, float Freq, float Period);//Функция настройки таймера
|
||||||
|
#endif /* SRC_PERIPHERALS_TIMERSEC_H_ */
|
||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* buttons.c
|
* buttons.c
|
||||||
*
|
*
|
||||||
* Created on: 15 авг. 2024 г.
|
* Created on: 15 авг. 2024 г.
|
||||||
* Author: sedov
|
* Author: sedov
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -12,8 +12,8 @@
|
|||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
#include "pwm_init.h"
|
#include "pwm_init.h"
|
||||||
#include "i2c_oled.h"
|
#include "i2c_oled.h"
|
||||||
#define MAX_SCREENS 5;
|
|
||||||
//button 0 - ничего 1-вверх 2-вниз 3-пуск 4- стоп
|
|
||||||
uint16_t Button_Sost = 0;
|
uint16_t Button_Sost = 0;
|
||||||
volatile uint16_t buttonClik=0, ScreenSost=0, button=0 ;
|
volatile uint16_t buttonClik=0, ScreenSost=0, button=0 ;
|
||||||
extern uint16_t I2C_TXdata[];
|
extern uint16_t I2C_TXdata[];
|
||||||
@ -24,12 +24,12 @@ typedef enum {
|
|||||||
MENU_STOP,
|
MENU_STOP,
|
||||||
MENU_TIME,
|
MENU_TIME,
|
||||||
MENU_TEMP,
|
MENU_TEMP,
|
||||||
MENU_BRIGHT_FUTURE,
|
|
||||||
MENU_DIAKONT,
|
MENU_DIAKONT,
|
||||||
MENU_ITEMS_COUNT // Количество пунктов меню
|
MENU_STATUS,
|
||||||
|
MENU_ITEMS_COUNT // Количество пунктов меню
|
||||||
} MenuItem;
|
} MenuItem;
|
||||||
|
|
||||||
// Глобальная переменная для хранения текущего пункта меню
|
// Глобальная переменная для хранения текущего пункта меню
|
||||||
MenuItem currentMenuItem = MENU_START;
|
MenuItem currentMenuItem = MENU_START;
|
||||||
|
|
||||||
__interrupt void buttonclik_isr(void)
|
__interrupt void buttonclik_isr(void)
|
||||||
@ -40,8 +40,8 @@ __interrupt void buttonclik_isr(void)
|
|||||||
}
|
}
|
||||||
int buttonsRead(){
|
int buttonsRead(){
|
||||||
int err1,err2;
|
int err1,err2;
|
||||||
err1=I2CWrite(0x25, 0, 0, false, &Button_Sost); // Перед чтением всегда должна быть запись
|
err1=I2CWrite(0x25, 0, 0, false, &Button_Sost); // Перед чтением всегда должна быть запись
|
||||||
err2=I2CRead(0x25, 1, true, &Button_Sost); // Читаем состояние порта
|
err2=I2CRead(0x25, 1, true, &Button_Sost); // Читаем состояние порта
|
||||||
if(err1 != 0 || err2 != 0){
|
if(err1 != 0 || err2 != 0){
|
||||||
return 1;
|
return 1;
|
||||||
}else{
|
}else{
|
||||||
@ -53,13 +53,13 @@ void buttonsInit(void) {
|
|||||||
uint16_t Conf0 = 0xFE;
|
uint16_t Conf0 = 0xFE;
|
||||||
uint16_t Conf1 = 0xFF;
|
uint16_t Conf1 = 0xFF;
|
||||||
|
|
||||||
I2CWrite(0x25, 6, 1, true, &Conf1); // Конфигурирование кнопок на вход
|
I2CWrite(0x25, 6, 1, true, &Conf1); // Конфигурирование кнопок на вход
|
||||||
I2CWrite(0x25, 7, 1, true, &Conf1);
|
I2CWrite(0x25, 7, 1, true, &Conf1);
|
||||||
|
|
||||||
I2CWrite(0x25, 0, 0, false, &Button_Sost); // Перед чтением всегда должна быть запись
|
I2CWrite(0x25, 0, 0, false, &Button_Sost); // Перед чтением всегда должна быть запись
|
||||||
I2CRead(0x25, 1, true, &Button_Sost); // Читаем состояние порта
|
I2CRead(0x25, 1, true, &Button_Sost); // Читаем состояние порта
|
||||||
|
|
||||||
// Настройка прерывания XINT3
|
// Настройка прерывания XINT3
|
||||||
EALLOW;
|
EALLOW;
|
||||||
PieVectTable.XINT3_INT = &buttonclik_isr;
|
PieVectTable.XINT3_INT = &buttonclik_isr;
|
||||||
IER |= M_INT12;
|
IER |= M_INT12;
|
||||||
@ -73,37 +73,37 @@ void buttonsInit(void) {
|
|||||||
|
|
||||||
int buttonsDisp(){
|
int buttonsDisp(){
|
||||||
if(buttonClik!=0){
|
if(buttonClik!=0){
|
||||||
//читаем состояние кнопок
|
//читаем состояние кнопок
|
||||||
if(buttonsRead()){
|
if(buttonsRead()){
|
||||||
//обработка ошибки чтения
|
//обработка ошибки чтения
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
Button_Sost=Button_Sost&0xF;
|
Button_Sost=Button_Sost&0xF;
|
||||||
switch(Button_Sost){
|
switch(Button_Sost){
|
||||||
case 0xD:
|
case 0xD:
|
||||||
if(button < 0XFF)
|
if(button < 0XFF)
|
||||||
button+=0XFF;//ПУСК
|
button+=0XFF;//ПУСК
|
||||||
break;
|
break;
|
||||||
case 0xB:
|
case 0xB:
|
||||||
if(button >= 0XFF)
|
if(button >= 0XFF)
|
||||||
button-=0XFF;//СТОП
|
button-=0XFF;//СТОП
|
||||||
break;
|
break;
|
||||||
case 0xE:
|
case 0x7:
|
||||||
if(button < 0XFF){
|
if(button < 0XFF){
|
||||||
button++;//ВЕРХ
|
button++;//ВЕРХ
|
||||||
if(button>MENU_ITEMS_COUNT-1){
|
if(button>MENU_ITEMS_COUNT-1){
|
||||||
button=0;
|
button=0;
|
||||||
}}
|
}}
|
||||||
break;
|
break;
|
||||||
case 0x7:
|
case 0xE:
|
||||||
if(button < 0XFF)
|
if(button < 0XFF)
|
||||||
button--;//ВЕРХ
|
button--;//ВЕРХ
|
||||||
if(button==0xFFFF){
|
if(button==0xFFFF){
|
||||||
button=MENU_ITEMS_COUNT-1;
|
button=MENU_ITEMS_COUNT-1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
//несколько кнопок нажали
|
//несколько кнопок нажали
|
||||||
}
|
}
|
||||||
SSD1306_Fill(SSD1306_COLOR_BLACK);
|
SSD1306_Fill(SSD1306_COLOR_BLACK);
|
||||||
Button_Sost=0;
|
Button_Sost=0;
|
||||||
@ -201,31 +201,44 @@ void MenuDisp(){
|
|||||||
ssd1306_DrawBitmap(0, 0, logo, 128, 52, SSD1306_COLOR_WHITE);
|
ssd1306_DrawBitmap(0, 0, logo, 128, 52, SSD1306_COLOR_WHITE);
|
||||||
SSD1306_UpdateScreen();
|
SSD1306_UpdateScreen();
|
||||||
break;
|
break;
|
||||||
|
case 5+0xFF:
|
||||||
|
SSD1306_GotoXY(10, 12);
|
||||||
|
SSD1306_Puts("OK", &Font_11x18, SSD1306_COLOR_WHITE);
|
||||||
|
SSD1306_GotoXY(10, 35);
|
||||||
|
SSD1306_Puts("But this is not accurate", &Font_7x10, SSD1306_COLOR_WHITE);
|
||||||
|
SSD1306_GotoXY(10, 45);
|
||||||
|
SSD1306_Puts(" accurate", &Font_7x10, SSD1306_COLOR_WHITE);
|
||||||
|
SSD1306_UpdateScreen();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static uint16_t topItem;
|
||||||
void MenuBilder(void) {
|
void MenuBilder(void) {
|
||||||
uint16_t i,yPos;
|
uint16_t i,yPos;
|
||||||
|
static uint16_t buff;
|
||||||
char tempStr[5];
|
char tempStr[5];
|
||||||
uint16_t topItem = currentMenuItem; // Верхний видимый пункт
|
topItem = (uint16_t)currentMenuItem;// Верхний видимый пункт
|
||||||
if (topItem > 2) {
|
if (currentMenuItem > 0 && (topItem-buff)==1) {
|
||||||
|
topItem = currentMenuItem - 1;
|
||||||
|
}else if (currentMenuItem > 0 && (topItem-buff)==2){
|
||||||
topItem = currentMenuItem - 2;
|
topItem = currentMenuItem - 2;
|
||||||
}
|
}
|
||||||
|
buff =topItem;
|
||||||
// Очищаем экран
|
// Очищаем экран
|
||||||
SSD1306_Fill(SSD1306_COLOR_BLACK);
|
SSD1306_Fill(SSD1306_COLOR_BLACK);
|
||||||
|
|
||||||
// Отрисовка заголовка
|
// Отрисовка заголовка
|
||||||
SSD1306_DrawFilledRectangle(0, 0, 128, 10, SSD1306_COLOR_WHITE);
|
SSD1306_DrawFilledRectangle(0, 0, 128, 10, SSD1306_COLOR_WHITE);
|
||||||
SSD1306_GotoXY(30, 1);
|
SSD1306_GotoXY(30, 1);
|
||||||
SSD1306_Puts("START MENU", &Font_7x10, SSD1306_COLOR_BLACK);
|
SSD1306_Puts("START MENU", &Font_7x10, SSD1306_COLOR_BLACK);
|
||||||
|
|
||||||
// Отрисовка пунктов меню
|
// Отрисовка пунктов меню
|
||||||
for ( i = 0; i < 4 && (topItem + i) < MENU_ITEMS_COUNT; i++) {
|
for ( i = 0; i < 4 && (topItem + i) < MENU_ITEMS_COUNT; i++) {
|
||||||
yPos = 13 + i*13; // Вертикальная позиция пункта
|
yPos = 13 + i*13; // Вертикальная позиция пункта
|
||||||
|
|
||||||
// Выделение выбранного пункта
|
// Выделение выбранного пункта
|
||||||
if (topItem + i == currentMenuItem) {
|
if (topItem + i == currentMenuItem) {
|
||||||
SSD1306_DrawFilledRectangle(0, yPos, 128, 13, SSD1306_COLOR_WHITE);
|
SSD1306_DrawFilledRectangle(0, yPos, 128, 13, SSD1306_COLOR_WHITE);
|
||||||
} else {
|
} else {
|
||||||
@ -234,19 +247,19 @@ void MenuBilder(void) {
|
|||||||
if(yPos==52){
|
if(yPos==52){
|
||||||
yPos-=2;
|
yPos-=2;
|
||||||
}
|
}
|
||||||
// Вывод номера пункта
|
// Вывод номера пункта
|
||||||
SSD1306_GotoXY(5, yPos + 3);
|
SSD1306_GotoXY(5, yPos + 3);
|
||||||
// Используем sprintf для преобразования числа в строку
|
// Используем sprintf для преобразования числа в строку
|
||||||
// Преобразование числа в строку "вручную"
|
// Преобразование числа в строку "вручную"
|
||||||
uint8_t num = topItem + i + 1;
|
uint8_t num = topItem + i + 1;
|
||||||
tempStr[0] = '0' + (num / 10); // Десятки
|
tempStr[0] = '0' + (num / 10); // Десятки
|
||||||
tempStr[1] = '0' + (num % 10); // Единицы
|
tempStr[1] = '0' + (num % 10); // Единицы
|
||||||
tempStr[2] = '\0'; // Нуль-терминатор
|
tempStr[2] = '\0'; // Нуль-терминатор
|
||||||
if (topItem + i == currentMenuItem) {
|
if (topItem + i == currentMenuItem) {
|
||||||
SSD1306_Puts(tempStr, &Font_7x10, SSD1306_COLOR_BLACK);
|
SSD1306_Puts(tempStr, &Font_7x10, SSD1306_COLOR_BLACK);
|
||||||
}else
|
}else
|
||||||
SSD1306_Puts(tempStr, &Font_7x10, SSD1306_COLOR_WHITE);
|
SSD1306_Puts(tempStr, &Font_7x10, SSD1306_COLOR_WHITE);
|
||||||
SSD1306_GotoXY(20, yPos + 3); // Сдвиг для названия пункта
|
SSD1306_GotoXY(20, yPos + 3); // Сдвиг для названия пункта
|
||||||
|
|
||||||
switch (topItem + i) {
|
switch (topItem + i) {
|
||||||
case MENU_START:
|
case MENU_START:
|
||||||
@ -261,13 +274,12 @@ void MenuBilder(void) {
|
|||||||
case MENU_TEMP:
|
case MENU_TEMP:
|
||||||
SSD1306_Puts(" TEMP", &Font_7x10, topItem + i == currentMenuItem ? SSD1306_COLOR_BLACK : SSD1306_COLOR_WHITE);
|
SSD1306_Puts(" TEMP", &Font_7x10, topItem + i == currentMenuItem ? SSD1306_COLOR_BLACK : SSD1306_COLOR_WHITE);
|
||||||
break;
|
break;
|
||||||
case MENU_BRIGHT_FUTURE:
|
|
||||||
SSD1306_Puts(" BRIGHT_FUTURE", &Font_7x10, topItem + i == currentMenuItem ? SSD1306_COLOR_BLACK : SSD1306_COLOR_WHITE);
|
|
||||||
break;
|
|
||||||
case MENU_DIAKONT:
|
case MENU_DIAKONT:
|
||||||
SSD1306_Puts(" DIAKONT", &Font_7x10, topItem + i == currentMenuItem ? SSD1306_COLOR_BLACK : SSD1306_COLOR_WHITE);
|
SSD1306_Puts(" DIAKONT", &Font_7x10, topItem + i == currentMenuItem ? SSD1306_COLOR_BLACK : SSD1306_COLOR_WHITE);
|
||||||
break;
|
break;
|
||||||
|
case MENU_STATUS:
|
||||||
|
SSD1306_Puts(" STATUS", &Font_7x10, topItem + i == currentMenuItem ? SSD1306_COLOR_BLACK : SSD1306_COLOR_WHITE);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,15 +97,15 @@ uint8_t SSD1306_Init(void) {
|
|||||||
|
|
||||||
void SSD1306_setPosition(uint8_t column, uint8_t page) {
|
void SSD1306_setPosition(uint8_t column, uint8_t page) {
|
||||||
if (column > SSD1306_WIDTH - 1) {
|
if (column > SSD1306_WIDTH - 1) {
|
||||||
column = 0; // Ограничение столбца
|
column = 0; // Ограничение столбца
|
||||||
}
|
}
|
||||||
if (page > 7) {
|
if (page > 7) {
|
||||||
page = 0; // Ограничение страницы
|
page = 0; // Ограничение страницы
|
||||||
}
|
}
|
||||||
|
|
||||||
SSD1306_WRITECOMMAND(0x20+column); // Начальный адрес столбца
|
SSD1306_WRITECOMMAND(0x20+column); // Начальный адрес столбца
|
||||||
SSD1306_WRITECOMMAND(SSD1306_PAGEADDR+page); // Начальный адрес страницы
|
SSD1306_WRITECOMMAND(SSD1306_PAGEADDR+page); // Начальный адрес страницы
|
||||||
// SSD1306_WRITECOMMAND(7); // Конечный адрес страницы
|
// SSD1306_WRITECOMMAND(7); // Конечный адрес страницы
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -118,9 +118,9 @@ void SSD1306_UpdateScreen(void) {
|
|||||||
SSD1306_WRITECOMMAND(0x10);
|
SSD1306_WRITECOMMAND(0x10);
|
||||||
SSD1306_WRITECOMMAND(0x00);
|
SSD1306_WRITECOMMAND(0x00);
|
||||||
for (column = 0; column < SSD1306_WIDTH; column++) { //
|
for (column = 0; column < SSD1306_WIDTH; column++) { //
|
||||||
SSD1306_setPosition(column, page); // Начало столбца - 0, страница - m
|
SSD1306_setPosition(column, page); // Начало столбца - 0, страница - m
|
||||||
// Запись данных в буфер дисплея
|
// Запись данных в буфер дисплея
|
||||||
I2C_TXdata[0] = 0x40; // Байт управления: данные
|
I2C_TXdata[0] = 0x40; // Байт управления: данные
|
||||||
for ( i = 0; i < SSD1306_WIDTH; i++) {
|
for ( i = 0; i < SSD1306_WIDTH; i++) {
|
||||||
I2C_TXdata[i + 1] = SSD1306_Buffer[SSD1306_WIDTH*page + i];
|
I2C_TXdata[i + 1] = SSD1306_Buffer[SSD1306_WIDTH*page + i];
|
||||||
}
|
}
|
||||||
@ -130,7 +130,7 @@ void SSD1306_UpdateScreen(void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SSD1306_UpdateScreenAFAST(void) {
|
void SSD1306_UpdateScreenАFAST(void) {
|
||||||
|
|
||||||
uint8_t page;
|
uint8_t page;
|
||||||
uint8_t column;
|
uint8_t column;
|
||||||
@ -140,12 +140,12 @@ void SSD1306_UpdateScreenAFAST(void) {
|
|||||||
SSD1306_WRITECOMMAND(0x10);
|
SSD1306_WRITECOMMAND(0x10);
|
||||||
SSD1306_WRITECOMMAND(0x00);
|
SSD1306_WRITECOMMAND(0x00);
|
||||||
for (column = 0; column < SSD1306_WIDTH-60; column++) { //
|
for (column = 0; column < SSD1306_WIDTH-60; column++) { //
|
||||||
SSD1306_setPosition(column+60, page); // Начало столбца - 0, страница - m
|
SSD1306_setPosition(column+60, page); // Начало столбца - 0, страница - m
|
||||||
|
|
||||||
// Запись данных в буфер дисплея
|
// Запись данных в буфер дисплея
|
||||||
I2C_TXdata[0] = 0x40; // Байт управления: данные
|
I2C_TXdata[0] = 0x40; // Байт управления: данные
|
||||||
// Заполнить буфер I2C данными из буфера дисплея
|
// Заполнить буфер I2C данными из буфера дисплея
|
||||||
I2C_TXdata[0] = 0x40; // Команда записи данных
|
I2C_TXdata[0] = 0x40; // Команда записи данных
|
||||||
I2C_TXdata[1] = SSD1306_Buffer[SSD1306_WIDTH*page + column];
|
I2C_TXdata[1] = SSD1306_Buffer[SSD1306_WIDTH*page + column];
|
||||||
I2CWriteOLED(I2C_SLAVE_ADDRESS, 2, true);
|
I2CWriteOLED(I2C_SLAVE_ADDRESS, 2, true);
|
||||||
}
|
}
|
||||||
@ -176,9 +176,9 @@ void SSD1306_DrawPixel(uint16_t x, uint16_t y, SSD1306_COLOR_t color) {
|
|||||||
color = (SSD1306_COLOR_t)!color;
|
color = (SSD1306_COLOR_t)!color;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Правильно вычисляем индекс байта в буфере
|
// Правильно вычисляем индекс байта в буфере
|
||||||
uint16_t byteIndex = x + (y / 8) * SSD1306_WIDTH;
|
uint16_t byteIndex = x + (y / 8) * SSD1306_WIDTH;
|
||||||
// Правильно вычисляем индекс бита в байт
|
// Правильно вычисляем индекс бита в байт
|
||||||
uint8_t bitIndex = y % 8;
|
uint8_t bitIndex = y % 8;
|
||||||
|
|
||||||
if (color == SSD1306_COLOR_WHITE) {
|
if (color == SSD1306_COLOR_WHITE) {
|
||||||
@ -251,19 +251,19 @@ char SSD1306_Putc(char ch, FontDef_t* Font, SSD1306_COLOR_t color) {
|
|||||||
return ch;
|
return ch;
|
||||||
}
|
}
|
||||||
void reverseString(char *str) {
|
void reverseString(char *str) {
|
||||||
if (str == NULL) { // Проверка на NULL указатель
|
if (str == NULL) { // Проверка на NULL указатель
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char *end = str; // Указатель на конец строки
|
char *end = str; // Указатель на конец строки
|
||||||
while (*end) {
|
while (*end) {
|
||||||
end++;
|
end++;
|
||||||
}
|
}
|
||||||
end--; // Перемещаем указатель на последний символ строки
|
end--; // Перемещаем указатель на последний символ строки
|
||||||
|
|
||||||
char temp;
|
char temp;
|
||||||
while (str < end) {
|
while (str < end) {
|
||||||
temp = *str; // Меняем местами символы
|
temp = *str; // Меняем местами символы
|
||||||
*str = *end;
|
*str = *end;
|
||||||
*end = temp;
|
*end = temp;
|
||||||
|
|
||||||
@ -660,11 +660,11 @@ char* ulongToStr(unsigned long num, char* buffer) {
|
|||||||
unsigned long startnum=num;
|
unsigned long startnum=num;
|
||||||
if (num == 0) {
|
if (num == 0) {
|
||||||
*buffer++ = '0';
|
*buffer++ = '0';
|
||||||
*buffer++ = '0'; // Добавляем нуль-терминатор
|
*buffer++ = '0'; // Добавляем нуль-терминатор
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
char temp[10]; // Буфер для временного хранения цифр
|
char temp[10]; // Буфер для временного хранения цифр
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
while (num > 0) {
|
while (num > 0) {
|
||||||
@ -674,30 +674,30 @@ char* ulongToStr(unsigned long num, char* buffer) {
|
|||||||
if(startnum<=9){
|
if(startnum<=9){
|
||||||
*buffer++ = '0';
|
*buffer++ = '0';
|
||||||
}
|
}
|
||||||
// Копируем цифры в обратном порядке в основной буфер
|
// Копируем цифры в обратном порядке в основной буфер
|
||||||
while (i > 0) {
|
while (i > 0) {
|
||||||
*buffer++ = temp[--i];
|
*buffer++ = temp[--i];
|
||||||
}
|
}
|
||||||
|
|
||||||
*buffer = '\0'; // Добавляем нуль-терминатор
|
*buffer = '\0'; // Добавляем нуль-терминатор
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
// Функция для отображения времени с момента запуска
|
// Функция для отображения времени с момента запуска
|
||||||
// Возвращает true, если время было обновлено, иначе false
|
// Возвращает true, если время было обновлено, иначе false
|
||||||
bool displayUptime(void) {
|
bool displayUptime(void) {
|
||||||
static unsigned long lastDisplayedTime = 0; // Последнее отображённое время
|
static unsigned long lastDisplayedTime = 0; // Последнее отображённое время
|
||||||
|
|
||||||
// Проверяем, нужно ли обновлять дисплей
|
// Проверяем, нужно ли обновлять дисплей
|
||||||
if (uptimeSeconds == lastDisplayedTime) {
|
if (uptimeSeconds == lastDisplayedTime) {
|
||||||
return false; // Время не изменилось
|
return false; // Время не изменилось
|
||||||
}
|
}
|
||||||
|
|
||||||
// Время изменилось, обновляем дисплей
|
// Время изменилось, обновляем дисплей
|
||||||
unsigned long hours = uptimeSeconds / 3600;
|
unsigned long hours = uptimeSeconds / 3600;
|
||||||
unsigned int minutes = (uptimeSeconds % 3600) / 60;
|
unsigned int minutes = (uptimeSeconds % 3600) / 60;
|
||||||
unsigned int seconds = uptimeSeconds % 60;
|
unsigned int seconds = uptimeSeconds % 60;
|
||||||
|
|
||||||
// Формируем строку времени
|
// Формируем строку времени
|
||||||
char timeStr[16] = "UP ";
|
char timeStr[16] = "UP ";
|
||||||
char *ptr = timeStr + 3;
|
char *ptr = timeStr + 3;
|
||||||
ptr = ulongToStr(hours, ptr);
|
ptr = ulongToStr(hours, ptr);
|
||||||
@ -710,19 +710,19 @@ bool displayUptime(void) {
|
|||||||
SSD1306_Puts(timeStr, &Font_7x10, SSD1306_COLOR_WHITE);
|
SSD1306_Puts(timeStr, &Font_7x10, SSD1306_COLOR_WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запоминаем последнее отображённое время
|
// Запоминаем последнее отображённое время
|
||||||
lastDisplayedTime = uptimeSeconds;
|
lastDisplayedTime = uptimeSeconds;
|
||||||
SSD1306_UpdateScreen();
|
SSD1306_UpdateScreen();
|
||||||
return true; // Время было обновлено
|
return true; // Время было обновлено
|
||||||
}
|
}
|
||||||
void displayTemp(void) {
|
void displayTemp(void) {
|
||||||
static int OldeqepTemperature = 0;
|
static int OldeqepTemperature = 0;
|
||||||
uint16_t temp, eqepTemperature = geteqepTemperature();
|
uint16_t temp, eqepTemperature = geteqepTemperature();
|
||||||
temp= eqepTemperature;
|
temp= eqepTemperature;
|
||||||
char temp_str[11];
|
char temp_str[11];
|
||||||
//int seconds = 30; // Пример значения секунд
|
//int seconds = 30; // Пример значения секунд
|
||||||
|
|
||||||
// Заполняем первые 6 символов вручную
|
// Заполняем первые 6 символов вручную
|
||||||
temp_str[0] = 'T';
|
temp_str[0] = 'T';
|
||||||
temp_str[1] = 'E';
|
temp_str[1] = 'E';
|
||||||
temp_str[2] = 'M';
|
temp_str[2] = 'M';
|
||||||
@ -735,19 +735,19 @@ void displayTemp(void) {
|
|||||||
temp_str[9] = ' ';
|
temp_str[9] = ' ';
|
||||||
temp_str[10] = ' ';
|
temp_str[10] = ' ';
|
||||||
|
|
||||||
// Заполняем последние 2 символа секундами через цикл for
|
// Заполняем последние 2 символа секундами через цикл for
|
||||||
// Преобразуем сотни
|
// Преобразуем сотни
|
||||||
// Флаг для отслеживания вывода цифр
|
// Флаг для отслеживания вывода цифр
|
||||||
int i=5;
|
int i=5;
|
||||||
bool startedOutput = false;
|
bool startedOutput = false;
|
||||||
// Преобразование сотен
|
// Преобразование сотен
|
||||||
if (temp >= 100 || startedOutput) {
|
if (temp >= 100 || startedOutput) {
|
||||||
temp_str[i] = (temp / 100) + '0';
|
temp_str[i] = (temp / 100) + '0';
|
||||||
i++;
|
i++;
|
||||||
startedOutput = true;
|
startedOutput = true;
|
||||||
}
|
}
|
||||||
temp %= 100;
|
temp %= 100;
|
||||||
// Преобразование десятков
|
// Преобразование десятков
|
||||||
if (temp >= 10 || startedOutput) {
|
if (temp >= 10 || startedOutput) {
|
||||||
temp_str[i] = (temp / 10) + '0';
|
temp_str[i] = (temp / 10) + '0';
|
||||||
startedOutput = true;
|
startedOutput = true;
|
||||||
@ -756,7 +756,7 @@ void displayTemp(void) {
|
|||||||
temp_str[i] = temp%10 + '0';
|
temp_str[i] = temp%10 + '0';
|
||||||
i++;
|
i++;
|
||||||
|
|
||||||
// Отображаем температуру
|
// Отображаем температуру
|
||||||
SSD1306_Puts(temp_str, &Font_11x18, SSD1306_COLOR_WHITE);
|
SSD1306_Puts(temp_str, &Font_11x18, SSD1306_COLOR_WHITE);
|
||||||
|
|
||||||
OldeqepTemperature = eqepTemperature;
|
OldeqepTemperature = eqepTemperature;
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
//
|
//
|
||||||
// Defines
|
// Defines
|
||||||
//
|
//
|
||||||
//typedef unsigned char uint8_t;
|
typedef unsigned char uint8_t;
|
||||||
#define I2C_SLAVE_ADDRESS 0x3C
|
#define I2C_SLAVE_ADDRESS 0x3C
|
||||||
|
|
||||||
#define MAX_BUFFER_SIZE 0x10
|
#define MAX_BUFFER_SIZE 0x10
|
||||||
@ -16,7 +16,7 @@
|
|||||||
#define GPIO_PIN_SCLA 1 // GPIO number for I2C SCLA
|
#define GPIO_PIN_SCLA 1 // GPIO number for I2C SCLA
|
||||||
|
|
||||||
|
|
||||||
// Макрос для отправки команды на OLED-дисплей
|
// Ìàêðîñ äëÿ îòïðàâêè êîìàíäû íà OLED-äèñïëåé
|
||||||
#define SSD1306_WRITECOMMAND(cmd) \
|
#define SSD1306_WRITECOMMAND(cmd) \
|
||||||
do { \
|
do { \
|
||||||
I2C_TXdata[0] = 0x00; \
|
I2C_TXdata[0] = 0x00; \
|
||||||
@ -44,5 +44,3 @@ typedef enum {
|
|||||||
} SSD1306_COLOR_t;
|
} SSD1306_COLOR_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void SSD1306_UpdateScreen(void);
|
|
||||||
|
|||||||
@ -26,143 +26,11 @@
|
|||||||
#include "rele.h"
|
#include "rele.h"
|
||||||
#include "rele.h"
|
#include "rele.h"
|
||||||
#include "i2c_oled.h"
|
#include "i2c_oled.h"
|
||||||
|
#include "timersec.h"
|
||||||
|
|
||||||
extern uint16_t I2C_TXdata[];
|
extern uint16_t I2C_TXdata[];
|
||||||
extern uint16_t I2C_RXdata[];
|
extern uint16_t I2C_RXdata[];
|
||||||
|
|
||||||
struct CPUTIMER_VARS CpuTimer0;
|
|
||||||
struct CPUTIMER_VARS CpuTimer1;
|
|
||||||
struct CPUTIMER_VARS CpuTimer2;
|
|
||||||
|
|
||||||
unsigned long uptimeSeconds = 0; // Глобальная переменная для часов
|
|
||||||
|
|
||||||
__interrupt void cpuTimer0ISR(void)
|
|
||||||
{
|
|
||||||
CpuTimer0.InterruptCount++;
|
|
||||||
uptimeSeconds++;
|
|
||||||
//
|
|
||||||
// Acknowledge this interrupt to receive more interrupts from group 1
|
|
||||||
//
|
|
||||||
PieCtrlRegs.PIEACK.all = PIEACK_GROUP1;
|
|
||||||
}
|
|
||||||
void InitCpuTimers(void)
|
|
||||||
{
|
|
||||||
//
|
|
||||||
// CPU Timer 0
|
|
||||||
// Initialize address pointers to respective timer registers:
|
|
||||||
//
|
|
||||||
CpuTimer0.RegsAddr = &CpuTimer0Regs;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Initialize timer period to maximum:
|
|
||||||
//
|
|
||||||
CpuTimer0Regs.PRD.all = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Initialize pre-scale counter to divide by 1 (SYSCLKOUT):
|
|
||||||
//
|
|
||||||
CpuTimer0Regs.TPR.all = 0;
|
|
||||||
CpuTimer0Regs.TPRH.all = 0;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Make sure timer is stopped:
|
|
||||||
//
|
|
||||||
CpuTimer0Regs.TCR.bit.TSS = 1;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reload all counter register with period value:
|
|
||||||
//
|
|
||||||
CpuTimer0Regs.TCR.bit.TRB = 1;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reset interrupt counters:
|
|
||||||
//
|
|
||||||
CpuTimer0.InterruptCount = 0;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Initialize address pointers to respective timer registers:
|
|
||||||
//
|
|
||||||
CpuTimer1.RegsAddr = &CpuTimer1Regs;
|
|
||||||
CpuTimer2.RegsAddr = &CpuTimer2Regs;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Initialize timer period to maximum:
|
|
||||||
//
|
|
||||||
CpuTimer1Regs.PRD.all = 0xFFFFFFFF;
|
|
||||||
CpuTimer2Regs.PRD.all = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Initialize pre-scale counter to divide by 1 (SYSCLKOUT):
|
|
||||||
//
|
|
||||||
CpuTimer1Regs.TPR.all = 0;
|
|
||||||
CpuTimer1Regs.TPRH.all = 0;
|
|
||||||
CpuTimer2Regs.TPR.all = 0;
|
|
||||||
CpuTimer2Regs.TPRH.all = 0;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Make sure timers are stopped:
|
|
||||||
//
|
|
||||||
CpuTimer1Regs.TCR.bit.TSS = 1;
|
|
||||||
CpuTimer2Regs.TCR.bit.TSS = 1;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reload all counter register with period value:
|
|
||||||
//
|
|
||||||
CpuTimer1Regs.TCR.bit.TRB = 1;
|
|
||||||
CpuTimer2Regs.TCR.bit.TRB = 1;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reset interrupt counters:
|
|
||||||
//
|
|
||||||
CpuTimer1.InterruptCount = 0;
|
|
||||||
CpuTimer2.InterruptCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// ConfigCpuTimer - This function initializes the selected timer to the period
|
|
||||||
// specified by the "Freq" and "Period" parameters. The "Freq"
|
|
||||||
// is entered as "MHz" and the period in "uSeconds". The timer
|
|
||||||
// is held in the stopped state after configuration.
|
|
||||||
//
|
|
||||||
void ConfigCpuTimer(struct CPUTIMER_VARS *Timer, float Freq, float Period)
|
|
||||||
{
|
|
||||||
Uint32 temp;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Initialize timer period:
|
|
||||||
//
|
|
||||||
Timer->CPUFreqInMHz = Freq;
|
|
||||||
Timer->PeriodInUSec = Period;
|
|
||||||
temp = (long) (Freq * Period);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Counter decrements PRD+1 times each period
|
|
||||||
//
|
|
||||||
Timer->RegsAddr->PRD.all = temp - 1;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Set pre-scale counter to divide by 1 (SYSCLKOUT):
|
|
||||||
//
|
|
||||||
Timer->RegsAddr->TPR.all = 0;
|
|
||||||
Timer->RegsAddr->TPRH.all = 0;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Initialize timer control register:
|
|
||||||
//
|
|
||||||
Timer->RegsAddr->TCR.bit.TSS = 1; // 1 = Stop timer, 0 = Start/Restart
|
|
||||||
// Timer
|
|
||||||
Timer->RegsAddr->TCR.bit.TRB = 1; // 1 = reload timer
|
|
||||||
Timer->RegsAddr->TCR.bit.SOFT = 0;
|
|
||||||
Timer->RegsAddr->TCR.bit.FREE = 0; // Timer Free Run Disabled
|
|
||||||
Timer->RegsAddr->TCR.bit.TIE = 1; // 0 = Disable/ 1 = Enable Timer
|
|
||||||
// Interrupt
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reset interrupt counter:
|
|
||||||
//
|
|
||||||
Timer->InterruptCount = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitPerif(void)
|
void InitPerif(void)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -227,32 +95,13 @@ void InitPerif(void)
|
|||||||
eqep_init();
|
eqep_init();
|
||||||
#endif
|
#endif
|
||||||
// Enable Timer0
|
// Enable Timer0
|
||||||
EALLOW;
|
|
||||||
PieVectTable.TIMER0_INT = &cpuTimer0ISR;
|
|
||||||
EDIS;
|
|
||||||
InitCpuTimers();
|
InitCpuTimers();
|
||||||
// Configure CPU-Timer 0, 1, and 2 to interrupt every second:
|
// Configure CPU-Timer 0, 1, and 2 to interrupt every second:
|
||||||
// 200MHz CPU Freq, 1 second Period (in uSeconds)
|
// 200MHz CPU Freq, 1 second Period (in uSeconds)
|
||||||
//
|
//
|
||||||
ConfigCpuTimer(&CpuTimer0, 200, 1000000);
|
ConfigCpuTimer(&CpuTimer0, 200, 1000000);
|
||||||
//
|
|
||||||
// To ensure precise timing, use write-only instructions to write to the
|
|
||||||
// entire register. Therefore, if any of the configuration bits are changed
|
|
||||||
// in ConfigCpuTimer and InitCpuTimers, the below settings must also be
|
|
||||||
// be updated.
|
|
||||||
//
|
|
||||||
CpuTimer0Regs.TCR.all = 0x4000;
|
|
||||||
//
|
|
||||||
// Enable CPU int1 which is connected to CPU-Timer 0, CPU int13
|
|
||||||
// which is connected to CPU-Timer 1, and CPU int 14, which is connected
|
|
||||||
// to CPU-Timer 2
|
|
||||||
//
|
|
||||||
IER |= M_INT1;
|
|
||||||
//
|
|
||||||
// Enable TINT0 in the PIE: Group 1 interrupt 7
|
|
||||||
//
|
|
||||||
PieCtrlRegs.PIEIER1.bit.INTx7 = 1;
|
|
||||||
//
|
|
||||||
// Enable global Interrupts and higher priority real-time debug events:
|
// Enable global Interrupts and higher priority real-time debug events:
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user