STM32+ENC28j60+DMA+косяки


Собственно в порт стека от Lifelover была добавлена поддержка DMA и заодно обнаружены неприятные косяки со связкой SPI1/SPI2 в STM32F100/103 и использовании еще одного канала DMA1.
Нет смысла приводить весь код, остановимся на DMA части и важных моментах. Использован STM32F100RBT — Discovery.
0. Естественно DMA надо поклокать и каждый раз перед транзакцией инициализировать текущими данными, но по сути меняется только количество байт которые мы хотим передать.
1. SPI пишичитай одновременно поэтому в любом случае нам надо 2 потока — rx/tx и теоретически 2 буфера
2. 2 буфера можно обмануть — т.е. если читаем то пишем просто 0x255 и не инкрементируем эту переменную, если же пишем то наоборот читаем в dummy переменную и не икрементируем ее.
3. Прирост скорости можно ожидать примерно реально в ~2 раза (теоретически и при чтении не c SD, а из памяти больше ~4 раза).
4. не забывать о некоторых найденных косяках :), о них в конце.


void enc28j60_read_buffer(uint8_t *buf, uint16_t len)
{
	tmpx[0]=0xFF;

	       DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(SPI1->DR));
	      
		  DMA_InitStructure.DMA_BufferSize = len;
		  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

		  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
		  DMA_InitStructure.DMA_MemoryDataSize =  DMA_MemoryDataSize_Byte;
		  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
		  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
		  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

		//tx
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
		DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(tmpx);//dummy value
		DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralDST;
		DMA_Init(DMA1_Channel3, &DMA_InitStructure);

		//rx
		DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
		DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(buf);
		DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	        DMA_Init(DMA1_Channel2, &DMA_InitStructure);

		// Enable the SPI Rx DMA request
		SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
		SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);

		enc28j60_select();

		enc28j60_tx(ENC28J60_SPI_RBM);

		// Enable the DMA channel for transfer

		// Enable the DMA SPI RX Stream
		DMA_Cmd(DMA1_Channel2, ENABLE);

		// Enable the DMA SPI TX Stream
		DMA_Cmd(DMA1_Channel3, ENABLE);

		  /* Waiting the end of Data transfer */

		while (DMA_GetFlagStatus(DMA1_FLAG_TC3)==RESET);

		while (DMA_GetFlagStatus(DMA1_FLAG_TC2)==RESET);

	  enc28j60_release();

	  DMA_ClearFlag(DMA1_FLAG_TC3);
	  DMA_ClearFlag(DMA1_FLAG_TC2);

	  DMA_Cmd(DMA1_Channel3, DISABLE);
	  DMA_Cmd(DMA1_Channel2, DISABLE);

	  SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);
	  SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
}
//***********************************************************************

void enc28j60_write_buffer(uint8_t *buf, uint16_t len)
{
          DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(SPI1->DR));
    	  DMA_InitStructure.DMA_BufferSize = len;
	  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	  DMA_InitStructure.DMA_MemoryDataSize =  DMA_MemoryDataSize_Byte;
	  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
	  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

	//tx
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(buf);
	DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralDST;
	DMA_Init(DMA1_Channel3, &DMA_InitStructure);

	//rx
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(tmpx);//dummy value
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        DMA_Init(DMA1_Channel2, &DMA_InitStructure);

  	/* Enable the SPI Rx DMA request */
	SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE);
	SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);

	enc28j60_select();
        //чтобы не возится с буферами - пишучитаю запрос обычным способом, а буфер уже dma
	enc28j60_tx(ENC28J60_SPI_WBM);

	/* Enable the DMA channel to transfer*/

	/* Enable the DMA SPI RX Stream */
	DMA_Cmd(DMA1_Channel2, ENABLE);

	/* Enable the DMA SPI TX Stream */
	DMA_Cmd(DMA1_Channel3, ENABLE);

	  /* Waiting the end of Data transfer */
	  while (DMA_GetFlagStatus(DMA1_FLAG_TC3)==RESET);

	 while (DMA_GetFlagStatus(DMA1_FLAG_TC2)==RESET);
 
 enc28j60_release();

DMA_ClearFlag(DMA1_FLAG_TC3);
DMA_ClearFlag(DMA1_FLAG_TC2);

DMA_Cmd(DMA1_Channel3, DISABLE);
DMA_Cmd(DMA1_Channel2, DISABLE);

SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);
SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
}


Чтение с SD (все тоже самое в принципе)


// read sectors from card
DRESULT disk_read(BYTE drive, BYTE *buf, DWORD sector, BYTE count)
{
	tmpx[0]=0xFF;

	if( (drive != 0) || (card_type == NO_CARD) )
		return RES_NOTRDY;

			DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(SPI2->DR));

			//dma dir different for channels
			DMA_InitStructure.DMA_BufferSize = 512;
			DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

			DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
		        DMA_InitStructure.DMA_MemoryDataSize =  DMA_MemoryDataSize_Byte;
			DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
			DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
			DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

				//tx
				DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
				DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(tmpx);
				DMA_InitStructure.DMA_DIR =DMA_DIR_PeripheralDST;
				DMA_Init(DMA1_Channel5, &DMA_InitStructure);

				//rx
				DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
				DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)(buf);
				DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
			    DMA_Init(DMA1_Channel4, &DMA_InitStructure);

				// Enable the SPI Rx DMA request
				SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx, ENABLE);
				SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);


				if(!mmc_read_init(sector++))
					return RES_ERROR;

				// Enable the DMA channel for transfer

				// Enable the DMA SPI RX Stream
				DMA_Cmd(DMA1_Channel4, ENABLE);

				// Enable the DMA SPI TX Stream
				DMA_Cmd(DMA1_Channel5, ENABLE);

/* Waiting the end of Data transfer */
while (DMA_GetFlagStatus(DMA1_FLAG_TC5)==RESET);
while (DMA_GetFlagStatus(DMA1_FLAG_TC4)==RESET);

mmc_read_final();
DMA_ClearFlag(DMA1_FLAG_TC4);
DMA_ClearFlag(DMA1_FLAG_TC5);

DMA_Cmd(DMA1_Channel4, DISABLE);
DMA_Cmd(DMA1_Channel5, DISABLE);

SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Rx, DISABLE);
SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, DISABLE);

return RES_OK;
}


Из найденных косяков: если SPI2 пытается чтонито записать в прерывании (не в этой конкретно программе) во время работы SPI1 DMA то происходит порча данных, поток чтения теряет данные и виснет в цикле ожидания проверки окончания транзакции — я это не мог долго отследить…
Подтверждаю еще один косяк — при одновременной работе с DMA1 Channel1 (у меня на нем висел ADC1) данные чтения также портятся при одновременной работе.
И кстати в STM32F4XX по видимо можно использовать только 2 потока на DMA2 там есть баг blog.frankvh.com/2012/01/13/stm32f2xx-stm32f4xx-dma-maximum-transactions/
Ну и статья кторая меня сподвигла на переписать это для enc28j60 javakys.wordpress.com/2014/09/04/upgrade-w5500-throughput-on-nucleo-stm32f401re-using-spi-dma/
Но там помоему в коде есть косячки.

зы форматрование все чето ползет лень с ним бороться

  • +6
  • 07 октября 2014, 14:19
  • GYUR22
  • 1
Файлы в топике: web_sd_discovery_dma.zip

Комментарии (9)

RSS свернуть / развернуть
По теме как то и нечего видимо сказать… Все отлично) От себя добавлю — осцилл монстр!!! На работе из это же серии есть, 5 гигасэмплов, полоса ЕМНИП 1 Ггц, лог. анализатор, памяти на канал столько, что хоть урастягивайся… Просто мечта!
0
Это не мой я у javakys стырил — КДПВ :)
0
пара Agilent MSO7104 на работе? тогда я, наверное, догадываюсь, что это за контора.
0
Я хорошо наслышан о двух братьях, работавших тут до меня… Только они могли знать, что таких осциллов «пара». Похоже, мир все же тесен)
0
тогда наверняка вам может быть что рассказать про сетевые возможности STM32F4 ;-) если вы доделываете этот проект…
0
Я пока был занят другим проектом, но, думаю, доберусь и до «этого». Ваш прототип лежит у меня на столе. Вообще, работа с сетью меня не особо привлекает, больше по всяким дисплеям и GUI болею. Быстрее статья по этой теме будет, чем о сети.
0
Небольшое сравнение скоростей на stm32f103 + enc + sdio:
без DMA
стек от Lifelover'а: ~180 кБ/сек,
lwip: ~210 кБ/сек
с DMA
стек от Lifelover'а: ~430 кБ/сек
lwip: ~560 кБ/сек
Спасибо за модификацию драйвера! Прирост получился довольно существенным!
0
Хм… Я работал в своем проекте с обоими SPI через DMA и с ADC через DMA до кучи. На STM32F103C8T6 вышеописанных багов не наблюдал. Битых данных не было. Правда не помню, пытался ли я что-то писать в прерывании SPI, т.к. скорей всего работал исключительно с прерываниями DMA…

По идее при использовании DMA — SPI прерывания и не нужны. Т.к. прием и передача ведутся одновременно — значит единственное нужное прерывание — это прерывание по окончанию приема серии в DMA.
0
Прерывание по таймеру (~300 раз в секунду) чтоб отправить байт в регистр -семисегментный индикатор.
Если со всем работать по очереди то проблем нет, а вот если во время работы SPI1 DMA, что-то попытается оправить SPI2 или работает ADC c DMA, то по какой-то причине число принятых байт- меньше чем оправленных и все благополучно виснет в ожидании приема.
зы пришлось перевести ADC на прерывания и SPI2 вынести в шедулер — все работает чудесно
ззы хотел написать код который гарантированно сбоит шоб отправить в STM, но пока руки не дошли.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.