27.3.4.2內(nèi)核下的I2C驅(qū)動(二)
1403
2025-04-01
一、multi_timer簡介
網(wǎng)紅multi_timer是一個極其輕量的軟件定時器,只要你的MCU容量夠的情況下,就可以無限拓展成為N個定時器,這在一定程度上方便了定期器資源較少的MCU,但有經(jīng)驗的老工程師會說:"我可以只用一個定時器,用計數(shù)器+標(biāo)志位的方式也可以COPY出N個定時器呀,資源少也阻擋不了我對它的充分利用"。是的沒錯,但multi_timer對比老工程師方法的優(yōu)勢在哪里呢?它可以取代傳統(tǒng)的標(biāo)志位+計數(shù)器的判斷方式,讓程序看起來更加優(yōu)雅更加好維護。
特點:簡單、優(yōu)雅、便捷、易維護
二、multi_timer的使用方法
1、定義一個multi_timer結(jié)構(gòu)體變量
Timer timer1 ;
2、注冊并初始化multi_timer定時器
timer_init(&timer1, timer1_callback, TIMER_TIMEOUT_500MS, TIMER_TIMEOUT_500MS);
3、啟動multi_timer定時器
timer_start(&timer1);
4、設(shè)置1ms硬件定時器循環(huán)調(diào)用計數(shù)器以提供時基
void xxx_callback(void)
{
timer_ticks();
}
5、在while循環(huán)中循環(huán)調(diào)用multi_timer的后臺處理函數(shù)
while(1)
{
//....
timer_loop();
}
三、multi_timer實戰(zhàn)
實戰(zhàn)演練1:在STC15F104W-35I-SOP8上實踐
買這個小模塊的原因是后面需要做一些開源項目,想做一些傳感器,最后用stm32或者其它的MCU與它建立通信用,還有一個用途就是以后移植一些開源項目,我希望現(xiàn)在低端一點的平臺上驗證(如果低端跑不了就直接上能跑的),后面再在高端點的平臺上實踐,有時間有條件我也會多在別的平臺上跑跑,這樣相當(dāng)于積累了多個平臺的開發(fā)經(jīng)驗,這款CPU完全兼容51單片機的指令集,所以把它當(dāng)成51單片機來用就行了。
這個小板子對應(yīng)的原理圖如下:
限于文章篇幅,如果想多了解這個小板子的信息,可以去我的CSDN博客上看看,之前寫了介紹:
https://blog.csdn.net/morixinguan/article/details/105130462
下面直接看實戰(zhàn)需求功能描述:
1、用multi_timer創(chuàng)建軟件定時器1,用來以500ms的頻率讓LED燈交替閃爍。
2、用multi_timer創(chuàng)建軟件定時器2,當(dāng)定時10s到達以后,常亮LED,并且刪除multi_timer創(chuàng)建的軟件定時器1和軟件定時器2。
創(chuàng)建51的Keil4工程,然后開始編寫代碼:
1、打開Keil4,然后創(chuàng)建一個AT89C51的工程
2、將multi_timer添加到keil4工程
3、創(chuàng)建一個Package目錄,將multi_timer的程序文件添加進來
4、編寫代碼
#include
#include "multi_timer.h"
Timer timer1 ;
Timer timer2 ;
/*用于定時10s的計數(shù)器*/
int Counter = 0 ;
/*根據(jù)板子原理圖,燈位于P3^3*/
sbit LED = P3 ^ 3 ;
/*晶振頻率為12M*/
#define FOSC 12000000L
/*指令速度為12T*/
#define command_speed 12
/*用multi_timer創(chuàng)建的定時器1定時時間 ?單位:ms*/
#define TIMER_TIMEOUT_500MS 500
/*用multi_timer創(chuàng)建的定時器2定時時間 ?單位:ms*/
#define TIMER_TIMEOUT_1S 1000
void timer0_init(void);
void timer1_callback(void);
void timer2_callback(void);
void main(void)
{
LED = 0;
timer0_init();
timer_init(&timer1, timer1_callback, TIMER_TIMEOUT_500MS, TIMER_TIMEOUT_500MS);
timer_init(&timer2, timer2_callback, TIMER_TIMEOUT_1S, TIMER_TIMEOUT_1S);
timer_start(&timer1);
timer_start(&timer2);
while(1)
{
timer_loop();
}
}
/*multi_timer回調(diào)函數(shù)1調(diào)用*/
void timer1_callback(void)
{
/*LED燈電平翻轉(zhuǎn)*/
LED = !LED ;
}
/*multi_timer回調(diào)函數(shù)2調(diào)用*/
void timer2_callback(void)
{
/*當(dāng)計數(shù)器到達10次以后刪除所有創(chuàng)建的軟件定時器
計數(shù)器清0,將LED電平置為1,常亮
*/
++Counter ;
if(Counter == 10)
{
Counter = 0 ;
LED = 1 ;
timer_stop(&timer1);
timer_stop(&timer2);
}
}
/*硬件定時器初始化*/
void timer0_init(void)
{
TMOD = 0x00;
TH0 = (65536 - FOSC / command_speed / 1000) >> 8;
TL0 = (65536 - FOSC / command_speed / 1000);
EA = 1;
ET0 = 1;
TR0 = 1;
}
/*利用系統(tǒng)定時器產(chǎn)生1ms的定時中斷*/
void timer0() interrupt 1
{
TH0 = (65536 - FOSC / command_speed / 1000) >> 8;
TL0 = (65536 - FOSC / command_speed / 1000);
/*multi_timer計數(shù)器自增*/
timer_ticks();
}
4、程序編譯與固件生成
我們看到編譯過后,整個程序的大小僅占用1.3K多,確實夠輕量!接下來將生成的.hex文件下載到開發(fā)板上。
最終程序按照我的設(shè)計思路完美運行!這里相當(dāng)于帶大家重新復(fù)習(xí)了下51單片機平臺的基本使用。
實戰(zhàn)演練2:在小熊派開發(fā)板上實戰(zhàn)
接下來我們在這個平臺上把實戰(zhàn)演練1的需求實現(xiàn)一下,首先先看小熊派開發(fā)板的原理圖,找到LED的位置:
使用stm32cubmx配置基礎(chǔ)工程:
1、芯片選型,這里選擇stm32l431rctx
2、配置rcc時鐘以及串行調(diào)試接口
這里我選擇的是高速,時鐘的話,直接用系統(tǒng)默認的內(nèi)部時鐘也可以,時鐘默認配置最高80MHz。
因為以前被坑過,導(dǎo)致程序沒法下載了,所以習(xí)慣性配置這個選項,后續(xù)有時間我寫篇文章解釋下。
3、配置LED
4、配置串口調(diào)試
方便根據(jù)調(diào)試信息查看程序執(zhí)行流程。
5、生成Keil5基礎(chǔ)工程
實際開發(fā)建議硬件外設(shè)分模塊,這樣看起來不要把所有的生成全部都擠到main.c里面去了,這點讓我非常討厭,所以生成工程時候習(xí)慣點擊設(shè)置以下這一項:
接下來點擊生成代碼:
1、將multi_timer添加到keil5工程
2、創(chuàng)建一個Package目錄,將multi_timer的程序文件添加進來
3、編寫代碼
由于篇幅限制,只看我自己代碼添加的位置:
main.h
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/*添加必要的頭文件*/
#include
#include "multi_timer.h"
/* USER CODE END Includes */
main.c
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/*用multi_timer創(chuàng)建的定時器1定時時間 ?單位:ms*/
#define TIMER_TIMEOUT_500MS 500
/*用multi_timer創(chuàng)建的定時器2定時時間 ?單位:ms*/
#define TIMER_TIMEOUT_1S 1000
/* USER CODE END PD */
/* USER CODE BEGIN PV */
Timer timer1 ;
Timer timer2 ;
/*用于定時10s的計數(shù)器*/
int Counter = 0 ;
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
/*定義重定向,這樣才能使用printf函數(shù)*/
int fputc(int ch, FILE *file)
{
return HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
}
/*multi_timer回調(diào)函數(shù)1調(diào)用*/
void timer1_callback(void)
{
/*LED燈電平翻轉(zhuǎn)*/
//LED = !LED ;
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
/*multi_timer回調(diào)函數(shù)2調(diào)用*/
void timer2_callback(void)
{
/*當(dāng)計數(shù)器到達10次以后刪除所有創(chuàng)建的軟件定時器
計數(shù)器清0,將LED電平置為1,常亮
*/
++Counter ;
if(Counter == 10)
{
Counter = 0 ;
//LED = 1 ;
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
printf("LED燈常亮\n");
timer_stop(&timer1);
printf("關(guān)閉定時器1\n");
timer_stop(&timer2);
printf("關(guān)閉定時器2\n");
}
}
/**
* @brief ?The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
timer_init(&timer1, timer1_callback, TIMER_TIMEOUT_500MS, TIMER_TIMEOUT_500MS);
timer_init(&timer2, timer2_callback, TIMER_TIMEOUT_1S, TIMER_TIMEOUT_1S);
timer_start(&timer1);
timer_start(&timer2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
timer_loop();
}
/* USER CODE END 3 */
}
stm32l4xx_it.c
這里利用系統(tǒng)時鐘的1ms的時基,就不用重新去創(chuàng)建一個硬件定時器了。
/**
* @brief This function handles System tick timer.
*/
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
timer_ticks();
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
4、程序編譯與固件生成
生成固件
選擇調(diào)試和下載器,這里是st-link
選擇下載程序后復(fù)位,如果不選擇則需要按開發(fā)板上的硬件復(fù)位。
編譯成功后直接點擊下載
5、程序執(zhí)行(看串口調(diào)試助手)
實驗成功!
四、multi_timer設(shè)計思想
所謂有道是知其然而知所以然,這么優(yōu)秀的作品,我們有必要來了解一下:
4.1 multi_timer的數(shù)據(jù)結(jié)構(gòu)及參數(shù)含義
typedef struct Timer
{
uint32_t timeout;
uint32_t repeat;
void (*timeout_cb)(void);
struct Timer* next;
} Timer;
參數(shù)含義:
4.2 multi_timer的函數(shù)解析
程序文件里的全局變量:
//timer handle list head.
static struct Timer* head_handle = NULL;
//Timer ticks
static uint32_t _timer_ticks = 0;
/**
* @brief ?background ticks, timer repeat invoking interval 1ms.
* @param ?None.
* @retval None.
*/
void timer_ticks()
{
_timer_ticks++;
}
這個函數(shù)的功能主要是產(chǎn)生計數(shù),而產(chǎn)生計數(shù)一定要有另外一個介質(zhì)去驅(qū)動它運行。
//Timer ticks
static uint32_t _timer_ticks = 0;
/**
* @brief ?Initializes the timer struct handle.
* @param ?handle: the timer handle strcut.
* @param ?timeout_cb: timeout callback.
* @param ?repeat: repeat interval time.
* @retval None
*/
void timer_init(struct Timer* handle, void(*timeout_cb)(), uint32_t timeout, uint32_t repeat)
{
handle->timeout_cb = timeout_cb;
handle->timeout = _timer_ticks + timeout;
handle->repeat = repeat;
}
初始化主要是給結(jié)構(gòu)體參數(shù)進行賦值操作,首先要確定是哪個結(jié)構(gòu)體成員,由handle參數(shù)確定,當(dāng)定時時間到了要做什么事情,由timeout_cb(定時器回調(diào)處理函數(shù))參數(shù)確定,定時時間多長才會觸發(fā)所謂的事情,由timeout(定時時間)參數(shù)確定,如果這個功能需要重復(fù)觸發(fā),我們就需要給repeat(循環(huán)定時觸發(fā)時間)參數(shù)()指定。
/**
* @brief ?Start the timer work, add the handle into work list.
* @param ?btn: target handle strcut.
* @retval 0: succeed. -1: already exist.
*/
int timer_start(struct Timer* handle)
{
struct Timer* target = head_handle;
while(target)
{
if(target == handle) return -1; //already exist.
target = target->next;
}
handle->next = head_handle;
head_handle = handle;
return 0;
}
這里將定時器句柄添加到鏈表里進行保存,循環(huán)指向鏈表的下一個節(jié)點去添加定時器節(jié)點,如果發(fā)現(xiàn)是同一個定時器句柄,則直接返回-1,表示當(dāng)前添加句柄不合法。
/**
* @brief ?Stop the timer work, remove the handle off work list.
* @param ?handle: target handle strcut.
* @retval None
*/
void timer_stop(struct Timer* handle)
{
struct Timer** curr;
for(curr = &head_handle; *curr; )
{
struct Timer* entry = *curr;
if (entry == handle)
{
*curr = entry->next;
}
else
curr = &entry->next;
}
}
這里非常巧妙的使用了一個二級指針curr,指向了對應(yīng)定時器句柄的地址,通過循環(huán)遍歷,找到對應(yīng)的句柄后將其刪除。
/**
* @brief ?main loop.
* @param ?None.
* @retval None
*/
void timer_loop()
{
struct Timer* target;
for(target = head_handle; target; target = target->next)
{
if(_timer_ticks >= target->timeout)
{
if(target->repeat == 0)
{
timer_stop(target);
}
else
{
target->timeout = _timer_ticks + target->repeat;
}
target->timeout_cb();
}
}
}
這個函數(shù)實現(xiàn)非常簡單,就是通過不斷遍歷鏈表各個節(jié)點,判斷是否到達定時時間(timeout參數(shù)),如果到達了定時時間,沒有指定循環(huán)定時觸發(fā)時間(repeat參數(shù))的時候,這時就會把當(dāng)前定時器句柄給移除,如果指定了循環(huán)定時觸發(fā)時間(repeat參數(shù)),則定時時間會被重新賦值,直到下一個定時到來,接下來會一直循環(huán)觸發(fā)。
實踐工程下載
鏈接:https://pan.baidu.com/s/1xwCnkMDnwjPTrKd8ulw58w
提取碼:eo5y
復(fù)制這段內(nèi)容后打開百度網(wǎng)盤手機App,操作更方便哦
注意:
大部分朋友的電腦都同時裝了Keil5和Keil4,不小心操作有時打開Keil4工程會卡死,解決方法如下:
將下面這個文件刪除,再重新打開就不會了。
軟件開發(fā) 小熊派 IoT
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。