STM32 новичку в ARM что к чему

Кто любит RISC в жизни, заходим, не стесняемся.
Ответить
Друг Кота
Сообщения: 6452
Зарегистрирован: Пт сен 13, 2013 13:11:31

Сообщение a5021 »

Жаль, что примера у вас не оказалось.
Реклама
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="VladislavS",url="/forum/viewtopic.php?p=3386097#p3386097"]Не стоит удивляться, что 98 из 100 эмбэдберов глядя на ваши "if constexpr" крутят у виска. Я 99-й :)[/uquote]
Как будто я заставляю кого-то использовать "if constexpr" :) Да и ничего в нем сложного, обычный if, просто выполняется на стадии компиляции, соответственно аргументы должны быть константами времени компиляции. Второе отличие в том, что ложные ветки отбрасываются при компиляции, а не оптимизатором, потому без оптимизации размер кода меньше и в некоторых случаях из такого поведения можно поиметь дополнительную выгоду. Например, есть у меня класс связанного списка, если у каждого нода будет по паре указателей, то это целых 8 байт, потому там используются индексы, которые в большинстве случаев могут быть однобайтными. Но если размер списка будет больше 254, то индексы должны уже быть uint16_t и хотя тип этих индексов можно передать после размера, это уже не очень удобно, особенно если список находится внутри других классов. Проблема разрешается довольно просто:

Код: Выделить всё

constexpr auto getEol()
{
    if constexpr(capacity > 254)
        return (uint16_t)0xFFFF;
    else
        return (uint8_t)0xFF;
}
Функция выводит возвращаемый тип самостоятельно, в зависимости от размера выбирается одна из веток и происходит возврат не просто разных значений(которые тоже используются), но и типы тоже будут разные. Если if будет обычный, то компилятор сначала подберет для обеих веток общий тип, потом оптимизатор выкинет вторую ветку и тип всегда будет одинаковым. Дальше можно у возвращаемого значения запросить тип или сначала создать псевдоним для типа:

Код: Выделить всё

using T = decltype(getEol()); // можно и в старом стиле, чем typedef
T prev, next;
Реклама
Друг Кота
Аватара пользователя
Сообщения: 3385
Зарегистрирован: Пн окт 11, 2010 19:00:08

Сообщение Мурик »

Reflector писал(а):обычный if, просто выполняется на стадии компиляции, соответственно аргументы должны быть константами времени компиляции. Второе отличие в том, что ложные ветки отбрасываются при компиляции, а не оптимизатором
С отладкой сложности не возникнет?
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

А откуда ей взяться? Если компилятор выкидывает код, то вы в него в рунтайме и не попали бы никак. Наоборот упрощает, так как, в отличии от #ifdef, код который выкидывается тоже компилируется и проверяется на корректность. Раздефайнишь кусок, а там ошибки. Тут же всё уже скомпилируется хотя бы.
Реклама
Эиком - электронные компоненты и радиодетали
Потрогал лапой паяльник
Аватара пользователя
Сообщения: 355
Зарегистрирован: Ср мар 09, 2016 08:07:41

Сообщение JackSmith »

[uquote="Reflector",url="/forum/viewtopic.php?p=3386145#p3386145"]

Код: Выделить всё

constexpr auto getEol()
{
    if constexpr(capacity > 254)
        return (uint16_t)0xFFFF;
    else
        return (uint8_t)0xFF;
}
[/uquote]
это же синтаксический сахар.
я не понимаю, с каких-то пор плюсы начали генерировать более компактный и быстрый код?
Реклама
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

А почему С++ код должен быть медленнее, если вы не злоупотребляете виртуальными методами и динамическим выделением памяти? Наоборот, более строгое типизирование и инкапсуляция позволяют лучше оптимизировать.
Больше чем уверен, что приведённый "синтаксический сахар" скомпилируется в одну ассемблерную команду загрузки константы в регистр. А если эта константа где-то ещё в константном выражении использоваться будет, то и этого может не произойти.
Реклама
Друг Кота
Сообщения: 6452
Зарегистрирован: Пт сен 13, 2013 13:11:31

Сообщение a5021 »

[uquote="JackSmith",url="/forum/viewtopic.php?p=3386253#p3386253"]это же синтаксический сахар.[/uquote]
Истинно так. Двое из ста искренне считают, что чем чаще его вспоминать, тем во рту будет слаще.

[uquote="VladislavS",url="/forum/viewtopic.php?p=3386271#p3386271"]Наоборот, более строгое типизирование и инкапсуляция позволяют лучше оптимизировать.[/uquote]
Подтверждения этому имеются?
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

[uquote="a5021",url="/forum/viewtopic.php?p=3386273#p3386273"]Истинно так. Двое из ста искренне считают, что чем чаще его вспоминать, тем во рту будет слаще.[/uquote]
Ну это же простой пример. Если накидать шаблонов с автовыведением типов, где это проявляется в полную силу, вы же взвоете.

[uquote="a5021",url="/forum/viewtopic.php?p=3386273#p3386273"]Подтверждения этому имеются?[/uquote]
Вам же Reflector дал пример

Код: Выделить всё

using T = decltype(getEol()); // можно и в старом стиле, чем typedef
T prev, next;
где с++ сделает два байта, а аналогичный по смыслу С-ный код четыре.
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="JackSmith",url="/forum/viewtopic.php?p=3386253#p3386253"]это же синтаксический сахар.
я не понимаю, с каких-то пор плюсы начали генерировать более компактный и быстрый код?[/uquote]
Синтаксический сахар - это когда предлагается более простой способ записи того, что можно было сделать и раньше, в данном случае это не так.
Эффективность могу продемонстрировать на примере реального кода из класса портов. Функция задает AF для всех установленных битов порта сразу, для этого она вычисляет четверные маски при помощи рекурсивной функций, хотя в С++14 можно все сделать и при помощи обычного цикла:
Спойлер

Код: Выделить всё

static void altFunc(uint32_t af)
{
    constexpr uint32_t qm1 = qmask(pinsMask);
    constexpr uint32_t qm2 = qmask(pinsMask >> 8);
    if constexpr (pinsMask & 0x00FF)  base()->AFR[0] = base()->AFR[0] & ~qm1 | (qmul(af) & qm1);
    if constexpr (pinsMask & 0xFF00)  base()->AFR[1] = base()->AFR[1] & ~qm2 | (qmul(af) & qm2);		
}

constexpr uint32_t qmask(uint32_t val, uint32_t outVal = 0, int cnt = 8)
{
   return cnt ? qmask(val >> 1, (val & 1) ? (outVal >> 4) | 0xF0000000 : outVal >> 4, cnt - 1) : outVal;
}

uint32_t qmul(uint32_t af)
{
    return af | af << 4 | af << 8 | af << 12 | af << 16 | af << 20 | af << 24 | af << 28;
}
Теперь пишем:

Код: Выделить всё

GpioA<0x0FF0> pa;
pa.altFunc(5);
На выходе получаем:
Спойлер

Код: Выделить всё

mov.w r2, #1207959552
ldr r3, [r2, #32] 
uxth r3, r3 
orr.w r3, r3, #1426063360
orr.w r3, r3, #5570560
str r3, [r2, #32] 
ldr r3, [r2, #36]
lsrs r3, r3, #16 
lsls r3, r3, #16 
orr.w r3, r3, #21760
orr.w r3, r3, #85
str r3, [r2, #36]
Как видно, компилятор все вызовы функций превратил в константы, как того требует модификатор constexpr. Убираем constexpr, компилируем снова:
Спойлер

Код: Выделить всё

movs r3, #8 
movs r1, #0 
mov.w r2, #4080
tst.w r2, #1 
mov.w r1, r1, lsr #4 
mov.w r0, r2, lsr #1 
it ne 
orrne.w r1, r1, #4026531840
subs r3, #1 
mov r2, r0 
bne.n 0x20002044 <main+824> 
movs r0, #8 
movs r2, #15 
tst.w r2, #1 
mov.w r3, r3, lsr #4 
mov.w r5, r2, lsr #1 
it ne 
orrne.w r3, r3, #4026531840
subs r0, #1 
mov r2, r5 
bne.n 0x20002060 <main+852> 
mov.w r0, #1207959552
ldr r5, [r0, #32] 
eor.w r2, r5, #1431655765
ands r2, r1 
eors r2, r5 
str r2, [r0, #32] 
ldr r1, [r0, #36]	; 0x24 
eor.w r2, r1, #1431655765
ands r3, r2 
eors r3, r1 
str r3, [r0, #36]	; 0x24
Теперь все функции оказались в коде, такое же поведение будет и в С.

Добавлено after 18 minutes 4 seconds:
[uquote="VladislavS",url="/forum/viewtopic.php?p=3386286#p3386286"]где с++ сделает два байта, а аналогичный по смыслу С-ный код четыре.[/uquote]
Там не в этом дело, можно и в Си 2 байта, но С++ может сам выбрать минимальное количество байт для заданного размера списка. А класс дека если видит, что размер у нас равен степени двойки, то выбирает одну реализацию всех методов, а если нет, то выбирает другую еще и увеличивает размер массива на 1. В обоих случаях программист указывает только размер. Если рассматривать в целом, то можно сказать, что это синтаксический сахар для контейнеров :)
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

[uquote="Reflector",url="/forum/viewtopic.php?p=3386300#p3386300"]Там не в этом дело, можно и в Си 2 байта,[/uquote]
Можно, но 99% С-программистов видя, что размерность может быть больше 256, напишут uint16_t и на этом остановятся.

[uquote="Reflector",url="/forum/viewtopic.php?p=3386300#p3386300"]

Код: Выделить всё

GpioA<0x0FF0> pa;
pa.altFunc(5);
[/uquote]Аааа!!! Ты сделал мой вечер! :)
Я то по простоте как-то так обхожусь

Код: Выделить всё

  GPIOA->AFRL = 0x55550000;             // Номер альтернативной фуекции для PA0-PA7
  GPIOA->AFRH = 0x00005555;              // Номер альтернативной фуекции для PA8-PA15
Понятно, что это не то же самое, ну ладно пусть будет так же

Код: Выделить всё

GPIOA->AFRL = (GPIOA->AFRL & 0x0000FFFF) |  0x55550000;
GPIOA->AFRH = (GPIOA->AFRH & 0xFFFF0000) |  0x00005555;
И мне кажется было бы нагляднее

Код: Выделить всё

GpioA<0b0000'1111'1111'0000> pa;
А ещё я могу вот так :)

Код: Выделить всё

  GPIOA->AFRL = 0x87654321;             // Номер альтернативной фуекции для PA0-PA7
  GPIOA->AFRH = 0x0FEDCBA9;              // Номер альтернативной фуекции для PA8-PA15
Друг Кота
Сообщения: 6452
Зарегистрирован: Пт сен 13, 2013 13:11:31

Сообщение a5021 »

А вот так можно записать, если чуть-чуть подумать:

Код: Выделить всё

#define PIN(PIN_NO)             (PIN_NO)
#define AF(PIN_NO)              (PIN_NO)
#define PIN_AF(PIN, AF)         ((uint32_t)AF << (4 * (PIN & 7)))

GPIOA->AFR[0] = (     // ---- Lower part of AFR
  PIN_AF(PIN(2), AF(1))   |  // PA2:  AF1 USART1_TX
  PIN_AF(PIN(3), AF(1))      // PA3:  AF1 USART1_RX
);

GPIOA->AFR[1] = (     // ---- Higher part of AFR
  PIN_AF(PIN(9),  AF(4))  |  // PA9:  AF4 I2C_SCL
  PIN_AF(PIN(10), AF(4))     // PA10: AF4 I2C_SDA
);
Просто, наглядно и без всякой этой вашей чепухи. Два присваивания и все.
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="VladislavS",url="/forum/viewtopic.php?p=3386339#p3386339"]Понятно, что это не то же самое, ну ладно пусть будет так же

Код: Выделить всё

GPIOA->AFRL = (GPIOA->AFRL & 0x0000FFFF) |  0x55550000;
GPIOA->AFRH = (GPIOA->AFRH & 0xFFFF0000) |  0x00005555;
[/uquote]
Если смотреть на выхлоп компилятора, то для этих двух строк он будет в точности как для моей функции. Естественно можно дополнительно наделать и лайт версии функций, которые будут надеяться на нулевое значение полей регистров и не будут выполнять предварительную очистку, но я таким не страдаю, т.к. при вызове функций выполняющих полную инициализацию никаких дополнительных усилий от меня не требуется, в отличии от человека пишущего код на регистрах. Но пока это одна функция, представим ситуацию когда нам требуется сделать библиотеку для работы с экраном сидящим, для простоты, на 8-ми битной шине, а FSMC у мк нет. Этот класс не знает на каких пинах какого порта сидит экран, он просто принимает тип шаблонного класса порта: GpioA<0x0FF0>. Затем он вызывает для него начальную инициализацию пинов, пишет в порт, читает из порта, переключает нужные пины на вход или выход. Если к начальной инициализации особых требований по скорости не предъявляется, то переключение направления желательно делать быстро и для этого есть специальные функции которые меняю только MODER. В этом регистре каждый режим задается двумя битами, чтобы изменить режим для всех пинов заданных маской, нужно посчитать двойную маску и опять она будет константой только в современном С++. При записи в порт работа идет с BSRR, чтобы не портить значения других пинов, но мы передали маску 0x0FF0, нельзя просто писать в порт, нужно байт предварительно сдвинуть, для чего есть специальная функция:

Код: Выделить всё

void writeAligned(uint16_t data)
{
    constexpr uint32_t clzr_ = clzr(pinsMask);
    base()->BSRR = (pinsMask << 16) | (data << clzr_);
}
Она сканирует в маске биты пока не найдет первую 1, результатом ее работы для данного случая будет константа 4(как все уже догадались, только в современном С++). При чтении естественно выполнится сдвиг в другую сторону. Более того, вместо класса порта можно подставить что угодно у чего можно вызвать нужные функции. Однажды мне было нужно запустить индикатор с 16-ти битной шиной подключенный к FSMC через ногодрыг, в таком случае можно передать вместо GpioX класс типа такого:
Спойлер

Код: Выделить всё

class LcdData
{
public:
	using PD = GpioD<0xC703>;
	using PE = GpioE<0xFF80>;

	static void writeAligned(uint16_t data) 
	{
		uint32_t pd = ((data & 3) << 14) | ((data >> 2) & 3) | ((data >> 5) & 0x0700);
		uint32_t pe = (data << 3) & 0xFF80;
		PD::write(pd);
		PE::write(pe);
	}
.......
};
И он замечательно работает, в самом классе экрана ничего менять не нужно. Теперь если посчитать сколько раз вместо простого вызова функции пришлось бы написать по несколько строк кода на регистрах, который будет работать только с конкретным портом и пинами, причем с той же эффективностью, то разница между подходами более чем очевидна. В итоге у меня один большой базовый класс для экранов в целом и по одному мелкому для каждой конкретной модели, которые передаются в первый класс как один из параметров шаблона. Каждый экран может быть 8/16 бит, работать через FMC/FSMC/ногодрыг, можно задать все ноги и все тайминги, все это делается одной строкой для каждого экрана, т.е. двумя строками я могу создать два класса которые могут работать параллельно, если их ноги не пересекаются. Наследование там есть, но т.к. это все шаблоны, то его как бы и нет, как и виртуальных функций, а потребляемое классом ОЗУ - 0 байт. Этот класс один на все разновидности мк, т.к. разница между ними скрыта в боле мелких классах портов и т.д., которые уже могут быть для каждой серии свои. Если кому-то кажется, что это обычный синтаксический сахар, то я буду настаивать на том, что как минимум это огромный синтаксический сахарище :)
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

Это как-то по кубовски. Если в пределе на одном порту 16 разных девайсов по одному пину, то порт будет 16 раз инициализирован. Стоит отделять мух от котлет. Вы когда нарисовали схему, и тем белее спаяли её, то фактически уже определили в каком режиме будет работать каждая ножка. Чаще всего инициализацию надо сделать один раз и залочить конфигурацию. Я это вот так делаю.

Код: Выделить всё

  // PA0=CKM_ADC, PA1=CKT, PA2=I1, PA9=VBUS, PA11=USBDM, PA12=USBDP, PA13=SWDIO, PA14=SWCLK
  GPIOA->AFRL = 0x00000000;                       // Номер альтернативной функции для PA0-PA7
  GPIOA->AFRH = 0x000AA000;                       // Номер альтернативной функции для PA8-PA15
  GPIOA->MODER  =    PORT_DIR(0222200000000333);  // 0 - Input, 1 - Output, 2 - Altarnate Function, 3 - Analog
  GPIOA->OTYPER =  PORT_OTYPE(0000000000000000);  // 0 - Output push-pull, 1 - Output open-drain
  GPIOA->PUPDR  =   PORT_PULL(0210000000000000);  // 0 - No pull-up, pull-down, 1 - Pull-up, 2 - Pull-down, 3 - Reserved
  GPIOA->ODR =       PORT_ODR(0000000000000000);  // Установить выход в 0/1
  GPIOA->OSPEEDR = PORT_SPEED(0002200000000000);  // 0 - 8 МГц, 1 - 50 МГц, 2 - 100 МГц, 3 - 180 МГц
  GPIO_PortLock(GPIOA,PORT_LOCK(1111111111111111));
Всё в одном месте, ни одна ножка не забыта, ни одна по ошибке на другую не наехала. И дым не пошёл из-за того что класс для этой ноги ещё не написан и она болтается неинициализированная. Если в процессе работы надо переконфигурировать порт, например для энергосбережения, то опять же лучше со всем портом сразу работать, чтобы сделать это быстро и ни одной ноги не забыть.

То есть, взяли в руки схему, инициализировали по ней все порты, закрыли хардварную секцию и переходите к собственно программированию. А там уже передавайте в свои индикаторы, клавиатуры, скрипперы хоть каждый пин отдельно. Шаблонируйте, инкапсулируйте, наследуйте и полиморфируйте - я только за. Но только после полной инициализации железа. Она ведь на разных камнях по разному делается, незачем вашим классам об этом знать. Да и дымок иногда можно пустить, если безответственно подойти к инициализации железа.

PS: Заглянул в стартап, который VisualGDB предлагает.

Код: Выделить всё

	SystemInit();
	__libc_init_array();
	(void)main();
	for (;;) ;
Как бы ясный намёк, что сначала инициализируй железо, потом С/C++ окружение, а потом программируй. В IAR всё тоже самое - сначала __low_level_init(), при нахождении в котором можно рассчитывать только на инициализированный стек, а потом уже С/С++ окружение, конструкторы и наконец main().
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="a5021",url="/forum/viewtopic.php?p=3386390#p3386390"]Просто, наглядно и без всякой этой вашей чепухи. Два присваивания и все.[/uquote]
Тут выполняется запись в AFR целиком, а моя функция писала только в нужные поля, так что аналог должен еще выполнять очистку по двум маскам. В любом случае если взглянуть на мой код который отправляет и принимает данные через USART при помощи DMA, то размер сопоставим с твоим кодом выполняющим простую установку AF для порта и если код USARTа переписать так просто, наглядно и без всякой чуши, то размера он будет не один экран.

[uquote="VladislavS",url="/forum/viewtopic.php?p=3386485#p3386485"]Это как-то по кубовски. Если в пределе на одном порту 16 разных девайсов по одному пину, то порт будет 16 раз инициализирован.[/uquote]
В целом да, но не обязательно. Допустим есть три светодиода, PinA<1> led1; и т.д., режим работы у них одинаков, потому вызываем функцию которой передается сразу 3 светодиода, она проверяет находятся ли они на одном порту, если да, то объединяет маски и выполняет одну запись, если нет, то возможно будет 2 записи, а 3 записи получим только если все светодиоды сидят на разных портах.
Стоит отделять мух от котлет. Вы когда нарисовали схему, и тем белее спаяли её, то фактически уже определили в каком режиме будет работать каждая ножка. Чаще всего инициализацию надо сделать один раз и залочить конфигурацию. Я это вот так делаю.
Всё в одном месте, ни одна ножка не забыта, ни одна по ошибке на другую не наехала. И дым не пошёл из-за того что класс для этой ноги ещё не написан и она болтается неинициализированная. Если в процессе работы надо переконфигурировать порт, например для энергосбережения, то опять же лучше со всем портом сразу работать, чтобы сделать это быстро и ни одной ноги не забыть.
Вернемся к USART, там была такая строка инициализации:

Код: Выделить всё

usart::init<PinA<2, 7>, PinA<3, 7>>(115200);
Передается два пина, для F1 нужны одни режимы их работы, для остальных серий - другие. Я даже не должен помнить какие это режимы, это зона ответственности самого класса усарта. Если переданы правильные пины, то гарантированно получим правильную их конфигурацию, а чтобы сменить пины, нужно в одном месте изменить несколько символов. Если нужен lock, то можно добавить еще один параметр и сделать его false по умолчанию или можно лочить порт целиком, как у тебя, но делать это на последнем этапе разработки. Ты же предлагаешь закопать эти два пина, как и множество других, среди 7-ти регистров портов, а самих портов тоже может быть 7 шт., как у меня на F429ZI. Более того, ты почему-то решил, что именно при таком подходе труднее получить дымок, но мне кажется, что все с точностью до наоборот :) Я предлагаю простоту, модульность и довольно высокую независимость от разной периферии, понятно, что выполнив всю инициализацию портов в начале программы можно сэкономить на размере прошивки, но мы экономили на PIC/AVR, где флеша могло быть 1-2КБ, теперь экономим на ARM, где флеша минимум 32КБ, а писать простые и переносимые программы когда будем? Подозреваю, что если появятся дешевые мк на ARM64 с минимум 512КБ флеша, все равно многие будут писать как на древних 8-ми битках :)
Собутыльник Кота
Аватара пользователя
Сообщения: 2567
Зарегистрирован: Вт май 01, 2018 19:44:47

Сообщение VladislavS »

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]В целом да, но не обязательно.[/uquote]Я же и написал "в пределе".

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]Ты же предлагаешь закопать эти два пина, как и множество других, среди 7-ти регистров портов, а самих портов тоже может быть 7 шт., как у меня на F429ZI.[/uquote]Не закопать, а наоборот откопать. У тебя в чипе 114 ног I/O. Если их все в разных классах по 2-3 штуки инициализировать... По всему коду разбросана инициализация будет, как это проверить всё? Я же копирую приведённый код 7 раз подряд, беру схему и по ней все ножки подряд прохожу. И тут же лочу.

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]Более того, ты почему-то решил, что именно при таком подходе труднее получить дымок, но мне кажется, что все с точностью до наоборот :)[/uquote]Потому что Я задаю последовательность инициализации в одном месте и один раз, а не "как классы лягут".

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]Я предлагаю простоту, модульность и довольно высокую независимость от разной периферии[/uquote]Ну так и пусть это касается более высокого уровня - протоколы, интерфейсы, алгоритмы. Не зря же придуманы драйверы, за которыми от прикладного ПО спрятана работа с железом.

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]понятно, что выполнив всю инициализацию портов в начале программы можно сэкономить на размере прошивки, но мы экономили на PIC/AVR, где флеша могло быть 1-2КБ, теперь экономим на ARM, где флеша минимум 32КБ, а писать простые и переносимые программы когда будем? Подозреваю, что если появятся дешевые мк на ARM64 с минимум 512КБ флеша, все равно многие будут писать как на древних 8-ми битках :)[/uquote]
Дело не в экономии, хотя и это важно, а в разделении задач на уровни. Инициализировали железо, создали среду для выполнения программ и программируйте себе на здоровье.

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]Вернемся к USART, там была такая строка инициализации:

Код: Выделить всё

usart::init<PinA<2, 7>, PinA<3, 7>>(115200);
[/uquote]Вот зачем классу USART знать на каких пинах он висит? Достаточно номера. А пины я могу заремапить как хочу в стартапе и он даже знать об этом ничего не будет.
Друг Кота
Сообщения: 6452
Зарегистрирован: Пт сен 13, 2013 13:11:31

Сообщение a5021 »

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]Тут выполняется запись в AFR целиком, а моя функция писала только в нужные поля,[/uquote]
А в чем счастье записи "в отдельные поля" ? Даже если мне придется переопределять значения в AFR, что весьма необычный кейс, я туда еще раз запишу целиком регистр. Какие проблемы?
В любом случае если взглянуть на мой код который отправляет и принимает данные через USART при помощи DMA, то размер сопоставим с твоим кодом выполняющим простую установку AF
У вас бывает такое, что вы устаете чушь пороть?

Код: Выделить всё

      9            GPIOA->AFR[0] = (     // ---- Lower part of AFR
     10              PIN_AF(PIN(2), AF(1))   |  // PA2:  AF1 USART1_TX
     11              PIN_AF(PIN(3), AF(1))      // PA3:  AF1 USART1_RX
     12            );
   \                     main: (+1)
   \   00000000   0x4803             LDR      R0,??main_0      ;; 0x48000020
   \   00000002   0x2188             MOVS     R1,#+136
   \   00000004   0x0149             LSLS     R1,R1,#+5        ;; #+4352
   \   00000006   0x6001             STR      R1,[R0, #+0]
     13            
     14            GPIOA->AFR[1] = (     // ---- Higher part of AFR
     15              PIN_AF(PIN(9),  AF(4))  |  // PA9:  AF4 I2C_SCL
     16              PIN_AF(PIN(10), AF(4))     // PA10: AF4 I2C_SDA
     17            );
   \   00000008   0x0889             LSRS     R1,R1,#+2
   \   0000000A   0x6041             STR      R1,[R0, #+4]
Итого, ровно шесть команд. Ничего не хотите пояснить, насчет "размер сопоставим" ?
для порта и если код USARTа переписать так просто, наглядно и без всякой чуши, то размера он будет не один экран.
Похоже, что вы не умеете пользоваться препроцессором, отчего имеете какие-то дикие представления.
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="VladislavS",url="/forum/viewtopic.php?p=3386700#p3386700"]Не закопать, а наоборот откопать. У тебя в чипе 114 ног I/O. Если их все в разных классах по 2-3 штуки инициализировать... По всему коду разбросана инициализация будет, как это проверить всё? Я же копирую приведённый код 7 раз подряд, беру схему и по ней все ножки подряд прохожу. И тут же лочу.[/uquote]
Основная инициализация в любом случае вся идет в начале, разница в том, что у меня она компактная и вся в одном месте, при этом значительно меньше вероятность что-то настроить не так. В одной строке идет инициализация USART вместе с инициализацией пинов, другая функция может инициализировать какой-то датчик висящий на USART, опять же туда передаются нужные типы, которые проинитит вложенный класс усарта. Затем идет инит экрана туда тоже передаются пины в разном количестве, в зависимости от того нужно ли задействовать FMC. Если нужно, класс экрана сам его инит вместо со всем остальным. Выглядит это как-то так:
Спойлер

Код: Выделить всё

using lcd = LcdFmc<SSD1298, 240, 320, 16, LcdOrient::Landscape, FmcBank1, PinG<8>, 9, 9, 1, 16, 1, 1>;

using spi4 = Spi4<PinE<2, 5>, PinE<5, 5>, PinE<6, 5>, PinE<4>>;
Sdcard<spi4, PinE<3>> sdcard;

Usart1Buf<128, 256> esp8266;

int main()
{
....
    esp8266.init<PinE<1, 8>, PinE<0, 8>>(115200);
    sdcard.init(SpiBaudRate::Presc_256);
    lcd::init();
}
Теперь если скопировать всю папку с файлами отвечающими за работу с экраном, а файлов там десяток, не считая шрифтов, с проекта для F429 в проект на F103С8 и заменить первую строку на следующую:

Код: Выделить всё

using lcd = LcdSoft<ST7735, 128, 160, 8, LcdOrient::PortraitRev, PinA<6>, GpioB<0xFF>, PinA<2>, PinA<9>, PinA<14>, PinA<3>, 50, 14, 1>;
плюс поменять SPI на существующий у данного мк, то код продолжит работать, хотя у нас другой драйвер экрана, другие размеры экрана, другая ширина шины экрана, другие порты, другие регистры отвечающие за настройку портов, другие тайминги и ногодрыг вместо FMC. При желании можно все подробно расписать и дать каждому пину осмысленное имя...
А у тебя, видимо, будет инициализация USART, естественно более громоздкая, и где-то там будет комментарий, что используются такие-то пины с такими-то AF. Пины эти могут быть на разных портах, потому нужно прогуляться в другое место, или места, где выполняется инит портов для всей периферии. Не знаю будут ли там комментарии касательно периферии, т.к. место они могут занимать весьма приличное, но чтобы прописать туда новые значения или проверить их на правильность, придется для каждого из 3-6 регистров сначала найти правильную позицию среди 16 символов и записать туда правильное число. Промахнулся на одну позицию и возможно затер настройки другой периферии, или поменял настройки портов, а комментарий в месте инициализации самой периферии не обновил... Устойчивость такого кода к ошибках явно не очень высокая.

[uquote="Reflector",url="/forum/viewtopic.php?p=3386558#p3386558"]Ну так и пусть это касается более высокого уровня - протоколы, интерфейсы, алгоритмы. Не зря же придуманы драйверы, за которыми от прикладного ПО спрятана работа с железом.[/uquote]
Если я могу однотипно проинитить порт и это будет работать на мк где работа этих портов отличается, то можешь воспринимать такой класс как драйвер портов. И он достаточно высокоуровневый, потому как программист имеет дело с объектами типа PinB<1>, а не с набором бит в разных регистрах.
Друг Кота
Сообщения: 6452
Зарегистрирован: Пт сен 13, 2013 13:11:31

Сообщение a5021 »

[uquote="VladislavS",url="/forum/viewtopic.php?p=3386485#p3386485"]

Код: Выделить всё

  // PA0=CKM_ADC, PA1=CKT, PA2=I1, PA9=VBUS, PA11=USBDM, PA12=USBDP, PA13=SWDIO, PA14=SWCLK
  GPIOA->AFRL = 0x00000000;                       // Номер альтернативной функции для PA0-PA7
  GPIOA->AFRH = 0x000AA000;                       // Номер альтернативной функции для PA8-PA15
  GPIOA->MODER  =    PORT_DIR(0222200000000333);  // 0 - Input, 1 - Output, 2 - Altarnate Function, 3 - Analog
  GPIOA->OTYPER =  PORT_OTYPE(0000000000000000);  // 0 - Output push-pull, 1 - Output open-drain
  GPIOA->PUPDR  =   PORT_PULL(0210000000000000);  // 0 - No pull-up, pull-down, 1 - Pull-up, 2 - Pull-down, 3 - Reserved
  GPIOA->ODR =       PORT_ODR(0000000000000000);  // Установить выход в 0/1
  GPIOA->OSPEEDR = PORT_SPEED(0002200000000000);  // 0 - 8 МГц, 1 - 50 МГц, 2 - 100 МГц, 3 - 180 МГц
  GPIO_PortLock(GPIOA,PORT_LOCK(1111111111111111));
[/uquote]

Это нечитабельно. Как наглядно инициализировать AFR я уже показывал. Теперь MODER:

Код: Выделить всё

#define ANALOG_MODE_FOR_ALL_PINS 0xFFFFFFFF
#define PIN_CONF(PIN, MODE)     ((uint32_t)MODE << (PIN * 2))

#define PINV_INPUT              3
#define PINV_OUTPUT             2
#define PINV_ALT_FUNC           1
#define PINV_ANALOG             0

GPIOA->MODER = ANALOG_MODE_FOR_ALL_PINS - ( // Configure GPIOA

#ifndef SWD_DISABLED      
  PIN_CONF(PIN(13), PINV_ALT_FUNC)  |       // PA13  AF0 -- SYS_SWDIO
  PIN_CONF(PIN(14), PINV_ALT_FUNC)  |       // PA14  AF0 -- SYS_SWDCLK
#endif

  PIN_CONF(PIN(10), PINV_ALT_FUNC)  |       // PA10: AF4 -- I2C_SDA
  PIN_CONF(PIN(9),  PINV_ALT_FUNC)  |       // PA9:  AF4 -- I2C_SCL

  PIN_CONF(PIN(3),  PINV_ALT_FUNC)  |       // PA3:  AF1 -- USART1_RX
  PIN_CONF(PIN(2),  PINV_ALT_FUNC)  |       // PA2:  AF1 -- USART1_TX

  PIN_CONF(PIN(1),  PINV_INPUT)     |       // PA1:  IN  -- TRG_A
  PIN_CONF(PIN(0),  PINV_OUTPUT)            // PA0:  OUT -- DC-DC ENABLE
);  
Остальные регистры, если требуется, можно привести к похожей записи.
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Сообщение Reflector »

[uquote="a5021",url="/forum/viewtopic.php?p=3386855#p3386855"]А в чем счастье записи "в отдельные поля" ? Даже если мне придется переопределять значения в AFR, что весьма необычный кейс, я туда еще раз запишу целиком регистр. Какие проблемы?[/uquote]
Еще раз, твой код решает более простую задачу, чем мой, при это размер исходника сопоставим, особенно после его переделки до полного эквивалента, по размеру с моим примером для USART + DMA. А пример для USART переписанный в таком размашистом стиле будет наверно занимать 2-3 экрана кода и это еще далеко не предел.
Друг Кота
Сообщения: 6452
Зарегистрирован: Пт сен 13, 2013 13:11:31

Сообщение a5021 »

[uquote="Reflector",url="/forum/viewtopic.php?p=3386869#p3386869"]Еще раз, твой код решает более простую задачу,[/uquote]
Какую еще "более простую"? Задача одна и таже -- инициализации GPIO. Нравится вам раком за виски сто сорок раз по одном биту переворачивать -- ваше право. Только не надо при этом плести, что вы какой-то новый смысл тем самым обретаете. Чепуха это.
чем мой, при это размер исходника сопоставим, особенно после его переделки до полного эквивалента, по размеру с моим примером для USART + DMA.
Вы чего с чем сравниваете? То, что вы выдаете за "USART + DMA" -- просто некий набросок неизвестного функционала. У меня же полностью законченная задача. Если мой код вставить в любое место программы, он произведет ровно те действия, которые содержит. Ваше "USART + DMA" -- обрывок неизвестно чего. Без полного текста он никаких действий произвести не может. Научитесь сравнивать то, что возможно сравнивать.
А пример для USART переписанный в таком размашистом стиле будет наверно занимать 2-3 экрана кода и это еще далеко не предел.
Мне не интересно рассуждать о сферических юсартах. Еще постановки задачи толком никто не видел, а у вас уже готовый разбор результатов. Не забегаете ?
Ответить

Вернуться в «ARM»