520了,用32做个简单的小程序

520到了,看着朋友圈里的花式秀恩爱,平常午餐最爱吃的泡面都变得不那么香了。于是!突发奇想,突然就来了更新的想法,今天用32来做一个非常简单的小程序:

stm32f103c8t6

因为基本只用到两个外设,程序容量也很小,所以用c8t6就刚刚好
在这里插入图片描述

无源蜂鸣器

这里要用的是无源蜂鸣器,其音调是可调的。

库函数

我们先声明要用到的引脚以及相应的函数:

#define BeeGpio	GPIO自选
#define Bee 	GPIO_Pin_自选	

void Bee_Init(void); //蜂鸣器初始化
void Bee_test(void); //蜂鸣器测试
void Play_Music(void);//播放音乐

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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)); 
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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);
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

为了给下文的演奏做铺垫,发出声响的原理现在要着重强调一下:
(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,
};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我在测试的时候发现如果严格按照音调对应频率的话,听起来反而与想象中的音乐差了不少(难道是蜂鸣器的事?)所以稍微改了一下频率。

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]); }	
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 因为在前文的乐谱中,记了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); 

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

.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);
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

OLED

.h

void OLED_Init(void);
void OLED_ON(void);
void OLED_OFF(void);
void OLED_Refresh_Gram(void);
void OLED_Clear(void);

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

.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); }
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77

显示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); 
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 第一个参数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,

};

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

这个软件还是很好用的,而且像素也可以自定,比如用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

  1. ↩︎

文章来源: blog.csdn.net,作者:康.,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/k_ksy/article/details/106233645

(完)