目錄
1、準備工作
1.首先我們需要準備32的最小系統板或者開發板。
2.準備一個LED燈(如果使用板子上的燈來實現則不需要,下面我是使用最小系統板上的LED燈來實現)。
3.若干杜邦線。
4.軟件方面的準備,我是直接使用開源PWM源碼進行修改。
2、思路分析
一、使用串口調試助手向單片機發送數據(這個數據可以是一個字符,也可以是字符串,根據個人需求),我們發送的數據被單片機接收到后,會被保存在數據緩沖區USART_RX_BUF這個函數中。
二、我們的數據是存在USART_RX_BUF函數中,只要我們對USART_RX_BUF函數中的數據進行判斷就可以讓它實現不同的功能,這個判斷可以按位操作,也可以使用數組的方式進行判斷。
三、主函數中寫入我們需要實現的功能函數,主要使用IF判斷語句,來進行判斷。
下面來看看實際操作。
3、實際操作
1)如果你也是使用開源的PWM模板的話,第一步就可以省略了,第一步主要做一些使能串口和定義串口,定時器等的工作,我這里我使用的是定時器3的通道2——PB5(部分重映射,因為最小系統板的LED燈是對應PC13口的,到時候看效果還要使用一根杜邦線把PB5和PC13連在一起。如果自己準備了LED的小伙伴也可以直接接自己的LED但是最好要接一個保護電阻,還有要與單片機共地哦)這些都是開源模板里面已經幫我們定義好的,我們直接使用就行。如果是想自己寫的小伙伴開源參考下面的代碼 GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定時器3時鐘 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外設和AFIO復用功能模塊時鐘 GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5 //設置該引腳為復用輸出功能,輸出TIM3 CH2的PWM脈沖波形 GPIOB.5 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; //TIM_CH2 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIO //初始化TIM3 TIM_TimeBaseStructure.TIM_Period = arr; //設置在下一個更新事件裝入活動的自動重裝載寄存器周期的值 TIM_TimeBaseStructure.TIM_Prescaler =psc; //設置用來作為TIMx時鐘頻率除數的預分頻值 TIM_TimeBaseStructure.TIM_ClockDivision = 0; //設置時鐘分割:TDTS = Tck_tim TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上計數模式 TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根據TIM_TimeBaseInitStruct中指定的參數初始化TIMx的時間基數單位 //初始化TIM3 Channel2 PWM模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //選擇定時器模式:TIM脈沖寬度調制模式2 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比較輸出使能 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //輸出極性:TIM輸出比較極性高 TIM_OC2Init(TIM3, &TIM_OCInitStructure); //根據T指定的參數初始化外設TIM3 OC2 TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3在CCR2上的預裝載寄存器 TIM_Cmd(TIM3, ENABLE); //使能TIM3 上面這串代碼就是使能了定時器3的通道2 ,和配置了相關的GPIO口。這就完成了第一步。
2)使能串口和配置串口,USART1_TX --GPIOA.9(發送);USART1_RX—GPIOA.10(接收),串口1的發生和接收分別對應著PA9和PA10,所以我們要使能和配置這兩個口,把PA9配置成輸出口,PA10配置成輸入口。然后還要使能中斷,其實在這個項目中,中斷不是必要的 ,但是最好也要搞一下。還要寫中斷服務函數,根據自己需要寫,我這里我只是把它用作了判斷數據是否接收成功。如果對應串口這個不是很了解的,也可以看我上一篇文章,是介紹串口和串口中斷的。分析到這些就OK了,下面上代碼。
- <font face="-apple-system, SF UI Text, Arial, PingFang SC, Hiragino Sans GB, Microsoft YaHei, WenQuanYi Micro Hei, sans-serif"><font color="#4d4d4d"> GPIO_InitTypeDef GPIO_InitStructure;
- USART_InitTypeDef USART_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
-
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA時鐘
-
- //USART1_TX GPIOA.9
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //復用推挽輸出
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
-
- //USART1_RX GPIOA.10初始化
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空輸入
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
-
- //Usart1 NVIC 配置
- NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//搶占優先級3
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子優先級3
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
- NVIC_Init(&NVIC_InitStructure); //根據指定的參數初始化VIC寄存器
-
- //USART 初始化設置
- USART_InitStructure.USART_BaudRate = bound;//串口波特率
- USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字長為8位數據格式
- USART_InitStructure.USART_StopBits = USART_StopBits_1;//一個停止位
- USART_InitStructure.USART_Parity = USART_Parity_No;//無奇偶校驗位
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//無硬件數據流控制
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收發模式
- USART_Init(USART1, &USART_InitStructure); //初始化串口1
- USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//開啟串口接受中斷
- USART_Cmd(USART1, ENABLE); //使能串口1 </font></font>
復制代碼 上面這些是串口的基本配置,下面是中斷服務函數
- void USART1_IRQHandler(void) //串口1中斷服務程序
- {
- u8 Res;
- #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真,則需要支持OS.
- OSIntEnter();
- #endif
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的數據必須是0x0d 0x0a結尾)
- {
- Res =USART_ReceiveData(USART1); //讀取接收到的數據
-
- if((USART_RX_STA&0x8000)==0)//接收未完成
- {
- if(USART_RX_STA&0x4000)//接收到了0x0d
- {
- if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始
- else USART_RX_STA|=0x8000; //接收完成了
- }
- else //還沒收到0X0D
- {
- if(Res==0x0d)USART_RX_STA|=0x4000;
- else
- {
- USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
- USART_RX_STA++;
- if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數據錯誤,重新開始接收
- }
- }
- }
- }
- #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS為真,則需要支持OS.
- OSIntExit();
- #endif
- }
復制代碼 如果想要主函數中比較簡潔的話,也可以把判斷的代碼放到中斷服務函數里面來,每次我們從串口發送一個數據過來,如果你寫了中斷的話,它都會進行中斷服務函數中的。
3)這個也是最重要的一步,前兩步在源碼中都有的,只要你根據你需要改就行。這步我們說如何控制LED的閃爍或者是呼吸的效果。我使用的是一個位一個位的判斷,這樣子比較的燒芯片,但是我當時想到的是這個辦法,后面我又知道可以使用數組進行判斷,這個數組函數是C語言中的,感興趣的小伙伴可以去查查,我這里主要講燒芯片的辦法,首先我先判斷串口調試助手發送進來的是不是“huxi”這個數據,如果是我就會令一個變量,這里是t,t=1,這樣后面我們就可以直接判斷t是否等于1來判斷要不要實現呼吸這個效果了,后面需要清除接收標記 USART_RX_STA=0;這樣之后串口才能重新接收數據。
- if(USART_RX_BUF[0]=='h'&&USART_RX_BUF[1]=='u'&&USART_RX_BUF[2]=='x'
- &&USART_RX_BUF[3]=='i')
- {
-
- t=1;
- USART_RX_STA=0;
- // printf("t2.txt=\"呼吸\"\xff\xff\xff");
- }
復制代碼- if(t==1)
- {
- delay_ms(10);//去抖動
- if(dir)led0pwmval++;
- else led0pwmval--;
- if(led0pwmval>200)dir=0;
- if(led0pwmval==0)dir=1;
- TIM_SetCompare2(TIM3,led0pwmval);
-
- }
復制代碼 上面這兩個代碼就是實現呼吸燈效果的,閃爍效果的做法跟呼吸燈是一樣的,也是先進行判斷,然后調用判斷結果,我這里是判斷接收是否等于“shanshuo”這個數據,如果等于t=0,后面調用t這個變量就可以了,話不多說,上代碼。- else if(USART_RX_BUF[0]=='s'&&USART_RX_BUF[1]=='h'&&USART_RX_BUF[2]=='a'
- &&USART_RX_BUF[3]=='n'&&USART_RX_BUF[4]=='s'&&USART_RX_BUF[5]=='h'&&USART_RX_BUF[6]=='u'&&USART_RX_BUF[7]=='o')
- {
-
- t=0;
- USART_RX_STA=0;
-
- }
復制代碼- if(t==0)
- {
- TIM_SetCompare2(TIM3,0);
- delay_ms(300);
- TIM_SetCompare2(TIM3,899);
- delay_ms(300);
-
- }
復制代碼 這樣使用兩次判斷就可以把這兩個功能都實現了。不過有一個小問題是,我們這樣子接收判斷是把原來存在數據緩沖區USART_RX_BUF中的數據給覆蓋掉的,如果前一個數據的長度比后一個要長,那就會覆蓋不完,最好還有加一個清除函數,這里介紹一種辦法使用運行庫函數memset():memset(str, 0, sizeof(str));這樣就可以把緩沖區的數據清除掉,當然還有其他辦法,但是我就想到這個,可能不好用。但是我們這個項目里面覆蓋完不完并不會影響結果,所以也可以用,不過在需要把數據打印到串口這樣的項目中,就很有必要把之前數據給清除掉,不然容易出錯。
為了代碼的完整,下面我把整個主函數的代碼給貼出來,給各位伙伴參考。
- int main(void)
- {
-
- u16 t;
- u16 led0pwmval=0;
- u8 dir=1;
- delay_init(); //延時函數初始化
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //設置NVIC中斷分組2:2位搶占優先級,2位響應優先級
- uart_init(115200); //串口初始化為115200
- LED_Init(); //LED端口初始化
- TIM3_PWM_Init(899,0); //不分頻。PWM頻率=72000000/900=80Khz
-
- while(1)
- {
- if(USART_RX_BUF[0]=='h'&&USART_RX_BUF[1]=='u'&&USART_RX_BUF[2]=='x'
- &&USART_RX_BUF[3]=='i')
- {
-
- t=1;
- USART_RX_STA=0;
- }
- else if(USART_RX_BUF[0]=='s'&&USART_RX_BUF[1]=='h'&&USART_RX_BUF[2]=='a'
- &&USART_RX_BUF[3]=='n'&&USART_RX_BUF[4]=='s'&&USART_RX_BUF[5]=='h'&&USART_RX_BUF[6]=='u'&&USART_RX_BUF[7]=='o')
- {
-
- t=0;
- USART_RX_STA=0;
-
- }
-
- if(t==1)
- {
- delay_ms(10);//去抖動
- if(dir)led0pwmval++;
- else led0pwmval--;
- if(led0pwmval>200)dir=0;
- if(led0pwmval==0)dir=1;
- TIM_SetCompare2(TIM3,led0pwmval);
-
- }
- if(t==0)
- {
- TIM_SetCompare2(TIM3,0);
- delay_ms(300);
- TIM_SetCompare2(TIM3,899);
- delay_ms(300);
-
- }
-
-
- }
- }
復制代碼
4、小結
1.在這個項目中要注意把PB5和PC13用杜邦線連到一樣哦,不然就看不到效果啦。
2.還有一個易錯點就是,在閃爍這個功能代碼中,很多人首先想到的肯定是讓那個GPIO口的電平置高或者置低來控制燈的閃爍,但是這樣子的話,你就不可以只用一個燈來實現呼吸和閃爍之間的轉換了,你需要使用兩個燈,一個呼吸一個閃爍,這樣子是比較麻煩的。但是也根據個人需要吧,如果想要只用一個燈實現兩個效果,就使用上面的方法,呼吸和閃爍都使用定時器3通道2來控制。這樣就可以達到轉換自如了。
3.就是數據覆蓋的問題,這個也是根據你要做的項目要解決吧,可以清除,也可以不用。
4.上面的辦法只是控制呼吸和閃爍的一種辦法,或許復雜了,希望有更加簡單辦法的大佬指導一下,我也是剛剛學習,如有不懂的,可以私信交流,分享到此,謝謝.
|