Logic analyzer – do we need it?

Logic analyzer and its use

When programming any MCU, no matter Arduino IDE, Keil, Atmel studio or other compilers and assemblers, many times we got intro problem not knowing what happening on our data transfer from and to MCU. For example, we want to use I2C protocol for communicating with some sensor, but it does not respond. Many things can be wrong, and without logic analyzer, we just don’t know whether our code is wrong, the sensor is broken, or maybe something else.

Cheap Chinese clone or professional one?

Good question, depend of many factors. Do you use it for your work, or do you program for fun? I am using for my personal projects, and this one that cost less than $50 US.:

It works fine, but there is limit in speed, especially with more than one channel. It is USB 2.0 product and has no analog part as it has original Saleae Logic analyzer. It is too much expensive for my pocket, especially because I am not professional. I just have dream that it will be mine one day, but until then… Anyway, here is video for anyone who are interested and does not know what I am talking about:

Recently I had problem programming STM32 and connecting OLED display. Despite spending hours googling what exact command should be implemented, and reading their documentation, I was not able to get picture on the OLED display. Then got idea – programmed Atmega 328p with Arduino example, then put logic analyzer to I2C bus, and got the codes for initialization of the display.

Another example is when I wanted to know toy grade quadcopters, how transmitter and receiver know where to ‘jump’ by using ‘frequency hopping’. Using SPI protocol and Logic analyzer, I was able to see exactly what is ‘the secret’. Aside many bytes that transmitter sending for moving flying device up/down, left and right, there is one byte that constantly changes even when remote sticks are still – that is information for next channel. So, TX sending information on current channel, then last bit is channel number for next transmission. Whole protocol is made so that if it goes briefly out of range, or missing one packet due to noise, receiver back to original ‘calling’ channel, and in the same time transmitter sending on this channel periodically next possible channel where will be next time. That is so cool to know.

STM32 example of DSP, ADC and DAC

DSP possible on small MCU?

Yes, DSP (Digital Signal Processing) is possible with some speed limitations. For example, if FIR filter (Finite Impulse Response) has too much taps, whole loop process will be slow, and sampling ratio depends strongly of number of those elements. Out there exist specialized MCUs with additional hardware for floating point calculation (FPU), but our STM 32 or whatever MCU you are using, can do DSP.

First, we need to find some math to calculate ‘taps’, you may use your GNU radio companion for that, or some free online calculators as is this one (really simple): http://t-filter.engineerjs.com/

Just set parameter of wanted frequency filtering, and on the right side you have two choices: plain text or C/C++ code. Chose code and copy/paste into my codes in ‘coefficients.h’ file, replacing old ones (or just comment old one with ‘//’. Change “static double filter_taps[FILTER_TAP_NUM] = {…” into “static const float taps[] = {….”, that is because it is intended for PC and other high frequency CPUs instead our MCUs. Name in ‘[]’ square brackets is defined above by “#define FILTER_TAP_NUM”, so leave those brackets empty, else compiler may complain about re-definition.

The codes:

#include "stm32f10x.h"
#include "coefficients.h"
#include "pwm_as_dac.h"
#include "adc.h"

float y=0.0;
int x[FILTER_TAP_NUM ];
int adc_value=0;

int main(void)
{
	ADCenable();
	timer2enablePWM();

	while(1)
	{
		y=0;
		for(uint16_t i=0;i<FILTER_TAP_NUM ;i++)
		{
			y=y+x[i]*taps[FILTER_TAP_NUM  - i - 1];
		}
		for(uint16_t i=0;i<FILTER_TAP_NUM -1;i++)
		{
			x[i]= x[i+1];
		}
		x[FILTER_TAP_NUM -1]= adc();

		dac((int)y/4-200);
	}
}
#ifndef coeficients_h
#define coeficients_h

// Define FILTER_TAP_NUM 33	
//static const float h[] = {4.684409422081195e-18, -0.005663635209202766, 6.734740765645466e-18, 0.011587434448301792, -1.2573590333975574e-17, -0.02472740039229393, 2.1312048747716416e-17, 0.04784134402871132, -3.1619763082060183e-17, -0.08602967858314514, 4.1927475762042725e-17, 0.15215569734573364, -5.0665930867061117e-17, -0.2940477132797241, 5.650478043539123e-17, 0.9478252530097961, 1.502117395401001, 0.9478252530097961, 5.650478043539123e-17, -0.2940477132797241, -5.0665930867061117e-17, 0.15215569734573364, 4.1927475762042725e-17, -0.08602967858314514, -3.1619763082060183e-17, 0.04784134402871132, 2.1312048747716416e-17, -0.02472740039229393, -1.2573590333975574e-17, 0.011587434448301792, 6.734740765645466e-18, -0.005663635209202766, -4.684409422081195e-18};	
	
//Define FILTER_TAP_NUM 33	
//static const float h[] = {8.712546657581782e-25, -0.0002239293826278299, 8.553268804612164e-19, 0.0025619769003242254, -3.885060542008433e-18, -0.009687605313956738, 1.0061897762988933e-17, 0.026369733735919, -1.98794331381399e-17, -0.06051621213555336, 3.243698113131562e-17, 0.12741217017173767, -4.522882306991331e-17, -0.2757503390312195, 5.4873560415101953e-17, 0.9398812055587769, 1.4999059438705444, 0.9398812055587769, 5.4873560415101953e-17, -0.2757503092288971, -4.522882306991331e-17, 0.12741221487522125, 3.243699105748297e-17, -0.06051621213555336, -1.987942982941745e-17, 0.02636972814798355, 1.006189693580832e-17, -0.009687609039247036, -3.8850630235502704e-18, 0.002561978530138731, 8.553270355575813e-19, -0.000223929833737202, 8.712546657581782e-25};	

	
/*

FIR filter designed with
http://t-filter.appspot.com

sampling frequency: 10000 Hz

* 0 Hz - 2700 Hz
  gain = 1
  desired ripple = 5 dB
  actual ripple = 3.9012411972242793 dB

* 3000 Hz - 5000 Hz
  gain = 0
  desired attenuation = -40 dB
  actual attenuation = -40.57030203503276 dB

*/

#define FILTER_TAP_NUM 35

static const float taps[] = {
  0.02414625111688885,
  0.06401728741588443,
  0.044933153881769605,
  -0.015681240139849045,
  -0.026149104235903296,
  0.021462665496812958,
  0.016109456391146398,
  -0.02947270429340234,
  -0.005886666419813914,
  0.03886200531313775,
  -0.010072111405837416,
  -0.04736804280493483,
  0.03610926485566697,
  0.05442550241090494,
  -0.08860251029391766,
  -0.058971910009376544,
  0.3122893416422745,
  0.5605715308468502,
  0.3122893416422745,
  -0.058971910009376544,
  -0.08860251029391766,
  0.05442550241090494,
  0.03610926485566697,
  -0.04736804280493483,
  -0.010072111405837416,
  0.03886200531313775,
  -0.005886666419813914,
  -0.02947270429340234,
  0.016109456391146398,
  0.021462665496812958,
  -0.026149104235903296,
  -0.015681240139849045,
  0.044933153881769605,
  0.06401728741588443,
  0.02414625111688885
};

	
#endif
#include "stm32f10x.h"
#include "delayUs.h"
#include "adc.h"

void ADCenable(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_ADC1EN | RCC_APB2ENR_AFIOEN; //enabling ADC clock, interrupt enable, 
	RCC->CFGR |= RCC_CFGR_ADCPRE_DIV4;// ADC clock = 12 MHz, maximum is 14, but there is no divider for that freq (72MHz / 6 = 12MHz). works with div6 too
	//while clock for port A and B is enabled down below
	GPIOB->CRL &= ~(GPIO_CRL_CNF0_0|GPIO_CRL_CNF0_1|GPIO_CRL_MODE0_0|GPIO_CRL_MODE0_1); // pin A0 is analog input
	

	//ADC1->CR1 |=ADC_CR1_EOCIE;   //ADC interrupt enabled
	//NVIC_EnableIRQ(ADC1_2_IRQn); //interrupt enabled
	
	//ADC1->SMPR2 |= ADC_SMPR2_SMP8_0;//|ADC_SMPR2_SMP8_1|ADC_SMPR2_SMP8_2;
	ADC1->SQR3 |= ADC_SQR3_SQ1_3; //for B0 in sequence 1, channel 8, it is 0b1000 = 8 (IN8)
	//ADC1->SQR3 |=8; // alternative way of setting the same thing as above

	ADC1->CR2 &= ~ADC_CR2_ALIGN; //data is right aligned (0bxxxx111111111111)

	ADC1->CR2 |= ADC_CR2_ADON | ADC_CR2_CONT; //ADC converter is on
	delay(1000); //alow ADC to stabilize - 1 mS, but my delay is not exactly 1 mS, it is much shorter...
  ADC1->CR2 |= ADC_CR2_CAL;
	delay(1000); //it is better to leave some time, just few clock cycles...
	ADC1->CR2 |= ADC_CR2_ADON; //not sure it requires to call it again?
	delay(1000); //After first ADON, ADC is just set, then second time ADC is actually enabled
}

/* For some unknown reason, DSP does not work if algorithm is included into IRQ handler, so NVIC is disabled for this IRQ */
void ADC1_2_IRQHandler(void)
{ 
	  if (ADC1->SR & ADC_SR_EOC) 
	{
    adc_value=ADC1->DR;
  }	
} 


int adc(void)
{
	int adc=0;
	ADC1->CR2 |=ADC_CR2_SWSTART;
	//if(ADC1->SR & ADC_SR_EOC)
	while(!(ADC1->SR & ADC_SR_EOC));
	adc=ADC1->DR;
	return adc;
}
#ifndef adc_h
#define adc_h

extern int adc_value;

void ADCenable(void);
int adc(void);

#endif
#include "stm32f10x.h"

void timer2enablePWM(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; //port B clock enabled (3), port A clock enable (2), Alternate IO clock enable (0)
	RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; //timer 2 clock enable (2)
	
	GPIOA->CRL |= GPIO_CRL_CNF1_1|GPIO_CRL_MODE1_0|GPIO_CRL_MODE1_1;
	GPIOA->CRL &= ~(GPIO_CRL_CNF1_0);
	
  TIM2->CCER |= TIM_CCER_CC2E; //capture/compare timer2 output enable
	TIM2->CR1 |= TIM_CR1_ARPE; //auto reload preload enable TIMxARR is buffered
	TIM2->CCMR1 |= TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2PE; //output compare 2 mode 0b110 (14:12)?, output compare 2 preload enable
	
	
	//PWM freq = Fclk/PSC/ARR  72MHz/1000
	//PWM Duty = CCR1/ARR
	TIM2->PSC = 0; //prescaler value, 72 MHz divided by:
	TIM2->ARR = 1024; //auto reload register, value of 1024 with prescaler value 0 result in PWM frequency of 70 kHz
	//TIM2->CCR2= 512; //capture/compare value, duty cycle (disabled here, enabling in call function in DSP_1.c)
	TIM2->EGR |= TIM_EGR_UG; //update generation, re-initialize
	TIM2->CR1 |= TIM_CR1_CEN; //counter enabled
}

void dac(int value)
{
	TIM2->CCR2 = value; //here we update capture/compare value for duty cycle
}
#ifndef pwm_as_dac_h
#define pwm_as_dac_h

void timer2enablePWM(void);
void dac(int value);

#endif