STM32 IAP固件升級(三)


章節說明

STM32 IAP固件升級實驗分為以下的章節(加粗的字體是本章節的內容):

一、Flash和RAM的區域划分、工程建立、程序分散加載、程序燒寫
二、Stm32 bootloader、application、firmware 程序的分析和編寫
三、使用DMA收發串口的不定長數據
四、通信協議的設計
五、STM32 IAP程序的設計
六、上位機的程序的編寫

一、前言

前面介紹了IAP需要的一些基礎知識,區域怎么划分,還有啟動跳轉過程等。當然如果需要實現IAP,通訊接口的驅動是不可或缺的。例如可以使用串口(USART)、以太網(ETH)、USB等通訊接口來進行數據收發。本實驗就不使用復雜的通訊接口了,使用比較常用又比較簡單的串口。當然將串口用好也不是那么簡單的,使用串口中斷接收數據效率一般比較低。所以為了提高效率,這里使用DMA(直接存儲器存取)+USART(串口)接收數據。

注意:下面涉及到串口的基礎配置這些就不過多解釋了,直接上源碼了。如果不會配置的,可以去看看其他大神寫的博客,或者去看看一些教學視頻。

一、串口配置

STM32F103有三個串口(USART1,USART2,USART3)。在實驗中,因為USART1主要用來程序的調試,所以使用USART2來做IAP的通訊接口。

1、USART2的配置程序如下:

a.初始化GPIO

void USART2_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	/* 初始化 USART2_TX  ----- PA2 */
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2; 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;	
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	/* 初始化 USART2_RX  ----- PA3 */
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_3;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);  
}

b.初始化串口

在STM32的串口里支持同步模式和異步模式,我門通常使用的是異步模式;詳細的是使用說明參考《STM32中文參考手冊》
同步模式雖然少用,但是它也是有優點的;在使用多一條同步時鍾線的情況下,數據傳輸會更可靠,更可控。
下面的配置禁止了同步模式,使用的是異步模式(通常使用異步模式 USART_ClockInitStructure 數據不設置也是可以的)。

void USART2_Config(u32 baudrate)
{
	USART_InitTypeDef USART_InitStructure;
	USART_ClockInitTypeDef  USART_ClockInitStructure;
	/* 初始化時鍾 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); 
/************************使用異步模式,這個不用配置也可以*****************************/
	/* 關閉同步時鍾(即關閉串口的同步模式; 需要使用同步模式,則使能這個) */
	USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
	/* 配置時鍾穩態值為低電平(即空閑時,時鍾線的電平為低電平) */
	USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
	/* 配置時鍾相位,在時鍾的第二個邊沿進行數據捕獲 */
	USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
	/* 最后一個數據位不從時鍾線上輸出 */
	USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
	/* 初始化 USART2 的同步參數,這里沒有使用同步時鍾 */
	USART_ClockInit(USART2, &USART_ClockInitStructure);
/************************使用異步模式,這個不用配置也可以*****************************/

	/* 設置波特率 */
	USART_InitStructure.USART_BaudRate = baudrate;
	/* 設置數據位長度為 8 */
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	/* 設置停止位是1 */
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	/* 不適用奇偶校驗 */
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	/* 關閉硬件流控 */
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	/* 使能 USART2 接收和接收 */
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	/* 初始化 USART2 的異步參數,在STM32中通常使用這種傳輸方式 */
	USART_Init(USART2, &USART_InitStructure);
}

STM32F103 DMA

1、STM32F103 DMA簡單介紹

在STM32里面有兩個DMA控制器(DMA1和DMA2)。兩個DMA控制器有一共有12個通道(DMA1 7個通道,DMA2 5個通道)。每個DMA通道對應的外設備如下圖:
對於詳細的DMA介紹參考《STM32中文參考手冊》,這里就不過多介紹了。

2、DMA 使用方法

DMA的使用並不復雜,通道配置過程如下:

  1. 設置外設起始地址
  2. 設置內存的起始地址
  3. 設置數據傳輸的長度(要傳輸多長的數據)
  4. 設置數據位寬
  5. 設置數據的傳輸方向
  6. 設置傳輸模式
  7. 設置通道的優先級
  8. 使能DMA通道

3、配置 USART2 的 DMA

從上面的兩張外設對應DMA通道的圖片知到: USART2_RX 對應DMA1的通道6,USART2_TX 對應DMA1的通道7。

a. 配置USART2_RX的DMA

這里只寫簡單的配置過程。DMA的更詳細的使用說明,請參考《STM32中文參考手冊》。配置程序如下:

void USART2_DMA_RX_Config(u8* Buffer, s32 NumData) 
{
	DMA_InitTypeDef  DMA_InitStructure;
	/* 使能時鍾 */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);             
	/* 將 DMA1_Channel6 設置為缺省 */
	DMA_DeInit(DMA1_Channel6);
	/* 配置外設的地址為串口的就收寄存器的地址 */
	DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)(&(USART2->DR));	
	/* 設置內存的地址 */
	DMA_InitStructure.DMA_MemoryBaseAddr      = (uint32_t)(Buffer);
	/* 設置傳輸方向為 外設到內存 */
	DMA_InitStructure.DMA_DIR                 = DMA_DIR_PeripheralSRC;
	/* 設置緩沖區Buffer的大小 */
	DMA_InitStructure.DMA_BufferSize          = NumData;
	/* 禁止外設的的地址自增 */
	DMA_InitStructure.DMA_PeripheralInc       = DMA_PeripheralInc_Disable;
	/* 使能內存地址自增 */
	DMA_InitStructure.DMA_MemoryInc           = DMA_MemoryInc_Enable;
	/* 設置外設每次傳輸數據的大小為字節(Byte) */
	DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;
	/* 設置內存每次傳輸數據的大小為字節(Byte) */
	DMA_InitStructure.DMA_MemoryDataSize      = DMA_MemoryDataSize_Byte;
	/* 將設置DMA傳輸的模式為循環傳輸 */
	DMA_InitStructure.DMA_Mode                = DMA_Mode_Circular;
	/* 設置當前DMA通道的優先級為高優先級 */
	DMA_InitStructure.DMA_Priority            = DMA_Priority_High;
	/* 禁止內存到內存傳輸 */
	DMA_InitStructure.DMA_M2M                 = DMA_M2M_Disable;
	
	/* 初始化DMA1通道6 */
	DMA_Init(DMA1_Channel6, &DMA_InitStructure);
}

b. 配置USART2_TX的DMA

void USART2_DMA_TX_Config(u8* Buffer, s32 NumData) 
{
	DMA_InitTypeDef  DMA_InitStructure;
	/* 使能時鍾 */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);             
	/* 將 DMA1_Channel7 設置為缺省 */
	DMA_DeInit(DMA1_Channel7);
	/* 配置外設的地址為串口的發送數據寄存器的地址 */
	DMA_InitStructure.DMA_PeripheralBaseAddr  = (uint32_t)(&(USART2->DR));	
	/* 設置內存的地址 */
	DMA_InitStructure.DMA_MemoryBaseAddr      = (uint32_t)(Buffer);
	/* 設置傳輸方向為 內存到外設 */
	DMA_InitStructure.DMA_DIR                 = DMA_DIR_PeripheralDST;
	/* 設置緩沖區Buffer的大小 */
	DMA_InitStructure.DMA_BufferSize          = NumData;
	/* 禁止外設的的地址自增 */
	DMA_InitStructure.DMA_PeripheralInc       = DMA_PeripheralInc_Disable;
	/* 使能內存地址自增 */
	DMA_InitStructure.DMA_MemoryInc           = DMA_MemoryInc_Enable;
	/* 設置外設每次傳輸數據的大小為字節(Byte) */
	DMA_InitStructure.DMA_PeripheralDataSize  = DMA_PeripheralDataSize_Byte;
	/* 設置內存每次傳輸數據的大小為字節(Byte) */
	DMA_InitStructure.DMA_MemoryDataSize      = DMA_MemoryDataSize_Byte;
	/* 將設置DMA傳輸的模式為正常傳輸 */
	DMA_InitStructure.DMA_Mode                = DMA_Mode_Normal;
	/* 設置當前DMA通道的優先級為高優先級 */
	DMA_InitStructure.DMA_Priority            = DMA_Priority_High;
	/* 禁止內存到內存傳輸 */
	DMA_InitStructure.DMA_M2M                 = DMA_M2M_Disable;
	/* 初始化DMA1通道7 */
	DMA_Init(DMA1_Channel7, &DMA_InitStructure);
}

三、初始化、使能串口和DMA

1、使能串口和DMA

使能串口和DMA的函數如下:

void USART2_Enable(void)
{
	/* 使能串口2 */
	USART_Cmd(USART2, ENABLE);
	/* 使能串口2的DMA發送 */
	USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
	/* 使能串口2的DMA接收 */
	USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);
	/* 使能DMA1通道7 */
	DMA_Cmd(DMA1_Channel7, ENABLE);	
	/* 使能DMA1通道6 */
	DMA_Cmd(DMA1_Channel6, ENABLE);	
}

2、初始化函數

USART2_DMA_TxBuffUSART2_DMA_RxBuff是一個全局數組,用於DMA發送或者接收數據;以后需要發送數據和接收數據就操作這兩個數組(buffer)就可以了

void USART2_Init(u32 baudrate)
{
	/* 初始化USART2的GPIO */
	USART2_GPIO_Config();
	/* 設置串口 */
	USART2_Config(baudrate);
	
	/* 設置USART2的發送DMA */
	USART2_DMA_TX_Config(USART2_DMA_TxBuff, 0); 
	/* 設置USART2的接收DMA */
	USART2_DMA_RX_Config(USART2_DMA_RxBuff, USART2_DMA_RXBUFFLEN);
	/* 使能 */
	USART2_Enable();
}

*** 配置到這里,基本就可以使用DMA接收和發送數據了;使用DMA發送數據是比較簡單的,但是使用DMA接收數據,需要費一點點功夫。 ***

四、DMA接收串口數據

1、處理接收到的數據

由於使用了DMA接收數據,所以不需要再操作 USART2DR 寄存器了(數據寄存器);硬件會直接將數據copy到USART2_DMA_RxBuff數組里面。又因為在接收的DMA設置函數里將DMA的模式配置成了循環模式,所以USART2_DMA_RxBuff的本質是一個環形緩沖區。
接下來就先上程序再慢慢分析吧。接收處理的代碼如下:

/* 說明一下。函數里的 USART2_Service 是一個全局的函數指針,用於處理接收到的數據的 (需要怎么處理,就看業務的需求是什么了) */
/* USART2_Service 函數指針定義如下: void (*USART2_Service)(u8 *buff, u16 len); */
/* 用戶需要自己初始化這個函數指針 */
/* 可以將 USART2_Service 設置為一個回調函數的樣式;在這里就使用全局的函數指針了 */
void USART2_DMA_Rx(void)
{ 	
	u32 pos;
	/* USART2_RX_Pos 指向環形緩沖區數據的頭部 */
	static volatile u16   USART2_RX_Pos;
	
	/* DMA1_Channel6 -> CNDTR 接收到的數據查長度 */
	/* 算出pos當前的指向,緩沖區數據的尾部 */
	pos = USART2_DMA_RXBUFFLEN - (DMA1_Channel6 -> CNDTR);
	/* 當前的pos上一次的pos大 */
	if(pos > USART2_RX_Pos)
	{
		/* 算個出buff的起始地址,算出接收到數據的長度 */
		USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, pos - USART2_RX_Pos);
		/* 改變buffer的起始指向 */
		USART2_RX_Pos = pos;
	}
	/* 如果上一次的pos比當前的pos大,說明有部分數據在前面 */
	else if(pos < USART2_RX_Pos)
	{
		/* 計算buffer的起始地址,先將后面部分的數據放入到內存管理 */
		USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, USART2_DMA_RXBUFFLEN - USART2_RX_Pos);
		if(pos != 0)
		{
			/* 如果頭部還有數據,也將頭部前面的數據放入內存管理 */
			USART2_Service(USART2_DMA_RxBuff, pos);
		}
		/* 改變頭部的起始指向 */
		USART2_RX_Pos = pos;
	}
	else
	{;}
}

*** 下面具體分析 USART2_DMA_Rx函數的原理 ***

2、代碼塊1的分析

    /* 當前的pos上一次的pos大 */
    if(pos > USART2_RX_Pos)
    {
        /* 算個出buff的起始地址,算出接收到數據的長度 */
        USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, pos - USART2_RX_Pos);
        /* 改變buffer的起始指向 */
        USART2_RX_Pos = pos;
    }

這部分代碼是處理接收到的數據是下面的情況(也就是需要處理綠色區的數據)

3、代碼塊2的分析

    /* 如果上一次的pos比當前的pos大,說明有部分數據在前面 */
    else if(pos < USART2_RX_Pos)
    {
        /* 計算buffer的起始地址,先將后面部分的數據放入到內存管理 */
        USART2_Service(USART2_DMA_RxBuff + USART2_RX_Pos, USART2_DMA_RXBUFFLEN - USART2_RX_Pos);
        if(pos != 0)
        {
            /* 如果頭部還有數據,也將頭部前面的數據放入內存管理 */
            USART2_Service(USART2_DMA_RxBuff, pos);
        }
        /* 改變頭部的起始指向 */
        USART2_RX_Pos = pos;
    }

這部分代碼是處理接收到的數據是下面的情況(也就是需要處理綠色區的數據)

五、總結

總結一下使用方法:

  1. 調用 USART2_Init 函數初始化串口和DMA
  2. 初始化 USART2_Service 函數指針;這個函數的參數有兩個,一個是數據的指針,一個是接收到數據的長度
  3. 通過大循環不斷調用USART2_DMA_Rx函數處理接收到的數據;

實驗現象:
實驗是在application環境下的,通過給USART2發送數據,USART2接收到的數據后再從USART1里打印出來。實驗現象如下圖:
左邊是給USART2發送數據,右邊是將接收到的數據通過USART1打印出來。

***注意: ***

使用這個方法接收串口的數據有優點也有缺點

  • 優點是:效率比較高,使用比較方便
  • 缺點是:實時性的要求比較高。如果緩沖區比較小,實時性又比較低;當有大量數據連續傳來時,很容易就會造成數據的覆蓋。在單線程里面使用時,盡量少使用大的延時函數。

projrct
串口的發送就不羅嗦了,網上比較多的教程(當然想要發送的效率更高可以使用雙緩沖的方式,具體怎么設計就留給各位小伙伴門自己發揮了)。最后將工程的鏈接附上--->點我project!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM