章節說明
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的使用並不復雜,通道配置過程如下:
- 設置外設起始地址
- 設置內存的起始地址
- 設置數據傳輸的長度(要傳輸多長的數據)
- 設置數據位寬
- 設置數據的傳輸方向
- 設置傳輸模式
- 設置通道的優先級
- 使能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_TxBuff
和USART2_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接收數據,所以不需要再操作
USART2
的DR
寄存器了(數據寄存器);硬件會直接將數據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;
}
這部分代碼是處理接收到的數據是下面的情況(也就是需要處理綠色區的數據)
五、總結
總結一下使用方法:
- 調用
USART2_Init
函數初始化串口和DMA- 初始化
USART2_Service
函數指針;這個函數的參數有兩個,一個是數據的指針,一個是接收到數據的長度- 通過大循環不斷調用
USART2_DMA_Rx
函數處理接收到的數據;
實驗現象:
實驗是在application
環境下的,通過給USART2
發送數據,USART2
接收到的數據后再從USART1
里打印出來。實驗現象如下圖:
左邊是給USART2
發送數據,右邊是將接收到的數據通過USART1
打印出來。
***注意: ***
使用這個方法接收串口的數據有優點也有缺點
- 優點是:效率比較高,使用比較方便
- 缺點是:實時性的要求比較高。如果緩沖區比較小,實時性又比較低;當有大量數據連續傳來時,很容易就會造成數據的覆蓋。在單線程里面使用時,盡量少使用大的延時函數。
projrct
串口的發送就不羅嗦了,網上比較多的教程(當然想要發送的效率更高可以使用雙緩沖的方式,具體怎么設計就留給各位小伙伴門自己發揮了)。最后將工程的鏈接附上--->點我project!!!