个人工作上的关系,需要做一款温度控制风扇速度的控制器,还需要能够看到温度和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 PWM2CH2->
TIM2->CCMR1|=1<<11; //CH2 PWM2CH2->
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
//pscPrescaler 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_REMAP10=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 PWM2CH2->
TIM2->CCMR1|=1<<11; //CH2 PWM2CH2->
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 是采用下面这个模块
以上内容是摘取网络資料整理出来,参考参考!