|
|
  |
C++ и работа с однотипной аппаратурой, Варианты оформления работы с однотипным железом на примере SPI |
|
|
|
Dec 17 2009, 13:48
|
Участник

Группа: Участник
Сообщений: 35
Регистрация: 22-12-05
Пользователь №: 12 556

|
Хочется написать прототип работы с железом так, чтоб потом по нему оформлять все остальные работы. В МК раньше работал только на чистом С, но недавно начал разбираться с 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 и его чипселект. Но тоже как-то мутно. Поделитесь своими идеями, как это лучше сделать.
|
|
|
|
|
Dec 18 2009, 05:50
|

Местный
  
Группа: Участник
Сообщений: 340
Регистрация: 25-10-05
Из: Пермь, Россия
Пользователь №: 10 091

|
Цитата(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); } И еще, заводить буфер в базовом классе, да еще давать к нему публичный доступ - плохая идея. Логичнее всю необходимую буферизацию делать в производных классах.
--------------------
Всего наилучшего, Alex Mogilnikov
|
|
|
|
|
Dec 18 2009, 10:30
|
Участник

Группа: Участник
Сообщений: 35
Регистрация: 22-12-05
Пользователь №: 12 556

|
Цитата(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 и все должно работать Но, сильно сомневаюсь, что этот вариант близок к оптимально удобному Если есть другие, поделитесь.
|
|
|
|
|
Dec 21 2009, 21:42
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Цитата(BAT @ Dec 18 2009, 13:30)  По удобству. Если дисплей вдруг перебросят на другой SPI и чипселект .... Честно говоря, жаль что тема подзаглохла. Меня тоже интересуют подходы разных программистов, в данном вопросе. А сам я хоть и нахожусь в подобном поиске, но выкладывать своё творчество неготов. По причине отсутствия опыта. Но некоторые свои соображения общего характера (чисто в плане обсуждения) хотелось бы высказать. Поскольку обобщать что-то вообще рано, то лучше по частному случаю (SPI). 1) Использование оборудования, даже одного и тогоже, в разных проектах, сильно отличается. Например работа с дисплеем и памятью требует объёмов и скоростей, а с каким-нибудь АЦП потребует дополнительных сигналов и синхронного управления (это я к примеру). Толи усложнять класс, толи делать несколько с разной функциональностью и ориентированностью? И в том и в другом случае, придётся городить какое-то описалово с уточнением деталей и подробной расшифровкой с уточнением "что для чего задумывалось". И в том и в другом случае возникает оверхед. Насколько он будет велик? 2) В проекте появился программный SPI. Или 2 на передачу с общим CLK. Или в частном случае, хотите обработать канал без прерывания (в совтовом это вообще сделать проблематично). Или в новом камне захотите задействовать DMA. Как это всё привести к общему знаменателю? А может поднятся на уровень чуть выше? К примеру был у меня проект, в котором информация хранилась в 24c512. Сейчас применили 45db021. Написал библиотеку для 45, чтобы работала как 24. Чтобы весь проект не лопатить. Может и с дисплеем что-то подобное сделать. Как бы сразу 2 уровня прописывать. Тогда уровень 0 (SPI) будет менее функциональный, но более оптимальный по результирующему коду. Я к этому не призываю, а размышляю просто. Возьмём знаменитую 7 уровневую модель OSI. Так когда реально драйвер пишут, обычно захватывают сразу 2-3 уровня.
|
|
|
|
|
Dec 22 2009, 07:52
|

читатель даташитов
   
Группа: Свой
Сообщений: 853
Регистрация: 5-11-06
Из: Днепропетровск
Пользователь №: 21 999

|
Цитата(SasaVitebsk @ Dec 21 2009, 23:42)  1) Использование оборудования, даже одного и тогоже, в разных проектах, сильно отличается. Например работа с дисплеем и памятью требует объёмов и скоростей, а с каким-нибудь АЦП потребует дополнительных сигналов и синхронного управления (это я к примеру). Толи усложнять класс, толи делать несколько с разной функциональностью и ориентированностью? И в том и в другом случае, придётся городить какое-то описалово с уточнением деталей и подробной расшифровкой с уточнением "что для чего задумывалось". И в том и в другом случае возникает оверхед. Насколько он будет велик? Я использую такой подход. В итоге базовый класс становится весьма сложным, хотя там всего лишь работа непосредственно с регистрами периферии. Наследники усложняются при повышении требований. Например, у меня один из наследников UART работает с RS-232, RS-485, J1708 плюс использует канал таймера для управления направлением. Оверхед немаленький, но специально не оценивал.
|
|
|
|
|
Dec 27 2009, 08:09
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
Цитата(BAT @ Dec 17 2009, 18:48)  Все работает. Но смотрю на это и понимаю, что коряво. Все должно быть гибче. Может кто может поделиться мыслями и пимерами, как это должно быть? Вот как у меня сделано:
spi_template.rar ( 2.54 килобайт )
Кол-во скачиваний: 122В конечном проекте пишу 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);
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Jul 13 2010, 07:12
|

Местный
  
Группа: Свой
Сообщений: 267
Регистрация: 11-11-04
Из: Одесса
Пользователь №: 1 103

|
Добрый день. По мотивам предыдущего поста написал работу с портом для АТМеги как с объектом (в приложении) Смущает очень сильно быстродействие того что я написал (с++ только начинаю изучать так что прошу совета как писать правильно). Инициализация объекта: Код portio pb (IOPORTB, IODDRB, IOPINB); // 130 тактов LOW, 129 MEDIUM и 25 HIGH оптимизация не зависит от вида по скорости или по объему. Перевод порта в Z состояние Код pb.portz(); // 65-LOW 64-MEDIUM 17-HIGH Можно ли писать более оптимально по скорости изначально? И как написать конструктор структуры что бы не было холостой инициализации на нулевой адрес?
|
|
|
|
|
Jul 13 2010, 18:39
|
Местный
  
Группа: Свой
Сообщений: 279
Регистрация: 2-07-08
Из: Новосибирск
Пользователь №: 38 699

|
Цитата(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_) {} }
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|