728x90

Showing posts with label PWM. Show all posts
Showing posts with label PWM. Show all posts

Tuesday, September 16, 2025

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)

In previous post I putted some examples of using the dsPIC30F2010 prototype board. However it's too long. So I need to write some remaining posts here.

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
PCBWay.com Sponsor PCB Project

 

Creating a PWM Output Using Code Generation Wizard

Generating a PWM output signal could be done from scratch with a few line of code using CCS PICC. We can use its code generation wizard or even manually. 

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
PWM Output Pin of OC1
 

After clicking on Create Project it will generate source code. Then pressing F9 to compile this project.

The main.c C source code is just like below.

  1. #include <main.h>


  2. void main()
  3. {

  4. while(TRUE)
  5. {
  6. //TODO: User Code
  7. }

  8. }

 Then double click on the main.h header file we will see its source code.

  1. #include <30F2010.h>
  2. #device ICSP=1
  3. #use delay(crystal=20000000)

  4. #FUSES NOWDT //No Watch Dog Timer
  5. #FUSES CKSFSM //Clock Switching is enabled, fail Safe clock monitor is enabled


  6. #use pwm(OC1,TIMER=2,FREQUENCY=10000,DUTY=0)


It will generate a PWM signal output at pin OC1 (RC13) with a frequency of 10kHz and 0% duty cycle. If we want a 50% duty cycle we need to change the DUTY parameter to 50, and rebuilt it.

PWM Duty Cycle Adjusting with ADC

After using the code generation wizard I got some idea of using PWM in CCS PICC. So I modify and write more codes to adjust PWM signal. I use the on-board ADC input from a potentiometer. Then it will convert to PWM duty cycle ranging from 0% to 100%.

  1. #include "board.h"

  2. #use pwm(OC1,TIMER=2,FREQUENCY=10000,STREAM=_1,DUTY=50)
  3. void main()
  4. {
  5. long adc_value = 0;
  6. float duty_cycle = 0;
  7. setup_adc_ports(sAN4);
  8. setup_adc(ADC_CLOCK_INTERNAL | ADC_TAD_MUL_31);


  9. while(TRUE)
  10. {
  11. //TODO: User Code
  12. set_adc_channel(4);
  13. delay_us(10);
  14. adc_value = read_adc();
  15. duty_cycle = (1000.0*adc_value)/1023;
  16. pwm_set_duty(_1,(int)duty_cycle);
  17. //pwm_set_duty_percent(_1,(int)duty_cycle);
  18. delay_ms(100);
  19. }

  20. }


And its "board.h" file:

  1. #include <30F2010.h>
  2. #device ADC=10
  3. #device ICSP=1
  4. #fuses HS,NODEBUG,NOWDT,PR,CKSFSM
  5. #use delay(crystal=20000000)


  6. #use FIXED_IO( D_outputs=PIN_D1,PIN_D0 )
  7. #use rs232(UART1, baud=9600, stream=UART_PORT1)

  8. #define LED0 PIN_D0
  9. #define LED1 PIN_D1
  10. #define SW0 PIN_C13
  11. #define SW1 PIN_C14

  12. #define DELAY 500


It work fine without wiring additional components. However we can connect the OC1(RC13) PWM pin to a larger LED ( for instance a 5VDC 10mm LED).

 

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
Low Duty Cycle

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
High Duty Cycle

Click here to download its source file.  

ADC with On-board Tactile Switches and PWM

Fortunately there are two on-board tactile switches that could be used to adjust PWM duty cycle. So I will use ADC channel 4, SW4 (RC13) and SW5(RC14) to adjust PWM duty cycle of OC1 and OC2 respectively.

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
PWM Adjustment Using Pot and Tactile Switches

The PWM OC1 is generated by Timer 2 while the PWM OC2 is generated by Timer 3. 

  1. #include "board.h"

  2. #use pwm(OC1,TIMER=2,FREQUENCY=10000,STREAM=_1,DUTY=0)
  3. #use pwm(OC2,TIMER=3,FREQUENCY=10000,STREAM=_2,DUTY=0)

  4. void main()
  5. {
  6. long adc_value = 0;
  7. int oc2_count=0;
  8. float duty_cycle = 0;
  9. set_pullup(TRUE,PIN_C13);
  10. set_pullup(TRUE,PIN_C14);
  11. setup_adc_ports(sAN4);
  12. setup_adc(ADC_CLOCK_INTERNAL | ADC_TAD_MUL_31);

  13. while(TRUE)
  14. {
  15. //TODO: User Code
  16. set_adc_channel(4);
  17. delay_us(10);
  18. adc_value = read_adc();
  19. duty_cycle = (1000.0*adc_value)/1023;
  20. pwm_set_duty(_1,(int)duty_cycle);
  21. if(input(SW0)==0){
  22. if(oc2_count<1000) oc2_count+=100;
  23. pwm_set_duty(_2,oc2_count);
  24. delay_ms(250);
  25. }
  26. if(input(SW1)==0){
  27. if(oc2_count>0) oc2_count-=100;
  28. pwm_set_duty(_2,oc2_count);
  29. delay_ms(250);
  30. }
  31. }

  32. }


Its "board.h" header file:

  1. #include <30F2010.h>
  2. #device ADC=10
  3. #device ICSP=1
  4. #fuses HS,NODEBUG,NOWDT,PR,CKSFSM
  5. #use delay(crystal=20000000)

  6. #use rs232(UART1, baud=9600, stream=UART_PORT1)

  7. #define LED0 PIN_D0
  8. #define LED1 PIN_D1
  9. #define SW0 PIN_C13
  10. #define SW1 PIN_C14

  11. #define DELAY 500

Click here to download its source file.

Timer Tick Example

We can use timer tick for timing delay and scheduling instead of using the delay function. We can call it from scratch using the CCS PICC IDE PIC24 project wizard.

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
Timer Tick Example Using Timer 1

 Then we get generated code lists below.

  • main.h
  1. #include <30F2010.h>
  2. #device ICSP=1
  3. #use delay(crystal=20000000)

  4. #FUSES NOWDT //No Watch Dog Timer
  5. #FUSES CKSFSM //Clock Switching is enabled, fail Safe clock monitor is enabled


  6. #use timer(timer=1,tick=100us,bits=32,NOISR)

  7. #define TICK_TYPE unsigned int32

  • main.c

 

  1. #include <main.h>
  2. TICK_TYPE GetTickDifference(TICK_TYPE currTick, TICK_TYPE prevTick)
  3. {
  4. return(currTick-prevTick);
  5. }

  6. void timer_1_tick(void)
  7. {
  8. //TODO: User Code
  9. }


  10. void main()
  11. {

  12. TICK_TYPE CurrentTick,PreviousTick;



  13. //Example program using Tick Timer
  14. CurrentTick = PreviousTick = get_ticks();

  15. while(TRUE)
  16. {
  17. CurrentTick = get_ticks();

  18. if(GetTickDifference(CurrentTick, PreviousTick) >= ((TICK_TYPE)TICKS_PER_SECOND*1)/1000)
  19. {
  20. timer_1_tick();
  21. PreviousTick = CurrentTick;
  22. }

  23. //TODO: User Code
  24. }

  25. }

 Then I need to add some codes to these existing source codes.

  •  main.h

 

  1. #include <30F2010.h>
  2. #device ICSP=1
  3. #use delay(crystal=20000000)

  4. #FUSES NOWDT //No Watch Dog Timer
  5. #FUSES CKSFSM //Clock Switching is enabled, fail Safe clock monitor is enabled


  6. #use FIXED_IO( D_outputs=PIN_D1,PIN_D0 )

  7. #define LED1 PIN_D0
  8. #define LED2 PIN_D1


  9. #use timer(timer=1,tick=100us,bits=32,NOISR)

  10. #define TICK_TYPE unsigned int32


  • main.c
  1. #include <main.h>

  2. unsigned int16 count_1_ms=0, count_100_ms=0;

  3. TICK_TYPE GetTickDifference(TICK_TYPE currTick, TICK_TYPE prevTick)
  4. {
  5. return(currTick-prevTick);
  6. }

  7. void timer_1_tick(void)
  8. {
  9. //TODO: User Code
  10. count_1_ms++;
  11. if(count_1_ms>=100) {
  12. output_toggle(LED2);
  13. count_1_ms=0;
  14. count_100_ms++;
  15. }
  16. if(count_100_ms>=5){
  17. output_toggle(LED1);
  18. count_100_ms=0;
  19. }
  20. }


  21. void main()
  22. {

  23. TICK_TYPE CurrentTick,PreviousTick;



  24. //Example program using Tick Timer
  25. CurrentTick = PreviousTick = get_ticks();

  26. while(TRUE)
  27. {
  28. CurrentTick = get_ticks();

  29. if(GetTickDifference(CurrentTick, PreviousTick) >= ((TICK_TYPE)TICKS_PER_SECOND*1)/1000)
  30. {
  31. timer_1_tick();
  32. PreviousTick = CurrentTick;
  33. }

  34. //TODO: User Code
  35. }

  36. }

 It will blink LEDs connect to RD0 and RD1 at different rates.

 

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
RD0 and RD1 at different rates

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
RD0 and RD1 at different rates

 Click here to download its source file.

Timer 1 Interrupt Example Using Coder Generation Wizard

Timer1 module operates in many modes up to software configuration, internal, gated and external. We can write its source manually or even using the CCS PICC code generation wizard. 

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
Using Code Generation Wizard

Then it will generate a skeleton code below that we have to add more code manually.

  1. #include <main.h>

  2. #INT_TIMER1
  3. void timer1_isr(void)
  4. {

  5. }



  6. void main()
  7. {

  8. setup_timer1(TMR_INTERNAL | TMR_DIV_BY_1, 1000);

  9. enable_interrupts(INT_TIMER1);
  10. enable_interrupts(INTR_GLOBAL);

  11. while(TRUE)
  12. {
  13. //TODO: User Code
  14. }

  15. }

 Then I need to add more codes both in main function and Timer interrupt service routine "#INT_TIMER1".

 

  1. #include <main.h>

  2. unsigned int16 timer_1_counts=0;
  3. #INT_TIMER1
  4. void timer1_isr(void)
  5. {
  6. output_toggle(PIN_D0);
  7. timer_1_counts++;
  8. clear_interrupt(INT_TIMER1);
  9. }



  10. void main()
  11. {

  12. setup_timer1(TMR_INTERNAL | TMR_DIV_BY_256, 1000);

  13. enable_interrupts(INT_TIMER1);
  14. enable_interrupts(INTR_GLOBAL);

  15. while(TRUE)
  16. {
  17. //TODO: User Code
  18. if(timer_1_counts>=100){
  19. output_toggle(pin_d1);
  20. timer_1_counts=0;
  21. }
  22. }

  23. }

This source codes will blink pin RD0 and RD1 at different rates. Click here to download its source file.

Change Notify (CN) Interrupt

Change Notify Interrupt (CNI) allow the program to fast response to external event change. For instance a change from logic high to low or vice versa. There are two tactile switches connect to RC13(CN1) and RC14(CN0) respectively. There are no external pull up or pull down resistors. So you need to add more external components to detect input logic change requirement.

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
TABLE 8-2: INPUT CHANGE NOTIFICATION REGISTER MAP (BITS 15-0)

Fortunately the Change Notify Interrupt (CNI) come with internal pull-up enable feature that we can enable or disable it by software setting. The CNENx and CNPUx special register responsible for this task. 

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue) 

The program below demonstrate a simple use of Change Notify (CN) Interrupt of pin CN0(RC14). The LED connects to pin RD0 keeps blinking at the rate of 500ms in main program's loop. Whenever the CN0 input logic changes to logic low the CNI occurs. It will toggle the LED connects to pin RD1.

  • main.c 
  1. #include <main.h>

  2. /*CN Pull Up Enable Register*/
  3. #byte CNPU1=0x0C4;
  4. #bit CN0PU=CNPU1.0;
  5. /*CN Interrupt Enable Register*/
  6. #byte CNIEN1=0x0C0;
  7. #bit CN0EN=CNIEN1.0;

  8. /*Interrupt Service Routine for CN Interrupt*/
  9. #INT_CNI
  10. void cni_isr(void)
  11. {
  12. if(!input(CN0)) output_toggle(LED2);
  13. clear_interrupt(INT_CNI);
  14. }


  15. void main()
  16. {
  17. /*PortC As Inputs*/
  18. set_tris_c(0xFFFF);
  19. /*Turn On CN0 PullUp and CN0 Interrupt*/
  20. CN0PU=1;
  21. CN0EN=1;
  22. /*Enable Interrupt*/
  23. enable_interrupts(INTR_CN_PIN|PIN_C13);
  24. enable_interrupts(INTR_GLOBAL);
  25. clear_interrupt(INT_CNI);
  26. while(TRUE)
  27. {
  28. //TODO: User Code
  29. output_toggle(LED1);
  30. delay_ms(500);
  31. }

  32. }
  •  main.h
  1. #include <30F2010.h>
  2. #device ICSP=1
  3. #use delay(crystal=20000000)

  4. #FUSES NOWDT
  5. //No Watch Dog Timer
  6. #FUSES CKSFSM
  7. //Clock Switching is enabled, fail Safe clock monitor is enabled


  8. #use FIXED_IO( D_outputs=PIN_D1,PIN_D0 )

  9. #define CN1 PIN_C13
  10. #define CN0 PIN_C14
  11. #define LED1 PIN_D0
  12. #define LED2 PIN_D1



 I tested this demo program on my dsPIC30F2010 prototype board. It work fine and very fast. Click here to download this example program.

Now I use all tactile switches connect to RC13 and RC14 to generate Change Notification Interrupt (CNI). Every time the CN interrupts occur it will toggle the LEDs.

  • main.c

 

  1. #include <main.h>

  2. /*CN Pull Up Enable Register*/
  3. #byte CNPU1=0x0C4;
  4. #bit CN0PU=CNPU1.0;
  5. #bit CN1PU=CNPU1.1;
  6. /*CN Interrupt Enable Register*/
  7. #byte CNIEN1=0x0C0;
  8. #bit CN0EN=CNIEN1.0;
  9. #bit CN1EN=CNIEN1.1;

  10. /*Interrupt Service Routine for CN Interrupt*/
  11. #INT_CNI
  12. void cni_isr(void)
  13. {
  14. if(!input(CN0)) output_toggle(LED2);
  15. if(!input(CN1)) output_toggle(LED1);
  16. clear_interrupt(INT_CNI);
  17. }


  18. void main()
  19. {
  20. /*PortC As Inputs*/
  21. set_tris_c(0xFFFF);
  22. /*Turn On CN0 PullUp and CN0 and CN1 Interrupt*/
  23. CN0PU=1;
  24. CN0EN=1;
  25. CN1PU=1;
  26. CN1EN=1;
  27. /*Enable Interrupt*/
  28. //enable_interrupts(INTR_CN_PIN|PIN_C13);
  29. //enable_interrupts(INTR_CN_PIN|PIN_C14);
  30. enable_interrupts(INT_CNI);
  31. enable_interrupts(INTR_GLOBAL);
  32. clear_interrupt(INT_CNI);
  33. while(TRUE)
  34. {
  35. //TODO: User Code
  36. }

  37. }

I take some special registers of CN interrupt to use in this program because using the CCS PICC built-in library is not enough.

  • main.h
  1. #include <30F2010.h>
  2. #device ICSP=1
  3. #use delay(crystal=20000000)

  4. #FUSES NOWDT
  5. //No Watch Dog Timer
  6. #FUSES CKSFSM
  7. //Clock Switching is enabled, fail Safe clock monitor is enabled


  8. #use FIXED_IO( D_outputs=PIN_D1,PIN_D0 )

  9. #define CN1 PIN_C13
  10. #define CN0 PIN_C14
  11. #define LED1 PIN_D0
  12. #define LED2 PIN_D1




There are some bounce while pressing the tactile switches. So it's suitable to add and RC filter circuit to eliminate this noise. Using a software delay in the ISR is a good choice to bypass this noisy bouncing period.

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)

dsPIC30F2010 Prototype Board CCS PICC Examples (Continue)
 

Click here to download this program example.
 

Wednesday, February 17, 2021

Arduino rotates a small servo motor with buttons

 

A servo is a kind of motor built in a module. The module contains a DC motor and a feed back control circuit. The feed back circuit regulates the motor rotating position to a specific angle receives from its master MCU, for example and Arduino.

Arduino rotates a small servo motor with buttons
Picture of this example

Controlling the position needs a specific timing. Conventionally, the signal timing frequency is 50 Hz. For more information about the servo motor timing, see this post

Servo motors are available in many physical sizes and interfaces. Choosing a servo motor depends on the application. With Arduino, there are many libraries to interface with external hardware module, including the servo motor. The Servo.h is already available after the installing of Arduino IDE. Servo control pin can be any where within the digital I/O pin and it's selected by source code writing.

To use the servo library we must #include <Servo.h>. Additionally, we must create a servo object, for example,

Servo sg90;

Using a very simple servo motor like the SG90 requires a few methods.

  1. attach() - sg90.attach(pin) : where pin is any pin on the Arduino board in use.
  2. write() - sg90.write(angle): where angle is between 0 and 190 degree.

Other sophisticated  servo motors have an angle reading line. But we don't need it here, as a simple example.

Arduino rotates a small servo motor with buttons
SG90 servo from device's specification

A typical servo SG90 has three lines - GND, VCC and Control. Control and VCC line requires a +5V pulse. Control line is the orange one's. It's no other than a PWM signal with low frequency.

Arduino rotates a small servo motor with buttons
SG90 pin diagram and its timing from the device's specification

In this example post, I use three tactile switches to rotate a SG90 servo motor with three corresponding angles 0 , 90 and 180 degrees respectively. I use four pin A0, A1, A2 and A3. 

Arduino rotates a small servo motor with buttons
Some required parts

Arduino rotates a small servo motor with buttons
Connections Diagram

Arduino code from Github gist lists below.


Click here to download Arduino sketch.

Arduino rotates a servo motor using a potentiometer

In the previous post, I use three tactile switches to rotate a servo motor to three angle positions. However, using a potentiometer, we can continuously rotates the angle of servo motor in accordance to the adjustment of the potentiometer. 

Arduino rotates a servo motor using a potentiometer
A picture of this example

With the  Arduino Servo.h and analogRead function, this task could be done easily. In this example, a potentiometer connects to pin A0 work as an analog input adjusting the angle of rotation. A servo again, connects to pin A3.

Arduino rotates a servo motor using a potentiometer
Connections Diagram

The analogRead() function return a 10-bit (1024) value from any reading from a specific analog input pin. Hence, this analog value must convert from 1024 to 180 in degree. This could be done by a simple equation as will be shown in source code.

Click here to download the sketch archive of this example.

Friday, February 12, 2021

ATMega32 Timer/Counter1 in Fast PWM Mode

Introduction

Timer/Counter1 module of ATMega32 is configurable to operate in fast Pulse Width Modulation (PWM) mode. There are two PWM blocks inside this module - PWM A and PWM B. Hence there are two corresponding output pins for these two blocks - OC1A and OC1B.

PWM output pulse has two mode, non-inverting and inverting mode. Its set and clear output pulse is the comparison between TCNT1 and OC1X register (OC1A or OC1B).

ATMega32 Timer/Counter1 in Fast PWM Mode
Fast PWM Mode - Timing Diagram

To make ATMega32 operates in this mode, user must set WGM13:0 of  Timer/Counter1 Control Register (TCCR1x) to 5, 6, 7, 14, or 15. Its frequency is calculated as below.

ATMega32 Timer/Counter1 in Fast PWM Mode
Fast PWM Mode Frequency

Its resolution is between 8 and 10-bit. Where,

ATMega32 Timer/Counter1 in Fast PWM Mode
Fast PWM Resolution

Programming

We need to configure some registers for fast PWM mode.
  • Timer/Counter1 Control Register A (TCCR1A)
ATMega32 Timer/Counter1 in Fast PWM Mode
TCCR1A

This register contains some settings for fast PWM mode. Compare Output Mode bits used for selecting between inverting and non-inverting mode of fast PWM mode.

ATMega32 Timer/Counter1 in Fast PWM Mode
Compare Output Mode Bits of TCCR1A

Waveform Generation Mode Bits must be set to any value as listed to make this module operates in fast PWM mode.

ATMega32 Timer/Counter1 in Fast PWM Mode
Waveform Generation Mode Bits

  • Timer/Counter1 Control Register B (TCCR1B)
This register contains prescaler selection for fast PWM mode. 
ATMega32 Timer/Counter1 in Fast PWM Mode
TCCR1B

Clock Select Bits (CS12:10) allow user to set its prescaler effecting output frequency of fast PWM.

Timer/Counter1 Control Register B (TCCR1B)
Clock Selection Bits
  • Output Compare Register A 
This is a pair of 8-bit registers - OCR1AH and OCR1AL. This register is a compare variable of TCNT1 that toggle output waveform of PWM signal.
ATMega32 Timer/Counter1 in Fast PWM Mode
Output Compare Register A
Now we make a simple example of using fast PWM. PWM A generates its output waveform on OC1A pin with a frequency of 490Hz, and 50% duty cycle. Output signal is non-inverting mode. Its resolution is 8-bit.

ATMega32 Timer/Counter1 in Fast PWM Mode
Schematic Diagram

Microcontroller clock is 16MHz from external crystal oscillator.

Source Code:

/*
 * timer_1_fast_pwm_8_bit_1.c
 *
 * Created: 2/10/2021 3:41:04 PM
 * Author : admin
 */ 

#include <avr/io.h>

int main(void)
{
/*OC1A PD5 Output*/
DDRD|=(1<<5);
/*Non-inverting mode, fast PWM 8-bit*/
TCCR1A=(1<<COM1A1)|(1<<COM1B1)|(1<<WGM12)|(1<<WGM10);
/*1:64 Prescaler*/
TCCR1B=(1<<CS11)|(1<<CS10);
/*Select 50% duty cycle*/
OCR1AH=0;
OCR1AL=127;
    while (1) 
    {
    }
}

Click here to download archive. 

Now let use both of this PWM output of Timer/Counter1 module. Either OC1A and OC1B output signal are inverting with different duty cycle, but its frequency is unique. 

ATMega32 Timer/Counter1 in Fast PWM Mode
Simulation screen shot

Source code:

/*
 * timer_1_fast_pwm_8_bit_2.c
 *
 * Created: 3/3/2021 8:38:00 PM
 * Author : admin
 */ 

#include <avr/io.h>


int main(void)
{
    /*OC1A and OC1B Output*/
DDRD=(1<<5)|(1<<4);
/*inverting fast PWM 8-bit*/
TCCR1A=(1<<COM1A1)|(1<<COM1B1)|(1<<COM1A0)|(1<<WGM12)|(1<<WGM10);
/*1::64 Pre-Scaler*/
TCCR1B=(1<<CS11)|(1<<CS10);
/*OC1A duty cycle*/
OCR1AH=0;
OCR1AL=127;
/*OC1B duty cycle*/
OCR1BH=0;
OCR1BL=75;
    while (1) 
    {
    }
}

Click here to download this example.



Search This Blog

Labels