사용환경

os: window 10

stm32CubeProgrammer: v2.8.0

사용 보드: [JK전자] Cortex-M4 STM32F407ZGT6 Multi Hit 개발보드

 

 

STM32CubeProgrammer가 나오기 전에는 STM의 시스템 부트로더를 통해 펌웨어 다운로드를 하기 위해 보통은 "Flash Loader Demonstrator" 툴을 사용 했을 것입니다.

Flash Loader Demonstrator

 

STM32CubeProgrammer가 나오면서 ST-Link는 물론, UART, USB를 통한 펌웨어 다운로드를 할 수 있게 되었습니다.

 

먼저 제가 가지고 있는 보드에 사용된 MCU는 STM32F407ZGT입니다.

데이트시트에 Boot modes를 보면 system memory에 위치한 bootloader가 지원하는 방식을 볼 수 있습니다.

STM32F407ZGT의 bootloader는 USART1, USART3, CAN2, USB를 지원한다.

 

먼저 System moemory영역으로 진입하기 위해서는 Boot0핀을 High로 Boot1핀을 Low로 설정해야합니다. 이에 대한 정보는 Reference manual에서 찾을 수 있습니다.

System memory영역에 진입하기 위해서는 BOOT1핀을 Low, BOOT0핀을 High로 설정해야한다.

 

이제 STM32CubeProgrammer를 켜고 아래 그림에 빨간영역을 UART로 설정합니다.

그러면 아래에 UART configuration이 나타나고 알맞는 Port를 설정합니다. 

여기서 Parity는 Even으로 설정합니다.

 

설정이 완료되었으면 보드에 전원을 넣고 프로그램의 Connect 버튼을 누릅니다. (연결이 잘 안되면 MCU 리셋한번 걸고 다시 시도해보시는 것을 추천드립니다)

UART로 잘 연결된 모습

 

연결이 잘 되었다면 왼쪽 2번째 메뉴버튼을 눌러 Erasing & Programming 페이지로 갑니다.

Erasing & Programming 페이지 화면

다운로드하고자 하는 File path를 설정해주고 Start Programm... 버튼을 눌러줍니다.

속도는 느리지만 다운로드가 잘 되는것을 볼 수 있습니다.

다운로드 완료 메세지

 

다운로드가 완료되면 위 그림과 같이 완료 메세지가 나타납니다.

이제 다운로드한 펌웨어를 구동시키기 위해서 앞에서 설정한 BOOT0와 BOOT1핀을 Main Flash Memory에 진입하기 위한 설정으로 바꿔줍니다.

Main Flash memory로 진입하기 위한 설정: Boot0핀을 Low로 바꾸면 됨.

 

Boot0핀을 Low로 바꾸고 MCU에 전원을 다시 인가하면 다운로드된 코드가 동작하게 됩니다.

 

반응형

안녕하세요.

 

부트로더에서 펌웨어 업데이트를 하기 위해 YMODEM 프로토콜을 사용하였습니다.

저는 STM에서 제공하는 IAP가 아니고 바람님이 작성하신 YMODEM 모듈을 사용하였습니다.

 

프로그램 작성 후 테라텀에서 YMODEM으로 파일 전송을 했는데 

첫번째 다운로드는 잘 되다가 두번째 다운로드(다른 파일)는 간혹 멈추는 현상이 있었습니다.

 

구글링을 해보니 이지훈님 블로그에서 원인을 알 수 있었습니다.

 

원인

테라텀 4.86버전 이상부터 최초 SOH 패킷을 전송한 이후에 SOH 패킷을 한번 더 전송하면서 발생되는 문제

 

해결법

1. 첫번째 수신한 데이터가 SOH이고 새로운 데이터가 SOH이면 데이터를 버리도록 코딩

2. 테라텀 버전을 4.85이하 버전 사용

 

아래는 바람님이 사용하시는 ymodem.c의 한 부분입니다.

 


bool ymodemReceivePacket(ymodem_packet_t *p_packet, uint8_t data_in)
{
  bool ret = false;

  switch(p_packet->state)
  {
    case YMODEM_PACKET_WAIT_FIRST:
      // 새로 추가된 부분 시작
      if (data_in == YMODEM_SOH && p_packet->stx == YMODEM_SOH) {
        break;
      }
      else if (data_in == YMODEM_SOH)
      // 새로 추가된 부분 끝 if (data_in == YMODEM_SOH)
      {
        p_packet->length = 128;
        p_packet->stx = data_in;
        p_packet->state = YMODEM_PACKET_WAIT_SEQ1;
      }
      if (data_in == YMODEM_STX)
      {
        p_packet->length = 1024;
        p_packet->stx = data_in;
        p_packet->state = YMODEM_PACKET_WAIT_SEQ1;
      }
      if (data_in == YMODEM_EOT)
      {
        p_packet->stx = data_in;
        ret = true;
      }
      if (data_in == YMODEM_CAN)
      {
        p_packet->stx = data_in;
        ret = true;
      }
      break;

    case YMODEM_PACKET_WAIT_SEQ1:
    
    ...
    
    
    ...
    
    ..
    
  }
}

 

 

기존 코드는 아래와 같은데 처음 데이터가 들어오면 stx에 YMODEM_SOH를 넣게 됩니다.

 

변경점을 보면 이미 위 코드에 의해 p_packet->stx가 YMODEM_SOH가 들어있기 때문에

새로 들어온 데이터도 YMODEM_SOH이면 아무 동작 안하도록 작성하였습니다.

 

이렇게 변경하니 멈추는 현상이 사라지고 다운로드가 잘 되었습니다.

 

 

 

 

감사합니다.

 

출처

stm32f103/ymodem.c at main · chcbaram/stm32f103 · GitHub

 

GitHub - chcbaram/stm32f103

Contribute to chcbaram/stm32f103 development by creating an account on GitHub.

github.com

[STM32 HAL] Flash Management + UART IAP 예제 : 네이버 블로그 (naver.com)

 

[STM32 HAL] Flash Management + UART IAP 예제

Reference: Mastering STM32 by Carmine Noviello ** 자료 해석에 오류가 있을 수 있습니다 ** [ 배...

blog.naver.com

 

반응형

IDE: STM32CubeIDE 1.6.1

STM32CubeMX v6.3.0

MCU: STM32F407ZGT

 

CubeIDE를 사용하여 DSP 라이브러리를 사용하도록 해보겠습니다.

 

STM32CubeMX를 열어 STM32F407ZGT로 선택하였습니다.

 

DSP 라이브러리를 사용하기 위해 아래 위치에 있는 "Software Packs > Select Components"를 클릭합니다.

 

Software Packs 메뉴

 

여기서 CMSIS DSP가 보입니다. Selection을 Library로 선택하고 OK를 해 줍니다.

만약 ARM.CMSIS가 설치 되어 있지 않다면 Install을 해 줍니다.

CMSIS DSP Library 선택

 

그러면 왼쪽 탭에 Software Packs 메뉴가 생기고 ARM.CMSIS.5.6.0을 클릭하여 CMSIS DSP를 체크해 줍니다.

 

CMSIS DSP 체크

 

 

클럭과 프로젝트 옵션 선택 해 주고 

 

Generate Code를 해 줍니다.

 

STM32CubeIDE로 가서 해당 프로젝트를 Import하여 프로젝트 구조를 보면 아래와 같이 Middlewares에 libarm_cortexM4lf_math.a 와 arm_math.h가 포함된 것을 확인 할 수 있습니다.

 

이제 main.c 에 #include "arm_math.h" 를 넣어주고 컴파일을 해 봅니다.

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "arm_math.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* 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();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

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

 

 

이제 STM32에서 DSP 라이브러리를 사용할 준비를 마쳤습니다.

 

프로젝트 속성에 가면 다음과 같은 부분이 설정되어 있는것을 확인 할 수 있습니다.

반응형

사용 MCU: STM32F407ZGT

사용 IDE: STM32CubeIDE

CubeMX버전: 6.0.1

사용 HAL 버전: STM32Cube FW_F4 V1.25.2

 

 

개요[1]

- Bosch 사에서 차량 내에서 호스트 컴퓨터 없이 마이크로 콘트롤러나 장치들이 서로 통신하기 위해 설계된 통신 규격

- 1993년에 처음 ISO 11898 국제 표준으로 제정

 

 

특징[2]

- Multi Master 구조

- 최대 1Mbps 통신 속도

- 최대 8byte 통신 데이터 수

- 통신 프로토콜 / 에러 처리를 하드웨어적으로 처리

- 다수의 장치(Standard: 11bit, Extended 29bit ID구분)간 통신 가능

- Twist pair wire와 차동신호를 사용하여 노이즈 환경에 강함

- 비용이 경제적

 

 

프로토콜[3]

- ISO 11898:2003 표준에서 Standard 11bit 식별자를 사용하여 125kbps ~ 1Mbps의 통신 속도를 제공합니다.

  이 표준은 나중에 Extended 29bit 식별자로 수정되었습니다.

- Standard 11bit: 2^11, 2048개의 메세지 식별자

- Extended 29bit: 2^29, 536,870,912개의 메세지 식별자

 

Standard CAN: 11-bit 식별자 Bit Fields

SOF 11-bit
Identifier
RTR IDE r0 DLC 0~8 bytes Data CRC ACK EOF IFS

SOF(Start of frame): 프레임의 시작 비트

Identifier: 메세지의 우선순위를 설정하는 11bit 식별자, 값이 낮을수록 우선 순위가 높다

RTR(single remote transmission request): 다른 노드로부터 정보가 필요할 때 정의. (Data Frame: 0, Remote Frame:1)

IDE(dominant single identifier extension): CAN 식별자 타입 정의 (Standard: 0, Extended: 1)

r0: Reserved bit

DLC(data length code): 송신하는 데이터의 byte 수로 4bit

Data: 송신 데이터로 최대 8byte

CRC(Cyclic redundancy check): 16bit checksum (15bit + 1bit 구분기호), 오류 감지를 위한 비트

EOF(End of Frame): 프레임 끝

IFS(interframe space): 7bit

 

Extended CAN: 29-bit 식별자 Bit Fields

SOF 11-bit
Identifier
SRR IDE 18-bit
Identifier
RTR r1 r0 DLC 0~8 bytes Data CRC ACK EOF IFS

SRR(substitute remote request): Extension format의 자리를 표시

IDE(identifier extension): 더 많은 식별자 비트가 뒤따른다는 것을 나타냄

r1: Reserved bit

 



회로

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

- R26, R27은 사용하고자 하는 CAN Transceiver 모듈에 따라 공급전압을 선택하기 위한 저항으로 사용되었습니다.

- R40은 임피던스 매칭을 위한 저항으로 사용되었습니다.

 

Transceiver

CAN 통신을 하기 위해서는 MCU에 CAN 핀이 있더라도 별도의 Transceiver를 달아주어야 합니다.

그 이유는 CAN 통신은 CANH와 CANL 라인으로 차동 신호로 통신을 하는데

MCU의 신호를 차동신호로, 차동 신호를 MCU가 받아들일 수 있는 디지털 신호로 변환시켜주는 역할을 하기 때문입니다. 또 정전기(ESD)같은 왜란에 의해 Transceiver가 데미지를 입어 MCU가 손상되는 것을 막아주는 역할이 될 수 있습니다.

 

종단저항

또 CANH와 CANL 양 종단에 120옴 정도의 저항을 달아주어야 합니다(거리가 가까우면 달아주지 않아도 괜찮다고 하기도 하는것 같습니다.) 120옴을 달아주어야 하는 이유는 신호 왜곡을 보호하기 위한 임피던스 매칭을 위해 ISO 11898 표준으로 정의했기 때문입니다.

 

퀀텀(Quantum, tq)[4][5]: bit time의 기본 시간 단위

* Synchronization Segment(Sync_Seg): 1 Time Quantum 길이, 다양한 버스 노드를 동기화에 사용

* Propagation Time Segment(Prop_Seg): CAN 네트워크 내에서 물리적 지연 시간을 보상에 사용

* Phase Buffer Segment 1(Phase_Seg1): 에지 위상 오류를 보상하는 데 사용 

* Phase Buffer Segment 2(Phase_Seg2): 에지 위상 오류를 보상하는 데 사용

 

 

CubeMX 설정

우선 STM32F407 데이터시트를 보면, CAN은 APB1을 사용하고 최대 42MHz의 클럭을 가지는 것을 확인할 수 있습니다.

 

 

HCLK 클럭 설정을 아래와 같이 최대로 설정하고 APB1의 클럭이 42MHz라는 것을 확인하였습니다.

 

 

 

Can을 활성화 하고 각 파라미터에 대해 알아보겠습니다.

Prescaler: 통신의 속도를 설정, (1/APB1 * Prescaler = Time Quantum, 1/42M * 16 = 380ns) 

 

 

Quanta 설정

CAN Bit Time Calculation (can-wiki.info) 사이트에 들어가서 파라미터를 얻습니다.

1. 사용 MCU 선택: ST Microelectronics bxCAN

2. Sample-Point 입력: 50~90%, (CANopen와 DeviceNet에서는 일반적으로 87.5%, ARINC 825에서는 75% 사용)

3. SJW: 1 고정

4. Baud rate 입력 (125 ~ 1000)

 

 

다음과 같은 값을 얻어 아래와 같이 입력하였습니다.

 

 

NVIC에서 CAN1에 대한 설정도 Enable 시켜줍니다.

 

 

 

프로젝트를 생성하고 다음과 같이 예제 프로그램을 작성하였습니다.

// 데이터를 송신예제

/* USER CODE BEGIN PV */
CAN_TxHeaderTypeDef   TxHeader;
uint8_t               TxData[8];
uint32_t              TxMailbox;
/* USER CODE END PV */


/* USER CODE BEGIN 0 */

// printf 를 사용하기 위한 함수
int _write(int file, char *ptr, int len)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)ptr, (uint16_t)len, 100);

	return (len);
}

/* USER CODE END 0 */


int main(void)
{
  /* USER CODE BEGIN 1 */

  /* 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_CAN1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("Start\r\n");

  /* Can Start */
  if (HAL_CAN_Start(&hcan1) != HAL_OK)
  {
    /* Start Error */
    Error_Handler();
  }

  /* Configure Transmission process */
  TxHeader.StdId = 0x321;                 // Standard Identifier, 0 ~ 0x7FF
  TxHeader.ExtId = 0x01;                  // Extended Identifier, 0 ~ 0x1FFFFFFF
  TxHeader.RTR = CAN_RTR_DATA;            // 전송하는 메세지의 프레임 타입, DATA or REMOTE
  TxHeader.IDE = CAN_ID_STD;              // 전송하는 메세지의 식별자 타입, STD or EXT
  TxHeader.DLC = 8;                       // 송신 프레임 길이, 0 ~ 8 byte
  TxHeader.TransmitGlobalTime = DISABLE;  // 프레임 전송 시작될 때 timestamp counter 값을 capture.

  /* Set the data to be transmitted */
  TxData[0] = 1;
  TxData[1] = 2;
  TxData[2] = 3;
  TxData[3] = 4;
  TxData[4] = 5;
  TxData[5] = 6;
  TxData[6] = 7;
  TxData[7] = 8;

  /* Start the Transmission process */

  if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK)
  {
	  printf("Can Send Fail\r\n");
      Error_Handler();
  }
  printf("Can Send Success\r\n");

/* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

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

 

// CAN 수신 예제

/* USER CODE BEGIN PV */
CAN_FilterTypeDef  sFilterConfig;   // 필터 설정 구조체 변수
CAN_RxHeaderTypeDef   RxHeader;
uint8_t               RxData[8];
/* USER CODE END PV */


/* USER CODE BEGIN 0 */

// printf 를 사용하기 위한 함수
int _write(int file, char *ptr, int len)
{
	HAL_UART_Transmit(&huart1, (uint8_t *)ptr, (uint16_t)len, 100);

	return (len);
}

// CAN 수신 인터럽트 콜백
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
	printf("%s\r\n", __FUNCTION__);
  /* Get RX message */
  if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK)
  {
    /* Reception Error */
    Error_Handler();
  }

  printf("StdID: %04lx, IDE: %ld, DLC: %ld\r\n", RxHeader.StdId, RxHeader.IDE, RxHeader.DLC);
  printf("Data: %d %d %d %d %d %d %d %d\r\n", RxData[0], RxData[1], RxData[2], RxData[3], RxData[4], RxData[5], RxData[6], RxData[7]);

}

// CAN Error 콜백
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan)
{
	printf("%s\r\n", __FUNCTION__);
}
/* USER CODE END 0 */


int main(void)
{
  /* USER CODE BEGIN 1 */

  /* 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_CAN1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  printf("Start\r\n");

  /* CAN Filter 설정 */
  sFilterConfig.FilterBank = 0;
  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
  sFilterConfig.FilterIdHigh = 0x0000;
  sFilterConfig.FilterIdLow = 0x0000;
  sFilterConfig.FilterMaskIdHigh = 0x0000;              // 0x00000000 = 모든 ID를 받아들이겠다
  sFilterConfig.FilterMaskIdLow = 0x0000;               
  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
  sFilterConfig.FilterActivation = ENABLE;
  sFilterConfig.SlaveStartFilterBank = 14;              // CAN2의 FilterBank시작 위치, CAN2를 사용한다면 FilterBank를 SlaveStartFilterBank보다 크게 설정해야 함.

  if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
  {
    /* Filter configuration Error */
    Error_Handler();
  }

  /* Can Start */
  if (HAL_CAN_Start(&hcan1) != HAL_OK)
  {
    /* Start Error */
    Error_Handler();
  }

  /* Activate CAN RX notification */
  if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
  {
    /* Notification Error */
    Error_Handler();
  }

  printf("Can Ready!!\r\n");

/* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

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

 

송신 쪽 디버그 창

 

수신부 쪽 디버그 창

 

 

Reference

[1] CAN 버스 - 위키백과, 우리 모두의 백과사전

[2] CAN통신사용.pdf-Realsys

[3] Introduction to the Controller Area Network - TI Application Report

[4] The Configuration of the CAN Bit Timing - BOSCH

[5] Can Bit Time Calculation

반응형

안녕하세요.

 

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

 

반응형

MCU: STM32F103VCT6

IDE: STM32CubeIDE

LCD Driver: SSD1963

 

목표: LCD화면을 1초마다 흰색 > 검정 > 파랑 > 초록 > 빨강으로  변경 시키기.

 

 

STM32F103에 있는 FSMC(Flexible static memory controller)를 사용하여 LCD를 제어해보도록 하겠습니다.

 

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

 

D[0:15] 인걸 봐서 16bit parellal 제어라는 것을 알 수 있고,

LCD RS, LCD_CS, LCD_WR, LCD_RD 핀이 있는걸 봐서는 8080 인터페이스를 쓴다는 것을 알 수 있습니다.(6800은 RD 대신 E)

 

 

CubeMX를 이용하여 아래와 같이 FSMC를 설정합니다.

 

Memory type: LCD Interface

LCD Register Select: A16

Data: 16bits

 

Address setup time i HCLK clock cycles: 0

Data setup time in HCLK clock cycles: 15

Bus turn around time in HCLK clock cycles: 0

 

제가 가지고 있는 보드는 8MHz의 크리스탈이 달려 있어 RCC 설정도 해 주고, PWM핀(PD13)을 GPIO Output으로 설정해 주고, GPIO Output level을 High로 설정했습니다.

클럭 설정도 최대 주파수로 설정해주었구요

 

프로젝트 설정과 (여기서 저는 CubeIDE로 프로젝트를 생성하였습니다.)

 

코드 제너레이터 설정까지 해 주고 GENERATE CODE 버튼을 눌러 프로젝트를 생성하였습니다.

 

 

STMCubeIDE를 켜고 fsmc.c를 켜면 아래와 같이 작성되어있습니다.

void MX_FSMC_Init(void)
{
  /* USER CODE BEGIN FSMC_Init 0 */

  /* USER CODE END FSMC_Init 0 */

  FSMC_NORSRAM_TimingTypeDef Timing = {0};

  /* USER CODE BEGIN FSMC_Init 1 */

  /* USER CODE END FSMC_Init 1 */

  /** Perform the SRAM1 memory initialization sequence
  */
  hsram1.Instance = FSMC_NORSRAM_DEVICE;
  hsram1.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
  /* hsram1.Init */
  hsram1.Init.NSBank = FSMC_NORSRAM_BANK1;
  hsram1.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;
  hsram1.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;
  hsram1.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;
  hsram1.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;
  hsram1.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW;
  hsram1.Init.WrapMode = FSMC_WRAP_MODE_DISABLE;
  hsram1.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;
  hsram1.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;
  hsram1.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;
  hsram1.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;
  hsram1.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;
  hsram1.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;
  /* Timing */
  Timing.AddressSetupTime = 0;
  Timing.AddressHoldTime = 15;
  Timing.DataSetupTime = 15;
  Timing.BusTurnAroundDuration = 0;
  Timing.CLKDivision = 16;
  Timing.DataLatency = 17;
  Timing.AccessMode = FSMC_ACCESS_MODE_A;
  /* ExtTiming */

  if (HAL_SRAM_Init(&hsram1, &Timing, NULL) != HAL_OK)
  {
    Error_Handler( );
  }

  /** Disconnect NADV
  */

  __HAL_AFIO_FSMCNADV_DISCONNECTED();

  /* USER CODE BEGIN FSMC_Init 2 */

  /* USER CODE END FSMC_Init 2 */
}

 

이 부분은 SSD1963의 데이터시트를 참조해야 합니다.

 

SSD1963 데이터시트에 있는 타이밍 특성

저희가 프로젝트에서 설정한 HCLK의 주파수는 72MHz였고 이는 13ns 입니다.

Address Setup Time, Address Hold Time 그리고 Write Data Setup Time 의 시간이 13ns보다 짧기 때문에 

아래와 같이 해당 속성을 1로 설정해주었습니다.

 

또, STM LCD 레퍼런스 메뉴얼에 AccessMode는 B로 설정한다고 해서 A도 하고 B도 해봤는데 차이는 없었습니다.

  Timing.AddressSetupTime = 1;
  Timing.AddressHoldTime = 1;
  Timing.DataSetupTime = 1;
  Timing.BusTurnAroundDuration = 0;
  Timing.CLKDivision = 16;
  Timing.DataLatency = 17;
  Timing.AccessMode = FSMC_ACCESS_MODE_B;

기본 설정은 마쳤고, LCD 제어 코드를 아래와 같이 작성했습니다.

 

주소에 대해서는 [MCU/STM32:HAL] - [STM32F]FSMC와 LCD 를 참조해 주세요

/* ssd1963.h */

#ifndef INC_SSD1963_H_
#define INC_SSD1963_H_

#define  SSD1963_LCD_PIXEL_WIDTH    ((uint16_t)480)
#define  SSD1963_LCD_PIXEL_HEIGHT   ((uint16_t)272)


// 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);
void Write_Data(uint16_t Data);
void LCD_init(void);
void LCD_Clear(uint16_t Color);

#endif /* INC_SSD1963_H_ */

 

/* ssd1963.c */

#include "main.h"
#include "ssd1963.h"

#define HDP     (479)

#define HT      (531)
#define HPS     (43)
#define LPS     (8)
#define HPW     (10)

#define VDP     (271)
#define VT      (288)
#define VPS     (12)
#define FPS     (4)
#define VPW     (10)

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

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


void LCD_init(void)
{

	Write_REG(0x00E2);	              //PLL multiplier, set PLL clock to 120M
	Write_Data(0x0023);               //N=0x36 for 6.5M, 0x23 for 10M crystal
	Write_Data(0x0002);
	Write_Data(0x0004);

	Write_REG(0x00E0);                // PLL enable
	Write_Data(0x0001);

	HAL_Delay(10);

	Write_REG(0x00E0);
	Write_Data(0x0003);

	HAL_Delay(10);

	Write_REG(0x0001);                // software reset

	HAL_Delay(10);

	Write_REG(0x00E6);	              //PLL setting for PCLK, depends on resolution
	Write_Data(0x0001);
	Write_Data(0x0033);
	Write_Data(0x0032);

	Write_REG(0x00B0);	              //LCD SPECIFICATION
	Write_Data(0x0018);
	Write_Data(0x0000);
	Write_Data((HDP >> 8) & 0x00FF);  //Set HDP
	Write_Data(HDP & 0x00FF);
	Write_Data((VDP >> 8) & 0x00FF);  //Set VDP
	Write_Data(VDP & 0x00FF);
	Write_Data(0x0000);

	Write_REG(0x00B4);	              //HSYNC
	Write_Data((HT >> 8) & 0x00FF);   //Set HT
	Write_Data(HT & 0x00FF);
	Write_Data((HPS >> 8) & 0x00FF);  //Set HPS
	Write_Data(HPS & 0x00FF);
	Write_Data(HPW);			      //Set HPW
	Write_Data((LPS >> 8) & 0x00FF);  //Set HPS
	Write_Data(LPS & 0x00FF);
	Write_Data(0x0000);

	Write_REG(0x00B6);	              //VSYNC
	Write_Data((VT >> 8) & 0x00FF);   //Set VT
	Write_Data(VT & 0x00FF);
	Write_Data((VPS >> 8) & 0x00FF);  //Set VPS
	Write_Data(VPS & 0x00FF);
	Write_Data(VPW);			      //Set VPW
	Write_Data((FPS >> 8) & 0x00FF);  //Set FPS
	Write_Data(FPS & 0x00FF);

	// 16bit per pixel
	Write_REG(0x0036);                //rotation
	Write_Data(0x0050);

	// rgb565 format
	Write_REG(0x00F0);                //pixel data interface
	Write_Data(0x0003);

	Write_REG(0x0021);

	Write_REG(0x00BC);
	Write_Data(0x0080);
	Write_Data(0x0080);
	Write_Data(0x0080);
	Write_Data(0x0001);

	HAL_Delay(5);

	Write_REG(0x0029); //display on
}

// LCD Window 범위 설정
void ssd1963_SetDisplayWindow(uint16_t Xpos, uint16_t Ypos, uint16_t Width, uint16_t Height)
{
	uint8_t data = 0;
	/* Column addr set, 4 args, no delay: XSTART = Xpos, XEND = (Xpos + Width - 1) */
	Write_REG(0x002A);
	data = ((Xpos) >> 8) & 0xFF;
	Write_Data(data);
	data = (Xpos) & 0xFF;
	Write_Data(data);
	data = ((Xpos + Width - 1) >> 8) & 0xFF;
	Write_Data(data);
	data = (Xpos + Width - 1) & 0xFF;
	Write_Data(data);
	/* Row addr set, 4 args, no delay: YSTART = Ypos, YEND = (Ypos + Height - 1) */
	Write_REG(0x002b);
	data = ((Ypos) >> 8) & 0xFF;
	Write_Data(data);
	data = (Ypos) & 0xFF;
	Write_Data(data);
	data = ((Ypos + Height - 1) >> 8) & 0xFF;
	Write_Data(data);
	data = (Ypos + Height - 1) & 0xFF;
	Write_Data(data);
}

// Horizontal Line 그리기 함수
void ssd1963_DrawHLine(uint16_t RGBCode, uint16_t Xpos, uint16_t Ypos, uint16_t Length)
{
	uint16_t counter = 0;

	if(Xpos + Length > SSD1963_LCD_PIXEL_WIDTH) return;

	/* Set Cursor */
	ssd1963_SetDisplayWindow(Xpos, Ypos, Length, 1);

	Write_REG(0x002c);

	for(counter = 0; counter < Length; counter++)
	{
		Write_Data(RGBCode);
	}
}

// 전체 화면을 Color로 덮어쓰기
void LCD_Clear(uint16_t Color)
{
  uint32_t counter = 0;

  for(counter = 0; counter < SSD1963_LCD_PIXEL_HEIGHT; counter++)
  {
	  ssd1963_DrawHLine(Color, 0, counter, SSD1963_LCD_PIXEL_WIDTH);
  }
}

/* main.c */
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"
#include "fsmc.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1963.h" // ssd1963 헤더 추가
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* 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_FSMC_Init();
  /* USER CODE BEGIN 2 */

  LCD_init(); // LCD 초기화

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  //White
	  LCD_Clear(0xFFFF);
	  HAL_Delay(1000);
	  //Black
	  LCD_Clear(0x0000);
	  HAL_Delay(1000);
	  //Blue
	  LCD_Clear(0x001F);
	  HAL_Delay(1000);
	  //Green
	  LCD_Clear(0x07E0);
	  HAL_Delay(1000);
	  //Red
	  LCD_Clear(0xF800);
	  HAL_Delay(1000);

  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

 

이제 프로그램을 MCU에 다운로딩하면 1초마다 흰색 > 검정색 > 파랑색 > 초록색 > 빨간색으로 화면이 바뀌는것을 확인 할 수 있습니다.

 

Reference

 

TFT LCD interfacing with the high-density STM32F10xxx FSMC

Microsoft Word - SSD1963_1.3.doc (displaytech-us.com)

[MCU/STM32:HAL] - [STM32F]FSMC와 LCD

LCD Init 참조 코드

반응형

사용 MCU : STM32F103VCT

 

Uart를 사용하기 위해서 CubeMX에서 USART1을 아래와 같이 활성화 시킵니다.

 

통신속도는 115200 Bits/s로 설정합니다. 

 

아래와 같이 USART TX RX 핀이 활성화 됩니다.

 

 

인터럽트 설정을 하기 위해서 NVIC에서 USART1 global interrupt Enabled에 체크합니다.

 

 

 

 

UART 관련 함수

// Uart 송신 인터럽트 함수: Uart Tx로 데이터를 Size만큼 전송하면 인터럽트가 발생한다.
// huart: uart 인스턴스
// pData: 송신 데이터 버퍼
// Size: 송신 데이터 개수
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);


// Uart 수신 인터럽트 함수: Uart Rx로 데이터가 Size만큼 들어오면 인터럽트가 발생한다.
// huart: uart 인스턴스
// pData: 수신 데이터 버퍼
// Size: 수신 데이터 개수
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

 

UART Interrupt예제

/*
  Uart Interrupt Echo 예제
  
  인터럽트로 수신한 Uart 데이터를 전송한다.
  
  main.c
*/


uint8_t rx_data;


// 인터럽트 콜백 함수: 인터럽트가 발생되면 이 함수가 호출된다.
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
	if (huart->Instance == USART1) {
        // 데이터 1개를 수신하면 인터럽트를 발생시킨다.
		HAL_UART_Receive_IT(&huart1, &rx_data, 1);
        
        // 받은 데이터를 전송한다.
		HAL_UART_Transmit(&huart1, &rx_data, 1, 10);
	}
}



void main ()
{

  /* USER CODE BEGIN 1 */

  /* 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();

  /* Initialize interrupts */
  MX_NVIC_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)
  {
    /* USER CODE END WHILE */

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

 

예제에서는 간단하게 인터럽트 수신을 받으면 바로 송신하도록 하였지만,

 

실제 운용할 때에는 Queue Buffer를 구성하고 main 에서 데이터를 빼내어 사용하여야 합니다.

 

[MCU/STM32:HAL] - [HAL][STM32F]USART와 Queue

반응형

+ Recent posts