Имитация FSMC
Jan. 22nd, 2016 02:41 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Т.к. для подключения более приличного экранчика с параллельной шиной данных мне понадобится сравнительно шустро работать с 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МГц можно и не рассчитывать!