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
По теме как то и нечего видимо сказать… Все отлично) От себя добавлю — осцилл монстр!!! На работе из это же серии есть, 5 гигасэмплов, полоса ЕМНИП 1 Ггц, лог. анализатор, памяти на канал столько, что хоть урастягивайся… Просто мечта!
Хм… Я работал в своем проекте с обоими SPI через DMA и с ADC через DMA до кучи. На STM32F103C8T6 вышеописанных багов не наблюдал. Битых данных не было. Правда не помню, пытался ли я что-то писать в прерывании SPI, т.к. скорей всего работал исключительно с прерываниями DMA…
По идее при использовании DMA — SPI прерывания и не нужны. Т.к. прием и передача ведутся одновременно — значит единственное нужное прерывание — это прерывание по окончанию приема серии в DMA.
По идее при использовании DMA — SPI прерывания и не нужны. Т.к. прием и передача ведутся одновременно — значит единственное нужное прерывание — это прерывание по окончанию приема серии в DMA.
Прерывание по таймеру (~300 раз в секунду) чтоб отправить байт в регистр -семисегментный индикатор.
Если со всем работать по очереди то проблем нет, а вот если во время работы SPI1 DMA, что-то попытается оправить SPI2 или работает ADC c DMA, то по какой-то причине число принятых байт- меньше чем оправленных и все благополучно виснет в ожидании приема.
зы пришлось перевести ADC на прерывания и SPI2 вынести в шедулер — все работает чудесно
ззы хотел написать код который гарантированно сбоит шоб отправить в STM, но пока руки не дошли.
Если со всем работать по очереди то проблем нет, а вот если во время работы SPI1 DMA, что-то попытается оправить SPI2 или работает ADC c DMA, то по какой-то причине число принятых байт- меньше чем оправленных и все благополучно виснет в ожидании приема.
зы пришлось перевести ADC на прерывания и SPI2 вынести в шедулер — все работает чудесно
ззы хотел написать код который гарантированно сбоит шоб отправить в STM, но пока руки не дошли.
Комментарии (9)
RSS свернуть / развернуть