Ви не увійшли.
Вирішив теж спробувати цей picorvd. Довелось обробити напильником, щоб зібрати під сучасним Arch.
По-перше, в pico_sdk_import.cmake замінив PicoSDK на 2.3.0, бо з 1.5.1 виникала помилка через версію cmake. Ще і з нею розбиратись не хотілось, тому просто бампнув тег на останній, там версію cmake уже пофіксили.
По-друге, в example/build замінив префікс тулчейна на riscv64-elf-, та шлях до хедерів newlib на /usr/risc64-elf/include, де воно в мене і лежить.
Також поправив blink.cpp, щоби блимало PC4, а не PD0 та PD4. Бо LED у мене на PC4, а на PD4 SWIO.
Прошилось і навіть щось запрацювало.
А, бачу, воно вже з -O0 компілиться. Але ж агрумент все одно через регістр передається: https://godbolt.org/z/o5YYEjYEK
Тут трохи не додивився: аргумент-то передається в регістрі, але в самій функції потім кладеться в стек.
При blink зібраному з "-ggdb -Og" навіть значення аргумента показує правильно.
$ riscv32-elf-gdb ./bin/blink.elf
Reading symbols from ./bin/blink.elf...
(gdb) target extended-remote /dev/ttyACM0
Remote debugging using /dev/ttyACM0
0x0000019e in busywait (x=x@entry=300000) at blink.cpp:8
8 for (volatile int i = 0; i < x; i++) {
(gdb) p x
$1 = 300000
(gdb) info frame
Stack level 0, frame at 0x200007f0:
pc = 0xd28 in busywait (blink.cpp:8); saved pc = 0xdb4
called by frame at 0x200007fc
source language c++.
Arglist at 0x200007f0, args: x=x@entry=300000
Locals at 0x200007f0, Previous frame's sp is 0x200007f0
(gdb) bt
#0 0x00000d28 in busywait (x=x@entry=300000) at blink.cpp:8
#1 0x00000db4 in main () at blink.cpp:35При зупинці по брекпоінту перестає, "Breakpoint 1, busywait (x=<error reading variable: Cannot access memory at address 0xfffffff0>) at blink.cpp:8"
А от якщо при збірці blink прибрати garbage collection секцій (при цьому розмір blink зростає майже до 4 кб), то початкове вхідне значення показує правильно, а поточне відповідає значенню регістра, в якому передавався аргумент:
(gdb) break 8
Breakpoint 1 at 0xd24: file blink.cpp, line 8.
Note: automatically using hardware breakpoints for read-only addresses.
(gdb) continue
Continuing.
Breakpoint 1, busywait (x=32800, x@entry=300000) at blink.cpp:8
8 for (volatile int i = 0; i < x; i++) {
(gdb) info registers
ra 0xda4 0xda4 <main()+108>
sp 0x200007f0 0x200007f0
gp 0x20000800 0x20000800
tp 0x804e06a4 0x804e06a4
t0 0x1a0 416
t1 0x40014281 1073824385
t2 0x5b763111 1534472465
fp 0x0 0x0 <InterruptVectorDefault>
s1 0x40011000 1073811456
a0 0x8020 32800
a1 0x4002200c 1073881100
a2 0x8000d40 134221120
a3 0x50000 327680
a4 0x10040 65600
a5 0x90000 589824
pc 0xd2e 0xd2e <busywait(int)+12>"monitor reset" працює, покрокове виконання працює.
Через файл gdbinit
А, ну якщо там є target remote / extended-remote, і gdb запускаєте з '-x gdbinit', то має зʼєднуватись.
Не знаю, чи лежить він там у вас як ~/.config/gdb/gdbinit і ваш gdb-multiarch автоматично виконує його при старті, чи потрібно в командному рядку кожний раз передавати.
Походу його не навчили цьому
Код для ресету там є: src/RVDebug.cpp:95. А
"monitor" command not supported by this target.
- це про команду "monitor", яка не supported, поки target не визначений. Тому логічно припустити, що gdb просто нікуди не підключений в цей момент.
а що debug не працює - незручно
По факту він-то працює. На брекпоінті зупинився ж якось. Регістри та машинний код показує? Ну а змусити значення змінних показувати, коли вони у регістрах - це навіть на рідній хостовій системі не завжди вдається.
У нас є gdb-multiarch, є ttyACM0
Ну gdb-multiarch має ж якось взнати, що є ttyACM0, на якому його будуть слухати.
є магічний пристій, що слухає ttyACM0 і смикає CH32V003 за SDIO.
Магічний пристрій і є монітор, який реалізує протокол gdb-сервера. В "класичній" конфігурації цю роль виконує openocd, який в свою чергу вже спілкується з залізом по іншом протоколу, наприклад, по JTAG.
ніт, "monitor" command not supported by this target.
А ви gdb з монітором зʼєднали?
After that run "gdb-multiarch {your_binary.elf}" and type "target extended-remote /dev/ttyACM0" to connect to the debugger (replace ttyACM0 with whatever port your Pico shows up as).
$CC -g -O0
Так, уже побачив, відредагував.
Трохи дивує що заливається не .bin, а .elf.
Gdb бере elf на вхід. З нього ж таблицю символів читає для дебага, і дебажні символи, якщо є, і цільову архітектуру для multiarch.
Сирий бінарик прошити можна командою restore.
Десь під капотом повинна бути магія objcopy?
Та яка там магія. Заголовок, секції з атрибутами, таблиця символів. Avrdude ж теж elf шʼє.
Ага, трохи роз"ясняється. picorvd - заливає прошивку правильно, але в кінці не робить reset.
А "monitor reset" працює?
ardulink, походу, робить reset через живлення
WCH-LinkE теж вміє живленням смикати. Корисно коли нема NRST.
Note: automatically using hardware breakpoints for read-only addresses.
Дивно, в README picorvd пишуть же:
The CH32V003 chip does not support any hardware breakpoints.
0x000001e4 in busywait (x=<error reading variable: Cannot access memory at address 0xfffffff4>) at blink.cpp:4
Скоріш за все той аргумент x зник ще на етапі компіляції. Але чому gdb в неіснуючу памʼять лізе, замість того щоб сказати value optimized out - то вже х.з.
Взагалі, щоб більш-менш адекватний рантайм дебаг отримати, потрібно збирати з -Og, або хоча б -fno-omit-frame-pointer.
UPD: А, бачу, воно вже з -O0 компілиться. Але ж агрумент все одно через регістр передається: https://godbolt.org/z/o5YYEjYEK
blink наче заливає, але рівень на пінах не міняється.
На пінах цільового CH32? А якщо той же бінарик зашити заздалегідь робочим програматором?
Впевніться, що дивитесь на тому ж піні, яким цей blink дригає. На різних платах LED на різних пінах може бути, це ж не ардуіно.
Що хотів сказати автор issue.. Не та версія компілятора? gdb? А яка потрібна?
А х.з., у нього треба питати. Мабуть тулчейн мається на увазі, бо в xpack і компілятор, і gdb.
Теоретично, на момент написання того комента уже був gdb 13-ї версії, а може й 14-ї. А platformio досі ставить 12.1.
Жаль що до кінця ту штуку не доробили
Я б не покладав сподівань на проект, в якому приклад blink лінкується з libgcc.a, що лежить в цьому ж репозиторії.
Няп тут про bootloader, який переводиться в режим dfu підтяжкою одної ноги. Для 8-ногого контроллера 3 ноги віддати для програмування це по багатому
Ну 2 з них - UART, як і в ардуіні. Атмегу ж теж можна хоч по ISP, хоч одним проводом по debugWIRE прошивати, але не всі хотять додаткові провода, хотять бутлоадер і аплоад з IDE. В деяких випадках так зручніше.
Колись хотів детально розібратись, які там є доступні варіанти прошивки окрім SWIO, але руки не дійшли. З WCH-LinkE запрацювало, цього було достатньо. Але надибав такий опис: WCH CH32V003 Factory Bootloader - The Missing Manual.
Ну.. такоє.. якщо в мене є програматор, який по 3 проводах може залити bootloader.. і працювати як debugger.. навіщо заливати програму через uart і 4 проводи?
Мені здається, сенс приведених лінків у тому, що Ardulink-pio - не єдине можливе рішення працювати з CH32V003 при відсутності WCH-LinkE.
А з Ardulink-pio прошивщик хіба не пише у віртуальний UART, який по USB пише в USB-UART адаптер, який в свою чергу пише в атмегу, яка уже по 3х проводах взаємодіє з цільовим МК? Тут від ардуіно просто зручність, що USB-UART та контролер на одній платі.
Це якщо взяти МК, який реалізує USB девайс, то можна позбутись UART'а із ланцюжка. Але тоді прошивщик має вміти взаємодіяти з vendor-specific USB девайсом.
Тепер би ще придумати до чого приспособити той wch-link
Ну як мінімум як USB-UART адаптер має працювати
А можна заморочитись і спробувати перешити його додатково під інший протокол, зробити 1-Wire / debugWIRE адаптер, чи I²C, чи повільний напівдуплексний SPI. Дивлячить на якому МК і як він там зроблений. А AVR ISP, мабуть, без хірургічного втручання не вийде, бо 4 лінії потрібно.
У 8 ногих дрібноконтролерів свій шарм
Так, теж прошивається.
Наскільки розумію, кристал у всіх CH32V003 абсолютно однаковий, просто в різних корпусах.
"Зовсім не зрозуміло, як це логіка роботи залежить від виду конструкції розгалуження" - легко
А, просто else забули.
Ну якщо Shag приймає тільки два значення, 1 і 2, то можна без другого if:
if (Shag == 1) {
...
} else {
} ...Або просто в кінці гілки робити return;
я так розумію це асемблер
Ні, там ніякого асемблера нема, тільки C++. Навіть ніяких особливостей від ++ не використовується, як C код він теж валідний.
CH32V003F4P6
Бавився з ними трохи, а також з восьминогими CH32V003J4M6. Правда, не через ардуіно фреймворк, а з ch32v003fun.
Якби їм ще зовнішній Vref зробили, взагалі ціни би їм не було. І засмутила відсутність SPI у J4M6.
він же SWD, і, схоже, не можна його просто взяти і використати як GPIO.
Так, не можна. Мало того, якщо спробувати використати, можна залочити чіп у непрацездатний стан. І якщо F4P6 розлочити доволі просто, то щоб розлочити J4M6 без NRST піна довелось потанцювати з бубном.
Я купив wch-link, а треба wch-linkE
wch-linkE сам по собі на CH32V305 зроблений)
виявилось що наступний імпульс формується відразу після закінчення формування попередн"ого.
А де ви обчислюєте значення для OCR після скидання піна, щоб наступне переривання виникало через потрібний проміжок часу?
Це якщо з одним перериванням, як в Servo, і як ви зараз робите. А можна з двома.
Щоб фаза для кожного канала була постійна, зробіть тайм-слот для кожного канала. Для 4 каналів рівномірно - по 5 мс виходить. На початку тайм-слота виставляєте пін у високий рівень та програмуйте OCR, в кінці тайм-слота скидайте пін у низький рівень.
Початок тайм слота - по перериванню COMPA, значення OCR1A визначає тривалість всього тайм-слота - 5 мс. Кінець - по COMPB. Значення OCR1B визначає тривалість високого рівня.
Там іще є варіанти, але поки не буду ускладнювати.
Знов довелося перейти на switch case
Зовсім не зрозуміло, як це логіка роботи залежить від виду конструкції розгалуження. Щось там не так.
Хто може ЛЮДСКОЮ мовою пояснити як працює цей код
4 канала, для кожного канала пін потрібно змінювати 2 раза. Виходить 4*2 = 8 станів.
Serv на кожному виклику циклічно приймає значення від 0 до 7: 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7, 0, ...
Із цього значення обчислюєм номер канала, від 0 до 3:
uint8_t channel = idx / 2;І номер крока, від 0 до 1 (по факту - інвертований стан піна).
uint8_t state = idx % 2;Їхні значення виходять такі
Serv channel state
0 0 0
1 0 1
2 1 0
3 1 1
4 2 0
5 2 1
6 3 0
7 3 1
0 0 0
1 0 1
2 1 0
...Виставляєм пін, що відповідає поточному каналу, у відповідний стан:
digitalWrite(pins[channel], !state);Програмуєм OCR на наступну ітерацію:
OCR1A = ocr[channel][state];Тривалість високого рівня на каналі k визначається значенням ocr[k][0] (аналогічно як у вашому коді Serv_1_1, Serv_2_1 і т.д.), тривалість низького рівня - ocr[k][1] (у вашому коді Serv_1_2, Serv_2_2 і т.д.).
якщо я ще приблизно розумію як він працює, то самостійно змінити за потреби 100% не зможу!!!
Якщо що - питайте, поясню. Там лише C-шні масиви та арифметика.
Ваші окремі змінні Serv_1_1, Serv_1_2, Serv_2_1, Serv_1_2 і т.д. просто переїхали в масив. Тепер вони доступні як ocr[0][0], ocr[0][1], ocr[1][0], ocr[1][1] і т.д.
№47 - може окремий if і виконується швидше(я спочатку їх і використав), але switch case дозволив обійтись без goto...
Звідки там goto береться? У вас завжди лише одна гілка виконується, і нічого більше. Робіть return в кінці кожної гілки:
switch (Serv) {
case 1:
switch (Shag) {
case 1:
digitalWrite(2, HIGH);
OCR1A = Serv_1_1;,
Shag=2;
return;
case 2:
digitalWrite(2, LOW);
OCR1A = Serv_1_2;
Shag=1;
Serv=2;
return;
}
case 2:
...
case 3:
...
case 4:
...
}Але все одно, вважаю такий код - збочення.
"потрібно оновлювати з вимкеними перериваннями" - детальніше можна, у поточній редакції word Serv_1_1
word - це unsigned int, тобто 16 біт в AVR. 8-бітний процесор пише в 16-бітну змінну побайтово, двома інструкціями. Коли переривання виникає між цими двома інструкціями, змінна виявляється оновлена не повністю, і обробник переривання читає некоректне значення.
Щоб обробник гарантовано прочитав коректне значення, обробку переривань на момент запису потрібно вимкнути.
На рівні фреймворку:
noInterrupts();
Serv_1_1 = new_value;
interrupts();Або на рівні avr-libc:
cli();
Serv_1_1 = new_value;
sei();Або для універсального коду, який може виконуватись як в основному потоці, так і в контексті переривання:
uint8_t sreg = SREG;
cli();
Serv_1_1 = new_value;
SREG = sreg;Правильні макроси спрощують життя.
Та з макросами теж можна намудрити, якщо зловживати. Хіба так багато місць у програмі де конфігурується таймер, чи потрібно перемикатись між декількома конфігураціями в рантаймі?
Якщо хочеться високорівневих абстракцій, то краще вже робити засобами C++. Ну то таке, якщо нема вимог до стилистики коду, то справа особистих вподобань.
Всегда казалось, что сдвиг это команда LSL (LSR) с установкой флага переноса
Це вже деталі імплементації для конкретної архітектури. Я маю на увазі з точки зору стандарту мови програмування в загальному. Він нічого "не знає" про біти, флаги та інструкції.
За стандарт не скажу, честно не знаю. Но помню книжка писала, что если не указано явно unsigned, то по умолчанию переменная всегда signed.
Саме так. А при зсуві signed:
The result of E1 << E2 is E1 left-shifted E2 bit positions; ... If E1 has a signed type and nonnegative value, and E1 × 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.
Та блин, эти стандарты уже так разрослись и отличаются, что уже не уследишь. ))
Стандартів для мов програмування не так уже й багато. Але так, вендори заліза клепають свої фреймворки з велосипедами на костилях. Тут нічого не поробиш, се ля ві.
та дурня. UL это просто unsigned long, т.е. захватывает 4 байта. Если я напишу 1ULL это будет восемь байт, то шо, я еще круче?
Суть не в long, а в unsigned. Поведінка (1 << 15) при 16-бітному int не визначена стандартом. Те ж саме з (1 << 31) на платформах з 32-бітним int.
Не в либах дело. Например, для размещения данных во флеш, у gcc используется PROGMEM
В gcc є підтримка named address spaces (ISO/IEC DTR 18037), так що в C __flash працює (для читання генерується lpm). В arduino, яке базується на C++, такого не буде.
Это же касается и асм вставок, надо писать именно так, как хочет конкретный компилятор, и пофиг что у другого по другому, они никогда не будут совместимы в этом плане.
Бо inline assembler не є частиною стандарту, це розширення конкретного компілятора.
Посмотрел. Раскладывается в тот же #define bit(b) (1UL << (b)).
1UL. В avr-libc просто 1.
важливіше розуміти як воно працює, а не щоб було красиво.
Одне іншому не заважає, а навіть допомагає. "Красиво" - це не мета, а наслідок коректності, зрозумілості та ефективності коду.
Це с"огодні в мене такі налаштування таймеру такі, а якщо завтра захочу встановити інші?
Так усе у одному місці і з пам"яткою "що за що відповіда"....
Так в чому проблема залишити все там само і так само, тільки записувати значення в регістр один раз замість чотирьох?
Вище наведено приклади, як можна написати щоб було і зручно, і правильно.
Мене більше турбує обробка преривання, до ц"ого часу я обходився оператором if, і на комбінацію switch case прейшов щоб не пхати у код goto .
Так всі гілки однакові, відрізняються лише значення змінних. Навіщо їх дублювати?
Нумеруйте все з нуля, окрема змінна Shag стає непотрібною. Всі ваші if та case перетворюються на:
static const uint8_t pins[4] = { 2, 4, 7, 8 };
static volatile uint16_t ocr[4][2];
ISR ...
{
uint8_t idx = Serv;
Serv = (idx + 1) % 8;
uint8_t channel = idx / 2;
uint8_t state = idx % 2;
digitalWrite(pins[channel], !state);
OCR1A = ocr[channel][state];
}Тільки не забувайте, що змінні, які читаються в обробнику переривань та більші за 8 біт, з основної програми потрібно оновлювати з вимкеними перериваннями. Ви ж тільки шматок коду показали, тому я не знаю, який там у вас тип у Serv_1_1 та інших.
Правильно мабуть
TCCR1B |= (1 << CS10); TCCR1B &= ~(1 << CS11); TCCR1B |= (1 << CS12);
Не зовсім. Правильно, щоб значення в регістрі оновлювалось однією інструкцією. А для працюючого таймера правильніше спочатку зупинити його, сконфігурувати, потім запустити. Звісно, якщо потрібно залишити якісь біти незмінними, то прочитати поточне значення і застосувати маску.
Якщо ж виставляється чи скидається лише один біт в одному з нижніх 64 регістрів, то компілятор може заоптимізувати це в одну інструкцію sbi чи cbi.
Або макросів навернути
Або static inline функцію. Але це вже синтаксичний цукор.
Якщо розписати так ... то таки чесно виконує безсмислену операцію
Справа не в беззмістовній операції, а в тому, що якби було би, наприклад,
TCCR1B |= (1 << CS10);
TCCR1B |= (0 << CS11);
TCCR1B |= (1 << CS12);то по закінчені цього коду таймер би уже натікав більше ніж треба.
Для покращення читабельності можна ще, наприклад, так:
uint8_t tmp = (1 << WGM12);
tmp |= (1 << CS10);
tmp |= (0 << CS11);
tmp |= (1 << CS12);
TCCR1B |= tmp;