STMS003F3的TIM定时器,ADC采样与普通IO输入输出配置(新手必看,保姆级)

配置定时器和ADC程序的时候主要注意几点:

1:使用的TIM引脚

        若是TIM使用的时候一定要注意引脚有没有ADC或者其他功能的使用,若是有一定要配置好选用的引脚。

        简单举个例子PD3做ADC采样用,PD2做普通IO口输出(这两个都是TIM2引脚的):在PD3引脚进行ADC初始化配置的时候一定要将ADC_CSR寄存器配置准确,使用哪个通道开启哪个,没用到的通道配置后会影响时钟,假设在PD3引脚ADC初始化配置的时候不慎开启了PD2通道,直观的影响就是同为TIM2的PD2引脚在输出高电平的时候会有一个20uS的低电平(形成一个伪PWM输出)。(有影响的时候你会发现,你不使用TIM计时或者不用ADC的时候PD2会正常输出,但是一用这两个就会输出不正常,当然你可以在硬件上挂一个电容滤波,但根本原因还是ADC配置问题。)

ADC_CSR  = 0x84;         //选择通道,禁止中断

2:了解定时器所用的寄存器是做什么的

可以参考官网寄存器配置手册:STM8S003F3 - 主流超值系列8位MCU,具有8 KB Flash、16 MHz CPU和集成EEPROM - 意法半导体STMicroelectronicsicon-default.png?t=N7T8https://www.st.com.cn/zh/microcontrollers-microprocessors/stm8s003f3.html#

(有中文版本参考)链接:https://pan.baidu.com/s/1GSqf-3vD6HsmuRCoN7KVow 
提取码:1234

TIM1_CR1 :计时器使能  0x00:不使能,0x01:使能(配置的时候记得一定要先关闭再开启)

TIM1_ARR:通俗讲就是计数用的数值范围,把你需要的时间拆分成数字写入计时器,比如你把定时器配置成1M的,1MHZ = 1MHZ = 1000KHZ = 1000000HZ对吧,1/1000000 = 1us。假设我们打算配置1ms ,1ms = 1000us。

TIM1_PSCR:将系统时钟分频,比如你之前配置的系统时钟是16M,那么就可以将寄存器值存入数值15,将计数器分频为1M。

其余的不用理解直接按照我下面的配置即可。

void TIM1_Init(void)
{
	TIM1_CR1 = 0x00;//stop
	TIM1_ARRH = ( unsigned char )( 1000 >> 8 );//定时1ms
	TIM1_ARRL = ( unsigned char )( 1000 );
	TIM1_PSCRH = ( unsigned char )( 0x0F >> 8 );        // 16M / (1+15) =1M
    TIM1_PSCRL = ( unsigned char )( 0x0F );
	TIM1_EGR = 0x01;//产生更新事件
	TIM1_SR1 = ( ~0x01 );//清除更新中断标志
	TIM1_CR1 = 0x01;//使能计数器
	TIM1_IER |= 0x01;//更新中断使能
}

使用定时器中断的时候注意一定要配置中断向量表

struct interrupt_vector const _vectab[] = {
	{0x82, (interrupt_handler_t)_stext}, /* reset */
	{0x82, NonHandledInterrupt}, /* trap  */
	{0x82, NonHandledInterrupt}, /* irq0  */
	{0x82, NonHandledInterrupt}, /* irq1  */
	{0x82, NonHandledInterrupt}, /* irq2  */
	{0x82, NonHandledInterrupt}, // irq3  PORTA
	{0x82, NonHandledInterrupt}, /* irq4  */
	{0x82, NonHandledInterrupt}, /* irq5  */
	{0x82, NonHandledInterrupt}, /* irq6  */
	{0x82, NonHandledInterrupt}, //* irq7  PORT
	{0x82, NonHandledInterrupt}, /* irq8  */
	{0x82, NonHandledInterrupt}, /* irq9  */
	{0x82, NonHandledInterrupt}, /* irq10 */
	{0x82, TIM1_OVR_IRQHandler}, //* irq11 TIM1 OVF
	{0x82, NonHandledInterrupt}, /* irq12 */
	{0x82, TIM2_OVR_IRQHandler}, // irq13 TIM2 OVF
	{0x82, NonHandledInterrupt}, /* irq14 */
	{0x82, NonHandledInterrupt}, /* irq15 */
	{0x82, NonHandledInterrupt}, /* irq16 */
	{0x82, NonHandledInterrupt}, /* irq17 */
	{0x82, NonHandledInterrupt}, // irq18 USART REV
	{0x82, NonHandledInterrupt}, /* irq19 */
	{0x82, NonHandledInterrupt}, /* irq20 */
	{0x82, NonHandledInterrupt}, // irq21 
	{0x82, NonHandledInterrupt}, /* irq22 */
	{0x82, NonHandledInterrupt}, /* irq23 */
	{0x82, NonHandledInterrupt}, /* irq24 */
	{0x82, NonHandledInterrupt}, /* irq25 */
	{0x82, NonHandledInterrupt}, /* irq26 */
	{0x82, NonHandledInterrupt}, /* irq27 */
	{0x82, NonHandledInterrupt}, /* irq28 */
	{0x82, NonHandledInterrupt}, /* irq29 */
};

 /***********************************************************
 函数名:@far @interrupt void TIM1_OVR_IRQHandler (void) 
 参  数:void
 返  回:void
 功  能:定时器1的中断函数,1ms产生一次中断
你可以把这个当作使用案例
 **********************************************************/
//已经通过定时器初始化配置将中断配置为1ms的中断
@far @interrupt void TIM1_OVR_IRQHandler (void) 
{
	TIM1_SR1 = 0x00;// 清除更新中断标记,这步不能漏掉,否则会 连续进入中断程序
	//定义一个1S计时的标志
    FLAG_1s_CNT++;
	if(FLAG_1s_CNT > 1000)
	{
		FLAG_1s_CNT = 0;
		FLAG_1s_CNT_F = 1;
	}
}

3:ADC寄存器配置要领

ADC_CSR:注意这个是选择通道的,使用引脚ADC采样需要查看芯片手册的引脚定义是几通道,比如PD3引脚就是AIN4通道4,就可以配置成0x84。

ADC_CR1:开始ADC转换,0x00关闭,0x01开启。

除了延时是必要的其他的可以按照我下面的配置,我只用了一个ADC通道,要是用多个的话需要切换ADC通道的。

 void AD_Init(void)
{
   u8 i;

   CLK_PCKENR2 |= 0x08; //使能ADC模块时钟
   ADC_CSR  = 0x84;         //选择通道,禁止中断
   ADC_CR1 |= 0X01;         //预分频,单次转换,模块上电
   ADC_CR2 |= 0x08;         //数据右对齐,扫描模式
   ADC_CSR &= ~0x80;       //清除EOC转换结束标志

   i = 100;
   while (i--); // 延时一段时间,至少7uS,保证ADC模块的上电完成?
}
/***********************************************************
 函数名: void AD_Process(void)
 参  数:void
 返  回:void
 功  能:单位ADC采样处理
你可以把这个当作一个使用案例
 **********************************************************/
void AD_Process(void)
{
	 u8 i;
   u16	temph = 0;
	 u16	ADValue = 0;
   u8	templ = 0;
	 //--------------通道4 控制器电源电压 --------------------
   ADC_CSR &= (~0x80);   // 清除EOC转换结束标志
   ADC_CSR &= (~0x0F); // Clear the ADC1 channels
   ADC_CSR |= (0x04);  // Select the ADC1 channel
   ADC_CR1 |= 0x01;             // ADON=1,AD转换开始
   while ((ADC_CSR & 0x80) != 0x80); // 等待转换结束
   templ = ADC_DRL; // Read LSB first
   temph = ADC_DRH; // Then read MSB
   ADValue = (u16)(templ | (u16)(temph << (u8)8));
   ADC_CSR &= (~0x80);   // 清除EOC转换结束标志
	 //return  ADValue;
	 PADCProperty.HC_bat = ADValue;
   PADCProperty.sum_BAT += ADValue;
   PADCProperty.CNT_BAT++;
	 
	 if (PADCProperty.CNT_BAT >= 512) //电池 1024
   {
      PADCProperty.BAT_data = PADCProperty.sum_BAT; // 除以1024,乘以32
			
	    PADCProperty.BAT_data = (PADCProperty.BAT_data + PADCProperty.BAT_data_old) >> 1;
		  PADCProperty.BAT_data_old = PADCProperty.BAT_data;
			PADCProperty.BAT_Voltage =  ((((PADCProperty.BAT_data * 50)* 11 ) /512) /1024) + 7;// 配合一下硬件分压,二极管0.7V
			
      PADCProperty.CNT_BAT = 0;
      PADCProperty.sum_BAT = 0;
			PADCProperty.HC_bat = 0;
   }
	 
}

4:普通IO口配置

直接写寄存器比较麻烦,所以我这里封装了一下,你也可以按照寄存器的直接写值配置,效果都是一样的。

/***********************************************************
*   宏定义,这个是对寄存器操作的一个封装你可以直接复制
***********************************************************/
//GPIO输入输出
#define PADIR(dir, index)    {dir == 0? (PA_DDR &= ~(1<<index)): (PA_DDR |= (1<<index));}
#define PBDIR(dir, index)    {dir == 0? (PB_DDR &= ~(1<<index)): (PB_DDR |= (1<<index));}
#define PCDIR(dir, index)    {dir == 0? (PC_DDR &= ~(1<<index)): (PC_DDR |= (1<<index));}
#define PDDIR(dir, index)    {dir == 0? (PD_DDR &= ~(1<<index)): (PD_DDR |= (1<<index));}
//GPIO模式选择
#define PAMODE(mode, index)    {mode == 0? (PA_CR1 &= ~(1<<index)): (PA_CR1 |= (1<<index));}
#define PBMODE(mode, index)    {mode == 0? (PB_CR1 &= ~(1<<index)): (PB_CR1 |= (1<<index));}
#define PCMODE(mode, index)    {mode == 0? (PC_CR1 &= ~(1<<index)): (PC_CR1 |= (1<<index));}
#define PDMODE(mode, index)    {mode == 0? (PD_CR1 &= ~(1<<index)): (PD_CR1 |= (1<<index));}
//输出电平设置
#define PA_OLEVEL(dir, index, level)    {dir == 1? (level == 0? (PA_ODR &= ~(1<<index)): (PA_ODR |= (1<<index))): PA_IDR;}
#define PB_OLEVEL(dir, index, level)    {dir == 1? (level == 0? (PB_ODR &= ~(1<<index)): (PB_ODR |= (1<<index))): PB_IDR;}
#define PC_OLEVEL(dir, index, level)    {dir == 1? (level == 0? (PC_ODR &= ~(1<<index)): (PC_ODR |= (1<<index))): PC_IDR;}
#define PD_OLEVEL(dir, index, level)    {dir == 1? (level == 0? (PD_ODR &= ~(1<<index)): (PD_ODR |= (1<<index))): PD_IDR;}
//输入电平设置
#define PA_ILEVEL(index)    (PA_IDR & (1<<index))
#define PB_ILEVEL(index)    (PB_IDR & (1<<index))
#define PC_ILEVEL(index)    (PC_IDR & (1<<index))
#define PD_ILEVEL(index)    (PD_IDR & (1<<index))
/***********************************************************
 函数名:void init_Pxx_as_gpio(TGpioProperty Portx)
 参  数:TGpioProperty.Portx
 返  回:void
 功  能:GPIO的引脚初始化
 **********************************************************/
void init_Pxx_as_gpio(TGpioProperty Portx)
{
	switch(Portx.port)
	{
		case PORTA:
				PADIR(Portx.dir, Portx.index);
				PAMODE(Portx.mode, Portx.index);
				PA_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		case PORTB:
				PBDIR(Portx.dir, Portx.index);
				PBMODE(Portx.mode, Portx.index);
				PB_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		case PORTC:
				PCDIR(Portx.dir, Portx.index);
				PCMODE(Portx.mode, Portx.index);
				PC_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		case PORTD:
				PDDIR(Portx.dir, Portx.index);
				PDMODE(Portx.mode, Portx.index);
				PD_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		default:
			break;
	}
}
/***********************************************************
 函数名:void set_Pxx_level(TGpioProperty Portx)
 参  数:TGpioProperty.Portx
 返  回:void
 功  能:设置引脚电平
 **********************************************************/
void set_Pxx_level(TGpioProperty Portx)
{
	switch(Portx.port)
	{
		case PORTA:
				PA_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		case PORTB:
				PB_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		case PORTC:
				PC_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		case PORTD:
				PD_OLEVEL(Portx.dir, Portx.index, Portx.outlevel);
			break;
		default:
			break;
	}
}
/***********************************************************
 函数名:bool get_Pxx_level(TGpioProperty Portx)
 参  数:TGpioProperty.Portx
 返  回:void
 功  能:获取引脚电平
 **********************************************************/
bool get_Pxx_level(TGpioProperty Portx)
{
	bool level = 0;;
	switch(Portx.port)
	{
			case PORTA:
					level = PA_ILEVEL(Portx.index);
				break;
			case PORTB:
					level = PB_ILEVEL(Portx.index);
				break;
			case PORTC:
					level = PC_ILEVEL(Portx.index);
				break;
			case PORTD:
					level = PD_ILEVEL(Portx.index);
				break;
			default:
				break;
	}
	
	return level;

}
/***********************************************************
 函数名: void GPIO_OutPutInit(void)
 参  数:void
 返  回:void
 功  能:输出引脚初始化
根据你需要的引脚按照格式配置就行了,其余的都是封装都可以直接复制使用
 **********************************************************/
 void GPIO_OutPutInit(void)
 {
	 
		 //PD2配置为推挽输出,//默认输出为高电平(电机反转)
		Portx.port = PORTD;
		Portx.dir = 1;//输入输出选择
		Portx.mode = 1;//模式选择
		Portx.index = 2;    //端口选择
		Portx.outlevel = 0;//输出时电平选择,输入模式无效
		init_Pxx_as_gpio(Portx);
		
		//PC7配置为推挽输出,//默认输出为低电平
		Portx.port = PORTC;
		Portx.dir = 1;//输入输出选择
		Portx.mode = 1;//模式选择
		Portx.index = 7;    //端口选择
		Portx.outlevel = 0;//输出时电平选择,输入模式无效
		init_Pxx_as_gpio(Portx);
		
		//PC6配置为推挽输出,//默认输出为低电平
		Portx.port = PORTC;
		Portx.dir = 1;//输入输出选择
		Portx.mode = 1;//模式选择
		Portx.index = 6;    //端口选择
		Portx.outlevel = 0;//输出时电平选择,输入模式无效
		init_Pxx_as_gpio(Portx);
		
		//PC5配置为推挽输出,//默认输出为低电平
		Portx.port = PORTC;
		Portx.dir = 1;//输入输出选择
		Portx.mode = 1;//模式选择
		Portx.index = 5;    //端口选择
		Portx.outlevel = 0;//输出时电平选择,输入模式无效
		init_Pxx_as_gpio(Portx);
		
		 //PD2配置为推挽输出,//默认输出为高电平(电机反转)
		Portx.port = PORTD;
		Portx.dir = 0;//输入输出选择
		Portx.mode = 0;//模式选择
		Portx.index = 3;    //端口选择
		//Portx.outlevel = 0;//输出时电平选择,输入模式无效
		init_Pxx_as_gpio(Portx);
		
		Portx.outlevel = 0;
		set_Pxx_level(Portx);
		
 }
 /***********************************************************
 函数名:  void Set_GPIO_OutPutlevel(void)
 参  数:u8 FAN_Outx:选择输出的引脚,u8 level:引脚输出电平
 返  回:void
 功  能:设置引脚输出电平
这个是具体使用的函数封装你可以把你需要的引脚按照下面的格式配置一下
 **********************************************************/
 void Set_GPIO_OutPutlevel(u8 FAN_Outx,u8 level)
 {
	 Portx.dir = 1;//输入输出选择
	 if(level == HIGHT_LEVEL)//引脚输出高电平,电机正转
	 {
		 switch(FAN_Outx)
		{
				case 1://K1
						Portx.port = PORTD;
						Portx.index = 2;
						Portx.outlevel = 1;
						set_Pxx_level(Portx);
					break;
				case 2://K2
						Portx.port = PORTC;
						Portx.index = 7;
						Portx.outlevel = 1;
						set_Pxx_level(Portx);
					break;
				case 3://K3
						Portx.port = PORTC;
						Portx.index = 6;
						Portx.outlevel = 1;
						set_Pxx_level(Portx);
					break;
				case 4://K4
						Portx.port = PORTC;
						Portx.index = 5;
						Portx.outlevel = 1;
						set_Pxx_level(Portx);
					break;
				default:
					break;
		}
	}
	else if(level == LOW_LEVEL)//引脚输出低电平,电机反转
	{
		switch(FAN_Outx)
		{
				case 1://K1
						Portx.port = PORTD;
						Portx.index = 2;
						Portx.outlevel = 0;
						set_Pxx_level(Portx);
					break;
				case 2://K2
						Portx.port = PORTC;
						Portx.index = 7;
						Portx.outlevel = 0;
						set_Pxx_level(Portx);
					break;
				case 3://K3
						Portx.port = PORTC;
						Portx.index = 6;
						Portx.outlevel = 0;
						set_Pxx_level(Portx);
					break;
				case 4://K4
						Portx.port = PORTC;
						Portx.index = 5;
						Portx.outlevel = 0;
						set_Pxx_level(Portx);
					break;
				default:
					break;
		}
	}
	else{}
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值