各个串口的引脚说明:
如果是同步通信,则会使用到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)将数据发送到上位机。