STMF103C8T6串口通信
串口作爲 MCU 的重要外部接口,同時也是軟件開發重要的調試手段,其重要性不言而喻。關於STM32F103C8T6工程文件自己下載https://download.csdn.net/download/weixin_45488643/12522971這個僅僅是一個核心工程文件,只需要自己添加以下代碼就可以。
串口設置的一般步驟可以總結爲如下幾個步驟:
- 串口時鐘使能, GPIO 時鐘使能
- 串口復位
- GPIO 端口模式設置
- 串口參數初始化
- 開啓中斷並且初始化 NVIC(如果需要開啓中斷才需要這個步驟)
- 使能串口
- 編寫中斷處理函數
串口相關的固件函數:
1.串口時鐘使能。 串口是掛載在 APB2 下面的外設,所以使能函數爲:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1);
2.串口復位。 當外設出現異常的時候可以通過復位設置,實現該外設的復位,然後重新配置這個外設達到讓其重新工作的目的。一般在系統剛開始配置外設的時候,都會先執行復位該外設的操作。復位的是在函數 USART_DeInit()中完成:
void USART_DeInit(USART_TypeDef* USARTx);//串口復位
比如我們要復位串口 1,方法爲:
USART_DeInit(USART1); //復位串口 1
3.串口參數初始化。 串口初始化是通過 USART_Init()函數實現的,
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
這個函數的的第一個入口參數是指定初始化的串口標號,這裏選USART1。
第二個入口參數是一個 USART_InitTypeDef 類型的結構體指針,這個結構體指針的成員變量用來設置串口的一些參數。一般的實現格式爲:
USART_InitStructure.USART_BaudRate = bound; //波特率;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長爲 8 位數據格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; //一個停止位
USART_InitStructure.USART_Parity = USART_Parity_No; //無奇偶校驗位
USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None; //無硬件數據流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
從上面的初始化格式可以看出初始化需要設置的參數爲:波特率,字長,停止位,奇偶校驗位,硬件數據流控制,模式(收,發)。我們可以根據需要設置這些參數。
4.數據發送與接收。 STM32 的發送與接收是通過數據寄存器 USART_DR 來實現的,這是一個雙寄存器,包含了 TDR 和 RDR。當向該寄存器寫數據的時候,串口就會自動發送,當收到收據的時候,也是存在該寄存器內。
STM32 庫函數操作 USART_DR 寄存器發送數據的函數是:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
通過該函數向串口寄存器 USART_DR 寫入一個數據。STM32 庫函數操作 USART_DR 寄存器讀取串口接收到的數據的函數是:
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
通過該函數可以讀取串口接受到的數據。
這裏我們關注一下兩個位,第 5、 6 位 RXNE 和 TC。RXNE(讀數據寄存器非空),當該位被置 1 的時候,就是提示已經有數據被接收到了,並且可以讀出來了。這時候我們要做的就是儘快去讀取 USART_DR,通過讀 USART_DR 可以將該位清零,也可以向該位寫 0,直接清除。TC(發送完成),當該位被置位的時候,表示 USART_DR 內的數據已經被髮送完成了。如果設置了這個位的中斷,則會產生中斷。該位也有兩種清零方式: 1)讀 USART_SR,寫USART_DR。
2)直接向該位寫 0。
讀取串口狀態的函數是:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
這個函數的第二個入口參數非常關鍵,它是標示我們要查看串口的哪種狀態,比如上面講解的RXNE(讀數據寄存器非空)以及 TC(發送完成)。例如我們要判斷讀寄存器是否非空(RXNE),操作庫函數的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
我們要判斷髮送是否完成(TC),操作庫函數的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_TC);
這些標識號在 MDK 裏面是通過宏定義定義的:
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TXE ((uint16_t)0x0727)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
#define USART_IT_IDLE ((uint16_t)0x0424)
#define USART_IT_LBD ((uint16_t)0x0846)
#define USART_IT_CTS ((uint16_t)0x096A)
#define USART_IT_ERR ((uint16_t)0x0060)
#define USART_IT_ORE ((uint16_t)0x0360)
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
6.串口使能。 串口使能是通過函數 USART_Cmd()來實現的,這個很容易理解,使用方法是:
USART_Cmd(USART1, ENABLE); //使能串口
7.開啓串口響應中斷。 有些時候當我們還需要開啓串口中斷,那麼我們還需要使能串口中斷,使能串口中斷的函數是:
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT,FunctionalState NewState)
這個函數的第二個入口參數是標示使能串口的類型,也就是使能哪種中斷,因爲串口的中斷類型有很多種。比如在接收到數據的時候(RXNE 讀數據寄存器非空),我們要產生中斷,那麼我們開啓中斷的方法是:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啓中斷,接收到數據中斷
我們在發送數據結束的時候(TC,發送完成)要產生中斷,那麼方法是:
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
8.獲取相應中斷狀態。 當我們使能了某個中斷的時候,當該中斷髮生了,就會設置狀態寄存器中的某個標誌位。經常我們在中斷處理函數中,要判斷該中斷是哪種中斷,使用的函數是:
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
比如我們使能了串口發送完成中斷,那麼當中斷髮生了, 我們便可以在中斷處理函數中調用這個函數來判斷到底是否是串口發送完成中斷,方法是:
USART_GetITStatus(USART1, USART_IT_TC)
返回值是 SET,說明是串口發送完成中斷髮生。
main
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
int main(void)
{
u8 t;
u8 len;
u16 times=0;
delay_init(); //延時函數初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 設置中斷優先級分組2
uart_init(9600); //串口初始化爲9600
LED_Init(); //初始化與LED連接的硬件接口
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的數據長度
printf("\r\n您發送的消息爲:\r\n");
for(t=0;t<len;t++)
{
USART1->DR=USART_RX_BUF[t];
while((USART1->SR&0X40)==0);//等待發送結束
}
printf("\r\n\r\n");//插入換行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\nSTM32F103C8T6開發板 串口實驗\r\n");
printf("QingYeMuRong\r\n\r\n\r\n");
}
if(times%200==0)printf("請輸入數據,以回車鍵結束\r\n");
if(times%30==0)LED0=!LED0;//閃爍LED,提示系統正在運行.
delay_ms(10);
}
}
}
usart.c
#include "sys.h"
#include "usart.h"
//如果使用ucos,則包括下面的頭文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h" //ucos 使用
#endif
#if 1
#pragma import(__use_no_semihosting)
//標準庫需要的支持函數
struct __FILE
{
int handle;
};
FILE __stdout;
//定義_sys_exit()以避免使用半主機模式
_sys_exit(int x)
{
x = x;
}
//重定義fputc函數
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循環發送,直到發送完畢
USART1->DR = (u8) ch;
return ch;
}
#endif
#if EN_USART1_RX //如果使能了接收
//串口1中斷服務程序
//注意,讀取USARTx->SR能避免莫名其妙的錯誤
u8 USART_RX_BUF[USART_REC_LEN]; //接收緩衝,最大USART_REC_LEN個字節.
//接收狀態
//bit15, 接收完成標誌
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字節數目
u16 USART_RX_STA=0; //接收狀態標記
void uart_init(u32 bound){
//GPIO端口設置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA時鐘
//USART1_TX GPIOA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //複用推輓輸出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
//USART1_RX GPIOA.10初始化
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶佔優先級3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器
//USART 初始化設置
USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長爲8位數據格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式
USART_Init(USART1, &USART_InitStructure); //初始化串口1
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啓串口接受中斷
USART_Cmd(USART1, ENABLE); //使能串口1
}
void USART1_IRQHandler(void) //串口1中斷服務程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS爲真,則需要支持OS.
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的數據必須是0x0d 0x0a結尾)
{
Res =USART_ReceiveData(USART1); //讀取接收到的數據
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始
else USART_RX_STA|=0x8000; //接收完成了
}
else //還沒收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數據錯誤,重新開始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS爲真,則需要支持OS.
OSIntExit();
#endif
}
#endif
usart.h
#ifndef __USART_H
#define __USART_H
#include "stdio.h"
#include "sys.h"
#define USART_REC_LEN 200 //定義最大接收字節數 200
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收
extern u8 USART_RX_BUF[USART_REC_LEN]; //接收緩衝,最大USART_REC_LEN個字節.末字節爲換行符
extern u16 USART_RX_STA; //接收狀態標記
//如果想串口中斷接收,請不要註釋以下宏定義
void uart_init(u32 bound);
#endif