Jump to content

    
Serhiy_UA

Ввод спец.регистров UART в классы C++ для STM32

Recommended Posts

10 hours ago, x893 said:

Смотрите, изучайте и улучшайте

https://github.com/stm32duino/Arduino_Core_STM32

haker_fox и x893, спасибо за информацию! Начал понимать, все не так сложно как казалось:).

Share this post


Link to post
Share on other sites

Вам в 3-м сообщении сказали: создайте класс UART, заведите в нем помимо ваших данных, поле указателя на структуру аппаратного UART из .h - файла микроконтроллера, инициализируйте его в конструкторе с параметром. Создайте объекты по количеству аппаратных UARTов, передавая в конструктор нужный указатель. Будут у вас несколько объектов одного класса. А в методах класса обращайтесь к регистрам аппаратного UARTа через этот указатель.

Share this post


Link to post
Share on other sites
2 hours ago, Darth Vader said:

Вам в 3-м сообщении сказали: создайте класс UART, заведите в нем помимо ваших данных, поле указателя на структуру аппаратного UART из .h - файла микроконтроллера, инициализируйте его в конструкторе с параметром. Создайте объекты по количеству аппаратных UARTов, передавая в конструктор нужный указатель. Будут у вас несколько объектов одного класса. А в методах класса обращайтесь к регистрам аппаратного UARTа через этот указатель.

Я так и понял, но Вы, что называется, разложили все конкретно по полочкам. Спасибо! 

Share this post


Link to post
Share on other sites
9 minutes ago, Serhiy_UA said:

Я так и понял, но Вы, что называется, разложили все конкретно по полочкам. Спасибо! 

"Разложили все по полочкам" все уже в первых постах, а Darth Vader вообще разжевал в кашу и практически ложку в рот положил. Жевать не надо. Вот уж удивительно будет, если и тут возникнут сложности )))

Share this post


Link to post
Share on other sites
53 minutes ago, Forger said:

 Вот уж удивительно будет, если и тут возникнут сложности )))

При сложностях спрошу еще. Благодарю всех за разъяснения, особенно Forger за выложенные коды. Эти проекты я делаю для самообразования, спешить мне не куда, разберусь.... 

Share this post


Link to post
Share on other sites
2 hours ago, Forger said:

Вот уж удивительно будет, если и тут возникнут сложности )))

А они есть. Для меня, например, до сих пор не совсем понятно, что лучше: делать классы драйверов на основе "синлтона", настраивая драйвер в run-time, либо делать на шаблонах, настраивая всё на этапе компиляции.. Но это тонкости. И лень не позволяет мне заняться этим вплотную. Единственное, что сделал, так это убрал из всех синглтонов динамическое выделение памяти с помощью new.

Share this post


Link to post
Share on other sites
1 hour ago, haker_fox said:

что лучше: делать классы драйверов на основе "синлтона", настраивая драйвер в run-time

Зачем тут вообще синглтон? Это же крест на масштабируемости.

Share this post


Link to post
Share on other sites
4 minutes ago, haker_fox said:

Не совсем понял, что вы имеете в виду?

Синглтон может быть только один. Мне непонятно куда его втыкать в контексте драйверов.

Синглтон-драйвер UART'а? Так UART'ов может быть несколько. В том числе несколько одинаковых.
Закрывать синглтоном доступ к ресурсам? Опять же - к каким? К выводам - не подходит, их может быть много. К общему буферу - читатель-писатель для обмена данными? Опять же, в ходе работы из одного общего буфера, как правило, выделяются области, являющиеся, по логике использования, такими же буферами, как общий, и их логично делать одинаковыми, а значит, их будет много.

Edited by one_eight_seven

Share this post


Link to post
Share on other sites
10 minutes ago, one_eight_seven said:

Синглтон может быть только один

Ох да, здесь я неточен в терминах (я не очень хорошо разбираюсь в териминологии программирования), скорее всего я имел в виду фабрику-синглтон. Или как правильно это называется? Посмотрите, пожалуйста здесь я привёл пример хидера своего драйвера SPI. В качестве аргумента функции instance передаётся номер шины, с которой мы будем работать. Естественно, что классический "одиночка" здесь не подходит)

Share this post


Link to post
Share on other sites
17 minutes ago, haker_fox said:

Или как правильно это называется?

Не знаю. Но ни на фабричный метод, ни на абстрактную фабрику это не похоже. На синглтон похоже, только стандартный конструктор доступен, так что создать ещё экземпляр труда не составит. При этом, создаётся, насколько я вижу статический массив драйверов.

Выглядит как переусложнённый массив из драйверов.

Какие тут плюсы? экономия памяти на методе instance?

Ну и тот самый крест на масштабируемости:

При изменении количества SPI необходимо переписывать этот класс - изменять размер массива, добавлять обработчик прерываний.

А если поднято автоматическое тестирование, CI/CD, то переписывание тестов.

Edited by one_eight_seven

Share this post


Link to post
Share on other sites

У меня назрел вопрос, наверное, не совсем по теме.

 

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

 

А какой смысл вообще делать какие-то универсальные интерфейсы для низкоуровневой работы с физическими интерфейсами МК?

Ну, допустим, тот же UART. Ну одинаковым там будет, разве что, инициализация. И то универсальность под вопросом.

Например, сегодня нужна инициализация UART и работа по прерываниям, а завтра к этому UART надо приклеить DMA и, соответственно, работать через него.

 

А учитывая громадную вариацию логики взаимодействия этого самого UART с тем же DMA, таймерами или еще чем, затея обобщить интерфейс работы с периферией МК видится не выигрышной. Или я ошибаюсь? Еще ни разу не видел проектов, где "один раз класс написал, и потом тупо используешь". Типа по канонам ООП. Один фиг приходится что-то допиливать, дорабатывать функционал классов и т.д.

 

Пример.

Есть железка, которая, помимо своих основных функций, должна подключаться к ПК по UART (отладочный порт).

На ПК крутится GUI-программа, вся из себя красивая с кнопочками, визуальными компонентами и текстовой консолью для print() с МК.

И вот чтобы добавить эту функциональность в свой проект, я просто создал сначала LL-драйвер dbg.h/dbg.c

Спойлер

dbg.h


#ifndef _DBG_H_
#define _DBG_H_


#include "types.h"
#include "stddefs.h"


#define DBG_GPIO_RX     A, 10
#define DBG_GPIO_TX     A,  9
#define DBG_UART_NUM    1

#define DBG_UART_BR     115200

#define DBG_MAXMSG_SIZE 256

typedef enum
{
  DBG_CMD_PRINTNORM = 0,
  DBG_CMD_PRINTWARN,
  DBG_CMD_PRINTCRIT,
  DBG_USRCMD_MINNUM
}eDbgCmd;


void    dbg_Init(void);
void    dbg_Print(u8 str[], u32 cmd);
#define dbg_PrintNorm(s) dbg_Print((u8 *)s, DBG_CMD_PRINTNORM)
#define dbg_PrintWarn(s) dbg_Print((u8 *)s, DBG_CMD_PRINTWARN)
#define dbg_PrintCrit(s) dbg_Print((u8 *)s, DBG_CMD_PRINTCRIT)

u32     dbg_RecvMsg(u8 msg[]);
u32     dbg_SendMsg(u8 msg[], u32 len);

#endif

 

dbg.c


#include <string.h>

#include "hw.h"
#include "dbg.h"
#include "ringbuf.h"
#include "stm32f10x.h"

#include "retarget.h"


#define UART           concat2(USART, DBG_UART_NUM)
#define UARTIRQ        concat3(USART, DBG_UART_NUM, _IRQn)

#define EnGPIOClk(d)   RCC->APB2ENR |= concat3(RCC_APB2ENR_IOP,  _selarg1(d), EN)
#if (DBG_UART_NUM == 1)
  #define EnUARTClk(d) RCC->APB2ENR |= concat3(RCC_APB2ENR_USART,         d,  EN)
#else
  #define EnUARTClk(d) RCC->APB1ENR |= concat3(RCC_APB1ENR_USART,         d,  EN)
#endif

#define DefPinInPEn(d) _CRREG(d)      = _CRREG(d) & ~(0xF << _CRPOS(d)) | 0x8 << _CRPOS(d)
#define DefPinAltPP(d) _CRREG(d)      = _CRREG(d) & ~(0xF << _CRPOS(d)) | 0xB << _CRPOS(d)
#define EnPinInPUp(d)  _GPIO(d)->ODR |= _PIN(d)

#define RxByteUART()   UART->DR
#define TxByteUART(b)  UART->DR   = (b)
#define EnTxIrqUART()  UART->CR1 |=  USART_CR1_TXEIE
#define DisTxIrqUART() UART->CR1 &= ~USART_CR1_TXEIE


#define _GPIO(g, p)    GPIO##g
#define _PIN(g, p)     (1 << (p))
#define _CRPOS(g, p)   (4 * ((p) % 8))
#define _CRREG(g, p)   *((p) > 7 ? &_GPIO(g, p)->CRH : &_GPIO(g, p)->CRL)


#define UARTISR()      void concat3(USART, DBG_UART_NUM, _IRQHandler)(void)


static sRingBuf RxRingQ, TxRingQ;


UARTISR()
{
  u32 isr = UART->SR;
  u32 icr = UART->CR1;
  if(icr & isr & USART_SR_RXNE)
  {
    static u8  cb   = 0, pb = 0;
    static u32 bcnt = 0, fs = 0;
    static u8 *msglen[sizeof(bcnt)];
    if((cb = RxByteUART()) != pb && pb == 0xC0)
    
    ...
  }
  
  ...
}


void dbg_Init(void)
{
  rb_Init(&RxRingQ);
  rb_Init(&TxRingQ);
  
  EnGPIOClk(DBG_GPIO_RX);
  EnGPIOClk(DBG_GPIO_TX);
  EnUARTClk(DBG_UART_NUM);
  
  __DSB();
  
  DefPinInPEn(DBG_GPIO_RX);
  DefPinAltPP(DBG_GPIO_TX);
  EnPinInPUp(DBG_GPIO_RX);
  
  sHWClkFreq freq;
  hw_GetAllClkFreq(&freq);
  u32 apbfreq = 0;
#if (DBG_UART_NUM == 1)
  apbfreq = freq.apb[1];
#else
  apbfreq = freq.apb[0];
#endif
  UART->BRR  = apbfreq / DBG_UART_BR;
  UART->CR1 |= USART_CR1_TE |
               USART_CR1_RE |
               USART_CR1_RXNEIE;
  NVIC_EnableIRQ(UARTIRQ);
  NVIC_SetPriority(UARTIRQ, 1);
  UART->CR1 |= USART_CR1_UE;
}

void dbg_Print(u8 str[], u32 cmd)
{
  u32 slen = strlen((char *)str) + 1;
  u32 tlen = slen + sizeof(cmd);
  if(slen > 1 && rb_GetFree(&TxRingQ) >= sizeof(tlen) + tlen)
  {
    rb_Write(&TxRingQ, (RBTYPE *)&tlen, sizeof(tlen));
    rb_Write(&TxRingQ, (RBTYPE *)&cmd,  sizeof(cmd));
    rb_Write(&TxRingQ, str, slen); EnTxIrqUART();
  }
}

u32 dbg_RecvMsg(u8 msg[])
{
  u32 len = 0;
  if(rb_GetBusy(&RxRingQ) > sizeof(len))
    rb_Peek(&RxRingQ, (RBTYPE *)&len, sizeof(len));
  if(len > 0)
  {
    if(len <= DBG_MAXMSG_SIZE)
    {
      rb_FreeR(&RxRingQ, sizeof(len));
      rb_Read(&RxRingQ, (RBTYPE *)msg, len);
    }
    else rb_FreeR(&RxRingQ, sizeof(len) + len), len = 0;
  }
  return len;
}

u32 dbg_SendMsg(u8 msg[], u32 len)
{
  u32 rets = 0;
  if(len > 0 && rb_GetFree(&TxRingQ) >= sizeof(len) + len)
  {
    rb_Write(&TxRingQ, (RBTYPE *)&len, sizeof(len));
    rb_Write(&TxRingQ, msg, len); EnTxIrqUART();
  }
  else rets = 1;
  return rets;
}

 

 

Этот драйвер реализует настройку UART и базовые операции отправки/приема данных с буферизацией сообщений.

 

А теперь я создаю, можно сказать, независимое приложение для многопоточной ОС. Реализуется оно в файлах pcdbg.h/pcdbg.c

Спойлер

pcdbg.h


#ifndef _PCDBG_H_
#define _PCDBG_H_


#include "types.h"
#include "stddefs.h"


void pcdbg_Init(void);

#endif

 

pcdbg.c


#include "dbg.h"
#include "rel.h"
#include "eds.h"
#include "crc.h"
#include "pcdbg.h"
#include "verhist.h"


typedef enum
{
  CMD_GETINF = DBG_USRCMD_MINNUM,
  CMD_ONREL,
  CMD_OFFREL
}eCmd;

typedef struct
{
  u32 cmd;
  u32 arg;
  u32 crc;
}sRxMsg;

typedef struct
{
  u32 cmd;
  struct
  {
    u32 can1    : 1;
    u32 can2    : 1;
    u32 can3    : 1;
    u32 canbyp  : 1;
    u32 canmode : 2;
  }rel;
  u32 snum;
  s32 tcpu;
  u32 vcpu;
  u32 hwv;
  u32 swv;
  u32 crc;
}sTxMsg;


static void RxMsgProc(u32 msg[], u32 len)
{
  sRxMsg *rxmsg = (sRxMsg *)msg;
  switch(rxmsg->cmd)
  {
    case CMD_GETINF:
    {
      sTxMsg txmsg;
      txmsg.cmd         = CMD_GETINF;
      txmsg.rel.can1    = rel_isEnCAN1();
      txmsg.rel.can2    = rel_isEnCAN2();
      txmsg.rel.can3    = rel_isEnCAN3();
      txmsg.rel.canbyp  = rel_isBypCAN();
      txmsg.rel.canmode = rel_GetCANMode();
      txmsg.snum        = 0;
      txmsg.tcpu        = 0;
      txmsg.vcpu        = 0;
      txmsg.hwv         = VERHIST_HWVER;
      txmsg.swv         = VERHIST_PACKSWVER;
      txmsg.crc         = crc_CalcSW((u32 *)&txmsg,
                            sizeof(txmsg) / 4 - 1);
      dbg_SendMsg((u8 *)&txmsg, sizeof(txmsg));
      break;
    }
    case CMD_ONREL:
      if(rxmsg->arg <= REL_CANBYP)
        rel_On(rxmsg->arg);
      break;
    case CMD_OFFREL:
      if(rxmsg->arg <= REL_CANBYP)
        rel_Off(rxmsg->arg);
      break;
  }
}

static void PCDbgTsk(void)
{
  dbg_PrintNorm("Run PC debug thread");
  
  while(1)
  {
    static u32 msg[DBG_MAXMSG_SIZE / 4];
    u32 len = dbg_RecvMsg((u8 *)msg) / 4;
    if(len > 0 && crc_CalcSW(msg, len) == 0)
      RxMsgProc(msg, len);
    else eds_Timeout(20);
  }
}

void pcdbg_Init(void)
{
  dbg_Init();
  
  eds_CreateTsk(EDS_TSKID_PCDBG, PCDbgTsk, 256);
}

 

 

Все! Логика отладчика добавлена в железку. При желании, может корректироваться независимо от остального проекта.

Номер UART и номера прерываний определяются макросами вверху .c-файла, а user-defined окружение (для UART это GPIO да скорость, как у меня) - в соответствующем .h.

И чем тогда подход в стиле ООП с классами/наследованием и т.д. лучше, допустим, того, что использую я в чистых Сях? Где тут я мог бы что-то универсализировать?

Share this post


Link to post
Share on other sites
2 minutes ago, Arlleex said:

И чем тогда подход в стиле ООП с классами/наследованием и т.д. лучше, допустим, того, что использую я в чистых Сях? Где тут я мог бы что-то универсализировать?

Меньше объем текста программы и проще в ней разбираться. Навык приходит только с опытом. 

з.ы А тем временем тема постепенно превращалась в типичный холивар.... )))

Share this post


Link to post
Share on other sites
46 minutes ago, one_eight_seven said:

Какие тут плюсы? экономия памяти на методе instance?

Об этом не думал. Наличие недостатков, как и достоинств не отрицаю. Здесь, скорее, дело привычки.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.