반응형

 

여기서는 STM32CubeMX 5.6.1 버전을 기준으로 할 것이다.

(어짜피 버전이 달라도 UI 또는 편의성 정도의 차이일 것이기에 크게 문제될 것은 없을 것 같다.)

 

MCU는 STM32103C8Tx(LQFP 48) 기준으로 작업할 것이다.

이것 또한 MCU가 다르다고 크게 다를 것은 없다.

 

여기서 MCU의 10번 핀을 PWM 핀으로 사용해볼 것이다.

데이타시트에 보면 확인할 수 있듯이 LQFP48에 대한 10번은 TIM2_CH1 기능을 사용할 수 있도록 되어있다.

타이머 채널들은 모두 PWM이 사용 가능하다고 보면 된다.

이제 STM32CubeMX를 실행하여 MCU의 기본적인 세팅을 해준다.(크리스탈, 디버거 설정 등)

나의 경우 8MHZ 크리스탈이 달려있고 ST-LINK를 사용할 것이기에 RCC_OSC 설정을 해주었고,

PA13, PA14쪽의 SWCLK와 SWDIO 설정도 같이 해주었다.

 

이제 10번 핀(PA0)을 PWM 핀으로 설정해보도록 하겠다 해당 핀을 클릭하면 아래와 같은 메뉴가 뜬다.

여기서 TIM2_CH1을 클릭한다.

이와 같이 노란색으로 바뀌었으며 이 상태는 핀을 사용하겠다고는 했지만 디테일한 세팅이 되어있지 않은 것이다.

위와 같이 TIM2에서 Channel1을 선택한 후 PWM Generation CH1을 선택해준다.

 

이와 같이 설정이 완료된 상태로 변경되었다.

이제 가장 중요한 설정을 할 차례다.

원하는 PWM 주파수를 만들기 위해 Prescaler와 주기 값 등을 지정해주어야 한다.

아래 파라미터 설정을 보고 설명하도록 하겠다.

 

가장 중요한 세가지 값이 있다.

Prescaler와 Counter Period로 PWM 주기를 지정하고 Pulse 값으로 Duty를 지정할 수 있다.

먼저 주기를 설정하기 위해서는 나의 타이머가 얼마의 클럭으로 돌아가는가를 알아야 한다.

 

STM32CubeMX의 Clock Configuration 탭을 확인하면 다음과 같이 클럭을 확인할 수 있다.

위와 같이 APB1 Timer clocks과 APB2 timer clocks를 확인하면 된다.

자신이 어떤 타이머를 사용할지에 따라 위, 아래의 클럭 중 어떤 것을 사용하는지가 결정된다.

이 또한 아래의 데이타시트 Clock tree에서 확인할 수 있다.

이는 MCU마다 차이가 있을 수 있으므로 반드시 Datasheet를 확인해야 한다.

TIM1은 APB2를 쓰고 TIM2,3,4는 APB1을 쓴다. 

나는 여기서 TIM2_CH1을 쓸 것이니 APB1 클럭만 신경쓰면 된다.

 

200hz의 50% Duty의 PWM 파형을 만들어보도록 하겠다.

 

Prescaler = 799

Counter Period = 49

PWM Pulse = 99 

이렇게 지정하였다.

여기서 각 값들이 800, 50, 100도 아니고 1씩 떨어져있는 것을 볼 수 있는데,

이것은 각 값들이 0부터 시작하므로 실제 적용할 값에서 -1을 해주어야 한다.

이제 위 값들이 어떻게 계산되는지 알아보도록 하겠다.

 

APB Clock 이 8Mhz였으므로, 먼저 해당 클럭이 프리스케일링된다.

8,000,000 / 800 (Prescaler) = 10,000 Hz(10Khz)

 

여기서 Counter Period에 의해 200Hz로 조정된다.

10,000 Hz / 50 (Counter Priod) = 200Hz

 

이제 200Hz 주기인 PWM의 Duty를 50% 로 지정한다.

200 / 100 (Pulse) = 2 (50% Duty)

 

CubeMX 에서의 모든 설정은 마쳤다고 볼 수 있다.

여기서 모두 값을 지정해주었지만, 얼마든지 코드 상에서 값을 수정할 수도 있다.

이제 우측 상단의 GENERATE CODE를 눌러서 코드를 생성하고 해당 코드를 불러오도록 한다.

PWM 파형을 동작시키는 코드를 작성해보겠다.

 

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); 한줄만 추가해주면 되므로 매우 간단하다. 

코드를 보면 유추할 수 있듯이 TIM2의 CH1의 PWM을 동작하도록 하는 코드이다.

이제 컴파일 후에 실행하여 결과를 확인해보면 정상적으로 PWM 파형이 발생하고 있을 것이다.

(참고로 Unknown Target 과 같은 컴파일 오류가 발생한다면 프로젝트 옵션에서 Target MCU 설정을 해주어야 한다.)

반응형
반응형

이 포스트는 STM32 칩을 쓰시는 분들 중 IAR을 사용하시는 분들에게 해당되는 내용이다.

 

위와 같은 오류를 한번쯤 경험했을 것이다.

 

일반적으로는 ST-LINK와 MCU간의 핀 맵핑이 잘못되어있거나,

MCU가 불량이거나, 전원이 제대로 들어가지 않거나 하는 등의 내용이 원인이다.

 

하지만 나의 경우는 더 난감한 경우를 경험하였다.

잘못된 코드를 다운로드하여 ST-LINK와 MCU와 연결을 할 수 없는 상태가 발생할 수 있는데

바로 이 상황을 겪었다.

 

문제의 코드는 바로 위의 '_HAL_AFIO_REMAP_SWJ_NOJTAG();'이다.

이것은 해당 ST-LINK가 다운로드 전용으로 사용하는 핀을 리맵핑하여

GPIO 등 다른 목적으로 쓰게 만들어준다.

하지만, 문제는 이렇게 하는 즉시 그 이후로는 다운로드 핀으로는 사용할 수 없게 되어버린다.

그 이후로는 맨 위의 디바이스를 찾을 수 없다는 메세지만 뜨게 된다.

해당 다운로드 핀을 반드시 써야하는게 아니라면 위의 코드는 쓰지 않는 것을 추천한다.

 

나는 보통 초기 MCU PIN 세팅 및 기본적인 세팅은 STM32CubeMX을 통해 코드화한다.

(STM32 칩을 쓰는 개발자라면 대부분이 이용할 것이다.)

나는 STM32CubeMX에서 각 기능을 세팅할 때 SYS의 Debug에 대해 관심을 갖지 않았었다.

하지만 저 부분을 Default인 'No Debug'로 두면 JTAG 혹은 ST-LINK와 같은 디버깅 툴을 사용하지

않는 것으로 간주하고, _HAL_AFIO_REMAP_SWJ_NOJTAG(); 코드를 생성해버린다.

 

위와 같이 ST-LINK를 사용한다면 Serial Wire, 그 외의 툴을 사용한다면 그에 맞게 지정하면 된다.

그리하면 위와 같은 코드가 생기지 않으며, 디버깅 툴을 문제없이 사용할 수 있게 된다.

 

이제 이미 잘못된 코드를 실수로 다운로드하여 더 이상 디버깅이 되지 않는 경우를

복구하는 방법을 설명할 것이다.

 

데이타시트를 보면 위와 같은 내용이 있다.

일반적으로 BOOT0을 GND에 붙여 사용하는데, BOOT0을 강제로 HIGH를 주고 전원을 넣을 경우

MCU는 메인 프로그램을 실행하지 않는다. 이 때는 디버깅 툴의 사용이 가능해지기 때문에,

해당 코드를 수정한 상태로 다운로드하고 원상복귀 시켜주면 해결이 된다.

 

이 외에도 Flash Loader를 이용해서 펌웨어를 다시 심는 방법도 있는데,

위 방법이 가장 간단한 것 같다.

반응형
반응형

TW2836 칩은 4채널 비디오 제어가 가능한 IC이다. 가격도 13불이나 된다.

 

4개 채널을 입력 받아서 한 채널로 출력을 내보내주는데 4CH 블랙박스를 예로 들 수 있다.

 

LCD에 4채널 비디오를 화면에 4분할해서 표시하는 역할을 이 IC가 하게 된다.

 

급히 ABOV MCU로 해당 칩을 제어할 일이 생겼었는데, 해당 칩의 내장된 I2C 기능을 쓰기에는 시간도 부족하였고 스터디가 안된 상태여서 그냥 GPIO I2C로 제어를 해보았다.

 

I2C는 비교적 SPI 통신보다는 훨씬 간단하다고 생각된다.. 그리고 클럭 주파수가 정확히 안맞더라도 알아서 잘 동작하는 경우가 많다.

 

대부분 I2C 통신 규격이 비슷하지만 칩마다 타이밍차트가 조금씩 다르고 프로토콜도 조금씩 다르므로 데이타시트를 유심히 봐야한다.

 

 

 

위는 TW2836 칩의 I2C 프로토콜이다.

 

내가 이 칩을 컨트롤하면서 좀 이상하다고 생각되는 부분이 READ 프로토콜쪽이다.

 

READ 프로토콜을 보면 DATA를 2바이트씩 읽을 수 있게 되어있다.

 

그래서 난 당연히 앞의 DATA를 상위 8비트에 넣고 뒤의 DATA를 하위 8비트에 넣어서 16비트 데이타로 받았는데,

 

값은 이상하게 출력되었다. 그래서 오실로스코프로 확인해본 결과 데이타가 8비트로 들어오는 것이었다...

 

확인안해보았으면 삽질만 하고 있을 뻔 했다. 데이타시트가 잘못된 것인지, 다른 의도가 있는 건지는 모르겠다..

 

결국은 앞의 DATA 후 ACK 체크하는 부분을 빼버리고, DATA 후 NACK만 체크하여 READ를 구현하니 잘 되었다.

 

 

 

위 이미지는 타이밍차트이다. 통신 프로토콜을 맞게 보내더라도 위 타이밍이 안맞으면 칩은 응답하지 않는다.

 

보통은 대충 딜레이 넣어서 통신해도 다 타이밍 내에 들어오기 때문에 문제될 일은 드문데 그래도 마이컴의 동작주파수 등 여러 조건을 잘 따져보아야 삽질을 방지할 수 있다.

 

아래는 GPIO I2C로 제어한 코드이다.

 

void i2c_init(void)
{
	P0IO |= 0xC0;  //Set SCL, SDA to output
	SCL = 1;  // Set SCL, SDA High
	SDA = 1;
}

void i2c_start(void)
{
	//start
	P0IO |= 0xC0;  //Set SCL, SDA to output
	SDA = 1;
	SCL = 1;  // Set SCL, SDA High
	Delay_us(7);

	SDA = 0;	// Clear SDA
	Delay_us(7);
 	SCL = 0;	// Clear SCL
	Delay_us(7);
}

void i2c_stop(void)
{
	P0IO |= 0x80;	// Set SDA to output
	SDA = 0;	// Clear SDA Low
	Delay_us(7);

	SCL = 1;	// Set SCL High
	Delay_us(7); 
	SDA = 1; // Set SDA High

 	P0IO &= (~0xC0);	// Set SDA to Input
}

void write_i2c_byte(unsigned char byte)
{
	unsigned char i = 0;

	P0IO |= 0x80;		// Set SDA to output
	
	for (i = 0; i < 8 ; i++)
	{
		if((byte & 0x80)==0x80)	SDA = 1;	// Set SDA High
		else					SDA = 0;	// Clear SDA Low

		SCL = 1;		// Set SCL High, Clock data
		_nop_();
		byte = byte << 1;	// Shift data in buffer right one
		SCL = 0;		// Clear SCL
		_nop_();
	}
	SDA = 0; //listen for ACK
	P0IO &= (~0x80);

	SCL = 1;		
	_nop_();_nop_();
	SCL = 0; 
	_nop_();_nop_(); //Clear SCL.
	P0IO |= 0x80;		// Set SDA to Output
}

unsigned char read_i2c_byte(unsigned char ch)
{
	unsigned char i, buff=0;

	P0IO &= (~0x80);	// Set SDA to input

	for(i=0; i<8; i++)
	{
		_nop_();_nop_();
		SCL = 1;	
		_nop_();_nop_();// Set SCL High, Clock bit out
		buff <<= 1;

		// Read data on SDA pin
		if ((P0&0x80) == 0x80) {
			buff |= 0x01;
		}
		SCL = 0; // Clear SCL
		_nop_();_nop_();
	}
   	if(ch == 0) //ACK
	{
		SDA = 1; //SDA HIGH.
	}
	else //NACK.
	{
		SDA = 0; //SDA LOW.
	}
	SCL = 1;		
	_nop_();_nop_();
	SCL = 0; //SCL LOW.
	SDA = 1; //SDA HIGH.
	_nop_();_nop_();
	P0IO |= 0x80;	// Set SDA to Output

	return buff;
}

unsigned int read_word(unsigned char slave, unsigned char page_address, unsigned char index_address)
{
	unsigned int temp;
	unsigned char ucHibyte = 0;
	unsigned char ucLobyte = 0;
	unsigned char ucNack = 0;

	ucHibyte=0;
	ucLobyte=0;

	i2c_start();
	write_i2c_byte(slave);
	write_i2c_byte(page_address);
	write_i2c_byte(index_address);

	i2c_start();
	write_i2c_byte(slave+1);

	ucLobyte=read_i2c_byte(1);
	//ucHibyte=read_i2c_byte(1);
	
	i2c_stop();

   	temp = (ucHibyte<<8) + ucLobyte;

	return temp;
}

void write_word(unsigned char slave, unsigned char page_address, unsigned char index_address, unsigned char value)
{
	i2c_start();
	write_i2c_byte(slave);
	write_i2c_byte(page_address);
	write_i2c_byte(index_address);
	write_i2c_byte(value);
	i2c_stop();
}

 

 

 

반응형

+ Recent posts