Chapter 11 - Hongjayeong-02/Basic_SoC_implementation GitHub Wiki
NUCLEO-F439ZIμ PA1 / ADC1_IN1μ μ λ ₯λλ μλ λ‘κ·Έ μ μμ 12-bit ADCλ‘ λ³ννκ³ , κ²°κ³Όκ°μ USART3λ‘ PC terminalμ μΆλ ₯νλ€.
Analog Voltage
0 ~ 3.3 V
β
PA1
β
ADC1_IN1
β
12-bit ADC
β
0 ~ 4095
β
USART3
β
PC Terminalμ΄λ² μ€μ΅μ ν΅μ¬:
PA1 β Analog Mode
ADC1 Channel β Channel 1
Resolution β 12-bit
Mode β Continuous Conversion
Status β EOC Polling
Result β ADC1->DRCPUλ μλ λ‘κ·Έ μ μ μ체λ₯Ό μ§μ μ²λ¦¬νμ§ λͺ»νλ€.
ADCκ°:
Analog Voltage
β
Digital Numberλ‘ λ³ννλ€.
STM32F439ZI ADCλ₯Ό 12-bitλ‘ μ¬μ©ν κ²½μ°:
2^12
=
4096 levelsλ°λΌμ κ²°κ³Ό λ²μ:
0 ~ 4095μ΄λ€.
λλ΅μ μΌλ‘:
0 V
β ADC β 0
1.65 V
β ADC β 2048
3.3 V
β ADC β 4095
λ‘ μκ°ν μ μλ€.
μ΄λ² μ½λμμ μ¬μ©νλ register:
| Register | μν |
|---|---|
| GPIOA->MODER | PA1μ Analog modeλ‘ μ€μ |
| RCC->APB2ENR | ADC1 clock enable |
| ADC1->SQR3 | 첫 λ²μ§Έ λ³ν channel μ ν |
| ADC1->SQR1 | Conversion sequence length |
| ADC1->CR2 | ADC enable / continuous / conversion start |
| ADC1->SR | ADC status |
| ADC1->DR | Conversion result |
μ£Όμ bit:
CR2.ADON
β ADC Enable
CR2.CONT
β Continuous Conversion
CR2.SWSTART
β Software Start
SR.EOC
β End Of Conversion
μ΄λ² μ€μ΅:
PA1
β
ADC1_IN1μμ F411 μμ μ F439ZIμμ μ΄ μ€μ΅μ νμν PA1 β ADC1 channel 1 mappingκ³Ό κΈ°λ³Έ register programming λ°©μμ κ·Έλλ‘ μ¬μ©ν μ μμλ€.
μ¦ Ch10 UARTμ²λΌ USART/pinμ ν¬κ² portingν νμκ° μμλ€.
Project:
Inc/
βββ adc.h
βββ uart.h
βββ systick.h
Src/
βββ adc.c
βββ uart.c
βββ systick.c
βββ main.c
Ch10μ uart.c, Ch08μ systick.cλ₯Ό κ·Έλλ‘ μ¬μ¬μ©νλ€.
#ifndef ADC_H__
#define ADC_H__
#include <stdint.h>
#include "stm32f4xx.h"
/*
- Configure:
-
-
PA1
-
β
-
ADC1_IN1
-
- for analog-to-digital conversion.
*/
void pa1_adc_init(void);
/*
- Start ADC1 continuous conversion.
-
- After SWSTART is set once,
- CONT mode keeps starting new conversions.
*/
void start_conversion(void);
/*
- Wait until conversion is complete,
- then return the ADC result.
-
- Return range for 12-bit conversion:
-
-
0 ~ 4095
*/
uint32_t adc_read(void);
#endif
#include "adc.h"
/*
- ============================================================
- RCC Clock Enable Bits
- ============================================================
*/
/*
- RCC AHB1ENR bit0:
-
- GPIOA clock enable.
-
- PA1 belongs to GPIOA.
*/
#define GPIOAEN (1U << 0)
/*
- RCC APB2ENR bit8:
-
- ADC1 peripheral clock enable.
*/
#define ADC1EN (1U << 8)
/*
- ============================================================
- ADC Channel / Sequence Configuration
- ============================================================
*/
/*
- ADC channel 1.
-
- In this practice:
-
-
PA1 β ADC1_IN1
-
- SQR3 SQ1 field selects the first channel
- in the conversion sequence.
-
- Channel number = 1.
*/
#define ADC_CH1 (1U << 0)
/*
- ADC sequence length.
-
- SQR1 L[3:0]:
-
-
0000
-
- means one conversion in the sequence.
-
- Therefore:
-
-
Sequence Length = 1
*/
#define ADC_SEQ_LEN_1 0x00U
/*
- ============================================================
- ADC CR2 Bits
- ============================================================
*/
/*
- CR2 bit0:
-
- ADON = ADC ON
-
- 0 β ADC disabled
- 1 β ADC enabled
*/
#define CR2_ADON (1U << 0)
/*
- CR2 bit1:
-
- CONT = Continuous Conversion
-
- 0 β one conversion
- 1 β automatically start next conversion
*/
#define CR2_CONT (1U << 1)
/*
- CR2 bit30:
-
- SWSTART = Software Start
-
- Setting this bit starts ADC conversion
- through software.
*/
#define CR2_SWSTART (1U << 30)
/*
- ============================================================
- ADC Status Bit
- ============================================================
*/
/*
- SR bit1:
-
- EOC = End Of Conversion
-
- Hardware sets EOC when conversion finishes
- and valid data is available in DR.
*/
#define SR_EOC (1U << 1)
/*
- ============================================================
- PA1 + ADC1 Initialization
- ============================================================
*/
void pa1_adc_init(void)
{
/*
* --------------------------------------------------------
* Step 1. Enable GPIOA Clock
* --------------------------------------------------------
*
* PA1 cannot be configured until
* GPIOA receives its peripheral clock.
*/
RCC->AHB1ENR |= GPIOAEN;
/*
* --------------------------------------------------------
* Step 2. Configure PA1 as Analog Mode
* --------------------------------------------------------
*
* GPIO MODER uses 2 bits per pin.
*
* PA1:
*
* MODER<span>[3:2]</span>
*
* GPIO mode:
*
* 00 β Input
* 01 β Output
* 10 β Alternate Function
* 11 β Analog
*
* Therefore both bit2 and bit3 are set.
*/
GPIOA->MODER |= (1U << 2);
GPIOA->MODER |= (1U << 3);
/*
* The original practice code uses OR only.
*
* Result:
*
* MODER<span>[3:2]</span> = 11
*
* PA1 β Analog Mode
*
* When writing more defensive driver code,
* clear-then-set can also be used:
*
* GPIOA->MODER &= ~(0x3U << (1 * 2));
* GPIOA->MODER |= (0x3U << (1 * 2));
*
* But this Wiki keeps the original practice
* implementation above.
*/
/*
* --------------------------------------------------------
* Step 3. Enable ADC1 Clock
* --------------------------------------------------------
*
* ADC1 is connected to APB2.
*
* APB2ENR bit8:
*
* ADC1EN = 1
*/
RCC->APB2ENR |= ADC1EN;
/*
* --------------------------------------------------------
* Step 4. Select ADC Channel
* --------------------------------------------------------
*
* ADC regular sequence first conversion:
*
* SQ1 = Channel 1
*
* PA1 is connected to ADC1_IN1.
*/
ADC1->SQR3 = ADC_CH1;
/*
* --------------------------------------------------------
* Step 5. Set Sequence Length
* --------------------------------------------------------
*
* Only one ADC channel is converted.
*
* SQR1 L<span>[3:0]</span> = 0000
*
* This represents sequence length = 1.
*/
ADC1->SQR1 = ADC_SEQ_LEN_1;
/*
* --------------------------------------------------------
* Step 6. Enable ADC1
* --------------------------------------------------------
*
* CR2.ADON = 1
*
* ADC peripheral becomes active.
*/
ADC1->CR2 |= CR2_ADON;
}
/*
- ============================================================
- Start ADC Conversion
- ============================================================
*/
void start_conversion(void)
{
/*
* --------------------------------------------------------
* Step 1. Enable Continuous Conversion
* --------------------------------------------------------
*
* CONT = 1
*
* After one conversion completes,
* ADC automatically starts another conversion.
*
* Therefore SWSTART does NOT need to be issued
* every time adc_read() is called.
*/
ADC1->CR2 |= CR2_CONT;
/*
* --------------------------------------------------------
* Step 2. Start First Conversion
* --------------------------------------------------------
*
* SWSTART = 1
*
* This starts the first ADC conversion.
*
* After that:
*
* CONT mode keeps conversions running.
*/
ADC1->CR2 |= CR2_SWSTART;
}
/*
- ============================================================
- Read ADC Result
- ============================================================
*/
uint32_t adc_read(void)
{
/*
* --------------------------------------------------------
* Step 1. Wait for Conversion Complete
* --------------------------------------------------------
*
* ADC conversion takes a finite amount of time.
*
* EOC:
*
* 0 β conversion not complete
* 1 β conversion complete
*
* The CPU waits here until valid conversion
* data is ready.
*
* This is Polling / Busy Waiting.
/
while (!(ADC1->SR & SR_EOC))
{
/
* Wait for End Of Conversion.
*/
}
/*
* --------------------------------------------------------
* Step 2. Read Conversion Data
* --------------------------------------------------------
*
* ADC1->DR contains the digital conversion result.
*
* In 12-bit mode:
*
* minimum = 0
* maximum = 4095
*/
return ADC1->DR;
}
μλ³Έ Notionμλ:
uart_print_value(sensor_value);νΈμΆμ κΈ°λ‘λμ΄ μμ§λ§ uart_print_value()μ μ€μ ν¨μ λ³Έλ¬Έμ λ¨μ μμ§ μλ€.
λ°λΌμ μλμμλ κΈ°μ‘΄ uart_write_string()λ§ μ΄μ©ν΄ λμΌν μΆλ ₯μ λ§λ€ μ μλλ‘ Wikiμ© helperλ₯Ό 보μνλ€.
#include <stdint.h>
#include "adc.h"
#include "systick.h"
#include "uart.h"
/*
- ============================================================
- Wiki Supplement
- uint32_t β Decimal ASCII Output
- ============================================================
-
- The original Notion record contains:
-
-
uart_print_value(sensor_value);
-
- but its implementation body was not archived.
-
- This helper is added for the Wiki so the example
- can be built without printf().
*/
/*
- Send one unsigned integer as decimal text.
-
- Example:
-
-
value = 1234
-
- UART output:
-
-
1234
/
static void uart_write_uint32(uint32_t value)
{
/
* Maximum uint32_t decimal representation:
*
* 4294967295
*
* = 10 digits
*/
char buffer[10];
uint32_t i = 0;
/*
* Special case:
*
* Without this check, value = 0 would
* generate no digits because the while-loop below
* would never execute.
*/
if (value == 0U)
{
uart_write_string("0");
return;
}
/*
* Convert integer digits in reverse order.
*
* Example:
*
* value = 123
*
* first:
* 123 % 10 = 3
*
* second:
* 12 % 10 = 2
*
* third:
* 1 % 10 = 1
*
* buffer initially contains:
*
* '3' '2' '1'
*/
while ((value > 0U) && (i < sizeof(buffer)))
{
buffer<span>[i]</span> = (char)('0' + (value % 10U));
value /= 10U;
i++;
}
/*
* Send the buffer backwards so the digits
* appear in normal order.
*/
while (i > 0U)
{
char out<span>[2]</span>;
i--;
out<span>[0]</span> = buffer<span>[i]</span>;
out<span>[1]</span> = '\0';
uart_write_string(out);
}
}
/*
- Print ADC value in the same logical format
- recorded in the original practice:
-
-
Sensor Value: NNNN
*/
static void uart_print_value(uint32_t value)
{
uart_write_string("Sensor Value: ");
uart_write_uint32(value);
uart_write_string("\r\n");
}
int main(void)
{
/*
* Variable that stores one ADC conversion result.
*
* Expected range:
*
* 0 ~ 4095
*/
uint32_t sensor_value;
/*
* --------------------------------------------------------
* Step 1. Initialize USART3
* --------------------------------------------------------
*
* Reuse Ch10 driver:
*
* USART3
* PD8 TX
* 115200 bps
*/
uart_init();
/*
* --------------------------------------------------------
* Step 2. Initialize ADC Input
* --------------------------------------------------------
*
* PA1:
*
* GPIO Analog Mode
* ADC1 Channel 1
*/
pa1_adc_init();
/*
* --------------------------------------------------------
* Step 3. Start Continuous Conversion
* --------------------------------------------------------
*
* ADC:
*
* CONT = 1
* SWSTART = 1
*
* ADC now repeatedly converts PA1 voltage.
*/
start_conversion();
while (1)
{
/*
* ----------------------------------------------------
* Step 4. Read Latest ADC Conversion
* ----------------------------------------------------
*
* adc_read():
*
* Wait EOC
* β
* Read DR
*/
sensor_value = adc_read();
/*
* ----------------------------------------------------
* Step 5. Print ADC Value
* ----------------------------------------------------
*
* Example:
*
* Sensor Value: 2048
*/
uart_print_value(sensor_value);
/*
* ----------------------------------------------------
* Step 6. Slow Down Terminal Output
* ----------------------------------------------------
*
* ADC itself converts continuously,
* but there is no need to flood the PC terminal.
*
* Print approximately every 200 ms.
*/
systick_msec_delay(200);
}
}
pa1_adc_init()
β
βββ GPIOA Clock Enable
β
βββ PA1 β Analog Mode
β
βββ ADC1 Clock Enable
β
βββ SQ1 β Channel 1
β
βββ Sequence Length β 1
β
βββ ADON = 1κ·Έλ€μ:
start_conversion()
β
βββ CONT = 1
βββ SWSTART = 1ADC continuously converting
β
βΌ
Analog Voltage on PA1
β
βΌ
ADC1_IN1
β
βΌ
ADC Conversion
β
βΌ
EOC = 1
β
βΌ
ADC1->DR
β
βΌ
sensor_value
β
βΌ
USART3
β
βΌ
PC TerminalPA1μ ADC inputμΌλ‘ μ¬μ©ν λ:
GPIOA->MODERλ₯Ό:
11λ‘ μ€μ νλ€.
μ¦:
PA1
β Analog Modeμ΄λ€.
GPIO digital input/output logic λμ analog peripheralμ΄ pinμ voltageλ₯Ό μ¬μ©ν μ μλλ‘ νλ€.
STM32 ADCλ μ¬λ¬ analog channel μ€ μ΄λ€ κ²μ λ³νν μ§ μ νν΄μΌ νλ€.
μ΄λ² μ°κ²°:
PA1
ADC1_IN1
ADC Channel 1
λ°λΌμ:
ADC1->SQR3 = ADC_CH1;μ μ¬μ©νλ€.
ADCλ μ¬λ¬ channelμ μμλλ‘ λ³νν μλ μλ€.
μ:
Channel 1
β
Channel 3
β
Channel 7
β
...νμ§λ§ μ΄λ² μ€μ΅μ channel νλλ§ μ¬μ©νλ€.
Sequence:
SQ1
β
Channel 1
κ·Έλμ:
ADC1->SQR1 = ADC_SEQ_LEN_1;μ¦:
Sequence Length = 1λ‘ μ€μ νλ€.
ν΅μ¬:
ADC1->CR2 |= CR2_CONT;
ADC1->CR2 |= CR2_SWSTART;μ²μ:
SWSTART
β
Conversion #1Conversion #1μ΄ λλλ©΄:
CONT = 1
β
Conversion #2
β
Conversion #3
β
Conversion #4
β
...μλμΌλ‘ κ³μ μ§νλλ€.
λ°λΌμ main loopμμ λ§€λ²:
ADC1->CR2 |= CR2_SWSTART;ν νμκ° μλ€.
ν΅μ¬ μ½λ:
while (!(ADC1->SR & SR_EOC))
{
}μλ―Έ:
Conversion started
β
EOC = 0
β
CPU waits
β
Conversion complete
β
EOC = 1
β
Read DRCh10 UARTμ:
while (!(USART3->SR & SR_TXE))μ ꡬ쑰μ μΌλ‘ λ§€μ° λΉμ·νλ€.
κ³΅ν΅ pattern:
Check Status Flag
β
Not Ready?
β
Wait
β
Ready?
β
Access Data Registerμ΄κ²μ΄ bare-metal peripheral programmingμμ λ§€μ° μμ£Ό λμ€λ λ°©μμ΄λ€.
12-bit ADC:
0 ~ 4095μ μκ³Ό κ²°κ³Όμ μ΄μμ μΈ κ΄κ³λ λλ΅:
ADC Value
β
(VIN / VREF) Γ 4095μλ₯Ό λ€μ΄ VREFλ₯Ό 3.3Vλ‘ κ°μ νλ©΄:
VIN = 0 V
β 0
VIN β 0.825 V
β β 1024
VIN β 1.65 V
β β 2048
VIN β 2.475 V
β β 3071
VIN β 3.3 V
β β 4095
μ€μ μΈ‘μ κ°μ μ μ, reference, noise λ±μ λ°λΌ μ½κ° λ¬λΌμ§ μ μλ€.
λ³νμ΄ λλλ©΄ ADC hardwareκ° κ²°κ³Όλ₯Ό:
ADC1->DRμ μ μ₯νλ€.
λ°λΌμ:
return ADC1->DR;μ΄:
Analog Input
β
ADC Hardware
β
Digital Result Register
β
CPUμ λ§μ§λ§ λ¨κ³λ€.
νμ¬ μ½λλ:
while (!(ADC1->SR & SR_EOC))
{
}λ‘ CPUκ° conversion μλ£λ₯Ό κΈ°λ€λ¦°λ€.
μ¦:
ADC Working
+
CPU Waitingꡬ쑰λ€.
μ΄νμλ:
ADC Interrupt
DMAλ₯Ό μ¬μ©νλ©΄ CPUκ° μ§μ λ§€ conversionλ§λ€ κΈ°λ€λ¦¬μ§ μμλ λλ€.
νΉν Ch17 DMAμμ ADCμ μ°κ²°ν μ μλ κ΅¬μ‘°λ‘ νμ₯ κ°λ₯νλ€.
κΈ°μ‘΄ μ€μ΅ κΈ°λ‘:
text = 1420 bytes
data = 0 bytes
bss = 2560 bytesκ·Έλ¦¬κ³ μ€μ NUCLEO-F439ZIμμ build/download/runμ μννμΌλ©°, ν¬ν
μ
λ―Έν° μ
λ ₯ λ³νμ λ°λΌ UARTμ ADC κ°μ΄ λλ΅ 0 ~ 4095 λ²μμμ λ³νλ κ²μ νμΈνλ€.
make clean
makeExpected:
Build PASS
text = 1420
data = 0
bss = 2560
Debugger:
GPIOA->MODERPA1 field:
MODER[3:2]
11
Expected:
PA1 = Analog ModeνμΈ:
ADC1->SQR3
ADC1->SQR1Expected:
SQ1 = Channel 1
Sequence Length = 1νμΈ:
ADC1->CR2Expected:
ADON = 1
CONT = 1
SWSTART = conversion startedWatch:
ADC1->DR
sensor_valueν¬ν μ λ―Έν°λ₯Ό μμ§μΌ λ:
0
β
500
β
1500
β
2500
β
4095μ²λΌ κ°μ΄ λ³νλμ§ νμΈνλ€.
Expected:
Sensor Value: 213
Sensor Value: 506
Sensor Value: 1058
Sensor Value: 2037
Sensor Value: 3120
Sensor Value: 4051μ€μ κ°μ μ λ ₯ μ μμ λ°λΌ λ¬λΌμ§λ€.
images/ch11_build.png
images/ch11_adc_registers.png
images/ch11_adc_min.png
images/ch11_adc_mid.png
images/ch11_adc_max.png
images/ch11_terminal.pngκ°μ₯ μ’μ μ¦κ±°:
Potentiometer Position
+
ADC1->DR
+
UART Terminal Valueλ₯Ό κ°μ΄ 보μ¬μ£Όλ κ²μ΄λ€.
Analog Voltage
β
PA1
β
Analog Mode
β
ADC1 Channel 1
β
SWSTART
β
Conversion
β
EOC
β
DR
β
0 ~ 4095
β
UARTPA1μ ADC1_IN1λ‘ μ¬μ©νλ€.
ADC input pinμ Analog modeλ‘ μ€μ νλ€.
ADC1μ APB2 clockμ μ¬μ©νλ€.
SQR3μμ 첫 λ²μ§Έ conversion channelμ μ ννλ€.SQR1μμ sequence lengthλ₯Ό μ€μ νλ€.ADONμ ADCλ₯Ό enableνλ€.CONTλ continuous conversionμ enableνλ€.SWSTARTλ software conversion startλ€.EOCλ conversion μλ£λ₯Ό λνλΈλ€.DRμλ ADC conversion κ²°κ³Όκ° μ μ₯λλ€.12-bit ADC κ²°κ³Ό λ²μλ
0 ~ 4095λ€.μ΄λ² ꡬνμ EOC polling λ°©μμ΄λ€.
μ€μ boardμμ ν¬ν μ λ―Έν° μ λ ₯μ λ°λ₯Έ ADC κ° λ³νκΉμ§ νμΈνλ€.