stm32f103最少有2个AD模数转换器,每个ADC都有18个通道,可以测量16个外部和2个内部模拟量。最大转换频率为1Mhz,也就是转换时间为1us(在 ADCCLK = 14Mhz,采样周期为1.5个时钟周期时)。最大时钟超过14Mhz,将导致ADC转换准确度降低。stm32的ADC是12位精度的。
stm32的ADC转换有两种通道,规则通道和注入通道,注入通道可以抢占式地打断规则通道的采样,执行注入通道采样后,再执行之前的规则通道采样,和中断类似。本例只使用规则通道实现独立模式的中断采样,这里不再赘述两种通道区别。
stm32的ADC可以由外部事件触发(例如定时器捕获,EXTI线)和软件触发(即在配置相关寄存器时,直接开启采样)。
本例实现AD采样PB0口,使用串口输出PB0口电压值。PB0口接变阻器以改变调整电压。
效果如下:
ADValue = 1.39v
ADValue = 1.38v
ADValue = 1.40v
ADValue = 1.38v
ADValue = 1.39v
直接操作寄存器
首先需要配置ADC的时钟分频值,在RCC->CFGR的[15:14]位:
- 00:PCLK2 2分频后作为ADC时钟 01:PCLK2 4分频后作为ADC时钟
- 10:PCLK2 6分频后作为ADC时钟 11:PCLK2 8分频后作为ADC时钟
设定各通道的采样时间ADCx->SMPR,该寄存器给每个通道3位来选择8种采样周期:
- 000:1.5周期 100:41.5周期
- 001:7.5周期 101:55.5周期
- 010:13.5周期 110:71.5周期
- 011:28.5周期 111:239.5周期
采样时间算法为: (采样周期+12.5)/分频后的时钟
ADC采样得到的只是一个相对值,将 转换值/4096*参考电压 即可得到采样电压 这里的4096是因为stm32的adc为12位精度,表示参考电压时即为 2^12=4096
代码如下: (system.h 和 stm32f10x_it.h 等相关代码参照
stm32 直接操作寄存器开发环境配置)
User/main.c
01 | #include <stm32f10x_lib.h> |
02 | #include "system.h" |
03 | #include "usart.h" |
04 | #include "adc.h" |
05 | #include "stdio.h" |
06 |
07 | #define LED1 PAout(4) |
08 | #define LED2 PAout(5) |
09 |
10 | #define VREF 3.3 //参考电压 |
11 | void Gpio_Init( void ); |
12 |
13 | int main( void ) |
14 | { |
15 | u16 ADValue; |
16 | float temp; |
17 |
18 | Rcc_Init(9); //系统时钟设置 |
19 | Usart1_Init(72,9600); //设置串口时钟和波特率 |
20 |
21 | Adc1_Init(8,7); //使用8通道采样,采样时间系数为7(111),据手册可得采样时间为 (239.5+12.5)/12= 21 (us) |
22 | Gpio_Init(); |
23 |
24 | while (1){ |
25 | |
26 | ADValue = Get_Adc(ADC_1,8); |
27 | temp = ( float )VREF*(ADValue/4096); //ADC精度为12位精度,即达到 VREF电压时为 2^12 = 4096 |
28 |
29 | printf ( "\r\n ADValue = %.2fv\r\n" ,temp); |
30 |
31 | LED2 = !LED2; |
32 |
33 | delay(100000); //延时100ms |
34 |
35 | } |
36 | } |
37 |
38 |
39 | void Gpio_Init( void ) |
40 | { |
41 | RCC->APB2ENR|=1<<2; //使能PORTA时钟 |
42 | RCC->APB2ENR|=1<<3; //使能PORTB时钟 |
43 | |
44 |
45 | GPIOA->CRL&=0xFF0FFFF0; |
46 | GPIOA->CRL|=0xFF3FFFF0; // PA0设置为模拟输入,PA4推挽输出 |
47 |
48 | GPIOB->CRL&=0xFFFFFFF0; |
49 | GPIOB->CRL|=0xFFFFFFF0; // PB0设置为模拟输入 |
50 |
51 | |
52 | //USART1 串口I/O设置 |
53 |
54 | GPIOA -> CRH&=0xFFFFF00F; //设置USART1 的Tx(PA.9)为第二功能推挽,50MHz;Rx(PA.10)为浮空输入 |
55 | GPIOA -> CRH|=0x000008B0; |
56 | } |
Library/src/adc.c
01 | #include <stm32f10x_lib.h> |
02 | #include "adc.h" |
03 |
04 |
05 | //ADC1采样初始化 |
06 | //独立工作模式 |
07 | //参数说明: |
08 | // ADC_CH_x 选择使用通道(0~17),目前暂支持0~15通道 |
09 | // ADC_CH_SMP 设定采样周期(0~7) |
10 | //采样周期算法: |
11 |
12 | void Adc1_Init(u8 ADC_CH_x,u8 ADC_CH_SMP) |
13 | { |
14 | RCC -> APB2ENR |= 1<<9; //开启ADC1时钟 |
15 | RCC -> APB2RSTR |= 1<<9; //复位ADC1 |
16 | RCC -> APB2RSTR &= ~(1<<9); //ADC1复位结束 |
17 |
18 | RCC -> CFGR &= ~(3<<14); //分频因子清零 |
19 | RCC -> CFGR |= 2<<14; //设定分频因数为2,PCLK2 6分频后作为ADC时钟 |
20 |
21 | ADC1 -> CR1 &= 0xF0FFFF; //工作模式清零 |
22 | ADC1 -> CR1 |= 0<<16; //设定为独立模式 |
23 | ADC1 -> CR1 &= ~(1<<8); //非扫描工作模式 |
24 | ADC1 -> CR2 &= ~(1<<1); //关闭连续转换 |
25 |
26 | ADC1 -> CR2 &= ~(7<<17); //清除规则通道启动事件 |
27 | ADC1 -> CR2 |= 7<<17; //设定规则通道启动事件为软件启动(SWSTART) |
28 |
29 | ADC1 -> CR2 |= 1<<20; //使用外部事件触发 SWSTART |
30 | ADC1 -> CR2 &= ~(1<<11); //设置对齐模式为右对齐 |
31 |
32 | ADC1 -> SQR1 &= ~(0xF<<20); //清零规则序列的数量 |
33 | ADC1 -> SQR1 |= 15<<20; //设置规则序列的数量为16 |
34 |
35 | ADC1 -> SMPR2 &= 0x00000000; //清零通道采样时间 |
36 | ADC1 -> SMPR1 &= 0xFF000000; |
37 |
38 | if (ADC_CH_x <= 9 ){ |
39 | ADC1 -> SMPR2 |= 7<<(ADC_CH_x*3); //设置通道x采样时间,提高采样时间可以提高采样精度 |
40 | } |
41 |
42 | if (ADC_CH_x > 9 ){ |
43 | ADC1 -> SMPR1 |= 7<<((ADC_CH_x-10)*3); |
44 | } |
45 | |
46 |
47 | ADC1 -> CR2 |= 1<<0; //开启AD转换 |
48 | ADC1 -> CR2 |= 1<<3; //使能复位校准,由硬件清零 |
49 | while ((ADC1 -> CR2)& (1<<3)); //等待校准结束 |
50 | ADC1 -> CR2 |= 1<<2; //开启AD校准,由硬件清零 |
51 | while ((ADC1 -> CR2)& (1<<2)); //等待校准结束 |
52 |
53 | } |
54 |
55 | //取得数模转换的值 |
56 | //参数说明:(参数定义于adc.h) |
57 | // ADC_x (0~3),选择数模转换器 |
58 | // ADC_CH_x (0~15),选择通道 |
59 | u16 Get_Adc(u8 ADC_x,u8 ADC_CH_x) |
60 | { |
61 | u16 data = 0; |
62 |
63 | switch (ADC_x) |
64 | { |
65 | case 1 : { |
66 |
67 | ADC1 -> SQR3 &= 0xFFFFFFE0; //清除通道选择 |
68 | ADC1 -> SQR3 |= ADC_CH_x; //选择通道 |
69 | ADC1 -> CR2 |= 1<<22; //开启AD转换 |
70 | while (!(ADC1 -> SR & 1<<1)); //等待转换结束 |
71 |
72 | data = ADC1->DR; |
73 | break ; |
74 | } |
75 | case 2 : { break ;} |
76 | case 3 : { break ;} |
77 | } |
78 |
79 | return data; |
80 | } |
Library/inc/adc.h
1 | #include <stm32f10x_lib.h> |
2 |
3 | #define ADC_1 0x01 |
4 | #define ADC_2 0x02 |
5 | #define ADC_3 0x03 |
6 |
7 | void Adc1_Init(u8 ADC_CH_x,u8 ADC_CH_SMP); |
8 | u16 Get_Adc(u8 ADC_x,u8 ADC_CH_x); |
库函数操作
main.c
001 | #include "stm32f10x.h" |
002 | #include "stdio.h" |
003 |
004 |
005 | #define PRINTF_ON 1 |
006 | #define VREF 3.3 // 参考电压 |
007 |
008 |
009 | void RCC_Configuration( void ); |
010 | void GPIO_Configuration( void ); |
011 | void USART_Configuration( void ); |
012 | void ADC_Configuration( void ); |
013 |
014 |
015 | int main( void ) |
016 | { |
017 | float ADValue = 0.00; |
018 | u32 delayTime = 0; |
019 |
020 | RCC_Configuration(); |
021 | GPIO_Configuration(); |
022 | USART_Configuration(); |
023 | ADC_Configuration(); |
024 |
025 | while (1) |
026 | { |
027 | if (delayTime++ >=2000000) |
028 | { |
029 | delayTime = 0; |
030 | ADValue = VREF*ADC_GetConversionValue(ADC1)/0x0fff; |
031 | printf ( "\r\n ADValue = %.2fv\r\n" ,ADValue); |
032 | |
033 | } |
034 | } |
035 | } |
036 |
037 | |
038 | void GPIO_Configuration( void ) |
039 | { |
040 | GPIO_InitTypeDef GPIO_InitStructure; |
041 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ; |
042 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; |
043 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
044 |
045 |
046 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; |
047 | GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; |
048 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; |
049 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
050 |
051 | GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; |
052 | GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; |
053 | GPIO_Init(GPIOA , &GPIO_InitStructure); |
054 | } |
055 |
056 | void ADC_Configuration( void ) |
057 | { |
058 | ADC_InitTypeDef ADC_InitStructure; |
059 |
060 | RCC_ADCCLKConfig(RCC_PCLK2_Div4); //配置ADC时钟分频 |
061 |
062 | ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; |
063 | ADC_InitStructure.ADC_ScanConvMode = ENABLE; |
064 | ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; |
065 | ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; |
066 | ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; |
067 | ADC_InitStructure.ADC_NbrOfChannel = 1; |
068 | ADC_Init(ADC1,&ADC_InitStructure); |
069 | |
070 | ADC_RegularChannelConfig(ADC1,ADC_Channel_8,1,ADC_SampleTime_55Cycles5); |
071 | ADC_Cmd(ADC1,ENABLE); |
072 | ADC_ResetCalibration(ADC1); |
073 | while (ADC_GetResetCalibrationStatus(ADC1)); |
074 | ADC_StartCalibration(ADC1); |
075 | while (ADC_GetCalibrationStatus(ADC1)); |
076 | ADC_SoftwareStartConvCmd(ADC1,ENABLE); |
077 |
078 |
079 | } |
080 |
081 |
082 | void RCC_Configuration( void ) |
083 | { |
084 | /* 定义枚举类型变量 HSEStartUpStatus */ |
085 | ErrorStatus HSEStartUpStatus; |
086 |
087 | /* 复位系统时钟设置*/ |
088 | RCC_DeInit(); |
089 | /* 开启HSE*/ |
090 | RCC_HSEConfig(RCC_HSE_ON); |
091 | /* 等待HSE起振并稳定*/ |
092 | HSEStartUpStatus = RCC_WaitForHSEStartUp(); |
093 | /* 判断HSE起是否振成功,是则进入if()内部 */ |
094 | if (HSEStartUpStatus == SUCCESS) |
095 | { |
096 | /* 选择HCLK(AHB)时钟源为SYSCLK 1分频 */ |
097 | RCC_HCLKConfig(RCC_SYSCLK_Div1); |
098 | /* 选择PCLK2时钟源为 HCLK(AHB) 1分频 */ |
099 | RCC_PCLK2Config(RCC_HCLK_Div1); |
100 | /* 选择PCLK1时钟源为 HCLK(AHB) 2分频 */ |
101 | RCC_PCLK1Config(RCC_HCLK_Div2); |
102 | /* 设置FLASH延时周期数为2 */ |
103 | FLASH_SetLatency(FLASH_Latency_2); |
104 | /* 使能FLASH预取缓存 */ |
105 | FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); |
106 | /* 选择锁相环(PLL)时钟源为HSE 1分频,倍频数为9,则PLL输出频率为 8MHz * 9 = 72MHz */ |
107 | RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); |
108 | /* 使能PLL */ |
109 | RCC_PLLCmd(ENABLE); |
110 | /* 等待PLL输出稳定 */ |
111 | while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); |
112 | /* 选择SYSCLK时钟源为PLL */ |
113 | RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); |
114 | /* 等待PLL成为SYSCLK时钟源 */ |
115 | while (RCC_GetSYSCLKSource() != 0x08); |
116 | } |
117 | /* 打开APB2总线上的GPIOA时钟*/ |
118 | RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOB|RCC_APB2Periph_ADC1, ENABLE); |
119 |
120 | //RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2 , ENABLE); |
121 | |
122 | } |
123 |
124 | |
125 | void USART_Configuration( void ) |
126 | { |
127 | USART_InitTypeDef USART_InitStructure; |
128 | USART_ClockInitTypeDef USART_ClockInitStructure; |
129 |
130 | USART_ClockInitStructure.USART_Clock = USART_Clock_Disable; |
131 | USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low; |
132 | USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge; |
133 | USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable; |
134 | USART_ClockInit(USART1 , &USART_ClockInitStructure); |
135 |
136 | USART_InitStructure.USART_BaudRate = 9600; |
137 | USART_InitStructure.USART_WordLength = USART_WordLength_8b; |
138 | USART_InitStructure.USART_StopBits = USART_StopBits_1; |
139 | USART_InitStructure.USART_Parity = USART_Parity_No; |
140 | USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; |
141 | USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx; |
142 | USART_Init(USART1,&USART_InitStructure); |
143 |
144 | USART_Cmd(USART1,ENABLE); |
145 | } |
146 |
147 |
148 | #if PRINTF_ON |
149 |
150 | int fputc ( int ch, FILE *f) |
151 | { |
152 | USART_SendData(USART1,(u8) ch); |
153 | while (USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET); |
154 | return ch; |
155 | } |
156 |
157 | #endif |