各個串口的引腳說明:


如果是同步通信,則會使用到SCLK同步時鍾,下面是它的結構體,如果是異步通信,就用不上(實際中常用的是異步通信)

USART串口通信使用到固件庫函數:


下面是要做的實驗:




===================================== (1)編寫簡單的串口通信實驗:從開發板發送數據到電腦的串口調試助手
(1)拷貝工程模板,改工程名為:USART,在main.c同級目錄下新建目錄usart,在usart目錄下新建兩個文件:bsp_usart.c、bsp_usart.h
(2)bsp_usart.h
#ifndef __BSP_USART_H__ #define __BSP_USART_H__ #include "stm32f10x.h" // ----------------------- 串口1-USART1 // 使用哪個串口(串口1..5) #define DEBUG_USARTx USART1 // APB2串口的同步時鍾 #define DEBUG_USART_CLK RCC_APB2Periph_USART1 // APB2系統時鍾(因為串口USART1是掛載到APB2總線上的,所以要打開APB2總線的時鍾) #define DEBUG_USART_APBxClkCmd RCC_APB2PeriphClockCmd // 串口通信的波特率 #define DEBUG_USART_BAUDRATE 115200 // ----------------------- USART GPIO 引腳宏定義 // GPIO引腳 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) // APB2系統時鍾(因為串口USART1是掛載到APB2總線上的,所以要打開APB2總線的時鍾) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB2PeriphClockCmd // GPIO引腳,發送接PA9,接收接PA10 #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_9 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_10 #define DEBUG_USART_IRQ USART1_IRQn #define DEBUG_USART_IRQHandler USART1_IRQHandler /* 串口調試配置函數:配置串口的相關參數,使能串口 */ void DEBUG_USART_Config(void); #endif /* __BSP_USART_H__ */
(3)bsp_usart.c
#include "./usart/bsp_usart.h"
/* 串口調試配置函數:配置串口的相關參數,使能串口 */
void DEBUG_USART_Config(void)
{
/* 結構體變量聲明 */
GPIO_InitTypeDef GPIO_InitStructure; // GPIO
USART_InitTypeDef USART_InitStructure; // USART
/* ------------ 第一步:初始化GPIO */
// 打開串口GPIO的時鍾
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 將USART Tx的GPIO配置為推挽復用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; // 引腳
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 速率
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 初始化結構體
// 將USART Rx的GPIO配置為浮空輸入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
/* ------------ 第二步:配置串口的初始化結構體 */
// 打開串口外設的時鍾
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
/* 配置串口的工作參數 */
// 波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 針數據字長
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 停止位
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(DEBUG_USARTx, &USART_InitStructure);
/* ------------ 第三步:使能串口 */
USART_Cmd(DEBUG_USARTx, ENABLE);
}
(4)main.c
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
int main(void)
{
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
USART_SendData(DEBUG_USARTx, 'Q');
while(1);
}
(5)實驗現象:
1.首先確保電腦已經安裝了USB轉串口的驅動CH340;

2.使用單片機的USB線將電腦USB口和USB轉串口接口連接,編譯代碼,並燒錄到單片機中;

3.打開秉火串口調試助手V1.0.exe,並設置好參數(和程序中參數一致);

4.按下單片機上的RESET鍵,在串口助手上就可以接收到數據

===================================== (2)發送一個字節的數據
(1)在bsp_usart.h中添加:
/* 發送一個字節 */ void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch);
(2)在bsp_usart.c中添加:
/* 發送一個字節 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch)
{
/* 發送一個字節數據到USART */
USART_SendData(pUSARTx, ch);
/* 等待發送數據寄存器為空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
(3)main.c
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
int main(void)
{
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
//USART_SendData(DEBUG_USARTx, 'Q');
Usart_SendByte(DEBUG_USARTx, 0x42);
while(1);
}
實驗現象:
(1)發送的數據是42,卻出現了FF(屬於亂碼)

(2)把波特率改為:19200,就不會有亂碼和其他奇怪的字符了,可能發送數據的速度太快了,又沒有進行校驗位校驗,而且是異步通信,沒有
系統時鍾進行同步,數據在傳輸過程中出現了重疊導致的錯誤。(具體原因還有待研究)

===================================== (3)發送字符串
(1)在bsp_usart.h中添加:
/* 發送字符串 */ void Usart_SendString(USART_TypeDef* pUSARTx, char* str);
(2)在bsp_usart.c中添加:
/* 發送字符串 */
void Usart_SendString(USART_TypeDef* pUSARTx, char* str)
{
unsigned int k=0;
do
{
Usart_SendByte(pUSARTx, *(str + k));
k++;
} while(*(str + k)!='\0');
/* 等待發送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC) == RESET);
}
(3)修改main.c
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
int main(void)
{
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
//USART_SendData(DEBUG_USARTx, 'Q'); // 發送一個字符
//Usart_SendByte(DEBUG_USARTx, 0x42); // 發送一個字節
Usart_SendString(DEBUG_USARTx, "歡迎使用STM32F103RCT6!"); // 發送字符串
while(1);
}
實驗現象:出現亂碼

用記事本打開本地的main.c文件,另存為ANSI格式(一定要保證本地和keil中改文件要是相同的格式,同是ANSI或同是UTF-8)。
重新編譯燒錄到開發板中,再試:

把波特率改成115200,再試:數據不整齊,可能會有亂碼

===================================== (4)使用C語言的printf函數輸出數據到上位機
main.c
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "stdio.h"
int main(void)
{
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
//USART_SendData(DEBUG_USARTx, 'Q'); // 發送一個字符
//Usart_SendByte(DEBUG_USARTx, 0x42); // 發送一個字節
//Usart_SendString(DEBUG_USARTx, "歡迎使用STM32F103RCT6!"); // 發送字符串
// 使用printf函數將數據輸出到上位機
printf("使用printf函數將數據輸出到上位機");
while(1);
}
實驗現象:將程序燒錄到單片機中,按Reset鍵並不會打印數據
原因:使用printf發送數據到上位機的過程是,printf -> fputchar -> USART_SendData函數 -> 電腦的上位機
所以下面將USART_SendData函數替換掉fputchar函數即可
(1)在bsp_usart.c中添加:
/* 重定向c庫函數printf到串口,重定向后可使用printf函數 */
int fputc(int ch, FILE *f)
{
/* 發送一個字節數據到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
/* 重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar等函數 */
int fgetc(FILE *f)
{
/* 等待串口輸入數據 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
(2)在bsp_usart.h中添加庫文件:
#include "stdio.h"
(3)修改main.c
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
int main(void)
{
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
//USART_SendData(DEBUG_USARTx, 'Q'); // 發送一個字符
//Usart_SendByte(DEBUG_USARTx, 0x42); // 發送一個字節
//Usart_SendString(DEBUG_USARTx, "歡迎使用STM32F103RCT6!"); // 發送字符串
// 使用printf函數將數據輸出到上位機
printf("使用printf函數將數據輸出到上位機\r\n");
while(1);
}
(4)如果使用的是MDK,請在工程屬性的“Target“-》”Code Generation“中勾選”Use MicroLIB“
實驗現象:按下RESET鍵,串口助手上打印顯示數據

========================== (5)上位機發送數據到單片機,單片機將數據返回給上位機
原理是:在bsp_usart.c串口配置函數中添加串口中斷配置函數,當單片機接收到上位機發過來的數據之后產生一個中斷,就會去bsp_usart.h中找
中斷函數的宏定義 USART1_IRQHandler,進而找到中斷服務函數的宏定義名函數名 DEBUG_USART_IRQHandler,找到中斷函數響應文件stm32f10x_it.c,
找到中斷服務函數 DEBUG_USART_IRQHandler,在這個函數里面接收上位機發送過來的數據,並將此數據返回到上位機,
上位機接收到數據之后打印出來。
(1)bsp_usart.c,在函數void DEBUG_USART_Config(void)中添加
/* -------------------------------------------------------- */ // 串口中斷優先級配置 NVIC_Configuration(); // 使能串口接收中斷 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); /* -------------------------------------------------------- */
完整文件:
#include "./usart/bsp_usart.h"
/* 串口中斷配置函數 */
static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
/* 嵌套向量中斷控制器組選擇 */
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/* 配置USART為中斷源 */
NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
/* 搶斷優先級*/
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
/* 子優先級 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
/* 使能中斷 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
/* 初始化配置NVIC */
NVIC_Init(&NVIC_InitStructure);
}
/* 串口調試配置函數:配置串口的相關參數,使能串口 */
void DEBUG_USART_Config(void)
{
/* 結構體變量聲明 */
GPIO_InitTypeDef GPIO_InitStructure; // GPIO
USART_InitTypeDef USART_InitStructure; // USART
/* ------------ 第一步:初始化GPIO */
// 打開串口GPIO的時鍾
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 將USART Tx的GPIO配置為推挽復用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN; // 引腳
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 速率
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure); // 初始化結構體
// 將USART Rx的GPIO配置為浮空輸入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
/* ------------ 第二步:配置串口的初始化結構體 */
// 打開串口外設的時鍾
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
/* 配置串口的工作參數 */
// 波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 針數據字長
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 停止位
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(DEBUG_USARTx, &USART_InitStructure);
/* -------------------------------------------------------- */
// 串口中斷優先級配置
NVIC_Configuration();
// 使能串口接收中斷
USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);
/* -------------------------------------------------------- */
/* ------------ 第三步:使能串口 */
USART_Cmd(DEBUG_USARTx, ENABLE);
}
/* 發送一個字節 */
void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch)
{
/* 發送一個字節數據到USART */
USART_SendData(pUSARTx, ch);
/* 等待發送數據寄存器為空 */
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
}
/* 發送字符串 */
void Usart_SendString(USART_TypeDef* pUSARTx, char* str)
{
unsigned int k=0;
do
{
Usart_SendByte(pUSARTx, *(str + k));
k++;
} while(*(str + k)!='\0');
/* 等待發送完成 */
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC) == RESET);
}
/* 重定向c庫函數printf到串口,重定向后可使用printf函數 */
int fputc(int ch, FILE *f)
{
/* 發送一個字節數據到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
/* 重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar等函數 */
int fgetc(FILE *f)
{
/* 等待串口輸入數據 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
(2)在stm32f10x_it.c
/* 串口中斷服務函數,DEBUG_USART_IRQHandler函數名在bsp_usart.h中宏定義 */
void DEBUG_USART_IRQHandler(void)
{
// 聲明一個變量暫時存放接收過來的數據
uint8_t ucTemp;
// 判斷是否接收到數據
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
{
// 如果接收到數據,那么從DEBUG_USARTx串口(在bsp_usart.h中定義為USART1)獲取數據
ucTemp = USART_ReceiveData(DEBUG_USARTx);
// 最后將數據返回到上位機
USART_SendData(DEBUG_USARTx, ucTemp);
}
}
(3)main.c不用修改
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
int main(void)
{
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
//USART_SendData(DEBUG_USARTx, 'Q'); // 發送一個字符
//Usart_SendByte(DEBUG_USARTx, 0x42); // 發送一個字節
//Usart_SendString(DEBUG_USARTx, "歡迎使用STM32F103RCT6!"); // 發送字符串
// 使用printf函數將數據輸出到上位機
//printf("使用printf函數將數據輸出到上位機\r\n");
while(1);
}
實驗現象:

以上的stm32f10x_it.c函數每接收一個數據的時候就產生一次中斷,這樣對CPU的負荷比較大,在實際的項目當中不會這么操作,而是使用隊列的形式來發送數據,
即等上位機將所有數據都發送到開發板之后,開發板再將數據一次性發送到上位機(只產生一次中斷)
/* 串口中斷服務函數,DEBUG_USART_IRQHandler函數名在bsp_usart.h中宏定義 */
void DEBUG_USART_IRQHandler(void)
{
// 聲明一個變量暫時存放接收過來的數據
uint8_t ucTemp;
// 判斷是否接收到數據
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
{
// 如果接收到數據,那么從DEBUG_USARTx串口(在bsp_usart.h中定義為USART1)獲取數據
ucTemp = USART_ReceiveData(DEBUG_USARTx);
// 最后將數據返回到上位機
USART_SendData(DEBUG_USARTx, ucTemp);
}
}
以下是環形隊列發送數據:

(1)rx_data_queue.h
#ifndef __ESP_DATA_QUEUE_H_
#define __ESP_DATA_QUEUE_H_
#include "stm32f10x.h"
#include <string.h>
#include <stdio.h>
//緩沖隊列的個數需要為2的冪
#define QUEUE_NODE_NUM (2) //緩沖隊列的大小(有多少個緩沖區)
#define QUEUE_NODE_DATA_LEN (2*1024 ) //單個接收緩沖區大小
// 隊列的主體數據類型接口
#define QUEUE_DATA_TYPE ESP_USART_FRAME
// 隊列的調試輸出接口
#define DATA_QUEUE_LOG QUEUE_DEBUG
#define DATA_QUEUE_LOG_ARRAY QUEUE_DEBUG_ARRAY
// 數據主體
typedef struct
{
char *head; // 緩沖區頭指針
uint16_t len; // 接收到的數據長度
}ESP_USART_FRAME;
// 隊列結構
typedef struct {
int size; /* 緩沖區大小 */
int read; /* 讀指針 */
int write; /* 寫指針 */
int read_using; /* 正在讀取的緩沖區指針 */
int write_using; /* 正在寫入的緩沖區指針 */
QUEUE_DATA_TYPE *elems[QUEUE_NODE_NUM]; /* 緩沖區地址 */
} QueueBuffer;
extern QueueBuffer rx_queue;
/* 信息輸出 */
#define QUEUE_DEBUG_ON 1
#define QUEUE_DEBUG_ARRAY_ON 1
#define QUEUE_INFO(fmt,arg...) printf("<<-QUEUE-INFO->> "fmt"\n",##arg)
#define QUEUE_ERROR(fmt,arg...) printf("<<-QUEUE-ERROR->> "fmt"\n",##arg)
#define QUEUE_DEBUG(fmt,arg...) do{\
if(QUEUE_DEBUG_ON)\
printf("<<-QUEUE-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
#define QUEUE_DEBUG_ARRAY(array, num) do{\
int32_t i;\
uint8_t* a = array;\
if(QUEUE_DEBUG_ARRAY_ON)\
{\
printf("\n<<-QUEUE-DEBUG-ARRAY->>\n");\
for (i = 0; i < (num); i++)\
{\
printf("%02x ", (a)[i]);\
if ((i + 1 ) %10 == 0)\
{\
printf("\n");\
}\
}\
printf("\n");\
}\
}while(0)
//輸出隊列的狀態信息
#define cbPrint(cb) DATA_QUEUE_LOG("size=0x%x, read=%d, write=%d\n", cb.size, cb.read, cb.write);\
DATA_QUEUE_LOG("size=0x%x, read_using=%d, write_using=%d\n", cb.size, cb.read_using, cb.write_using);
QUEUE_DATA_TYPE* cbWrite(QueueBuffer *cb);
QUEUE_DATA_TYPE* cbRead(QueueBuffer *cb);
void cbReadFinish(QueueBuffer *cb);
void cbWriteFinish(QueueBuffer *cb);
//void cbPrint(QueueBuffer *cb);
QUEUE_DATA_TYPE* cbWriteUsing(QueueBuffer *cb) ;
int cbIsFull(QueueBuffer *cb) ;
int cbIsEmpty(QueueBuffer *cb) ;
void rx_queue_init(void);
void pull_data_from_queue(void);
void push_data_to_queue(char *src_dat,uint16_t src_len);
#endif
(2)rx_data_queue.c
/**
******************************************************************************
* @file rx_data_queue.c
* @author fire
* @version V1.0
* @date 2015-01-xx
* @brief 環形緩沖區,適用於接收外部數據時用作緩沖
******************************************************************************
* @attention
*
* 實驗平台:秉火 IOT STM32 開發板
* 論壇 :http://www.firebbs.cn
* 淘寶 :https://fire-stm32.taobao.com
*
******************************************************************************
*/
#include "./usart/rx_data_queue.h"
//實例化節點數據類型
QUEUE_DATA_TYPE node_data[QUEUE_NODE_NUM];
//實例化隊列類型
QueueBuffer rx_queue;
//隊列緩沖區的內存池
__align(4) char node_buff[QUEUE_NODE_NUM][QUEUE_NODE_DATA_LEN] ;
/*環形緩沖隊列*/
/**
* @brief 初始化緩沖隊列
* @param cb:緩沖隊列結構體
* @param size: 緩沖隊列的元素個數
* @note 初始化時還需要給cb->elems指針賦值
*/
void cbInit(QueueBuffer *cb, int size)
{
cb->size = size; /* maximum number of elements */
cb->read = 0; /* index of oldest element */
cb->write = 0; /* index at which to write new element */
// cb->elems = (uint8_t *)calloc(cb->size, sizeof(uint8_t)); //elems 要額外初始化
}
//(此函數改成了宏,在頭文件)
/**
* @brief 輸出緩沖隊列當前的狀態信息
* @param cb:緩沖隊列結構體
*/
//void cbPrint(QueueBuffer *cb)
//{
// DATA_QUEUE_LOG("size=0x%x, read=%d, write=%d\n", cb->size, cb->read, cb->write);
// DATA_QUEUE_LOG("size=0x%x, read_using=%d, write_using=%d\n", cb->size, cb->read_using, cb->write_using);
//}
/**
* @brief 判斷緩沖隊列是(1)否(0)已滿
* @param cb:緩沖隊列結構體
*/
int cbIsFull(QueueBuffer *cb)
{
return cb->write == (cb->read ^ cb->size); /* This inverts the most significant bit of read before comparison */
}
/**
* @brief 判斷緩沖隊列是(1)否(0)全空
* @param cb:緩沖隊列結構體
*/
int cbIsEmpty(QueueBuffer *cb)
{
return cb->write == cb->read;
}
/**
* @brief 對緩沖隊列的指針加1
* @param cb:緩沖隊列結構體
* @param p:要加1的指針
* @return 返回加1的結果
*/
int cbIncr(QueueBuffer *cb, int p)
{
return (p + 1)&(2*cb->size-1); /* read and write pointers incrementation is done modulo 2*size */
}
/**
* @brief 獲取可寫入的緩沖區指針
* @param cb:緩沖隊列結構體
* @return 可進行寫入的緩沖區指針
* @note 得到指針后可進入寫入操作,但寫指針不會立即加1,
寫完數據時,應調用cbWriteFinish對寫指針加1
*/
QUEUE_DATA_TYPE* cbWrite(QueueBuffer *cb)
{
if (cbIsFull(cb)) /* full, overwrite moves read pointer */
{
return NULL;
}
else
{
//當wriet和write_using相等時,表示上一個緩沖區已寫入完畢,需要對寫指針加1
if(cb->write == cb->write_using)
{
cb->write_using = cbIncr(cb, cb->write); //未滿,則增加1
}
}
return cb->elems[cb->write_using&(cb->size-1)];
}
/**
* @brief 數據寫入完畢,更新寫指針到緩沖結構體
* @param cb:緩沖隊列結構體
*/
void cbWriteFinish(QueueBuffer *cb)
{
cb->write = cb->write_using;
}
/**
* @brief 獲取可讀取的緩沖區指針
* @param cb:緩沖隊列結構體
* @return 可進行讀取的緩沖區指針
* @note 得到指針后可進入讀取操作,但讀指針不會立即加1,
讀取完數據時,應調用cbReadFinish對讀指針加1
*/
QUEUE_DATA_TYPE* cbRead(QueueBuffer *cb)
{
if(cbIsEmpty(cb))
return NULL;
//當read和read_using相等時,表示上一個緩沖區已讀取完畢(即已調用cbReadFinish),
//需要對寫指針加1
if(cb->read == cb->read_using)
cb->read_using = cbIncr(cb, cb->read);
return cb->elems[cb->read_using&(cb->size-1)];
}
/**
* @brief 數據讀取完畢,更新讀指針到緩沖結構體
* @param cb:緩沖隊列結構體
*/
void cbReadFinish(QueueBuffer *cb)
{
//重置當前讀完的數據節點的長度
cb->elems[cb->read_using&(cb->size-1)]->len = 0;
cb->read = cb->read_using;
}
//隊列的指針指向的緩沖區全部銷毀
void camera_queue_free(void)
{
uint32_t i = 0;
for(i = 0; i < QUEUE_NODE_NUM; i ++)
{
if(node_data[i].head != NULL)
{
//若是動態申請的空間才要free
// free(node_data[i].head);
node_data[i].head = NULL;
}
}
return;
}
/**
* @brief 緩沖隊列初始化,分配內存,使用緩沖隊列時,
* @param 無
* @retval 無
*/
void rx_queue_init(void)
{
uint32_t i = 0;
memset(node_data, 0, sizeof(node_data));
/*初始化緩沖隊列*/
cbInit(&rx_queue,QUEUE_NODE_NUM);
for(i = 0; i < QUEUE_NODE_NUM; i ++)
{
node_data[i].head = node_buff[i];
/*初始化隊列緩沖指針,指向實際的內存*/
rx_queue.elems[i] = &node_data[i];
DATA_QUEUE_LOG("node_data[i].head=0x%x,\r\nrx_queue.elems[i] =0x%x", (uint32_t)node_data[i].head,(uint32_t)rx_queue.elems[i]->head);
memset(node_data[i].head, 0, QUEUE_NODE_DATA_LEN);
}
cbPrint(rx_queue);
}
/**
* @brief 往隊列中寫入數據的樣例
*/
void push_data_to_queue(char *src_dat,uint16_t src_len)
{
QUEUE_DATA_TYPE *data_p;
uint8_t i;
for(i=0;i<src_len;i++)
{
/*獲取寫緩沖區指針,准備寫入新數據*/
data_p = cbWrite(&rx_queue);
if (data_p != NULL) //若緩沖隊列未滿,開始傳輸
{
//往緩沖區寫入數據,如使用串口接收、dma寫入等方式
*(data_p->head + i) = src_dat[i];
data_p->len++;
printf("\r\ndata_p->len =%d",data_p->len);
}else return;
cbPrint(rx_queue);
}
/*寫入緩沖區完畢*/
cbWriteFinish(&rx_queue);
cbPrint(rx_queue);
}
/**
* @brief 從隊列中取數據的樣例
*/
void pull_data_from_queue(void)
{
QUEUE_DATA_TYPE *rx_data;
/*從緩沖區讀取數據,進行處理,*/
rx_data = cbRead(&rx_queue);
if(rx_data != NULL)//緩沖隊列非空
{
//加上字符串結束符,方便直接輸出字符串
*(rx_data->head+rx_data->len) = '\0';
QUEUE_DEBUG("接收到的數據:%s",rx_data->head);
QUEUE_DEBUG_ARRAY((uint8_t*)rx_data->head,rx_data->len);
//使用完數據必須調用cbReadFinish更新讀指針
cbReadFinish(&rx_queue);
}
}
stm32f10x_it.c
// 串口中斷服務函數
// 把接收到的數據寫入緩沖區,在main函數中輪詢緩沖區輸出數據
void DEBUG_USART_IRQHandler(void)
{
uint8_t ucCh;
QUEUE_DATA_TYPE *data_p;
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
ucCh = USART_ReceiveData( DEBUG_USARTx );
/*獲取寫緩沖區指針,准備寫入新數據*/
data_p = cbWrite(&rx_queue);
if (data_p != NULL) //若緩沖隊列未滿,開始傳輸
{
//往緩沖區寫入數據,如使用串口接收、dma寫入等方式
*(data_p->head + data_p->len) = ucCh;
if( ++data_p->len >= QUEUE_NODE_DATA_LEN)
{
cbWriteFinish(&rx_queue);
}
}else return;
}
if ( USART_GetITStatus( DEBUG_USARTx, USART_IT_IDLE ) == SET )//數據幀接收完畢
{
/*寫入緩沖區完畢*/
cbWriteFinish(&rx_queue);
ucCh = USART_ReceiveData( DEBUG_USARTx ); //由軟件序列清除中斷標志位(先讀USART_SR,然后讀USART_DR)
}
}
在bsp_uart.c中的void DEBUG_USART_Config(void)函數中添加:
/* -------------------------------------------------------- */ // 串口中斷優先級配置 NVIC_Configuration(); // 使能串口接收中斷【環形隊列接收數據配置】 USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE); USART_ITConfig(DEBUG_USARTx, USART_IT_IDLE, ENABLE); //使能串口總線空閑中斷 /* -------------------------------------------------------- */
在main.c文件中調用環形隊列發送數據函數:
/**
******************************************************************************
* @file main.c
* @author fire
* @version V1.0
* @date 2013-xx-xx
* @brief 使用環形緩沖區的方式接收串口數據
******************************************************************************
* @attention
*
* 實驗平台:秉火 F103-MINI STM32 開發板
* 論壇 :http://www.firebbs.cn
* 淘寶 :https://fire-stm32.taobao.com
*
******************************************************************************
*/
#include "stm32f10x.h"
#include "bsp_usart.h"
#include "./usart/rx_data_queue.h"
/**
* @brief 主函數
* @param 無
* @retval 無
*/
int main(void)
{
/*初始化USART 配置模式為 115200 8-N-1,中斷接收*/
USART_Config();
rx_queue_init();
/* 發送一個字符串 */
Usart_SendString( DEBUG_USARTx,"這是一個串口中斷接收回顯實驗\n");
printf("歡迎使用秉火STM32開發板\n\n\n\n");
while(1)
{
//獲取數據並輸出
//實際應用中可參考pull data的方式獲取數據進行處理
pull_data_from_queue();
}
}
/*********************************************END OF FILE**********************/
實驗現象:使用環形隊列方式發送數據

========================== (6)如何使用USART2、USART3、USART4、USART5進行通信?
根據引腳接線圖,可知,串口USART2接在APB1總線上,TX對應的引腳是PA2,TX對應的引腳是PA3

(1)將板子上的串口區域的TXD引腳和PA10引腳之間的跳帽拔掉,RXD引腳和PA9引腳之間的跳帽拔掉,使用兩根杜邦線將
TXD引腳和A2引腳連接,RXD引腳和A3引腳連接;
(2)修改bsp_usart.h
#ifndef __BSP_USART_H__ #define __BSP_USART_H__ #include "stm32f10x.h" #include "stdio.h" // ----------------------- 串口2-USART2 // 使用哪個串口(串口1..5),改為串口2 #define DEBUG_USARTx USART2 // APB1串口的同步時鍾,改為總線APB1 #define DEBUG_USART_CLK RCC_APB1Periph_USART2 // APB1系統時鍾(因為串口USART2是掛載到APB1總線上的,所以要打開APB1總線的時鍾) #define DEBUG_USART_APBxClkCmd RCC_APB1PeriphClockCmd // 串口通信的波特率 #define DEBUG_USART_BAUDRATE 19200 // ----------------------- USART GPIO 引腳宏定義 // GPIO引腳 #define DEBUG_USART_GPIO_CLK (RCC_APB2Periph_GPIOA) // APB1系統時鍾(因為串口USART2是掛載到APB1總線上的,所以要打開APB1總線的時鍾) #define DEBUG_USART_GPIO_APBxClkCmd RCC_APB1PeriphClockCmd // GPIO引腳,改成:發送接PA2,接收接PA3 #define DEBUG_USART_TX_GPIO_PORT GPIOA #define DEBUG_USART_TX_GPIO_PIN GPIO_Pin_2 #define DEBUG_USART_RX_GPIO_PORT GPIOA #define DEBUG_USART_RX_GPIO_PIN GPIO_Pin_3 // 中斷源改為串口2,在stm32f10x.h中 #define DEBUG_USART_IRQ USART2_IRQn // USART1_IRQHandler在startup_stm32f10x_hd.s的中斷向量表中定義了,改成串口2的中斷服務函數 #define DEBUG_USART_IRQHandler USART2_IRQHandler /* 串口調試配置函數:配置串口的相關參數,使能串口 */ void DEBUG_USART_Config(void); /* 發送一個字節 */ void Usart_SendByte(USART_TypeDef* pUSARTx, uint8_t ch); /* 發送字符串 */ void Usart_SendString(USART_TypeDef* pUSARTx, char* str); #endif /* __BSP_USART_H__ */
其他文件都不要修改
============================================================================
一、上位機給單片機發送數據,單片機有兩個方法可以接收數據:
(1)使用中斷。
單片機在檢測到有數據過來的時候,產生一個中斷,然后在中斷服務函數中將數據接收下來。
(2)使用庫函數重定向。
單片機使用getchar()函數將數據接收下來。需要編寫fgetc(FILE *f)重定向函數。
/* 重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar等函數 */
int fgetc(FILE *f)
{
/* 等待串口輸入數據 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
二、單片機給上位機發送數據,單片機有兩個方法可以發送數據:
(1)使用printf()函數。需要編寫fputc(int ch, FILE *f)重定向函數。其實函數底層還是使用USART_SendData函數
/* 重定向c庫函數printf到串口,重定向后可使用printf函數 */
int fputc(int ch, FILE *f)
{
/* 發送一個字節數據到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
(2)直接使用固件庫函數USART_SendData(DEBUG_USARTx, (uint8_t) ch)將數據發送到上位機。
============================================================================
========================== (7)使用getchar函數將數據發送到上位機
(1)為了不相互干涉,先把中斷函數屏蔽掉:


(2)在bsp_usart.c中添加 fgetc函數,用於單片機使用 getchar() 函數接收數據:
/* 重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar等函數 */
int fgetc(FILE *f)
{
/* 等待串口輸入數據 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
(3)main.c
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
int main(void)
{
// 接收到的數據存放到局部變量中
uint8_t temp;
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
//USART_SendData(DEBUG_USARTx, 'Q'); // 發送一個字符
//Usart_SendByte(DEBUG_USARTx, 0x42); // 發送一個字節
//Usart_SendString(DEBUG_USARTx, "歡迎使用STM32F103RCT6!"); // 發送字符串
// 使用printf函數將數據輸出到上位機
//printf("使用printf函數將數據輸出到上位機\r\n");
while(1)
{
// 使用getchar函數接收上位機數據
temp = getchar();
// 使用printf函數將數據返回到上位機
printf("接收到的字符為:%c\n", temp);
}
}
(4)實驗現象:
將程序燒錄到單片機,打開串口調試助手,發送一個字符 1 到單片機,單片機將字符 1 返回到串口調試助手

========================== (8)控制單片機上的LED燈的亮滅
(1)發送字符 1,D4亮D5滅
(2)發送字符 2,D4滅D5亮
(3)發送非 1/2字符,D4、D5均滅
-----------------------------------------------------------------------------------
(1)在main.c的同級目錄下新建目錄led,在led目錄下新建兩個文件bsp_led.c和bsp_led.h,並將這兩個文件添加到工程中的USER目錄下
bsp_led.h:
/* 和LED功能模塊相關的程序 */
#ifndef __BSP_LED_H__
#define __BSP_LED_H__
#include "stm32f10x.h"
/*宏定義*/
#define GPIO_CLK_D4 RCC_APB2Periph_GPIOC // 時鍾
#define GPIO_PORT_D4 GPIOC // C端口
#define GPIO_PIN_D4 GPIO_Pin_2 // PC2引腳
#define GPIO_CLK_D5 RCC_APB2Periph_GPIOC // 時鍾
#define GPIO_PORT_D5 GPIOC // C端口
#define GPIO_PIN_D5 GPIO_Pin_3 // PC2引腳
/*參數宏定義*/
/*
digitalTOGGLE(p,i)是參數宏定義,p表示LED的端口號,ODR是數據輸出寄存器,
查stm32f10x的官方中文手冊的第8.2章的ODR寄存器,要點亮LED,根據原理圖,要輸出低電平0,
C語言中,^表示異或,即a^b表示a和b不同時輸出為1,相同時輸出為0,比如0^1=1,1^1=0,0^0=0,
這里為什么操作ODR,p是什么?查看stm32f10x.h文件,搜索GPIO_TypeDef就會明白,
i是LED的引腳對應的位電平,經過digitalTOGGLE(p,i) {p->ODR ^= i;}之后,
第一次p為0,i一直為1,第一次異或結果輸出1,第二次輸出0,第三次輸出1,這樣間斷輸出010101,燈不斷亮滅
*/
// LED燈的狀態翻轉
//#define digitalTOGGLE(p,i) {p->ODR ^= i;}
// 輸出高電平(讓LED端口置1,BSRR寄存器用於位置1)
#define digitalHi(p,i) {p->BSRR = i;}
// 輸出低電平(讓LED端口置0,BRR寄存器用於位清除)
#define digitalLo(p,i) {p->BRR = i;}
// LED狀態翻轉
//#define LED1_TOGGLE digitalTOGGLE(GPIO_PORT_D4,GPIO_PIN_D4)
//#define LED2_TOGGLE digitalTOGGLE(GPIO_PORT_D5,GPIO_PIN_D5)
// D4這個LED亮
#define D4_LED_ON digitalLo(GPIO_PORT_D4,GPIO_PIN_D4)
// D4這個LED滅
#define D4_LED_OFF digitalHi(GPIO_PORT_D4,GPIO_PIN_D4)
// D5這個LED亮
#define D5_LED_ON digitalLo(GPIO_PORT_D5,GPIO_PIN_D5)
// D5這個LED滅
#define D5_LED_OFF digitalHi(GPIO_PORT_D5,GPIO_PIN_D5)
/*配置GPIO*/
void LED_GPIO_Config(void);
#endif /*__BSP_LED_H__*/
bsp_led.c:
/* 和LED功能模塊相關的程序頭文件 */
/*絕對路徑,也可在Options for target中設置頭文件*/
#include "./led/bsp_led.h"
/*GPIO初始化*/
void LED_GPIO_Config(void)
{
/*外設結構體*/
GPIO_InitTypeDef GPIO_InitStruct_D4, GPIO_InitStruct_D5;
/*第一步:打開外設的時鍾,看stm32f10x_rcc.c這個文件的RCC_APB2PeriphClockCmd函數介紹*/
RCC_APB2PeriphClockCmd(GPIO_CLK_D4, ENABLE);
/*第二步:配置外設的初始化結構體*/
GPIO_InitStruct_D4.GPIO_Pin = GPIO_PIN_D4; // PC2的那盞LED燈(D4)的引腳
GPIO_InitStruct_D4.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽輸出模式
GPIO_InitStruct_D4.GPIO_Speed = GPIO_Speed_10MHz; // 引腳速率
GPIO_InitStruct_D5.GPIO_Pin = GPIO_PIN_D5; // PC3的那盞LED燈(D5)的引腳
GPIO_InitStruct_D5.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽輸出模式
GPIO_InitStruct_D5.GPIO_Speed = GPIO_Speed_10MHz; // 引腳速率
/*第三步:調用外設初始化函數,把配置好的結構體成員寫到寄存器里面*/
GPIO_Init(GPIO_PORT_D4, &GPIO_InitStruct_D4);
GPIO_Init(GPIO_PORT_D5, &GPIO_InitStruct_D5);
/* 默認情況下D4和D5是不亮的 */
D4_LED_OFF;
D5_LED_OFF;
}
(2)main.c
/*
USART串口通信實驗:先實現開發發送數據到電腦的串口通信實驗的最簡單操作
隨便發送一個簡單字符到電腦,使用串口調試助手接收並打印顯示出來
*/
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
int main(void)
{
// 接收到的數據存放到局部變量中
uint8_t temp;
/* LED初始化 */
LED_GPIO_Config();
/* USART串口通信初始化 */
DEBUG_USART_Config();
/* 嘗試從開發板發送一個字符到電腦上的串口調試助手,並顯示 */
//USART_SendData(DEBUG_USARTx, 'Q'); // 發送一個字符
//Usart_SendByte(DEBUG_USARTx, 0x42); // 發送一個字節
//Usart_SendString(DEBUG_USARTx, "歡迎使用STM32F103RCT6!"); // 發送字符串
// 使用printf函數將數據輸出到上位機
//printf("使用printf函數將數據輸出到上位機\r\n");
while(1)
{
// 使用getchar函數接收上位機數據
temp = getchar();
// 使用printf函數將數據返回到上位機
printf("接收到的字符為:%c\n", temp);
/* ============ 控制LED的亮滅 ============= */
switch(temp)
{
case '1':
D4_LED_ON;
D5_LED_OFF;
break;
case '2':
D4_LED_OFF;
D5_LED_ON;
break;
default:
D4_LED_OFF;
D5_LED_OFF;
break;
}
}
}
實驗現象:
將程序燒錄到單片機中,打開串口調試助手,默認情況下,D4和D5兩盞燈都是滅的,輸入1,D4亮D5滅,輸入2,D4滅D5亮,輸入其他字符,D4和D5都滅




一、上位機給單片機發送數據,單片機有兩個方法可以接收數據:
(1)使用中斷。
單片機在檢測到有數據過來的時候,產生一個中斷,然后在中斷服務函數中將數據接收下來。
(2)使用庫函數重定向。
單片機使用getchar()函數將數據接收下來。需要編寫fgetc(FILE *f)重定向函數。
/* 重定向c庫函數scanf到串口,重寫向后可使用scanf、getchar等函數 */
int fgetc(FILE *f)
{
/* 等待串口輸入數據 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);
return (int)USART_ReceiveData(DEBUG_USARTx);
}
二、單片機給上位機發送數據,單片機有兩個方法可以發送數據:
(1)使用printf()函數。需要編寫fputc(int ch, FILE *f)重定向函數。其實函數底層還是使用USART_SendData函數
/* 重定向c庫函數printf到串口,重定向后可使用printf函數 */
int fputc(int ch, FILE *f)
{
/* 發送一個字節數據到串口 */
USART_SendData(DEBUG_USARTx, (uint8_t) ch);
/* 等待發送完畢 */
while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
return (ch);
}
(2)直接使用固件庫函數USART_SendData(DEBUG_USARTx, (uint8_t) ch)將數據發送到上位機。
