STM32F103RB 实作笔记(九)- PWM + SPI +MAX6675 整合试验 (正点原子 STM32F103 nano开发板)程式解析

个人工作上的关系,需要做一款温度控制风扇速度的控制器,还需要能够看到温度和PWM的值。于是我用这个不熟悉的 STM32F103 试试,顺便把 PWM 和 SPI 也了解一番。
一开始当然也是跌跌撞撞,搞了很久才弄清楚。这篇笔记记录怎么做好的过程。
实验的过程是以正点原子 STM32F103 nano开发板和他附上的练习程式开始。

参考 NANO 的程式

依照正点原子的教材,跑了一下 nano 程式,执行了 OK!原来的程式里面 PWM 用 TIM3 的 channel_1 控制 PC6 的 LED 闪烁。SPI 是用 SPI2 控制 W25Q16 进行读写。

由于 PWM 和 SPI2 都被开发板占有,我选了另外一组 TIM2_CH2 和 SPI1 做我的控制件。作业如下:

PWM

从原来的程式,可以知道要调整那么多开关,以下是 nano 的 测试程式内容:

void TIM3_PWM_Init(u16 arr,u16 psc) 
{                   
  RCC->APB1ENR|=1<<1;      //TIM3 时钟使能   
  RCC->APB2ENR|=1<<4;          //使能 PORTC 时钟   
  GPIOC->CRL&=0XF0FFFFFF;//PC6 输出 
  GPIOC->CRL|=0X0B000000;    //复用功能输出         
  RCC->APB2ENR|=1<<0;          //使能 AFIO 时钟   
  AFIO->MAPR&=0XFFFFF3FF; //清除 MAPR 的[11:10] 
  AFIO->MAPR|=3<<10;              //部分重映像,TIM3_CH1->PC6 
  TIM3->ARR=arr;          //设定计数器自动重装值   
  TIM3->PSC=psc;          //预分频器不分频 
  TIM3->CCMR1|=7<<4;          //CH1 PWM2 模式       
  TIM3->CCMR1|=1<<3;        //CH1 预装载使能         
  TIM3->CCER|=1<<0;            //OC1  输出使能        
  TIM3->CR1=0x0080;            //ARPE 使能   
  TIM3->CR1|=0x01;              //使能定时器 3      
}           

这里直接写正确的进行方式,不讲其他的故事了

设定 TIMx_CHx 有下面几个部分:

1. 查看 APIO 的資料,把 TIM2 的 “REMAP” 里面对应的 GPIO pin 脚选定。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
参考 nano 的预留 IO 脚,我们选择 TIM2_CH2, 脚位是 PA1. 这个时候 要记得 APIO_MAPR 的 TIM2_REMAP 要选择 “00" 或 ”10“.

2. 进RCC,要把 TIM2 的开关打开
在这里插入图片描述

这个是 RCC->APB1ENR 的内容,可以看到 TIM3 bit_1, TIM2 是在 bit_0. 所以,还要把 AFIO 输出的开关 打开,改成

RCC->APB1ENR|=1<<0;      //TIM2 时钟使能
RCC->APB2ENR|=1<<0;     //select AFIO output

4. 把 GPIO 的相关开关打开
这个部分要很熟悉才是, 但是记得 用 0xB 设定 pin 脚输出模式:

	RCC->APB2ENR|=1<<2; //PORTA 
	GPIOA->CRL&=0XFFFFFF0F;	//PA1
	GPIOA->CRL|=0X000000B0;	//'1011 to PA1

5. 把 APIO 的开关打开
主要就是 复用重映射和调试I/O配置寄存器(AFIO_MAPR) 的设定
在这里插入图片描述

   AFIO->MAPR&=0XFFFFFCFF; // '1100' to clear REMAPR[9:8] TIM2

6. 把 TIM2 的相关开关设定好
这个部分就是抄袭原来的范例,没有变更:

	TIM2->ARR=arr;			//set frequency to TIM2  
	TIM2->PSC=psc;			//psc
	TIM2->CR1=0x0080;   	//ARPE 
	TIM2->CR1|=0x01;    	//3 

7. 把 CH2 的相关开关设定好
这里要特变注意到的是 CH1,CH2 在 TIMx_CCMR1 ;CH3,CH4 在 TIMx_CCMR2. 两个不同寄存器里面。检查寄存器的结构,可以看出 bit 0-7 一组(CH1, CH3)和 bit 8~15(CH2, CH4);
在这里插入图片描述
在这里插入图片描述
因为我们选的是 CH2, 所以设定在 CCMR1.
CCER 没有变化,直接拿过来。

//----TIM2_CH2 setting--	
	TIM2->CCMR1|=7<<12;  	//CH2 PWM2CH2->		 
	TIM2->CCMR1|=1<<11; 	//CH2 PWM2CH2->   
	TIM2->CCER|=1<<4;   	//CC2E_OC2 	

8. 最后一个很重要 CCRx
这一段資料在 pwm.h 的 Define 里面。就是 每一个CH 都有自己的 CCR
CH1 对 CCR1,CH2 对 CCR2,CH3 对 CCR3,CH4 对 CCR4.下面示范了几个不同写法;

//TIM3->CCR1 LED6 
#define LED6_PWM_VAL TIM3->CCR1 
//TIM3->CCR2 LED7 
#define PC7_PWM_VAL TIM3->CCR2 
//TIM2->CCR3 PB10
#define PB10_PWM_VAL TIM2->CCR3 
//TIM2->CCR2 PA1
#define PA1_PWM_VAL TIM2->CCR2 

如果对直接修改的过程没有信心, 可以先改一个 TIM3_CH2 的过程测试(PC7 LED 明暗)。多验证几种,多改几次会有信心些。
以下是修改完成的 PWM 程式
pwm.c

//
//change it to TIM2 CH2-PA1
//arr the valeu to auto-reload
//pscPrescaler value--f CK_PSC /(PSC[15:0]+1)

void TIM2_PWM_Init(u16 arr,u16 psc)
{  	//--------GPIOB-- setting ------------------------
	

	RCC->APB2ENR|=1<<2; //PORTA 
	GPIOA->CRL&=0XFFFFFF0F;	//PA1
	GPIOA->CRL|=0X000000B0;	//'1011 to PA1
//---TIM2 setting		  
	RCC->APB1ENR|=1<<0;	    //TIM2 
	RCC->APB2ENR|=1<<0;     //select AFIO output	
    AFIO->MAPR&=0XFFFFFCFF; //清'1100' to clear REMAPR[9:8] TIM2
//---TIM2-CH2 setting    
//	AFIO->MAPR|=0xE<<8;      //TIM2_REMAP10=10,->'1110'
	TIM2->ARR=arr;			//set frequency to TIM2  
	TIM2->PSC=psc;			//psc
	TIM2->CR1=0x0080;   	//ARPE 
	TIM2->CR1|=0x01;    	//3 	
//----TIM2_CH2 setting--	
	TIM2->CCMR1|=7<<12;  	//CH2 PWM2CH2->		 
	TIM2->CCMR1|=1<<11; 	//CH2 PWM2CH2->   
	TIM2->CCER|=1<<4;   	//CC2E_OC2 	   
				
}

pwm.h

#ifndef __PWM_H
#define __PWM_H
#include "sys.h"

//	 
//try a new PWM by TIM2									  
//

//TIM3->CCR1 LED6 
#define LED6_PWM_VAL TIM3->CCR1 
//TIM3->CCR1 LED6 
#define PA7_PWM_VAL TIM3->CCR2 
//TIM2->CCR3 PB10
#define PB10_PWM_VAL TIM2->CCR3 
//TIM2->CCR2 PA1
#define PA1_PWM_VAL TIM2->CCR2 

void TIM3_PWM_Init(u16 arr,u16 psc);
void TIM2_PWM_Init(u16 arr,u16 psc);
#endif

pwm.h 里面保留了 nano 开发板原来的 PC6 亮度调变程式;目的在视觉观察程式是否正常用的。

SPI

SPI 的原件很多,应用也很广,在网上也能够取得许多不同的程式。这次试验的目的在用 SPI1 接收 MAX6675 的讯号。这里先调整 SPI。
下面是 nano 板的程式,选的是 SPI2.
spi2.c

//-------------------------------------------------
void SPI2_Init(void) 
{     
  RCC->APB2ENR|=1<<3;        //PORTB 时钟使能       
  RCC->APB1ENR|=1<<14;          //SPI2 时钟使能   
  //这里只针对 SPI 口初始化 
  GPIOB->CRH&=0X000FFFFF;   
  GPIOB->CRH|=0XBBB00000;  //PB13/14/15 复用             
  GPIOB->ODR|=0X7<<13;        //PB13/14/15 上拉 
  SPI2->CR1|=0<<10;      //全双工模式   
  SPI2->CR1|=1<<9;        //软件 nss 管理 
  SPI2->CR1|=1<<8;       
  SPI2->CR1|=1<<2;        //SPI 主机 
  SPI2->CR1|=0<<11;      //8bit 数据格式   
  SPI2->CR1|=1<<1;        //空闲模式下 SCK 为 1 CPOL=1 
  SPI2->CR1|=1<<0;        //数据采样从第二个时间边沿开始,CPHA=1     
  //对 SPI2 属于 APB1 的外设.时钟频率最大为 36M. 
  SPI2->CR1|=3<<3;        //Fsck=Fpclk1/256 
    SPI2->CR1|=0<<7;        //MSBfirst       
  SPI2->CR1|=1<<6;        //SPI 设备使能 
  SPI2_ReadWriteByte(0xff);    //启动传输       
}     

//---------------------------------------------------
u8 SPI2_ReadWriteByte(u8 TxData) 
{     
  u16 retry=0;           
  while((SPI2->SR&1<<1)==0)  //等待发送区空   
  { 
    retry++; 
    if(retry>=0XFFFE)return 0;   //超时退出 
  }           
  SPI2->DR=TxData;            //发送一个 byte   
  retry=0; 
  while((SPI2->SR&1<<0)==0)    //等待接收完一个 byte     
  { 
    retry++; 
    if(retry>=0XFFFE)return 0;  //超时退出 
  }                           
  return SPI2->DR;                      //返回收到的数据                 
}         

spi2.h

#ifndef __SPI_H 
#define __SPI_H 
#include "sys.h" 
// SPI 总线速度设置   
#define SPI_SPEED_2        0 
#define SPI_SPEED_4        1 
#define SPI_SPEED_8        2 
#define SPI_SPEED_16       3 
#define SPI_SPEED_32       4 
#define SPI_SPEED_64       5 
#define SPI_SPEED_128      6 
#define SPI_SPEED_256      7   
void SPI2_Init(void);          //初始化 SPI2 口 
void SPI2_SetSpeed(u8 SpeedSet);    //设置 SPI2 速度       
u8 SPI2_ReadWriteByte(u8 TxData);  //SPI2 总线读写一个字节 
#endif 

1. 查看 APIO 的資料,把 SPI1 的 "REMAP” 里面对应的 GPIO pin 脚选定。
选 pin 脚的事情要好好看看!

A。 没有 SPI2 的复用資料?
因为没有 SPI2 資料表比较,很容易就忽略了 **“NSS”**的使用技巧。从复用表上了解到需要用到的 pin 脚有 4 个,NSS\SCK\MISO\MOSI. 再看看 nano 的程式

  GPIOB->CRH|=0XBBB00000;  //PB13/14/15 复用             
  GPIOB->ODR|=0X7<<13;        //PB13/14/15 上拉 

只定义了 3 个 脚位,就是 SCK\MISO\MOSI。nano 資料说明是 “使用的是 PB13、14、15 这 3 个(SCK.、MISO、MOSI,CS 使用软件管理方式),所以设置这三个为复用功能 IO”。
再查了下网络其他讨论 STM32F103 的文章,有许多负面的声音。不过,没关系,我们只要知道怎么用就好。

B。选用 nano 预留,没其他使用的脚位
SPI1 的 PA4\5\6\7 这一组可以使用。
在这里插入图片描述
为了日后使用方便,我们把复用 GPIO 另外定义如下:
复用的 IO 要用 0xB. 没有定义 PA4.

void SPI1_GPIO_Init(void)
{
    RCC->APB2ENR|=1<<3;       //PORTA selected 	 
	AFIO->MAPR&=!(1)<<0;	//SPI1_REMAP=0, pin out PA4,5,6,7
	//SPI set GPIO output
	GPIOA->CRL&=0X000FFFFF; //clear PA5,6,7
	GPIOA->CRL|=0XBBB00000;//PA5,6,7 '1011' 
	GPIOA->ODR|=0X7<<5;    //PA5,6,7 high
}

2. 进RCC,要把 SPI1 的开关打开
从資料上看到 SPI1 是在 RCC->APB2ENR 的 pin 12.
在这里插入图片描述
所以:

RCC->APB2ENR|=1<<12;      //SPI1 enable, APB2ENR bit12 

3.设定 SPI1 的相关参数
参考STM32F103 的 資料,SPI 的主要设定都在 CR1,如下:
在这里插入图片描述
再参考其他程式 和 nano 板的程式,改写成下面这样:

	// ====setting SPI    
    SPI1->CR1|=0<<10;// dual-channel	
	SPI1->CR1|=1<<8;  //if SSM=1,internal slave

	SPI1->CR1|=1<<2; //set as a SPI master
	SPI1->CR1|=0<<11;//8 bit data frame format	
	SPI1->CR1|=1<<1; //set SCK nowork at '1', CPOL=1
	SPI1->CR1|=1<<0; //sampling at 2nd clock edge,CPHA=1 
	//====SPI1 clock setting 
	SPI1->CR1|=7<<3; //Fsck=Fcpu/256
	SPI1->CR1|=0<<7; //MSB send first 
	SPI1->CR1|=1<<9; //software can management  nss 

所有参数设定完成后,再把 CR1 的 SPI enable 打开。

	SPI1->CR1|=1<<6; //SPI enable
	SPI1_ReadWriteByte(0xff);//启动传输(主要作用:维持MOSI为高)	

4. 重写 SPI_ReadWriteByte(u8 TxData) 程式
这一段程式很重要,因为几乎每一个应用程式都会呼叫这一段。就是说,这一段之前的 SPI 设定就完成了,是个分水岭。修改方法就是 把 SPI2 改成 SPI1.

u8 SPI1_ReadWriteByte(u8 TxData)
{
 u16 retry=0;				 
	while((SPI1->SR&1<<1)==0)//等待发送区空	
	{
		retry++;
		if(retry>0XFFFE)return 0;  //timeout return
	}			  
	SPI1->DR=TxData;	 	  //send a byte 
	retry=0;
	while((SPI1->SR&1<<0)==0) //waiting for receiver completed abyte  
	{
		retry++;
		if(retry>0XFFFE)return 0;  //timeout return
	}	  						    
	return SPI1->DR;          //return a receive data	
}

5. 设定 NSS(CS) 的结构在应用原件的程式里
对于 MAX6675 原件,只需要读取資料,不需要写,所以把 PA4(NSS) 定义如下:

void max6675_init(void)
{
    RCC->APB2ENR|=1<<3;       //PORTA selected 	 
   	GPIOA->CRL&=0XFFF0FFFF; //clear PA4,
	GPIOA->CRL|=0X00030000;//PA4,'0011' 
	PAout(4)=1;   //	T_CS=1
	
   SPI1_Init();	
} 

SPI 被定义一次后,就可以直接套用在其他 需要 SPI 的器件上使用。下面再看看 MAX6675 的程式:

MAX6675

以下的程式是从网络上找来的,除了 max6675_init(void) 大改,还有与 NSS(CS) “0”、“1” 开关 --PAout(4) 需要调整,其他的都不变。修改后如下:

#include "max6675.h"
#include "spi1.h"

void SPI1_Init(void);			 //SPI2 init
void SPI1_SetSpeed(u8 SpeedSet); //setting SPI speed   
u8 SPI1_ReadWriteByte(u8 TxData);//SPI2 read/write a byte 

/**
  * @brief  max66675 module initinization
  * @param  None
  * @retval None
  */
void max6675_init(void)
{
    RCC->APB2ENR|=1<<3;       //PORTA selected 	 
   	GPIOA->CRL&=0XFFF0FFFF; //clear PA4,
	GPIOA->CRL|=0X00030000;//PA4,'0011' 
	PAout(4)=1;   //	T_CS=1
	
 SPI1_Init();	
} 

/**
  * @brief  max6675 read/write a byte
  * @param  txData the data need to send 
  * @retval receive data
  */
uint8_t max6675_readWriteByte(uint8_t txData)
{		
  return SPI1_ReadWriteByte(txData);
}  

/**
  * @brief  max6675 read a datum from chip 
  * @param  None
  * @retval original data from max6675 
  */
uint16_t max6675_readRawValue(void)
{
  uint16_t tmp;
  
//  GPIO_ResetBits(MAX6675_CS1_PORT,MAX6675_CS1_PIN); //enable max6675
  PAout(4)=0;
 
  tmp = max6675_readWriteByte(0XFF); //read MSB
  tmp <<= 8;
  tmp |= max6675_readWriteByte(0XFF); //read LSB
  
//  GPIO_SetBits(MAX6675_CS1_PORT,MAX6675_CS1_PIN); //disable max6675
  PAout(4)=1;  
  
  if (tmp & 4) 
  {
    // thermocouple open
    tmp = 4095; //if it was no data
  }
  else 
  {
    tmp = tmp >> 3;
  }
  return tmp;
}

/**
  * @brief  max6675 read a datum from chip
  * @param  None
  * @retval convert data to degree C
  */
float max6675_readTemperature(void)
{
  return (max6675_readRawValue() * 1024.0 / 4096);
}

修改主程式 main.c

这里我们是把 nano 第 21 个试验 **“DS18B20 数字温度传感器实验”**拿来修改。只要把 DS18B20 用 MAX6675 取代,就可以直接在 7 节显示 LED 看到温度。下面附上的程式留了许多测试过程的痕迹,可供参考用。

int main(void)
{ 
	Stm32_Clock_Init(9);	//system clock init
	uart_init(72,115200);	//uart to sscom, baud rate 115200
	delay_init(72);	   	 	//init delay
	LED_Init();		  		//init LED light
	Key_EXTIX_Init();  // INT EXTI withe Key setup
/* PWM 	*/
	arr_s=899;
	TIM2_PWM_Init(arr_s,0);//init PWM, arr= 899, f =72000/(899+1)=80Khz 	
	PWM_VAL = (int)(arr_s/2);
/*  SPI  */
	SPI1_GPIO_Init();
	SPI1_Init();
  	max6675_init();	
//======================	
	LED_SMG_Init();         //init 7-seg
 
	printf("NANO STM32\r\n");
	printf("MAX6675 TEST\r\n");
 
    TIM3_Init(19,7199);  // cycle time 2ms,fo 7-seg 
    
	while(1)
	{
	 	raw_t = max6675_readTemperature(); //get temp
//		printf("the raw_t_t temperature is:%.2f  \n",raw_t);
//   		printf("cc\n");
		tem_get[count]=raw_t;  //put Temp to show register
		if(count >0)
		  {	count--;
		  }else count=4;
		
		if(count==0)
		 {  avg_t=tem_get[3];
			for(j=0;j>4;j++)
		    	{avg_t=(avg_t+tem_get[j])/2;}
		 }
		 
		 avg_t=raw_t;  //--send temp to show----
		 show_temp(avg_t);
		 show_PWM(PWM_VAL,arr_s); //put PWM to show register		
	    if (prt_cnt==300)
	    	{
				printf("the avg_t temperature is:%.2f  \n",avg_t);
	    		printf("cc\n");
			}
		else prt_cnt++;
		delay_ms(300);	
		}	
}

MAX6675 是采用下面这个模块
在这里插入图片描述

以上内容是摘取网络資料整理出来,参考参考!

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值