Главная » Статьи » Программирование в CodeWarrior Development Studio 1.0.0

Работа с TVPE
TVPE - это аппаратный турбо и витерби декодер. TVPE - это модуль MAPLE-B. Последний включает в себя так же FFT, iFFT, DFT, iDFT, CRC. Мы рассмотрим только турбо декодер.

Открытие MAPLE

MAPLE-B получает данные по средством DMA (рис 1). Т.е. перед работой непосредственно с TVPE необходимо сначала настроить общение с MAPLE-B.


cop_dev_handlemaple_dev_handle;//Handle для MAPLE
maple_open_params_t maple_open_params;
cop_dev_open_params_t cop_dev_open_params;

// Open MAPLE device
maple_open_params.error_callback= NULL;
maple_open_params.system_err_num= 0;
maple_open_params.prog_ecc_err_num= 0;
maple_open_params.data_ecc_err_num= 0;
maple_open_params.config_mbus_mmu= TRUE;
maple_open_params.config_sbus_mmu= FALSE;
maple_open_params.mbus_base = (void *)0xFED00000;
maple_open_params.sbus_base = (void *)0xFFF1C000;
maple_open_params.hw_semaphore_addr[0] = NULL;
maple_open_params.hw_semaphore_addr[1] = NULL;
maple_open_params.srio_controller[0] = NULL;
maple_open_params.srio_controller[1] = NULL;
maple_open_params.dev_init_params.initialization_func= MAPLE_INIT_FOR_WIMAX;
maple_open_params.dev_init_params.ucode = MAPLE_UCODE_WIMAX;
maple_open_params.dev_init_params.mbus_base= (void *) 0xFED00000;
maple_open_params.dev_init_params.sbus_base= (void *) 0xFFF1C000;
maple_open_params.dev_init_params.ecc_protect= FALSE;
maple_open_params.dev_init_params.config_write= NULL;
maple_open_params.dev_init_params.config_read = NULL;
maple_open_params.dev_init_params.dev_id= 0;
maple_open_params.dev_init_params.config_param= 0;
maple_open_params.dev_init_params.timer_period = 0;
maple_open_params.dev_init_params.mode = 0;
cop_dev_open_params.dispatch_callback= appDummyDispatchCb;
cop_dev_open_params.error_callback = mapleErrorCb;
cop_dev_open_params.lld_params = &maple_open_params;

Стоит описать что же тут настраивается.
error_callback - Это ссылка на функцию, вызываемую если происходит какая-либо ошибка в MAPLE-B.
mbus_base, sbus_base - адреса шин. Стандартные.
config_mbus_mmu - разрешает использование MBUS.
config_sbus_mmu - при наших настройках включать не надо.
dev_init_params.initialization_func, dev_init_params.ucode - Эти 2 параметра определяют стандарт декодера, который будет использовать система. MAPLE-B поддерживает 4 стандарта. Это 3GLTE, WIMAX, 3GPP, 3GPP2. Мы будем использовать WiMax.
dispatch_callback - возвращаемая функция по завершении работы MAPLE-B. В нашем случае мы пока поставили заглущку.
О настройке всё. Теперь это всё надо записать в MAPLE с помощью следующей строки:

maple_dev_handle = osCopDeviceOpen(MAPLE_DEV_NAME_0, &cop_dev_open_params);

В итоге у нас есть номер нашего устройства - maple_dev_handle, если всё прошло хорошо и 0, если что то пошло не так.

Открытие TVPE

tvpe_turbo_params_t tvpe_turbo_params;
tvpe_open_params_t tvpe_open_params;
cop_dev_open_params_tTVPE_Device;//Handle for TVPE

// Open TVPE device
tvpe_open_params.maple_handle= maple_dev_handle;//Handle нашего открытого MAPLE
tvpe_open_params.maple_pe_bd_priority= MAPLE_PE_BD_RING_H;
tvpe_open_params.maple_pe_num_bd= MAPLE_PE_NUM_BD_RINGS_8;
tvpe_open_params.out_order = DATA_OUT_BYTE_ASCENDING | DATA_OUT_BIT_ASCENDING;
tvpe_open_params.tvpe_turbo_params = &tvpe_turbo_params;
tvpe_open_params.tvpe_viterbi_params= NULL;
tvpe_open_params.hw_sem_id= 0xFF;//заглушка
tvpe_turbo_params.stop_countSTOP_CNT_4;
tvpe_turbo_params.apq_stop_criteria= 0;
tvpe_turbo_params.apq_threshold = 0;
vpe_turbo_params.crc_stop_criteria= 0;
cop_dev_open_params.dispatch_callback= appDummyDispatchCb;//заглушка
cop_dev_open_params.error_callback= mapleErrorCb;
cop_dev_open_params.lld_params= &tvpe_open_params;

В остальных настройках, если необходимо, можете поразбираться сами.
Когда всё заполнено, посылаем настройки командой:

TVPE_Device = osCopDeviceOpen(TVPE_DEV_NAME_0, &cop_dev_open_params);

Открытие канала TVPE

Теперь для работы с TVPE осталось открыть канал по которому мы будем посылать данные.

cop_channel_tTVPE_channel;
maple_tvpe_ch_open_params_tmaple_tvpe_ch_open_params;
cop_ch_open_params_tch_open_params;


// Open TVPE channel
maple_tvpe_ch_open_params.flags = 0;
maple_tvpe_ch_open_params.no_automatic_reap= FALSE;
maple_tvpe_ch_open_params.high_priority= TRUE;
maple_tvpe_ch_open_params.int_enable = TRUE;
maple_tvpe_ch_open_params.int_enable = FALSE;
maple_tvpe_ch_open_params.no_translation= OS_MEM_LOCAL;
maple_tvpe_ch_open_params.channel_location= OS_MEM_LOCAL;
maple_tvpe_ch_open_params.interrupt_type= INT_LINE;
maple_tvpe_ch_open_params.maple_int_trgt= (maple_pe_int_trgt_t)(osGetCoreID() << 4);
maple_tvpe_ch_open_params.int_num(os_hwi_handle)(OS_INT_MAPLE_CH_0 + osGetCoreID());
maple_tvpe_ch_open_params.int_priority= OS_HWI_PRIORITY3;
maple_tvpe_ch_open_params.host_id = 0;
ch_open_params.channel_num= (uint16_t)osGetCoreID();
ch_open_params.num_jobs = TVPE_MAX_NUM_BD_FOR_DISPACTH;
ch_open_params.callback_parameter= (void*)ch_open_params.channel_num;
ch_open_params.channel_num= (uint16_t)osGetCoreID();
ch_open_params.num_jobs= TVPE_MAX_NUM_BD_FOR_DISPACTH;
ch_open_params.callback_parameter= (void*)ch_open_params.channel_num;
ch_open_params.error_callback_parameter = 0;
ch_open_params.heap= OS_MEM_LOCAL;
ch_open_params.lld_params= &maple_tvpe_ch_open_params;

Теперь нам надо передать и эти настройки.

osCopChannelOpen(TVPE_Device, &TVPE_channel, &ch_open_params);

Эта функция возвращает OS_SUCCESS если прошла успешно и NULL если это не так.
Если было получено OS_SUCCESS, следуем дальше.

Когда вся настройка завершена, нам нужно, что бы вызывалась какая-то функция, говорящая, что преобразование завершено. Её можно приписать к TVPE следующим образом:

osCopChannelCtrl(&TVPE_channel, COP_CHANNEL_CALLBACK_SET, appTvpeDispatchCb)

Она так же возвращает OS_SUCCESS, если выполнена успешно.

Теперь, по окончании преобразования будет вызвана функция appTvpeDispatchCb.
На этом настройку можно считать завершённой.

Формат входных данных.

Когда DMA настроено, нужно подготовить данные и отправить их в MAPLE.
На этом этапе нам надо разобраться в каком формате данные посылать в турбо декодер.
Все данные представляют собой 8-ми битные мягкие решения, т.е. в одном байте у нас 1 бит (Почитайте про мягкие решения в интернете). Что бы разобраться с порядком следования данных нужно обратиться к тому как работает кодер.
На входе у кодера сплошной битовый поток. Кодер захватывает данные по 2 бита, условно называемые А и В. Создаёт для них проверочные биты условно названные Y1,Y2,W1W2. Т.е. на выходе данных в 3 раза больше чем на входе. Т.е. скорость потока становится 1/3. Именно эту скорость поддерживает для WIMAX наш декодер. Для выходных данных кодера определяется размер блока, на основе этой цифры и кодируются и в дальнейшем собираются полученные данные. Сборка данных обратно в один поток производится следующим образом: Сначала идут биты А в количестве, равном размеру блока. Потом соответствующие им биты В. За ними идут чередующиеся Y1и Y2. Последними следуют чередующиеся W1 и W2. В итоге А представляет 1/6 от всего блока данных. Размер В тоже равен 1/6. Размер каждого из блоков Y1Y2 и W1W2 равен 1/3. Зная это можно настраивать.
По стандарту данные можно поделить на 4 части: A, B, Y1Y2, W1W2.

Подготовка данных.

Теперь подготовим данные для записи их в работу. Предположим, что у нас есть описанный выше поток входных данных.
#define DATA_SIZE 480
unsigned char DataIn[DATA_SIZE*3];

Для того, чтобы заполнить работу данными, нам надо разбить их на 4 части, упорядоченные по 256 байт.
Определим массив, в котором будут располагаться наши данные:
#define BLOCK_SIZE 15000//Можно взять и поменьше.
uint8_t Data_in_mem[BLOCK_SIZE];
И указатели на данные в нём:
int8_t* TVPE_A;
int8_t* TVPE_B;
int8_t* TVPE_Y1Y2;
int8_t* TVPE_W1W2;

Теперь надо в нашем массиве получить адреса, кратные 256. Это можно сделать по следующему алгоритму:
uint32_t addr = (uint32_t)Data_in_mem;
addr += 255;
TVPE_A &= ~(((uint32_t)256) - 1);

В переменной TVPE_A будет уже адрес, кратный 256.
Аналогично находятся адреса для других данных.
Копируем данные из входного массива в соответствующие области:

memcpy(TVPE_A, DataIn, DATA_SIZE >> 1);

Т.о. необходимо заполнить все 4 массива.
После копирования необходимо обновить кэш следующими строками:
uint32_t cache_hi, cache_lo;
os_status status;

cache_hi = CACHE_OP_HIGH_ADDR(TVPE_A, DATA_SIZE >> 1, ARCH_CACHE_LINE_SIZE);
cache_lo = CACHE_OP_LOW_ADDR(TVPE_A, ARCH_CACHE_LINE_SIZE);

status = osCacheL1L2DataFlush((const void*)cache_lo, (const void*)cache_hi, MMU_SHARED_PID);

Чтобы MAPLE работал с новыми данными.

Настройка работы.

Прежде всего необходимо выделить память до настройки работы. Это делается следующим образом:

jobs_pool = osMemPartCreate(ALIGN_SIZE(sizeof(app_tvpe_job), ALIGNED_4_BYTES),
TVPE_MAX_NUM_BD_FOR_DISPACTH,
jobs_space,
ALIGNED_4_BYTES,
OFFSET_0_BYTES,
(os_mem_part_t *)&jobs_mem_manager,
FALSE);

Здесь стоит остановиться на термине упорядочивание массивов данных в памяти. Это значит, что первый элемент массива имеет адрес, кратный размеру по которому упорядочивают. Т.е. если производится упорядочивание по 256 байт, то последние 8 бит адреса должны быть нулевыми. Обычно это используется для того, чтобы программа могла сама находить начало новых данных.
Как можно видеть в настройке работы, она должна быть упорядочена по 4 байта, т.е. первые 2 бита должны быть нулевыми.
Что входит в настройки: Первый параметр - это размер размер настроек нашей работы, причём уже учитывается упорядоченный вариант. Второй - максимально возможное число одновременных запросов. Третий - Адрес, с которого начинается расположение данных. Четвёртый - упорядочивание. Пятый - смещение. Шестой - Адрес пространства менеджера памяти для инициализации. Седьмой - Флаг, показывающий, что данные распределены между несколькими ядрами.

Для правильной работы надо определить следующие значения:
#define MAPLE_BD_ALIGN_REQUIRMENTALIGNED_256_BYTES
#define MAPLE_MEM_PER_CORE (((PRAM_BD_RING_SIZE - MAPLE_MANAGEMENT) / OS_NUM_OF_CORES) & (~MAPLE_BD_ALIGN_REQUIRMENT))
#define BD_CONSTRAINT_PER_CORE (MAPLE_MEM_PER_CORE / TVPE_BD_SIZE)
#define NUM_JOBS_PER_COREBD_CONSTRAINT_PER_CORE
os_mem_part_t*jobs_pool;//Memory pool for job
uint8_tjobs_space[ALIGN_SIZE((NUM_JOBS_PER_CORE * sizeof(app_tvpe_job)), ALIGNED_4_BYTES)];
uint8_tjobs_mem_manager[MEM_PART_SIZE(TVPE_MAX_NUM_BD_FOR_DISPACTH)];

После того, как выделена память для работы надо определить на неё указатель.

typedef struct
{
cop_job_handle cop_job;
maple_tvpe_job_t tvpe_job;
} app_tvpe_job;

app_tvpe_job *job=NULL;//Job for TVPE

В коде пишем:

job = (app_tvpe_job*)osMemBlockGet(jobs_pool);

Инициализация работы производится следующим образом:
//Инициализируем работу.
job->cop_job.job_id= (cop_job_id)ID;
job->cop_job.device_specific= &job->tvpe_job;
job->cop_job.next = NULL;
job->tvpe_job.buffer_size = 0;
job->tvpe_job.hard_output_addr = (void *)out;
job->tvpe_job.soft_output_addr = NULL;
job->tvpe_job.e_param_ptr = NULL;
job->tvpe_job.inputs[INPUT_BASE_ADDR]= TVPE_A;
job->tvpe_job.inputs[ADDR_OFFSET_0]= TVPE_A;
job->tvpe_job.inputs[ADDR_OFFSET_1]= TVPE_B;
job->tvpe_job.inputs[ADDR_OFFSET_2]= TVPE_Y1Y2;
job->tvpe_job.inputs[ADDR_OFFSET_3]= TVPE_W1W2;
job->tvpe_job.inputs[ADDR_OFFSET_4]= 0;
job->tvpe_job.inputs[ADDR_OFFSET_5]= 0;
job->tvpe_job.first_flags= (uint32_t)(TURBO_JOB | STRUCT_SUB_BLK_INTRLV | MAX_ITER_16 | MIN_ITER_3 | RATE_1_3 );
job->tvpe_job.second_flags= 0;
job->tvpe_job.third_flags= TVPE_HOE;
job->tvpe_job.block_size = input_size;

Прокомментирую настройки:
job_id - нужен, если вы одновременно записываете больше одного задания в MAPLE.
next - сюда записывается ссылка на следующую работу. Собственно выглядит это так: Вы посылаете в MAPLE самую первую работу. MAPLE же смотрит есть ли продолжение, считывая этот параметр. Если параметр не NULL, то MAPLE считывает следующую работу по указанному адресу. И т.д. пока next не будет равен NULL.
hard_output_addr - переменная куда будут записаны наши выходные данные. Так же, как и входные данные она должна быть упорядочена по 256 байт.
inputs - указатель на входные данные. Данные могут читаться либо с INPUT_BASE_ADDR если не определены данные с ADDR_OFFSET. Либо с ADDR_OFFSET.
Самая важная часть - флаги:
first_flags - тут на данный мамент определены следующие параметры:
TURBO_JOB - определяет то, что работа для турбо декодера.
STRUCT_SUB_BLK_INTRLV - определяет способ которым передаются данные в декодер. В данном случае у нас данные с перемежёнными сабблоками и именно такой способ подачи данных используется. При нём данные как раз и разбиваются на 4 части и подаются в ADDR_OFFSET. Если у вас данные без сабблокового перемежителя, то вам надо будет читать техническую документацию и разбираться как подавать данные в вашем случае (точнее в каком виде).
MAX_ITER_16 - Максимальное число итераций.
MIN_ITER_3 - Минимальное число итераций.
RATE_1_3 - Скорость данных. В данном случае можено только RATE_1_3 - 1/3. Это именно та скорость, что описана в разделе формат данных.
TVPE_HOE - включает аппаратные прерывания.
block_size - размер данных. Сюда записывается количество мягких информационных бит. Т.е. размер сабблока *2

Ожидаем пока данные полностью будут переданы в физическую память.

osCacheDataWaitSweep();

Посылаем данные в MAPLE

while (osCopChannelDispatch(&TVPE_channel, &job->cop_job, &num_jobs) != OS_SUCCESS ){}

Отправляем запросы о готовности. Иначе MAPLE может прерывание и не выдать.

while (DecDataComp == 0)
{
osHwiSwiftDisable();
osCopChannelCtrl(&TVPE_channel, MAPLE_TVPE_CMD_RX_POLL, NULL);
osHwiSwiftEnable();
}

DecDataComp - эту переменную мы сделаем равной 1 в функции прерывания, чтобы программа продолжила своё выполнение.

Ну что ж. Данные посланы, обработаны, прерывание сработало. Опишем функцию, которая будет вызвана:

static void appTvpeDispatchCb(void *cop_job, void * error_status)
{
cop_job_handle*job = (cop_job_handle *)cop_job;
maple_tvpe_job_t*tvpe_job;
uint32_tjob_id;


OS_ASSERT_COND(!((uint32_t)error_status & 0x0F000000));

tvpe_job = (maple_tvpe_job_t *)job->device_specific;
job_id = (uint32_t)job->job_id;

maple_read(output_data, tvpe_job->hard_output_addr, Length_Subblock>>2);
osMemBlockFree(jobs_pool, job);
DecDataComp = 1;
}

Собственно на этом всё. Наши данные лежат в переменной out. Она же tvpe_job->hard_output_addr.
Но для полной честности рассмотрим подробнее функцию maple_read. она копирует данные из tvpe_job->hard_output_addr в наш буфер. разумеется буфер должен быть размером больше, чем количество считываемых данных. Причём данные на выходе представлены уже в байтах (биты А и В объединены по алгоритму: А0 В0 А1 В1 А2 В2 и т.д.) и размер считываемых данных вычисляется как размер сабблока/4.
maple_read выглядит следующим образом:

static inline void maple_read(void *dst, const void *src, int size)
{
OS_ASSERT_COMPILER_COND(IS_ALIGNED(src, ARCH_CACHE_LINE_SIZE));

sweep_cache((uint32_t)src, size, CACHE_INVALIDATE);
memcpy(dst, src, (size_t)size);
}

static inline void sweep_cache(uint32_t addr, int size, uint32_t mode)
{
uint32_t cache_hi, cache_lo;
os_status status;
cache_hi = CACHE_OP_HIGH_ADDR(addr, size, ARCH_CACHE_LINE_SIZE);
cache_lo = CACHE_OP_LOW_ADDR(addr, ARCH_CACHE_LINE_SIZE);

status = osCacheL1L2DataSweep((const void*)cache_lo, (const void*)cache_hi, MMU_SHARED_PID, mode);
}

В данной функции данные копируются в наш массив, а затем сбрасывают кэш.
На этом работа TVPE закончена.

Статья получилась большая и может содержать множество опечаток, поэтому может использоваться только в ознакомительных целях. Копировать куски кода из неё настоятельно не рекомендуется. Для этого есть готовый пример работы, который можно найти тут. В данном архиве содержится пример работы с декодером и технические документации на процессор.
Категория: Программирование в CodeWarrior Development Studio 1.0.0 | Добавил: Korvin (25.09.2012)
Просмотров: 1219 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]