一、前言
想著搞個(gè)新奇的玩意玩一玩來著,想用c++編寫代碼來控制stm32,結(jié)果在keil5中,把踩給我踩悶了,這里簡(jiǎn)單記錄一下。注意一定要按照如下流程進(jìn)行操作,一步都不要跟丟了。
二、配置圖解
所需要的一些文件放在百度網(wǎng)盤了。
先把最新的庫函數(shù)和CMSIS安裝好。
我這里為了方便就直接安裝在了keil5的文件夾路徑里。
廢話不多說,直接上圖解。
記得把use microlib的勾選去掉。配置和我圖片上一樣就沒問題。
那這樣配置過后會(huì)不會(huì)就好用了?當(dāng)然不是,還要使用最新的標(biāo)準(zhǔn)庫函數(shù)才行。
如何配置以后方便移植勒?當(dāng)然是這樣操作啦。
這里用的是普中科技32f103zet6板子的案例教程。
如圖,找到你工程目錄下的CMSIS把之前老版本的刪除掉。
注意自己找到你最新的CMSIS的安裝路徑。
再把你Keil_v5\ARM\Packs\ARM\CMSIS\5.9.0\CMSIS\Core\Include
里邊的文件全部復(fù)制過去。
在把Keil_v5\ARM\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include
里邊的system_stm32f10x.h
和Keil_v5\ARM\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Source
里邊的system_stm32f10x.c
復(fù)制出來。 然后再繼續(xù)在Keil_v5\ARM\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Source\ARM
找到你對(duì)應(yīng)單片機(jī)的后綴文件。
最后你工程目錄的CMSIS下邊就應(yīng)該是這些文件。
2.再把Libraries下的STM32F10x_StdPeriph_Driver
里的inc和src文件替換為最新的固件庫的。
安裝的最新的固件庫的文件位置在Keil_v5\ARM\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\StdPeriph_Driver
里。
3.替換user文件下的如下文件
找到文件路徑在Keil_v5\ARM\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\StdPeriph_Driver\templates
文件下
在找打之前的Keil_v5\ARM\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include
里的這個(gè)文件
用這些把之前的文件替換掉就行。
接下來先把你工程中的一些c結(jié)尾文件按如下配置
就可以編譯運(yùn)行了,但是直接運(yùn)行會(huì)報(bào)錯(cuò)。
建議先進(jìn)入stm32f10x_conf.h文件中把”#include "RTE_Components.h"
注釋掉,因?yàn)槲覀儼凑兆瞿0宓呐渲貌僮鞯?,沒有進(jìn)入之前的動(dòng)態(tài)環(huán)境中去配置東西,如果選擇了如下配置的東西就會(huì)在你的工程文件下生成RTE的一個(gè)文件夾。如果你選擇了這樣操作的話就不會(huì)報(bào)這個(gè)錯(cuò)誤。如果要配置的話記得把依賴勾選完整,如果依賴勾選正確會(huì)顯示綠色,如果不正確會(huì)顯示黃色,這里不在贅述,純粹是為你滿足你們的好奇心。
當(dāng)然現(xiàn)在也可以不用管,因?yàn)楹筮厱?huì)配置自定義的串口輸出,會(huì)勾選一些配置,到時(shí)候他就會(huì)自動(dòng)生成這個(gè)RTE_Components.h
所需要的環(huán)境。
這下配置好了,淺淺的點(diǎn)個(gè)燈吧,點(diǎn)燈大師已經(jīng)準(zhǔn)備上線了,直接操作。
寫個(gè)led.h
/* LED時(shí)鐘端口、引腳定義 */
#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
在寫個(gè)led.cpp
#include "led.h"
void Led::LED_GPIO_Config()
{
GPIO_InitTypeDef GPIO_InitStructure;//定義結(jié)構(gòu)體變量
RCC_APB2PeriphClockCmd(LED_PORT_RCC, ENABLE);
GPIO_InitStructure.GPIO_Pin = LED_PIN; //選擇你要設(shè)置的IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //設(shè)置推挽輸出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //設(shè)置傳輸速率
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);}
}
再寫個(gè)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;
}
啊哈,好了,恭喜你成功成為一名光榮的點(diǎn)燈工程師。
單純點(diǎn)燈
三、std::cout串口重定向
既然都用到c++了,那么這個(gè)特色的輸出肯定不能放過,那么怎么使用它把信息通過串口輸出到上位機(jī)上啊,別急,一步一步來,showtime!
1.先點(diǎn)擊這個(gè)運(yùn)行環(huán)境配置圖標(biāo)
2.依次進(jìn)入Compiler->I/O
,將里面的都勾選上,其實(shí)也不用就勾選個(gè)STDOUT
我感覺就可以了,當(dāng)然勾上也沒有什么影響,并將variant列依次選擇如下圖所示:
這里配置完成后,需要你在你自己的usart.h文件中實(shí)現(xiàn)
#ifdef __cplusplus
extern "C"
{
#endif
int stdout_putchar(int ch);
int stderr_putchar(int ch);
#ifdef __cplusplus
}
#endif
usart.cpp函數(shù)中實(shí)現(xiàn)
//標(biāo)準(zhǔn)輸出流
int stdout_putchar(int ch)
{
USART_SendData(USART1,(u8)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET){};
return ch;
}
標(biāo)準(zhǔn)錯(cuò)誤流
int stderr_putchar(int ch)
{
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE)==RESET);
return (int)USART_ReceiveData(USART1);
}
這樣你就可以直接在main.cpp中調(diào)用函數(shù)了。這次點(diǎn)燈加上串口通信。
#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里邊有些特性和標(biāo)準(zhǔn)的c++不太一樣,這里的我就不過多闡述,待你們使用的時(shí)候自然就明白了。
打開你的串口調(diào)試助手就可以得到如下的信息。
哇哦,不僅現(xiàn)在點(diǎn)燈成功了,還玩明白了這個(gè)c++重定向串口輸出了。
四、串口中斷服務(wù)函數(shù)
那么接下來再試一試串口中斷服務(wù)函數(shù)咋樣?
這個(gè)有個(gè)坑哈,坑了我一天多,給我人搞麻木了,注意這個(gè)c++的編譯后的中斷服務(wù)函數(shù)代碼和原來stm32庫函數(shù)開發(fā)的中斷向量表對(duì)不上,導(dǎo)致無法進(jìn)入中斷服務(wù)函數(shù)。
只需要在前面加上extern "C"即可鏈接原來stm32庫函數(shù)中的中斷服務(wù)函數(shù)了。
這里咱們上點(diǎn)強(qiáng)度,使用GM65二維碼掃描器來和控制小燈的亮和滅。并把讀取到的二維碼數(shù)據(jù)上傳到pc。這里串口收發(fā)數(shù)據(jù)頻繁的話可以使用DMA功能,我這里就不做演示了,為啥?因?yàn)槲疫@數(shù)據(jù)接收并不頻繁,又不用去搞什么優(yōu)化,摸摸魚啦。都很簡(jiǎn)單隨便找個(gè)教程看一看就行了。我就不寫了。
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 // 定義數(shù)組緩沖的最大長(zhǎng)度
#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}; //數(shù)據(jù)長(zhǎng)度,uart2_len+1加上幀尾
u8 RxCounter{0};
u8 RxBuffer[BUFFER_SIZE]{0};
u16 check;
//標(biāo)準(zhǔn)輸出流
int stdout_putchar(int ch)
{
USART_SendData(USART1,(u8)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET){};
return ch;
}
標(biāo)準(zhǔn)錯(cuò)誤流
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結(jié)構(gòu)體
USART_InitTypeDef USART_Init_Structure; //定義串口結(jié)構(gòu)體
NVIC_InitTypeDef NVIC_Init_Structure; //定義中斷結(jié)構(gòu)體
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB2PeriphClockCmd(USART1_GPIO_CLK, ENABLE); //開啟GPIOA時(shí)鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //開啟APB2總線復(fù)用時(shí)鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //開啟USART1時(shí)鐘
//配置PA9 TX
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_AF_PP; //復(fù)用推挽
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; //復(fù)用推挽
GPIO_Init_Structure.GPIO_Pin = USART1_RX_GPIO_PIN;
GPIO_Init( USART1_GPIO_PORT, &GPIO_Init_Structure);
//串口相關(guān)配置
USART_ITConfig(USART1,USART_IT_RXNE,ENABLE); //串口中斷配置
USART_Init_Structure.USART_BaudRate = baud; //波特率設(shè)置為9600
USART_Init_Structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制為無
USART_Init_Structure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式設(shè)為收和發(fā)
USART_Init_Structure.USART_Parity = USART_Parity_No; //無校驗(yàn)位
USART_Init_Structure.USART_StopBits = USART_StopBits_1; //一位停止位
USART_Init_Structure.USART_WordLength = USART_WordLength_8b; //字長(zhǎng)為8位
USART_Init(USART1, &USART_Init_Structure); //初始化
USART_Cmd(USART1, ENABLE); //串口使能
//中斷結(jié)構(gòu)體配置
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結(jié)構(gòu)體
USART_InitTypeDef USART_Init_Structure; //定義串口結(jié)構(gòu)體
NVIC_InitTypeDef NVIC_Init_Structure; //定義中斷結(jié)構(gòu)體
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
RCC_APB2PeriphClockCmd(USART2_GPIO_CLK, ENABLE); //開啟GPIOA時(shí)鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //開啟APB2總線復(fù)用時(shí)鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //開啟USART1時(shí)鐘
//配置PA2 TX
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_AF_PP; //復(fù)用推挽
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; //復(fù)用推挽
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; //波特率設(shè)置為9600
USART_Init_Structure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制為無
USART_Init_Structure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式設(shè)為收和發(fā)
USART_Init_Structure.USART_Parity = USART_Parity_No; //無校驗(yàn)位
USART_Init_Structure.USART_StopBits = USART_StopBits_1; //一位停止位
USART_Init_Structure.USART_WordLength = USART_WordLength_8b; //字長(zhǎng)為8位
USART_Init(USART2, &USART_Init_Structure);
USART_Cmd(USART2, ENABLE);
//中斷結(jié)構(gòu)體配置
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);
}
//二個(gè)串口初始化函數(shù)
void Init_Usart(void)
{
usart_init(9600);
usart_init2(9600);
}
/**
* 功能:串口寫字節(jié)函數(shù)
* 參數(shù)1:USARTx :串口號(hào)
* 參數(shù)2:Data :需寫入的字節(jié)
* 返回值:None
*/
void USART_Send_Byte(USART_TypeDef* USARTx, uint16_t Data)
{
USART_SendData(USARTx, Data);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE)==RESET);
}
/**
* 功能:串口寫字符串函數(shù)
* 參數(shù)1:USARTx :串口號(hào)
* 參數(shù)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) != '\0');
while(USART_GetFlagStatus(USART1, USART_FLAG_TC)==RESET);
}
/*
void USART1_IRQHandler(void)
{
volatile char temp;
if(USART_GetITStatus(USART1,USART_IT_RXNE)!= RESET)
{
temp = USART_ReceiveData(USART1);
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清空標(biāo)志位
}
}
*/
unsigned short CRC16_XMODEM(unsigned char *puchMsg, unsigned int usDataLen)
{
unsigned short wCRCin = 0x0000;
unsigned short wCPoly= 0x1021;
unsigned char wChar = 0;
while (usDataLen--)
{
wChar = *(puchMsg++);//4,1 3,2 3,3, 1,4 1,5
wCRCin ^=(wChar << 8);
for(auto i = 0; i< 8; ++i)
{
if(wCRCin & 0x8000)
{
wCRCin =(wCRCin << 1) ^ wCPoly;
}
else
{
wCRCin = wCRCin << 1;
}
}
}
return(wCRCin);
}
extern "C" void USART2_IRQHandler(void)
{
if( USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET) //接收中斷
{
USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除中斷標(biāo)志
RxBuffer[RxCounter++] = USART_ReceiveData(USART2);
}
if(RxBuffer[RxCounter-1]==13) //0x0D
{
uart2_len=RxCounter-1;
check=CRC16_XMODEM(&RxBuffer[RxCounter],uart2_len+1); //校驗(yàn)和(crc)
if(((check&0x00ff)==RxBuffer[uart2_len+1])&&(((check>>8)&0x00ff)==RxBuffer[uart2_len+1]))
{
RxState=1;
}
}
if(USART_GetFlagStatus(USART2,USART_FLAG_ORE) == SET) //判斷中斷是否溢出
{
USART_ClearFlag(USART2,USART_FLAG_ORE);
USART_ReceiveData(USART2);
}
if(RxState)
{
//find_key(RxBuffer,"on");
if(strstr((const char*)RxBuffer, "on"))
{
GPIO_ResetBits(LED_PORT, GPIO_Pin_4);
}
if(strstr((const char*)RxBuffer, "off"))
{
GPIO_SetBits(LED_PORT, GPIO_Pin_4);
}
for(auto x=0;x<uart2_len+1;++x)
{
USART_SendData(USART1,RxBuffer[x]);
delay_ms(10);
}
RxState=0;
RxCounter=0;
}
}
main.cpp
#include "system.h"
int main(void)
{
SysTick_Init(72);
Init_Usart();
std::shared_ptr<Led> led = std::make_shared<Led>();
while(1)
{
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;
}
接下里就是編譯調(diào)試了。什么都好就是胖了點(diǎn),不過問題不大,有的是方法減肥,這里我就不逼逼了。好在ZET6 512k的存儲(chǔ)容量,這小小100k,還不夠。
把編譯好的hex文件下載到單片機(jī)上,就可以觀測(cè)到結(jié)果了。
簡(jiǎn)單的一個(gè)演示視頻;
掃描點(diǎn)燈
使用的gm65我接的二號(hào)串口,GPIOC使用的5號(hào)口,具體的文件代碼我會(huì)分享給大家。因?yàn)橛行〇|西我這上邊沒有寫,不夠你拿到我這弄好的模板,操作之后的就好弄了。
五、結(jié)尾廢話
說著是使用c++快樂的,為啥感覺沒用到多少,其實(shí)正常的,代碼量不夠的話,其實(shí)寫C更方便點(diǎn),還有就是這里邊使用c++11的感覺沒那爽,不夠還行勉強(qiáng)夠用了,搞好c pulspuls以后軟硬通吃就行,那不香香啊。文章來源:http://www.zghlxwxcb.cn/news/detail-425166.html
啊,
什么?
你問我?
代碼在哪?
別急馬上給。文章來源地址http://www.zghlxwxcb.cn/news/detail-425166.html
到了這里,關(guān)于keil5使用c++編寫stm32控制程序的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!