Кто любит RISC в жизни, заходим, не стесняемся.
Ответить

RTC: установка даты и времени через HAL

Вт июл 19, 2022 05:31:48

Коллеги, подскажите, плз... Никак не въеду в установку даты-времени через HAL. Ситуация элементарная: оставил я плату со своим STM32F407VET6 на полгода в покое, RTC работает от батарейки. Включаю, а часы отстали или убежали на несколько часов.

Через регистры, вроде-бы, все понятно, а вот через HAL что-то никак не въеду. Подкиньте, плз, последовательность вызовов HAL для установки даты и времени?

Re: RTC: установка даты и времени через HAL

Вт июл 19, 2022 09:26:17

А что, надо именно через кал?
Тернист путь…

Re: RTC: установка даты и времени через HAL

Вт июл 19, 2022 11:12:31

Да я уже тоже склоняюсь к тому, чтобы забить на эту гадость и сделать всё через регистры. Просто у меня большой проект собран из кубиков, там задействована FATFS от Чана, основное взаимодействие с периферией у меня и так через регистры, через кубики идёт только FATFS. А RTC так, между делом, есть оборудование на МК, почему бы его и не использовать. Только посмотрел я на эти кубические драйвера HAL и решил, что эту дрянь я себе в голову загружать не буду. В слабой надежде спросил, может кто это делал, а сам сегодня поразглядывал работу с RTC через регистры. В общем-то, ничего военного, просто я надеялся, что у кого-нибудь найдется готовое решение, которое можно будет скопипастить, и мне можно будет не вникать в подробности этого самого RTC. Получается, нет! Ладно, вникну...

Re: RTC: установка даты и времени через HAL

Ср июл 20, 2022 18:10:51

afz, так вроде FATFS требует от железа только процедуру чтения/отправки байта через SPI... И ее вполне на регистрах можно...
А, в какой то версии еще FATFS хотел тик от таймера 100 раз в сек. Но там сервисные функции - их можно дергать и не строго 100 раз в сек.

Re: RTC: установка даты и времени через HAL

Ср июл 20, 2022 18:20:16

Есть же компактные ФС для МК - специально, чтобы не было всякого излишества (скажем, зачем там права пользователя как в ext2?). Можно и вообще свое что-нибудь примитивное реализовать. Допустим, если файлы не нужно удалять, можно сделать последовательную запись, чем-то напоминающую структуру архива tar. Потом его просто при помощи dd к себе забрать и распаковать стандартными средствами.

Re: RTC: установка даты и времени через HAL

Ср июл 20, 2022 21:51:22

Нет, коллеги, мне была нужна настоящая FAT32, чтобы можно было выдернуть uSD-шку из моего контроллера, сунуть ее в кардридер и работать с ней на PC, потом выдернуть из кардридера, сунуть в мой контроллер и продолжить работу на нем. Если нужны подробности, приведу ссылки, но думаю, что вас это не заинтересует.

А на тему моего вопроса, так я уже, практически, вник в работу с RTC через регистры, да и нашел, где подкрутить, чтобы кубики под ногами не путались, когда мне это не нужно. Не хотелось вникать, надеялся скопипастить, но, увы, пришлось таки вникнуть...

Тогда еще один вопрос.
Хочу задействовать USART1, пусть кубики его проинитят, но потом хочу перехватить у них управление и отработать опять же, через регистры. Напомните, плз, где там подключить свою программу прерываний - вроде-бы там было что-то, объявленное, как weak и туда можно подсунуть свою программулю. Кажется, у них это называется коллбэком...

Добавлено after 2 hours 42 minutes 19 seconds:
Что-то я ничего не нашел внятного про кубические коллбэки. То есть, конечно, никто мне не мешает вписать в программу STM32F4xx_it.c между
/* USER CODE BEGIN USART1_IRQn 0 */ и /* USER CODE END USART1_IRQn 0 */ вызов своей программы обработки прерываний, только вот это еще одно место правки сгенерированного кубического кода, что мне активно не нравится. То есть, конечно, именно этот исходник должен сохранить мои изменения при повторной генерации из кубиков, но перегенеришь что-то по-другому, так не забыть и здесь подправить...

Re: RTC: установка даты и времени через HAL

Чт июл 21, 2022 12:30:20

Писать программу между USER CODE BEGIN USART1_IRQn 0 USER CODE END USART1_IRQn 0 должен мешать здравый смысл, т.к. в этот момент ядро контроллера будет находиться в обработчике прерывания, а он должен быть максимально коротким и минимально взаимодействовать с общей памятью (ну, кроме чтения - читать можно, но можно получить случайные данные).

Может вам дать код на регистрах? Для себя писал на кольцевом буфере (на чтение и запись), работа с DMA для приёма и передачи. Дополнительных сложностей не добавлял, типа snprintf(что-нибудь) - просто console_put(нуль-терминированная-строка) и дополнительные сущности типа _hex_word, _hex_byte, _int...

В общем, если будет интересно, код ниже.
Спойлер
Код:
volatile uint8_t DMA_STATE;
#define DMA_tty0_TX_ACTIVE   0x01

uint8_t tty0_TX_BUF[1024];
volatile uint16_t tty0_TX_POS;
volatile uint16_t tty0_WR_POS;

void DMA2_Stream7_IRQHandler(void) {
   // tty0 (отладочная консоль)
   if (DMA2->HISR & DMA_HISR_TCIF7) {
      DMA2->HIFCR = DMA_HIFCR_CTCIF7;         // сброс флага события TCIF
      DMA_STATE &= ~DMA_tty0_TX_ACTIVE;
      tty0_ActivateDMA();
   } else if (DMA2->HISR & DMA_HISR_HTIF7) {
      DMA2->HIFCR = DMA_HIFCR_CHTIF7;         // сброс флага события HTIF
   } else if (DMA2->HISR & DMA_HISR_FEIF7) {
      DMA2->HIFCR = DMA_HIFCR_CFEIF7;         // сброс флага события FEIF
   } else if (DMA2->HISR & DMA_HISR_DMEIF7) {
      DMA2->HIFCR = DMA_HIFCR_CDMEIF7;      // сброс флага события DMEIF
   } else if (DMA2->HISR & DMA_HISR_TEIF7) {
      DMA2->HIFCR = DMA_HIFCR_CTEIF7;         // сброс флага события TEIF
   };
};
void USART1_IRQHandler(void) {
   uint8_t tmp;
   while (USART1->SR & USART_SR_RXNE) {
      tmp = USART1->DR;
      if (DMA_STATE & DMA_tty0_thread_ready) {
#ifdef USE_FREERTOS
         xQueueSendFromISR(Q_tty0, &tmp, 0);
#endif
      } else {
         microrl_insert_char(prl, tmp);
      };
   };
};

void tty0_ActivateDMA(void) {
   uint16_t CurrWrPos = tty0_WR_POS;
   uint16_t DataToSend;
   DataToSend = 0;
   if(!(DMA_STATE & DMA_tty0_TX_ACTIVE)) {
      DMA2_Stream7->CR &= ~DMA_SxCR_EN;   // Отключаем поток DMA
      if (tty0_TX_POS != CurrWrPos) {
         // Если не совпадает - значит, данные есть. Или малый шанс на переполнение буфера.
         DMA2_Stream7->M0AR = (uint32_t)&(tty0_TX_BUF[tty0_TX_POS]);
         if (tty0_TX_POS < CurrWrPos) {
            // Нет перехода через конец буфера
            DataToSend = (CurrWrPos - tty0_TX_POS);
            tty0_TX_POS = CurrWrPos;
         } else {
            // Нужно сделать кольцо.
            DataToSend = (sizeof(tty0_TX_BUF) - tty0_TX_POS);
            tty0_TX_POS = 0;
         };
         DMA2_Stream7->NDTR = DataToSend;
         DMA2_Stream7->FCR = 0;
         USART1->SR = ~(USART_SR_TC);
         // И только ПОСЛЕ этого включаем его. Да, странность. Но иначе он уходит в ошибку.
         DMA2_Stream7->CR |= DMA_SxCR_EN;
         DMA_STATE |= DMA_tty0_TX_ACTIVE;
      };
   };   // Если активен - сработает при вызове события завершения обмена.
};


// ---------- Аппаратно-зависимые функции - инициализпция ----------
void UpdateUSART_BR(void) {
   static uint8_t PrescalerTable[8] = {1,1,1,1,2,4,8,16};
   static uint16_t AHB_Presc[16] = {1,1,1,1,1,1,1,1,2,4,8,16,64,128,256,512};
   uint8_t presc;
   uint16_t APB_Presc;
   SystemCoreClockUpdate();
   APB_Presc = AHB_Presc[(RCC->CFGR >> 4) & 0x0F];
   presc = PrescalerTable[(RCC->CFGR >> 13) & 0x07];
   USART1->CR1 &= ~(USART_CR1_TE | USART_CR1_RE | USART_CR1_UE);
   // Системная консоль / tty0 - 115200
   USART1->BRR = (SystemCoreClock / 115200 / presc / APB_Presc);   // Делим системную частоту на скорость порта, на делители APB и AHB, чтобы получить правильный делитель.
   USART1->CR1 |= (USART_CR1_TE | USART_CR1_RE | USART_CR1_UE);
};
void InitUSART(void) {
   GPIOB->MODER &= 0xFFFF0FFF;   // GPIOB[6-7].MODER = 00 - Input
   GPIOB->MODER |= 0x0000A000;   // GPIOB[6-7].MODER = 10 - Alternate
   GPIOB->AFR[0] &= 0xFFFFFFFF;   // GPIOB[6-7].Alternate = 0 (Sys)
   GPIOB->AFR[0] |= 0x77000000;   // GPIOB[6-7].Alternate = 7 (USART1)
   // Enable USART1 (tty0)
   RCC->APB2ENR |= RCC_APB2ENR_USART1EN;
   // Reset USART1
   RCC->APB2RSTR |= RCC_APB2RSTR_USART1RST;
   // UnReset USART1
   RCC->APB2RSTR &= ~RCC_APB2RSTR_USART1RST;
   USART1->CR2 = 0;
   USART1->CR3 = 0;
   // Сброс DMA
   RCC->AHB1RSTR |= RCC_AHB1RSTR_DMA2RST | RCC_AHB1RSTR_DMA1RST;
   RCC->AHB1RSTR &= ~(RCC_AHB1RSTR_DMA2RST | RCC_AHB1RSTR_DMA1RST);
   // Enable DMA1 & DMA2.
   RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN | RCC_AHB1ENR_DMA1EN;
   // Config USART1 to DMA Operations and clear flags.
   USART1->CR3 |= /*USART_CR3_DMAR |*/ USART_CR3_DMAT;
   USART1->SR &= ~(USART_SR_TC/* | USART_SR_RXNE*/);
   UpdateUSART_BR();
   // Указываем периферию
   DMA2_Stream7->PAR = (uint32_t)&(USART1->DR);
   // Указываем память
   DMA2_Stream7->M0AR = (uint32_t)&tty0_TX_BUF;
   // DMA1:Stream1:Channel4 (HW Console RX): MemInc + P->M + Transfer Complete INT.
//    DMA1_Stream1->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_MINC | DMA_SxCR_TCIE;
   // DMA1:Stream3:Channel4 (HW Console TX): MemInc + M->P + Transfer Complete INT.
   DMA2_Stream7->CR = DMA_SxCR_CHSEL_2 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE;
   NVIC_EnableIRQ(USART1_IRQn);
   NVIC_SetPriority(USART1_IRQn, 0x04);
   NVIC_SetPriority(DMA2_Stream7_IRQn, 0x03);
   NVIC_EnableIRQ(DMA2_Stream7_IRQn);
//   microrl_init (prl, console_put);         // Инициализпция консоли Microtl
//   microrl_set_complete_callback(prl, console_complete);   // Выставляем callback "Дополнить!"
//   microrl_set_execute_callback(prl, console_execute);   // Добавляем callback на выполнение.
   USART1->CR1 = (USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE | USART_CR1_UE);
};
// ---------- "Высокоуровневые" функции.

char char_to_hex(char value) {
   if (value > 9)
      return '7' + value;
   return '0' + value;
};
uint8_t hchar_to_int(char value) {
   if ((value >= '0') && (value <= '9')) {
      return (value - '0');
   };
   if ((value >= 'A') && (value <= 'F')) {
      return (value - 'A' + 0x0A);
   };
   if ((value >= 'a') && (value <= 'f')) {
      return (value - 'a' + 0x0A);
   };
   return 0;
};
void console_put(const char *text) {
   while(*text) {
      // Пока не нуль-терминатор
      tty0_TX_BUF[tty0_WR_POS] = *text;      // Копируем данные в буфер
      text++;                  // Сдвигаем указатель текста.
      tty0_WR_POS++;               // Сдвигаем указатель на 1 байт дальше.
      if (tty0_WR_POS >= sizeof(tty0_TX_BUF)) tty0_WR_POS = 0;   // Указатель должен быть в пределах допустимых значений.
   };
   // Здесь всё хорошо, записываем от старой позиции до новой.
   tty0_ActivateDMA();
};
void console_put_hex_dword(const uint32_t Data) {
   char txt[9];
   txt[0] = char_to_hex((Data >> 28) & 0x0F);
   txt[1] = char_to_hex((Data >> 24) & 0x0F);
   txt[2] = char_to_hex((Data >> 20) & 0x0F);
   txt[3] = char_to_hex((Data >> 16) & 0x0F);
   txt[4] = char_to_hex((Data >> 12) & 0x0F);
   txt[5] = char_to_hex((Data >> 8) & 0x0F);
   txt[6] = char_to_hex((Data >> 4) & 0x0F);
   txt[7] = char_to_hex((Data) & 0x0F);
   txt[8] = 0;
   console_put(txt);
};
void console_put_hex_word(const uint16_t Data) {
   char txt[5];
   txt[0] = char_to_hex((Data >> 12) & 0x0F);
   txt[1] = char_to_hex((Data >> 8) & 0x0F);
   txt[2] = char_to_hex((Data >> 4) & 0x0F);
   txt[3] = char_to_hex((Data) & 0x0F);
   txt[4] = 0;
   console_put(txt);
};
void console_put_hex_byte(const uint8_t Data) {
   char txt[3];
   txt[0] = char_to_hex((Data >> 4) & 0x0F);
   txt[1] = char_to_hex((Data) & 0x0F);
   txt[2] = 0;
   console_put(txt);
};
void console_put_int(const uint32_t value) {
   uint8_t txt[13];
   txt[12] = 0;
   console_put(utoa_builtin_div(value, (char *)txt));
};

Собственно, любой вывод текста отправляется в console_put(), который занимается рутиной, копируя строку из входа в кольцевой буфер. После этого пинается DMA контроллер, который перебрасывает данные в USART1. Если данные закончились - производится новая попытка запустить DMA, перепроверив указатели на буфер. Если совпадают - все данные были отправлены (или буфер был переполнен идеально полностью) и запускать не нужно. Если нужно - указатели соответствующим образом обновляются (указатель, который принадлежит DMA, а не функции!).

Получение байтов - посимвольное. Где-то в другом проекте принимал по DMA, но переписывать, чтобы заработало, немного лень.

Re: RTC: установка даты и времени через HAL

Пт июл 22, 2022 19:25:04

AlanDrakes, вообще-то, не так всё плохо. Точнее, многое зависит от кучи всяких факторов, но, если нет каких-то параллельных скоростных процессов, то, грамотно используя прерывания, все проблемы легко обходятся. Что действительно нельзя делать - это ждать чего-то в этом обработчике, а так - времени от одного до другого прерывания по TXE - 14.5 тыс тактов при работе на168 мГц и скорости USART'а 115200. И я с трудом представляю, чего туда такого можно насочинять, что бы потребовало больше 1000 тактов.

Естественно, все программы, работающие в прерываниях, должны быть быстрыми. То есть взяли что-то из регистров, поместили что-то в регистры, где-то отметились, и на выход. А ожидания будут в основном цикле, который while(1).

А без DMA не обойтись только тогда, когда нужно что-то скоростное и/или с минимально возможным джиттером.

Re: RTC: установка даты и времени через HAL

Вс июл 24, 2022 05:21:07

А без DMA не обойтись только тогда, когда нужно что-то скоростное и/или с минимально возможным джиттером.


Либо наоборот, очень медленное, что может работать пока ядро спит =)
Ответить