电路连接:
连接显示屏模块,显示屏的SCL在B10,SDA在B11。
程序目的:
发送@LED_ON指令打开板载LED灯,发送@LED_OFF关闭板载LED灯,与上一个博客不同,这个实际上是实现串口收发文本数据包。
开始编程:
Serial.c
初始化GPIO与中断
- 初始化A9引脚,设置为复用推挽输出,也就是让内部硬件控制引脚
- 初始化A10引脚,设置为浮空输入或上拉输入,这里使用上拉输入,具有较好的抗干扰能力
- 不使用硬件流控制,也就是不使用RTS,CTS等
- 串口模式为TX|RX(Transform)|(Receive)表示发送和接收
- 无校验位,可选择奇校验,偶校验等
- 1位停止位,可选择0.5 1 1.5 2这几个
- 8字长,不需要校验选8位,需要选9位
- 开启RXNE(RX No Empty)到NVIC的输出,也就是开启中断
- 配置中断
void Serial_Init() {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入或者上拉输入,使用上拉输入抗干扰能力更强
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制(不使用,CTS,CTS&RTS)
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长
USART_Init(USART1, &USART_InitStructure);
//串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC
//开启中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);//开启USART
}
中断函数:
状态机如图:
这里的中断函数与HEX数据包不同,当收到@字符时转为第一个状态,接收数据,由于这个数据不是固定包长,那么收到\r就进入状态2,再收到\n表示接收完成,进入状态0。这里如果包尾不是两个字符的话,只需要设置两个状态即可。
由于是字符串,因此在状态2转移到状态0时,需要加上字符串的自带的'\0',这样才能定义字符串跟接受到的字符串比较。
还需要建立两个全局变量,char Serial_RxPacket[100];uint8_t Serial_RxFlag;一个是存放接受的数据,一个是存放接收数据标志位。
在中断函数中,定义两个静态变量,类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用,这两个静态变量:static uint8_t RxState = 0;static uint8_t pRxPacket = 0;一个用于定位状态,一个用于定位接收到的数据。
中断函数代码:
char Serial_RxPacket[100];
uint8_t Serial_RxFlag;
void USART1_IRQHandler() {
static uint8_t RxState = 0;//类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用
static uint8_t pRxPacket = 0;
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
//如果读取DR就自动清除标志位,如果没有就需要手动清除
uint8_t RxData = USART_ReceiveData(USART1);
if(RxState == 0){
//若在这里将RxState置为1,那么下面就会立马执行,因此要加上else,也可用switch case语句
if(RxData == '@') {
RxState = 1;
pRxPacket = 0;
}
}
else if(RxState == 1) {
if(RxData == '\r'){
RxState = 2;
}
else {
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
}
}
else if(RxState == 2){
if(RxData == '\n') {
RxState = 0;
Serial_RxFlag = 1;
Serial_RxPacket[pRxPacket] = '\0';//不加不能使用OLED_ShowString
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
Serial.c整体代码
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
char Serial_RxPacket[100];
uint8_t Serial_RxFlag;
void Serial_Init() {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//浮空输入或者上拉输入,使用上拉输入抗干扰能力更强
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600;//波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制(不使用,CTS,CTS&RTS)
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//串口模式 可以使用(或)|符号实现Tx和Rx同时设置
USART_InitStructure.USART_Parity = USART_Parity_No;//校验位,无需校验
USART_InitStructure.USART_StopBits = USART_StopBits_1;//停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长
USART_Init(USART1, &USART_InitStructure);
//串口接收部分可以采用查询或者中断的方式,如果采用中断就需要在这里配置NVIC
//开启中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启RXNE到NVIC的输出
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);//开启USART
}
void Serial_SendByte(uint8_t Byte) {
USART_SendData(USART1, Byte);//发送数据
while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {//等待发送寄存器空,
//TXE就是发送寄存器空的标志位,不需要手动清零,下一次发送数据时候会自动清零
}
}
void Serial_SendArray(uint8_t *Array, uint16_t Length){
uint16_t i;
for(int i = 0; i < Length; i++) {
Serial_SendByte(Array[i]);
}
}
void Serial_SendString(char *Str) {//字符串自带结束标志位
uint8_t i;
for(int i = 0; Str[i] != '\0'; i++) {
Serial_SendByte(Str[i]);
}
}
uint32_t Serial_Pow(uint32_t X, uint32_t y) {
uint32_t Result = 1;
while(y--) {
Result *= X;
}
return Result;
}
void Serial_SendNumber(uint32_t Number, uint8_t Length) {
uint8_t i;
for(int i = 0; i < Length; i++){
Serial_SendByte((Number / Serial_Pow(10, Length - i - 1)) % 10 + '0');
}
}
int fputc(int ch, FILE* f){
Serial_SendByte(ch);//重定向到串口,使得Printf打印到串口
return ch;
}
//使用sprintf让其他的串口也能使用,sprintf可以把格式化字符输出到一个字符串里
void Serial_Printf(char* format,...){//三个点用来接收后面可变参数列表
char String[100];
va_list arg;
va_start(arg, format);//从format位置开始接收参数表,放在arg里面
vsprintf(String, format, arg);
va_end(arg);
Serial_SendString(String);
}
uint8_t Serial_GetRxFlag() {
if(Serial_RxFlag == 1){
Serial_RxFlag = 0;
return 1;
}
return 0;
}
void Serial_SendPacket(){
}
void USART1_IRQHandler() {
static uint8_t RxState = 0;//类似全局变量,函数进入只会初始化一次0,函数退出仍然有效,与全局函数不同,静态变量只能在本函数中使用
static uint8_t pRxPacket = 0;
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
//如果读取DR就自动清除标志位,如果没有就需要手动清除
uint8_t RxData = USART_ReceiveData(USART1);
if(RxState == 0){
//若在这里将RxState置为1,那么下面就会立马执行,因此要加上else,也可用switch case语句
if(RxData == '@') {
RxState = 1;
pRxPacket = 0;
}
}
else if(RxState == 1) {
if(RxData == '\r'){
RxState = 2;
}
else {
Serial_RxPacket[pRxPacket] = RxData;
pRxPacket ++;
}
}
else if(RxState == 2){
if(RxData == '\n') {
RxState = 0;
Serial_RxFlag = 1;
Serial_RxPacket[pRxPacket] = '\0';//不加不能使用OLED_ShowString
}
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
Serial.h
源代码:
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
extern char Serial_RxPacket[];
void Serial_Init();
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char* format,...);
uint8_t Serial_GetRxFlag();
#endif
GpioControl.c:
编写GPIO控制函数,封装GPIO引脚的初始化和控制功能。
#include "stm32f10x.h" // Device header
void GpioInit(GPIO_TypeDef *GPIOx, uint16_t Pin, GPIOMode_TypeDef GpioMode){
uint32_t RCC_APB2Periph_GPIOx;
if(GPIOx == GPIOA) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOA;
}
else if(GPIOx == GPIOB) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOB;
}
else if(GPIOx == GPIOC) {
RCC_APB2Periph_GPIOx = RCC_APB2Periph_GPIOC;
}
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOx, ENABLE);//ctrl + Alt + 空格:可以出现代码提示
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GpioMode;//推挽输出
GPIO_InitStructure.GPIO_Pin = Pin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOx, &GPIO_InitStructure);
GPIO_ResetBits(GPIOx, Pin);
}
void GpioTurn(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN) {//反转当前引脚状态
if(GPIO_ReadOutputDataBit(GPIOx,GPIO_PIN) == 0){
GPIO_SetBits(GPIOx,GPIO_PIN);
}
else{
GPIO_ResetBits(GPIOx, GPIO_PIN);
}
}
void GpioControl(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN, uint8_t sign) {//控制引脚
if(sign == ENABLE){
GPIO_SetBits(GPIOx, GPIO_PIN);
}
if(sign == DISABLE){
GPIO_ResetBits(GPIOx, GPIO_PIN);
}
}
GpioControl.h:
#ifndef __GPIOCONTROL_H
#define __GPIOCONTROL_H
void GpioInit(GPIO_TypeDef *GPIOx, uint16_t Pin, GPIOMode_TypeDef GpioMode);
void GpioTurn(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN);
void GpioControl(GPIO_TypeDef *GPIOx, uint16_t GPIO_PIN, uint8_t sign);
#endif
main.c
在main函数中,主要逻辑就是判断标志位来得到是否有数据接收,若有则跟指令进行对比,如果是打开灯指令,那么就置C13引脚为低电平并发送LED_ON_OK指令,点亮LED灯。若是关灯指令,那么就置引脚为高电平,关闭LED灯并发送LED_OFF_OK,若都不是,那么就输出ERROR_CMD指令表示指令错误。
主要代码如下:
#include "stm32f10x.h" // Device header
#include "DELAY.h"
#include "OLED.h"
#include "Serial.h"
#include "GpioControl.h"
#include <string.h>
uint8_t RxData;
uint8_t KeyNum;
int main() {
GpioInit(GPIOC, GPIO_Pin_13, GPIO_Mode_Out_PP);
GPIO_SetBits(GPIOC,GPIO_Pin_13);
OLED_Init();
Serial_Init();
OLED_ShowString(1, 1, "TxData:");
OLED_ShowString(3, 1, "RxData:");
while(1){
if(Serial_GetRxFlag() == 1) {
OLED_ShowString(4,1, " ");//清除第四行
OLED_ShowString(4,1, Serial_RxPacket);
if(strcmp(Serial_RxPacket, "LED_ON") == 0) {
GpioControl(GPIOC, GPIO_Pin_13, DISABLE);
Serial_SendString("LED_ON_OK\r\n");
OLED_ShowString(2,1," ");
OLED_ShowString(2,1,"LED_ON_OK");
}
else if(strcmp(Serial_RxPacket, "LED_OFF") == 0) {
GpioControl(GPIOC, GPIO_Pin_13, ENABLE);
Serial_SendString("LED_OFF_OK\r\n");
OLED_ShowString(2,1,"LED_OFF_OK");
}
else {
Serial_SendString("ERROR_CMD\r\n");
OLED_ShowString(2,1," ");
OLED_ShowString(2,1,"ERROR_CMD");
}
}
}
}
程序现象:
程序及软件下载:
程序打包代码:程序包下载
串口助手下载:串口助手下载