作者:良知猶存 轉載授權以及圍觀:歡迎添加微信:Allen-Iverson-me-LYN 前言 最近想開發一段單片機的代碼,代碼本身有很多的重覆元素,這重覆定義的一些結構體使用起來有些繁瑣,所以就想用C++開發,C++的繼承 模板類可以很容易的解決這些問題。因為在單片機運行,習慣用MDK或者IAR ...
作者:良知猶存
轉載授權以及圍觀:歡迎添加微信:Allen-Iverson-me-LYN
前言
最近想開發一段單片機的代碼,代碼本身有很多的重覆元素,這重覆定義的一些結構體使用起來有些繁瑣,所以就想用C++開發,C++的繼承 模板類可以很容易的解決這些問題。因為在單片機運行,習慣用MDK或者IAR這些軟體。但是這些軟體都是預設C開發的,用C++開發需要重新配置,有些麻煩。但是我還是試了試,做了一個小demo供大家參考。
代碼文件我傳到我的github中去了,大家有興趣可以參考一下
https://github.com/conscience-still/MDK-Cplusplus--LED
一、STM32CubeMX生成底層代碼
因為是做一個demo,不需要很複雜,就用cubemx生成了一個簡單的串口和IO控制的MDK代碼,用了精簡的LL庫,具體實現就不講了,詳細操作可以看我博客CubeMX配置的一些文章。我的博客名是:良知猶存
二、進行IDE的C++配置(去掉C環境的配置)
1.首先打開MDK軟體,去掉use microlib 勾選,這個一個C的依賴庫,但比標準的庫小,它可以減少C代碼的大小。CubeMX生成的文件預設選擇此項。因為這個精簡庫不支持C++,所以我們需要去掉此項功能。
2.Options for Target 再點C/C++ 在下邊的Misc Controls 中輸入—cpp
3.去掉C99 mode選項
三、代碼中C++的編寫註意
1. IDE中的編譯器的這個工程時候,當文件尾碼是C的時候IDE會使用C編譯器進行編譯,如果文件尾碼是CPP則IDE使用C++編譯器進行編譯,工程包含的頭文件是使用C++編譯器進行編譯的,不過頭文件聲明的還是C文件的符號,所以IDE會無法正確編譯鏈接。此時我們應該將頭文件所有聲明C符號的部分用預編譯巨集加extern "C" { }的形式包含起來,告訴編譯器該段要使用C編譯器進行編譯。只包含需要進行C編譯的部分即可
#ifndef __MAIN_H
#define __MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f0xx_ll_crs.h"
#include "stm32f0xx_ll_rcc.h"
#include "stm32f0xx_ll_bus.h"
#include "stm32f0xx_ll_system.h"
#include "stm32f0xx_ll_exti.h"
#include "stm32f0xx_ll_cortex.h"
#include "stm32f0xx_ll_utils.h"
#include "stm32f0xx_ll_pwr.h"
#include "stm32f0xx_ll_dma.h"
#include "stm32f0xx_ll_usart.h"
#include "stm32f0xx_ll_gpio.h"
#ifdef __cplusplus
}
#endif
2.設置需要C++編譯的文件,這時候有兩種方法實現。
1>.在代碼文件的界面,選擇文件右擊選擇Option for Files "你點擊的文件",然後設置file type為需要的C++
2>.直接將文件改為.cpp文件,重新添加,此時候IDE自動進行C++編譯
第二種方法簡單快捷,但是第一種方法雖然麻煩,但是有個好處,我們不需要修改文件名稱,這樣STM32CubeMX下一次生成代碼就不會在生成相應名稱的C代碼了。
3.將中斷服務函數添加 extern "C" 的標識,因為C++中無法直接識別中斷函數,所以用C的方法進行設備編譯。而在Cpp文件中引入C的部分代碼,需要進行extern "C" { }進行修飾,否則不能通過編譯鏈接。
四、C++實現時候遇到的情況
c++test\c++test.axf: Error: L6218E:Undefined symbol vtable for STM32_TEST::TestGPIO (referred from main.o).
把類中的虛函數改為定義好的函數即可。
2.因為我把串口初始化都放在類中實現,我想進行類的構造的時候進行串口數據的列印,但是網上查詢得知,MDK不支持std的流列印輸出,所以我就用sub和super補丁函數,進行系統main函數執行前進行串口的初始化。
這是一種特殊模式:用於有一個已經存在且不能被改變的函數 的情況。使用這兩個模式可以幫原函數打補丁。如存在一個函數foo();
$Sub$ $foo :定義的新功能函數,在foo()函數之前/後使用$Sub$$foo 可以添加一些新的程式代碼。
$Super$ $foo :就是原始的未修補的foo函數,使用這個$Super$ $foo函數將直接跳轉到foo()函數。
具體教程可以看ARM官網的資料學習哈,http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0377g/pge1362065967698.html。
因為super與sub函數屬於c所以我們在cpp文件下需要添加extern“C”進行編譯才行,否則就要出現如下問題了,這些我都遇到過,給大家把雷趟了一遍。
3.最後的一個bug,STDIO的初始化。
本來一個簡單的C++程式就寫完了,主要就是運行環境,但是程式收錄進去之後無法工作,並且在硬體調試下明顯看到系統到了__main之後不知道跑哪裡去了,F5全速執行幾次程式才有機會正常運行,這就很奇怪了,後來在網上找資料,終於找到問題所在了,在以為博主的文章看到,他最後找到問題原來是:
事實上本人也找了近兩天的時間才找到解決辦法,一開始認為是heap和stack沒有初始化好,嘗試了好久均未成功,後來在網上得到啟發,這個問題是出在STDIO初始化上。
如果要使用C/C++標準庫就要對其STDIO進行Retarget的,很簡單,但卻是非常關鍵的一步,就是這麼一回事啦。
我按照他的操作然後程式就可以正常運行了,下載ARM官方的retarget文件,並加入到工程當中。下載鏈接:
http://infocenter.arm.com/help/topic/com.arm.doc.faqs/attached/3844/retarget.c
然後將裡面的串口讀寫按照我現有的硬體需求進行重寫就可以了。如下代碼所示:
char UART_read(void);
void UART_write(char ch);
char UART_read(void)
{
return 0;
}
void UART_write(char ch)
{
while(!(USART2->ISR & USART_ISR_TXE)){};
USART2->TDR = ch;
}
五、最後測試的一些體驗與感想
剛開始想用C++在MDK中開發是因為,有些個需求的功能C++特別符合,但是在調試這個demo過程中,發現使用的單片機容量太小,一個<iostream>頭文件的包含就讓一個只有串口加幾組IO控制的最小程式代碼膨脹到了32K,而去掉該頭文件,代碼縮小到了5K。
代碼過大是c++的依賴項過多,而C++ 中模板類 、虛擬繼承 、STL庫等精華由於依賴的問題都不建議在單片機中用,代碼膨脹的時候單片機吃不住。所以C++雖好,可不一定適合小容量的單片機,大家需要按照自己的功能進行有效的使用C++,精簡使用的依賴,這個可以通過每次編譯的生成的.map文件進行增該刪,其次對於C++中記憶體以及代碼擴增一些基礎知識需要熟悉,負責很容易代碼膨脹,導致我們的程式無法在單片機使用。
這就是我分享的在MDK用C++開發的demo,裡面代碼是實踐過的,如果大家有什麼更好的思路,歡迎分享交流哈。
更多分享,掃碼關註我
微信:Allen-Iverson-me-LYN