发此帖,一是工作总结,二是经验分享。
	
CONTENTS
已完成:
	
	
	
	
 
					
				 
					
				FreeRTOS移植
开发环境: keil uVersion5 ,ST-link,USB转TTL。
去年玩四轴的时候用过FreeRTOS,觉得还算好用,今年继续用这个吧。RTOS,实时操作系统,有兴趣的可以去谷歌。以后的开发实验都在这个系统上进行了。
一、新建文件夹Balancer123,作为我们存放工程的目录,在里面新建lib,list,output,user四个文件夹,并新建一个空工程:
		 
	
		
	
		
	
		
		 
			二、下载FreeRTOS压缩文件包,下载STM32库函数文件stsw-stm32054.zip(STM32F10x_StdPeriph_Lib_V3.5.0),并分别解压出FreeRTOSV8.2.1、STM32F10x_StdPeriph_Lib_V3.5.0两个文件夹。
		 
			三、将解压出来的外设库中STM32F10x_StdPeriph_Lib_V3.5.0\Libraries下两个文件夹拷至工程Lib目录,复制FreeRTOSV8.2.1\FreeRTOS\Source文件夹到工程下Lib文件夹,并重命名为FreeRTOS。
		
	
		
		 
			1复制\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template目录下stm32f10x_conf.h、stm32f10x_it.c、stm32f10x_it.h文件至\user\目录下。
		 
			2复制FreeRTOSV8.2.1\FreeRTOS\Demo\CORTEX_STM32F103_Keil目录下FreeRTOSConfig.h和main.c文件到工程文件夹的\user\目录下。
		 
			 3复制Balancer123\lib\CMSIS\CM3\DeviceSupport\ST\STM32F10x目录下
		 
			stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h三个文件至\user\目录下。
		 
			 4复制Balancer123\lib\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm目录下startup_stm32f10x_md.s文件到\user\目录下并改名为STM32F10x.s(该文件为系统的启动文件,适用于64K-128K Flash空间的芯片)。取消该STM32F10x.s文件只读属性,修改如下:
		 
			在 __heap_limit(位于50行) 段后添加
		 
			 
		 
			                IMPORT xPortPendSVHandler
		 
			                IMPORT xPortSysTickHandler
		 
			                IMPORT vPortSVCHandler
		 
			 
		 
			__Vectors区段中断向量表将SVCall Handler、PendSV_Handler、SysTick Handler改由RTOS管理。直接注释掉旧的,添加新代码并保存。
		 
			 
		 
			                ;DCD     SVC_Handler                ; SVCall Handler
		 
			                DCD     vPortSVCHandler           ; SVCall Handler
		 
			 
		 
			                ;DCD     PendSV_Handler             ; PendSV Handler  
		 
			                DCD     xPortPendSVHandler        ; PendSV Handler
		 
			                ;DCD     SysTick_Handler            ; SysTick Handler
		 
			                DCD     xPortSysTickHandler       ; SysTick Handler 
		 
			四、如下图进入工程项目管理菜单。
		
	
		
		 
			笔者简单分了四组,分别是user,lib,freertos,drivers。三组中分别添加文件,如下图:
		
	
		1、User添加user目录下四个程序文件。 
			4、Drivers文件夹留待以后存放自己编写的设备驱动,如电机,编码器,i2c设备,spi设备等。
		 
			添加完成后如下图所示:
		
2、lib添加Balancer123\lib\STM32F10x_StdPeriph_Driver\src目录下所有c文件。
3、freertos添加\Balancer123\lib\FreeRTOS\目录下list.c,queue.c,task.c,timer.c四个文件及Balancer123\lib\FreeRTOS\portable\MemMang\目录下heap_2.c文件及Balancer123\lib\FreeRTOS\portable\RVDS\ARM_CM3\目录下port.c文件。最后一个目录下有个portmacro.h文件后面也会用得到。
		
	
		
		 
			五、配置属性:
		
	
选择时钟频率为72.0,选中右上角的“Use MicroLIB”复选框。
		
	
		点击“Seletc Folder for Objects...”选择工程目录下创建的output文件夹,选中下面的“Create Hex File”复选框。
	
		
	
		同上步一样选择listing目录为工程中的list文件夹。
	
		
	
		定义“Preprocessor Symbols”,添加“USE_STDPERIPH_DRIVER,STM32F10X_MD”表示使用标准外设库并使用中等容量Flash的芯片。定义“IncludePaths”,添加需要用到的头文件的路径,如上所示就差不多了。记得添加Balancer123\lib\FreeRTOS\portable\RVDS\ARM_CM3目录到头文件目录,里面有portmacro.h文件需要用到。
	
		
	
		笔者使用ST-link,设置右上角为“ST-Link Debugger”并点右侧“Settings”按钮配置,调出配置菜单。“Debug”页修改左侧菜单中的“ort:”改为“SW”,“Flash Download”页在“Download Functions”中选中“reset and Run”,这样程序下载到flash后自动运行。点击“Add”按钮添加Flash 编程方法,选128K的那项,即下载到芯片中的Flash中。如下图所示
	
		
	
		
	
		六、编译程序
由于使用了其他工程的main.c文件,把用到的不必要的头文件,宏定义,其他功能或函数都删除。仅保留 static void prvSetupHardware( void )函数即可(该函数最后一行如果提示有错误可以删掉),该函数初始化对时钟等初始化。Main()函数删减到
int main( void )
{
 prvSetupHardware();
 /* Start the scheduler. */
 vTaskStartScheduler();
 /* Will only get here if there was not enough heap space to create the
 idle task. */
 return 0;
}
即可。编译一下,对可能出的错误按提示修改。如果没错误,一个空的工程建立成功了。
	
 
					
				LED GPIO端口测试
LED端口定义如下:
	 
驱动很简单,往PB8端口输出0后LED亮,输出1时LED灭,直接贴程序代码:
	
 
		 
	#include "led.h"
#include "stm32f10x.h"
#include <stdbool.h>
/*FreeRtos includes*/
#include "FreeRTOS.h"
#include "task.h"
//Hardware configuration
#define LED_GPIO_PERIF   RCC_APB2Periph_GPIOB		
#define LED_GPIO_PORT    GPIOB						
#define LED_GPIO_GREEN   GPIO_Pin_8						
#define LED_POL_GREEN    LED_POL_NEG
static bool isInit=false;
void ledSet(bool value);
//Initialize the green led pin as output
void ledInit()
{
  GPIO_InitTypeDef GPIO_InitStructure;
  if(isInit)
    return;
  // Enable GPIO
  RCC_APB2PeriphClockCmd(LED_GPIO_PERIF, ENABLE);
  //Initialize the LED pins as an output
  GPIO_InitStructure.GPIO_Pin = LED_GPIO_GREEN;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
  GPIO_Init(GPIOB, &GPIO_InitStructure);	
  //Turn off the LED:s
  ledSet(0);
  isInit = true;
}
bool ledTest(void)
{
  return isInit;
}
void ledSet(bool value)
{
  if(value)
    GPIO_ResetBits(GPIOB, LED_GPIO_GREEN); 
  else
    GPIO_SetBits(GPIOB, LED_GPIO_GREEN);
}
void vLEDTask( void *pvParameters )
{
    for( ;; )
    {
    ledSet(true);
    vTaskDelay(500);
    ledSet(false);
    vTaskDelay(2000);
    }
}
	
	
 
		 
	int main( void )
{
	prvSetupHardware();
	ledInit();
	
	xTaskCreate(vLEDTask,"LED", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+3, NULL );
	/* Start the scheduler. */
	vTaskStartScheduler();
	/* Will only get here if there was not enough heap space to create the
	idle task. */
	
	return 0;
}
	
	
 
					
				电机驱动实验
原理:通过STM32的定时器产生PWM信号,控制电机的转速。根据原理图知道,电机A接PB1,电机B接PB0。根据下表定义知道,用到了定时器TIM3的CH4和CH3。
	 
直接贴程序,通过程序基本上就弄懂原理了。可以直接使用初始化的程序。
	
 
		 
	#include <stdbool.h>
#include "stdio.h"
/* ST includes */
#include "stm32f10x.h"
#include "motors.h"
//FreeRTOS includes
#include "FreeRTOS.h"
#include "task.h"
// HW defines
#define MOTORS_GPIO_TIM_PERIF     RCC_APB1Periph_TIM3
#define MOTORS_GPIO_TIM_DBG       DBGMCU_TIM3_STOP
#define MOTORS_GPIO_TIM           TIM3
#define MOTORS_GPIO_PERIF         RCC_APB2Periph_GPIOB
#define MOTORS_GPIO_PORT          GPIOB
#define MOTORS_GPIO_M1            GPIO_Pin_1 // TIM3_CH4
#define MOTORS_GPIO_M2            GPIO_Pin_0 // TIM3_CH3
#define MOTORS_TIM_PERIF          RCC_APB1Periph_TIM3
#define M1_GPIO_IN1               GPIO_Pin_14
#define M1_GPIO_IN2               GPIO_Pin_15
#define M2_GPIO_IN1               GPIO_Pin_13
#define M2_GPIO_IN2               GPIO_Pin_12
const int MOTORS[] = { MOTOR_M1, MOTOR_M2};
static bool isInit = false;
void TIM3_GPIO_Config(void)
{
	
  GPIO_InitTypeDef GPIO_InitStructure;
	
  /* GPIOB clock enable */ 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);  
  /*GPIOB Configuration: TIM3 channel 3 and 4 as alternate function push-pull */ 
  GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_0 | GPIO_Pin_1; 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
  GPIO_Init(GPIOB, &GPIO_InitStructure); 
  GPIO_InitStructure.GPIO_Pin =  M1_GPIO_IN1 | M1_GPIO_IN2 | M2_GPIO_IN1 | M2_GPIO_IN2; 
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure); 
  GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//stop jtag,enable swd
}
void TIM3_Mode_Config(void)
{
  //Init structures
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  TIM_OCInitTypeDef  TIM_OCInitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
  //Timer configuration
  TIM_TimeBaseStructure.TIM_Period = MOTORS_PWM_PERIOD;
  TIM_TimeBaseStructure.TIM_Prescaler = 0;
  TIM_TimeBaseStructure.TIM_ClockDivision = 0;
  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  //PWM channels configuration (All identical!)
  TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
  TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
  TIM_OCInitStructure.TIM_Pulse = 0;
  TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
  //M1:TIM3_CH3N
  TIM_OC3Init(MOTORS_GPIO_TIM, &TIM_OCInitStructure);
  TIM_OC3PreloadConfig(MOTORS_GPIO_TIM, TIM_OCPreload_Enable);
  //M2:TIM3_CH4N
  TIM_OC4Init(MOTORS_GPIO_TIM, &TIM_OCInitStructure);
  TIM_OC4PreloadConfig(MOTORS_GPIO_TIM, TIM_OCPreload_Enable);
//TIM_ARRPreloadConfig(TIM3,ENABLE);
  TIM_Cmd(TIM3,ENABLE);
  TIM_CtrlPWMOutputs(MOTORS_GPIO_TIM, ENABLE);
  DBGMCU_Config(MOTORS_GPIO_TIM_DBG, ENABLE);
	
}
void motor1SetDir(int ratio){
  if(ratio < 0){
    GPIO_ResetBits(GPIOB, M1_GPIO_IN1); 
    GPIO_SetBits(GPIOB, M1_GPIO_IN2);
    return;
  }
  GPIO_ResetBits(GPIOB, M1_GPIO_IN2); 
  GPIO_SetBits(GPIOB, M1_GPIO_IN1);
  return;
}
void motor2SetDir(int ratio){
  if(ratio < 0){
    GPIO_ResetBits(GPIOB, M1_GPIO_IN1); 
    GPIO_SetBits(GPIOB, M1_GPIO_IN2);
    return;
  }
  GPIO_ResetBits(GPIOB, M2_GPIO_IN2); 
  GPIO_SetBits(GPIOB, M2_GPIO_IN1);
  return;
}
/* Public functions */
//Initialization. Will set all motors ratio to 0%
void motorsInit(void)
{
  if (isInit)
    return;
	
  TIM3_GPIO_Config();
  TIM3_Mode_Config();
  isInit = true;
}
//-2047 <=  ration <= 2047
void motorsSetRatio(int id, int ratio)
{
  switch(id)
  {
    case MOTOR_M1:
      motor1SetDir(ratio);
      TIM_SetCompare4(TIM3, ratio);
      break;
    case MOTOR_M2:
      motor2SetDir(ratio);
      TIM_SetCompare3(TIM3, ratio);
      break;
  }
}
// FreeRTOS Task to test the Motors driver
void motorsTestTask(void* params)
{
  //Wait 3 seconds before starting the motors
  vTaskDelay(3000);
	
  motorsSetRatio(MOTOR_M1, 100);
  motorsSetRatio(MOTOR_M2, 100);
  while(1)
  {
    printf("This is in motorsTestTask\r\n");
    vTaskDelay(1000);
  }
}
	
 
					
				编码器数据采集实验
电机驱动器将两组编码器输出信号分别接到PA0、PA1和PB6、PB7接口。如下图所示,用到TIM2 的CH1、CH2,TIM4的CH1、CH2。
	

直接贴代码,初始化的部分可以直接使用。不懂得找找百度谷歌,应该不太难。程序每半秒输出一次编码器的位置。头文件仅添加了两个public函数的声明。
	
 
		 
	#include <stdbool.h>
#include "stdio.h"
/* ST includes */
#include "stm32f10x.h"
#include "encoder.h"
//FreeRTOS includes
#include "FreeRTOS.h"
#include "task.h"
#define ENCODER1_GPIO_PERIF       RCC_APB2Periph_GPIOA
#define ENCODER1_GPIO_PORT        GPIOA
#define ENCODER1_GPIO_A           GPIO_Pin_0
#define ENCODER1_GPIO_B           GPIO_Pin_1
#define ENCODER2_GPIO_PERIF       RCC_APB2Periph_GPIOB
#define ENCODER2_GPIO_PORT        GPIOB
#define ENCODER2_GPIO_A           GPIO_Pin_6
#define ENCODER2_GPIO_B           GPIO_Pin_7
#define ENCODER1_TIMER            TIM2
#define ENCODER2_TIMER            TIM4
void EncoderInit(void)
{
  
  GPIO_InitTypeDef GPIO_InitStructure;
  TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  RCC_APB2PeriphClockCmd(ENCODER1_GPIO_PERIF | ENCODER2_GPIO_PERIF,ENABLE);  
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 | RCC_APB1Periph_TIM4, ENABLE);
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Pin = ENCODER1_GPIO_A | ENCODER1_GPIO_B;
  GPIO_Init(ENCODER1_GPIO_PORT,&GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = ENCODER2_GPIO_A | ENCODER2_GPIO_B;
  GPIO_Init(ENCODER2_GPIO_PORT,&GPIO_InitStructure);
  
  /* Timer configuration in Encoder mode */
  TIM_DeInit(ENCODER1_TIMER);
  TIM_DeInit(ENCODER2_TIMER);
  TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
  TIM_TimeBaseInit(ENCODER1_TIMER, &TIM_TimeBaseStructure);
  TIM_TimeBaseInit(ENCODER2_TIMER, &TIM_TimeBaseStructure);
  TIM_EncoderInterfaceConfig(ENCODER1_TIMER, TIM_EncoderMode_TI12, 
                             TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
  TIM_EncoderInterfaceConfig(ENCODER2_TIMER, TIM_EncoderMode_TI12, 
                             TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
  //Reset counter
  ENCODER1_TIMER->CNT = 0;
  ENCODER2_TIMER->CNT = 0;
  TIM_Cmd(ENCODER1_TIMER, ENABLE);
  TIM_Cmd(ENCODER2_TIMER, ENABLE);
}
void vEncoderTestTask(void* params){
  uint16_t counter1,counter2;
  
  while(1){
    counter1 = TIM_GetCounter(ENCODER1_TIMER);
    counter2 = TIM_GetCounter(ENCODER2_TIMER);
    printf("Encoder counter %7d %7d\r\n",counter1,counter2);
    vTaskDelay(500);
  }
  
}
	
	
	
 
					
				ADC实验
该实验测量电池电压,是不可省略的一部分。由于ADC中有一项是测量STM32芯片内温度的,顺便求出温度。采用ADC1的DMA方式读取。DMA1选择通道1,如下图所示:
	 
 
至于ADC1的通道选择,查下表:
	
 
原理图中通过分压后接PA4,因此选择ADC1的通道4,通道16,通道17:
	
 
直接贴代码,照着代码比较容易理解。一次采集三个数据,采集完16组数据并通过DMA传给数组后,产生一个中断。中断处理函数对数组中的值求均值作为ADC的结果。代码应该没问题,相关函数可以直接copy & paste使用。
	
	
 
		 
	#include "stm32f10x.h"  
#include "FreeRTOS.h"  
#include "task.h"  
#include "semphr.h"  
#include "adc.h"  
  
#include "uart1.h"  
#include "Nvicconf.h"  
#include "stdio.h"  
  
#define NVIC_ADC_PRI          12  
  
// PORT A  
#define GPIO_VBAT        GPIO_Pin_4  
  
// CHANNELS  
#define CH_VBAT               ADC_Channel_4  
#define CH_VREF               ADC_Channel_17  
#define CH_TEMP               ADC_Channel_16  
#define ADC_DECIMATE_TO_BITS  12  
#define ADC_MEAN_SIZE         16  
  
#define ADC_RESOLUTION        12  
#define ADC_DECIMATE_DIVEDEND (ADC_MEAN_SIZE)  
  
#define ADC_SAMPLING_FREQ      100  
#define ADC_OVERSAMPLING_FREQ  (ADC_SAMPLING_FREQ * ADC_MEAN_SIZE)  
  
#define ADC_TRIG_PRESCALE       1  
#define ADC_TRIG_PRESCALE_FREQ  (72000000 / (ADC_TRIG_PRESCALE + 1))  
#define ADC_TRIG_PERIOD         (ADC_TRIG_PRESCALE_FREQ / (ADC_OVERSAMPLING_FREQ))  
  
#define ADC_INTERNAL_VREF   1.20  
  
/******** Types ********/  
  
  /*
typedef struct  
{  
  uint16_t vbat;  
  uint16_t vtemp;  
  uint16_t vref;  
} AdcGroup;  
  */
  
  
static bool isInit;  
volatile AdcGroup adcValues[ADC_MEAN_SIZE];  
  
xQueueHandle      adcQueue;  
  
static void adcDmaInit(void)  
{  
  DMA_InitTypeDef DMA_InitStructure;  
  
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  
  
  // DMA channel1 configuration  
  DMA_DeInit(DMA1_Channel1);  
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;  
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adcValues;  
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  
  DMA_InitStructure.DMA_BufferSize = 3*(ADC_MEAN_SIZE);  
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;  
  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;  
  DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;  
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  
  DMA_Init(DMA1_Channel1, &DMA_InitStructure);  
  // Enable DMA channel1  
  // Enable the Transfer Complete Interrupt  
  DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);  
  DMA_Cmd(DMA1_Channel1, ENABLE);  
}  
  
  
/** 
 * Decimates the adc samples after oversampling 
 */  
static void adcDecimate(AdcGroup* oversampled, AdcGroup* decimated)  
{  
  uint32_t i;  
  uint32_t sumVbat;  
  uint32_t sumVtemp;  
  uint32_t sumVref;  
  AdcGroup *adcIterator;  
  AdcGroup *adcOversampledGroup;  
  
  // Compute sums and decimate  
  
  adcIterator = oversampled;  
  sumVbat = 0;  
  sumVtemp = 0;  
  sumVref = 0;  
  for (i = 0; i < ADC_MEAN_SIZE; i++)  
  {  
    adcOversampledGroup = &((AdcGroup*)adcIterator)[i];  
    sumVbat += adcOversampledGroup->vbat;  
    sumVtemp += adcOversampledGroup->vtemp;  
    sumVref += adcOversampledGroup->vref;  
  }  
    // Decimate  
  decimated->vbat = sumVbat / ADC_DECIMATE_DIVEDEND;  
  decimated->vtemp = sumVtemp / ADC_DECIMATE_DIVEDEND;  
  decimated->vref = sumVref / ADC_DECIMATE_DIVEDEND;  
}  
  
void adcInit(void)  
{  
  
  GPIO_InitTypeDef GPIO_InitStructure;  
  ADC_InitTypeDef ADC_InitStructure;  
  NVIC_InitTypeDef NVIC_InitStructure;  
  
  if(isInit)  
    return;  
  
  // Enable TIM2, GPIOA and ADC1 clock  
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);  
    
  RCC_ADCCLKConfig(RCC_PCLK2_Div6);  
  
  GPIO_InitStructure.GPIO_Pin = GPIO_VBAT;  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;  
  GPIO_Init(GPIOB, &GPIO_InitStructure);          
  // ADC1 configuration  
  ADC_DeInit(ADC1);  
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;  
  ADC_InitStructure.ADC_ScanConvMode = ENABLE;  
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;  
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;  
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;  
  ADC_InitStructure.ADC_NbrOfChannel = 3;  
  ADC_Init(ADC1, &ADC_InitStructure);  
  
  // ADC1 channel sequence  
  ADC_RegularChannelConfig(ADC1, CH_VBAT, 1, ADC_SampleTime_239Cycles5);  
  ADC_RegularChannelConfig(ADC1, CH_TEMP, 2, ADC_SampleTime_239Cycles5);  
  ADC_RegularChannelConfig(ADC1, CH_VREF, 3, ADC_SampleTime_239Cycles5);  
  
  // Enable ADC1 external trigger  
  ADC_ExternalTrigConvCmd(ADC1, DISABLE);  
  ADC_TempSensorVrefintCmd(ENABLE);  
  
  adcDmaInit();  
  
  // Enable ADC1 DMA  
  ADC_DMACmd(ADC1, ENABLE);  
  
  // Enable ADC1  
  ADC_Cmd(ADC1, ENABLE);  
  // Calibrate ADC1  
  ADC_ResetCalibration(ADC1);  
  while(ADC_GetResetCalibrationStatus(ADC1));  
  ADC_StartCalibration(ADC1);  
  while(ADC_GetCalibrationStatus(ADC1));  
  
  // Enable the DMA1 channel1 Interrupt  
  NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;  
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_ADC_PRI;  
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
  NVIC_Init(&NVIC_InitStructure);  
  
  adcQueue = xQueueCreate(1, sizeof(AdcGroup*));  
  
  
  printf("this is in adcInit\r\n");  
  isInit = true;  
}  
  
  
float adcConvertToVoltageFloat(uint16_t v, uint16_t vref)  
{  
  return (v / (vref / ADC_INTERNAL_VREF));  
}  
  
float adcConvertToTempFloat(uint16_t v, uint16_t vref)  
{  
  return 25.0 + (1.43 - (v / (vref / ADC_INTERNAL_VREF)))*232.56;  
}  
  
  
void adcInterruptHandler(void)  
{  
  portBASE_TYPE xHigherPriorityTaskWoken;  
  AdcGroup* adcBuffer;  
  
  if(DMA_GetITStatus(DMA1_IT_TC1))  
  {  
    DMA_ClearITPendingBit(DMA1_IT_TC1);  
    adcBuffer = (AdcGroup*)&adcValues[0];  
    xQueueSendFromISR(adcQueue, &adcBuffer, &xHigherPriorityTaskWoken);  
  }  
}  
  
  
void vAdcTask(void *param)  
{  
  AdcGroup* adcRawValues;  
  AdcGroup adcValues;  
  float vbat;  
  float temp;  
  
  vTaskDelay(1000);  
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
  while(1)  
  {  
    xQueueReceive(adcQueue, &adcRawValues, portMAX_DELAY);  
    adcDecimate(adcRawValues, &adcValues);  // 10% CPU  
  
    vbat = adcConvertToVoltageFloat(adcValues.vbat,adcValues.vref);  
    temp = adcConvertToTempFloat(adcValues.vtemp,adcValues.vref);  
      
    printf("ADC %04x %04x %04x",adcValues.vbat,adcValues.vtemp,adcValues.vref);  
    printf("  %.2f   %.2f\r\n",vbat*7.73,temp);  
    vTaskDelay(5000);  
  }  
}  
	
	
 
					
				板载按键程序
板子上有个按键,连接到PB5,如图:
	 
决定通过外部中断方式检测按键操作,如果按键被按下,PB5电平被拉低,产生一个下降沿。
	 
因此,相关宏定义如下:
#define GPIO_PORT_KEY GPIOB
#define GPIO_KEY GPIO_Pin_5
#define KEY_GPIO_IRQ_SRC_PORT GPIO_PortSourceGPIOB
#define KEY_GPIO_IRQ_SRC GPIO_PinSource5
#define KEY_GPIO_IRQ_LINE EXTI_Line5
		 
	
#define KEY_EXTI_IRQn EXTI9_5_IRQn
我们使用中断服务程序:EXTI9_5_IRQHandler。
程序比较简单,简单易懂。相关的初始化函数可以直接使用,keyTask函数每隔0.1秒输出一次值,该值由按键控制。
	
 
		 
	#include "key.h"
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "uart1.h"
#define NVIC_KEY_PRI 14
#define GPIO_PORT_KEY GPIOB
#define GPIO_KEY  GPIO_Pin_5
#define KEY_GPIO_IRQ_SRC_PORT GPIO_PortSourceGPIOB
#define KEY_GPIO_IRQ_SRC  GPIO_PinSource5
#define KEY_GPIO_IRQ_LINE EXTI_Line5
#define KEY_EXTI_IRQn EXTI9_5_IRQn
static bool isInit;
uint8_t key;
void keyInit(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  EXTI_InitTypeDef EXTI_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
  key = 0x55;
  if(isInit)
    return;
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
  /* Configure PULL UP -------------------------*/
  GPIO_InitStructure.GPIO_Pin = GPIO_KEY;  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  GPIO_Init(GPIO_PORT_KEY, &GPIO_InitStructure);
  GPIO_EXTILineConfig(KEY_GPIO_IRQ_SRC_PORT,KEY_GPIO_IRQ_SRC);
  EXTI_InitStructure.EXTI_Line = KEY_GPIO_IRQ_LINE;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  NVIC_InitStructure.NVIC_IRQChannel = KEY_EXTI_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = NVIC_KEY_PRI;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  isInit = true;
}
void keyInterruptHandler(void)
{
  if(EXTI_GetITStatus(KEY_GPIO_IRQ_LINE) != RESET)
  {
    key = ~key;
    EXTI_ClearITPendingBit(KEY_GPIO_IRQ_LINE);
  }
}
void keyTask(void *param)
{
  
  while(1)
  {
    vTaskDelay(100);
    printf("key1 : %x \r\n",key);
  }
}
	
 
					
				MPU6050驱动实验
	
这个实验花了一周多的时间,总结一下,首先决定用DMP处理,这样省略了数据融合部分,且DMP的处理速度可以到200Hz,完全达到性能要求,关键还是经过DMP处理的数据结果比较精确。查找资料知道DMP的驱动有6.0版的,下载该版本驱动后,驱动的ReleaseNote说:
	
所以下载了5.1.2的驱动,发现开发者使用的驱动就是用这个驱动移植过来的。当然还有别的驱动程序,比如CrazePony四轴飞行器里,也是采用MPU6050的驱动,就和5.1.2版本的不太一样。
	    决定使用5.1.2版的驱动,一是自己根据驱动重写函数,因为自己的i2c库函数名不太一样。搞了好几天也没弄出来,只好拿作者的文件用了,把作者的i2c函数换成自己程序中使用的i2c函数。终于调试成功,测试发现每隔十几秒可能会出现一次fifo读取错误,具体函数是mpu_read_fifo_stream(),问题不大,具体原因没有细查。随着读取时间间隔的不同,出错的概率也不同,在每2ms读取一次时出错的概率是最低的,在10s以上会出一次,周期还比较固定 。
。
下面来张静止状态的输出截图,可以看出数据很稳定,误差在1/1000以内:
	
来个插图,使用原作者提供的函数,读取 数据的程序。第一个任务是每2ms读取一次DMP数据,第二个任务是每隔200ms输出一次倾角数据。
	
 
					
				蓝牙的连接实验
电路图中将蓝牙串口模块接到了USART3上,如下图所示。注意下图的UART-TX及UART-RX表示的是蓝牙模块的发送端口和接收端口,PB10为stm32的TX,接到蓝牙的RX上。
	 
 
	 
 
	
	 
 
	
只需要配置好USART3,就可以从该端口接收蓝牙的数据了,不用管蓝牙具体的工作方式及配置等,只需要使用手机客户端连接到该蓝牙模块即可。
蓝牙模块在这里就相当于两根导线,从小车的USART3连接到手机的串口上,连线的过程就是蓝牙配对连接的过程。
程序比较简单,直接贴代码,使用库函数配置,初始化的代码及中断函数可以直接使用。任务程序只是一个测试接收数据的任务。
/**
 * uart3.c - uart3 bluetooth functions
 */
#include 
#include 
/*ST includes */
#include "stm32f10x.h"
/*FreeRtos includes*/
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#define UART3_PERIF      RCC_APB1Periph_USART3
#define UART3_GPIO_PERIF RCC_APB2Periph_GPIOB
#define UART3_GPIO_TX    GPIO_Pin_10
#define UART3_GPIO_RX    GPIO_Pin_11
xQueueHandle      uart3RxQueue;
void uart3Init(void)
{
  USART_InitTypeDef USART_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;
	
  /* Enable GPIO and USART clock */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	RCC_APB1PeriphClockCmd(UART3_PERIF, ENABLE);
  GPIO_InitStructure.GPIO_Pin   = UART3_GPIO_RX;
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin   = UART3_GPIO_TX;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  USART_InitStructure.USART_BaudRate            = 9600;
  USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;
  USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits            = USART_StopBits_1;
  USART_InitStructure.USART_Parity              = USART_Parity_No ;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  USART_Init(USART3, &USART_InitStructure);
  USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
  NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  USART_Cmd(USART3, ENABLE);
}
void uart3RxHandler(void)
{
  unsigned char Uart3_Rx;
  portBASE_TYPE xHigherPriorityTaskWoken;
	
  if(USART_GetITStatus(USART3,USART_IT_RXNE))
  {
    Uart3_Rx = USART_ReceiveData(USART3);
    xQueueSendFromISR(uart3RxQueue, &Uart3_Rx, &xHigherPriorityTaskWoken);
    USART_ClearITPendingBit(USART3,USART_FLAG_TC);
		return;
  }
}
void bluetoothInit(void)
{
  uart3Init();
	uart3RxQueue = xQueueCreate(3, 1);
	printf("Bluetooth initialized \r\n");
}
void receiveBluetooth(unsigned char *ch)
{
  xQueueReceive(uart3RxQueue,ch,portMAX_DELAY);
}
void vBluetoothTestTask( void *pvParameters )
{
	unsigned char bt;
  for( ;; )
  {
		receiveBluetooth(&bt);
		printf("UART3 Received %02x\r\n",bt);
  }
}
	
	
| 有奖活动 | |
|---|---|
| 硬核工程师专属补给计划——填盲盒 | |
| “我踩过的那些坑”主题活动——第002期 | |
| 【EEPW电子工程师创研计划】技术变现通道已开启~ | |
| 发原创文章 【每月瓜分千元赏金 凭实力攒钱买好礼~】 | |
| 【EEPW在线】E起听工程师的声音! | |
| 高校联络员开始招募啦!有惊喜!! | |
| 【工程师专属福利】每天30秒,积分轻松拿!EEPW宠粉打卡计划启动! | |
| 送您一块开发板,2025年“我要开发板活动”又开始了! | |