2025年2月8日 星期六

老工程師的技術生活(三十九) --- 單晶片入門 ?

這是農曆新年後的第一篇文章,隨著一年一年的過,也不禁感慨歲月如梭啊。

自從離開創業公司之後,在武漢待了一兩年,幸運地在疫情爆發前回台,

也非常感謝許多昔日園區老同事與老朋友的幫忙,安然度過這幾年,

想想:離開創業公司,何嘗不是一個很好的安排,想想市場瞬息萬變,更不用

說科技業的技術進展與趨勢方向,都已經與昔日自己所認知的情況相去甚遠,

所以啦,現在呢,就閒來沒事手癢就可以回顧的做作一些小玩意兒,也可以

綜合整理一下屬於我們這個世代的技術發展與經驗分享,或許,如果還可以

讓一些有心人士可以引用或參考,也算是一種時代眼淚的剩餘價值吧。

以個人的職場發展所賴以維生的,最重要的當然就是單晶片的學習與應用,

8051 更是扮演著一個世代的崛起與主流,但慢慢地隨著時代的演進,市場的

選擇性就越來越多樣化了,當然考慮的也不一定就得依據個人喜好、或單價成本?

或其他因素。就如同我上一篇文章提到的:能找到好的應用市場,又能夠變現獲利,

才是最重要的。其他像那些所謂的學習開發環境的適應性,或是功能強大與否?

對我們這些老工程師來說:也就是一種工具的轉換而已,有很多技術基礎概念,

或是產品開發測試驗證方法與程序也都大同小異而已罷了

我今天就拿一個簡單的例子來說明與示範一下吧。我先強調一下:以下這些

單晶片的韌體撰寫,也都不是我個人所新創,我也沒有比較厲害,而只是我們

年紀大,經驗豐富一點,有很多東西也都在大公司裡,有著人才濟濟的人力資源

下可以相互切磋學習進步的。學習技術開發,真的不要有一種錯誤的觀念:

只要我關起門來努力用功,給他磨個五年、十年就可以成仙成精了。也不要以為

現在網路到處有個開源平台,只要我個人努力就可以了。沒有那個學習與競爭

環境,或實際產品開發與導入量產流程的操作,你也真的也很難能夠完善一個

技術的精隨與其系統開發上的概念。也更不用常常奢望天下會隨時掉下來外包

委託設計案給你一兩個人的小公司既可以練功又可以長期賺錢獲利的事。

----

那我今天要做甚麼呢?

很簡單:就是拿個32 bit MCU(stm32) 來置換 8 bit MCU (8051) 而已。

這不是一件很簡單的事嗎?是啊,為什麼?因為這些新一代的 MCU 的開發環境

與平台都很成熟穩定,MCU 本身的周邊裝置與介面也都很完善了,而傳統的

8051 單晶片除非是特殊規格的東西,要不然許多使用方便性也不一定就比較好。

我拿個簡單的板子例子來試試吧:


這個就是一個簡單一兩百元的 8051 學習套件:喔~ 賣家也沒有提供原始碼,沒差吧。


剛好是SMD 的板子,剛好也可以拿來用熱風槍來上件吧。


只是不知道有一顆 0805 電阻被熱風吹到哪了?只好拿手上 0603 來頂替一下吧。

再來看一下完成後它的功能吧:



這應該就是一些單晶片很基礎的 Timer 加 I/O 控制而已。賣家沒有提供原始碼,

但在網路上有提供操作功能說明:


那就當我們的開發規格書吧。

接下來就是要拿一個 STM32 MCU 來取代 8051,網路上也有人賣這種 pin to pin 可以直接

取代 8051 的載板:


他的載板接腳對應原來 8051 單晶片定義:


因為載板上 stm32 有自己的 Reset 與震盪器,所以原來的接腳就不用了。

這樣子就可以無縫接軌的直接用 STM32 開發原來的 8051 系統應用平台了。

所以我就先將我自己的 STM32 的開發工具與平台給架上去:


大家從上圖可以看到:LED 已經有顯示了,就是代表系統已經可以跑起來了啦。

接下來簡單說明一下韌體吧。以前我們剛開始學習 8051 時,其實都有點呆啦,

當然就是沒有經驗啦,也都翻書一頁一頁,一行一行程式Key 進去跑。 只要系統

操作複雜一點,就很容易卡關了。不是上網查資料就是去社群媒體粉絲團問問別人,

但別人也真的很難一時懂你的問題出在哪啊?所以又有點緩不濟急。學習起來

就真的很辛苦。當然自己學會、走過就又覺得不得了了。但其實我一開始就說了:

那是你自己的學習平台與環境不對,你只想關起門來自己努力K 而已。其實,

有很多東西只要你去大公司過個水,搞一兩個實際產品開發案例就夠了:

事半功倍,又有薪水養家餬口的,也可以結交一大堆技術上的難兄難弟。

有甚麼不好的呢?以下就是我二十幾年前在大公司上班時,一樣是拿 8051 

做MP3 SOC 系統的韌體開發平台。這個也不是我做的或新創的,而是我

找一位台清交資工科班出身的朋友來我部門上班幫忙寫的。喔~他寫完這個

架構沒多久(有一年嗎?),他就覺得這種東西沒啥挑戰性,就又離職了。

我是有點不好意思啦,他跟我說:沒關係啦,這種系統的東西就是如此吧,

以後如果還有比較具有挑戰性的東西再找他也是可以的啦,所以我們就一直

保持聯繫,當然他後來就對技術以外的,譬如 Business Model 等比較有興趣。

這也是我一直強調:他們這些聰明優秀的工程師都不會光從技術看未來發展的啦

我們從單晶片的系統應用來說:無非就是一些簡單的 I/O 的顯示互動操作而已。

所以韌體架構就可以簡化成:


簡單來說:就是"狀態"(Status)""觸發事件(Event) "兩者的交互作用。

上圖是我二十幾年前就已經做過的簡報資料。因為我要將程式移交給 FAE 部門。

必須整理說明的教學資料。主程式很短吧~其實我們當初的8051韌體程式碼是

超過 128 KBytes 的:他的系統是很複雜的啦:





你當然不可以用傳統學校或教科書教你的那一套:Main 就一路拼命的往下寫吧?

你要怎麼維護與將所有的韌體開發工作給分配出去處理呢?

所以基本上你系統韌體要做的事主要就圍繞在這兩個主要程式裡:

UI_KeyEventProcedure();

UI_Processorstate_transition();

----

他們的定義分別如下:


不管你要用甚麼方法(中斷或輪詢),就是要從人機介面上取得"觸發事件(Event) "的。

因為它會改變你系統的"狀態"(Status)":就會構成一個交錯的系統操作表單了:


上一圖表中:縱軸就是你的"觸發事件(Event) ";橫軸就是你系統的"狀態"(Status)"

(我記得那個 R_PLAY 是短按鍵,P_PLAY 是長按鍵,所以在 PLAY 撥放狀態下,按長鍵

是無效鍵,而短按鍵就是在 PALY 與 PASUE 兩個"狀態"(Status)"下相互切換的。)

中間只要定義你每個應用上的需求就可以了。只要是簡單的單晶片系統基本上

都可以套用這樣的架構的。他們資工科班的術語好像稱為 " State- Machine" 吧?

(不好意思,我不是念資工的,這種作業系統上的術語我沒念過書、也沒上過課...)

所以這個簡單的範例我們就拿來試試看吧:我們的Main 主程式:

  1. int main(void)
  2. {
  3. /* USER CODE BEGIN 1 */
  4.  
  5. /* USER CODE END 1 */
  6.  
  7. /* MCU Configuration--------------------------------------------------------*/
  8.  
  9. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  10. HAL_Init();
  11.  
  12. /* USER CODE BEGIN Init */
  13.  
  14. /* USER CODE END Init */
  15.  
  16. /* Configure the system clock */
  17. SystemClock_Config();
  18.  
  19. /* USER CODE BEGIN SysInit */
  20. SysTick_Init();
  21. /* USER CODE END SysInit */
  22.  
  23. /* Initialize all configured peripherals */
  24. MX_GPIO_Init();
  25. //--
  26. //-- Input I/O
  27. //BSP_KEY_Init();
  28. /* Initialize Key Button EV board */
  29. BSP_PB_Init(BUTTON_K1, BUTTON_MODE_GPIO);
  30. BSP_PB_Init(BUTTON_K2, BUTTON_MODE_GPIO);
  31. _GKeyEvent= 0;
  32. G_Status = KSTATUS_IDLE; // Idle Status
  33. G_PreStatus = KSTATUS_IDLE; // Idle Status
  34. G_Status = KSTATUS_NormalEW; // Idle Status
  35. nEVTimerRTC=31;
  36. //
  37. G_KeyEvent = KEYNULL; // No any Event
  38. KeyButton_IO_Init();
  39. MX_USARTx_UART_Init(COM1);
  40.  
  41. #if 1
  42. printf("\n\rChamberplus STM32F1_8051 Platform Initialize...\n\r");
  43. //printf("** Test finished successfully. ** \n\r");
  44. #endif
  45. /* USER CODE BEGIN 2 */
  46.     nOneSecond=0;
  47.     _GLEDEVT = 0;
  48.     _GLEDTGflag = 0;
  49. nDigital10 = 0;
  50. nDigital01 = 0;
  51. /* USER CODE END 2 */
  52. /* Infinite loop */
  53. /* USER CODE BEGIN WHILE */
  54. while (1)
  55. {
  56. //--- UI Key Event ---
  57. UI_KeyEventProcedure();
  58. UI_Processorstate_transition();
  59. /* USER CODE END WHILE */
  60. //=== Seven Segment LED Display
  61. if(_GLEDEVT&0x01) {
  62. _GLEDEVT &= ~0x01;
  63. UI_SevenSegmentDisplay(nEVTimerRTC);
  64. }
  65. /* USER CODE BEGIN 3 */
  66.     /* USER CODE END 3 */
  67. }
  68. }

---

那我們的"觸發事件(Event) "定義:

  1. /*
  2. static void UI_KeyEventProcedure(void)
  3. {
  4. if(_GKeyEvent&0x01) {
  5. _GKeyEvent &= ~0x01;
  6. button_ticks();
  7. ///---------------
  8. if(_GKeyEvent&0x02) { // Any Key Event
  9. _GKeyEvent &= ~0x02;
  10. //-- K1 Key
  11. if(K1KeyEvent==SINGLE_CLICK){
  12. if(_GKeyEvent&0xE0){ //0x12
  13. //--None Real Start Event, clear Event flag only !
  14. _GKeyEvent &= ~0x10;
  15. }else{
  16. printf("K1\n\r");
  17. _GKeyEvent &= ~0xF0;
  18. G_KeyEvent = KEYK1Short; // Key Event Enable
  19. }
  20. K1KeyEvent=NONE_PRESS;
  21. }else if(K1KeyEvent==LONG_PRESS_ON){
  22. _GKeyEvent &= ~0x01; // Long Key Press, clear again
  23. //printf("Start Long key_:%2X \n\r", _GKeyEvent);
  24. if(_GKeyEvent&0x08){
  25. _GKeyEvent &= ~0xF8;
  26. printf("LK1\n\r");
  27. G_KeyEvent = KEYK1LONG; // Key Event Enable
  28. }
  29. K1KeyEvent=NONE_PRESS;
  30. }else{
  31. K1KeyEvent=NONE_PRESS;
  32. }
  33. //-- SW3 Key
  34. if(K2KeyEvent==SINGLE_CLICK){
  35. if(_GKeyEvent&0xD0){
  36. //--None Real Stop Event, clear Event flag only !
  37. _GKeyEvent &= ~0xD0;
  38. }else{
  39. _GKeyEvent &= ~0x20;
  40. G_KeyEvent = KEYK2Short; // Key Event Enable
  41. printf("K2\n\r");
  42. }
  43. K2KeyEvent=NONE_PRESS;
  44. }else if(K2KeyEvent==LONG_PRESS_ON){
  45. _GKeyEvent &= ~0x01; // Long Key Press, clear again
  46. //printf("Stop Long key_:%2X \n\r", _GKeyEvent);
  47. if(_GKeyEvent&0x04){
  48. _GKeyEvent &= ~0xF4;
  49. printf("LK2\n\r");
  50. G_KeyEvent = KEYK2LONG; // Key Event Enable
  51. }
  52. K2KeyEvent=NONE_PRESS;
  53. }else{
  54. K2KeyEvent=NONE_PRESS;
  55. }
  56. }
  57. }
  58. }
  59. //---
  60.  

---

這裡硬體上雖然只有兩個按鍵,但可以依照按鍵的特性可以定義出至少四種

不同的"觸發事件(Event) "。譬如K1/K2 分別都有長短鍵事件之分。

---

而我們這個系統的"狀態"(Status)"定義:

  1. static void UI_Processorstate_transition(void)
  2. {
  3. switch(G_Status)
  4. {//state
  5. //-- STATE 0: --
  6. /// Idle -- Idle -- Idle -- Idle -- Idle -- Idle -- Idle -- Idle -- Idle -- Idle --
  7. case KSTATUS_IDLE:
  8. //printf("%d", G_KeyEvent);
  9. switch(G_KeyEvent)
  10. {//IDLE event
  11. case KEYK1Short:
  12. G_KeyEvent = KEYNULL;
  13. break;
  14. case KEYK2Short:
  15. G_KeyEvent = KEYNULL;
  16. break;
  17. case KEYK1LONG:
  18. case KEYK2LONG:
  19. G_KeyEvent = KEYNULL;
  20. break;
  21. default:
  22. G_KeyEvent = KEYNULL;
  23. break;
  24. }
  25. //=====
  26. break;
  27. //-- STATE 1: --
  28. /// StandBy -- StandBy -- StandBy -- StandBy -- StandBy -- StandBy -- StandBy -- StandBy --
  29. case KSTATUS_STDBY: //State StandBy on--
  30. switch(G_KeyEvent)
  31. {//Key event
  32. case KEYK1Short:
  33. G_KeyEvent = KEYNULL;
  34. //__nop();
  35. break;
  36. case KEYK2Short:
  37. G_KeyEvent = KEYNULL;
  38. break;
  39. case KEYK1LONG:
  40. G_KeyEvent = KEYNULL;
  41. break;
  42. case KEYK2LONG:
  43. G_KeyEvent = KEYNULL;
  44. break;
  45. default:
  46. G_KeyEvent = KEYNULL;
  47. //GIdleKeyEventDT=0;
  48. break;
  49. }
  50. break;
  51. //-- STATE 2: --
  52. /// Normal North and South --
  53. case KSTATUS_NormalNS:
  54. switch(G_KeyEvent)
  55. {//Key event
  56. case KEYK1Short:
  57. G_KeyEvent = KEYNULL;
  58. //__nop();
  59. break;
  60. case KEYK2Short:
  61. G_KeyEvent = KEYNULL;
  62. break;
  63. case KEYK1LONG:
  64. G_KeyEvent = KEYNULL;
  65. break;
  66. case KEYK2LONG:
  67. G_KeyEvent = KEYNULL;
  68. break;
  69. default:
  70. G_KeyEvent = KEYNULL;
  71. //GIdleKeyEventDT=0;
  72. break;
  73. }
  74. //--- One Second Event ---
  75. if(G_TimeStampU16.UTimeSTP.bTSTP00){
  76. G_TimeStampU16.UTimeSTP.bTSTP00=0;
  77. nEVTimerRTC--;
  78. printf("Status=%d, nEVTimerRTC=%d\n\r", G_Status, nEVTimerRTC);
  79. if(nEVTimerRTC==5){
  80. G_Status = KSTATUS_Transition;
  81. G_PreStatus = KSTATUS_NormalNS;
  82. BSP_LED_On(LED0);
  83. BSP_LED_Off(LED1);
  84. BSP_LED_Off(LED2);
  85. BSP_LED_Off(LED3);
  86. BSP_LED_On(LED4);
  87. BSP_LED_Off(LED5);
  88. BSP_LED_On(LED6);
  89. BSP_LED_Off(LED7);
  90. BSP_LED_Off(LED8);
  91. BSP_LED_Off(LED9);
  92. BSP_LED_On(LEDA);
  93. BSP_LED_Off(LEDB);
  94. }else{
  95. LED_NorthSouthPass();
  96. }
  97. //if(nEVTimerRTC==0) nEVTimerRTC=60;
  98. //printf("nEVTimerRTC=%d\n\r", nEVTimerRTC);
  99. }
  100. break;
  101. //-- STATE 3: --
  102. /// Normal North and South Transit to Normal East and West or Reverse it--
  103. case KSTATUS_Transition:
  104. switch(G_KeyEvent)
  105. {//Key event
  106. case KEYK1Short:
  107. G_KeyEvent = KEYNULL;
  108. //__nop();
  109. break;
  110. case KEYK2Short:
  111. G_KeyEvent = KEYNULL;
  112. break;
  113. case KEYK1LONG:
  114. G_KeyEvent = KEYNULL;
  115. break;
  116. case KEYK2LONG:
  117. G_KeyEvent = KEYNULL;
  118. break;
  119. default:
  120. G_KeyEvent = KEYNULL;
  121. //GIdleKeyEventDT=0;
  122. break;
  123. }
  124. //--- One Second Event ---
  125. if(G_TimeStampU16.UTimeSTP.bTSTP00){
  126. G_TimeStampU16.UTimeSTP.bTSTP00=0;
  127. nEVTimerRTC--;
  128. printf("Status=%d, nEVTimerRTC=%d\n\r", G_Status, nEVTimerRTC);
  129. if(nEVTimerRTC){
  130. if(nEVTimerRTC&0x01){
  131. BSP_LED_On(LED0);
  132. BSP_LED_Off(LED1);
  133. BSP_LED_Off(LED2);
  134. BSP_LED_Off(LED3);
  135. BSP_LED_On(LED4);
  136. BSP_LED_Off(LED5);
  137. BSP_LED_On(LED6);
  138. BSP_LED_Off(LED7);
  139. BSP_LED_Off(LED8);
  140. BSP_LED_Off(LED9);
  141. BSP_LED_On(LEDA);
  142. BSP_LED_Off(LEDB);
  143. }else{
  144. BSP_LED_Off(LED0);
  145. BSP_LED_Off(LED1);
  146. BSP_LED_Off(LED2);
  147. BSP_LED_Off(LED3);
  148. BSP_LED_Off(LED4);
  149. BSP_LED_Off(LED5);
  150. BSP_LED_Off(LED6);
  151. BSP_LED_Off(LED7);
  152. BSP_LED_Off(LED8);
  153. BSP_LED_Off(LED9);
  154. BSP_LED_Off(LEDA);
  155. BSP_LED_Off(LEDB);
  156. }
  157. }else{
  158. if(G_PreStatus == KSTATUS_NormalNS){
  159. G_Status = KSTATUS_NormalEW;
  160. nEVTimerRTC=30;
  161. LED_EastWestPass();
  162. }else if(G_PreStatus == KSTATUS_NormalEW){
  163. G_Status = KSTATUS_NormalNS;
  164. nEVTimerRTC=20;
  165. LED_NorthSouthPass();
  166. }else{
  167. //System Error
  168. }
  169. }
  170. }
  171. break;
  172. //-- STATE 4: --
  173. /// Normal East and West --
  174. case KSTATUS_NormalEW:
  175. switch(G_KeyEvent)
  176. {//Key event
  177. case KEYK1Short:
  178. G_KeyEvent = KEYNULL;
  179. //__nop();
  180. break;
  181. case KEYK2Short:
  182. G_KeyEvent = KEYNULL;
  183. break;
  184. case KEYK1LONG:
  185. G_KeyEvent = KEYNULL;
  186. break;
  187. case KEYK2LONG:
  188. G_KeyEvent = KEYNULL;
  189. break;
  190. default:
  191. G_KeyEvent = KEYNULL;
  192. //GIdleKeyEventDT=0;
  193. break;
  194. }
  195. //--- One Second Event ---
  196. if(G_TimeStampU16.UTimeSTP.bTSTP00){
  197. G_TimeStampU16.UTimeSTP.bTSTP00=0;
  198. nEVTimerRTC--;
  199. printf("Status=%d, nEVTimerRTC=%d\n\r", G_Status, nEVTimerRTC);
  200. if(nEVTimerRTC==5){
  201. G_Status = KSTATUS_Transition;
  202. G_PreStatus = KSTATUS_NormalEW;
  203. BSP_LED_On(LED0);
  204. BSP_LED_Off(LED1);
  205. BSP_LED_Off(LED2);
  206. BSP_LED_Off(LED3);
  207. BSP_LED_On(LED4);
  208. BSP_LED_Off(LED5);
  209. BSP_LED_On(LED6);
  210. BSP_LED_Off(LED7);
  211. BSP_LED_Off(LED8);
  212. BSP_LED_Off(LED9);
  213. BSP_LED_On(LEDA);
  214. BSP_LED_Off(LEDB);
  215. }else{
  216. LED_EastWestPass();
  217. }
  218. }
  219. break;
  220. //-- STATE 5: --
  221. /// K1 Mode --
  222. case KSTATUS_K1Mode:
  223. switch(G_KeyEvent)
  224. {//Key event
  225. case KEYK1Short:
  226. G_KeyEvent = KEYNULL;
  227. //__nop();
  228. break;
  229. case KEYK2Short:
  230. G_KeyEvent = KEYNULL;
  231. break;
  232. case KEYK1LONG:
  233. G_KeyEvent = KEYNULL;
  234. break;
  235. case KEYK2LONG:
  236. G_KeyEvent = KEYNULL;
  237. break;
  238. default:
  239. G_KeyEvent = KEYNULL;
  240. //GIdleKeyEventDT=0;
  241. break;
  242. }
  243. break;
  244. //-- STATE 6: --
  245. /// K2 Mode --
  246. case KSTATUS_K2Mode:
  247. switch(G_KeyEvent)
  248. {//Key event
  249. case KEYK1Short:
  250. G_KeyEvent = KEYNULL;
  251. //__nop();
  252. break;
  253. case KEYK2Short:
  254. G_KeyEvent = KEYNULL;
  255. break;
  256. case KEYK1LONG:
  257. G_KeyEvent = KEYNULL;
  258. break;
  259. case KEYK2LONG:
  260. G_KeyEvent = KEYNULL;
  261. break;
  262. default:
  263. G_KeyEvent = KEYNULL;
  264. //GIdleKeyEventDT=0;
  265. break;
  266. }
  267. break;
  268. /// SYSTEM Error -- SYSTEM Error -- SYSTEM Error -- SYSTEM Error -- SYSTEM Error -- SYSTEM Error -- SYSTEM Error--
  269. case KSTATUS_SYSError: //State USB on ...--
  270. //-- Toggle Error LED ---
  271. // Any key to clear Status
  272. switch(G_KeyEvent)
  273. {
  274. case KEYK1Short:
  275. case KEYK2Short:
  276. G_KeyEvent = KEYNULL;
  277. G_Status = KSTATUS_STDBY;
  278. break;
  279. default:
  280. G_KeyEvent = KEYNULL;
  281. break;
  282. }
  283. break;
  284. default:
  285. break;
  286. }
  287. }
  288. //=====
  289.  
  290.  

-----

其實有很多系統初始還是會有所謂的 Idle 及 StandBy 兩道程序狀態的,

譬如這也可以拿來調整複雜的開機程序(譬如周邊系統的初始化等),

或是做為系統省電或其他待機狀態的~不過,這個原始範例看起也沒有這兩個,

所以我就直接切到:

/// system Status

typedef enum {

KSTATUS_IDLE=0,

KSTATUS_STDBY,

KSTATUS_NormalNS,

KSTATUS_Transition,

KSTATUS_NormalEW,

KSTATUS_K1Mode,

KSTATUS_K2Mode,

KSTATUS_SYSError

} KGSTATUS;

中間那三道重複執行而已。至於規格書裡的那兩個按鍵功能就是觸發 K1_Mode 及

K2_Mode 兩種狀態而已,我這邊就不再說明了,而且~上述程式碼中,我目前是

沒有把對應的"觸發事件(Event) "功能寫進去,有興趣者可以自行加入調整。

我只是先完成原來基本功能的展示:


---

另外你的開發平台也可以輕易利用 USART 把韌體中想看的變數內容給 Printf 出來:


這些都是系統開發的基礎功夫吧。

---

好了。本文中我利用一個 STM32 MCU 來取代 8051 MCU 的簡單示範完畢了。

我再一次強調說明一下:我沒有比較厲害,而是我老了,程式撰寫上有一點經驗

累積而已,況且這也不是我原創的。這些都是很基礎的架構,現在 32 bit MCU 的開發

平台上都很容易實現完成的,因為它還可以架一個 RTOS (譬如 FreeRTOS) 來處理按鍵

"觸發事件(Event) ",也可以用同樣的 RTOS 來處理你系統應用上的"狀態"(Status)"

所以這邊我示範的就是一個很基礎的雕蟲小技而已。二十幾年前就已經被玩到爛了。

重點還是在於你要拿甚麼MCU 平台?來做甚麼系統應用開發?最、最、最重要的

是你要如何拿這個東西完成可以大量生產的產品銷售變現啦

---

最後,還是奉勸一下有心從事單晶片系統開發的年輕工程師們:想學甚麼技術?

想用甚麼開發平台?最重要的還是要挑對你的學習與具有挑戰性的環境,

光只是在網路社群媒體上,看到別人玩玩這些東西(就像我這篇文章一樣),

就想自己買本書、報名去上個課... 然後自己又買一些儀器設備或弄個自己工作室。

就想閉門苦讀努力地創造自己技術上的價值?想想我上述的那些台清交的

工程師們的表現吧。你是不是弄錯你的學習重點與方向?也是該不該再調整一下

你工程師學習進步的方法呢?

分享給各位。謝謝!

-----

PS:忘了補上PCB 電路圖:

喔~順便提一下:因為 stm32 是 3.3V ,所以這板子上我是直接供 : 3.3V 。

那個Buzzer 就靠 I/O sink 電流,真的沒啥效果了。就不特別處理了。

17 則留言:

  1. 那張 "典型的系統架構圖", 版大在 MP3_SOC 裡面每個子功能都有參與?
    如果 "是", 那在當時是很好的歷練, 但過程應該是不容易的...
    它的經驗值, 可不是買塊開發版跑個LED 燈, 或是書本的範例程式可以比擬的

    回覆刪除
    回覆
    1. 那是Data Flow Analysis,比較舊的程式資料分析方法。因為這類分析也用在生產流水線排程上有類似的。我也有買過這樣的書,在納莉颱風將書淹掉了,之後就找不到資料了。

      刪除
    2. https://en.wikipedia.org/wiki/Data-flow_diagram 這是我20年前自學的,因為RAM有限,所以要畫出來,程式算是資料處理器,吃資料吐出另一型資料又要存去另一個RAM。寫8051有畫的話,程式大架構很容易就理解。

      刪除
    3. 關於"典型的系統架構圖"裡的東西,真的太多了啦。
      我當然不可能所有都懂得,就拿那個 DSP 所處理的
      Audio Decode/Encode... 那是另一個IP 設計部門的專業,
      不過,我們部門也有負責DSP 上層介面的工程師,
      他們只要定期跟我報告進度就可以了,當然我們
      單晶片的韌體工程師也會負責跟他們對接相關應用介面。
      我們只負責介面處理而已;
      而二十幾年前也沒有所謂的 FATFS 開源平台,
      那也是我們公司另一個數位相機部門有人專門撰寫維護的。
      我還記得那位工程師的名字:楊X琳先生。
      是一位香港僑生,人很好~也很聰明。

      大公司就是如此,會有專業分工領域,但所有的IP 設計,
      也不是只有我這個 MP3 SOC 會用到的。我們只不過把
      公司裡所有建立的IP 拿出來拼湊出可以變現獲利的產品而已。
      ---
      後來產品出貨後,部門不到十個人,也可以貢獻年營收上億元,
      這些對大公司來說:都是理所當然的運作模式。
      大家都是很優秀的工程師,也不會有人說:誰比較厲害的啦。

      當然啊~誠如你所說的:這些都是一些非常好的工程歷練,
      大家都可以輕鬆學習成長,公司也會提供一定的員工福利。
      薪水待遇分紅也不會少...這就是一種雙贏的結果。

      所以我才說:你工程師生涯不是只有關起門來努力學習而已。
      還有很多東西值得你去觀察與學習的。

      刪除
    4. @Bee 謝謝你所補充的說明與資料。
      不好意思的是:我真的也不是科班出身的,
      也沒機會接觸到這類的學習資料,
      相信世上有各種學派或理論名詞。
      我也不可能有機會去接觸到或應用~
      我說了:我也只不過借助別人的智慧經驗來完善
      我自已系統所需的架構。是不是最好?或最洽當的?
      沒有認真實驗比較過~所以也沒有答案。

      就像RTOS 基本上,每個RTOS 門派都會自吹自擂。
      系統使用RTOS 的確可以幫助我們在系統開發上的
      方便性,但歲月與精力有限...真的沒辦法一一的走一遍
      的來找屬於自己的答案:最終還是哪個方便可以變現
      獲利的,就是最好的。

      剩下的就是每個技術開發門派的人,自己要去想如何
      讓我們這些客戶願意相信,而且很簡單輕鬆上手的,
      你們的技術門派才會有人願意去幫你發揚光大吧。
      不就是如此嗎?

      刪除
    5. 科班?我是化學系出來的,半路學電機,就業以後才寫C。只要有程式技術的書都買下來自學出來的。就業不太久就先鎖定compiler及rtos技術,自學了6,7年才會用。我用的書都不是科班標準。像這次的data flow diagram要不是回查,我根本沒有想到它是從寫資料庫的人在用的東西,我只是覺得寫8051應該有更好的分析工具,然後自己就找一個來用,用了20年才發現不是MCU的人應該學的。

      刪除
    6. 是啊。辛苦你了。
      如果不是科班出身,又想在這個領域學有專精真的很辛苦。
      所以我個人就希望以自身的經驗分享給有類似想法的人。

      想想我文中舉例的那些台清交科班優秀的工程師...
      至少他們在學校裡已經受過一定的教育訓練,所以他們
      出了社會進了職場,就可以很快地從學理與實務中
      掌握一些技術發展動態。
      不用像我們還要回頭去找過去所沒有的專業知識,
      至少會花比較少的冤枉路與時間來摸索...
      就像我提到的:我們都還不知道該如何架設一個簡易的系統,
      他們就可以很快地從腦袋中找到對應的東西。

      就像我們累積一些經驗之後,當我們在社群媒體粉絲團裡,
      聽到別人提到我們過往經驗時,我們也可以很快地想到
      相關的"關鍵字"。再來在網路搜尋中就簡單了。
      (我們當初也沒有Google 搜尋這種東西啊,更辛苦啊。)

      個人覺得另一點就比較容易造成個性與觀察事物的偏頗:
      因為就是辛苦學成,性格上就多少有一點"不服輸"的傲氣。
      苦學出身,花了不少精神、成本(構築實驗儀器設備等)才有
      那一絲的成就,就會比較鑽牛角尖的往技術領域一頭栽進去。
      再也不在乎技術以外的人事物與基本財務管理與業務市場的
      相關策略經營。
      其實殊不知:人家這些原本優秀的科班出身工程師們,
      早就跳脫技術這一層視野,用更正向的眼光觀察審視
      市場趨勢與自己或公司未來發展方向策略。
      所以才有那種:在技術開發上,只是人家不想理你,
      而自己還以為優秀,別人都是笨蛋的偏執心態...

      造成這種結果就非常吃虧與惋惜,所以,我才會不斷的
      以自身的經歷與所觀察的角度來分享,也讓別人能有
      更健康,更成熟穩健地走在這條技術人生路上啊。

      刪除
    7. 早期寫MCU的都是電子科系,看了組合語言就開始寫。我接過組合語言寫call,不寫ret,程式跑幾秒就當機,然後它再修改sp暫存器使它不當掉。後面再改用jmp而不用call。真的很難追。那位前輩換寫PC上的C語言,main()寫了4000多行,中間goto來goto去,寫到後來就轉去做業務,原始碼就丟出來給別人接,接手的人好像重寫一次。這些事都是我就業第一年就遇到,所以就開始注意是不是有模型工具可以做寫程式前的規劃。

      刪除
    8. 個人覺的這一個狀況真的看人,
      畢竟在學校時,
      老師就有強調有幾個指令必定要配對使用
      除非你系統有非常重要的功能控制(比如說防烤貝、防維護)......
      是說老闆(主管)都不CARE了,
      在那種狀況下就只能自立自強了,
      這種狀況在台商比較常看到就是了。

      刪除
    9. 因為非本科,拿了手冊開始寫組語,call在前面,就寫上去,後面沒有ret下模擬器也沒有反應,就一路寫下去。同樣的在C上又一次,看到main函式內可以執行,就像組語一路寫下去。我個人要不是在電機系有上過微處理機以及系統程式,不然觀念不知何時可以建立。我就業在1998年,電機系只有微處理機是必修,我想那位前輩可能連微處理機都沒有被教過,才會寫出如此怪的程式。

      刪除
    10. 所以你就知道我們這些非科班出身的,
      一就業時,基礎有多差了吧。
      真的,要不是真的對這個領域有興趣,而且
      也想在這個領域闖出一點成績,也真的不用這麼跌跌撞撞的
      辛苦學習歷程啊。
      更何況:在業界學跟在學校學,那是完全不同的學習環境,
      在學校你可以犯錯,頂多就是成績差一點,但在業界就是
      比真槍實彈的~搞不定就回家吃自己,哪個主管可以長期
      忍受一個慢慢學習成長的部屬?要不然就是:人家下午
      五、六點可以下班,你就得十點、十一、二點回家。
      不會時,還得厚著臉皮到處問別人,包括資歷比你淺的。
      沒辦法啊...環境就是如此。
      我個人運氣是比較好一點的:一來我在財團法人服國防役,
      沒有很大的業績壓力,二來單位還可以讓你出去上上課。
      在單位裡也是可以慢慢地摸索...然後轉換跑道到園區時,
      也是到一家比較小的新創公司,人家新創公司也一時
      找不到優秀資深工程師啊....也只能慢慢地帶你入行啊。
      我當然非常感謝這些前輩的提攜。

      但回頭看我兒子。他們就真的是科班出身的,在學校就是
      微處理器課程或數位電子實驗...寫個 8051或Arduino 就
      上手了,畢業到業界,就直接切到大型 Embedded System
      。誰還在跟你在那邊寫 8051 或 Arduino 啊? 當然也是如此,
      才可以留一口飯給我們這些LKK 或學不了 Embedded System
      或 最新的AI 平台的人,還可以寫寫這些低階應用來餬口度日啊。

      想想現在整個科技業趨勢,就是往AI 大模型這種方向在走了,
      像文章所述的那些單晶片系統,也真的出不了廳堂,也沒辦法
      拿到檯面上跟別人吹甚麼的啦。這才是我寫這篇文章的感想啦。

      刪除
    11. 以下是今晚稍早我朋友分享給我的:

      本科生推翻姚期智40年前的猜想,提出哈希表算法突破搜索效率极限
      https://www.toutiao.com/article/7470028548795810304/

      現在整個全球各國都在拚AI 了。
      https://youtu.be/VCZ1hHrBCLs?si=PJphPPT6rV29ED6c&t=376

      文章中這些傳統單晶片韌體的開發工作,其實慢慢都會被原廠的
      資源給處理掉了,你要在單晶片平台上弄個 RTOS、寫個複雜的
      LCD 面板顯示與UI互動功能或是寫個基本無刷馬達控制程式等,
      這些都不難啦,我說的:只要公司或有人付你薪水,拿個原廠
      提供基本開發平台給你玩幾天,大概也可以上手弄個雛型出來,
      剩下都是驗證測試的苦差事而已。

      但你想想:現在大部分科班出身的優秀工程師們都應該去做甚麼?
      或會想往哪個未來領域發展呢?
      當然就是搞AI 晶片或相關演算法、軟體開發了,至少,我相信
      有錢的老闆、公司或國家政策也都會往這個領域丟資源的啦。
      雖然不一定可以馬上跟上檯面上的這些大公司,雖然開發能力
      有現跟不上,但至少也要學會如何應用吧?
      所以你可以看到有人把它應用在文書文案撰寫、影像數位內容
      創作等。那你說會不會有我們這個領域的人,藉由AI 來簡化或
      取代我們做基礎的韌體開發呢?
      就算一時做不到,也會從這些基礎上,努力地透過模型學習
      慢慢地實現這些理想目標?
      我們常用的 STM 也開始推廣了啊:
      https://stm32ai.st.com/stm32-cube-ai/

      你不想學?可以,難道你還要你的小孩跟我們一樣從這些
      單晶片的基礎或這種學習模式一步一腳印的走嗎?
      這樣子你就知道我的感觸了吧。

      刪除
    12. 這個簡報整理出基本發展方向:
      chrome-extension://efaidnbmnnnibpcajpcglclefindmkaj/https://stm32ai.st.com/wp-content/uploads/2025/01/Edge-AI-solutions-on-STM32-overview.pdf

      還有另一篇,我乾脆把他們先整理到雲端:
      https://drive.google.com/drive/folders/1cO0YaIqxfiSmYPh392-oeM3WZKjooI3l?usp=sharing

      刪除
  2. "離開創業公司"
    偶然遇到前公司的長官, 由於辦公室政治的原故, 因而離開, 靠接案過日子,
    與對方言談之中, 感受到少了過去的自信, 意氣風發, 多了些落寞, 失意...

    提起這件事, 不是說當個吃瓜群眾, 在旁看戲....
    在他身上發生的事, 也有很大的機會我會遇到, 到時該如何呢? (還在想)

    回覆刪除
    回覆
    1. 這種事就真的要看開一點啦。
      得知我幸,失之我命。
      我自己也繳了學費,但又如何?只要自己還行,
      就自己再慢慢賺啊。

      我常說:開公司創業,真的不只是光從技術面來評論。
      如果天時地利人和,萬事皆不如時,何不從另一個角度
      看待之呢?只要不違法,對得起天地良知,離開之後,
      或許有著另一個海闊天空的際遇也不一定啊。

      像我自己反而非常感謝離開的這件事,他給了我一個
      更豐富的人生:去了武漢做機車ABS 系統測試驗證,
      完善了我在車用電子系統測試驗證技術完整的一塊拼圖。
      雖然後來不一定還有機會用得上,但在這塊領域也無憾了。
      那就更不用說:可以疫情爆發前回來,真的感謝上蒼。

      之後,因工作也真正踏入 32 bits ARM 的應用世界,
      寫了不少USB 裝置端與主機PC 端的應用軟體,
      有薪水領,又有滿滿的技術成長。
      家裡賈老師也順利退休,小孩畢業後也都找到一份工作,
      許多空閒時,我們闔家經常可以一起帶著我們家的
      麻糬(一隻米克斯狗狗) 外出散步聊聊天。
      (認識我的朋友都知道這幾年,這隻陪我早晚散步的狗)

      創業經營公司真的很辛苦的啦。或許沒有離開就
      沒辦法地去體會這麼多得令人感動的事。
      所以現在的我:凡事就多一點感恩,少一點抱怨。
      就有著更不一樣的視野與人生品味吧。

      有機會就勸勸你前公司長官吧。
      至於要不要當吃瓜群眾?不要太認真就好。
      不以物喜,不以己悲。人生會更自在的啦。

      刪除
  3. "有機會就勸勸你前公司長官吧。" <== 這個喔, 不會特意去講, 看緣分吧...

    "後來產品出貨後,部門不到十個人,也可以貢獻年營收上億元,
    這些對大公司來說:都是理所當然的運作模式。
    大家都是很優秀的工程師,也不會有人說:誰比較厲害的啦。"
    借用上述 "誰比較厲害的啦" 這段話說明理由:

    之前在那個地方上班, 常常會聽到, 公司某某人講(做)的是錯的, 觀念不正確, 我才是對的 (我才厲害), 彼此較勁, 喜歡 "踩"人來顯得自己"高",
    當時要離開時, 只是不經意的跟長官說, 做代理, 不就是幫原廠, 幫客戶抬轎,
    對方聽到當下立馬解釋(反駁?) , 不是的, 是他們(原廠與客戶)幫我們 (代理商)抬轎,
    是他們須要我們 ... 說著說著, 接續補上一句 "我能力很好...",
    為了順利離職, 不想掀起波連, 也只能點頭稱是, 結束談話

    勸對方, 如果弄得不好, 最後演變成跟他抬槓, 何必呢?

    回覆刪除
    回覆
    1. 喔~如果這樣子的話,那就隨緣吧。
      有些做技術出身,也多多少少都會自負一點吧。
      但我說過了,做技術也只不過是一種手段而已。
      公司要成功,還是要面面俱到的。

      尤其是通路代理商,在財務風險尤其更顯得重要。
      至於產品開發的技術,我贊同你的觀點:
      "不就是幫原廠, 幫客戶抬轎..."
      你不爭辯也是對的,
      我說了:人家不答腔,只是不想理你而已。

      反正職場上光怪陸離的事多了,但也並不代表,
      自己創業當老闆就不會發生這些鳥事,
      道理都是一樣的啦。大家也只能把握在共同利益當下,
      錢可以進每個人的口袋就比較實際了。

      誰比較厲害?天外有天,人外有人,
      真正厲害的都是"惦惦吃三碗公"的人啦。

      刪除