电路连接:        

        连接显示屏模块,显示屏的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

#include

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

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

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");

}

}

}

}

程序现象:

 

程序及软件下载:

程序打包代码:程序包下载

串口助手下载:串口助手下载

好文阅读

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。