안녕하세요.

 

MCU에서 인터럽트 핸들러 안에서는 동작을 최대한 짧게 가져가야 합니다.

 

그래서 USART와 같은 통신 인터페이스를 사용할때에는 Queue Buffer 구조를 사용합니다.

 

큐(Queue)는 FIFO(First-In, First-Out)로 가장 먼저 들어온 데이터가 가장 먼저 나가는 자료 구조입니다.

Queue 구조, 먼저 들어간 데이터가 가장 먼저 나온다.

 

그 중에서도 원형큐(Circular Queue) 또는 링 버퍼(Ring Buffer)는 다음과 같은 구조를 가지고 있습니다.

원형 큐 구조

 

만약 원이 다 차서 새로운 데이터가 들어오게 된다면 어떻게 될까요?

-> Data1 자리에 덮어쓰게 됩니다. 그 다음 데이터는 Data2 자리에..

 

원으로 계속 반복하게 된다고 해서 원형 큐라고 합니다.

 

원형큐를 다음과 같이 구현할 수 있습니다.

/*
*  Queue 구조 예시
*  만약 head가 tail을 역전할 경우 처리는..?
*/

#define MAX_QUEUE_SIZE		(255)

uint8_t queue_buffer[MAX_QUEUE_SIZE] = {0,};

uint8_t head;
uint8_t tail;

// push: 새로운 데이터를 Queue buffer에 담는다.
void push(uint8_t new_data)
{
  // 새로운 데이터를 queue에 넣는다.
  queue_buffer[head] = new_data;
  
  head++;
  
  // head가 queue 마지막 위치에 도달했다면, 0으로 초기화
  if (head >= MAX_QUEUE_SIZE) {
    head = 0;
  }
}

// pop: 가장 오래된 데이터를 가져온다.
uint8_t pop(void)
{
  // tail의 위치에 데이터를 가져온다
  uint8_t pop_data = queue_buffer[tail];
  
  tail++;
  
  // tail이 queue 마지막 위치에 도달했다면, 0으로 초기화
  if (tail >= MAX_QUEUE_SIZE) {
    tail = 0;
  }
  
  return pop_data;
}

uint8_t isEmpty(void)
{
  // head와 tail의 위치가 같으면 queue가 비어있음
  return head == tail;
}

 

 

위의 코드를 이용하여 USART 데이터를 처리하는 방식을 알아봅시다.

 


/* USER CODE BEGIN PFP */

uint8_t rx_data;

/*Interrupts RX Callback************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1) {
    push(rx_data);
    HAL_UART_Receive_IT(&huart1, &rx_data, 1);
  }
}

int main(void)
{
  /* USER CODE BEGIN 1 */
  uint8_t data;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  
  MX_GPIO_Init();
  MX_USART1_UART_Init();

  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, &rx_data, 1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (isEmpty() == 0) {
      data = pop();
      
      // 데이터 처리
      // ...
    }
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

상세히 살펴볼게요

HAL_UART_RxCpltCallback 함수는 Uart Rx 인터럽트가 발생하면 호출되는 함수입니다.

 

1. huart 인스턴스가 USART1로부터 Rx 인터럽트가 발생되었다면

2. rx_data를 queue buffer에 넣고

3. 1개 USART 데이터를 수신했다면 인터럽트를 발생시켜라

 

가 되겠습니다.

 

main을 분석해보면

 

1. 1개 데이터를 수신했다면 인터럽트를 발생시켜라

2. 만약 Queue Buffer에 데이터가 있다면

3. 데이터를 꺼내와라

4. - 데이터 처리 -

5. 2 반복

 

이 되겠습니다.

 

데이터 처리는 제품에 맞는 프로토콜에 따라 프로그램하면 되겠습니다.

 

 

 

이를 모듈화 하기 위해서 구조체를 사용하여 구조를 잡아주면 좋을거 같습니다.

// module_uart.h

#define MAX_BUFFER_SIZE    (255)

typedef struct{
  uint8_t head;
  uint8_t tail;
  uint8_t buffer[MAX_BUFFER_SIZE];
}uart_t;

void push(uart_t*, uint8_t);
uint8_t pop(uart_t*);
uint8_t isEmpty(uart_t*);



// module_uart.c

void init_uart(uart_t* u)
{
  u->head = 0;
  u->tail = 0;
  memset(u->buffer, 0, sizeof(u->buffer));
}

void push(uart_t* u, uint8_t data)
{
  u->buffer[u->head] = data;
  
  u->head++;
  
  if (u->head >= MAX_BUFFER_SIZE) {
    u->head = 0;
  }
}

uint8_t pop(uart_t* u)
{
  uint8_t data = u->buffer[u->tail];
  
  u->tail++;
  
  if (u->tail >= MAX_BUFFER_SIZE) {
    u->tail = 0;
  }
  
  return data;
}

uint8_t isEmpty(uart_t* u)
{
  return u->head == u->tail;
}




// main.c

#include "module_uart.h"

uint8_t rx_data;
uart_t uart;

/*Interrupts RX Callback************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1) {
    push(&uart, rx_data);
    HAL_UART_Receive_IT(&huart1, &rx_data, 1);
  }
}

void main() {
  uint8_t data;


  // ..
  
  
  init_uart(&uart);
  
  HAL_UART_Receive_IT(&huart1, &rx_data, 1);
    
  while(1) {
    if (isEmpty(&uart) == 0) {
      data = pop(&uart);
    
    }
  }
}

 

반응형

MCU: STM32F103VCT

 

STM에는 Flexible static memory controller(이하 FSMC)라고 하는 동기 / 비동기 외부 메모리를 컨트롤 하기 위한 인터페이스가 있습니다. 여기에서 외부 메모리라함은 SRAM, NOR Flash, NAND Flash 그리고 LCD 모듈들이 있습니다.

 

LCD를 제어하기 위해서는 SPI, I2C로 제어하는 직렬(serial) 방식이나 8bit 또는 16bit로 제어하는 병렬(parallel) 방식이 있습니다.

 

오늘은 병렬로 제어하는 방식을 알아봅시다.

 

병렬 인터페이스에는 Intel 사의 8080 타입, 모토로라 사의 6800 타입이 있습니다.

두 타입의 차이점은 아래와 같습니다.

 

6800타입과 8080타입의 차이점

 

FSMC를 사용해서 LCD를 제어하기 위해서는 아래와 같이 핀을 연결해야 합니다.

 

FSMC [D0:D15] = FSMC 16bit 데이터 버스

FSMC NEx       = FSMC Chip Select

FSMC NOE      = FSMC Output Enable

FSMC NWE      = FSMC Write Enable

FSMC Ax         = LCD Register와 LCD Display RAM을 선택하기 위한 Address line (0~25)

                       (LCD의 RS핀과 연결합니다)

 

다음은 8080 타입 인터페이스의 LCD를 STM의 FSMC 인터페이스와  연결하는 방법을 나타냅니다.

 

 

다음은 6800 타입 인터페이스의 LCD를 STM의 FSMC 인터페이스와 연결하는 방법을 나타냅니다.

또, LCD를 제어하기 위한 타이밍 에 대한 설명도 나타냅니다. (이 부분은 수식을 봐도 잘 이해가 안됩니다..)

  • Address setup time
  • Address hold time
  • Data setup time

 

STM32F1 시리즈에서 FSMC관련된 메모리는 아래와 같이 구성됩니다.

 

 

STM Application Note에 보면 Nor Flash/SRAM bank는 병렬 LCD 인터페이스를 제어하는데 알맞다고 설명합니다.

따라서 Bank1을 사용하게 되고 Bank1은 sub bank를 4개를 가지고 있습니다. 이 sub bank에 따라 NEx의 숫자가 정해집니다.

 

 

그리고 RS핀에 연결된 Ax핀에 따라 LCD Display RAM를 제어하기 위한 주소가 달라집니다.

위 Table 101에 작은 글씨를 보면, 16bit 메모리의 경우에 내부적으로 HADDR의 25:1 의 범위를 사용한다고 되어있습니다. 즉 Ax에 왼쪽으로 1칸 쉬프트 시킨 값을 사용합니다. 여기서 x는 0부터 시작합니다.

 

예를 들면,

NE1에 A16을 사용한다. (= FSMC bank1 NOR/PSRAM 1을 사용)

= Base Address: 0x60000000

   RAM Address: (0b0000 0000 0000 0001 0000 0000 0000 0000 << 1)

                    = 0b0000 0000 0000 0010 0000 0000 0000 0000 = 0x20000

 

 

그렇다면 코드로는 간단하게 아래와 같이 작성하게 됩니다.

// Bank1 NE1
#define TFT_LCD_REG         (*((volatile unsigned short *) 0x60000000))
// A16
#define TFT_LCD_DATA        (*((volatile unsigned short *) 0x60020000))

void Write_REG(uint16_t Reg)
{
	TFT_LCD_REG = Reg;
}

void Write_Data(uint16_t Data)
{
	TFT_LCD_D = Data;
}

 

 

다음에는 FSMC를 사용하여 SSD1963 드라이버가 내장되어있는 LCD를 제어해보도록 하겠습니다.

 

감사합니다.

 

 

 

Reference

 

TFT LCD interfacing with the high-density STM32F10xxx FSMC

STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced Arm®-based 32-bit MCUs - Reference manual

 

반응형

DAC는 ADC의 반대로, Digital값을 Analog 값으로 변환하는 모듈입니다.

 

STM32F103에는 12bit DAC 모듈을 가지고 있습니다.

0부터 Vref+ 에 인가된 전압을 4096(2^12)개로 쪼개어 출력으로 보냅니다.

 

DAC 출력 공식(출처: Reference Manual)

DACoutput:  DAC 출력

VREF: VREF+ 핀에 인가된 전압

DOR: Data Output Register로 여기에 0~4095값이 쓰여짐

 

 

Vref+ 가 3.3V일경우

3.3*1 / 4095 = 0.000806 이 됩니다.

 

즉 Digital Value 1당 0.000806V가 출력으로 나가게 됩니다.

 

 

CubeMX 설정

CubeMX에서 DAC 설정화면

 

여기서 Output Buffer는 DAC 출력핀의 버퍼를 두어 출력 임피던스를 줄이는 역할을 합니다.

 

* DAC Output Buffer를 Enable로 했을 경우, 200mV 이하로 내려가지 않는 이슈가 있습니다.

community.st.com/s/question/0D50X00009XkXzE/minimum-dac-output-voltage-on-stm32f30x

 

PA4와 PA5핀에 DAC 기능이 활성화된 화면

 

HAL

HAL 드라이버에서 초기화를 제외한 DAC에 필요한 함수는 아래와 같습니다.

HAL_StatusTypeDef HAL_DAC_Start(DAC_HandleTypeDef *hdac, uint32_t Channel);
// DAC Output Enable 함수
// hdac: dac 인스턴스
// Channel: DAC_CHANNEL_1 or DAC_CHANNEL_2

HAL_StatusTypeDef HAL_DAC_Stop(DAC_HandleTypeDef *hdac, uint32_t Channel);
// DAC Output Disable 함수
// hadc: dac 인스턴스
// Channel: DAC_CHANNEL_1 or DAC_CHANNEL_2

HAL_StatusTypeDef HAL_DAC_SetValue(DAC_HandleTypeDef *hdac, uint32_t Channel, uint32_t Alignment, uint32_t Data);
// DAC Value 설정 함수
// hadc: dac 인스턴스
// Channel: DAC_CHANNEL_1 or DAC_CHANNEL_2
// Alignment: DAC_ALIGN_12B_R or DAC_ALIGN_12B_L or DAC_ALIGN_8B_R
// data: 0 ~ 4095 값

 

Alignment 설정에 따라 데이터가 정렬되는 모습

 

사용법

 

목표: DAC 채널 1의 출력을 2V로 설정하고 싶다.

 

DOR = 2V * 4095 / 3.3V = 2481.82 ≒ 2482 

 

HAL_DAC_SetValue(&hdac1, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 2482);

HAL_DAC_Start(&hdac1, DAC_CHANNEL_1);

 

반응형

사용 MCU: STM32F103VCT

 

안녕하세요.

 

STM32F103에 있는 USB IP중 CDC(Communication Device Class)를 CubeMX로 적용하는 방법에 대해 알아봅시다.

CDC를 사용하면 Uart 핀에 Uart To Serial 모듈을 따로 쓰지 않고 바로 USB를 통해 컴퓨터와 통신 할 수 있다는 장점이 있습니다.

 

 

제가 가지고 있는 보드의 USB 회로는 아래와 같습니다.

USB 회로

컴퓨터에서 USB를 인식하려면 PA1 핀이 High 상태로 있어야 합니다.

 

USB in a NutShell - Chapter 2 - Hardware (beyondlogic.org)

 

USB in a NutShell - Chapter 2 - Hardware

Connectors All devices have an upstream connection to the host and all hosts have a downstream connection to the device. Upstream and downstream connectors are not mechanically interchangeable, thus eliminating illegal loopback connections at hubs such as

www.beyondlogic.org

 

PA11과 PA12에 연결된 저항은 22옴이 사용되었습니다.

 

 

CubeMX를 실행 시켜

 

Connectivity > USB 메뉴에 Device(FS)를 체크해줍니다.

USB 기능 ON

 

 

그러면 아래 처럼 USB_DP 핀과 USB_DM핀이 활성화 될 것입니다.

 

USB 핀이 설정된 화면

 

Middleware > USB_DEVICE 메뉴로 가서 Class For FS IP를 Communication Device Class (Virtual Port Com)을 선택해주고 GENERATE CODE를 해줍니다.

 

CDC기능 ON

 

Device Descriptor 탭으로 가면 아래 화면처럼 설정되어 있습니다.

 

VID는 Vendor ID의 약자로 제조사 아이디를 뜻합니다.

 

디폴트 설정으로 1155로 설정되어있는데

모든 VID는 usb.org에서 관리되어 있습니다. 

https://usb.org/sites/default/files/vendor_ids051920_0.pdf에서 확인해 보면

1155는 STMicroelectonics라는 것을 확인 할 수 있습니다.

 

VID를 위 리스트에 없는 숫자로 해도 정상적으로 동작합니다. (15768)

 

PID는 Product ID 로 제품 ID를 설정해 주는 파라미터입니다.

 

PA1핀도 Output으로 설정해주고 Generate Code를 해줍니다.

 

USB 송수신에 관련된 함수는 usbd_cdc_if.c 파일에 있습니다.

// usbd_cdc_if.c


// USB로부터 수신한 데이터가 있을경우 호출되는 함수 
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
	...
}


// USB로부터 데이터를 송신하기 위한 함수
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
{
	...
}

 

 

제가 가지고 있는 보드는 PA1핀을 High로 만들어줘야지 컴퓨터가 USB를 인식한다고 했습니다.

따라서 프로그램이 시작되면 PA1핀을 High로 만들어 주고 printf가 USB를 통해 출력되도록 코드를 수정해줍니다.

 

/*
  1초마다 USB로 "Hello CDC Test\r\n" 출력하기 예제

*/

// USB로 printf를 출력시키기 위한 설정 함수
int _write(int file, char *ptr, int len){
    //HAL_UART_Transmit(&huart1, (uint8_t*)ptr, len, 100);
    // USB로 ptr를 출력합니다.
    CDC_Transmit_FS(ptr, len);
    return (len);
}


int main(void)
{
  static uint32_t time = 0;
  ...
  MX_GPIO_Init();
 
  MX_USB_DEVICE_Init();
  ...
  
  // PA1 Pin High
  HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);

  while (1)
  {
    if (HAL_GetTick() - time > 1000) {
      time = HAL_GetTick();
      printf("Hello CDC Test\r\n"); 
    }
  
  }

}

 

프로그램 다운로드를 완료한 후 장치관리자에 보면 아래와 같이 USB 직렬 장치라고 인식이 됩니다.

USB를 인식한 화면

 

가지고 있는 시리얼 통신 프로그램으로 COM15에 연결하면 1초마다 Hello CDC Test 라는 메세지가 나타날 것입니다.

반응형

'MCU > STM32:HAL' 카테고리의 다른 글

[STM32F]FSMC와 LCD  (0) 2021.02.28
[STM32F]FSMC로 LCD(SSD1963) 제어하기  (2) 2021.02.07
[STM32F][HAL] USART Interrupt  (0) 2020.12.23
[STM32F][HAL] DAC (Digital to Analog Converter)  (0) 2020.12.22
[STM32F][HAL] OUTPUT COMPARE 사용하기  (0) 2020.06.01
[STM32F][HAL] HAL_Delay  (0) 2020.05.21
[STM32F][HAL] ADC - ADC Calibration  (4) 2020.05.17
[STM32F][HAL]GPIO  (0) 2020.05.10

HAL드라이버 내에 일정시간동안 지연시켜주는 HAL_Delay 함수가 있습니다.

/**
  * @brief This function provides minimum delay (in milliseconds) based
  *        on variable incremented.
  * @note In the default implementation , SysTick timer is the source of time base.
  *       It is used to generate interrupts at regular time intervals where uwTick
  *       is incremented.
  * @note This function is declared as __weak to be overwritten in case of other
  *       implementations in user file.
  * @param Delay specifies the delay time length, in milliseconds.
  * @retval None
  */
__weak void HAL_Delay(uint32_t Delay)

지연 시간은 (Delay + 1)ms입니다.

 

즉 HAL_Delay(0);을 호출하면 1ms의 딜레이가 발생됩니다. 

 

함수 내부를 보면

__weak void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  uint32_t wait = Delay;

  /* Add a freq to guarantee minimum wait */
  if (wait < HAL_MAX_DELAY)
  {
    wait += (uint32_t)(uwTickFreq);
  }

  while ((HAL_GetTick() - tickstart) < wait)
  {
  }
}

 함수가 호출된 시점부터 wait동안 while에 묶여있도록 코드가 작성되어있습니다.

 

default로 HAL 드라이버는 1ms마다 1Tick씩 증가하도록 코딩되어 있습니다.

 

또 mininum wait로 1ms(uwTickFreq)가 추가되도록 되어있어 Delay에 0을 넣으면 1ms의 딜레이가 발생되는 것 입니다.

 

HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT;  /* 1KHz */

typedef enum
{
  HAL_TICK_FREQ_10HZ         = 100U,
  HAL_TICK_FREQ_100HZ        = 10U,
  HAL_TICK_FREQ_1KHZ         = 1U,
  HAL_TICK_FREQ_DEFAULT      = HAL_TICK_FREQ_1KHZ
} HAL_TickFreqTypeDef;

 

1넣었을 때 1ms 딜레이가 걸리게 하고 싶다!

1000 넣었을 때 1초 딜레이가 걸리게 하고 싶다!

 

하시는 분은 HAL_Delay 함수를 아래처럼 따로 작성하시면 됩니다.

 

/* main.c */
void HAL_Delay(uint32_t Delay)
{
  uint32_t tickstart = HAL_GetTick();
  while ((HAL_GetTick() - tickstart) < Delay)
  {
  }
}
​

 

하지만 HAL_Delay 함수를 사용하게 되면

그 위치에서 멈춰있기 때문에 저는 잘 사용하지 않습니다.

반응형

+ Recent posts