|
|
  |
Как ПРАВИЛЬНО программировать на С++, Вопросы по программированию на С++ для микроконтроллеров. |
|
|
|
Sep 27 2010, 14:48
|
Местный
  
Группа: Участник
Сообщений: 214
Регистрация: 22-03-10
Из: Саратов
Пользователь №: 56 123

|
Кстати, списки линий поддерживают и манипуляцию отделными битами из списка. Переводя на пример, который запросил Quasy это будет выглядеть так: Код #include <avr/io.h> #include "iopins.h" #include "pinlist.h"
using namespace IO;
typedef PinList<Pa0, Pb0, Pd3, Pd4> Pins;
int main() { Pins::Pin<0>::Clear(); Pins::Pin<1>::Set(); Pins::Pin<2>::Clear(); Pins::Pin<3>::Set(); } Асм листинг: Код cbi 0x1b, 0; 27 sbi 0x18, 0; 24 cbi 0x12, 3; 18 sbi 0x12, 4; 18 Quasy, вы это хотели увидеть?
|
|
|
|
|
Sep 27 2010, 15:58
|
Местный
  
Группа: Участник
Сообщений: 352
Регистрация: 10-08-06
Из: Санкт-Петербург
Пользователь №: 19 471

|
2 Andy Mozzhevilov Я говорил про то, что управление конкретными линиями GPIO должно быть только в одном потоке. Иначе вообще странная ситуация складывается - мы пытаемся двумя разными потоками управлять одним и тем же, при этом боимся где-то пересечься. Просто программист, который будет потом разбираться в коде, должен быть в курсе того, что есть такое пересечение потоков. Кстати, Вы сказали про "приходится настройку портов оборачивать в критические секции", но если это задается вначале программы, то зачем оборачивать в критическую секцию, когда прерывания от периферии еще не разрешены?
|
|
|
|
|
Sep 27 2010, 16:13
|
Местный
  
Группа: Свой
Сообщений: 279
Регистрация: 2-07-08
Из: Новосибирск
Пользователь №: 38 699

|
Цитата(Сега @ Sep 27 2010, 22:58)  2 Andy Mozzhevilov Я говорил про то, что управление конкретными линиями GPIO должно быть только в одном потоке. Иначе вообще странная ситуация складывается - мы пытаемся двумя разными потоками управлять одним и тем же, при этом боимся где-то пересечься. Согласен. ИМХО если есть какой-то разделяемый ресур ввода/вывода, то правильно будет ввести сущность "драйвер/менеджер" устройства, и все операции вести через него. Соответственно, вся забота о разделении доступа ложится на этот драйвер
|
|
|
|
|
Sep 28 2010, 04:42
|

Знающий
   
Группа: Свой
Сообщений: 877
Регистрация: 26-01-05
Из: Екатеринбург
Пользователь №: 2 206

|
Цитата(Сега @ Sep 27 2010, 19:58)  2 Andy Mozzhevilov Я говорил про то, что управление конкретными линиями GPIO должно быть только в одном потоке. Иначе вообще странная ситуация складывается - мы пытаемся двумя разными потоками управлять одним и тем же, при этом боимся где-то пересечься. Значит вы не поняли суть проблемы. Она в том, что для для модификации значения одного бита порта используется команда чтения значения всего порта (8 бит для avr), модификация нужного бита в регистре, и запись 8 бит обратно в порт. То есть модификация 1 бита фактически - это модификация всего порта. Следовательно, если на этом порту есть 2 разных линии, каждая из которых управляется из разных потоков, возможны трудноуловимые баги, обусловленный неатомарными операциями с 8-битным портом. В предыдущем сообщении я написал, как это решается в ARM NXP. Для avr предлагается использовать команды манипуляции отдельными битами (насколько я понял из их мненоники и описания). А то что вы писали - это управление одной линией из разных потоков (или прерываний и фона) - это другая тема. И такое тоже имеет место быть при необходимости - не надо кричать, что это бред. Например, в фоне выставляется запрос внешнему устройству на каком-то пине, ответ от устройства обрабатывается по прерыванию, в котором в том числе и запрос снимается. Только в этом случае автомат состояния делается общий для такого управления, и там где нужно, изменения состояния автомата оборачиваются в критические секции. Цитата Кстати, Вы сказали про "приходится настройку портов оборачивать в критические секции", но если это задается вначале программы, то зачем оборачивать в критическую секцию, когда прерывания от периферии еще не разрешены? Если это касается main - то не нужно. На я использую драйвера периферии на С++, где в конструкторе производится инициализация и настройка альтернативных режимов пинов. Все конструкторы закрыты в критические секции, поскольку объекты могут быть созданы не только глобально (конструкторы их будут вызваны до main), но и локально в функции (задаче). В этом случае конструктор будет вызван в месте создания, когда прерывания уже во всю имеют место быть.
--------------------
Пасу котов...
|
|
|
|
|
Sep 28 2010, 05:57
|
Местный
  
Группа: Свой
Сообщений: 460
Регистрация: 5-10-06
Из: Херсон
Пользователь №: 21 006

|
Цитата(neiver @ Sep 27 2010, 14:29)  xor.w TRISB // TRISB = 0x0022 --> один бит потеряли, должно быть 0x002A
Так, что и тут есть подводные камни. Но только в случае, если модифицируется пересекающееся множество бит. Если модифицируемые биты различны, то проблем нет. Дело не в подводных камнях просто пример совершенно не корректный, скажу более он в принципе не имеет решения. С нашим устройством связаны биты о маска 0х000F и они должны меняться из одного места. Меняйте любые другие и все будет работать. Если подойти с логической стороны дела, выставляем требуемые биты и момент прерывания меняем один или несколько управляемых битов. Завершаем прерывание и продолжаем выставлять биты. Но вопрос в другом какое из состояний верное то которое выставляли или то которое изменили в прерывании? Если верны оба то только критические секции и скорее всего они тоже не помогут. Макросы прекрасно работают в реальных проектах под TNKernel и позволяют отказаться во многих местах от использования критических секций.
|
|
|
|
|
Sep 28 2010, 06:03
|
Местный
  
Группа: Участник
Сообщений: 352
Регистрация: 10-08-06
Из: Санкт-Петербург
Пользователь №: 19 471

|
Цитата(Andy Mozzhevilov @ Sep 28 2010, 08:42)  Если это касается main - то не нужно. На я использую драйвера периферии на С++, где в конструкторе производится инициализация и настройка альтернативных режимов пинов. Все конструкторы закрыты в критические секции, поскольку объекты могут быть созданы не только глобально (конструкторы их будут вызваны до main), но и локально в функции (задаче). В этом случае конструктор будет вызван в месте создания, когда прерывания уже во всю имеют место быть. Хорошо, ну а какой смысл вообще оборачивать конструкторы в критические секции? Ну сработало прерывание (не касающееся этой периферии, так как инициализация еще не завершилась), отработали его, вернулись к инициализации и поползли дальше по конструктору, что должно сломаться? ЗЫ: я не спорю, мне просто интересно
|
|
|
|
|
Sep 28 2010, 06:51
|

Знающий
   
Группа: Свой
Сообщений: 877
Регистрация: 26-01-05
Из: Екатеринбург
Пользователь №: 2 206

|
Цитата(Сега @ Sep 28 2010, 10:03)  Хорошо, ну а какой смысл вообще оборачивать конструкторы в критические секции? Ну сработало прерывание (не касающееся этой периферии, так как инициализация еще не завершилась), отработали его, вернулись к инициализации и поползли дальше по конструктору, что должно сломаться? ЗЫ: я не спорю, мне просто интересно Так смысл абсолютно тот же, что и обсуждается сейчас здесь про неатомарные команды модификации битов порта. Поясню на примере с ARM NXP. Eсть регистр ввода-вывода (32 битный). В этом регистре настраиваются 16 линий порта на некоторые альтернативные функции, по 2 бита на пин. То есть, чтобы перенастроить некоторую линию (или группу линий) на другую функцию - нужно: 1. Считать значение IO регистра 2. Модифицировать значение нужным образом. 3. Записать новое значение в IO регистр. При этом в конкретном драйвере мне нужно (предположим) перенастроить только 2 IO линии из 16-ти, за которые отвечает этот регистр. Теперь - у меня RTOS с вытеснением (аналогичные рассуждения для прерываний). В другой задаче у меня так же есть код, который оперирует настройкой линий IO в пределах тех же 16-ти линий того же порта. Проследите, что будет, если где-то между 1. и 3. произойдет переключение контекста на другую задачу, которая решит также перенастроить линии порта (другие линии, но находящиеся в пределах этих 16-ти). 1a. Считать значение IO регистра --- контекст a->b 1б. Считать значение IO регистра 2б. Модифицировать значение нужным образом. 3б. Записать новое значение в IO регистр. --- контекст b->a 2a. Модифицировать значение нужным образом. 3a. Записать новое значение в IO регистр. В результате операции, выполненные в 1б, 2б, 3б - будут похерены. ЗЫ: Проблема в том, что подобные баги - трудноуловимы. Их проявление - стечение обстоятельств. То есть как правило - это нечто вроде: "У меня софт прекрасно работает месяц, но потом иногда затыкается обмен данными". Или: "У меня 1 раз из 100 устройство не включается, но после включения все 99 раз все дальше работает без сбоев".
--------------------
Пасу котов...
|
|
|
|
|
Sep 28 2010, 09:13
|
Участник

Группа: Участник
Сообщений: 17
Регистрация: 23-09-10
Пользователь №: 59 665

|
Цитата(neiver @ Sep 27 2010, 18:48)  Асм листинг: Код cbi 0x1b, 0; 27 sbi 0x18, 0; 24 cbi 0x12, 3; 18 sbi 0x12, 4; 18 Quasy, вы это хотели увидеть? Для данной конфигурации - да. Остался, кажется, один шаг - заставить Вашу библиотечку разбирать: когда выводить побитно, а когда организовать критическую секцию с "Чтением-Модификацией-Записью". Это, кстати, нужно только для эффективного вывода констант (для переменных вывод побитный, там групповой вывод не работает). Если интересно, то скажу, что в моем тупом варианте это делается весьма легко: в препроцессоре макрос считает для константы биты на каждом порту, и препроцессор же делает выбор. Цитата #if (GROUP_0_COUNT_BITS_OF_PORTA < 4) && (PORTA_HAVE_BITS_ACCESS) Вот тут побитно SBI/CBI для порта А #else ENTER_ATOMIC Ну, для XMEGA критическая секция не нужна RMW PORTA RELEASE_ATONIC #endif ... и так для всех портов. Что для этого потребовалось? Ну, где-то сказать, что ПортА на битовом пространстве, ПортG - нет. (для ХМЕГИ это означает - сказать на какой виртуальный порт отображен PORTn) И макрос соорудить для подсчета бит. Это совсем легко. Негатив такого варианта: для каждой Группы нужен h-файл с макросами для подсчета бит.. Они у меня, собаки, здоровенные получились, но каши не просят
|
|
|
|
|
Sep 28 2010, 09:41
|

Беспросветный оптимист
     
Группа: Свой
Сообщений: 4 640
Регистрация: 26-12-07
Из: Н.Новгород
Пользователь №: 33 646

|
Цитата(Quasy @ Sep 28 2010, 13:13)  Остался, кажется, один шаг - заставить Вашу библиотечку разбирать: когда выводить побитно, а когда организовать критическую секцию с "Чтением-Модификацией-Записью". Компилятор сам решит, он умный. Посчитает количество тактов либо количество занятых под программу байт - в зависимости от ключей оптимизации. А библиотека должна быть нейтральной. Кстати, насчёт пресловутой атомарности. В msp430 (моём любимом  ) с этим никаких проблем. Зря я волновался. У него чтение-модификация-запись производится в пределах одной команды, засчёт ортогональной системы команд и адресации. Вот пример, выцепил из рабочей прошивки. Код static __inline__ void SwPortToSend(void) { LINE_DIR.out.LINE_DIR_PIN = LINE_DIR_TRANSMIT; 7a70: f2 f0 bf ff and.b #-65, &0x001d;#0xffbf 7a74: 1d 00 На порт по адресу 001d накладывается маска 0xffbf, и всё это за одну команду, которая не может быть прервана никаким прерыванием.
--------------------
Программирование делится на системное и бессистемное. ©Моё :) — а для кого-то БГ — это Bill Gilbert =)
|
|
|
|
|
Sep 28 2010, 10:09
|
Участник

Группа: Участник
Сообщений: 17
Регистрация: 23-09-10
Пользователь №: 59 665

|
Цитата(MrYuran @ Sep 28 2010, 13:41)  Компилятор сам решит, он умный. Посчитает количество тактов либо количество занятых под программу байт - в зависимости от ключей оптимизации. А библиотека должна быть нейтральной. Как хорошо иметь компилятор, умеющий без лишнего кода различить, что PORTA |= 0x07 надо делать тремя SBI, а PORTA |= 0x0f с помощью RMW в критической секции.
|
|
|
|
|
Sep 28 2010, 17:15
|
Местный
  
Группа: Участник
Сообщений: 214
Регистрация: 22-03-10
Из: Саратов
Пользователь №: 56 123

|
Цитата(Quasy @ Sep 28 2010, 13:13)  Остался, кажется, один шаг - заставить Вашу библиотечку разбирать: когда выводить побитно, а когда организовать критическую секцию с "Чтением-Модификацией-Записью". Это, кстати, нужно только для эффективного вывода констант (для переменных вывод побитный, там групповой вывод не работает). Меня больше интересует именно групповой вывод для переменных. Есть у кого нибудь предложеня по дальнейшей оптимизации этой операции? Какие ещё приёмы отображения битов из входного значения в соответствующие биты портов можно придумать? Эффективные приемы для частных случаев? А так-же какие есть предложения по дальнейшему развитию функционала? - Отдельная функция для гарантированно атомарного вывода - Управление подтяжкой линий - Управление режимами прерываний
|
|
|
|
|
Dec 30 2010, 12:13
|
Местный
  
Группа: Участник
Сообщений: 214
Регистрация: 22-03-10
Из: Саратов
Пользователь №: 56 123

|
Доброго всем времени суток. В моей библиотеке произошло довольно много изменений: - поддержка МК Texas Instruments MSP430; - поддержка STM32; - поддерживаются компиляторы GCC, IAR, Keil; - новый подход к конфигурации портов; - убраны предупреждения компиляторов; - лучшая оптимизация; - исправлены ошибки; - автоматические тесты; - лиценизия изменена на BSD. Найти ее можно по старому адресу: https://github.com/KonstantinChizhov/AvrPro...e/master/mcucpp
|
|
|
|
|
Dec 30 2010, 15:12
|

Нечётный пользователь.
     
Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417

|
Цитата(neiver @ Dec 30 2010, 17:13)  - лиценизия изменена на BSD. Вот это, пожалуй, самое важное :-) В отличие от, к примеру, портирования на STM32, это самостоятельно сделать возможности никакой не было.
--------------------
Ну, я пошёл… Если что – звоните…
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|