謹(jǐn)以此文和我去年前的一篇藍(lán)橋杯單片機(jī)的教程構(gòu)成電子類的青銅雙壁.
國(guó)信長(zhǎng)天單片機(jī)競(jìng)賽訓(xùn)練之原理圖講解及常用外設(shè)原理(遺失的章節(jié)-零)_昊月光華的博客-CSDN博客
?
?
目錄
時(shí)鐘樹
串口重定向:printf輸出
動(dòng)態(tài)點(diǎn)燈(點(diǎn)燈大師)
按鍵(常用狀態(tài)機(jī))
同一時(shí)刻對(duì)多個(gè)按鍵按下進(jìn)行判斷
系統(tǒng)滴答定時(shí)器Systick(了解)
*ADC的DMA多通道采樣+平均濾波處理(多通道使用)
*串口的DMA+空閑中斷不定長(zhǎng)接受任意類型的數(shù)據(jù)(高效且簡(jiǎn)潔)
定時(shí)器通道的輸入捕獲(采集頻率和占空比)
運(yùn)行期間動(dòng)態(tài)更改PWM的頻率
PWM波的生成
IIC讀寫epprom
擴(kuò)展板之ds18b20溫度傳感器
擴(kuò)展板之dht11溫度傳感器
擴(kuò)展板之三位數(shù)碼管
擴(kuò)展板之ADC按鍵
擴(kuò)展板之三軸加速度傳感器
擴(kuò)展板之光敏電阻DO&AO
擴(kuò)展板之兩路AD采集
?擴(kuò)展板之頻率測(cè)量PUS1,PUS2,PWM1,PWM2
時(shí)鐘樹
串口重定向:printf輸出
注意:
- 勾選微庫(kù).若發(fā)現(xiàn)根本進(jìn)不了main函數(shù)里面一般是這個(gè)原因.
- 包含#include "stdio.h"
int fputc(int ch,FILE * f )
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1,0xff);
return ch;
}
動(dòng)態(tài)點(diǎn)燈(點(diǎn)燈大師)
u8 LEDINDEX[]= {0X00,1<<0,1<<1,1<<2,1<<3,1<<4,1<<5,1<<6,1<<7};
u8 LEDDT[]={0,0,0,0,0,0,0,0,0};
void led_display(u8 ds)
{
HAL_GPIO_WritePin(GPIOC,0xff<<8,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOC,ds<<8,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET);
}
void led_scan(){
int ds = 0;
for(int i = 0 ;i < 4;i++)
{
ds|=LEDINDEX[LEDDT[i]];
}
led_display(ds);
}
運(yùn)行期間只需要更改LEDDT的值就行,LEDDT沒(méi)有順序,表示最多同時(shí)點(diǎn)亮的燈的數(shù)目。
按鍵(常用狀態(tài)機(jī))
(狀態(tài)機(jī),假設(shè)同時(shí)只有一個(gè)按鍵按下,無(wú)論長(zhǎng)按還是短按都只判斷按下,記作一次)這個(gè)代碼只能一次性判斷一個(gè)按鍵按下,無(wú)法對(duì)同一時(shí)刻多個(gè)按鍵按下進(jìn)行判斷,一般這個(gè)代碼就夠用了)
使用定時(shí)器7,配置為20ms進(jìn)入一次回調(diào)函數(shù)。這種我認(rèn)為是日常中比較常用的按鍵操作,因?yàn)橐郧皩?duì)51單片機(jī)按鍵掃描的記憶,故回顧了下。
#define ReadB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define ReadB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define ReadB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define ReadB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
u8 key_state= 0;
u8 rkey = 0;
u8 key = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
LedFlusht++;
if(time++ == 1000)
{
time = 0;
LEDDT[1] = LEDDT[1]?0:1;
}
}
else if(htim->Instance == TIM7)
{
//執(zhí)行按鍵掃描
switch(key_state)
{
case 0:
if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
{
key_state = 1;
}
break;
case 1:
if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
{
key_state = 2;
if(!ReadB1) key = 1;
else if(!ReadB2) key=2;
else if(!ReadB3) key=3;
else if(!ReadB4) key =4;
rkey = key;
}
else
{
key_state = 0;
key = 0;
}
break;
case 2:
rkey = 0;//在只需要判斷按下之后,不管長(zhǎng)按短按(有沒(méi)有松開)都試做按下一次
if(!ReadB1 || !ReadB2 ||!ReadB3 || !ReadB4)
{
}
else
{
key = 0;
key_state= 0;
}
break;
}
keyAction();
}
}
同一時(shí)刻對(duì)多個(gè)按鍵按下進(jìn)行判斷
原理:把按鍵定義成一個(gè)結(jié)構(gòu)體變量:
typedef struct Key{
u16 keytime;
u8 keystate;
bool sflag;
bool lflag;
bool state;
}Key;
按鍵掃描:(按下小于500ms屬于短按,大于500ms屬于長(zhǎng)按) ,按鍵掃描函數(shù)單獨(dú)用一個(gè)定時(shí)器,每10ms產(chǎn)生一次中斷去執(zhí)行,這也就是為什么keytime+=10的原因.
#define ReadB1 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)
#define ReadB2 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)
#define ReadB3 HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_2)
#define ReadB4 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)
void key_scan()
{
keys[0].state = READB1;
keys[1].state = READB2;
keys[2].state = READB3;
keys[3].state = READB4;
for(int i = 0 ; i < 4 ;i++)
{
switch(keys[i].keystate)
{
case 0:
if( !keys[i].state){
keys[i].keystate =1;
}
break;
case 1:
if(!keys[i].state){
keys[i].keystate =2;
}
else
keys[i].keystate = 0;
break;
case 2:
if(!keys[i].state)
{
keys[i].keytime+=10;
}
else
{
if(keys[i].keytime > 500)
{
keys[i].lflag = 1;
}
else
keys[i].sflag = 1;
keys[i].keytime = 0;
keys[i].keystate=0;
}
break;
default:
break;
}
}
}
//串口測(cè)試代碼
void keywork(void)
{
if(keys[0].sflag)
{
printf("0\r\n");
keys[0].sflag = 0;
}
else if(keys[0].lflag)
{
printf("long 0\r\n");
keys[0].lflag = 0;
}
if(keys[1].sflag)
{
printf("1\r\n"); keys[1].sflag = 0;
}
else if(keys[1].lflag)
{
printf("long 1\r\n");
keys[1].lflag = 0;
}
if(keys[2].sflag)
{
printf("2\r\n");
keys[2].sflag = 0;
}
if(keys[3].sflag)
{
printf("3\r\n");
keys[3].sflag = 0;
}
}
邏輯處理: 處理完后sflag (短按標(biāo)志)或lflag(長(zhǎng)按標(biāo)志)記得清零處理。
系統(tǒng)滴答定時(shí)器Systick(了解)
HAL_GetTick() 函數(shù)是 STM32 微控制器 HAL(硬件抽象層)庫(kù)提供的函數(shù)。它返回當(dāng)前以毫秒為單位的節(jié)拍計(jì)數(shù)。默認(rèn)情況下,系統(tǒng)定時(shí)器每1ms生成一個(gè)中斷以更新節(jié)拍計(jì)數(shù)。因此,在大多數(shù)情況下,每個(gè)節(jié)拍的速率為1ms。
此函數(shù)可用于實(shí)現(xiàn)基于時(shí)間的操作,例如延遲、超時(shí)和任務(wù)調(diào)度。通過(guò)比較當(dāng)前節(jié)拍計(jì)數(shù)與之前保存的值,可以確定自那個(gè)事件以來(lái)經(jīng)過(guò)的時(shí)間。
例如,要?jiǎng)?chuàng)建100ms的延遲,您可以使用 HAL_GetTick() 保存當(dāng)前節(jié)拍計(jì)數(shù),然后在循環(huán)中等待,直到保存的節(jié)拍計(jì)數(shù)和當(dāng)前節(jié)拍計(jì)數(shù)之間的差達(dá)到100ms。
uint32_t startTick = HAL_GetTick(); while ((HAL_GetTick() - startTick) < 100);
請(qǐng)注意,當(dāng)節(jié)拍計(jì)數(shù)達(dá)到其最大值(0xFFFFFFFF)后,它會(huì)繞回,并且在計(jì)算長(zhǎng)時(shí)間內(nèi)經(jīng)過(guò)的時(shí)間時(shí)必須考慮到這一點(diǎn)。
我們使用DHT11,DS18B20需要用到的us級(jí)延時(shí)就需要通過(guò)systick來(lái)實(shí)現(xiàn)。
HAL_GetTick(); 獲取當(dāng)前節(jié)拍.默認(rèn)是1ms產(chǎn)生1個(gè)tick(節(jié)拍),則systick的計(jì)數(shù)值1ms發(fā)生溢出產(chǎn)生中斷,
systick的默認(rèn)中斷函數(shù):(每次中斷溢出則增加節(jié)拍 ”Tick“)
Systick的主頻:
SysTick定時(shí)器的主頻取決于所使用的系統(tǒng)時(shí)鐘(HCLK)頻率和它的分頻系數(shù)。在STM32微控制器中,SysTick定時(shí)器的時(shí)鐘源可以配置為HCLK/8或HCLK。
如果SysTick定時(shí)器的時(shí)鐘源被配置為HCLK/8,則SysTick定時(shí)器的主頻將為HCLK/8。例如,如果HCLK的頻率為72MHz,則SysTick定時(shí)器的主頻將為9 MHz。
如果SysTick定時(shí)器的時(shí)鐘源被配置為HCLK,則SysTick定時(shí)器的主頻將為HCLK。例如,如果HCLK的頻率為72MHz,則SysTick定時(shí)器的主頻將為72 MHz。
需要注意的是,SysTick的主頻越高,計(jì)數(shù)器溢出的時(shí)間間隔就越短,因此可以獲得更高的精度和分辨率。但是,SysTick的主頻和計(jì)數(shù)器位數(shù)的組合也會(huì)影響 SysTick 的最大定時(shí)周期。
systick的時(shí)鐘源一般被設(shè)置為AHB總線上的時(shí)鐘頻率,比如常見的80MHZ.
配置systick的時(shí)鐘頻率 HAL_SYSTICK_CLKSourceConfig()?
兩種選擇 HCLK 或 HCLK/8?
?溢出時(shí)間判斷:SYSTICK->LOAD的裝載值為79999,所以:
產(chǎn)生一次中斷的時(shí)間為:1/(80M-1)*(79999+1) =1/10^3 = 1ms
默認(rèn)的SYSTICK中斷函數(shù):
/** * @brief This function handles System tick timer. */ __weak void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ /* USER CODE END SysTick_IRQn 1 */ } /** * @brief This function is called to increment a global variable "uwTick" * used as application time base. * @note In the default implementation, this variable is incremented each 1ms * in SysTick ISR. * @note This function is declared as __weak to be overwritten in case of other * implementations in user file. * @retval None */ __weak void HAL_IncTick(void) { uwTick += uwTickFreq; }
?把SYSTICK做定時(shí)用,重寫systick的中斷函數(shù).(節(jié)省定時(shí)器,一般情況下,我們都不會(huì)這樣去做,因?yàn)镺S會(huì)把它作為時(shí)基,而我們的一些ms級(jí)延時(shí)也要得益改變systick的頻率得到)
此時(shí)需要把原來(lái)的SysTick_Handler標(biāo)記為weak。不好的地方在于每次生成代碼都需要改.
這一點(diǎn)純屬當(dāng)拓展知識(shí)去用,知道可以這么干就可以了.
void SysTick_Handler(void)
{
HAL_IncTick();
static uint32_t ick = 0; // 定義靜態(tài)變量tick,記錄自系統(tǒng)啟動(dòng)以來(lái)經(jīng)過(guò)的毫秒數(shù)
ick++; // 每次中斷觸發(fā)時(shí)tick值加1
if (ick == 1000) { // 如果tick值等于1000,則表示已經(jīng)過(guò)了1秒鐘
// TODO: 執(zhí)行每秒鐘需要執(zhí)行的任務(wù)
ick = 0; // 將tick值重置為0,開始新的計(jì)數(shù)
printf("hello world\r\n");
}
}
*ADC的DMA多通道采樣+平均濾波處理(多通道使用)
比賽實(shí)際上用單通道就可以,每次采樣需要指定adc的通道.
STM32基于hal庫(kù)的adc以DMA的多通道采樣以及所遇問(wèn)題解決_stm32 hal adc dma_昊月光華的博客-CSDN博客
*串口的DMA+空閑中斷不定長(zhǎng)接受任意類型的數(shù)據(jù)(高效且簡(jiǎn)潔)
(記得數(shù)據(jù)寬度設(shè)置為1個(gè)字節(jié) 8位)
若是hal庫(kù)的版本并不是特別的新,那么還是用串口接受中斷+空閑中斷吧,我這邊測(cè)試1.3的固件包是可以用DMA的那個(gè)空閑中斷函數(shù) ,如:
HAL_UARTEx_ReceiveToIdle_DMA(&huart1,Rx1Buf,200); //串口1開啟DMA接受
基于HAL庫(kù)的STM32的串口DMA發(fā)送數(shù)據(jù)(解決只發(fā)送一次數(shù)據(jù))及DMA+空閑中斷接受數(shù)據(jù)_hal 串口dma發(fā)送_昊月光華的博客-CSDN博客
定時(shí)器通道的輸入捕獲(采集頻率和占空比)
對(duì)輸入的脈沖進(jìn)行捕獲,算出求頻率和占空比.設(shè)置定時(shí)器的頻率的1M用來(lái)對(duì)輸入的脈沖進(jìn)行計(jì)數(shù),假設(shè)輸入脈沖一個(gè)周期的計(jì)數(shù)值為t(從一個(gè)脈沖的上升沿到下一個(gè)脈沖的上升沿).則頻率計(jì)算為? ?1000000 /? t? .
比如對(duì)PA15信號(hào)發(fā)生器.
?配置(PA15接脈沖發(fā)生器測(cè)出來(lái)的占空比大概為50%。
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
static u16 t = 0;
static u16 d = 0;
if(htim->Instance == TIM2)
{
if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
LEDDT[0]=1;
t = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_1)+1;
freq = 1000000 / t;
duty = (float)d/t*100;
}
else if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2)
{
LEDDT[1]=2;
d = HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_2)+1;
}
}
}
?通過(guò)按鍵打印
運(yùn)行期間動(dòng)態(tài)更改PWM的頻率
pwm的頻率 = 定時(shí)器的頻率/autoreload的值.其中定時(shí)器的頻率 = 時(shí)鐘頻率/分頻系數(shù).
之前參加過(guò)藍(lán)橋杯單片機(jī)的相信有過(guò)經(jīng)歷:用定時(shí)器去實(shí)習(xí)pwm波:
記得我們是如何實(shí)現(xiàn)的?
假設(shè)定時(shí)器的頻率是1M.每1us計(jì)數(shù)值加1,若設(shè)置為1000個(gè)計(jì)數(shù)值后溢出則產(chǎn)生一次中斷,則1ms進(jìn)入一次中斷,在中斷函數(shù)內(nèi),我們讓其定義一個(gè)變量,讓其加到200后歸0,其中前100ms設(shè)置為高電平,后100ms設(shè)置為低電平,則周期為200ms,頻率為1/200ms=5HZ,占空比為50%。(假設(shè)有效電平為高電平).
再回到STM32來(lái),這里的autoreload就是計(jì)數(shù)值,一個(gè)計(jì)數(shù)的時(shí)間是1/定時(shí)器的頻率。則PWM的頻率為1??/? ?[autoreload*1/定時(shí)器的頻率) ]? = 定時(shí)器的頻率/ autoreload .證明完畢.
我們通過(guò)更改autoreload的值去動(dòng)態(tài)的更改pwm的頻率,需要注意的是當(dāng)autoreload的值發(fā)生改變,需要重新去計(jì)算占空比。 占空比 = TIM15->CCR1 /autoreload .
//改變PWM的頻率且穩(wěn)定占空比保持不變 以TIM15->CCR1為例
void changePwmFreqAndkeepDuty(float duty,u16 autoloadval)
{
extern TIM_HandleTypeDef htim15;
TIM15->CCR1 = duty*autoloadval;
printf("new freq: %d\r\n",10000/autoloadval);
__HAL_TIM_SetAutoreload(&htim15,autoloadval-1);
HAL_TIM_GenerateEvent(&htim15, TIM_EVENTSOURCE_UPDATE);
}
通過(guò)按鍵觸發(fā)更改
按鍵觸發(fā)的demo
if(keys[0].sflag)
{
static u8 t =0;
t++;
static bool flag = false;
if( t == 100) t=0;
void changePwmFreqAndkeepDuty(float duty,u16 autoloadval);
flag =!flag;
if(flag) changePwmFreqAndkeepDuty(0.8,50);
else changePwmFreqAndkeepDuty(0.5,100);
printf("0\r\n");
keys[0].sflag = 0;
}
PWM波的生成
相信看到這,PWM的產(chǎn)生原理已經(jīng)很清楚了,用cubeMX配置將會(huì)更加清楚.
?一個(gè)定時(shí)器可以配置多個(gè)通道產(chǎn)生多組PWM波.
?開啟PWM波:
HAL_TIM_PWM_Start_IT(&htim15,TIM_CHANNEL_1);
更改PWM的值(如TIM15) TIM15->CCR1 =XXX (表示TIM15的通道1)
IIC讀寫epprom
需要注意在讀數(shù)據(jù)時(shí),需要主機(jī)發(fā)送應(yīng)答信號(hào)表示。以及需要初始化.
I2CInit(void);
//EEPROM讀寫代碼
void eeprom_write(unsigned char *pucBuf, unsigned char ucAddr, unsigned char Size)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(ucAddr);
I2CWaitAck();
while(Size--)
{
I2CSendByte(*pucBuf++);
I2CWaitAck();
}
I2CStop();
}
void eeprom_read(unsigned char *pucBuf, unsigned char ucAddr, unsigned char Size)
{
I2CStart();
I2CSendByte(0xa0);
I2CWaitAck();
I2CSendByte(ucAddr);
I2CWaitAck();
I2CStart();
I2CSendByte(0xa1);
I2CWaitAck();
while(ucNum--)
{
*pucBuf++ = I2CReceiveByte();
if(Size)
I2CSendAck();
else
I2CSendNotAck();
}
I2CStop();
}
擴(kuò)展板之ds18b20溫度傳感器
驅(qū)動(dòng)代碼比賽官方會(huì)給出.只需要自己寫一個(gè)讀取溫度的函數(shù)就行.給出的驅(qū)動(dòng)有個(gè)不穩(wěn)定的因素,那就是delay_us()這個(gè)延時(shí)函數(shù)沒(méi)有重寫。驅(qū)動(dòng)是以80M做的一個(gè)微秒級(jí)延時(shí),當(dāng)在更改系統(tǒng)時(shí)鐘頻率時(shí)則有大問(wèn)題(因?yàn)橹噶钪芷跁?huì)隨著系統(tǒng)的時(shí)鐘頻率而變化!),同樣的,DHT11的驅(qū)動(dòng)也有這個(gè)問(wèn)題.,這完全是讓學(xué)生專注于與邏輯業(yè)務(wù)的實(shí)現(xiàn),驅(qū)動(dòng)能跑就行(記得設(shè)置系統(tǒng)主頻為80M).
資源包給的延時(shí)函數(shù):
#define Delay_us(X) delay((X)*80/5)
void delay(unsigned int n)
{
while(n--);
}
跳線帽連接 P4 與 P3 的 TDQ進(jìn)行連接.
main函數(shù)中調(diào)用初始化:
ds18b20_init_x();
讀取溫度
float ds18b20_read(void)
{
unsigned char th,tl;
unsigned short res;
ow_reset();
ow_byte_wr(0xcc);
ow_byte_wr(0x44);
ow_reset();
ow_byte_wr(0xcc);
ow_byte_wr(0xbe);
tl = ow_byte_wr(0xff);
th = ow_byte_wr(0xff);
res=(th<<8)|tl;
return res*0.0625;
}
擴(kuò)展板之dht11溫度傳感器
每次讀取完后的下一次讀取則會(huì)失敗,這不是我們的問(wèn)題。只需要得到在它正確讀取時(shí)的值就行。
dht11比賽賽點(diǎn)資料包(14屆)給的驅(qū)動(dòng)代碼只給了2個(gè)函數(shù),兩個(gè)改變DQ輸入輸出的函數(shù)。。。
dht11_hal.h
#ifndef __DHT11_HAL_H
#define __DHT11_HAL_H
#include "stm32g4xx_hal.h"
#define HDQ GPIO_PIN_7
typedef struct {
float temp;
float humi;
}dht11Data;
void dht11Init(void);
int Readdht11(void);
#endif
dht11_hal.c
#include "dht11_hal.h"
#define OUTDQLOW HAL_GPIO_WritePin(GPIOA, HDQ, GPIO_PIN_RESET)
#define OUTDQHIGH HAL_GPIO_WritePin(GPIOA, HDQ, GPIO_PIN_SET)
#define READDQ HAL_GPIO_ReadPin(GPIOA,HDQ)
dht11Data dht11;
//
static void usDelay(uint32_t us)
{
uint16_t i = 0;
while(us--){
i = 30;
while(i--);
}
}
//
void outDQ(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = HDQ;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//
void inDQ(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.Pin = HDQ;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
}
//
void dht11Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE();
outDQ();
OUTDQHIGH;
}
//
uint8_t recData(void)
{
uint8_t i,temp=0,j=220;
for(i=0; i<8; i++){
while(!HAL_GPIO_ReadPin(GPIOA,HDQ));
usDelay(40);
if(HAL_GPIO_ReadPin(GPIOA,HDQ))
{
temp=(temp<<1)|1;
while(HAL_GPIO_ReadPin(GPIOA,HDQ)&&(j--));
}
else
{
temp=(temp<<1)|0;
}
}
return temp;
}
//復(fù)位DHT11
void DHT11_Rst(void)
{
outDQ(); //設(shè)置為輸出
OUTDQLOW;
HAL_Delay(20); //拉低至少18ms
OUTDQHIGH;
usDelay(60); //主機(jī)拉高20~40us
}
//等待DHT11的回應(yīng)
//返回1:未檢測(cè)到DHT11的存在
//返回0:存在
unsigned char DHT11_Check(void)
{
unsigned char re = 0;
inDQ(); //設(shè)置為輸入
while (READDQ && re < 100) //DHT11會(huì)拉低40~80us
{
re++;
usDelay(1);
};
if(re >= 100)return 1;
else re = 0;
while (!READDQ && re < 100) //DHT11拉低后會(huì)再次拉高40~80us
{
re++;
usDelay(1);
};
if(re >= 100)return 1;
return 0;
}
int Readdht11(void)
{
unsigned char buf[5];
unsigned char i;
DHT11_Rst();
if(DHT11_Check() == 0)
{
for(i = 0; i < 5; i++)
{
buf[i] = recData();
}
if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
dht11.humi = buf[0];
dht11.temp =buf[2];
}
}
else return 1;
return 0;
}
擴(kuò)展板之三位數(shù)碼管
使用三個(gè)引腳去驅(qū)動(dòng)三位數(shù)碼管,采用SPI通信的方式
- RCLK:? 存儲(chǔ)允許信號(hào)?低電平有效
RCLK是存儲(chǔ)允許信號(hào)(Register Clock signal)。它控制數(shù)碼管何時(shí)實(shí)際顯示存儲(chǔ)在內(nèi)部寄存器中的數(shù)據(jù)。當(dāng)RCLK信號(hào)拉低時(shí),數(shù)碼管會(huì)更新內(nèi)部寄存器,存儲(chǔ)要顯示的數(shù)據(jù)。當(dāng)RCLK信號(hào)拉高時(shí),數(shù)碼管會(huì)從內(nèi)部寄存器中讀取數(shù)據(jù),并實(shí)際顯示出來(lái)。
- SCK: 時(shí)鐘信號(hào)線?
在其下降沿時(shí)開始發(fā)送數(shù)據(jù)
- SER
SER是段選通信號(hào)(Segment Enable Register)。它控制數(shù)碼管的哪些段被選通發(fā)光。數(shù)碼管是由多個(gè)發(fā)光二極管構(gòu)成的,它包含a、b、c、d、e、f、g等7段,以及小數(shù)點(diǎn)段。要顯示某個(gè)數(shù)字,需要選通對(duì)應(yīng)數(shù)字的發(fā)光段。例如,要顯示數(shù)字“2”,需要選通a、b、d、e、g幾個(gè)段。
與數(shù)碼管的通信邏輯: 每次在SCK為低電平時(shí)傳輸數(shù)碼管的段選數(shù)據(jù)位,先傳ds3,再ds2,最后ds1.傳輸?shù)臄?shù)據(jù)由SN74LS595N進(jìn)行移位.
SN74LS595N芯片
????????SN74LS595N是一種8位移位寄存器,也稱為串行輸入并行輸出(SIPO)移位寄存器。它可以將接收的串行數(shù)據(jù)轉(zhuǎn)換成并行數(shù)據(jù)輸出。該芯片的主要特征有:- 8位移位寄存器,用于存儲(chǔ)輸入的串行數(shù)據(jù)并并行輸出- 三態(tài)輸出,可以直接驅(qū)動(dòng)LED或數(shù)碼管段選通- 簡(jiǎn)單的串行數(shù)據(jù)輸入和時(shí)鐘輸入- 高電平電源:5V該芯片常用于LED點(diǎn)陣的驅(qū)動(dòng)或數(shù)碼管的段選通驅(qū)動(dòng)。使用此芯片可以大大簡(jiǎn)化外圍電路,減少芯片數(shù)量。
?關(guān)于為什么需要從第三位段選數(shù)據(jù)開始傳輸?
1. SN74LS595N是一種移位寄存器,它會(huì)將輸入的串行數(shù)據(jù)依次存入8個(gè)寄存器位,并在RCLK信號(hào)的跳變沿將這8位數(shù)據(jù)并行輸出。2. 數(shù)碼管的段選通信號(hào)是并行的,依賴這8個(gè)輸出的數(shù)據(jù)選擇點(diǎn)亮相應(yīng)段。3. 所以,第一組輸入的8位數(shù)據(jù)會(huì)存入移位寄存器的最末8位,并第一個(gè)輸出驅(qū)動(dòng)第一個(gè)數(shù)碼管。4. 然后第二組8位數(shù)據(jù)輸入會(huì)將第一組數(shù)據(jù)移到更高8位,自己存入最末8位,并輸出驅(qū)動(dòng)第二個(gè)數(shù)碼管。5. 以此類推,每輸入一組8位數(shù)據(jù),之前的輸出數(shù)據(jù)會(huì)移位8位,新的8位數(shù)據(jù)輸出驅(qū)動(dòng)新的數(shù)碼管。(seg信號(hào)->DH#1->DH#2)所以從第三位數(shù)碼管的段選信號(hào)開始)
數(shù)碼管的驅(qū)動(dòng)以及定時(shí)動(dòng)態(tài)掃描,只需要更改smgDT[ ] 數(shù)組內(nèi)的值完成數(shù)碼管的顯示。?
#include "seg.h"
//
uc8 Seg7[17] = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71, 0x00};
u8 SmgDT[] ={ 16,16,16};
void SEG_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct ;
__HAL_RCC_GPIOA_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
//
void SEG_DisplayValue(u8 Bit1, u8 Bit2, u8 Bit3)
{
u8 i = 0;
u8 code_tmp = 0;
RCLK_L;
code_tmp = Seg7[Bit3];
for(i = 0; i < 8; i++)
{
SCK_L;
if(code_tmp & 0x80)
{
SER_H;
}
else
{
SER_L;
}
code_tmp = code_tmp << 1;
SCK_L;
SCK_H;
}
code_tmp = Seg7[Bit2];
for(i = 0; i < 8; i++)
{
SCK_L;
if(code_tmp & 0x80)
{
SER_H;
}
else
{
SER_L;
}
code_tmp = code_tmp << 1;
SCK_L;
SCK_H;
}
code_tmp = Seg7[Bit1];
for(i = 0; i < 8; i++)
{
SCK_L;
if(code_tmp & 0x80)
{
SER_H;
}
else
{
SER_L;
}
code_tmp = code_tmp << 1;
SCK_L;
SCK_H;
}
RCLK_L;
RCLK_H;
}
void ScanDisplay(void)
{
extern u8 smgt;
if(smgt > 153 ) SEG_DisplayValue(SmgDT[0],SmgDT[1],SmgDT[2]),smgt = 0;
}
擴(kuò)展板之ADC按鍵
原理:通過(guò)讀取adc的值(0-4096之間)來(lái)映射按鍵1到按鍵8。
?狀態(tài)機(jī)判斷adc按鍵單擊操作
u8 getadcKey()
{
// 這里的值完全可以試出來(lái),我也記不住
if(madc2 < 10) return 1;
if(madc2 < 800) return 2;
if(madc2 < 1600) return 3;
if(madc2 < 2000) return 4;
if(madc2 < 2700) return 5;
if(madc2 < 3300) return 6;
if(madc2 < 3700) return 7;
if(madc2 < 4000) return 8;
return 0;
}
u8 readadckey()
{
u8 static keystate =0;
u8 temp = 0;
u8 val = 0;
switch(keystate)
{
case 0:
temp =getadcKey();
if(temp)keystate = 1;
break;
case 1:
temp =getadcKey();
if(temp)keystate = 2,val = temp;
else keystate =0;
break;
case 2:
temp =getadcKey();
if(!temp)keystate =0;
break;
default:
break;
}
return val;
}
擴(kuò)展板之三軸加速度傳感器
采用IIC通信協(xié)議. 注意,之前用epprom用的也是IIC,三軸加速度傳感器的IIC的兩根線:SCL,SDA是PA4,PA5.
mems.c
#include "i2c.h"
#include "mems.h"
s8 alz[3] ;
//
void LIS302DL_Write(unsigned char reg, unsigned char info)
{
I2CStart();
I2CSendByte(0x38);
I2CWaitAck();
I2CSendByte(reg);
I2CWaitAck();
I2CSendByte(info);
I2CWaitAck();
I2CStop();
}
//
uint8_t LIS302DL_Read(uint8_t address)
{
unsigned char val;
I2CStart();
I2CSendByte(0x38);
I2CWaitAck();
I2CSendByte(address);
I2CWaitAck();
I2CStart();
I2CSendByte(0x39);
I2CWaitAck();
val = I2CReceiveByte();
I2CSendNotAck();
I2CStop();
return(val);
}
//
s8 *Lis302DL_Output(void)
{
if((LIS302DL_Read(0x27) & 0x08) != 0)
{
alz[0] = (LIS302DL_Read(0x29)); //x
alz[1] = (LIS302DL_Read(0x2B)); //y
alz[2] = (LIS302DL_Read(0x2D)); //z
}
return alz;
}
//
void LIS302DL_Config(void)
{
//Power up (100Hz data rate)
LIS302DL_Write(CTRL_REG1, 0x47);
//HP filter bypassed
// LIS302DL_Write(CTRL_REG2, 0x00);
// //FF_WU 1 interrupt send to INT Pad1(open drain) active low
// LIS302DL_Write(CTRL_REG3, 0xC1);
// //threshold :0.5 g
// LIS302DL_Write(FF_WU_THS_1, 0x28);
// //filter :200ms @100Hz
// LIS302DL_Write(FF_WU_DURATION_1, 40);
// //ZLIE event
// LIS302DL_Write(FF_WU_CFG_1, 0x10);
}
//
uint8_t LIS302DL_Check(void)
{
if(LIS302DL_Read(0x0f))
{
return 1;
}
else
{
return 0;
}
}
mems.h
#ifndef __MEMS_H
#define __MEMS_H
#include "main.h"
#define CTRL_REG1 0x20
#define CTRL_REG2 0x21
#define CTRL_REG3 0x22
#define FF_WU_THS_1 0x32
#define FF_WU_DURATION_1 0x33
#define FF_WU_CFG_1 0x30
#define STATUS_REG 0x27
uint8_t LIS302DL_Check(void);
void LIS302DL_Config(void);
s8 *Lis302DL_Output(void);
#endif
擴(kuò)展板之光敏電阻DO&AO
?原理: 把RP7(電位器)處得到的電壓值與3處的電壓值進(jìn)行比較器輸出得到DO.AO則直接進(jìn)行adc采樣再數(shù)模轉(zhuǎn)換得到電壓。
DO:讀取數(shù)字量,非常簡(jiǎn)單,直接讀取PA3的引腳電平得出。通過(guò)轉(zhuǎn)到電位器R7判斷是否讀取正確。
if(!HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3) )
{
LCD_DisplayStringLine(Line7, (u8 *)" DO:High ");
}
else
{
LCD_DisplayStringLine(Line7, (u8 *)" DO:Low ");
}
AO:讀取模擬量,本質(zhì)就是讀取adc的值.
擴(kuò)展板之兩路AD采集
這里用到的是同一個(gè)adc的兩個(gè)通道,完全可以用我上面的adc的dma的多通道采集實(shí)現(xiàn).擴(kuò)展板給的例程代碼相當(dāng)于每次都初始化一遍adc,然后再單通道采集。這種方式好處在于易于理解,而用adc的dma多通道則方便多了。
給出非DMA的單通道采集,當(dāng)多通道使用則每次更改adc的配置參數(shù)并重新初始化adc.
u16 getadc(u32 ch){
ADC_ChannelConfTypeDef _adc = { 0 } ;
_adc.Channel=ch;
_adc.Rank=1;
_adc.Rank = ADC_REGULAR_RANK_1;
_adc.SamplingTime=ADC_SAMPLETIME_640CYCLES_5;
_adc.SingleDiff = ADC_SINGLE_ENDED;
_adc.OffsetNumber = ADC_OFFSET_NONE;
HAL_ADC_ConfigChannel(&hadc2,&_adc);
HAL_ADC_Start(&hadc2);
return (u16)HAL_ADC_GetValue(&hadc2);
}
?擴(kuò)展板之頻率測(cè)量PUS1,PUS2,PWM1,PWM2
頻率測(cè)量與上面定時(shí)器通道的輸入捕獲同理,可獲取脈沖的頻率和占空比.
需要注意的是多路輸入捕獲測(cè)量頻率和多路捕獲PWM的頻率和占空比
基于HAL庫(kù)的STM32單定時(shí)器多路輸入捕獲測(cè)量PWM的頻率和占空比實(shí)現(xiàn)(狀態(tài)機(jī)方式實(shí)現(xiàn))_昊月光華的博客-CSDN博客文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-467290.html
基于HAL庫(kù)的STM32的單定時(shí)器的多路輸入捕獲測(cè)量脈沖頻率(外部時(shí)鐘實(shí)現(xiàn))_昊月光華的博客-CSDN博客文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-467290.html
到了這里,關(guān)于藍(lán)橋杯嵌入式STM32G431RBT6競(jìng)賽指南與模板——最后的絕唱的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!