23 final關鍵字(完結器)
520到了,看著朋友圈裡的花式秀恩愛,平常午餐最愛吃的泡麵都變得不那麼香了。於是!突發奇想,突然就來了更新的想法,今天用32來做一個非常簡單的小程式:
簡單放個歌,再放個圖
stm32f103c8t6
因為基本只用到兩個外設,程式容量也很小,所以用c8t6就剛剛好
無源蜂鳴器
這裡要用的是無源蜂鳴器
庫函式
我們先宣告要用到的引腳以及相應的函式:
#define BeeGpio GPIO自選
#define Bee GPIO_Pin_自選
void Bee_Init(void); //蜂鳴器初始化
void Bee_test(void); //蜂鳴器測試
void Play_Music(void);//播放音樂
void Bee_Init(void)
這個也非常好理解,和初始化引腳是一樣的 。
void Bee_Init(void){
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = Bee;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推輓輸出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(BeeGpio, &GPIO_InitStructure);
GPIO_WriteBit(BeeGpio,Bee,(BitAction)(1));
}
void Bee_test(void)
在主函式演奏之前,我們先測試一下蜂鳴器好不好使,讓它先響一聲:
void Bee_test(void){
u16 i;
for (i=0;i<200;i++){
GPIO_WriteBit(BeeGpio,Bee,(BitAction)(0));
delay_us(500);
GPIO_WriteBit(BeeGpio,Bee,(BitAction)(1));
delay_us(500);
}
}
為了給下文的演奏做鋪墊,發出聲響的原理現在要著重強調一下:
(delay函式是已經寫好的,有us、ms、s等等單位,這裡用的是us)
- 在這個for迴圈裡,先後兩次的delay_us(500)加在一起構成了一個週期,這個週期的時間長是1000us,也就是1ms。在這1ms的時間裡,一半的時間蜂鳴器不響,另一半的時間響,如此重複200次,就成為了我們人類耳朵聽到的一個時間約為200ms的響聲。
樂譜(簡譜)
以一個非常簡單的粉刷匠為例:(希望我沒有記錯譜子哈哈哈)
- C調中音12345對應的聲音訊率分別是:523、587、659、698、784Hz。所以我們就可以把簡譜中的數字依次替換(C調其他音對應頻率見文末註腳1)
- 每個音都是要持續一定時間的,以ms為單位,比如“2432”的聲音要保持一致,而“5-”要持續略長的時間
以“2432|5-”為例,我們把音調與對應的時間 兩兩一組,放到一個數組裡:
uc16 m_24325[10]={//奇數項為頻率,偶數項為持續時間(ms)
587,300,
698,300,
659,300,
587,300,
784,750,
};
我在測試的時候發現如果嚴格按照音調對應頻率的話,聽起來反而與想象中的音樂差了不少(難道是蜂鳴器的事?)所以稍微改了一下頻率。
void Play_Music(void)
void Play_Music(void){
u16 i,j;
for(i=0;i<5;i++){
for(j=0;j<m_24325[i*2]*m_24325[i*2+1]/1000;j++){
GPIO_WriteBit(BeeGpio,Bee,(BitAction)(0));
delay_us(500000/m_24325[i*2]);
GPIO_WriteBit(BeeGpio,Bee,(BitAction)(1));
delay_us(500000/m_24325[i*2]);
}
}
}
- 因為在前文的樂譜中,記了10個數據,5對音調與時間,所以令i=0;i<5
- 在第二個for迴圈中,先後兩次delay_us(500000/music1[i*2]),使得週期變為1000 000/頻率
- 而 j<m_24325[i*2]m_24325[i2+1]/1000 和 週期共同決定了蜂鳴器發出這個頻率對應音調的時間
演算一下:以“523Hz”響750ms為例:
如此,我們便能演奏一些基本的曲子了,只需要自己寫一個樂譜就好了。
void Play_Music(void)也可以寫為有輸入引數的函式,這樣便於我們用同一個函式呼叫不同的樂譜。
接下來就到了另一個模組:
OLED模組(7腳64*128)
買到OLED模組以後,商家往往都會附贈配套程式的,不過往往都會贈IIC的程式。這裡把我以前用的SPI程式放上。
模擬SPI
.h
#define OLED_CMD 0
#define OLED_DATA 1
#define OLED_CLK PAout(4)
#define OLED_MOSI PAout(3)
#define OLED_RST PAout(2)
#define OLED_DC PAout(1)
void OLED_SPI_Init(void);
void SPI_WriteByte(uint8_t addr,uint8_t data);
void WriteCmd(unsigned char cmd);
void WriteData(unsigned char data);
.c
void OLED_SPI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA ,ENABLE);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
void SPI_WriteByte(unsigned char data,unsigned char cmd)
{
unsigned char i=0;
OLED_DC =cmd;
OLED_CLK=0;
for(i=0;i<8;i++)
{
OLED_CLK=0;
if(data&0x80)OLED_MOSI=1;
else OLED_MOSI=0;
OLED_CLK=1;
data<<=1;
}
OLED_CLK=1;
OLED_DC=1;
}
void WriteCmd(unsigned char cmd)
{
SPI_WriteByte(cmd,OLED_CMD);
}
void WriteData(unsigned char data)
{
SPI_WriteByte(data,OLED_DATA);
}
OLED
.h
void OLED_Init(void);
void OLED_ON(void);
void OLED_OFF(void);
void OLED_Refresh_Gram(void);
void OLED_Clear(void);
.c
u8 OLED_GRAM[128][8];
void OLED_DLY_ms(unsigned int ms)
{
unsigned int a;
while(ms)
{
a=1335;
while(a--);
ms--;
}
}
void OLED_Init(void)
{
OLED_SPI_Init();
OLED_CLK = 1;
OLED_RST = 0;
OLED_DLY_ms(100);
OLED_RST = 1;
WriteCmd(0xae);
WriteCmd(0x00);
WriteCmd(0x10);
WriteCmd(0xd5);
WriteCmd(0x80);
WriteCmd(0xa8);
WriteCmd(0x3f);
WriteCmd(0xd3);
WriteCmd(0x00);
WriteCmd(0xB0);
WriteCmd(0x40);
WriteCmd(0x8d);
WriteCmd(0x14);
WriteCmd(0xa1);
WriteCmd(0xc8);
WriteCmd(0xda);
WriteCmd(0x12);
WriteCmd(0x81);
WriteCmd(0xff);
WriteCmd(0xd9);
WriteCmd(0xf1);
WriteCmd(0xdb);
WriteCmd(0x30);
WriteCmd(0x20);
WriteCmd(0x00);
WriteCmd(0xa4);
WriteCmd(0xa6);
WriteCmd(0xaf);
OLED_Clear();
}
void OLED_Refresh_Gram(void)
{
u8 i,n;
for(i=0;i<8;i++)
{
WriteCmd(0xb0+i);
WriteCmd(0x00);
WriteCmd(0x10);
for(n=0;n<128;n++)WriteData(OLED_GRAM[n][i]);
}
}
void OLED_Clear(void)
{
u8 j,t;
for(t=0xB0;t<0xB8;t++){
WriteCmd(t);
WriteCmd(0x10);
WriteCmd(0x00);
for(j=0;j<132;j++){
WriteData(0x11);
}
}
}
顯示16*16的字元
這個是仿照商家的IIC例程改成SPI的寫法,其實驅動OLED的方法都是一樣的,只不過IIC和SPI略有不同而已(3個輸入引數會在稍後講到)
void OLED_DISPLAY_16x16(u8 x,u8 y, u16 w){
u8 j,t,c=0;
y=y-14;
for(t=0;t<2;t++){
WriteCmd(0xb0+x);
WriteCmd(y/16+0x10);
WriteCmd(y%16);
for(j=0;j<16;j++){
WriteData(M_16[(w*32)+c]);
c++;}x++;
}
WriteCmd(0xAF);
}
- 第一個引數x:字元的行:0、2、4、6共4行(4*16=64,把64個畫素分為4行)
- 第二個引數y:字元的列:共128列(畫素),但是因為字元是16*16的,所以用n * 16代替,便於計算
- 第三個引數w:對應庫中的第幾個字元
- 庫:M_16(在倒數第5行),這個內容馬上就講到
比如:OLED_DISPLAY_16x16(4,8*16,8),在OLED螢幕第3行的第8列,顯示庫中的第9個字元
字元/圖片庫,取模
這個庫是需要咱們自己建立的,可以由取模軟體自動生成每個字元對應的16進位制資料。
我們用到的取模軟體是:PCtoLCD2002
配置如圖:
用它生成資料以後就可以把資料放到一個單獨的.h檔案中,作為我們自己的字元庫。這裡以兩個16*16的空白為例
uc8 M_16[] = {
//" "
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
//" "
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
這個軟體還是很好用的,而且畫素也可以自定,比如用64 * 128的圖片鋪滿oled等等。詳見生日快樂(b站投稿)。這裡用的就是杜洋老師的開發板,我最初學32的時候就是學習杜洋老師的教程,雖然後來我又學了野火的32,正點原子的linux…(我很專一的/doge)
跑題了,這裡只是舉了一個16 * 16字元的例子,還有8 * 16字元、字串、64 * 128圖片等等,就請各位自己研究了/doge
我是康,希望做一名能幫助到各位的博主! 我不是本來要更機器學習的嘛? 在做了在做了(0%)預計下週會發布,歡迎感興趣的小夥伴與我共同學習,一起進步!
C調低音 | 頻率(Hz) | C調中音 | 頻率(Hz) | C調高音 | 頻率(Hz) |
---|---|---|---|---|---|
1 | 262 | 1 | 523 | 1 | 1046 |
1# | 277 | 1# | 554 | 1# | 1109 |
2 | 294 | 2 | 587 | 2 | 1175 |
2# | 311 | 2# | 622 | 2# | 1245 |
3 | 330 | 3 | 659 | 3 | 1318 |
4 | 349 | 4 | 698 | 4 | 1397 |
4# | 370 | 4# | 740 | 4# | 1480 |
5 | 392 | 5 | 784 | 5 | 1568 |
5# | 415 | 5# | 831 | 5# | 1661 |
6 | 440 | 6 | 880 | 6 | 1760 |
6# | 466 | 6# | 932 | 6# | 1865 |
7 | 494 | 7 | 988 | 7 | 1976 |