您现在的位置是:首页 >技术交流 >keil5使用c++编写stm32控制程序网站首页技术交流
keil5使用c++编写stm32控制程序
keil5使用c++编写stm32控制程序
一、前言
想着搞个新奇的玩意玩一玩来着,想用c++编写代码来控制stm32,结果在keil5中,把踩给我踩闷了,这里简单记录一下。注意一定要按照如下流程进行操作,一步都不要跟丢了。
二、配置图解
所需要的一些文件放在百度网盘了。
先把最新的库函数和CMSIS安装好。
我这里为了方便就直接安装在了keil5的文件夹路径里。
废话不多说,直接上图解。
记得把use microlib的勾选去掉。配置和我图片上一样就没问题。
那这样配置过后会不会就好用了?当然不是,还要使用最新的标准库函数才行。
如何配置以后方便移植勒?当然是这样操作啦。
这里用的是普中科技32f103zet6板子的案例教程。
如图,找到你工程目录下的CMSIS把之前老版本的删除掉。
注意自己找到你最新的CMSIS的安装路径。
再把你Keil_v5ARMPacksARMCMSIS5.9.0CMSISCoreInclude
里边的文件全部复制过去。
在把Keil_v5ARMPacksKeilSTM32F1xx_DFP2.4.0DeviceInclude
里边的system_stm32f10x.h
和Keil_v5ARMPacksKeilSTM32F1xx_DFP2.4.0DeviceSource
里边的system_stm32f10x.c
复制出来。 然后再继续在Keil_v5ARMPacksKeilSTM32F1xx_DFP2.4.0DeviceSourceARM
找到你对应单片机的后缀文件。
最后你工程目录的CMSIS下边就应该是这些文件。
2.再把Libraries下的STM32F10x_StdPeriph_Driver
里的inc和src文件替换为最新的固件库的。
安装的最新的固件库的文件位置在Keil_v5ARMPacksKeilSTM32F1xx_DFP2.4.0DeviceStdPeriph_Driver
里。
3.替换user文件下的如下文件
找到文件路径在Keil_v5ARMPacksKeilSTM32F1xx_DFP2.4.0DeviceStdPeriph_Driver emplates
文件下
在找打之前的Keil_v5ARMPacksKeilSTM32F1xx_DFP2.4.0DeviceInclude
里的这个文件
用这些把之前的文件替换掉就行。
接下来先把你工程中的一些c结尾文件按如下配置
就可以编译运行了,但是直接运行会报错。
建议先进入stm32f10x_conf.h文件中把
”#include "RTE_Components.h"
注释掉,因为我们按照做模板的配置操作的,没有进入之前的动态环境中去配置东西,如果选择了如下配置的东西就会在你的工程文件下生成RTE的一个文件夹。如果你选择了这样操作的话就不会报这个错误。如果要配置的话记得把依赖勾选完整,如果依赖勾选正确会显示绿色,如果不正确会显示黄色,这里不在赘述,纯粹是为你满足你们的好奇心。
当然现在也可以不用管,因为后边会配置自定义的串口输出,会勾选一些配置,到时候他就会自动生成这个RTE_Components.h
所需要的环境。
这下配置好了,浅浅的点个灯吧,点灯大师已经准备上线了,直接操作。
写个led.h
/* LED时钟端口、引脚定义 */
#ifndef _led_H
#define _led_H
#include "system.h"
#define LED_PORT GPIOC
#define LED_PIN (GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_PORT_RCC RCC_APB2Periph_GPIOC
//这里是stm32的管脚的按位操作。
#define led1 PCout(1) //D2指示灯连接的是PC1管脚
#define led2 PCout(2) //D2指示灯连接的是PC2管脚
#ifdef __cplusplus
class Led{
public:
Led(){LED_GPIO_Config();}
void LED_GPIO_Config(void);
void TurnOn( u16 port, bool status);
~Led(){std::cout<<std::move("I am relased!")<<std::endl;};
private:
};
#endif
在写个led.cpp
#include "led.h"
void Led::LED_GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;//定义结构体变量
RCC_APB2PeriphClockCmd(LED_PORT_RCC, ENABLE);
GPIO_InitStructure.GPIO_Pin = LED_PIN; //选择你要设置的IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //设置推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置传输速率
GPIO_Init(LED_PORT, &GPIO_InitStructure); /* 初始化GPIO */
GPIO_SetBits(LED_PORT, LED_PIN); //将LED端口拉高,熄灭所有LED
}
void Led::TurnOn(u16 port, bool status)
{
if(status){
GPIO_ResetBits(LED_PORT, port);} //将LED端口拉高,熄灭所有LED
else{
GPIO_SetBits(LED_PORT, port);}
}
再写个main.cpp
#include "system.h"
int main(void)
{
SysTick_Init(72);
std::shared_ptr<Led> led = std::make_shared<Led>();
while(1)
{
led1 = !led1;
delay_ms(500);
led->TurnOn(GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7, false);
delay_ms(500);
led->TurnOn(GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7, true);
delay_ms(500);
}
return 0;
}
啊哈,好了,恭喜你成功成为一名光荣的点灯工程师。
单纯点灯
三、std::cout串口重定向
既然都用到c++了,那么这个特色的输出肯定不能放过,那么怎么使用它把信息通过串口输出到上位机上啊,别急,一步一步来,showtime!
1.先点击这个运行环境配置图标
2.依次进入Compiler->I/O
,将里面的都勾选上,其实也不用就勾选个STDOUT
我感觉就可以了,当然勾上也没有什么影响,并将variant列依次选择如下图所示:
这里配置完成后,需要你在你自己的usart.h文件中实现
#ifdef __cplusplus
extern "C"
{
#endif
int stdout_putchar(int ch);
int stderr_putchar(int ch);
#ifdef __cplusplus
}
#endif
usart.cpp函数中实现
//标准输出流
int stdout_putchar(int ch)
{
USART_SendData(USART1,(u8)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET){};
return ch;
}
标准错误流
int stderr_putchar(int ch)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)==RESET);
return (int)USART_ReceiveData(USART1);
}
这样你就可以直接在main.cpp中调用函数了。这次点灯加上串口通信。
#include "system.h"
int main(void)
{
SysTick_Init(72);
Init_Usart();
std::shared_ptr<Led> led = std::make_shared<Led>();
while(1)
{
led1 = !led1;
delay_ms(500);
led->TurnOn(GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7, false);
delay_ms(500);
led->TurnOn(GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7, true);
delay_ms(500);
std::cout<<std::move("================Usart_Test=================")<<std::endl;
}
return 0;
}
这里多说几句,这c++在这里keil里边有些特性和标准的c++不太一样,这里的我就不过多阐述,待你们使用的时候自然就明白了。
打开你的串口调试助手就可以得到如下的信息。
哇哦,不仅现在点灯成功了,还玩明白了这个c++重定向串口输出了。
四、串口中断服务函数
那么接下来再试一试串口中断服务函数咋样?
这个有个坑哈,坑了我一天多,给我人搞麻木了,注意这个c++的编译后的中断服务函数代码和原来stm32库函数开发的中断向量表对不上,导致无法进入中断服务函数。
只需要在前面加上extern "C"即可链接原来stm32库函数中的中断服务函数了。
这里咱们上点强度,使用GM65二维码扫描器来和控制小灯的亮和灭。并把读取到的二维码数据上传到pc。这里串口收发数据频繁的话可以使用DMA功能,我这里就不做演示了,为啥?因为我这数据接收并不频繁,又不用去搞什么优化,摸摸鱼啦。都很简单随便找个教程看一看就行了。我就不写了。
usart.h
#ifndef __usart_H
#define __usart_H
#include "system.h"
//串口1
#define USART1_GPIO_PORT GPIOA
#define USART1_GPIO_CLK RCC_APB2Periph_GPIOA
#define USART1_TX_GPIO_PIN GPIO_Pin_9
#define USART1_RX_GPIO_PIN GPIO_Pin_10
//串口2
#define USART2_GPIO_PORT GPIOA
#define USART2_GPIO_CLK RCC_APB2Periph_GPIOA
#define USART2_TX_GPIO_PIN GPIO_Pin_2
#define USART2_RX_GPIO_PIN GPIO_Pin_3
#define BUFFER_SIZE 32 // 定义数组缓冲的最大长度
#ifdef __cplusplus
extern "C"
{
#endif
int stdout_putchar(int ch);
int stderr_putchar(int ch);
void usart_init(unsigned int baud);
void usart_init2(unsigned int baud);
void Init_Usart(void);
void USART_Send_Byte(USART_TypeDef* USARTx, uint16_t Data);
void USART_Send_String(USART_TypeDef* USARTx, char *str);
#ifdef __cplusplus
}
#endif
#endif
usart.cpp
#include "usart.h"
bool RxState{0};
u8 uart2_len{0}; //数据长度,uart2_len+1加上帧尾
u8 RxCounter{0};
u8 RxBuffer[BUFFER_SIZE]{0};
u16 check;
//标准输出流
int stdout_putchar(int ch)
{
USART_SendData(USART1,(u8)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET){};
return ch;
}
标准错误流
int stderr_putchar(int ch)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)==RESET);
return (int)USART_ReceiveData(USART1);
}
void usart_init(unsigned int baud)
{
GPIO_InitTypeDef GPIO_Init_Structure; //定义GPIO结构体
USART_InitTypeDef USART_Init_Structure; //定义串口结构体
NVIC_InitTypeDef NVIC_Init_Structure; //定义中断结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB2PeriphClockCmd(USART1_GPIO_CLK, ENABLE); //开启GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启APB2总线复用时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1时钟
//配置PA9 TX
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_Init_Structure.GPIO_Pin = USART1_TX_GPIO_PIN;
GPIO_Init_Structure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( USART1_GPIO_PORT, &GPIO_Init_Structure);
//配置PA10 RX
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //复用推挽
GPIO_Init_Structure.GPIO_Pin = USART1_RX_GPIO_PIN;
GPIO_Init( USART1_GPIO_PORT, &GPIO_Init_Structure);
//串口相关配置
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //串口中断配置
USART_Init_Structure.USART_BaudRate = baud; //波特率设置为9600
USART_Init_Structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制为无
USART_Init_Structure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式设为收和发
USART_Init_Structure.USART_Parity = USART_Parity_No; //无校验位
USART_Init_Structure.USART_StopBits = USART_StopBits_1; //一位停止位
USART_Init_Structure.USART_WordLength = USART_WordLength_8b; //字长为8位
USART_Init(USART1, &USART_Init_Structure); //初始化
USART_Cmd(USART1, ENABLE); //串口使能
//中断结构体配置
NVIC_Init_Structure.NVIC_IRQChannel = USART1_IRQn;
NVIC_Init_Structure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init_Structure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_Init_Structure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_Init_Structure);
}
void usart_init2(unsigned int baud)
{
GPIO_InitTypeDef GPIO_Init_Structure; //定义GPIO结构体
USART_InitTypeDef USART_Init_Structure; //定义串口结构体
NVIC_InitTypeDef NVIC_Init_Structure; //定义中断结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB2PeriphClockCmd(USART2_GPIO_CLK, ENABLE); //开启GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启APB2总线复用时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //开启USART1时钟
//配置PA2 TX
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽
GPIO_Init_Structure.GPIO_Pin = USART2_TX_GPIO_PIN;
GPIO_Init_Structure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( USART2_GPIO_PORT, &GPIO_Init_Structure);
//配置PA3 RX
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //复用推挽
GPIO_Init_Structure.GPIO_Pin = USART2_RX_GPIO_PIN;
GPIO_Init( USART2_GPIO_PORT, &GPIO_Init_Structure);
USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
USART_Init_Structure.USART_BaudRate = 9600; //波特率设置为9600
USART_Init_Structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制为无
USART_Init_Structure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式设为收和发
USART_Init_Structure.USART_Parity = USART_Parity_No; //无校验位
USART_Init_Structure.USART_StopBits = USART_StopBits_1; //一位停止位
USART_Init_Structure.USART_WordLength = USART_WordLength_8b; //字长为8位
USART_Init(USART2, &USART_Init_Structure);
USART_Cmd(USART2, ENABLE);
//中断结构体配置
NVIC_Init_Structure.NVIC_IRQChannel = USART2_IRQn;
NVIC_Init_Structure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init_Structure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_Init_Structure.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_Init_Structure);
}
//二个串口初始化函数
void Init_Usart(void)
{
usart_init(9600);
usart_init2(9600);
}
/**
* 功能:串口写字节函数
* 参数1:USARTx :串口号
* 参数2:Data :需写入的字节
* 返回值:None
*/
void USART_Send_Byte(USART_TypeDef* USARTx, uint16_t Data)
{
USART_SendData(USARTx, Data);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
}
/**
* 功能:串口写字符串函数
* 参数1:USARTx :串口号
* 参数2:str :需写入的字符串
* 返回值:None
*/
void USART_Send_String(USART_TypeDef* USARTx, char *str)
{
uint16_t i=0;
do
{
USART_Send_Byte(USARTx, *(str+i));
i++;
}
while(*(str + i) != '