UART 是一般嵌入式系統最常用的 debug 介面,通常 PC 端必需要有接收的硬體。不過 neculeo 的電路版已經包含了一個 virtual com port 連接到 USART2。所以只要設定好 USART2 就能在 PC 上透過終端機程式(ex: putty )與 neculeo 連線了。
STM32 週邊的初始化包含下面的步驟
- 設定腳位:由於腳位的數量有限,所以有些週邊會共用接腳。而且為了使用上的彈性,週邊也可能有一組以上的腳位可選擇。所以使用前要將腳位設定為正確的模式。
- 設定週邊:設定週邊的硬體參數
- 設定中斷:如果週邊在應用上會使用到中斷,需要設定 NVIC
另外,STM32 的週邊預設 clock 是關閉的,所以使用任何的週邊都需先開啟該週邊的 clock ,包含 GPIO 也是要先開啟 clock 。
3.1 設定 GPIO
USART2 使用的腳位是 GPIOA2 和 GPIOA3,先將這兩隻腳位設定給 USART 使用。
- 呼叫 RCC_AHB1PeriphClockCmd() 打開 GPIOA 的 clock
- 宣告一個 GPIO_InitTypeDef 的 struct 準備給 GPIO_Init() 使用
- 呼叫 GPIO_StructInit() 將 GPIO_InitTypeDef 的 struct 進行初始化。當然你也可以手動對 struct 內的 member 設定,但如果未來更新了 stm32 提供的 library , GPIO_InitTypeDef 的 member 可能會改變,那這段程式碼可能就會出問題。使用第三方提供的 library 時,最好依它提供的方式使用
- 修改 GPIO_InitTypeDef struct 的內容
- 呼叫 GPIO_Init() 設定 GPIO ,目前只是將 GPIO_Mode 設定為週邊使用,但還沒指定給哪個週邊
- 呼叫 GPIO_PinAFConfig() 將 GPIOA2 和 GPIOA3 指定為 USART2 使用
詳細的程式碼如下:
// Enable clock of GPIOA
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// Configure pins PA2 and PA3 for Alternate function Mode
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_StructInit(&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Configure pins PA2 and PA3 for USART2
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
3.2 設定 USART
USART 的設定使用預設值即可,只要修改 BaudRate 為需要的速度,這邊是設為 115200。設定的流程與 GPIO 類似。USART 的 API 放在 stm32f4xx_usart.c 中,記得要修改 makefile 將它加入編譯。
- 呼叫 RCC_AHB1PeriphClockCmd() 打開 USART2 的 clock
- 宣告一個 USART_InitTypeDef 的 struct 準備給 USART_Init() 使用
- 呼叫 USART_StructInit() 將 USART_InitTypeDef 的 struct 進行初始化
- 修改 USART_InitTypeDef struct 的內容
- 呼叫 USART_Init() 設定 USART2
- 呼叫 USART_Cmd 讓 USART2 開始工作
詳細的程式碼如下:
// Enable clock of USART2
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
// Configure USART2
USART_InitTypeDef USART_InitStructure;
USART_StructInit(&USART_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_Init(USART2, &USART_InitStructure);
// Enable USART2
USART_Cmd(USART2, ENABLE);
USART 設定完成後,即可透過 USART_SendData() 輸出字元。每送出一個字元必須等待硬體完成傳送才能送出下一個,所以要使用 USART_GetFlagStatus() 等待 USART_FLAG_TXE 這個 flag。
USART_SendData(USART2, Data);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET );
3.3 將 printf 導向 UART
USART_SendData 只能送出單一字元,如果想要輸出變數還是 printf() 來得方便。想要使用 printf() 就必需實現它所使用到的系統呼叫。新增一個檔案 syscall.c ,將下面的程式碼貼上,加入 makefile 一起編譯就可以使用 printf() 了。
#include <sys/stat.h>
#include <errno.h>
#undef errno
extern int errno;
register char *stack_ptr asm ("sp");
/**
* @brief Close a file
* @param file
* @return
*/
int _close(int file)
{
return -1;
}
/**
* @brief Status of an open files
* @param file
* @param st
* @return
*/
int _fstat(int file, struct stat *st)
{
st->st_mode = S_IFCHR;
return 0;
}
/**
* @brief Query whether output stream is a terminal
* @param file
* @return
*/
int _isatty(int file)
{
return 1;
}
/**
* @brief Set position in a file
* @param file
* @param ptr
* @param dir
* @return
*/
int _lseek(int file, int ptr, int dir)
{
return 0;
}
/**
* @brief Read from a file
* @param file
* @param ptr
* @param len
* @return
*/
int _read(int file, char *ptr, int len)
{
return 0;
}
/**
* @brief Write to a file
* @param file
* @param ptr
* @param len
* @return
*/
int _write(int file, char *ptr, int len)
{
int todo;
for (todo = 0; todo < len; todo++)
{
USART_SendData(USART2, *ptr);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
ptr++;
}
return len;
}
/**
* @brief Increase program data space
* @param incr
* @return
*/
caddr_t _sbrk(int incr)
{
extern char _end; /* Defined by the linker */
static char *heap_end;
char *prev_heap_end;
if (heap_end == 0)
{
heap_end = &_end;
}
prev_heap_end = heap_end;
if (heap_end + incr > stack_ptr)
{
_write(1, "Heap and stack collision\n", 25);
while(1);
}
heap_end += incr;
return (caddr_t) prev_heap_end;
}
3.4 設定 USART 接收中斷
在 USART_Cmd(USART2, ENABLE ) 之前加入開啟 USART RX 中斷的遮照,並設定 NVIC。
// Configure USART2 interrupt
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 10;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
NVIC_EnableIRQ(USART2_IRQn);
新增 USART interrupt handler 處理中斷
void USART2_IRQHandler(void)
{
while (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
{
char data = USART_ReceiveData(USART2);
USART_SendData(USART2, data);
while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);
}
}
上面的程式碼只是將接收到的字元再傳送出去, 如果要對接收的資料進一步的分析,需再另外撰寫程式處理。
留言
張貼留言