Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: C++ и работа с однотипной аппаратурой
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
BAT
Хочется написать прототип работы с железом так, чтоб потом по нему оформлять все остальные работы. В МК раньше работал только на чистом С,
но недавно начал разбираться с ScmRTOS, и решил стараться оформлять все по максимому в С++. Начал оформление работы с SPI (AT91SAM7X256).
Написал в лоб. Создал базовый CustomSPI с нестатическим локом (локировать на время работы одного чипселекта). От него создал два класса для SPI0 и SPI1.
Каждому прописал по своему статическому обработчику прерываний с флагом.
Прошу сразу простить за корявый С++.
CODE

class TCustomSpi
{
public:
TCustomSpi() : Locker() { }
char SPI_BUFF[SPI_BUFF_LEN];
virtual void spi_transaction(uint8_t* sdata, uint32_t slen, uint8_t* rdata, uint32_t rlen) = 0;
protected:
bool spi_wait;
char cs_curent;
OS::TMutex Locker;
};


class TSpi0 : public TCustomSpi
{
public:
TSpi0 () {init_spi();}
void configure_chipselect(uint32_t ConfigCS, uint32_t chipselect);
void change_chipselect(uint32_t chipselect);
void spi_transaction(uint8_t* sdata, uint32_t slen, uint8_t* rdata, uint32_t rlen);
protected:
void init_spi();
static OS_INTERRUPT void spi_RXBUFF();
static OS::TEventFlag Ready;
};
...

extern TSpi0 spi0;
extern TSpi1 spi1;


// Обработчик из cpp

OS_INTERRUPT void TSpi0::spi_RXBUFF(void)
{
OS::TISRW ISR;

// Clear interrupt
volatile dword Tmp = *AT91C_SPI0_SR;
if ((Tmp&AT91C_SPI_RXBUFF)!=0) spi0.Ready.SignalISR();
}


Все работает. Но смотрю на это и понимаю, что коряво. Все должно быть гибче. Может кто может поделиться мыслями и пимерами, как это должно быть?
Постоянно в конечной плате еще на этапе промежуточных прототипов плавают адреса, чипселекты и тд. Хочется оформить так, чтобы поменяв только в одном месте define
для каждого потребителя SPI его номер(0,1) и чипселект(0..3) можно было добиться его корректной работы. При этом не наплодить много лишнего кода съедающего память
и время работы.
Возможно правильно будеть свалить оба spi в один класс со статическими методами, а от него породить по экземпляру на каждый чипселект, где на стадии создания
будет определяться номер spi и его чипселект. Но тоже как-то мутно.
Поделитесь своими идеями, как это лучше сделать.
HARMHARM
Andy Mozzevilov выкладывал на форуме свой вариант. Поищите.
alx2
Цитата(BAT @ Dec 17 2009, 18:48) *
Хочется написать прототип работы с железом так, чтоб потом по нему оформлять все остальные работы.
Не совсем понятна задача. В интерфейсе базового класса определен виртуальный метод spi_transaction. Если вся работа с устройством предполагается через него, то зачем в производном классе определяются дополнительные публичные методы (configure_chipset, change_chipselect)? Если предполагается, что при старте будут созданы все необходимые объекты производных классов, и потом весь остальной код будет работать с ними через указатель на базовый класс, то кроме sip_transaction ему (остальному коду) все равно ничего не будет доступно. То есть, если эти методы действительно нужны при работе с устройством, все они должны быть объявлены в базовом классе как виртуальные. Если не нужны - то и в производных классах их нет смысла объявлять как публичные.
Базовый адрес, номер устройства и другие необходимые для работы с устройством параметры удобно задавать один раз при создании объекта конкретного устройства:
Код
class TMySpiType1 : TCustomSpi {
public:
  TMySpiType1(unsigned int base, int num): base_address(base), device_number(num) {}
  virtual void spi_transaction(...blablabla...);
private:
  const unsigned int base_address;
  const int device_number;
};

TCustomSpi *spi0;
TCustomSpi *spi1;
TCustomSpi *spi2;
TCustomSpi *spi3;
TCustomSpi *spi4;
TCustomSpi *spi5;

void setup()
{
   spi0 = new TMySpiType1(0x12340, 0);  // Два устройства на одном чипе типа 1
   spi1 = new TMySpiType1(0x12340, 1);

   spi2 = new TMySpiType2(0x65410, 0);  // И еще 4 устройства на двух чипах типа 2
   spi3 = new TMySpiType2(0x65410, 1);  // по два на каждом чипе
   spi4 = new TMySpiType2(0x65490, 0);
   spi5 = new TMySpiType2(0x65490, 1);
}

И еще, заводить буфер в базовом классе, да еще давать к нему публичный доступ - плохая идея. Логичнее всю необходимую буферизацию делать в производных классах.
BAT
Цитата(alx2 @ Dec 18 2009, 08:50) *
Не совсем понятна задача.

Задача оформить работу с железом ARM (uart, spi, twi и тд) в коде С++ максимально одинаково и гибко настраиваемо.
Spi взял за основу, как в теории наиболее полон (несколько портов, у каждого по несколько чипселектов)
по возможным вариантам работы.
Мой последний вариант чем-то близок к предложенному.

CODE
class TCustomSpi
{
public:
TCustomSpi() : Locker() { }
char SPI_BUFF[SPI_BUFF_LEN];
uint32_t spi_int_ptr; //указатель на процедуру обработки прерывания
void configure_chipselect(uint32_t ConfigCS, uint8_t chipselect);

void change_chipselect(uint8_t chipselect);
virtual void transaction( uint8_t* send_data, uint32_t send_size,
uint8_t* recv_data, uint32_t recv_size );
virtual void transaction( uint32_t send_size);

uint16_t ID_SPI; // ИД в списке оборудования
AT91PS_SPI Spi; // Указатель на структуру Spi
const Pin* Pins_SPI_cs; // конфигурация портов для чипселекта spi
const Pin* Pins_SPI; // конфигурация портов для spi
protected:
void init();
bool spi_wait;
char cs_curent;
OS::TMutex Locker; // один для всех чипселектов для одного SPI
};


extern const Pin Pins_SPI1_Cs [];
extern const Pin Pins_SPI0_Cs [];
extern const Pin Pins_SPI1 [];
extern const Pin Pins_SPI0 [];


class TSpi0 : public TCustomSpi
{
public:
TSpi0 () { ID_SPI = AT91C_ID_SPI0; Spi = AT91C_BASE_SPI0;
Pins_SPI = Pins_SPI0; Pins_SPI_cs = Pins_SPI0_Cs;
ReadyISR = &Ready; spi_int_ptr = (dword)spi_int; init();}//init_spi();}
protected:
static OS_INTERRUPT void spi_int();
static OS::TEventFlag Ready;
};

// Тоже самое для Spi1

....

extern TSpi0 spi0;
extern TSpi1 spi1;



В коде, где применяется SPI

#define OLED_SPI spi0
#define OLED_SPI_CS 2

void TOled::Init()
{

....

OLED_SPI.configure_chipselect(
(AT91C_SPI_DLYBCT & (0x01 << 24)) |
(AT91C_SPI_DLYBS & (0x01 << 16)) |
(AT91C_SPI_SCBR & (0x03 << 8)) |
(AT91C_SPI_BITS & (AT91C_SPI_BITS_8)) |
(AT91C_SPI_CSAAT & (0x0 << 3)) |
(AT91C_SPI_NCPHA & (0x0 << 1)) |
(AT91C_SPI_CPOL & (0x01 << 0)),
2);
OLED_SPI.change_chipselect(OLED_SPI_CS);

....

OLED_SPI.transaction(...);

...

}



Так мне пока кажется удобнее. Мьютекс блокирующий остальных потребителей(с другим чипселектом на том же порте),
флаг прерывания и сама функция обработки прерывания получается по одной штуке на каждый SPI (привязка к конкретному порту).
Если же порождать все от базового класса, то непонятно, как разделить работу spi1 и spi0.
Сами объекты spi у меня статически, heap в проекте пока просто отсутствует. Возможно не очень хорошо,
но вся аппаратура определяется еще на стадии сборки проекта, решил прописать сразу.
При использовании динамической инициализации есть подводные камни в виде не оптимальности получаемого
кода по скорости и объему? Или все как раз наоборот?
По поводу буфера. Он ведь в итоге будет в каждом экземпляре свой? Доступ снаружи к нему нужен для специфических
целей. Как один из вариантов - заполнить весь буфер константой и передать, не выделяя для этого дополнительно память снаружи.
Но возможно проще от него вообще отказаться и изменитьнемного алгоритм. Реально просто притянулось из старого
проекта на Си.

По удобству.
Если дисплей вдруг перебросят на другой SPI и чипселект, можно будет в сводном файле поменять
#define OLED_SPI spi1
#define OLED_SPI_CS 3
и все должно работать

Но, сильно сомневаюсь, что этот вариант близок к оптимально удобному
laughing.gif
Если есть другие, поделитесь.
Terminator
Я написал класс в который при создании передаётся pio и pin нужного чипселекта, по ним определяется какой из SPI задействовать. В sam7 работает.
SasaVitebsk
Цитата(BAT @ Dec 18 2009, 13:30) *
По удобству.
Если дисплей вдруг перебросят на другой SPI и чипселект ....

Честно говоря, жаль что тема подзаглохла. wassat.gif

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

Но некоторые свои соображения общего характера (чисто в плане обсуждения) хотелось бы высказать. Поскольку обобщать что-то вообще рано, то лучше по частному случаю (SPI).

1) Использование оборудования, даже одного и тогоже, в разных проектах, сильно отличается. Например работа с дисплеем и памятью требует объёмов и скоростей, а с каким-нибудь АЦП потребует дополнительных сигналов и синхронного управления (это я к примеру). Толи усложнять класс, толи делать несколько с разной функциональностью и ориентированностью? И в том и в другом случае, придётся городить какое-то описалово с уточнением деталей и подробной расшифровкой с уточнением "что для чего задумывалось". И в том и в другом случае возникает оверхед. Насколько он будет велик?

2) В проекте появился программный SPI. Или 2 на передачу с общим CLK. Или в частном случае, хотите обработать канал без прерывания (в совтовом это вообще сделать проблематично). Или в новом камне захотите задействовать DMA.

Как это всё привести к общему знаменателю?

А может поднятся на уровень чуть выше? К примеру был у меня проект, в котором информация хранилась в 24c512. Сейчас применили 45db021. Написал библиотеку для 45, чтобы работала как 24. Чтобы весь проект не лопатить. Может и с дисплеем что-то подобное сделать. Как бы сразу 2 уровня прописывать. Тогда уровень 0 (SPI) будет менее функциональный, но более оптимальный по результирующему коду. Я к этому не призываю, а размышляю просто.
Возьмём знаменитую 7 уровневую модель OSI. Так когда реально драйвер пишут, обычно захватывают сразу 2-3 уровня.
HARMHARM
Цитата(SasaVitebsk @ Dec 21 2009, 23:42) *
1) Использование оборудования, даже одного и тогоже, в разных проектах, сильно отличается. Например работа с дисплеем и памятью требует объёмов и скоростей, а с каким-нибудь АЦП потребует дополнительных сигналов и синхронного управления (это я к примеру). Толи усложнять класс, толи делать несколько с разной функциональностью и ориентированностью? И в том и в другом случае, придётся городить какое-то описалово с уточнением деталей и подробной расшифровкой с уточнением "что для чего задумывалось". И в том и в другом случае возникает оверхед. Насколько он будет велик?

Я использую такой подход. В итоге базовый класс становится весьма сложным, хотя там всего лишь работа непосредственно с регистрами периферии.
Наследники усложняются при повышении требований. Например, у меня один из наследников UART работает с RS-232, RS-485, J1708 плюс использует канал таймера для управления направлением. Оверхед немаленький, но специально не оценивал.
AHTOXA
Цитата(BAT @ Dec 17 2009, 18:48) *
Все работает. Но смотрю на это и понимаю, что коряво. Все должно быть гибче. Может кто может поделиться мыслями и пимерами, как это должно быть?

Вот как у меня сделано: Нажмите для просмотра прикрепленного файла

В конечном проекте пишу

hw.h:
Код
// SPI1:
  typedef spi_t<SPI_1> spi1_t;
  extern spi1_t spi1;
  
  // чипселект для ЦАП-а
  typedef Pin<'C', 9, 'L'> PIN_DAC_CS;
  
  // ЦАП:
  typedef ad5663_t<PIN_DAC_CS> dac_t;
  extern dac_t dac;


hw.cpp:
Код
spi1_t spi1;
  dac_t dac(spi1);
  eeprom_t at25(spi1);
Serega Doc
Добрый день. По мотивам предыдущего поста написал работу с портом для АТМеги как с объектом (в приложении)
Смущает очень сильно быстродействие того что я написал (с++ только начинаю изучать так что прошу совета как писать правильно).
Инициализация объекта:
Код
portio pb (IOPORTB, IODDRB, IOPINB); // 130 тактов LOW, 129 MEDIUM и 25 HIGH оптимизация не зависит от вида по скорости или по объему.

Перевод порта в Z состояние
Код
pb.portz(); // 65-LOW 64-MEDIUM 17-HIGH


Можно ли писать более оптимально по скорости изначально?
И как написать конструктор структуры что бы не было холостой инициализации на нулевой адрес?
demiurg_spb
Жесть! Изучите макросы имени Аскольда Волкова.
AHTOXA
Цитата(Serega Doc @ Jul 13 2010, 21:12) *
Добрый день. По мотивам предыдущего поста написал работу с портом для АТМеги как с объектом (в приложении)
Смущает очень сильно быстродействие того что я написал (с++ только начинаю изучать так что прошу совета как писать правильно).

Гляньте ещё вот этот топик. Я там в конце выложил свои варианты.
Dima_G
Цитата(Serega Doc @ Jul 13 2010, 22:12) *
Можно ли писать более оптимально по скорости изначально?


Подробно код не смотрел, но первое что бросилось в глаза: попробуйте инлайнить (inline) мелкие функции. Подозреваю, что все быстродействие съелось на вызовах.

Цитата(Serega Doc @ Jul 13 2010, 22:12) *
И как написать конструктор структуры что бы не было холостой инициализации на нулевой адрес?


Код
class ClSlon
{
  public:
    int iLength_m;

  public:
    ClSlon(int iLen_):iLength_m(iLen_)
    {}
}
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.