EC11驅動程式
不多做介紹,百度上很多相關的介紹
首先,放上參考檔案
代碼有正轉、反轉、按下正轉、按下反轉、單機、雙擊、長按和長安松手檢測,有什么問題可以在下方留言
我是對該檔案代碼進行一些修改,偏向于我的習慣做的修改,
主要是作者的代碼在按下并旋轉的時候會觸發長按(也許是我移植的時候,改錯了什么所導致的)、雙擊,
我把它改為不觸發,并且讓函式做回傳值處理,最終的按鍵操作在主函式或者單獨寫一個設定,便于后續移植,
- EncoderEC11.c
//---->>>>----檔案描述:EC11旋轉編碼器底層驅動程式---<<<<----//
//---->>>>----檔案版本:V1.0----<<<<----//
#include "EncoderEC11.h"
//-------->>>>>>>>--------注意事項:EC11旋轉編碼器的掃描時間間隔控制在1~4ms之間,否則5ms及以上的掃描時間在快速旋轉時可能會誤判旋轉方向--------<<<<<<<<--------//
//*******************************************************************/
//功能:初始化EC11旋轉編碼器相關引數
//形參:EC11旋轉編碼器的型別-->> unsigned char Set_EC11_TYPE <<-- :0----一定位對應一脈沖;1(或非0)----兩定位對應一脈沖,
//回傳:無
//詳解:對EC11旋轉編碼器的連接IO口做IO口模式設定,以及將相關的變數進行初始化
//*******************************************************************/
void Encoder_EC11_Init(unsigned char Set_EC11_TYPE)
{
// //IO口模式初始化,初始化EC11的IO口為準雙向模式
// P35_QB();
// P36_QB();
// P37_QB();
EC11_A_Now = 1;
EC11_B_Now = 1;
EC11_Key = 1;
//EC11型別選擇:0-一定位一脈沖;1-兩定位一脈沖
if(Set_EC11_TYPE == 0)
{
EC11_Type = 0;
}
else
{
EC11_Type = 1;
}
//避免上電時EC11旋鈕位置不確定導致一次動作誤判
EC11_A_Last = EC11_A_Now;
EC11_B_Last = EC11_B_Now;
//--------清除按鍵計數器和標志位--------//
EC11_KEY_COUNT = 0; //EC11按鍵動作計數器
EC11_KEY_DoubleClick_Count = 0; //EC11按鍵雙擊動作計數器
FLAG_EC11_KEY_ShotClick = 0; //EC11按鍵短按動作標志
FLAG_EC11_KEY_LongClick = 0; //EC11按鍵長按動作標志
FLAG_EC11_KEY_DoubleClick = 0; //EC11按鍵雙擊動作標志
}
//*******************************************************************/
//功能:掃描EC11旋轉編碼器的動作并將引數回傳給動作分析函式使用
//形參:EC11旋轉編碼器的型別-->> unsigned char Set_EC11_TYPE <<-- :0----一定位對應一脈沖;1(或非0)----兩定位對應一脈沖
//回傳:EC11旋轉編碼器的掃描結果-->> char ScanResult -->> 0:無動作;1:正轉; -1:反轉;2:只按下按鍵;3:按著按鍵正轉;-3:按著按鍵反轉
//詳解:只掃描EC11旋轉編碼器有沒有動作,不關心是第幾次按下按鍵或長按或雙擊,回傳值直接作為形參傳給 [ void Encoder_EC11_Analyze(char EC11_Value); ] 函式使用
//*******************************************************************/
char Encoder_EC11_Scan()
{
//以下儲存A、B上一次值的變數宣告為靜態全域變數,方便對EC11對應的IO口做初始化
// static char EC11_A_Last = 0;
// static char EC11_B_Last = 0;
char ScanResult = 0; //回傳編碼器掃描結果,用于分析編碼器的動作
//回傳值的取值: 0:無動作; 1:正轉; -1:反轉;
// 2:只按下按鍵; 3:按著按鍵正轉; -3:按著按鍵反轉
//======================================================//
if(EC11_Type == 0) //================一定位對應一脈沖的EC11================//
{ //======================================================//
if(EC11_A_Now != EC11_A_Last) //以A為時鐘,B為資料,正轉時AB反相,反轉時AB同相
{
if(EC11_A_Now == 0)
{
if(EC11_B_Now ==1) //只需要采集A的上升沿或下降沿的任意一個狀態,若A下降沿時B為1,正轉
ScanResult = 1; //正轉
else //反轉
ScanResult = -1;
}
EC11_A_Last = EC11_A_Now; //更新編碼器上一個狀態暫存變數
EC11_B_Last = EC11_B_Now; //更新編碼器上一個狀態暫存變數
}
}
//======================================================//
else //================兩定位對應一脈沖的EC11================//
{ //======================================================//
if(EC11_A_Now !=EC11_A_Last) //當A發生跳變時采集B當前的狀態,并將B與上一次的狀態進行對比,
{ //若A 0->1 時,B 1->0 正轉;若A 1->0 時,B 0->1 正轉;
//若A 0->1 時,B 0->1 反轉;若A 1->0 時,B 1->0 反轉
if(EC11_A_Now == 1) //EC11_A和上一次狀態相比,為上升沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次狀態相比,為下降沿
ScanResult = 1; //正轉
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次狀態相比,為上升沿
ScanResult = -1; //反轉
//>>>>>>>>>>>>>>>>下面為正轉一次再反轉或反轉一次再正轉處理<<<<<<<<<<<<<<<<//
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿時,采集的B不變且為0
ScanResult = 1; //正轉
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿時,采集的B不變且為1
ScanResult = -1; //反轉
}
else //EC11_A和上一次狀態相比,為下降沿
{
if((EC11_B_Last == 1)&&(EC11_B_Now == 0)) //EC11_B和上一次狀態相比,為下降沿
ScanResult = -1; //反轉
if((EC11_B_Last == 0)&&(EC11_B_Now == 1)) //EC11_B和上一次狀態相比,為上升沿
ScanResult = 1; //正轉
//>>>>>>>>>>>>>>>>下面為正轉一次再反轉或反轉一次再正轉處理<<<<<<<<<<<<<<<<//
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 0)) //A上升沿時,采集的B不變且為0
ScanResult = -1; //反轉
if((EC11_B_Last == EC11_B_Now)&&(EC11_B_Now == 1)) //A上升沿時,采集的B不變且為1
ScanResult = 1; //正轉
}
EC11_A_Last = EC11_A_Now; //更新編碼器上一個狀態暫存變數
EC11_B_Last = EC11_B_Now; //更新編碼器上一個狀態暫存變數
}
}
if(EC11_Key == 0) //如果EC11的按鍵按下,并且沒有EC11沒有轉動,
{
if(ScanResult == 0) //按下按鍵時未轉動
ScanResult = 2; //回傳值為2
else
{
if(ScanResult == 1) //按下按鍵時候正轉
ScanResult = 3; //回傳值為3
if(ScanResult == -1) //按下按鍵時候反轉
ScanResult = -3; //回傳值為-3
}
}
return ScanResult; //回傳值的取值: 0:無動作; 1:正轉; -1:反轉;
} // 2:只按下按鍵; 3:按著按鍵正轉; -3:按著按鍵反轉
//*******************************************************************/
//功能:對EC11旋轉編碼器的動作進行分析,并作出相應的動作處理代碼
//形參:無
//回傳:char AnalyzeResult = 0;目前無用,若在該函式里做了動作處理,則函式的回傳值無需理會
//詳解:對EC11旋轉編碼器的動作進行模式分析,是單擊還是雙擊還是長按松手還是一直按下,形參從 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函式傳入,在本函式內修改需要的動作處理代碼
//*******************************************************************/
char Encoder_EC11_Analyze(char EC11_Value)
{
static bit FLAG_KEY_invalid;
char AnalyzeResult = 0;
static unsigned int TMP_Value = 0; //中間計數值,用于連續長按按鍵的動作延時間隔
//>>>>>>>>>>>>>>>>編碼器正轉處理程式<<<<<<<<<<<<<<<<//
if(EC11_Value == 1) //正轉
{
//--------編碼器正轉動作代碼--------//
AnalyzeResult = 1;
}
//>>>>>>>>>>>>>>>>編碼器反轉處理程式<<<<<<<<<<<<<<<<//
else if(EC11_Value == -1) //反轉
{
//--------編碼器反轉動作代碼--------//
AnalyzeResult = 2;
}
//>>>>>>>>>>>>>>>>編碼器按鍵按下并正轉處理程式<<<<<<<<<<<<<<<<//
else if(EC11_Value == 3)
{
//--------編碼器按鍵按下并正轉動作代碼--------//
AnalyzeResult = 3;
}
//>>>>>>>>>>>>>>>>編碼器按鍵按下并反轉處理程式<<<<<<<<<<<<<<<<//
else if(EC11_Value == -3)
{
//--------編碼器按鍵按下并反轉動作代碼--------//
AnalyzeResult = 4;
}
if((AnalyzeResult == 3) || (AnalyzeResult == 4))// 按下并轉動
{
FLAG_KEY_invalid = 1;//使按鍵單操作無效
EC11_KEY_COUNT = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_LongClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
EC11_KEY_DoubleClick_Count = 0;
}
if(FLAG_KEY_invalid == 0)
{
//>>>>>>>>>>>>>>>>編碼器按鍵按下處理程式<<<<<<<<<<<<<<<<//
if(EC11_Value == 2) //====檢測到按鍵按下====//
{
if(EC11_KEY_COUNT<10000) //打開按鍵按下時間定時器
EC11_KEY_COUNT++;
if(EC11_KEY_COUNT == KEY_COUNT_DESHAKING) //按下按鍵時間到達消抖時間時
{ //置位短按按鍵標志
FLAG_EC11_KEY_ShotClick = 1;
}
if((EC11_KEY_DoubleClick_Count > 0)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //松開按鍵后,又在定時器在雙擊時間內按下按鍵
{ //置位雙擊按鍵標志
FLAG_EC11_KEY_DoubleClick = 1;
}
if(EC11_KEY_COUNT == KEY_COUNT_LONGTIME) //按下按鍵時間到達長按時間
{ //置位長按按鍵標志并復位短按按鍵標志
FLAG_EC11_KEY_LongClick = 1;
FLAG_EC11_KEY_ShotClick = 0;
}
}
else //====檢測到按鍵松開====//
{
if(EC11_KEY_COUNT < KEY_COUNT_DESHAKING) //沒到消抖時長就松開按鍵,復位所有定時器和按鍵標志
{
EC11_KEY_COUNT = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_LongClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
EC11_KEY_DoubleClick_Count = 0;
}
else
{
if(FLAG_EC11_KEY_ShotClick == 1) //短按按鍵定時有效期間
{
if((FLAG_EC11_KEY_DoubleClick == 0)&&(EC11_KEY_DoubleClick_Count >= 0))
EC11_KEY_DoubleClick_Count++;
if((FLAG_EC11_KEY_DoubleClick == 1)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //如果在規定雙擊時間內再次按下按鍵
{ //認為按鍵是雙擊動作
FLAG_EC11_KEY_DoubleClick = 2;
}
if((FLAG_EC11_KEY_DoubleClick == 0)&&(EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)) //如果沒有在規定雙擊時間內再次按下按鍵
FLAG_EC11_KEY_ShotClick = 0; //認為按鍵是單擊動作
}
if(FLAG_EC11_KEY_LongClick == 1) //檢測到長按按鍵松開
FLAG_EC11_KEY_LongClick = 0;
}
}
//>>>>>>>>>>>>>>>>編碼器按鍵分析處理程式<<<<<<<<<<<<<<<<//
if(EC11_KEY_COUNT > KEY_COUNT_DESHAKING) //短按按鍵延時到了時間
{
//短按按鍵動作結束代碼
if((FLAG_EC11_KEY_ShotClick == 0)&&(EC11_KEY_DoubleClick_Count > KEY_COUNT_DUALCLICKTIME)&&(EC11_KEY_COUNT < KEY_COUNT_LONGTIME)) //短按按鍵動作結束代碼
{
//--------短按按鍵動作結束代碼--------//
AnalyzeResult = 5;
//--------清除標志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//雙擊按鍵動作結束代碼
if((FLAG_EC11_KEY_DoubleClick == 2)&&(EC11_KEY_DoubleClick_Count > 0)&&(EC11_KEY_DoubleClick_Count <= KEY_COUNT_DUALCLICKTIME)) //雙擊按鍵動作結束代碼
{
//--------雙擊按鍵動作結束代碼--------//
AnalyzeResult = 6;
//--------清除標志位--------//
EC11_KEY_COUNT = 0;
EC11_KEY_DoubleClick_Count = 0;
FLAG_EC11_KEY_ShotClick = 0;
FLAG_EC11_KEY_DoubleClick = 0;
}
//連續長按按鍵按下代碼
if((FLAG_EC11_KEY_LongClick == 1)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //連續長按按鍵按下代碼
{
TMP_Value ++;
if(TMP_Value % KEY_LONG_REPEAT_TIME == 0)
{
TMP_Value = 0;
//-------連續長按按鍵按下代碼--------//
AnalyzeResult = 8;
}
}
//長按按鍵動作結束代碼
if((FLAG_EC11_KEY_LongClick == 0)&&(EC11_KEY_COUNT >= KEY_COUNT_LONGTIME)) //長按按鍵動作結束代碼
{
//--------長按按鍵按下動作結束代碼--------//
AnalyzeResult = 7;
//--------清除標志位--------//
EC11_KEY_COUNT = 0;
}
}
}
if(EC11_Key == 1)
{
FLAG_KEY_invalid = 0;
}
return AnalyzeResult;
//1:正轉
// 2:反轉
// 3:按下并正轉
// 4:按下并反轉
// 5:短按
// 6:雙擊
// 7:長按結束
// 8:長按
}
- EncoderEC11.h
//---->>>>----檔案描述:EC11旋轉編碼器底層驅動程式---<<<<----//
//---->>>>----檔案版本:V1.0----<<<<----//
#ifndef __EncoderEC11_H
#define __EncoderEC11_H
//#include "config.H"
#include <stc8.h>
//----------------IO口定義----------------//
#define EC11_A_Now P00 //EC11的A引腳,視為時鐘線
#define EC11_B_Now P01 //EC11的B引腳,視為信號線
#define EC11_Key P02 //EC11的按鍵
//----------------編碼器動作代碼相關定義----------------//
extern int G_PWM_NUM1;
extern int G_PWM_NUM2;
extern int G_PWM_NUM3;
static unsigned char EC11_NUM_SW = 0;
//----------------編碼器引數微調宏定義----------------//
#define EC11_SCAN_PERIOD_MS 1 //EC11編碼器掃描周期
#define KEY_COUNT_DESHAKING ( 20/EC11_SCAN_PERIOD_MS) //按鍵消抖時間
#define KEY_COUNT_LONGTIME (600/EC11_SCAN_PERIOD_MS) //長按按鍵判斷時間
#define KEY_COUNT_DUALCLICKTIME (150/EC11_SCAN_PERIOD_MS) //雙擊按鍵判斷時間
#define KEY_LONG_REPEAT_TIME (200/EC11_SCAN_PERIOD_MS) //長按按鍵的回報率的倒數,即一直長按按鍵時回應的時間間隔
//----------------區域檔案內變數串列----------------//
static char EC11_A_Last = 0; //EC11的A引腳上一次的狀態
static char EC11_B_Last = 0; //EC11的B引腳上一次的狀態
static char EC11_Type = 1; //定義變數暫存EC11的型別---->>>>---- 0:一定位對應一脈沖; 1:兩定位對應一脈沖
//所謂一定位對應一脈沖,是指EC11旋轉編碼器每轉動一格,A和B都會輸出一個完整的方波,
//而 兩定位對應一脈沖,是指EC11旋轉編碼器每轉動兩格,A和B才會輸出一個完整的方波,只轉動一格只輸出A和B的上升沿或下降沿
static int EC11_KEY_COUNT = 0; //EC11按鍵動作計數器
static int EC11_KEY_DoubleClick_Count = 0; //EC11按鍵雙擊動作計數器
static char FLAG_EC11_KEY_ShotClick = 0; //EC11按鍵短按動作標志
static char FLAG_EC11_KEY_LongClick = 0; //EC11按鍵長按動作標志
static char FLAG_EC11_KEY_DoubleClick = 0; //EC11按鍵雙擊動作標志
//----------------函式快速呼叫(復制粘貼)串列----------------//
//
/*******************************************************************
void Encoder_EC11_Init(unsigned char Set_EC11_TYPE); //初始化EC11旋轉編碼器IO口和型別以及變數初始化
char Encoder_EC11_Scan(); //掃描旋轉編碼器的動作
void Encoder_EC11_Analyze(char EC11_Value); //分析EC11旋轉編碼器的動作以及動作處理代碼
******************************************************************/
//-------->>>>>>>>--------注意事項:EC11旋轉編碼器的掃描時間間隔控制在1~4ms之間,否則5ms及以上的掃描時間在快速旋轉時可能會誤判旋轉方向--------<<<<<<<<--------//
//-------->>>>>>>>--------注意事項:EC11旋轉編碼器的掃描時間間隔控制在1~4ms之間,否則5ms及以上的掃描時間在快速旋轉時可能會誤判旋轉方向--------<<<<<<<<--------//
//-------->>>>>>>>--------注意事項:EC11旋轉編碼器的掃描時間間隔控制在1~4ms之間,否則5ms及以上的掃描時間在快速旋轉時可能會誤判旋轉方向--------<<<<<<<<--------//
//----------------函式宣告串列----------------//
//
//*******************************************************************/
//功能:初始化EC11旋轉編碼器相關引數
//形參:EC11旋轉編碼器的型別-->> unsigned char Set_EC11_TYPE <<-- :0----一定位對應一脈沖;1(或非0)----兩定位對應一脈沖,
//回傳:無
//詳解:對EC11旋轉編碼器的連接IO口做IO口模式設定,以及將相關的變數進行初始化
//*******************************************************************/
void Encoder_EC11_Init(unsigned char Set_EC11_TYPE);
//*******************************************************************/
//功能:掃描EC11旋轉編碼器的動作并將引數回傳給動作分析函式使用
//形參:EC11旋轉編碼器的型別-->> unsigned char Set_EC11_TYPE <<-- :0----一定位對應一脈沖;1(或非0)----兩定位對應一脈沖
//回傳:EC11旋轉編碼器的掃描結果-->> char ScanResult -->> 0:無動作;1:正轉; -1:反轉;2:只按下按鍵;3:按著按鍵正轉;-3:按著按鍵反轉
//詳解:只掃描EC11旋轉編碼器有沒有動作,不關心是第幾次按下按鍵或長按或雙擊,回傳值直接作為形參傳給 [ void Encoder_EC11_Analyze(char EC11_Value); ] 函式使用
//*******************************************************************/
char Encoder_EC11_Scan();
//*******************************************************************/
//功能:對EC11旋轉編碼器的動作進行分析,并作出相應的動作處理代碼
//形參:無
//回傳:char AnalyzeResult = 0;目前無用,若在該函式里做了動作處理,則函式的回傳值無需理會
//詳解:對EC11旋轉編碼器的動作進行模式分析,是單擊還是雙擊還是長按松手還是一直按下,形參從 [ char Encoder_EC11_Scan(unsigned char Set_EC11_TYPE) ] 函式傳入,在本函式內修改需要的動作處理代碼
//*******************************************************************/
char Encoder_EC11_Analyze(char EC11_Value);
#endif
//---->>>>----函式使用示例----<<<<----//
/********
#include "config.h"
#include "delay.h"
#include "EncoderEC11.h"
int cnt = -1; //流水燈速查表偏移變數
unsigned char disp_tmp[] = {~0x01,~0x02,~0x04,~0x08,~0x10,~0x20,~0x40,~0x80}; //流水燈速查表
void Timer0Init(void) //1毫秒@22.1184MHz
{
AUXR &= 0x7F; //定時器時鐘12T模式
TMOD &= 0xF0; //設定定時器模式
TL0 = 0xCD; //設定定時初值
TH0 = 0xF8; //設定定時初值
TF0 = 0; //清除TF0標志
TR0 = 1; //定時器0開始計時
}
void main()
{
P1_QB_ALL();
P2_QB_ALL();
P3_QB_ALL();
delay_ms(50); //延時100毫秒等待所有MCU復位
Encoder_EC11_Init(1);
EA = 1;
ET0 = 1;
Timer0Init();
while(1)
{
}
}
//-------->>>>>>>>--------注意事項:EC11旋轉編碼器的掃描時間間隔控制在1~4ms之間,否則5ms及以上的掃描時間在快速旋轉時可能會誤判旋轉方向--------<<<<<<<<--------//
void T0_ISR() interrupt 1
{
static int tmp =0;
Encoder_EC11_Analyze(Encoder_EC11_Scan());
if(P33 == 0)
{
tmp ++;
if(tmp == 500)
{
tmp =0;
P27 = !P27;
}
}
}
********/
這里我買的編碼旋鈕不知道為什么無法把單片機準雙向模式的IO口拉低(拿示波器才發現),于是設定為開漏模式,也可以設定為高阻模式
- main.c
#include "EncoderEC11.h"
#include <stc8.h>
#include <stdio.h>
void Timer0Init(void); //1毫秒@24.000MHz
void UartInit(void); //115200bps@24.000MHz
void SeriPushSend(unsigned char *pStr);
//int dat;
void main(void)
{
// char str[5];
P0M0 = 0xFF;//設定為開漏模式
P0M1 = 0XFF;
UartInit(); //115200bps@24.000MHz
Timer0Init(); //1毫秒@24.000MHz
Encoder_EC11_Init(0);//0-一定位對應一脈沖;1-兩定位對應一脈沖
ET0 = 1;
EA = 1;
while(1)
{
// if(dat)
// {
// sprintf(str,"%d\r\n",dat);
// SeriPushSend(str);
// dat = 0;
// }
}
}
void timer0(void) interrupt 1
{
static int dat;
char str[5];
dat = Encoder_EC11_Analyze(Encoder_EC11_Scan());
if(dat)
{
sprintf(str,"%d\r\n",dat);
SeriPushSend(str);
// dat = 0;
}
}
void Timer0Init(void) //1毫秒@24.000MHz
{
AUXR |= 0x80; //定時器時鐘1T模式
TMOD &= 0xF0; //設定定時器模式
TL0 = 0x40; //設定定時初值
TH0 = 0xA2; //設定定時初值
TF0 = 0; //清除TF0標志
TR0 = 1; //定時器0開始計時
}
void UartInit(void) //115200bps@24.000MHz
{
SCON = 0x50; //8位資料,可變波特率
AUXR |= 0x40; //定時器1時鐘為Fosc,即1T
AUXR &= 0xFE; //串口1選擇定時器1為波特率發生器
TMOD &= 0x0F; //設定定時器1為16位自動重裝方式
TL1 = 0xCC; //設定定時初值
TH1 = 0xFF; //設定定時初值
ET1 = 0; //禁止定時器1中斷
TR1 = 1; //啟動定時器1
}
void SeriPushSend(unsigned char *pStr)
{
while(*pStr != '\0')//不是字串結尾就一直執行
{
SBUF = *pStr;//首先發送第“0”個字串
while(TI == 0);//等待TI由硬體置一
TI = 0;//TI置零
pStr++;//地址++
}
}
希望看完原作者文章的你,也能去注冊并點個贊,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/272560.html
標籤:其他
上一篇:keil RTE HAL庫 STM32CubeMX 串口收發
下一篇:STM32
