Имитация FSMC
Т.к. для подключения более приличного экранчика с параллельной шиной данных мне понадобится сравнительно шустро работать с GPIO, а у меня нет девборд с "жирными" STM32, где есть FSMC, нужно как-то имитировать эту самую FSMC. Я попробовал два способа: с таймером и с DMA (код поместил в репозиторий stm32samples, директории GPIO_TIM и DMA_GPIO).
Итак, с таймером все просто. Настраиваем таймер:
Заводим локальный буфер, куда сбрасываем данные, передаваемые на ноги GPIO (я использовал младшие 8 бит PA) и перед отправлением заполняем его и выставляем счетчики:
Ну, а сам таймер отправляет на ноги GPIO очередную порцию данных:
С прямым доступом к памяти немного по-другому. Сначала настраиваем таймер и ПДП:
Далее для передачи данных копируем их в локальный буфер и пинаем таймер с ПДП:
Обработчик прерывания или пишет "Фигвамушки", если возникает ошибка (а у меня поначалу и были ошибки, когда я пытался восьмибитный буфер в 16-битный регистр запихнуть, надо быть внимательней), или же выставляет флаг окончания передачи и отрубает уже ненужные функции:
Вчера я часа 3 вечером убил, да еще и сегодня где-то с час, пока наконец оживил ПДП. А все было из-за того, что я вместо ((uint32_t)&GPIOA_ODR) в DMAGPIO_TARGADDR запихнул непосредственно GPIOA_ODR (т.е. значение битов в порту).
Результаты неутешительные: хоть и есть режим GPIO под гордым названием GPIO_MODE_OUTPUT_50_MHZ, на деле все не так. Наибольшая скорость, которую я достиг с ПДП, составляла около 6.25МГц (т.к. у меня китайский клон Saleae Logick, ему я на таких скоростях не поверил, что, кстати, сделал правильно: брехал он изрядно, а посмотрел на осциллографе). У пинания портов GPIO в прерывании таймера по понятным причинам (обработчик прерывания длится дольше периода таймера) скорость еще меньше: не выше 1.1МГц.
Таким образом, передавать данные по параллельной шине с STM32F103 на скоростях выше 6МГц можно и не рассчитывать!
Итак, с таймером все просто. Настраиваем таймер:
void timgpio_init(){
// init TIM2 & DMA1ch2 (TIM2UP)
rcc_periph_clock_enable(RCC_TIM2);
rcc_periph_clock_enable(RCC_DMA1);
timer_reset(TIM2);
// timer have frequency of 1MHz
timer_set_mode(TIM2, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
// 72MHz main freq, 2MHz for timer
TIM2_PSC = 0;
TIM2_ARR = 35;
TIM2_DIER = TIM_DIER_UDE | TIM_DIER_UIE;
nvic_enable_irq(NVIC_TIM2_IRQ);
}
Заводим локальный буфер, куда сбрасываем данные, передаваемые на ноги GPIO (я использовал младшие 8 бит PA) и перед отправлением заполняем его и выставляем счетчики:
void timgpio_transfer(uint8_t *databuf, uint32_t length){
transfer_complete = 0;
memcpy(addr, databuf, length);
len = length;
curidx = 0;
TIM2_CR1 |= TIM_CR1_CEN; // run timer
}
Ну, а сам таймер отправляет на ноги GPIO очередную порцию данных:
void tim2_isr(){
if(TIM2_SR & TIM_SR_UIF){ // update interrupt
GPIOA_ODR = addr[curidx];
if(++curidx >= len){
TIM2_CR1 &= ~TIM_CR1_CEN;
transfer_complete = 1;
}
TIM2_SR = 0;
}
}
С прямым доступом к памяти немного по-другому. Сначала настраиваем таймер и ПДП:
void dmagpio_init(){
// init TIM2 & DMA1ch2 (TIM2UP)
rcc_periph_clock_enable(RCC_TIM2);
rcc_periph_clock_enable(RCC_DMA1);
timer_reset(TIM2);
// timer have frequency of 1MHz
timer_set_mode(TIM2, TIM_CR1_CKD_CK_INT, TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
// 72MHz div 18 = 4MHz
TIM2_PSC = 0; // prescaler is (div - 1)
TIM2_ARR = 1; // 36MHz (6.25)
TIM2_DIER = TIM_DIER_UDE;// | TIM_DIER_UIE;
dma_channel_reset(DMA1, DMA_CHANNEL2);
// mem2mem, medium prio, 8bits, memory increment, read from mem, transfer complete en
DMA1_CCR2 = DMA_CCR_PL_MEDIUM | DMA_CCR_MSIZE_16BIT |
DMA_CCR_PSIZE_16BIT | DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE | DMA_CCR_TEIE ;
nvic_enable_irq(NVIC_DMA1_CHANNEL2_IRQ);
// target address:
DMA1_CPAR2 = DMAGPIO_TARGADDR;
DMA1_CMAR2 = (uint32_t) gpiobuff;
}
Далее для передачи данных копируем их в локальный буфер и пинаем таймер с ПДП:
void dmagpio_transfer(uint8_t *databuf, uint32_t length){
while(DMA1_CCR2 & DMA_CCR_EN);
transfer_complete = 0;
DMA1_IFCR = 0xff00; // clear all flags for ch2
// buffer length
DMA1_CNDTR2 = length;
uint32_t i;
for(i = 0; i < length; ++i) gpiobuff[i] = databuf[i];
TIM2_CR1 |= TIM_CR1_CEN; // run timer
DMA1_CCR2 |= DMA_CCR_EN;
}
Обработчик прерывания или пишет "Фигвамушки", если возникает ошибка (а у меня поначалу и были ошибки, когда я пытался восьмибитный буфер в 16-битный регистр запихнуть, надо быть внимательней), или же выставляет флаг окончания передачи и отрубает уже ненужные функции:
void dma1_channel2_isr(){
if(DMA1_ISR & DMA_ISR_TCIF2){
transfer_complete = 1;
// stop timer & turn off DMA
TIM2_CR1 &= ~TIM_CR1_CEN;
DMA1_CCR2 &= ~DMA_CCR_EN;
DMA1_IFCR = DMA_IFCR_CTCIF2; // clear flag
}else if(DMA1_ISR & DMA_ISR_TEIF2){
P("Error\n");
DMA1_IFCR = DMA_IFCR_CTEIF2;
TIM2_CR1 &= ~TIM_CR1_CEN;
DMA1_CCR2 &= ~DMA_CCR_EN;
}
}
Вчера я часа 3 вечером убил, да еще и сегодня где-то с час, пока наконец оживил ПДП. А все было из-за того, что я вместо ((uint32_t)&GPIOA_ODR) в DMAGPIO_TARGADDR запихнул непосредственно GPIOA_ODR (т.е. значение битов в порту).
Результаты неутешительные: хоть и есть режим GPIO под гордым названием GPIO_MODE_OUTPUT_50_MHZ, на деле все не так. Наибольшая скорость, которую я достиг с ПДП, составляла около 6.25МГц (т.к. у меня китайский клон Saleae Logick, ему я на таких скоростях не поверил, что, кстати, сделал правильно: брехал он изрядно, а посмотрел на осциллографе). У пинания портов GPIO в прерывании таймера по понятным причинам (обработчик прерывания длится дольше периода таймера) скорость еще меньше: не выше 1.1МГц.
Таким образом, передавать данные по параллельной шине с STM32F103 на скоростях выше 6МГц можно и не рассчитывать!
no subject
Для мелких просто и SPI хватает. А для крупных уже процессор нужен помощнее, чтобы графику генерировать...
no subject
У него то ли ILI9325, то ли ILI9328 (не знаю еще: драйвер под экранчиком, раскурочивать не хочу). Хоть драйвер и поддерживает SPI, наружу вывели только параллельный 8-битный интерфейс, а SPI только от тачскрина и флешки.
Да и всяко быстрей будет по параллельному интерфейсу писать, нежели по SPI (а там если провод длинный, то вообще фигня будет на высоких скоростях).
no subject
Я правда купил без платки такие дисплеи, но зато по цене 1$/шт :)
Про SPI - не все так плохо. Ради интереса пробовал писать на 36 МГц по проводам 20 см длинной. Работает, без сбоев.
no subject
no subject
1. Первоначальная инициализация.
2. Нарисовать на дисплей прямоугольную область из памяти.
Остальной код универсальный, и от дисплея мало зависит.
Лично я скопипастил с Arduino и сильно оптимизировал код для своих дисплеев. Но вы наверняка этот код уже встречали в куче проектов так либо иначе модифицированный :) https://github.com/balmerdx/BalmerVNA/tree/master/4code/src/ili
no subject
Противно, что паузы в 150нс приходится вручную вбульбенивать через счетчик циклов DWT_CYCCNT ☹
Ну, да ладно, дальше при пересылке буфера с изображением через ПДП этим всем будет рулить таймер. Правда, придется таймер настроить на шимогенерацию и в прерывании дергать ногу WR или RD (смотря что за действо надо выполнить).
no subject
и так далее. Т.е. номер записанного регистра потом четырехкратно считывается.
Фиг поймешь, что ж за контроллер там ☹
UPD. Фух, я по невнимательности после редактирования пинов все равно писал в порт A командные последовательности вместо порта B…
Идентификатор считал: это ILI 9321, буду дальше мучить[ся].
no subject
no subject
Ну и тест был только текстового режима, в нем как раз проще сразу отсылать символы. А вот в графическом режиме — да, рисуем, потом весь буфер отсылаем.
no subject
UPD: победил. HAL мне нравится всё меньше, надо и правда пробовать libopencm3 + регистры (пробовал запуск таймера переписать с HAL на регистры - стало чуть длиннее, но отпала необходимость вникать в кучу кода в HAL - без этого как-то не получается).
Скорости получились ещё ниже, причём завестись почему-то удаётся только до тактовой 48 MHz.