打算在STM32上面弄FTP,希望在两个星期内(元旦前)能搞定。
(1)
先弄成功TFTP。
昨天加上今天2017.12.15一上午,搞定了TFTP,可以用tftp64这个小软件与单片机进行get和put文件。这样就说明lwip驱动和SD卡驱动没问题。
传输速度还凑合,读能快点,没太注意速度是多少,大概也就1M吧(后来发现没这么快,估计也就200~300K),写能慢点。我是查询方式接收,如果用中断方式接收有可能会快一点。这个TFTP好像是发一帧回一帧,感觉效率有点低。另外SD卡驱动速度可能还可以优化。
另外TFTP的传输文件的文件名字不能太长,不能含有汉字,否则传输不成功。要想传长名字的文件或者含有汉字名字的文件,需要把fatfs有个宏定义要改一下,并且要加上一个C文件才能支持汉字。
还有传输的过程中,在TFTP的上位机界面显示了block的个数,当个数大于65536的时候就停顿了,用wireshark看了一下,一直在不停地收发好像是空帧。就是说明可能需要把fatfs改点啥,比如改数据类型uint16为uint32.可能就能传更多的数据。
还有我电脑是64位机,用TFTP32软件传输总是不成功。
(2)然后弄FTP
今天下午2017.12.15实现了连接并且可以log in,但是到了PWD(好像叫这个命令)指令的时候不行,就是浏览目录的文件大概。用wireshark看了一下,确实是如此。不过仍能ping通。大概查了一下,应该是SD卡初始化没弄。所以下一步是要把SD卡初始化弄弄明白,然后估计需要看看TCPIP卷1的第27章《FTP:文件传输协议》。如果FTP都搞定了,下一步可能会直接弄micropython,也有可能进一步理解加深lwip,毕竟还有很多工作要做,比如lwip的实时性接收,多个IP。lwip实现多个协议栈等等还有对TCP协议的理解等等。想的太远也白搭,走一步看一步吧。
(3)2017年12月20日,FTP可以上传和下载了。但是稳定性极差。传输速度也很慢,几十K吧。
稳定性差,有时候能传成功,有时候不能。只是支持PASSIVE模式。不支持长文件名。主要是我对FTP协议本身不熟悉,对FATFS也不熟悉。
(4)2017年12月21日,支持长文件名。但是只能传两个文件,传送第三个文件就传送不成功。
(5)2017年12月22日,找到传送第三个文件不成功原因。MEMP_NUM_TCP_PCB_LISTEN这个宏定义的值决定了
传送成功的个数。因为每次调用tcp_bind就要绑定一个不同的port。这种估计是有问题的。
还有速度,需要把一些TCP参数调整。后来查出来了,基本上是写SD卡浪费了时间。
所以要研究下这个问题。两个方案,一个是额外建立一个大缓冲区,然后调用disk_write这个函数。估计这种方法能稍微快一点。还有个方案就是研究disk_write这个函数本身(可以自己写个简单工程来单独验证这个函数的用法),充分把DMA的优势发挥出来,看了官网例子,感觉没挖掘出DMA优势。还有个文章值得注意:http://www.360doc.com/content/13/1031/15/348183_325589978.shtml
这个文章是野火开发板的,说解决了官网的软件的BUG。
另外,http://bbs.armfly.com/read.php?tid=11880这个文章指出了这个bug.
(5)2017年12月26日,只是文件写入可以4M了。是SDIO_BusWide_1b的。也不清楚SDIO_BusWide_4b和SDIO_BusWide_1b速度上有什么区别。另外看到成立还开起中断DMA2_Stream3_IRQHandler。
另外:
* ===================================================================
* How to use this driver
* ===================================================================
* It implements a high level communication layer for read and write
* from/to this memory. The needed STM32 hardware resources (SDIO and
* GPIO) are defined in stm324xg_eval.h file, and the initialization is
* performed in SD_LowLevel_Init() function declared in stm324xg_eval.c
* file.
* You can easily tailor this driver to any other development board,
* by just adapting the defines for hardware resources and
* SD_LowLevel_Init() function.
*
* A - SD Card Initialization and configuration
* ============================================
* - To initialize the SD Card, use the SD_Init() function. It
* Initializes the SD Card and put it into StandBy State (Ready
* for data transfer). This function provide the following operations:
*
* 1 - Apply the SD Card initialization process at 400KHz and check
* the SD Card type (Standard Capacity or High Capacity). You
* can change or adapt this frequency by adjusting the
* "SDIO_INIT_CLK_DIV" define inside the stm324xg_eval.h file.
* The SD Card frequency (SDIO_CK) is computed as follows:
*
* +---------------------------------------------+
* | SDIO_CK = SDIOCLK / (SDIO_INIT_CLK_DIV + 2) |
* +---------------------------------------------+
*
* In initialization mode and according to the SD Card standard,
* make sure that the SDIO_CK frequency don't exceed 400KHz.
*
* 2 - Get the SD CID and CSD data. All these information are
* managed by the SDCardInfo structure. This structure provide
* also ready computed SD Card capacity and Block size.
*
* 3 - Configure the SD Card Data transfer frequency. By Default,
* the card transfer frequency is set to 24MHz. You can change
* or adapt this frequency by adjusting the "SDIO_TRANSFER_CLK_DIV"
* define inside the stm324xg_eval.h file.
* The SD Card frequency (SDIO_CK) is computed as follows:
*
* +---------------------------------------------+
* | SDIO_CK = SDIOCLK / (SDIO_INIT_CLK_DIV + 2) |
* +---------------------------------------------+
*
* In transfer mode and according to the SD Card standard,
* make sure that the SDIO_CK frequency don't exceed 25MHz
* and 50MHz in High-speed mode switch.
* To be able to use a frequency higher than 24MHz, you should
* use the SDIO peripheral in bypass mode. Refer to the
* corresponding reference manual for more details.
*
* 4 - Select the corresponding SD Card according to the address
* read with the step 2.
*
* 5 - Configure the SD Card in wide bus mode: 4-bits data.
*
* B - SD Card Read operation
* ==========================
* - You can read SD card by using two function: SD_ReadBlock() and
* SD_ReadMultiBlocks() functions. These functions support only
* 512-byte block length.
* - The SD_ReadBlock() function read only one block (512-byte). This
* function can transfer the data using DMA controller or using
* polling mode. To select between DMA or polling mode refer to
* "SD_DMA_MODE" or "SD_POLLING_MODE" inside the stm324xg_eval_sdio_sd.h
* file and uncomment the corresponding line. By default the SD DMA
* mode is selected
* - The SD_ReadMultiBlocks() function read only mutli blocks (multiple
* of 512-byte).
* - Any read operation should be followed by two functions to check
* if the DMA Controller and SD Card status.
* - SD_ReadWaitOperation(): this function insure that the DMA
* controller has finished all data transfer.
* - SD_GetStatus(): to check that the SD Card has finished the
* data transfer and it is ready for data.
*
* - The DMA transfer is finished by the SDIO Data End interrupt.
* User has to call the SD_ProcessIRQ() function inside the SDIO_IRQHandler()
* and SD_ProcessDMAIRQ() function inside the DMA2_Streamx_IRQHandler().
* Don't forget to enable the SDIO_IRQn and DMA2_Stream3_IRQn or
* DMA2_Stream6_IRQn interrupts using the NVIC controller.
*
* C - SD Card Write operation
* ===========================
* - You can write SD card by using two function: SD_WriteBlock() and
* SD_WriteMultiBlocks() functions. These functions support only
* 512-byte block length.
* - The SD_WriteBlock() function write only one block (512-byte). This
* function can transfer the data using DMA controller or using
* polling mode. To select between DMA or polling mode refer to
* "SD_DMA_MODE" or "SD_POLLING_MODE" inside the stm324xg_eval_sdio_sd.h
* file and uncomment the corresponding line. By default the SD DMA
* mode is selected
* - The SD_WriteMultiBlocks() function write only mutli blocks (multiple
* of 512-byte).
* - Any write operation should be followed by two functions to check
* if the DMA Controller and SD Card status.
* - SD_ReadWaitOperation(): this function insure that the DMA
* controller has finished all data transfer.
* - SD_GetStatus(): to check that the SD Card has finished the
* data transfer and it is ready for data.
*
* - The DMA transfer is finished by the SDIO Data End interrupt.
* User has to call the SD_ProcessIRQ() function inside the SDIO_IRQHandler()
* and SD_ProcessDMAIRQ() function inside the DMA2_Streamx_IRQHandler().
* Don't forget to enable the SDIO_IRQn and DMA2_Stream3_IRQn or
* DMA2_Stream6_IRQn interrupts using the NVIC controller.
*
*
* D - SD card status
* ==================
* - At any time, you can check the SD Card status and get the SD card
* state by using the SD_GetStatus() function. This function checks
* first if the SD card is still connected and then get the internal
* SD Card transfer state.
* - You can also get the SD card SD Status register by using the
* SD_SendSDStatus() function.
*
* E - Programming Model (Selecting DMA for SDIO data Transfer)
* ============================================================
* Status = SD_Init(); // Initialization Step as described in section A
*
* // SDIO Interrupt ENABLE
* NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn;
* NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
* NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
* NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
* NVIC_Init(&NVIC_InitStructure);
* // DMA2 STREAMx Interrupt ENABLE
* NVIC_InitStructure.NVIC_IRQChannel = SD_SDIO_DMA_IRQn;
* NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
* NVIC_Init(&NVIC_InitStructure);
*
* // Write operation as described in Section C
* Status = SD_WriteBlock(buffer, address, 512);
* Status = SD_WaitWriteOperation();
* while(SD_GetStatus() != SD_TRANSFER_OK);
*
* Status = SD_WriteMultiBlocks(buffer, address, 512, NUMBEROFBLOCKS);
* Status = SD_WaitWriteOperation();
* while(SD_GetStatus() != SD_TRANSFER_OK);
*
* // Read operation as described in Section B
* Status = SD_ReadBlock(buffer, address, 512);
* Status = SD_WaitReadOperation();
* while(SD_GetStatus() != SD_TRANSFER_OK);
*
* Status = SD_ReadMultiBlocks(buffer, address, 512, NUMBEROFBLOCKS);
* Status = SD_WaitReadOperation();
* while(SD_GetStatus() != SD_TRANSFER_OK);
*
* - Add the SDIO and DMA2 StreamX (3 or 6) IRQ Handlers:
* void SDIO_IRQHandler(void)
* {
* SD_ProcessIRQ();
* }
* void SD_SDIO_DMA_IRQHANDLER(void)
* {
* SD_ProcessDMAIRQ();
* }
*
* F - Programming Model (Selecting Polling for SDIO data Transfer)
* ================================================================
* //Only SD Card Single Block operation are managed.
* Status = SD_Init(); // Initialization Step as described in section
*
* // Write operation as described in Section C
* Status = SD_WriteBlock(buffer, address, 512);
*
* // Read operation as described in Section B
* Status = SD_ReadBlock(buffer, address, 512);
*
* STM32 SDIO Pin assignment
* =========================
* +-----------------------------------------------------------+
* | Pin assignment |
* +-----------------------------+---------------+-------------+
* | STM32 SDIO Pins | SD | Pin |
* +-----------------------------+---------------+-------------+
* | SDIO D2 | D2 | 1 |
* | SDIO D3 | D3 | 2 |
* | SDIO CMD | CMD | 3 |
* | | VCC | 4 (3.3 V)|
* | SDIO CLK | CLK | 5 |
* | | GND | 6 (0 V) |
* | SDIO D0 | D0 | 7 |
* | SDIO D1 | D1 | 8 |
* +-----------------------------+---------------+-------------+
*
* @endverbatim
上例中,SD_WriteBlock和SD_WriteMultiBlocks调用之后都用了同一个等待函数SD_WaitWriteOperation,
当执行完SD_WriteBlock后函数SD_WaitWriteOperation执行的时间比执行完SD_WriteMultiBlocks后函数SD_WaitWriteOperation执行的时间要短一些。前者大概2个ms,一般不超过3个ms,后者就有6个ms左右,当然也有4个ms。及其个别时候能达到10个ms。
网上还有种另类用法SD_WriteBlock和SD_WriteMultiBlocks调用之后都用了SD_WaitReadOperation。用这种方法时间能更短一些,执行SD_WriteMultiBlocks之后能等待个3、4个ms。函数SD_WaitReadOperation与SD_WaitWriteOperation函数内部就是有个标志位不一样,一个是接收标志一个是发送标志。
这些函数都在diskio.C里面,这个C文件很重要。网上有一种的disk_write函数是这么写的:
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber (0..) */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to write (1..128) */
)
{
DRESULT res;
switch (pdrv) {
case FS_SD :
{
SD_Error Status = SD_OK;
if (count == 1)
{
cnt_store1 = LocalTime;
Status = SD_WriteBlock((uint8_t *)buff, sector << 9 ,SECTOR_SIZE);
if (Status != SD_OK)
{
return RES_ERROR;
}
#ifdef SD_DMA_MODE
/* SDIO¹¤×÷ÔÚDMAģʽ£¬ÐèÒª¼ì²é²Ù×÷DMA´«ÊäÊÇ·ñÍê³É */
Status = SD_WaitReadOperation();
if (Status != SD_OK)
{
return RES_ERROR;
}
while(SD_GetStatus() != SD_TRANSFER_OK);
#endif
period1 = LocalTime - cnt_store1;
return RES_OK;
}
else
{
cnt_store2 = LocalTime;
/* ´Ë´¦´æÔÚÒÉÎÊ£º ÉÈÇø¸öÊýÈç¹ûд count £¬½«µ¼ÖÂ×îºó1¸öblockÎÞ·¨Ð´Èë */
//Status = SD_WriteMultiBlocks((uint8_t *)buff, sector << 9 ,SECTOR_SIZE, count);
Status = SD_WriteMultiBlocks((uint8_t *)buff, sector << 9 ,SECTOR_SIZE, count + 1);
if (Status != SD_OK)
{
return RES_ERROR;
}
#ifdef SD_DMA_MODE
/* SDIO¹¤×÷ÔÚDMAģʽ£¬ÐèÒª¼ì²é²Ù×÷DMA´«ÊäÊÇ·ñÍê³É */
Status = SD_WaitReadOperation();
if (Status != SD_OK)
{
return RES_ERROR;
}
while(SD_GetStatus() != SD_TRANSFER_OK);
#endif
period2 = LocalTime - cnt_store2;
return RES_OK;
}
}
case FS_NAND :
res = RES_OK;
return res;
}
return RES_PARERR;
}
我以上的关于几个ms的时间测试都是基于这个函数的。这个函数与ST官网给的函数有很大区别。并且比官网的快很多,但是稳定性不一定高。
(6)2017年12月27日,事实上FATFS和LWIP完全可以分开调试,就是在FTP程序里面把FASFS读写那个接口函数注释掉,就是这个函数不耗费时间,就有办法一步步的搞定了。通过这个方法,我知道了我以太网方面的传输速度最快是1.1M(因为我用的查询法,接收也没有缓冲区啥的),这时候如果想把文件写入速度大于1.1M,那么程序不完善的话,就崩掉了(这句话看起来有问题啊!以后再深究此事)。
[博主FTP开发小结]:通过这一阶段对FTP的接触,发现FTP想要开发稳定还是比较困难的事情。我是在裸机上(没用操作系统)上开发,想要在STM32上开发稳定的FTP需要如下技能:
(1)对fatfs有较深入研究,用好文件系统api各种函数接口。对SDIO的诸多函数有理解,知道速度的瓶颈在哪里。(2)查阅资料的能力,查阅网友在fatfs曾经遇到过的坑。
(3)熟悉lwip的各种接口
(4)对FTP协议有非常透彻的理解,因为是在STM32上裸机开发,网上根本没有例子,因此只能深刻理解TCPIP卷1的第27章《FTP:文件传输协议》。
(5)会自己编写断言等辅助调试手段。
(6)还有在FTP读写过程中,如果需要记录别的数据,恐怕要考虑一些问题了。比如说同时进行FTP和TFTP协议传输。
如果能做到以上几点,那么STM32F407的SDIO上开发,预计通过FTP协议向SD卡上拷贝数据达到1~2M这个速度。当然如果能把DMA性能发挥出来就更好了(因为需要死等几个ms,这个时间如果能腾挪出来用来传输以太网数据那就能支撑更高的速度,当然这需要更大的内存支撑并且对SDIO的驱动有较深的理解。