2023年11月29日 星期三

for .net core mvc ,關於json輸出、反序列化一二事

 1.有時系統太自動也不好,像是在json轉換時就是最好的例子了
明明在serverside裡的類別屬性是大寫開頭,結果輸出到json ( 像是在Action裡用「return Json(.....) 」) 去就變成小寫開頭
這就是System.Text.Json的問題了
想整個規避掉就直接在Program.cs裡做點小設定
-------------------------.net 6.0的 program.cs--------------------
 builder.Services.AddControllersWithViews()        
 .AddJsonOptions(options => {
     options.JsonSerializerOptions.PropertyNamingPolicy = null; // This preserves the original case
 });
-------------------------------------------------
(一般都只是「 builder.Services.AddControllersWithViews() ;  」而沒有用到AddJsonOptions)


有json出就有json入,最常用的是系統預設的
System.Text.Json.JsonSerializer.Deserialize<T>(string)」
沒有什麼不好啦,只是厚....如果遇到null轉換就會出錯了
如:
-----------------------
System.Text.Json.JsonException: 'The JSON value could not be converted to System.Nullable`1[System.Decimal]
-----------------------
像上述的錯誤會發生在parsing json string 把null轉成其他型態時會發生錯誤
如何解決?
這時就要請老朋友--NewtonSoft的Json lib相助了
-------------------------------------------
var v = Newtonsoft.Json.JsonConvert.DeserializeObject<Type>(jsonfiledString);
-----------------------------------------
問題就解決了
參考這裡

以上,大家加油吧




2023年10月28日 星期六

informix之.net core EF 套用.

 直接參考這文章做就差不多了
很多人會卡在Scaffold-DbContext這一關
重點是要注意一下PORT ,因為informix EF Core用的是DRDA的port,所以不是之前我們用odbc的(預設)9088而是9089.
如同上述內文說的:Please note: The IBM Data Server providers only works with the Distributed Relational Database Architecture™ (DRDA) protocols, and you therefore need to ensure that your Informix server is configured accordingly.


(這點你要跟你們家的DBA問一下給不給連,也要問網管開不開port,說真的,我也沒把握一次要他們從DEV、UAT到Production這樣一次開這麼多東西給你,所以本單元真的就是自己練功用的)


上述超連結提到的package有點舊了,我實驗的package有update,也都正常
-----------project target framework是.net 6.0------------------



-----------------------------
這是我localhost的scaffold-Dbcontext script,留做我日後小抄用
------------------------------------------------------------- 
Scaffold-DbContext -Connection "user id=informix;server=localhost:9089;database=db_with_log;Password=xxxOOO" -Provider IBM.EntityFrameworkCore -OutputDir Models  -Force -UseDatabaseNames -Tables test_user,test_data

--------------------------------------------
彩蛋一下:
這份PDF不錯,如果你是.net+informix的重度使用者,可以看一下

OS:明明用到的機會渺茫.我還是堅持到底地把它試出來玩了幾番,這也是種職業病吧





2023年10月20日 星期五

拜見老前輩--informix使用記錄(.net 4.5用)

最近因為案子需要,所以接觸到informix

為此在本機(win11)安裝了informix develper version
照理說應該不需要管到informix server的管理,這應該是DBA的工作
不過如果是本機開發測試需要,真的都要自己來

註:以下是為ODBC+.net4.5準備的操作,如果是可以用 .net core開發informix應用的話,請用entity framework core即可,IBM有為.net core發行了相關的EFcore套件.比較不必費心在transaction控管上面.既然上面要求要用.net4.x就老老實實配合就是了

----言歸正傳-----
安裝informix develper server不是問題,我們是用Odbc連線做之後的增刪修查
(因為這樣日後移機會比較好改比較簡單,只要做odbc 設定就好)
雖說「比較簡單」,一點也不簡單
首先來說,你要怎麼測能不能連,當然是用他們自家的工具--ConnectTesst Demo 測比較快

(以上的database下拉可以選才是真的連起來了,有驚嘆號沒選項就是沒連好,仔細看他的訊息,別使戇力猛試)



如果連不上,或是想把上面的host改成ip的話,請到「C:\Program Files\IBM Informix Software Bundle\etc\sqlhosts.ol_informix1410」去改把host pc name改成 ip

--------------------
ol_informix1410 olsoctcp 127.0.0.1 ol_informix1410 
dr_informix1410 drsoctcp 127.0.0.1 dr_informix1410 
lo_informix1410 olsoctcp 127.0.0.1 lo_informix1410 
--------------------
必要時,連「C:\Program Files\IBM Informix Software Bundle\ol_informix1410.cmd」的內容也要改
(連在C:\Windows\System32\drivers\etc\裡的「hosts」也加上本機名稱跟ip對應也再所不惜地改吧)
--------------------

set INFORMIXDIR=C:\PROGRA~1\IBMINF~1
rem set REGMACHINE=\\你的主機名稱    <--先remark起來,以下用ip替代
set REGMACHINE=127.0.0.1
set INFORMIXSERVER=ol_informix1410
set ONCONFIG=onconfig.ol_informix1410
set INFORMIXSQLHOSTS=C:\Program Files\IBM Informix Software Bundle\etc\sqlhosts.ol_informix1410
set GL_USEGLU=1
set PATH=%INFORMIXDIR%\bin;%PATH%
--------------------
這關過了,之後就是ODBC設定了(基本上,「ConnectTest Demo」 能通的話,這也是可以通了)
「可是只能用英文,我要能支援中文的話,可以嗎」

當然可以,而且你也一定不會在內建的「sysxxx」db裡操作(DBA也會把你趕出去的),我們要用別的工具來create db.免驚啦,免錢的哦,他就是鼎鼎大名的DBVisualizer 而且重點不是只有在creeate db而已,是在其連線過程.
一般只會講到這一頁


但是,為了要設定之後的locale,就要用到這一頁了
(注意上面有三個user setting:DB_LOCALE,CLIENT_LOCALE & charSet,一般是沒這三個設定)

這樣後,用DBvisualizer連入informix後,就可以用它來create db了



然後,再回到odbc設定語系,才不會因為語系不同而無法連入
(照理dba會幫你做好db的建置的,但別忘了註明我要中文在地化支援)
看一下中文語系是不是DB的預設語系


這樣你就可以好好享用ODBC + .net操作了
「可是....我怎麼試,transaction總是auto commit,到底要怎樣才能支援transaction?」
「很好,這表示你真的有在玩下去,你可以出山下山了」
想要用.net的OdbcTransaction ,你不只要在odbc那裡把「Auto Commit Optimization」不打勾之外


(以上這點有沒有 check都沒差)
更重要的是.....
在create db時,log mode一定不能選「none」,好歹有個log模式就是了(不知道就用「with log」)


這樣,才會支援交易機制
上述的設定,請參考以下討論
大家加油吧
(忘了跟這次的要角--IBM informix ODBC Driver Setup合影一下,來,笑一個吧)





2023年7月5日 星期三

小試一下,FSMC之於LCD(NT35510),在F103的設定(by Stm32CubeMx)

  雖然用GPIO會比較快,也可以省卻「如果你的chip不支援FSMC...」的問題

不過多學會點會比較好些,也許哪天會用到也不一定,
本篇參考以下兩個URL:這個 & 這個
NT35510的規格書在此

先上圖吧


(別忘了Back Light要On起來,沒有back light,沒有顯示)
先用read ID & color fomat測看看,如果讀出是亂碼,請到上圖的NOR/PSRAME timing for wirte access那裡改一下,有時要多點clock才會正確 (參考上面兩個URL的內文介紹如個推算,因為我是用NT35510的,所以會以第二個link為主)

程式碼如下
---------------------------------------------------
//因為是用base1 第 4區,所以base address是0x6C000000
 (即0x60000000~0x6FFFFFFF分成區,第四區起始位址即為0x6C000000,參考「STM32F103 战舰开发指南V1.1.pdf」P365)
#define FMC_ADDR_CMD  ((uint32_t) 0x6C000000)
//而A10做為C/D(或曰register select),又要往左shfit一位,故為00800
#define FMC_ADDR_DATA  ((uint32_t) 0x6C000800)
//在 MX_FSMC_Init();後,這些對應建立起來後,就可以直接用位址的方式讀寫,不必再用8080介面操作了...
void lcd_wr_cmd(volatile uint16_t cmd)
{
    cmd = cmd;
    *(uint16_t *)(FMC_ADDR_CMD) = cmd;
}
void lcd_wr_data(volatile uint16_t data)
{
     data = data;
*(uint16_t *)(FMC_ADDR_DATA) = data;
}
uint16_t lcd_rd_data(void)
{
volatile uint16_t ram; 
ram = *(uint16_t *)(FMC_ADDR_DATA);
return ram;
}
...
...
main的內容
    HAL_Init();
   MX_FSMC_Init();
   MX_USART1_UART_Init();
 //神說,要有光,於是就有了光
 HAL_GPIO_WritePin(LCD_BL_GPIO_Port,LCD_BL_Pin,GPIO_PIN_SET);
  HAL_Delay(10);
  lcd_wr_cmd(0x1100);    //Sleep out & booster on 
  HAL_Delay(10);
  lcd_wr_cmd(0x2900);   //display memory  to frame.
  HAL_Delay(10);
  //第一步,先試著讀id,如果傳回值不是80h的話,就是錯了
  lcd_wr_cmd(0x0401);
  HAL_Delay(100);
  uint16_t chip_id=0xabcd;
  chip_id=lcd_rd_data();
  
  //試著讀寫 color format,原先是8x8x8 RBG格式,如今我們為了測試,用565格式
  //0x0C00是read color mode,而0x3A00是write color mode.
  uint16_t pix_fomrat_org=0;
  lcd_wr_cmd(0x0c00);pix_fomrat_org=lcd_rd_data();
  lcd_wr_cmd(0x3A00); lcd_wr_data(0x55);
  lcd_wr_cmd(0x0c00);
  uint16_t pix_fomrat=lcd_rd_data();
   //清除資料一下 (預設尺寸是480*800)
  lcd_wr_cmd(0x2c00);; //demo how to do "clear screen"
  for(uint32_t i=0;i<(0x01df-1)*(0x03ef-1);i++){
      lcd_wr_data(0x07E0);
  }
  
  //最後,把大頭照放上去 x,y : 200,200, width& height:140x140
  lcd_wr_cmd(0x2A00);lcd_wr_data(0);;
  lcd_wr_cmd(0x2A01);lcd_wr_data(200);
  lcd_wr_cmd(0x2A02);lcd_wr_data(339>>8);
  lcd_wr_cmd(0x2A03);lcd_wr_data(339&0xff);
  
  lcd_wr_cmd(0x2B00);lcd_wr_data(0);
  lcd_wr_cmd(0x2B01);lcd_wr_data(200);
  lcd_wr_cmd(0x2B02);lcd_wr_data(339>>8);
  lcd_wr_cmd(0x2B03);lcd_wr_data(339&0xff);  
  lcd_wr_cmd(0x2c00); //demo how to do "clear screen"
  for(uint32_t i=0;i<(140*140);i++){
      lcd_wr_data(g_pexl[i]);
  }
  
 //然後就是while(1)...

---------------------------------------------------
 耶~ 有看到圖片了(會不會太自戀了?)
------




------
這次的實作基本上只是個小插曲,還是建議用GPIO直接去操作,以清除螢幕這個函數來說好了
-----------
void fill_color(uint16_t color, uint32_t count)

   //當然以下的HAL_GPIO_WritePin都可以再直接操作BSRR暫存器來最佳化之,
  //只是為了實作驗證是否了解正確,直接用HAL程式庫了
    set_d0tod7_output();
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port,LCD_DC_Pin,GPIO_PIN_RESET);//RS:0
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_SET);//write 1
    HAL_GPIO_WritePin(LCD_READ_GPIO_Port,LCD_READ_Pin,GPIO_PIN_SET);//RD 1
    HAL_GPIO_WritePin(LCD_CS_GPIO_Port,LCD_CS_Pin,GPIO_PIN_RESET);//CS 0
    
    write_byte_to_gpio(0x2c00);
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_RESET);//write 0
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_SET);//write 1
    
    //set to data mode
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port,LCD_DC_Pin,GPIO_PIN_SET);//RS:1
    write_byte_to_gpio(color);
    uint32_t pin16u=(uint32_t)LCD_WRITE_Pin<< 16u;
    for(uint32_t i=0;i<count;i++)
    {
         LCD_WRITE_GPIO_Port->BSRR = pin16u; //RESET
         LCD_WRITE_GPIO_Port->BSRR = LCD_WRITE_Pin;//SET
     }
    //all done
    HAL_GPIO_WritePin(LCD_CS_GPIO_Port,LCD_CS_Pin,GPIO_PIN_SET);//CS 1
    HAL_GPIO_WritePin(LCD_DC_GPIO_Port,LCD_DC_Pin,GPIO_PIN_SET);//RS:1
    HAL_GPIO_WritePin(LCD_READ_GPIO_Port,LCD_READ_Pin,GPIO_PIN_SET);//read 1
    HAL_GPIO_WritePin(LCD_WRITE_GPIO_Port,LCD_WRITE_Pin,GPIO_PIN_SET);//write 1
}
-----------
這比填入FSMC再填入LCD快了點(直接高低電位切換,當然快啊,這樣比很不講武德捏)
而且會看技術規格文件,是這行必備的生存技能
當然FSMC會了也是很好的一件事,畢竟很多時候可以對應到別的設備的,大大節省開發難度
要學的東西真的太多了,一起加油吧.

2023年6月18日 星期日

For Xamarin.android 如何用Android.Hardware.usbconnect來做HID 的write report類似的動作

 最近拿到一個設備做整合的案子,是USB-HID介面
跟廠商要到的sample code是用HidSharp的寫.
-------------------------------
                    var device = HidDevices.Enumerate(vendorId, productId).First();
                    dev.OpenDevice();
                    const UInt32 ZEROS=0x00;
                    const UInt32  Heading04=.....;
                    var bytes=new List<Byte>();
                    bytes.AddRange(BitConverter.GetBytes(Heading04).Reverse().ToArray());
                    bytes.AddRange(BitConverter.GetBytes(ZEROS).Reverse().ToArray());
                    //to write data to HID,use "report" as pocket
                    var outputReport = dev.CreateReport();
                    outputReport.ReportId = 0;
                    outputReport.Data=bytes.ToArray();
                    dev.WriteReport(outputReport);
-------------------------------
今欲轉移到安卓設備上,一直找不到HID 類似的實作.
「既然沒有,就自己包了」,不過HID真的是很不單純的規格(光是USB就有一堆書跟規格要看了),談個容易
「不然就觀察看看上述的code,在USB介面是如何動作的?」
 這時就要借助好用的工具「DeviceMonitor Studio」(試用期14天,動作快!!)
因為我們知道是用USB的HID規格,所以監視方式請用HID等選項


不過,真正比較有用的是在packet view的狀態.....

嗯,看那裡面的參數Request、Value、index、data.... ,真的是「火燒豬頭-- 面熟面熟咧」 
「阿這不就是UsbConnection的ControlTransfer嘛」
「好啊,那你試看看用controlTransfer會不會傳回-1?如果不是-1表示成功了」
先讀看看Descrption看看,如果可能,就可以把其他動作給它(controlTransfer)來做了
xamarin.android ControlTransfer的API在此
「我試了UsbAddressing.In的方式一直讀取不了資料,心很累....」
別一開始就埋頭苦幹用戇力(gōng-lak)--先找成功案例的code來改來測才好比對驗證
先以這篇討論的例子來改
---
UsbDeviceConnection.controlTranfer(0x81, 0x06, 0x2200, 0x00, byte[] buffer, 104, 2000)
----
「第一個參數不是UsbAddressing型態嗎?怎麼會是一個叫0x81的整數?」
「儘信API不如無API,更何況那是從java包裝來的,直接把0x81 cast成UsbAddress就是了」
所以用以下的code來測,結果傳回值不再是-1,而是37(是的buffer 裡也有值了,我出運啊,我出運啊啦...)
-------------------------
var returvalue = conn.ControlTransfer((UsbAddressing)0x81, 0x06, 0x2200, 0,buffer,200,int.MaxValue);
//returnvalue為37
--------------------------
「嗯....又是另一個問題了,,為什麼是0x81,而不是UsbAddress.In? 為什麼用UsbAddress.In & UsbAddress.Out傳回都是-1 ...為什麼」
「請把剛才那篇討論文看完,答案就在大師回覆的連結裡」
參考這段文章吧,為什麼address選0x81? 因為bit7 & bit0是1,表示為Device to host(Interface)
這個bmRequestType表很重要,他是問題核心.


為什麼request用0x06? 因為要讀Description


綜上所知,如果要把我們在DeviceMonitor studio的packet所看到的動作,用controlTransfer來包裝的話
那address要選什麼?.....答案就在畫面裡....


試試看把Request & Value都改成我們要傳送的資料,而UsbAddress改成0b00100001看看
(即表"bmRequestType"裡的"interface" & "class"兩個bit都要on起來)
--------------------------
List<Byte> bytes = new List<Byte>(0);
              ......
                bytes.AddRange(BitConverter.GetBytes(Heading04).Reverse().ToArray());
                bytes.AddRange(BitConverter.GetBytes(ZEROS).ToArray());
                returvalue = await conn.ControlTransferAsync((UsbAddressing)0b00100001, 0x09, 0x0200, 0, bytes.ToArray(), bytes.Count, int.MaxValue);
                System.Console.WriteLine($" return: {returvalue}");
                await Task.Delay(10);
---------------------------
returnvalue不再是-1,也很正確地輸出訊號到HID上了
這次真的很難得的經驗啊
一起加油吧







2023年5月15日 星期一

for STM32F103,抓輸入的PWM的週期及高電位時間

 別看原子哥老師的程式碼寫的落落長,

反正就把握一個原則:
測量器TIM8的slave mode用reset mode (即:一有輸入,即觸發一次reset /update 事件)
TIM8的trigger是TI1FP1 (即,以timer input filter polarity channel1 做為觸源)
但,因為CNT的增減還是由這個timer自動動作,所以還是要有時基,故選內部時鐘
是要做為測量工具,儘可能求最細精度及最大週期,所以就用不分頻及period為65535為基礎,如下圖


至於direct & indirect ,是看pdf 教材來的

(摘錄自「STM32F103 战舰开发指南V1.1.pdf」22.5節  高級定時器PWM輸入模式實驗 323頁)
別隨便用chatGPT的code,那會讓你更迷糊的
至於待測物--PWM訊號源的話,先以TIM3 CHANNEL2為PWM源,跳線PB5<->PC6 (TIM8 channel1的GPIO pin),時鐘的基本設定上,因為考量到TIM1的最長週期為65535個count 約0.0009秒,即900微秒...
「這麼短,如果遇到超過1亳秒的週期豈不是掛了」
「先從小的測,測出來正確再放大」
TIM3的初設定:72-1分頻,ARR為100-1,佔空比為50 
初版程式如下:
------------------------------------------

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM3_Init();
  MX_TIM8_Init();
  MX_USART1_UART_Init();

   HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_2);
   HAL_Delay(5000);
   HAL_TIM_Base_Start(&htim8);
   HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_1);
   HAL_TIM_IC_Start_IT(&htim8,TIM_CHANNEL_2);
  
  while (1)
  {
      HAL_Delay(100);
      if(1)
      {
          printf(" period :%i ; hi-time:%i \r\n",period,high_time);
      }
  }
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
     period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
     high_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
}
------------------------------------------
結果輸出的值是period為7198,hi-time為3598.都要加2才是正確的7200 & 3600
(其中的+1是因為「從0起算」;另一個加一是什麼?知道的朋友請開示一下)
驗算一下:7200* 1/72000000 = 0.0001 sec ,佔空比50%,故為3600 * 1/72000000=0.00005 sec



「那,放大到10ms的週期,要怎麼改?」
「就在TIM8的update事件都加上365536,就這麼簡單」
「可是update事件也可能是因為TIM3 rising edge引發的,也可能是TIM8自己的reset引發的,
如何是好?」
這就好好安排「update 」及「capture compare」兩個中斷的順序了
我們打算先在input capture做一個旗標,確定是真的已經進入了一個偵測流程了(而且要排除在半路遇到下降緣引發的input capture 中斷),日後的update interrupt 再來進行計次,
所以中斷的優先高低是input capture 高於 update 
「一定要這麼複雜嗎?」
「一定要,因為預設是update優於input capture」



然後,code小改一下...
1.main那裡要呼叫「__HAL_TIM_ENABLE_IT(&htim8,TIM_IT_UPDATE);」
(別忘了配置了什麼中斷就要用這方式enable起來)
---------------------------------
uint8_t begin_capture=0;
uint32_t overflow_count=0;
//讓timer8一直count下去吧
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if(htim->Instance==TIM8 && begin_capture==1)
    {
        overflow_count++;
    }
}
//加上over flow之後的推算
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
   if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1 )
   {
      //測得rsing edge,但是否為一個新的測量流程?
       if(begin_capture==0)
       {
           begin_capture=1;
           overflow_count=0;
           return;
       }else{
          //測量完畢
           begin_capture=0;
          //別忘了+2
           period = (HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1)+2)+
                            (overflow_count>0?(65536*(overflow_count-1)):0);
          
       }
       
   }
    //測得falling edge ,而且還是「測量中」才算數
   if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2 && begin_capture==1)
   {
       high_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2)+2+
                            (overflow_count>0?(65536*(overflow_count-1)):0);
   }
}
---------------------------------
我們再去把TIM3改成很長的週期:預分頻為7200-1,ARR為50000-1,pulse為2500
程式輸出結果為
period:360000000 & hi-time:18000000 
算一下 360000000*72Mhz=5sec;  18000000 * 72Mhz=0.25秒,

 
正確無誤,
而且也不必像正點原子哥的程式那麼複雜
「為什麼那個overflow要減一才能乘65536?」
「因為第一次的input capture calback後還是執行了update中斷了,所以要把最初的那次扣掉」
(事實上我也是實驗,先修改/扣除...再回想,才了解這一點的,要學的東西太多了)

附記:
STM32 timer系列可以參考這個播放 清單, 這位老師也是座寶山....




2023年4月12日 星期三

for stm32,如果要在GPIO的EXTI中斷處理裡面用delay的話,怎麼設計比較好

或許你會直接想說那好辨,就是在 HAL_GPIO_EXTI_Callback 裡做HAL_Delay
但是這會讓你debug到死都會在HAL_Delay裡完成不了,為什麼?
因為預設的SysTick的優先序是排到15去,而後續定義的EXTI的優先層級都比它還高
當然是SysTick在EXTI call back裡會死在那裡 ,參考這裡說明
所以,要調整一下下兩者間的NVIC優先序,如下圖
-------------------------------------


-------------------------------------
這樣就可以在HAL_GPIO_EXTI_Callback 裡呼叫HAL_Delay了
為什麼SysTick會被安排到這麼後面的優先序裡去?可能是因為分時多工會用到
既然是分時多工之用,那一定是會有時間分割上的出入,自然不若GPIO那樣要即時處理電位起伏帶來的事件/中斷來得緊急(優先)之故,畢竟GPIO是在最前線作戰的單位,其所關注的事件一定是有很高的優先處理順序.這點很合理的
「可是,如果我要delay到小於1毫秒的delay,或是不想去修改它們的優先序,如何是好?」
如果不想用Standard Peripheral Library的用操作SysTick暫存器的寫法的話,可以用Timer對counter增減的方式來做delay的依據
參方這位老師的教學,(prescaler 72-1=  72MHz/72 -> 1MHz--> 1us ) 注意,是微秒級,不是奈秒.
-------------------


--------------------
並在main.c裡加入delay_us函數.(當然你也要有「MX_TIM1_Init();」跟「HAL_TIM_Base_Start(&htim1);」啟用TIM1)
-------------------
void delay_us (uint16_t us)
{
	__HAL_TIM_SET_COUNTER(&htim1,0);  // set the counter value a 0
	while (__HAL_TIM_GET_COUNTER(&htim1) < us);  // wait for the counter to reach the us input in the parameter
}
-------------------
之後,再回到我們之前要處理的「HAL_GPIO_EXTI_Callback 」使用它
------------假設我們設定的是下降緣觸發事件--------------
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{   
       //delay一下,事緩則圓--消除突波之用
        delay_us(1000*50);
        delay_us(1000*50);
        //如果此時按鈕線被拉回高電位的話,不處理
        //密技:即便是用在中斷模式,stm32的GPIO 腳位還是可以讀取狀態的
        if(HAL_GPIO_ReadPin(BUTTON_KEY1_GPIO_Port,BUTTON_KEY1_Pin)==  GPIO_PIN_SET)
        {
          return;
        }
       //是的,已經確定是按下狀態
      //處理中 ...
       ....
------------------------------------------------------------------
以上.
(科班出身的都那麼拚了,那非本科出身的,豈不是要更努力才行了?)