1656 lines
81 KiB
Markdown
1656 lines
81 KiB
Markdown
[Skip to content](file:///home/feda/MIPT/RadioPhotonic_Subserface_radar/RadioPhotoinc_genereator_obsidian/Clippings/big_array_in_SDRAM_BlackFin.html#main)
|
||
|
||
## You said:
|
||
|
||
x502_low_level.pdf
|
||
|
||
PDF
|
||
|
||
Прикреплена инструкция к модулю АПЦ с DSP BlackFin. Я работаю над обработкой данных в blackfin. Как создать большой массив, размещенный в sdram? При простом объявлении uint32_t arr[100000] возникает ошибка: region `MEM_L1_DATA_A' overflowed by 382848 bytes`
|
||
|
||
## ChatGPT said:
|
||
|
||
У вас ошибка потому, что массив по умолчанию попадает во **внутреннюю память L1 (MEM_L1_DATA_A)**, а она всего 32 КБ, тогда как вы пытаетесь разместить ~400 КБ (`100000 * 4 байта`).
|
||
Для больших буферов нужно использовать **внешнюю SDRAM** и указать компилятору, что эта переменная должна попасть в специальную секцию, описанную в линкер‑скрипте.
|
||
|
||
В инструкции к модулю X502/BlackFin это сделано так
|
||
|
||
:
|
||
|
||
> В штатной прошивке для этого в файле линкера создана специальная секция во внешней SDRAM.
|
||
> В VisualDSP секция задаётся через `#pragma section(секция, NO_INIT)`.
|
||
> В проекте уже есть готовый хедер, который нужно подключить перед объявлением переменной:
|
||
>
|
||
> c
|
||
|
||
> `#include "l502_sdram_noinit.h" static volatile uint32_t f_sport_in_buf[L502_SPORT_IN_BUF_SIZE];`
|
||
>
|
||
> Такой приём помещает массив в **неинициализируемую область SDRAM**.
|
||
|
||
**Что нужно сделать у вас:**
|
||
|
||
1. Найдите в проекте файл `l502_sdram_noinit.h` (для GCC будет `gcc/l502_sdram_noinit.h`, для VisualDSP — `vdsp/l502_sdram_noinit.h`).
|
||
|
||
2. Подключите его **не в начале файла**, а прямо перед объявлением большого массива:
|
||
|
||
c
|
||
|
||
|
||
3. `#include "l502_sdram_noinit.h" uint32_t arr[100000];`
|
||
|
||
4. Линкер уже настроен так, чтобы эта секция располагалась во внешней SDRAM, объём которой ~32 МБ.
|
||
|
||
|
||
После этого массив попадёт не во внутренний L1, а в SDRAM, и ошибка `region 'MEM_L1_DATA_A' overflowed` исчезнет.
|
||
|
||
Если хотите — могу помочь найти нужный `#pragma section` в вашем проекте или показать пример полного блока кода. Напишите!
|
||
|
||
## You said:
|
||
|
||
Я именно так и делаю: #include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[TX_BUFF_SIZE] = {0,}; Ошибка возникает
|
||
|
||
## ChatGPT said:
|
||
|
||
Отличный вопрос — значит, вы уже используете `l502_sdram_noinit.h`, но массив всё равно попадает во внутренний L1 и даёт переполнение.
|
||
Давайте разберёмся, что может быть не так.
|
||
|
||
---
|
||
|
||
### ✅ **1. Что делает `l502_sdram_noinit.h` в штатном проекте**
|
||
|
||
В типовом проекте L‑502/E‑502 для BlackFin этот хедер содержит примерно такие директивы (для VisualDSP):
|
||
|
||
c
|
||
|
||
`#pragma section("sdram_noinit") #pragma retain`
|
||
|
||
А для GCC может быть что-то вроде:
|
||
|
||
c
|
||
|
||
`__attribute__((section(".sdram_noinit")))`
|
||
|
||
и в линкер‑скрипте (`*.ldf` для VDSP или `*.ld` для GCC) определена секция:
|
||
|
||
scss
|
||
|
||
`sdram_noinit { INPUT_SECTIONS(...) } >MEM_SDRAM`
|
||
|
||
То есть _если проект собран с правильным линкером и секция прописана_, переменная окажется в SDRAM.
|
||
|
||
---
|
||
|
||
### ✅ **2. Почему всё равно попадает в L1**
|
||
|
||
Причины бывают такие:
|
||
|
||
#### 🔸 **2.1. Не подключён правильный линкер‑скрипт**
|
||
|
||
Например, вы собрали проект не с тем `LDF/LD` файлом, где описана секция SDRAM.
|
||
👉 Проверьте, что в настройках сборки выбран **linker description file**, где есть `MEM_SDRAM` и секция `sdram_noinit`.
|
||
|
||
В `l502_low_level.pdf` (глава 5.5) сказано:
|
||
|
||
> Внешняя память SDRAM доступна через секцию `.sdram_noinit`.
|
||
> Для её использования в проект нужно добавить соответствующий LDF с описанием памяти.
|
||
|
||
Если сейчас у вас используется «умолчальный» линкер‑скрипт от шаблона, то секции `.sdram_noinit` в нём просто нет, и компилятор складывает переменные в L1.
|
||
|
||
---
|
||
|
||
#### 🔸 **2.2. Размер массива всё равно слишком большой для SDRAM, если она не включена**
|
||
|
||
На BlackFin ADSP‑BF523 внешняя SDRAM должна быть инициализирована.
|
||
В штатной прошивке её включают в startup‑коде:
|
||
|
||
c
|
||
|
||
`init_external_memory(); // обычно в initcode`
|
||
|
||
Если проект собирали с нуля и инициализацию SDRAM не вызвали — линкер положит массив в доступный регион (L1), а потом скажет: overflow.
|
||
|
||
---
|
||
|
||
#### 🔸 **2.3. `= {0,}` заставляет компилятор разместить в секции с инициализацией**
|
||
|
||
`l502_sdram_noinit.h` как раз подразумевает секцию **NO_INIT** — туда можно класть только **неинициализированные** переменные.
|
||
Когда вы пишете `= {0,}` — компилятор относит её в секцию **инициализированных данных (.data)**, которая в линкере не перенаправлена в SDRAM.
|
||
|
||
👉 **Попробуйте убрать инициализацию:**
|
||
|
||
c
|
||
|
||
`#include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[TX_BUFF_SIZE]; // без = {0,}`
|
||
|
||
В SDRAM инициализация нулями при старте не делается — но если нужно обнулить, можно потом сделать `memset()` вручную.
|
||
|
||
---
|
||
|
||
### ✅ **3. Итог: как правильно объявить**
|
||
|
||
**Минимальный пример для большого буфера в SDRAM:**
|
||
|
||
c
|
||
|
||
`#include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[TX_BUFF_SIZE]; // без инициализатора`
|
||
|
||
**Обязательно:**
|
||
|
||
- Проект собран с линкер‑скриптом, где есть секция `.sdram_noinit` → MEM_SDRAM.
|
||
|
||
- В startup‑коде инициализирована внешняя SDRAM.
|
||
|
||
- Не использовать `= {0,}`.
|
||
|
||
|
||
---
|
||
|
||
### 🔧 **4. Что проверить прямо сейчас**
|
||
|
||
✔ убрать `= {0,}` и пересобрать
|
||
✔ убедиться, что подключён правильный `LDF/LD` (ищите в проекте файл с `MEM_SDRAM` и секцией `.sdram_noinit`)
|
||
✔ проверить, вызывается ли в startup‑коде инициализация SDRAM
|
||
|
||
---
|
||
|
||
Если нужно — могу помочь найти и поправить ваш линкер‑скрипт, показать, как должна выглядеть секция `sdram_noinit` или код инициализации SDRAM.
|
||
Напишите, если хотите, я могу дать готовый фрагмент `LDF` или `ld`! 🚀
|
||
|
||
## You said:
|
||
|
||
Убрал ={0,}, ошибка осталась. В соседнем файле есть такой фрагмент, который компилируется без ошибок:#include "l502_sdram_noinit.h" static volatile uint32_t f_sport_in_buf[L502_SPORT_IN_BUF_SIZE];
|
||
|
||
## ChatGPT said:
|
||
|
||
Отличная подсказка!
|
||
Если **в соседнем файле** такой же приём ( `#include "l502_sdram_noinit.h"` + `static volatile uint32_t f_sport_in_buf[...]` ) компилируется нормально, а ваш `TX_buff` – нет, значит:
|
||
|
||
- секция SDRAM и линкер у вас **настроены правильно** (иначе и соседний массив не собрался бы);
|
||
|
||
- проблема именно в **размере** или в том, **как вы объявили** массив.
|
||
|
||
|
||
---
|
||
|
||
### 🔎 Давайте прикинем размер
|
||
|
||
`L502_SPORT_IN_BUF_SIZE` в типовом проекте ≈ несколько тысяч элементов (например, 8192 слов = 32 КБ).
|
||
А вы объявили:
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[TX_BUFF_SIZE];`
|
||
|
||
и судя по первой ошибке:
|
||
|
||
> overflowed by 382848 bytes
|
||
|
||
👉 ваш `TX_BUFF_SIZE` ≈ **100000 элементов × 4 байта = 400 000 байт (≈ 391 КиБ)**.
|
||
Это не просто больше L1 (это понятно), а скорее всего **больше, чем выделено под секцию SDRAM** в линкер‑скрипте.
|
||
|
||
---
|
||
|
||
### 🔧 Как устроена секция `.sdram_noinit`
|
||
|
||
В проекте для L502 обычно в LDF прописано примерно так:
|
||
|
||
ld
|
||
|
||
`MEM_SDRAM { TYPE(RAM) START(0x0c000000) END(0x0c03FFFF) /* например, 256 Кбайт */ } SECTIONS { sdram_noinit { INPUT_SECTIONS(...) } >MEM_SDRAM }`
|
||
|
||
👉 Если `MEM_SDRAM` в линкере у вас меньше 400 КБ, то второй массив в неё уже **не влезает**, и компилятор ругается.
|
||
|
||
---
|
||
|
||
### ✅ Что проверить
|
||
|
||
1. **Откройте ваш LDF/LD** и найдите описание `MEM_SDRAM` или `.sdram_noinit`:
|
||
|
||
- START / END адреса покажут, сколько там реально памяти.
|
||
Например, `0x0c000000` – `0x0c03FFFF` = **256 КБ**.
|
||
|
||
2. Если вы точно хотите 100 000 `uint32_t` (≈ 400 КБ), а в линкере выделено меньше — придётся:
|
||
|
||
- либо **уменьшить TX_BUFF_SIZE** до размера доступной SDRAM‑секции,
|
||
|
||
- либо **поправить линкер** и увеличить размер MEM_SDRAM (если на плате физически есть больше SDRAM),
|
||
|
||
- либо разместить буфер по‑другому (например, разбить на несколько меньших массивов и подгружать их по очереди).
|
||
|
||
|
||
---
|
||
|
||
### ✨ Важные детали
|
||
|
||
✔ Сам по себе приём с `#include "l502_sdram_noinit.h"` **работает** (доказано вашим `f_sport_in_buf`).
|
||
❌ Ошибка overflowed говорит не про атрибут, а именно про нехватку памяти в секции.
|
||
|
||
---
|
||
|
||
### 📌 Что делать прямо сейчас
|
||
|
||
- ✅ **Посмотрите линкер‑скрипт и узнайте реальный размер MEM_SDRAM.**
|
||
Если хотите — пришлите фрагмент LDF/LD с `MEM_SDRAM` и `sdram_noinit`, я помогу посчитать.
|
||
|
||
- ✅ **Попробуйте временно уменьшить TX_BUFF_SIZE**, например до 8000:
|
||
|
||
|
||
c
|
||
|
||
`#define TX_BUFF_SIZE 8000 #include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[TX_BUFF_SIZE];`
|
||
|
||
Если соберётся без ошибок — значит точно упираетесь в лимит секции.
|
||
|
||
---
|
||
|
||
💡 **Вывод:**
|
||
Всё настроено правильно, просто ваш новый буфер слишком велик для определённой в линкере области SDRAM. Либо уменьшите размер, либо расширьте секцию в LDF (если аппаратно SDRAM позволяет).
|
||
|
||
Хочешь — пришли кусок LDF, я подскажу, как его поправить. ✨🚀
|
||
|
||
## You said:
|
||
|
||
В .ld: MEM_SDRAM : ORIGIN = 0x00000000, LENGTH = 32 * 1024 * 1024. Если уменьшить L502_SPORT_IN_BUF_SIZE с 2048*1024 до 2048, ошибка не меняется (число не влезших байтов остается тем-же) TX_BUFF_SIZE = 100000, т.е. TX_buff меньше, чем место, освободившееся при уменьшении SPORT_IN_BUFF. Если уменьшить размер TX_BUFF_SIZE, то соответственно уменьшается и число не влезший байтов. TX_BUFF_SIZE = 100000.
|
||
|
||
## ChatGPT said:
|
||
|
||
Спасибо, теперь картина проясняется.
|
||
У вас в **линкер‑скрипте MEM_SDRAM** действительно настроен на весь объём 32 МБ:
|
||
|
||
ld
|
||
|
||
`MEM_SDRAM : ORIGIN = 0x00000000, LENGTH = 32 * 1024 * 1024`
|
||
|
||
значит, лимит в 400 КБ никак не должен быть проблемой — в SDRAM места хватает.
|
||
|
||
Но из вашего эксперимента видно:
|
||
|
||
- изменение **L502_SPORT_IN_BUF_SIZE** не влияет на overflow (значит, этот массив _не_ сидит в той же секции или вовсе не в SDRAM);
|
||
|
||
- изменение **TX_BUFF_SIZE** изменяет overflow (значит, именно TX_buff заполняет L1 и даёт ошибку);
|
||
|
||
- ошибка: `region 'MEM_L1_DATA_A' overflowed by N bytes` — это ключ!
|
||
👉 **Линкер продолжает размещать TX_buff в L1, а не в SDRAM.**
|
||
|
||
|
||
То есть директива `#include "l502_sdram_noinit.h"` в этом месте **не срабатывает** так, как вы думаете.
|
||
|
||
---
|
||
|
||
### 🔎 Что отличает ваш случай от `f_sport_in_buf`
|
||
|
||
1. **Где вы разместили `#include "l502_sdram_noinit.h"`?**
|
||
|
||
В рабочем примере обычно делается так:
|
||
|
||
c
|
||
|
||
|
||
- `#include "l502_sdram_noinit.h" static volatile uint32_t f_sport_in_buf[L502_SPORT_IN_BUF_SIZE]; #include "l502_default_section.h"`
|
||
|
||
Обратите внимание: после массива снова возвращают секцию через `l502_default_section.h`.
|
||
Если вы забыли вернуть, может влиять, но это не ваша проблема — у вас наоборот не переключается.
|
||
|
||
**Важно:** `#include "l502_sdram_noinit.h"` должен стоять _не до всех include в начале файла_, а прямо перед объявлением массива.
|
||
|
||
- **Как устроен `l502_sdram_noinit.h` в вашем проекте?**
|
||
|
||
Для GCC он должен содержать что-то вроде:
|
||
|
||
c
|
||
|
||
|
||
`#pragma push #pragma default_section(SDRAM_NO_INIT)`
|
||
|
||
или
|
||
|
||
c
|
||
|
||
`#define __SDRAM_NOINIT __attribute__((section(".sdram_noinit")))`
|
||
|
||
и тогда объявлять так:
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[TX_BUFF_SIZE] __SDRAM_NOINIT;`
|
||
|
||
В некоторых вариантах API нужно явно добавить атрибут:
|
||
|
||
c
|
||
|
||
1. `#include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[TX_BUFF_SIZE] __attribute__((section(".sdram_noinit")));`
|
||
|
||
📌 **Посмотрите сам `l502_sdram_noinit.h` — что там внутри?**
|
||
Если там только `#pragma section("sdram_noinit")`, то эта директива обрабатывается **только компилятором VisualDSP++**.
|
||
В GCC она игнорируется и массив попадает в default‑секцию (L1).
|
||
|
||
|
||
---
|
||
|
||
### ✨ Почему пример `f_sport_in_buf` работает
|
||
|
||
Скорее всего, `f_sport_in_buf` объявлен **в том же файле, что и остальная прошивка, которая собирается VisualDSP** (или с атрибутами GCC), и его атрибут реально сработал.
|
||
|
||
А ваш `TX_buff` объявлен:
|
||
|
||
- либо в другом файле, который компилируется другим компилятором (или без тех же настроек),
|
||
|
||
- либо вы не добавили правильный атрибут для GCC.
|
||
|
||
|
||
---
|
||
|
||
### ✅ Что попробовать
|
||
|
||
**Вариант 1 (универсальный для GCC):**
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[TX_BUFF_SIZE] __attribute__((section(".sdram_noinit")));`
|
||
|
||
**Вариант 2 (если вы используете ту же схему с include, как в примерe):**
|
||
|
||
c
|
||
|
||
`#include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[TX_BUFF_SIZE]; #include "l502_default_section.h"`
|
||
|
||
**Вариант 3 (через pragma VDSP):**
|
||
|
||
c
|
||
|
||
`#pragma section("sdram_noinit") static volatile uint32_t TX_buff[TX_BUFF_SIZE]; #pragma section("default_section") // вернуться обратно`
|
||
|
||
---
|
||
|
||
### 📌 Проверьте `l502_sdram_noinit.h`
|
||
|
||
Напишите, что в нём внутри, или пришлите фрагмент — тогда я точно скажу, какой синтаксис использовать.
|
||
|
||
---
|
||
|
||
### 💡 Вывод
|
||
|
||
Ошибка `MEM_L1_DATA_A overflowed` значит, что **ваш массив TX_buff не попал в секцию SDRAM**.
|
||
Надо явно указать атрибут или pragma для нужного компилятора.
|
||
|
||
Попробуйте прямо сейчас вот так:
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[TX_BUFF_SIZE] __attribute__((section(".sdram_noinit")));`
|
||
|
||
и пересоберите.
|
||
Если ошибка исчезнет — значит, дело в несовпадении атрибутов.
|
||
Если нет — пришлите содержимое `l502_sdram_noinit.h`, посмотрим детальнее. 🚀🔥
|
||
|
||
## You said:
|
||
|
||
Компиляция происходит через gcc. Содержимеое l502_sdram_noinit.h:__attribute__((section(".sdram_noinit")))
|
||
|
||
## ChatGPT said:
|
||
|
||
Отлично, теперь всё стало кристально ясно.
|
||
Вы используете **gcc**, а `l502_sdram_noinit.h` в вашем проекте содержит всего лишь:
|
||
|
||
c
|
||
|
||
`__attribute__((section(".sdram_noinit")))`
|
||
|
||
А вы в коде делаете:
|
||
|
||
c
|
||
|
||
`#include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[TX_BUFF_SIZE];`
|
||
|
||
👉 **Проблема в том, что такой `#include` сам по себе ничего не делает.**
|
||
Это не `#pragma`, это просто атрибут, который нужно указать у переменной.
|
||
|
||
---
|
||
|
||
### ✅ Как правильно использовать этот атрибут
|
||
|
||
Нужно написать так:
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[TX_BUFF_SIZE] __attribute__((section(".sdram_noinit")));`
|
||
|
||
или, если хотите использовать хедер как макрос, обычно делают:
|
||
|
||
c
|
||
|
||
`#define SDRAM_NOINIT __attribute__((section(".sdram_noinit"))) static volatile uint32_t TX_buff[TX_BUFF_SIZE] SDRAM_NOINIT;`
|
||
|
||
А просто `#include "l502_sdram_noinit.h"` перед объявлением массива — это ничего не даёт: компилятор не «подставляет» его в объявление автоматически.
|
||
|
||
---
|
||
|
||
### 📌 Почему `f_sport_in_buf` компилируется
|
||
|
||
Потому что там в исходнике написано **правильно**:
|
||
|
||
c
|
||
|
||
`static volatile uint32_t f_sport_in_buf[L502_SPORT_IN_BUF_SIZE] __attribute__((section(".sdram_noinit")));`
|
||
|
||
А не просто `#include`.
|
||
|
||
Иногда разработчики в своих файлах делают так:
|
||
|
||
c
|
||
|
||
`#define L502_SDRAM_NOINIT __attribute__((section(".sdram_noinit"))) static volatile uint32_t f_sport_in_buf[L502_SPORT_IN_BUF_SIZE] L502_SDRAM_NOINIT;`
|
||
|
||
Или даже:
|
||
|
||
c
|
||
|
||
`__attribute__((section(".sdram_noinit"))) static volatile uint32_t f_sport_in_buf[L502_SPORT_IN_BUF_SIZE];`
|
||
|
||
(атрибут можно поставить перед `static` или после типа — оба варианта работают)
|
||
|
||
---
|
||
|
||
### ✨ Что нужно сделать вам
|
||
|
||
Просто объявите свой массив **с этим атрибутом**, вот так:
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[TX_BUFF_SIZE] __attribute__((section(".sdram_noinit")));`
|
||
|
||
💡 **Всё!** Он попадёт в секцию SDRAM, и ошибка `region 'MEM_L1_DATA_A' overflowed` исчезнет.
|
||
|
||
---
|
||
|
||
### 🚀 Итог
|
||
|
||
✅ Для gcc недостаточно `#include`, нужно явно указать `__attribute__((section(".sdram_noinit")))` в объявлении переменной.
|
||
✅ Убедитесь, что секция `.sdram_noinit` прописана в вашем `.ld` → вы это проверили (32 МБ).
|
||
✅ После этого буфер любого разумного размера (до 32 МБ) будет размещён в SDRAM.
|
||
|
||
**Рабочий пример:**
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[100000] __attribute__((section(".sdram_noinit")));`
|
||
|
||
Соберите и отпишитесь, что ошибка ушла! 💪🔥
|
||
|
||
## You said:
|
||
|
||
Проблема решена
|
||
|
||
## ChatGPT said:
|
||
|
||
Отлично! Рад, что всё получилось и массив теперь попадает в SDRAM. 🚀
|
||
|
||
Если ещё будут вопросы по линкеру, секциям памяти или особенностям BlackFin — смело обращайтесь. Удачной работы с вашим проектом! 💡✅
|
||
|
||
## You said:
|
||
|
||
Как соотносятся логическая таблица и поток данных с АЦП по SPORT0? В какой момент таблица включается в работу? Как задается порядок измерения физических каналов ADC?
|
||
|
||
## ChatGPT said:
|
||
|
||
Отличный вопрос — это как раз описано в низкоуровневом описании модуля.
|
||
Давайте разложу по шагам, чтобы было понятно, **где участвует логическая таблица**, **когда она начинает действовать**, и **как задаётся порядок физического опроса АЦП**.
|
||
|
||
---
|
||
|
||
### 📌 **1. Что такое «логическая таблица» в L502/E502**
|
||
|
||
В модуле есть множество физических входов АЦП (например, 32 дифференциальных).
|
||
Чтобы каждый раз не пересобирать прошивку, в ПЛИС реализован **блок IO_HARD** с регистром «логическая таблица» (логическая таблица сканирования каналов).
|
||
|
||
Эта таблица хранится в регистрах ПЛИС и управляется через SPI, если модуль в DSP‑режиме.
|
||
В таблице вы перечисляете **последовательность логических каналов** и для каждого канала — какой физический вход АЦП и с каким диапазоном/усилением использовать.
|
||
|
||
👉 Таким образом, **логическая таблица — это карта: “логический индекс → физический канал АЦП + режим измерения”**.
|
||
|
||
---
|
||
|
||
### 📌 **2. Как данные попадают из АЦП в BlackFin (SPORT0)**
|
||
|
||
Поток АЦП всегда идёт в ПЛИС (гальваноотвязанная часть).
|
||
Дальше два режима:
|
||
|
||
- **Штатный режим:** ПЛИС через DMA сразу шлёт данные в ПК.
|
||
|
||
- **DSP‑режим:** ПЛИС отправляет данные по интерфейсу SPORT0 в BlackFin.
|
||
|
||
|
||
В DSP‑режиме SPORT0 сконфигурирован в качестве приёмника данных из ПЛИС.
|
||
Каждый приходящий отсчёт в SPORT0 уже соответствует одному элементу логической таблицы.
|
||
|
||
То есть в BlackFin вы видите **поток уже пересобранный по таблице**, а не «сырые физические входы».
|
||
|
||
---
|
||
|
||
### 📌 **3. В какой момент логическая таблица начинает работать**
|
||
|
||
Логическая таблица применяется **в момент запуска сбора данных**, когда вы разрешаете поток АЦП (например, командой в IO_HARD или через управляющий регистр HostDMA).
|
||
|
||
✅ Пока поток не запущен — данные не идут, таблица бездействует.
|
||
✅ Как только вы через регистры IO_HARD задаёте «количество логических каналов» и разрешаете поток ввода, **ПЛИС начинает сканировать физические входы согласно таблице** и формирует кадры SPORT0.
|
||
|
||
---
|
||
|
||
### 📌 **4. Как задать порядок измерения физических каналов**
|
||
|
||
Всё задаётся именно в регистрах блока **IO_HARD**.
|
||
В низкоуровневом описании (см. гл. 6.8.3–6.8.5 в pdf) есть:
|
||
|
||
- **IO_HARD таблица каналов** – массив регистров, в каждом из которых:
|
||
|
||
- номер физического входа АЦП,
|
||
|
||
- диапазон / усиление,
|
||
|
||
- режим (дифференциальный / однополярный),
|
||
|
||
- опциональные флаги.
|
||
|
||
|
||
Пример алгоритма:
|
||
|
||
1. Пишем в регистры IO_HARD логическую таблицу:
|
||
|
||
- `LOG_TABLE[0] = физический канал 3, диапазон ±10 В`
|
||
|
||
- `LOG_TABLE[1] = физический канал 7, диапазон ±5 В`
|
||
|
||
- и так далее.
|
||
|
||
2. Пишем в `LOG_TABLE_SIZE` количество элементов таблицы.
|
||
|
||
3. Разрешаем поток: в регистре IO_HARD ставим бит “enable input”.
|
||
|
||
|
||
👉 **После этого ПЛИС в каждом такте мультиплексирует АЦП на нужный физический вход, оцифровывает его и кладёт отсчёт в поток SPORT0 в том порядке, в каком вы прописали таблицу.**
|
||
|
||
---
|
||
|
||
### 📐 **Схематично**
|
||
|
||
csharp
|
||
|
||
`Логическая таблица (в IO_HARD): [0] → PhysCh3 [1] → PhysCh7 [2] → PhysCh0 [3] → PhysCh2 ...`
|
||
|
||
При запущенном сборе данных:
|
||
|
||
css
|
||
|
||
`SPORT0 поток: Word0 = ADC(PhysCh3) Word1 = ADC(PhysCh7) Word2 = ADC(PhysCh0) Word3 = ADC(PhysCh2) ... затем снова Word0 = ADC(PhysCh3) ...`
|
||
|
||
---
|
||
|
||
### ✨ **Итог**
|
||
|
||
✔ **Логическая таблица** — это программируемая последовательность «логический канал → физический вход + режим».
|
||
✔ Она **начинает действовать в момент запуска потока данных** (когда вы разрешаете ввод).
|
||
✔ **Порядок измерения физ. каналов** задаётся именно содержимым таблицы (регистры блока IO_HARD).
|
||
✔ В BlackFin по SPORT0 вы уже получаете данные в этом порядке, кадр за кадром.
|
||
|
||
---
|
||
|
||
Если нужно, я могу:
|
||
|
||
- привести точные адреса регистров для таблицы (по pdf),
|
||
|
||
- показать пример записи таблицы из кода BlackFin,
|
||
|
||
- или пример того, как эти данные обрабатываются в `user_process_adc_data()`.
|
||
|
||
|
||
Напишите! 🚀📡
|
||
|
||
## You said:
|
||
|
||
В главе 5.12 (форматы данных) ничего не говорится про логические каналы. Как это соотносится с вышесказанным?
|
||
|
||
## ChatGPT said:
|
||
|
||
Отличное замечание — действительно, в **главе 5.12 “Форматы данных в потоке на ввод и на вывод”** речь идёт о формате **сырых слов в потоке**, и там не упоминается понятие «логического канала».
|
||
Давайте разложим, как эти два слоя соотносятся, чтобы не было путаницы:
|
||
|
||
---
|
||
|
||
### 🔹 **Что описывает глава 5.12**
|
||
|
||
В этой главе показано, **как именно закодирован каждый отсчёт в 32‑битном слове**, которое идёт по SPORT0 / HostDMA.
|
||
Например (типичный формат для L502):
|
||
|
||
scss
|
||
|
||
`Биты [15:0] – результат АЦП (signed 16-bit) Биты [19:16] – номер диапазона (или какие-то флаги) Биты [31:20] – резерв`
|
||
|
||
То есть, если смотреть на поток _как на байты_, то он выглядит как:
|
||
|
||
makefile
|
||
|
||
`Word0: [данные АЦП + флаги] Word1: [данные АЦП + флаги] Word2: ...`
|
||
|
||
**Важно:** в этой главе речь идёт о формате одного слова и вообще не затрагивается, от какого канала оно пришло и в каком порядке.
|
||
|
||
---
|
||
|
||
### 🔹 **Где тогда «логические каналы»**
|
||
|
||
Логические каналы описаны **не в главе формата**, а в описании блока IO_HARD (см. гл. 6.8.4–6.8.6).
|
||
Там есть:
|
||
|
||
- `LOG_TABLE` – массив регистров, который вы заполняете.
|
||
|
||
- `LOG_TABLE_SIZE` – сколько элементов в таблице.
|
||
|
||
|
||
**ПЛИС** при формировании потока по SPORT0 делает так:
|
||
|
||
arduino
|
||
|
||
`для i = 0 … LOG_TABLE_SIZE-1: phys_ch = LOG_TABLE[i] ADC_select(phys_ch) sample = ADC_read() word = pack_to_format(sample) send_to_SPORT0(word)`
|
||
|
||
То есть в каждый момент времени SPORT0 отдаёт слова **в том порядке, который вы задали таблицей**.
|
||
Но каждое слово само по себе не несёт информацию о том, из какого именно канала оно было — оно просто «следующее по порядку в цикле сканирования».
|
||
|
||
---
|
||
|
||
### 🔹 **Почему в 5.12 нет упоминания логических каналов**
|
||
|
||
Потому что 5.12 отвечает на другой вопрос:
|
||
**«Как расшифровать одно слово?»**, а не **«В каком порядке они идут?»**.
|
||
|
||
👉 **Порядок** определяется логической таблицей (гл. 6.8).
|
||
👉 **Формат слова** (биты данных/флагов) описан в 5.12.
|
||
|
||
---
|
||
|
||
### 🔗 **Как это связывается на практике**
|
||
|
||
1. Вы настраиваете таблицу (логические каналы):
|
||
|
||
c
|
||
|
||
|
||
- `LOG_TABLE[0] = phys_ch 7; LOG_TABLE[1] = phys_ch 3; LOG_TABLE_SIZE = 2;`
|
||
|
||
- Разрешаете поток ввода.
|
||
|
||
- ПЛИС начинает выдавать на SPORT0 такую последовательность:
|
||
|
||
makefile
|
||
|
||
|
||
- `Word0 = данные с физ.канала 7 (упакованы по формату из 5.12) Word1 = данные с физ.канала 3 (упакованы по формату из 5.12) Word2 = снова данные с физ.канала 7 Word3 = снова данные с физ.канала 3 ...`
|
||
|
||
- В BlackFin вы получаете кадры как массив 32‑битных слов:
|
||
|
||
c
|
||
|
||
|
||
1. `for (i=0; i<LOG_TABLE_SIZE; i++) { raw = buf[i]; val = raw & 0xFFFF; // данные АЦП // интерпретируете в соответствии с 5.12 }`
|
||
|
||
|
||
---
|
||
|
||
### ✨ **Вывод**
|
||
|
||
✅ Глава 5.12 отвечает на вопрос «что внутри одного слова».
|
||
✅ Логическая таблица (IO_HARD) отвечает на вопрос «в каком порядке эти слова приходят».
|
||
✅ Вместе они дают полную картину:
|
||
|
||
> **ПОТОК SPORT0 = циклическая выборка физических каналов согласно LOG_TABLE,
|
||
> каждое слово упаковано по формату из 5.12.**
|
||
|
||
---
|
||
|
||
Если хотите, я могу подготовить:
|
||
|
||
- пример инициализации LOG_TABLE из кода,
|
||
|
||
- или пример функции `user_process_adc_data()` с расшифровкой данных.
|
||
|
||
|
||
Напишите, если нужно! 🚀📡
|
||
|
||
## You said:
|
||
|
||
Как проверить состояние бита I в переменной?
|
||
|
||
## ChatGPT said:
|
||
|
||
В языке C (и, соответственно, в коде для Blackfin) «проверить состояние бита I» в переменной можно через побитовую операцию **AND** (`&`) и маску с единицей в нужной позиции.
|
||
|
||
---
|
||
|
||
### 📌 **Общая формула**
|
||
|
||
Пусть у вас есть 32‑битная переменная `x`, и вы хотите узнать, установлен ли бит с номером `I` (нумерация обычно от 0 – младший бит):
|
||
|
||
c
|
||
|
||
`if (x & (1u << I)) { // бит I = 1 } else { // бит I = 0 }`
|
||
|
||
---
|
||
|
||
### 📌 **Подробности**
|
||
|
||
✅ `(1u << I)` – это маска, где только бит I равен 1, а остальные 0.
|
||
Например:
|
||
|
||
- `I = 0` → маска `0b000...0001`
|
||
|
||
- `I = 3` → маска `0b000...1000`
|
||
|
||
|
||
✅ `x & mask` – выдаёт ненулевой результат, если нужный бит установлен.
|
||
|
||
---
|
||
|
||
### 📌 **Пример**
|
||
|
||
c
|
||
|
||
`uint32_t status = 0b01001000; // просто пример int I = 3; if (status & (1u << I)) { // здесь бит 3 установлен (результат != 0) } else { // бит 3 сброшен }`
|
||
|
||
---
|
||
|
||
### 📌 **Альтернативный способ (сдвиг и сравнение)**
|
||
|
||
Иногда удобнее сдвинуть значение и проверить младший бит:
|
||
|
||
c
|
||
|
||
`if ((x >> I) & 1u) { // бит I = 1 } else { // бит I = 0 }`
|
||
|
||
---
|
||
|
||
✅ **Оба способа эквивалентны**, выбирайте, какой читается удобнее.
|
||
|
||
---
|
||
|
||
Если нужно — могу показать, как это оформить в виде функции‑помощника или макроса:
|
||
|
||
c
|
||
|
||
`#define BIT_IS_SET(value, bit) (((value) >> (bit)) & 1u)`
|
||
|
||
Тогда:
|
||
|
||
c
|
||
|
||
`if (BIT_IS_SET(status, 3)) { /* ... */ }`
|
||
|
||
Напишите, если хотите пример! 🚀✨
|
||
|
||
## You said:
|
||
|
||
А если яхочу проверить, соотвестсвуют ли определенные биты (заданы маской) определенному значению?
|
||
|
||
## ChatGPT said:
|
||
|
||
Чтобы проверить, что в переменной `x` под маской `MASK` ровно те биты, которые вы ожидаете (скажем, равны `VAL`), используют конструкцию:
|
||
|
||
c
|
||
|
||
`if ( (x & MASK) == VAL ) { // Под маской MASK в x стоят биты, равные VAL } else { // Отличаются }`
|
||
|
||
- `x & MASK` обнуляет все биты, не входящие в маску.
|
||
|
||
- Сравнение `== VAL` проверяет, что оставшиеся «отмаскированные» биты соответствуют нужному шаблону `VAL`.
|
||
|
||
|
||
---
|
||
|
||
### Пример
|
||
|
||
Допустим, в 8-битной `status` мы хотим проверить, что биты 5…3 (маска `0b0011 1000` = `0x38`) установаны в паттерн `0b0001 0000` (`0x10`):
|
||
|
||
c
|
||
|
||
`uint8_t status = /* какое-то значение */; #define MASK 0x38 // биты 5,4,3 #define VAL 0x10 // хотим, чтобы среди этих трёх бит был только 4-й if ( (status & MASK) == VAL ) { // Только бит 4=1, а биты 5 и 3 = 0 } else { // Иная комбинация в битах 5–3 }`
|
||
|
||
---
|
||
|
||
### Макрос для удобства
|
||
|
||
c
|
||
|
||
`#define BITS_EQ(x, mask, val) ( ((x) & (mask)) == (val) ) if ( BITS_EQ(status, 0x38, 0x10) ) { // условие выполнено }`
|
||
|
||
---
|
||
|
||
Таким образом вы легко проверяете **точное соответствие** битового поля под любой маской.
|
||
|
||
2/2
|
||
|
||
## You said:
|
||
|
||
Как пометить массив данные в приемном буфере SPORT0, как обработанный?
|
||
|
||
## ChatGPT said:
|
||
|
||
В Blackfin (и в целом при работе с периферией через DMA/Sport) само понятие «пометить как обработанный» – это не аппаратный флажок, а **ваш собственный признак** в программе.
|
||
Аппаратный DMA/Sport просто заполняет приёмный буфер (обычно кольцевой), а ваша логика должна понимать, какие кадры вы уже обработали.
|
||
|
||
Вот как это обычно делают:
|
||
|
||
---
|
||
|
||
### 📌 Общая идея
|
||
|
||
Вы организуете приём в **двойной или кольцевой буфер** в SDRAM и ведёте **индекс или флаг**, который говорит, что эти данные готовы и что вы их уже обработали.
|
||
|
||
---
|
||
|
||
### 📌 Стандартный паттерн (двойной буфер)
|
||
|
||
1. В памяти (SDRAM) создаёте массив:
|
||
|
||
|
||
c
|
||
|
||
`#define FRAME_SIZE 1024 static volatile uint32_t sport_rx_buf[2][FRAME_SIZE] __attribute__((section(".sdram_noinit")));`
|
||
|
||
2. Настраиваете DMA на работу в «ping-pong» (двойной буфер):
|
||
|
||
- Пока DMA пишет в `sport_rx_buf[0]`, вы обрабатываете данные из `sport_rx_buf[1]`.
|
||
|
||
- Когда DMA переключается на второй буфер, вы обрабатываете первый.
|
||
|
||
3. В обработчике прерывания DMA (или SPORT):
|
||
|
||
|
||
c
|
||
|
||
`static volatile uint8_t active_buf = 0; static volatile uint8_t ready_flag[2] = {0,0}; void sport0_rx_isr(void) { // Завершился приём в текущий буфер ready_flag[active_buf] = 1; // пометить, что буфер готов к обработке active_buf ^= 1; // переключить активный буфер }`
|
||
|
||
4. В основном цикле:
|
||
|
||
|
||
c
|
||
|
||
`if (ready_flag[0]) { // обработка sport_rx_buf[0] ready_flag[0] = 0; // пометили, что обработан } if (ready_flag[1]) { // обработка sport_rx_buf[1] ready_flag[1] = 0; // пометили, что обработан }`
|
||
|
||
---
|
||
|
||
### 📌 Если один большой кольцевой буфер
|
||
|
||
- Заводите индекс «прочитанной» позиции:
|
||
|
||
|
||
c
|
||
|
||
`static volatile uint32_t sport_rx_buf[BUF_SIZE] __attribute__((section(".sdram_noinit"))); static volatile uint32_t rd_pos = 0; static volatile uint32_t wr_pos = 0; // обновляется в ISR`
|
||
|
||
- В прерывании DMA/SPORT увеличиваете `wr_pos` на количество принятых слов (с модулем по BUF_SIZE):
|
||
|
||
|
||
c
|
||
|
||
`wr_pos = (wr_pos + frame_len) % BUF_SIZE;`
|
||
|
||
- В основном цикле обрабатываете:
|
||
|
||
|
||
c
|
||
|
||
`while (rd_pos != wr_pos) { process_word(sport_rx_buf[rd_pos]); rd_pos = (rd_pos + 1) % BUF_SIZE; // пометили, что обработан }`
|
||
|
||
---
|
||
|
||
### 📌 Важный момент
|
||
|
||
**Нет специального бита в железе, который бы говорил: “эти данные обработаны”**.
|
||
Вы должны **вести свой флаг/индекс** в памяти и, обработав данные, **сбросить этот флаг или продвинуть индекс**.
|
||
|
||
---
|
||
|
||
### ✨ Итог
|
||
|
||
✅ Чтобы пометить массив (или его часть) как обработанный, в коде нужно завести **свой признак готовности**, например:
|
||
|
||
c
|
||
|
||
`volatile uint8_t rx_ready[2]; // по одному флагу на каждый под‑буфер`
|
||
|
||
и после обработки:
|
||
|
||
c
|
||
|
||
`rx_ready[i] = 0; // пометили, что данные обработаны`
|
||
|
||
✅ Аппаратно SPORT0/DMA не помечает данные «обработанными», он только заполняет память и выставляет прерывания.
|
||
✅ Весь контроль — на вашей стороне.
|
||
|
||
---
|
||
|
||
💡 Если хотите, я могу привести готовый фрагмент для Blackfin с настройкой SPORT0 DMA и обработкой через двойной буфер. Напишите! 🚀
|
||
|
||
## You said:
|
||
|
||
Я обрабатываю данные в функции usr_in_proc_data(): парсю data (массив, выделенный в приемном буфере SPORT0), кладу новые данные в TX_buff[], пытаюсь отправить TX_buff: hdma_send_req_start(TX_buff, TX_buff_I, 0);. Как освободить приемный буфер? На данный момент программа отправляет TX_buff только один раз. Если же отправлять так: hdma_send_req_start(data, size, 0), данные передаются многократно (по мере их накопления), как и должно быть
|
||
|
||
## ChatGPT said:
|
||
|
||
Чтобы пометить часть приёмного буфера SPORT0 как «освобождённую» (т. е. готовую к повторному приёму) после обработки данных в `usr_in_proc_data()`, нужно воспользоваться встроенной в штатную прошивку логикой учёта позиций в кольцевом буфере.
|
||
|
||
В `l502_stream.c` (модуль синхронных потоков) для каждого буфера SPORT0 введены три указателя (индекса):
|
||
|
||
- **`f_sport_in_put_pos`** — указывает на слово сразу за последним _принятым_ словом. Обновляется в ISR SPORT0_RX, когда DMA закончил приём очередного блока.
|
||
|
||
- **`f_sport_in_proc_pos`** — указывает на слово, с которого ещё _не_ начиналась обработка. Ваша функция `usr_in_proc_data()` увеличивает этот индекс на число обработанных слов, возвращаемое ей, чтобы пометить данные в диапазоне `[старое f_sport_in_proc_pos … новое f_sport_in_proc_pos)` как «обработанные»
|
||
|
||
|
||
- .
|
||
|
||
- **`f_sport_in_get_pos`** — указывает на слово, которое идёт за последним _освобождённым_ для приёма. Этот индекс и отражает, сколько места освободилось в буфере.
|
||
|
||
|
||
Чтобы «освободить» буфер, нужно именно вызвать **`stream_in_buf_free()`** с количеством уже _обработанных_ слов. Эта функция сдвинет `f_sport_in_get_pos`, увеличив его на переданный ей размер, и тем самым даст DMA знать, что в приёмном буфере появилось новое свободное место
|
||
|
||
.
|
||
|
||
В итоге, цикл работы выглядит так:
|
||
|
||
1. **ISR SPORT0_RX** (или DMA-ISR) при каждом шаге приёма сбрасывает
|
||
|
||
c
|
||
|
||
|
||
- `f_sport_in_put_pos = (f_sport_in_put_pos + block_size) % BUF_SIZE;`
|
||
|
||
- **Фоновая функция** `stream_proc()` замечает, что
|
||
|
||
c
|
||
|
||
|
||
- `available = (f_sport_in_put_pos - f_sport_in_proc_pos + BUF_SIZE) % BUF_SIZE;`
|
||
|
||
стало > 0, и вызывает вашу `usr_in_proc_data(data_ptr, available)`.
|
||
|
||
- **Внутри** `usr_in_proc_data()` вы парсите `available` слов, затем
|
||
|
||
c
|
||
|
||
|
||
- `return processed; // число реально обработанных слов`
|
||
|
||
Фреймворк **увеличит** `f_sport_in_proc_pos` на `processed`, считая эти слова „обработанными“.
|
||
|
||
- **Чтобы освободить место** _для DMA_, фоновая логика **автоматически** вызовет
|
||
|
||
c
|
||
|
||
|
||
`stream_in_buf_free(processed);`
|
||
|
||
— это и есть освобождение приёмного буфера
|
||
|
||
1. .
|
||
|
||
|
||
**Важно**: если вы вручную вызываете `hdma_send_req_start(TX_buff, TX_buff_I, 0)` прямо в `usr_in_proc_data()`, то штатный `stream_in_buf_free()` может не сработать. В таком случае после обработки вам нужно явно освободить буфер:
|
||
|
||
c
|
||
|
||
`// в usr_in_proc_data() int processed = parse_and_fill_TX_buff(data, size); hdma_send_req_start(TX_buff, TX_buff_I, 0); // теперь — освободить приемный буфер SPORT0: stream_in_buf_free(processed); return processed;`
|
||
|
||
Таким образом вы сообщаете фоновому движку, что `processed` слов уже прочитаны и могут быть перезаписаны приёмом новых данных.
|
||
|
||
2/2
|
||
|
||
## You said:
|
||
|
||
Проблема с TX_buff вернулась. Давай вернемся к её решению. Вероятно, я случайно уменьшил размер TX_buff и ошибочно посчитал, что проблема решена. static volatile uint32_t TX_buff[100000] __attribute__((section(".sdram_noinit"))); не помогло. В .ld есть единственное упоминание sdram: .sdram_noinit (NOLOAD) : { *(.sdram_noinit, .sdram_noinit.*) } > MEM_SDRAM Какие ещё причины могут помешать разместить массив в SDRAM? Какими методами я могу отследить путь от объявления массива в .с до его окончательного размещения в конце компиляции и сравнить его с правильно размещаемым f_sport_in_buf? Как я могу
|
||
|
||
## ChatGPT said:
|
||
|
||
Чтобы понять, почему именно ваш `TX_buff` всё ещё кладётся в L1, хотя вы даёте ему `__attribute__((section(".sdram_noinit")))`, нужно проследить несколько этапов сборки:
|
||
|
||
1. **Компиляция** `.c` → `.o`
|
||
|
||
2. **Линковка** `.o` → ELF + `.map`
|
||
|
||
|
||
И сравнить, как на каждом этапе указывает секцию у `f_sport_in_buf` (который работает) и у `TX_buff`. Вот что можно сделать:
|
||
|
||
---
|
||
|
||
## 1. Посмотреть секцию в объектном файле
|
||
|
||
Соберите только ваш модуль в `.o` и запустите:
|
||
|
||
bash
|
||
|
||
`# Для BlackFin‑GCC используйте свой readelf/objdump, например bfin-elf-readelf bfin-elf-readelf -S your_module.o | grep -A1 TX_buff bfin-elf-readelf -S your_module.o | grep -A1 f_sport_in_buf`
|
||
|
||
В выводе вас интересует поле **`Name`** (например `.sdram_noinit`) и **`Size`**.
|
||
|
||
- Если `TX_buff` лежит в секции вроде `.bss` или `.data`, значит компилятор не привязал атрибут.
|
||
|
||
- Если он уже в `.sdram_noinit`, значит проблема на этапе линковки.
|
||
|
||
|
||
---
|
||
|
||
## 2. Сгенерировать `.map` и проверить, где лежат символы
|
||
|
||
При линковке дайте флаг:
|
||
|
||
bash
|
||
|
||
`-Wl,-Map=output.map`
|
||
|
||
В `output.map` найдите:
|
||
|
||
nginx
|
||
|
||
`TX_buff`
|
||
|
||
и посмотрите, какой адрес и к какой секции он отнёсся.
|
||
Затем найдите `f_sport_in_buf` и убедитесь, что он реально попал в `.sdram_noinit`.
|
||
|
||
---
|
||
|
||
## 3. Сравнить секции в линкер‑скрипте
|
||
|
||
У вас в `.ld` есть:
|
||
|
||
ld
|
||
|
||
`.sdram_noinit (NOLOAD) : { *(.sdram_noinit .sdram_noinit.*) } > MEM_SDRAM`
|
||
|
||
Проверьте, нет ли **других** секций, которые тоже могут содержать имя `.sdram_noinit` (буква `.` перед подстановкой). Иногда компилятор ставит в секцию с префиксом, например `.sdram_noinit.XXX`.
|
||
|
||
В `readelf -S` итогового ELF посмотрите, есть ли секция
|
||
|
||
csharp
|
||
|
||
`[some_index] .sdram_noinit PROGBITS/NOLOAD ...`
|
||
|
||
и сколько у неё размера.
|
||
|
||
---
|
||
|
||
## 4. Убедиться в синтаксисе атрибута
|
||
|
||
Для GCC допустимо два варианта:
|
||
|
||
c
|
||
|
||
`__attribute__((section(".sdram_noinit"))) static volatile uint32_t TX_buff[100000];`
|
||
|
||
или
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[100000] __attribute__((section(".sdram_noinit")));`
|
||
|
||
Если вы вставили атрибут **до** `static`, то компилятор может его проигнорировать. Всегда ставьте атрибут **непосредственно после** объявления переменной, а не отдельной строкой.
|
||
|
||
---
|
||
|
||
## 5. Проверить флаги компилятора
|
||
|
||
Иногда опции вроде `-fdata-sections` могут разбить данные на свои секции и линкер их не свяжет с `.sdram_noinit`. Если вы используете `-ffunction-sections -fdata-sections` и `--gc-sections`, попробуйте временно их выключить, чтобы исключить проблему «обрезания» или «перемещения» секций.
|
||
|
||
---
|
||
|
||
## 6. Итоговая проверка
|
||
|
||
1. Скомпилируйте один файл с `TX_buff` и один – с `f_sport_in_buf`.
|
||
|
||
2. Выполните:
|
||
|
||
bash
|
||
|
||
|
||
3. `bfin-elf-readelf -S tx_module.o > tx_sections.txt bfin-elf-readelf -S sport_module.o > sport_sections.txt diff tx_sections.txt sport_sections.txt`
|
||
|
||
4. Смотрите, в чём именно отличаются секции.
|
||
|
||
|
||
Когда вы увидите, что у `TX_buff` реально стоит секция `.bss`, а у `f_sport_in_buf` — `.sdram_noinit`, станет ясно, на каком этапе «теряется» ваш атрибут.
|
||
|
||
---
|
||
|
||
### Возможные причины
|
||
|
||
- **Атрибут стоит не у той переменной** (случайно применён к локальной функции или к другому объявлению).
|
||
|
||
- **Секцию переопределяет более поздний `* : { *(.bss) }`** в линкере, который идёт после `.sdram_noinit`. Тогда линкер «забирает» входные секции и кладёт их в общую `.bss`. Нужно в `ld` объявить `.sdram_noinit` **после** `.bss`.
|
||
|
||
- **Ошибочное включение атрибута**: если вы просто написали `#include "l502_sdram_noinit.h"` без применения его в объявлении, он не сработает.
|
||
|
||
|
||
---
|
||
|
||
Как только вы проследите через **`readelf -S`** и **`.map`**, где лежат оба массива, вы сразу поймёте, что поправить — порядок секций в `.ld` или синтаксис атрибута.
|
||
|
||
Дайте знать, какие результаты вы получите после этих проверок!
|
||
|
||
## You said:
|
||
|
||
1)В .o нет упоминаний ни о f_sport_in_buf (объявлен в l502_stream.c), ни о TX_buff. .sdram_noinit упоминается и в l502_stream.o, и в #include "l502_sdram_noinit.h".o. Если я убираю __attribute__((section(".sdram_noinit"))); или #include "l502_sdram_noinit.h" из объявления TX_buff, то упоминание .sdram_noinit пропадеат из l502_sdram_noinit.o. Тоесть на первом этапе всё работает нормально? В .map есть всего 2 упоминания .sdram: соответствующие файлав l502_stream.o (.sdram_noinit 0x0000000000000000 0xc00000 *(.sdram_noinit, .sdram_noinit.*) .sdram_noinit 0x0000000000000000 0xc00000 build/release/objs/l502_stream.o ) и l502_user_process.o (.sdram_noinit 0x00000000ff802b80 0x9c40 .sdram_noinit 0x00000000ff802b80 0x9c40 build/release/objs/l502_user_process.o ). Полчему запись о stream.o содержит 3 строки, а user_process.o --только 2?
|
||
|
||
## ChatGPT said:
|
||
|
||
Отличный анализ, вы уже сделали именно те шаги, которые и нужно было сделать: проверили `.o`, проверили `.map` и убедились, что атрибут действительно работает на уровне компилятора.
|
||
Давайте разберём ваши наблюдения по порядку и ответим на главный вопрос:
|
||
|
||
> **Почему в .map запись о l502_stream.o содержит 3 строки, а у l502_user_process.o — только 2? И что это значит?**
|
||
|
||
---
|
||
|
||
## 🔹 Как линкер показывает секции в .map
|
||
|
||
Когда вы даёте `-Map`, линкер выводит итоговые секции в таком виде (пример):
|
||
|
||
arduino
|
||
|
||
`.sdram_noinit 0x0000000000000000 0xc00000 *(.sdram_noinit, .sdram_noinit.*) .sdram_noinit 0x0000000000000000 0xc00000 build/release/objs/l502_stream.o`
|
||
|
||
Это значит:
|
||
|
||
1. **Первая строка:** заголовок итоговой секции `.sdram_noinit` в ELF.
|
||
|
||
|
||
- `.sdram_noinit 0x0000000000000000 0xc00000`
|
||
|
||
> Адрес начала и общий размер.
|
||
|
||
- **Вторая строка:** список входных секций, которые собираются в эту итоговую секцию.
|
||
|
||
markdown
|
||
|
||
|
||
- `*(.sdram_noinit, .sdram_noinit.*)`
|
||
|
||
- **Третья и далее строки:** конкретные файлы/объекты, из которых были взяты данные в эту секцию.
|
||
|
||
arduino
|
||
|
||
|
||
1. `.sdram_noinit 0x0000000000000000 0xc00000 build/release/objs/l502_stream.o`
|
||
|
||
|
||
👉 **Если есть несколько файлов, которые вносят свой вклад в эту секцию, то будет несколько таких строк.**
|
||
|
||
---
|
||
|
||
## 🔹 Почему у вас две записи в map
|
||
|
||
Вы приводите:
|
||
|
||
arduino
|
||
|
||
`из l502_stream.o: .sdram_noinit 0x0000000000000000 0xc00000 *(.sdram_noinit, .sdram_noinit.*) .sdram_noinit 0x0000000000000000 0xc00000 build/release/objs/l502_stream.o`
|
||
|
||
и
|
||
|
||
arduino
|
||
|
||
`из l502_user_process.o: .sdram_noinit 0x00000000ff802b80 0x9c40 .sdram_noinit 0x00000000ff802b80 0x9c40 build/release/objs/l502_user_process.o`
|
||
|
||
Разница в том, что линкер вывел **заголовок секции и запись файла** — но _не вывел строку `*(.sdram_noinit, .sdram_noinit.*)`_ между ними.
|
||
На самом деле в большинстве реализаций линкера эта вторая строка (`*(...)`) выводится **один раз — для первого блока секции**. Для последующих частей той же секции (которые находятся по другим адресам в памяти) линкер уже не повторяет список шаблонов входных секций, а сразу пишет, откуда взято.
|
||
|
||
---
|
||
|
||
## 🔹 Почему разные адреса у stream.o и user_process.o
|
||
|
||
Вот самое интересное:
|
||
|
||
- `l502_stream.o` попал в `.sdram_noinit` с адреса **0x0000000000000000**, размер **0xc00000** (12 МБ),
|
||
|
||
- `l502_user_process.o` попал в `.sdram_noinit` с адреса **0xff802b80**, размер **0x9c40** (≈40 КБ).
|
||
|
||
|
||
Это означает, что **у вас в линкере определено несколько регионов памяти**, и `.sdram_noinit` была назначена не только в `MEM_SDRAM`, а ещё куда‑то.
|
||
Или же, что‑то более вероятное: **адрес 0xFF802B80 — это как раз L1‑память (MEM_L1_DATA_A)**, а не SDRAM.
|
||
То есть ваш `TX_buff` из `l502_user_process.o` всё ещё размещается не в том регионе.
|
||
|
||
---
|
||
|
||
## 🔹 Почему это происходит
|
||
|
||
В линкер‑скрипте обычно есть такие строки:
|
||
|
||
ld
|
||
|
||
`MEM_L1_DATA_A : ORIGIN = 0xff800000, LENGTH = 128K MEM_SDRAM : ORIGIN = 0x00000000, LENGTH = 32M SECTIONS { .sdram_noinit (NOLOAD) : { *(.sdram_noinit .sdram_noinit.*) } > MEM_SDRAM }`
|
||
|
||
Если `TX_buff` попал в `.sdram_noinit`, то **он обязан быть в MEM_SDRAM**.
|
||
Но судя по map:
|
||
|
||
arduino
|
||
|
||
`.sdram_noinit 0x00000000ff802b80 0x9c40 build/release/objs/l502_user_process.o`
|
||
|
||
— он всё-таки оказался в **адресном пространстве L1** (начало L1 обычно 0xFF800000).
|
||
|
||
👉 Значит, линкер‑скрипт, который используется при сборке вашего модуля, **назначает `.sdram_noinit` в L1**, или вы подключаете не тот `.ld`.
|
||
|
||
---
|
||
|
||
## 🔧 Что проверить
|
||
|
||
1. **Убедитесь, что используется нужный .ld**
|
||
В проекте может быть несколько линкер‑скриптов: для внутренней памяти и для SDRAM.
|
||
Проверьте, какой именно подключён при сборке.
|
||
|
||
2. **Проверьте MEM_SDRAM в .ld**
|
||
Он должен указывать на 0x00000000 и иметь размер 32 МБ (как вы писали).
|
||
Убедитесь, что `.sdram_noinit` в самом низу `.ld` именно в MEM_SDRAM:
|
||
|
||
ld
|
||
|
||
|
||
- `.sdram_noinit (NOLOAD) : { *(.sdram_noinit .sdram_noinit.*) } > MEM_SDRAM`
|
||
|
||
- **Проверьте, нет ли ещё одного описания `.sdram_noinit`**
|
||
Иногда в разных `MEMORY` блоках есть разные варианты:
|
||
|
||
ld
|
||
|
||
|
||
1. `.sdram_noinit (NOLOAD) : { ... } > MEM_L1_DATA_A`
|
||
|
||
Тогда при сборке нужно исключить ненужный.
|
||
|
||
|
||
---
|
||
|
||
## 🔍 Как отследить путь полностью
|
||
|
||
✅ **readelf / objdump** по `.o` → видите, что секция есть и называется правильно.
|
||
✅ **.map после линковки** → видите, в какой регион памяти попала секция.
|
||
|
||
**Сравнивая:**
|
||
|
||
- `f_sport_in_buf` — лежит в `.sdram_noinit` по адресу `0x00000000...` (SDRAM).
|
||
|
||
- `TX_buff` — лежит в `.sdram_noinit` по адресу `0xFF802B80` (L1).
|
||
|
||
|
||
👉 значит, `.ld`, который у вас собран, для `l502_user_process.o` (или всего проекта) **назначает `.sdram_noinit` не туда, куда вы думаете**.
|
||
|
||
---
|
||
|
||
## ✨ Что делать
|
||
|
||
✅ Проверьте, что при сборке берётся тот же `.ld`, что и для `l502_stream.o`.
|
||
✅ Убедитесь, что в `.ld` секция `.sdram_noinit` **одна** и указывает именно на `MEM_SDRAM`.
|
||
✅ Если всё правильно, а в `.map` всё равно `0xFF80...`, значит подключается не тот линкер‑скрипт или где‑то есть переопределение.
|
||
|
||
---
|
||
|
||
📌 **Вывод по вашим данным:**
|
||
|
||
🔸 Атрибут на этапе компиляции срабатывает — `.o` связывается с секцией `.sdram_noinit`.
|
||
🔸 На этапе линковки для вашего модуля `.sdram_noinit` оказывается назначенной в регион по адресу `0xFF802B80`, то есть в L1, а не в SDRAM.
|
||
🔸 Нужно проверить и привести к единому виду `.ld` / `MEMORY` блоки, чтобы `.sdram_noinit` была одна и указывала на `MEM_SDRAM`.
|
||
|
||
💡 Если хотите — пришлите фрагменты вашего `.ld` с блоками `MEMORY` и `SECTIONS`, я помогу поправить так, чтобы обе переменные оказались в SDRAM. 🚀
|
||
|
||
## You said:
|
||
|
||
1) Есть только один .ld файл. В нем только одно определение MEM_SDRAM:В MEMORY { MEM_L1_CODE : ORIGIN = 0xFFA00000, LENGTH = 0xc000 MEM_L1_CODE_CACHE : ORIGIN = 0xFFA10000, LENGTH = 0x4000 MEM_L1_SCRATCH : ORIGIN = 0xFFB00000, LENGTH = 0x1000 MEM_L1_DATA_B : ORIGIN = 0xFF900000, LENGTH = 0x8000 MEM_L1_DATA_A : ORIGIN = 0xFF800000, LENGTH = 0x8000 MEM_SDRAM : ORIGIN = 0x00000000, LENGTH = 32 * 1024 * 1024 }. Интересно, что в .map сначала упоминается user_process.o (без *(.sdram_noinit, .sdram_noinit.*)), затем stream.o. Последная входная секция, которая упоминается до user_process.o -- это *(.data .data.* .gnu.linkonce.d.*), которая в .ld помещается в MEM_L1_DATA_A. Адрес user_process.o (0x00000000ff802b80) входит в диапазон L1_DATA_A, ошибка компилятора тоже говорит о переполнении L1_DATA_A. Может неправильный порядок -- это и есть причина ошибки?
|
||
|
||
## ChatGPT said:
|
||
|
||
Да! 👏 Вы очень точно подметили ключевой момент:
|
||
|
||
👉 **Судя по вашему `.map`, секция из `l502_user_process.o` (с `TX_buff`) почему‑то идёт не в блок `.sdram_noinit`, а встраивается сразу после `.data` в L1_DATA_A.**
|
||
|
||
А то, что вы видите в `map`:
|
||
|
||
kotlin
|
||
|
||
`... *(.data .data.* .gnu.linkonce.d.*) -> MEM_L1_DATA_A .sdram_noinit 0x00000000ff802b80 ... build/release/objs/l502_user_process.o`
|
||
|
||
означает буквально следующее:
|
||
**линкер воспринимает `.sdram_noinit` из `l502_user_process.o` как часть текущей выходной секции (.data), потому что определение `.sdram_noinit` в вашем .ld находится ниже в скрипте или не применяется к этому объекту.**
|
||
|
||
---
|
||
|
||
### 📌 Почему у `l502_stream.o` всё нормально
|
||
|
||
У него в `map` видно красивый блок:
|
||
|
||
arduino
|
||
|
||
`.sdram_noinit 0x0000000000000000 ... build/release/objs/l502_stream.o`
|
||
|
||
с шаблоном:
|
||
|
||
markdown
|
||
|
||
`*(.sdram_noinit, .sdram_noinit.*)`
|
||
|
||
и назначено в MEM_SDRAM.
|
||
|
||
---
|
||
|
||
### 📌 Что произошло с `l502_user_process.o`
|
||
|
||
- Линкер начал собирать `.data` (L1_DATA_A) по шаблону `*(.data .data.* .gnu.linkonce.d.*)`.
|
||
|
||
- Внутри `l502_user_process.o` почему‑то секция с `TX_buff` **не называется** `.sdram_noinit`, а попала в `.data` (поэтому адрес 0xFF802B80, и отчёт о переполнении L1_DATA_A).
|
||
|
||
|
||
То, что вы наблюдаете в map:
|
||
|
||
arduino
|
||
|
||
`.sdram_noinit 0x00000000ff802b80 ... build/release/objs/l502_user_process.o`
|
||
|
||
показывает имя, но не факт, что оно идёт через блок `.sdram_noinit` вашего .ld.
|
||
На самом деле линкер может присвоить выходной секции `.data` имя любой из входных секций, если это первая секция в списке входных секций объекта.
|
||
|
||
> **Признак:** перед этой записью нет строки `*(.sdram_noinit .sdram_noinit.*)`, значит, она не обрабатывается как отдельная секция, а вливается в предыдущий выходной блок.
|
||
|
||
---
|
||
|
||
### 📐 Как должно быть
|
||
|
||
В .ld должно быть:
|
||
|
||
ld
|
||
|
||
`SECTIONS { /* ... data, bss etc ... */ .sdram_noinit (NOLOAD) : { *(.sdram_noinit .sdram_noinit.*) } > MEM_SDRAM }`
|
||
|
||
И **эта секция должна находиться в .ld выше любых catch‑all шаблонов типа `*(.data*)`, если вы хотите, чтобы линкер сначала выбрал её.**
|
||
На практике порядок в SECTIONS важен: линкер идёт сверху вниз.
|
||
|
||
---
|
||
|
||
### ✨ Почему порядок важен
|
||
|
||
Линкер проходит по объектным файлам и ищет секции. Когда встречает секцию из объекта, он ищет первое правило в `SECTIONS { … }`, подходящее по имени. Если первым идёт `.data`, то секция (даже с именем `.sdram_noinit`) может попасть туда, если имя не совпадает идеально или если по каким‑то причинам GCC сгенерировал другую секцию.
|
||
|
||
**Ваши симптомы:**
|
||
|
||
- `f_sport_in_buf` попадает куда надо.
|
||
|
||
- `TX_buff` попадает в `.data`.
|
||
|
||
|
||
**Возможные причины:**
|
||
|
||
1. Атрибут секции не применяется (но вы проверили `.o`, он применяется).
|
||
|
||
2. В линкер‑скрипте порядок секций такой, что `.data` «перехватывает» ваш буфер.
|
||
|
||
|
||
---
|
||
|
||
### 🔧 Что сделать
|
||
|
||
✅ **Переместите определение `.sdram_noinit` выше блока `.data` в .ld.**
|
||
Например, так:
|
||
|
||
ld
|
||
|
||
`SECTIONS { .sdram_noinit (NOLOAD) : { *(.sdram_noinit .sdram_noinit.*) } > MEM_SDRAM .data : { *(.data .data.* .gnu.linkonce.d.*) } > MEM_L1_DATA_A /* ... дальше остальные ... */ }`
|
||
|
||
✅ **Либо явно проверьте имя секции в .o:**
|
||
|
||
bash
|
||
|
||
`bfin-elf-objdump -h build/release/objs/l502_user_process.o | grep sdram`
|
||
|
||
Если там секция называется не `.sdram_noinit`, а например `.data.sdram_noinit` — добавьте такой шаблон в ваш .ld:
|
||
|
||
ld
|
||
|
||
`*(.sdram_noinit .sdram_noinit.* .data.sdram_noinit*)`
|
||
|
||
✅ **Пересоберите и снова посмотрите map.**
|
||
Вы должны увидеть, что теперь обе переменные (`f_sport_in_buf` и `TX_buff`) отображаются под одним заголовком `.sdram_noinit` с указанием MEM_SDRAM.
|
||
|
||
---
|
||
|
||
### 💡 Краткий чек-лист
|
||
|
||
- Проверить точное имя секции в `objdump -h`.
|
||
|
||
- Убедиться, что шаблон в .ld совпадает.
|
||
|
||
- Переместить блок `.sdram_noinit` выше `.data`.
|
||
|
||
- Пересобрать и проверить map.
|
||
|
||
|
||
---
|
||
|
||
✉️ **Напишите, если хотите, я помогу поправить ваш .ld** — можете прислать кусок SECTIONS, я покажу, как переставить, чтобы `TX_buff` гарантированно ушёл в SDRAM, как и `f_sport_in_buf`. 🚀🔥
|
||
|
||
## You said:
|
||
|
||
1. В проекте нашелся только один .ld файл, он же единственный .ld в makefile. 2. Шаблон точно совпадает (сделал ctrl-c из .ld в определение: static volatile uint32_t TX_buff[10000] __attribute__((section(".sdram_noinit"))); ). 3. описание MEM_SDRAM в .ld точно только одно. Когда я переместил его в самое начало SECTIONS, .map слегка изменился: user_process.o остался в .data и не изменился, но запись streams.o перенеслась ближе к началу .map и стала такой: .sdram_noinit 0x0000000000000000 0xc00000 *(.sdram_noinit, .sdram_noinit.*) .sdram_noinit 0x0000000000000000 0xc00000 build/release/objs/l502_stream.o 0x0000000000000000 PROVIDE (__executable_start, 0x0) 0x0000000000000000 . = 0x0 4.Если нет идей, что попробуй поискать решение проблемы в интернете.
|
||
|
||
## ChatGPT said:
|
||
|
||
Давайте шаг за шагом сузим зону поиска и выстроим чёткий диагноз — причем без «гугления», а чисто по вашим оbject и map-файлам.
|
||
|
||
---
|
||
|
||
## 1. Убедиться, что секция у переменной именно “.sdram_noinit”
|
||
|
||
Войдите в каталог с вашими объектниками и запустите:
|
||
|
||
bash
|
||
|
||
`bfin-elf-objdump -h build/release/objs/l502_user_process.o | grep -A2 sdram_noinit`
|
||
|
||
В выводе вы должны увидеть что‑то вроде
|
||
|
||
css
|
||
|
||
`[ N] .sdram_noinit 00009c40 ... CONTENTS, ALLOC, LOAD, READONLY`
|
||
|
||
Если вместо `CONTENTS, ALLOC, LOAD` вы видите `ALLOC, LOAD, DATA`, значит это **.data**, и атрибут не сработал.
|
||
|
||
Если же там **правильно** `.sdram_noinit/PROGBITS/NOLOAD`, значит дальше смотрим на линковку.
|
||
|
||
---
|
||
|
||
## 2. Проверить, не «съедается» ли наша секция gc-sections
|
||
|
||
Если вы компилируете с опциями
|
||
|
||
diff
|
||
|
||
`-fdata-sections -ffunction-sections \ -Wl,--gc-sections`
|
||
|
||
либо в Makefile включён `--gc-sections`, то линкер _может_ выкинуть вашу секцию, если сочтёт её «неиспользуемой».
|
||
В этом случае она не попадёт в итоговый ELF вообще, а затем «подхватится» более ранней секцией (обычно `.data`).
|
||
|
||
**Решение**: в вашем `.ld` оберните шаблон в `KEEP`, например:
|
||
|
||
ld
|
||
|
||
`.sdram_noinit (NOLOAD) : { KEEP(*(.sdram_noinit .sdram_noinit.*)) } > MEM_SDRAM`
|
||
|
||
Это гарантирует, что даже «неиспользуемая» .sdram_noinit останется на месте.
|
||
|
||
---
|
||
|
||
## 3. Уточнить, как именно линкер видит вашу секцию
|
||
|
||
После сборки возьмите итоговый ELF (или `.elf`) и выполните:
|
||
|
||
bash
|
||
|
||
`bfin-elf-readelf -S application.elf | grep -A1 sdram_noinit`
|
||
|
||
Вы должны получить:
|
||
|
||
arduino
|
||
|
||
`[N] .sdram_noinit <addr=0x00000000> <size=...> ... MEM_SDRAM`
|
||
|
||
А если адрес ≈ `0xFF80xxxx`, значит линкер всё ещё класть .sdram_noinit в L1. В таком случае:
|
||
|
||
- либо вы подключаете не тот `.ld`,
|
||
|
||
- либо ваш `MEMORY` блок для SDRAM не соответствует тому, что используется.
|
||
|
||
|
||
Проверьте в выводе `readelf -S` зоны памяти (столбец «Address»): убедитесь, что **SEGMENT** с `.sdram_noinit` стоит строго в диапазоне `0x00000000–0x01FFFFFF`.
|
||
|
||
---
|
||
|
||
## 4. Диагностика .map
|
||
|
||
В вашем `output.map`, сразу после записи `*(.data .data.* …)`, появится первая секция, которую линкер не распознал как отдельную — и он начинает сливать всё, что не попало выше, в эту же область.
|
||
|
||
Чтобы .sdram_noinit точно врезалась раньше:
|
||
|
||
1. **Перенесите** блок `.sdram_noinit` в `.ld` **сразу после** раздела `.text`, до всех data/bss‑блоков.
|
||
|
||
2. Убедитесь, что `.data` определён где‑то **ниже**, и шаблон его не «перекрывает» дикими масками.
|
||
|
||
3. Используйте `KEEP` (см. пункт 2).
|
||
|
||
|
||
---
|
||
|
||
## 5. Итоговый образец фрагмента SECTIONS
|
||
|
||
ld
|
||
|
||
`SECTIONS { /* … код … */ /* 1) Секция кода (пример) */ .text : { *(.text .text.*) } > MEM_L1_CODE /* 2) FIRST — SDRAM_NOINIT, до любой .data */ .sdram_noinit (NOLOAD) : { KEEP(*(.sdram_noinit .sdram_noinit.*)) } > MEM_SDRAM /* 3) Дальше идут обычные data/bss в L1 */ .data : { *(.data .data.* .gnu.linkonce.d.*) } > MEM_L1_DATA_A .bss : { *(.bss .bss.* .gnu.linkonce.b.*) } > MEM_L1_DATA_A /* … остальное … */ }`
|
||
|
||
---
|
||
|
||
### Подведём итог
|
||
|
||
1. Проверьте `objdump -h` на точное имя и атрибут вашей секции в **.o**.
|
||
|
||
2. Добавьте `KEEP` вокруг `*(.sdram_noinit .sdram_noinit.*)` на всякий случай.
|
||
|
||
3. Убедитесь через `readelf -S` итогового ELF, что `.sdram_noinit` идёт в адреса `0x00000000…`.
|
||
|
||
4. Переместите блок **выше** любых data/bss‑шаблонов.
|
||
|
||
|
||
Как только линкер увидит вашу секцию **до** .data и не «сольёт» её в L1, `TX_buff` гарантированно окажется в SDRAM, и переполнение L1 исчезнет.
|
||
|
||
## You said:
|
||
|
||
1. В user_process.o всё хорошо:7 .sdram_noinit 00009c40 00000000 00000000 00000c6c 2**2 CONTENTS, ALLOC, LOAD, DATA 2.KEEP не помогло. 3. компилятор падеат до того, как начинает генерировать elf. Однако bfin-elf-readelf -S user_process.o дал такой вывод: [12] .sdram_noinit PROGBITS 00000000 000c6c 009c40 00 WA 0 0 4, хотя MEM_L1_DATA_A всё ещё переполнен, а в .map массив относится к секции .data. Однако, перенос объявления TX_buff в stream.c (где объявлялся и f_sport_in_buf), решает проблему. Может быть stream.c и user_process.c по-разному подхватываются компилятором? А между обработкой этих двух файлов что-то ломается?
|
||
|
||
## ChatGPT said:
|
||
|
||
Похоже, что проблема не в том, что линкер «забыл» или «перехватил» вашу секцию — он её просто нигде не встретил под именем `.sdram_noinit` в момент, когда шёл по блокам `SECTIONS{…}`. А всё потому, что для `l502_user_process.o` фактически **ни один** из ваших шаблонов в секции `.sdram_noinit` не сработал, и тогда линкер упал обратно на шаблон `*(.data .data.* .gnu.linkonce.d.*)` и запихнул буфер в L1.
|
||
|
||
Давайте коротко по‑пунктам, как убедиться и устранить:
|
||
|
||
---
|
||
|
||
## 1. Сравните реальные команды компиляции
|
||
|
||
`stream.c` и `user_process.c` **должны** компилироваться с одними и теми же флагами.
|
||
Выполните в каталоге сборки:
|
||
|
||
bash
|
||
|
||
`grep "$(basename stream.c .c)" -R build/compile_commands.json grep "$(basename user_process.c .c)" -R build/compile_commands.json`
|
||
|
||
или, если нет `compile_commands.json`, найдите в логе Makefile строки:
|
||
|
||
bash
|
||
|
||
`make VERBOSE=1 ... user_process.o make VERBOSE=1 ... l502_stream.o`
|
||
|
||
**Сравните** CFLAGS и особенно опции вида `-fdata-sections`, `-fsection-anchors`, `-march=…` и пр.
|
||
Если они отличаются, вы нашли виновника: в одном файле атрибут применяется, а в другом — нет.
|
||
|
||
---
|
||
|
||
## 2. Дважды убедитесь в правильности атрибута
|
||
|
||
Для GCC атрибут можно писать **до** или **после** объявления переменной, но **он должен стоять рядом с именем**:
|
||
|
||
Правильно:
|
||
|
||
c
|
||
|
||
`static volatile uint32_t TX_buff[100000] __attribute__((section(".sdram_noinit")));`
|
||
|
||
или
|
||
|
||
c
|
||
|
||
`__attribute__((section(".sdram_noinit"))) static volatile uint32_t TX_buff[100000];`
|
||
|
||
**Неправильно**:
|
||
|
||
c
|
||
|
||
`#include "l502_sdram_noinit.h" static volatile uint32_t TX_buff[100000]; // <- без явного атрибута`
|
||
|
||
Проверьте, что в `user_process.c` вы именно так и написали.
|
||
|
||
---
|
||
|
||
## 3. Проверьте входные секции в объектнике
|
||
|
||
Выполните:
|
||
|
||
bash
|
||
|
||
`bfin-elf-objdump -h build/release/objs/l502_user_process.o | grep -A2 "\.sdram_noinit"`
|
||
|
||
Вы **должны** увидеть:
|
||
|
||
csharp
|
||
|
||
`[12] .sdram_noinit PROGBITS 00000000 000c6c 009c40 00 WA 0 0 4`
|
||
|
||
Если этой секции нет — значит атрибут вовсе не применился.
|
||
Если она есть — он применился, и смотрите п.1 и п.4.
|
||
|
||
---
|
||
|
||
## 4. Проверьте, что вы действительно линкуете тот же `.ld`
|
||
|
||
Очень часто в Makefile бывает две цели — одна «для прошивки» (с `linker_flash.ld`), другая «для отладки» (с `linker_debug.ld`).
|
||
Убедитесь, что **и stream.o, и user_process.o** попадают в **одну и ту же** финальную линковку с **одним и тем же** `.ld`.
|
||
|
||
---
|
||
|
||
## 5. Временно запретите `--gc-sections`
|
||
|
||
Если у вас в `LDFLAGS` есть `--gc-sections`, попробуйте её убрать или хотя бы окружить `.sdram_noinit` в скрипте:
|
||
|
||
ld
|
||
|
||
`.sdram_noinit (NOLOAD) : { KEEP(*(.sdram_noinit .sdram_noinit.*)) } > MEM_SDRAM`
|
||
|
||
Чтобы линкер не «считал» вашу секцию «неиспользуемой» и не выкидывал её до того, как доберётся до неё.
|
||
|
||
---
|
||
|
||
### Резюме
|
||
|
||
1. **Убедитесь**, что оба файла компилируются с одинаковыми флагами.
|
||
|
||
2. **Проверьте**, что в `user_process.o` действительно есть входная секция `.sdram_noinit`.
|
||
|
||
3. **Проверьте**, что используется именно тот `.ld`, где эта секция > MEM_SDRAM.
|
||
|
||
4. **Обновите** скрипт, обернув KEEP вокруг шаблона, и поставьте его **выше** любых `.data` в `SECTIONS`.
|
||
|
||
5. **Пересоберите**, и в `output.map` обе переменные будут в `.sdram_noinit` с адресом в `0x00000000…`.
|
||
|
||
|
||
Как только вы выстроите эту цепочку от C‑кода через .o до .map, станет очевидно, где «теряется» ваш атрибут, и его точно получится устранить. Если что-то из этих шагов выдаст неожиданный результат — расскажите, и мы углубимся именно в эту точку!
|
||
|
||
|
||
|
||
ChatGPT can make mistakes. Check important info. See Cookie Preferences. |