Thursday 23 July 2009

integrations tests

Just in between the overview, a small off road let say, of the proof of concepts and building blocks I want to present a small piece of code needed to test our final design. I will dedicate this intermidiate topic to cover some small integrations tests I wrote using the firmware libraries. Refer to my dimmers blog to see what we actually want to test about here. As the code snippets are pretty simple I will add them here anyway because the intends are not of much importance.
(still hoping there will be a proper intend and tab support on these blogger forums one day)

Hardware to test

- 6 inputs
- 6 outputs triac/led
- 4 dip switches
- 1 main led

Test Purposes

  • Every button that goes low ( can be easly inverted with define ) toggles the triac/led output such that button0 toggles led0 and so on ...
  • Every DIP switch of four that goes low ( can be easly inverted with define ) toggles the main led solely.
GPIO layout used

Here you can see how easy and quit readible the code can be written using the high level init structures of the firmware:

GpioDef gx_gpios[]={
//******************* PORT A
{GPIO_Pin_1, GPIO_Speed_50MHz, GPIO_Mode_AIN, GPIOA}, /** zero cross **/
//******************* PORT B
{GPIO_Pin_6, GPIO_Speed_50MHz, GPIO_Mode_Out_OD, GPIOB}, /** i2c1 scl **/
{GPIO_Pin_7, GPIO_Speed_50MHz, GPIO_Mode_Out_OD, GPIOB}, /** i2c1 sda **/
{GPIO_Pin_10, GPIO_Speed_50MHz, GPIO_Mode_Out_OD, GPIOB}, /** i2c2 scl **/
{GPIO_Pin_11, GPIO_Speed_50MHz, GPIO_Mode_Out_OD, GPIOB}, /** i2c2 sda **/
{GPIO_Pin_12, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOB}, /** dip 1 **/
{GPIO_Pin_13, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOB}, /** dip 2 **/
{GPIO_Pin_14, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOB}, /** dip 3 **/
{GPIO_Pin_15, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOB}, /** dip 4 **/
//******************* PORT C
{GPIO_Pin_0, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOC}, /** button 1 **/
{GPIO_Pin_1, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOC}, /** button 2 **/
{GPIO_Pin_2, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOC}, /** button 3 **/
{GPIO_Pin_3, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOC}, /** button 4 **/
{GPIO_Pin_4, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOC}, /** button 5 **/
{GPIO_Pin_5, GPIO_Speed_2MHz, GPIO_Mode_IPU, GPIOC}, /** button 6 **/
{GPIO_Pin_6, GPIO_Speed_50MHz, GPIO_Mode_Out_PP, GPIOC}, /** triac 1 **/
{GPIO_Pin_7, GPIO_Speed_50MHz, GPIO_Mode_Out_PP, GPIOC}, /** triac 2 **/
{GPIO_Pin_8, GPIO_Speed_50MHz, GPIO_Mode_Out_PP, GPIOC}, /** triac 3 **/
{GPIO_Pin_9, GPIO_Speed_50MHz, GPIO_Mode_Out_PP, GPIOC}, /** triac 4 **/
{GPIO_Pin_10, GPIO_Speed_50MHz, GPIO_Mode_Out_PP, GPIOC}, /** triac 5 **/
{GPIO_Pin_11, GPIO_Speed_50MHz, GPIO_Mode_Out_PP, GPIOC}, /** triac 6 **/
{GPIO_Pin_12, GPIO_Speed_50MHz, GPIO_Mode_Out_PP, GPIOC}, /** main led **/
};

Main routine

Kept pretty easy to only test the basics here:

#define PUSHED (0)
#define ACTIVE (0)

RccInit();
GpioInit();
GpioClockSet();

while (1)
{
// INTEGRATIONS TEST input/output
// toggle in I/O sequence button0-5 led/triac0-5 if ( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_0) == PUSHED ){
GPIO_WriteBit(GPIOC, GPIO_Pin_6, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_6))));
}
if ( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_1) == PUSHED ){
GPIO_WriteBit(GPIOC, GPIO_Pin_7, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_7))));
}
if ( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2) == PUSHED ){
GPIO_WriteBit(GPIOC, GPIO_Pin_8, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_8))));
}
if ( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_3) == PUSHED ){
GPIO_WriteBit(GPIOC, GPIO_Pin_9, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_9))));
}
if ( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_4) == PUSHED ){
GPIO_WriteBit(GPIOC, GPIO_Pin_10, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_10))));
}
if ( GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_5) == PUSHED ){
GPIO_WriteBit(GPIOC, GPIO_Pin_11, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_11))));
}

// INTEGRATIONS TEST DIP
// toggle main led when one of 4 dip is active

if ( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12) == ACTIVE ){
GPIO_WriteBit(GPIOC, GPIO_Pin_12, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_12))));
}
if ( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_13) == ACTIVE ){
GPIO_WriteBit(GPIOC, GPIO_Pin_12, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_12))));
}
if ( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_14) == ACTIVE ){
GPIO_WriteBit(GPIOC, GPIO_Pin_12, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_12))));
}
if ( GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15) == ACTIVE){
GPIO_WriteBit(GPIOC, GPIO_Pin_12, (BitAction)((1-GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_12))));
}
}
}

Monday 20 April 2009

pulse timers

As I need a short pulse to fire the triacs I checked upon the possibilities of the other timers. TIM2,3 and 4 act as general purpose timers and are well suited for the job. I experimented with PWM mode but I found it not so good in my case. I decided to toggle the IOs manually on compare match events. The good thing is that you have 4 compare match events per timer. This opens up a lot of possibilities. I will put the principal of working here using TIM2 as an example but in fact I use all 3 of them to fire 6 triacs in total. The first thing to do is setting the period of the timer. In my case this must be set to 10ms as we work with 50HZ/20ms mains frequency. We must fire for the positive as well as for the negative sine wave so that makes a period of 10ms.

/* Time 2 base configuration 10ms */
TIM_TimeBaseStructure.TIM_Period = 9999;
TIM_TimeBaseStructure.TIM_Prescaler = 72;
TIM_TimeBaseStructure.TIM_ClockDivision =TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

Period calculation:

72MHz / 72 = 1MHz or 1µs period unit.
1µs + 9999 x (1µs) = 10ms

Now we need just a little bit more to set up comparing to TIM1 because we want to use the compare matches. This is done initializing the OC_struct with ride;

TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Inactive;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = triacvalue;
TIM_OC1Init(TIM2, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Disable);

This one above sets the CC1 to trigger at triacvalue. This trigger will be signalled with an interrupt. The preload must be disabled to able to change the compare value at run time. Be aware to put OCMode to TIM_OCMode_Inactive because otherwise the timer output lines are enabled. In this design I wanted to select my own gpio pins.

TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = triacvalue + 50;
TIM_OC2Init(TIM2, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Disable);

The second compare match will be just a bit later than first one to make our short fire pulse. In this case we make a pulse of 50µs. This process of setting up the compare matches repeats over all the timers you want to bind with some interrupt event. The NVIC interrupt controller must be set as follows:

/* Enable the TIM2 Interrupt */
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

The only thing you need to do after that is writing your code in the interrupt handler and don't forget to start the timer clock which is different from the TIM1 :

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_ITConfig(TIM2, TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4, ENABLE);

You interrupt handler code might be :

void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
GPIO_SetBits(GPIOC, GPIO_Pin_9);
}
else if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET)
TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
GPIO_ResetBits(GPIOC, GPIO_Pin_9);
}
.....

Also here as simply as that. So the pulses are made by toggling the gpios on interrupt events. You can also change your interrupt priority here. But there i will cover that later on as there's plenty to say on it.

Complexity is completely avoided in this architecture. The speed of writing code is highly increased if you use the ride firmware library for the arm target. You have to download the rkit for arm to be able to use this and that library in your ride IDE.

main timer

My first timer (TIM1) is acting as the start of ADC conversion. I know I can bind some timers on the ADC events but I choose to separate that. The code snippet is:

/* Time 1 Base configuration 25µs */
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 4;
TIM_TimeBaseStructure.TIM_Prescaler = 360;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);

TIM_CKD_DIV1 is actually the main cpu clock and in my case 72MHz. The precaler is dividing that frequency to have the final period.

72MHz/360 = 200kHZ or 5µs period unit.

The minimum period you can set up is 5µs because the STM needs one complete cycle first to start counting. The TIM_Period struct member defines the final timer period you want to work with in my case:

5µs + 4 x (5µs) = 25µs or 40kHz sampling frequency.

We want our timer to just count up until it's overflowed and gives an interrupt on overflow that will be done by taking TIM_CounterMode_Up as the value for CounterMode member. To enable the interrupt on that timer following snippet is needed:

/* Enable and configure TIM1 IRQ channel */
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQChannel;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);

The only thing you need to do now is simply put some code in your int handler known as vector address. In the ride environment this is done in a separate file [target]_it.c

void TIM1_UP_IRQHandler(void)
{
TIM_ClearITPendingBit(TIM1, TIM_IT_Update );
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}

You can change the interrupt priority here but I will go into details on general interrupt handling in an other blog. Don't forget to enable the clock for the timer and the interrupt:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);

TIM_ITConfig(TIM1, TIM_IT_Update , ENABLE);

That's basically it so pretty simple and straightforward and easy to learn if you are familiar with other targets.

Saturday 28 March 2009

use of the functional blocks

I started my cortex journey in experimenting step by step all the great features this target provides. The best way of writing this down is by giving you an overview of them. So her we go than;
  1. Timer1 to generate a fixed interrupt every 25µs and start an ADC conversion
  2. Timer2 with a period of 10ms
  3. Timer2 CC1/2 interrupt for triac1 pulse
  4. Timer2 CC3/4 interrupt for triac2 pulse
  5. Timer3 with a period of 10ms
  6. Timer3 CC1/2 interrupt for triac3 pulse
  7. Timer3 CC3/4 interrupt for triac4 pulse
  8. Timer4 with a period of 10ms
  9. Timer4 CC1/2 interrupt for triac5 pulse
  10. Timer4 CC3/4 interrupt for triac6 pulse
  11. ADC end of conversion interrupt
  12. Systick every 1ms to generate delay
  13. EXTI0 to capture a button1 state change
  14. EXTI1 to capture a button2 state change
  15. EXTI2 to capture a button3 state change
  16. EXTI3 to capture a button4 state change
  17. EXTI4 to capture a button5 state change
  18. EXTI5 to capture a button6 state change
  19. WWDG watchdog interrupt every 58ms
  20. I2C1/2 event interrupts
As you can see quit a list ! This baby must handle 15 different interrupts which is a lot. But my tests didn't show any major problems. I had just a bit of priority tuning to do. Of course I did a couple of weeks to get trough al this as you could guess. But it went very smoothly I must say. Especially using the ride firmware library was highly speeding up all this. In my next blog I will go into details of every topic.

Sunday 8 March 2009

brand new target

Because of the calculation and speed limits of the AVR I had to change my target for the trios project. The reason why I had to do this is explained in the trios blog. I have chosen the ARM CORTEX STM32 target as a replacement. After lots of web searches I discovered a nice development board from the olimex site: http://www.olimex.com . On the picture you can see the board already adapted on the prototype area.I took the STM32-P103 header providing also a the prototype area which I needed to prepare some extra input signals. Its available for ca 80€ which is a great price for what you get ! I copied the list of all the features here:
  • MCU: STM32F103RBT6 ARM 32 bit CORTEX M3™ with 128K Bytes Program Flash, 20K Bytes RAM, USB, CAN, x2 I2C, x2 ADC 12 bit, x3 UART, x2 SPI, x3 TIMERS, up to 72Mhz operation
  • standard JTAG connector with ARM 2x10 pin layout for programming/debugging with ARM-JTAG
  • USB connector
  • CAN driver and connector
  • RS232 driver and connector
  • UEXT connector which allow different modules to be connected (as MOD-MP3, MOD-NRF24LR, etc)
  • SD-MMC connector
  • backup battery connector
  • RESET button
  • status LED
  • power supply LED
  • on board voltage regulator 3.3V with up to 800mA current
    single power supply: takes power from USB port or power supply jack
  • 8 Mhz crystal oscillator
  • 32768 Hz crystal and RTC backup battery connector
  • extension headers for all uC ports
  • PCB: FR-4, 1.5 mm (0,062"), soldermask, silkscreen component print
  • Dimensions: 100 x 90mm (3.94 x 3.5")

I also bought the JTAG debug Rlink from Raisonance see picture. It connects the target very easy using USB to a host PC where you can debug the target straight from within the Ride 7 Raisonance IDE. This IDE is free for download. The libraries provided for the ST target are great to work with. So join me again on the road to the final project we are almost there. Next blog will cover some of the processor in depth features like interrupts and timers that I will use.