eddy_em: (Костерок)
eddy_em ([personal profile] eddy_em) wrote2016-01-22 02:41 pm
Entry tags:

Имитация FSMC

Т.к. для подключения более приличного экранчика с параллельной шиной данных мне понадобится сравнительно шустро работать с GPIO, а у меня нет девборд с "жирными" STM32, где есть FSMC, нужно как-то имитировать эту самую FSMC. Я попробовал два способа: с таймером и с DMA (код поместил в репозиторий stm32samples, директории GPIO_TIM и DMA_GPIO).

Итак, с таймером все просто. Настраиваем таймер:
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МГц можно и не рассчитывать!

[identity profile] balmerdx.livejournal.com 2016-01-22 12:07 pm (UTC)(link)
А для каких дисплеев это нужно?
Для мелких просто и SPI хватает. А для крупных уже процессор нужен помощнее, чтобы графику генерировать...

[identity profile] eddy-em.livejournal.com 2016-01-22 12:19 pm (UTC)(link)
Таких, например. Я уже давно его себе купил, но никак руки не доходили.
У него то ли ILI9325, то ли ILI9328 (не знаю еще: драйвер под экранчиком, раскурочивать не хочу). Хоть драйвер и поддерживает SPI, наружу вывели только параллельный 8-битный интерфейс, а SPI только от тачскрина и флешки.
Да и всяко быстрей будет по параллельному интерфейсу писать, нежели по SPI (а там если провод длинный, то вообще фигня будет на высоких скоростях).

[identity profile] balmerdx.livejournal.com 2016-01-22 12:27 pm (UTC)(link)
Хорошие дисплейчики. У меня правда тоже до подобных никак руки не дойдут, потому как запас SPI ILI9341 еще не хакончился.
Я правда купил без платки такие дисплеи, но зато по цене 1$/шт :)

Про SPI - не все так плохо. Ради интереса пробовал писать на 36 МГц по проводам 20 см длинной. Работает, без сбоев.

[identity profile] eddy-em.livejournal.com 2016-01-22 12:30 pm (UTC)(link)
В любом случае, протокол-то один. Надо сначала его реализовать. У adafruit больше тысячи строк в коде! Офигеть… Вот и думаю: с нуля писать или пытаться портировать их код?

[identity profile] balmerdx.livejournal.com 2016-01-22 12:39 pm (UTC)(link)
Ну, если честно - протокол там везде довольно простой. По большому счету есть две функции.
1. Первоначальная инициализация.
2. Нарисовать на дисплей прямоугольную область из памяти.

Остальной код универсальный, и от дисплея мало зависит.

Лично я скопипастил с Arduino и сильно оптимизировал код для своих дисплеев. Но вы наверняка этот код уже встречали в куче проектов так либо иначе модифицированный :) https://github.com/balmerdx/BalmerVNA/tree/master/4code/src/ili

[identity profile] eddy-em.livejournal.com 2016-01-22 03:07 pm (UTC)(link)
Да, сейчас из адафрутовского переделываю. Для начала надо идентификатор прочитать, а то я и не в курсе, что ж там на деле за чип (погуглил, оказывается, внешний вид не гарантирует однозначно какой-то чип, у кого-то даже 8357 был.
Противно, что паузы в 150нс приходится вручную вбульбенивать через счетчик циклов DWT_CYCCNT ☹

Ну, да ладно, дальше при пересылке буфера с изображением через ПДП этим всем будет рулить таймер. Правда, придется таймер настроить на шимогенерацию и в прерывании дергать ногу WR или RD (смотря что за действо надо выполнить).

[identity profile] eddy-em.livejournal.com 2016-01-22 06:16 pm (UTC)(link)
Блин, не могу даже идентификатор считать! Хрень какая-то получается. Для прикола каждые 10 секунд считываю содержимое всех регистров, получается муть вроде
0 = 0x0000
1 = 0x0000
2 = 0x0000
3 = 0x0000
4 = 0x0404
5 = 0x0404
6 = 0x0404
7 = 0x0404
8 = 0x0808
9 = 0x0808
10 = 0x0808
11 = 0x0808
12 = 0x0c0c
13 = 0x0c0c
14 = 0x0c0c
15 = 0x0c0c
16 = 0x1010
17 = 0x1010
18 = 0x1010

и так далее. Т.е. номер записанного регистра потом четырехкратно считывается.
Фиг поймешь, что ж за контроллер там ☹

UPD. Фух, я по невнимательности после редактирования пинов все равно писал в порт A командные последовательности вместо порта B…
Идентификатор считал: это ILI 9321, буду дальше мучить[ся].
Edited 2016-01-22 18:22 (UTC)

[identity profile] balmerdx.livejournal.com 2016-01-22 05:23 pm (UTC)(link)
Кстати про https://github.com/eddyem/stm32samples/blob/master/nokia5110/pcd8544.c . Лично мне кажется неправильным, что там рисуются символы сразу. На таком дисплее выгоднее заполнить буфер, а потом отослать его целиком. Потому как он по размеру всего 504 байта. Это и быстрее, и можно сказать SPI - Отсылай данные, а самому другой коды выполнять.

[identity profile] eddy-em.livejournal.com 2016-01-22 05:53 pm (UTC)(link)
Я скопипастил вариант для STM8, там, как сам понимаешь, не сильно-то разгуляешься с буфером, на S003 вообще места под него в оперативе нет!

Ну и тест был только текстового режима, в нем как раз проще сразу отсылать символы. А вот в графическом режиме — да, рисуем, потом весь буфер отсылаем.

[identity profile] aamonster.livejournal.com 2016-02-08 07:16 pm (UTC)(link)
Вот ведь... Аккурат сейчас пытаюсь примерно того же добиться (быстро отправлять в BSRR данные из циклического буфера), но пока не очень получается (начал с куба + IAR, тактирование и т.п. настраивается нормально по картинке, USB-CDC тоже запустить удалось, а вот c DMA-GPIO по таймеру как-то нескладуха пока... да и собственно с таймером пока фигня, судя по логическому анализатору - подоткнул PWM, так его частота не зависит от TIMx->ARR и вообще несообразная какая-то).

UPD: победил. HAL мне нравится всё меньше, надо и правда пробовать libopencm3 + регистры (пробовал запуск таймера переписать с HAL на регистры - стало чуть длиннее, но отпала необходимость вникать в кучу кода в HAL - без этого как-то не получается).
Скорости получились ещё ниже, причём завестись почему-то удаётся только до тактовой 48 MHz.
Edited 2016-02-09 22:13 (UTC)