• Simplified Chinese
  • Japanese
  • Korean
   
Provide feedback on this website to help us improve:

How likely are you to recommend this website to a friend or colleague?

Not at all likely
0
1
2
3
4
5
6
7
8
9
10
Extremely likely

Additional comments:

Email:

Close

Home > Cypress Developer Community > Blogs > The PSoC Hacker Blog


The PSoC Hacker Blog
Nov 06, 2009

A couple of weeks ago, I had written about a method to implement a High Resolution DAC using two 8 bit DACs.  Recently I had a very interesting discussion with one of my colleagues and a good friend, a young and brilliant guy named Kannan.  He showed me how to implement a high resolution Dithered DAC using a single 8 bit DAC, an 8 bit Mux, a couple of Control Registers and a PWM.  It is amazing what you can do with the PSoC 3 hardware.

Dithering is a widely used technique in Digital Processing where a noise is intentionally introduced into a system to increase the resolution of the system.  Say we have an 8 bit DAC with a full scale value of 255mV.  Each count of the DAC represents 1mV.  What if we wanted an output of 1.25mV from the DAC.  Switch the DAC output between 1mv and 2mV keeping the output at 2mV 25% of the time and 1mV 75% of the time, the average value of the output would be 1.25mV.  For an output of 1.5mV, the DAC output should be maintained at 2mV for 50% of the time and 1mV 50% of the time.


Now let us see how this method can be implemented using the PSoC 3 hardware to create a 10 bit DAC.

Place an 8 bit voltage DAC.  Select 1.020V (4mV / bit) as the range.  Set the Data_Source to “DAC Bus” and Strobe_Mode to “External”.  The output of the DAC can now be controlled using an 8 bit data bus and the output will be updated on the rising edge of the Strobe input.

Place a Multiplexer from the Digital >> Logic category.  Set number of input terminals to 2 and terminal width to 8.  The multiplexer can now switch between two 8 bit data buses.  Place two control registers from the Digital >> Registers category.  Set the number of outputs to 8.  Connect the 8 bit data bus of the Control registers to the input of the Multiplexer. 

Place a PWM, with a period of 3 and compare type set to “Less than”.  The output of the PWM now can be controlled to 0%, 25% and 75% duty cycle.  Connect the output of the PWM to the control input of the Mux.  Use the same clock of the PWM as a strobe to the DAC.  The hardware is now ready.

In firmware, write the Most Significant 8 bits of the 10 bit DAC value to the ControlRegister0 and one count higher value to ControlRegister1.  Use the Least Significant 2 bits to update the PWM’s pulse width.  Now, the PWM switches the input of the DAC between the two 8 bit Control Register values and the average output of the DAC would be our 10 bit DAC result.  Use an external Low Pass filter to remove the switching frequency.  As the switching frequency is at 125KHz, a simple RC filter will do the trick.  Below is the code.

void main()
{
   uint16 DacValue;
   VDAC8_Start();
   PwmMsb_Start();
   Amplifier_Start();
   for(;;)
   {
      DacMsbReg0_Write(DacValue >> 2);
      DacMsbReg1_Write((DacValue >> 2) + 1);
      PwmMsb_WriteCompare((uint8)(DacValue & 0x03));
   }
}

The range of the DAC now is 0 to 1020 counts; each count representing 1mV, subject to some offset and gain error inherent in the 8 bit DAC.

Quoting my brilliant friend “I bet this cannot be implemented with any other controller out there in the market!!”

Rating: (4.8/5) by 10 users
Comments (3)
Oct 25, 2009

Most if not all of the microcontroller designs that involve analog signal processing will require an ADC to convert an analog signal into a digital value that can be processed by the CPU.  PSoC 1 has a vast selection of ADCs that can be used depending on the application, ADCINC, ADCINCVR, ADCINC14, DELSIG8, DELSIG11, DelSig to name a few.  Most of the users, beginners to experts face some or other problem while using an ADC like ADC not completing the conversion, ADC result always zero, ADC result incorrect etc.  Below are Five Golden Rules that will help you to tame the PSoC 1 ™ ADC.

GLOBAL INTERRUPTS

A PSoC 1 ADC is a combination of analog and digital blocks and the CPU.  An SC block configured as a modulator converts the input analog signal into a digital bit stream.  A counter is used to count the time the bit stream is high for a given period of integration time.  A timer or a PWM is used to set the integration time.  At the end of integration cycle, the timer or PWM generates an interrupt and the processor reads the counter inside the interrupt. 


When Global Interrupt is disabled, the CPU will not respond to the timer’s (or PWM’s) interrupt to read the ADC result.  So, the ADC would never complete the conversion.  Any instruction, for example,

while(ADC_fIsDataAvailable() == 0);

that waits for the ADC result to complete will execute forever

Golden Rule No.1 – Always enable Global Interrupt

ANALOG POWER

The Analog power parameter under the Global Resources sets the power to the SC blocks and the reference. 

 

For the ADC to work correctly, always select the power settings “SC On / Ref Low”, “SC On / Ref Medium” or “SC On / Ref High”.  Selecting a reference with “SC Off” or “All Off” will result in the ADC not working.

The Reference power should be selected so that the Reference Generator is able to provide adequate power to the Analog blocks.  The power to the Reference section should always be equal to or greater than the highest power used by any analog block in the design.  For example, if you have a PGA and an ADC where the PGA operates at Low power and the ADC at Medium power, the Reference power should be set to SC On / Ref Medium or SC On / Ref High.

Golden Rule No.2 – Always provide power to SC Blocks and set appropriate Reference Power.

COLUMN CLOCK AND DATA CLOCK

The ADC has a parameter called “Clock”.  This selects the clock source to the Digital section of the ADC.  The column clock for the SC Block should be set to the same value as that of the “Clock” parameter.  In the picture below, the Clock parameter is set to VC2.  So, the Analog Column clock should also be set to VC2.  If the Column clock and Data clock are not the same, the output of the ADC will not be correct.

Golden Rule-3: The Data Clock and Column Clock should always be equal

CLOCK PHASE

The output of Switch Capacitor blocks is not a continuous signal.  SC blocks have two phases of operation.  Phase-1 is the charge acquisition phase when the input signal is sampled.  During this phase, the output of the SC block is 0.  Phase-2 is the charge transfer phase when the acquired charge is transferred to the output and the output is proportional to the ratio of input and output switch capacitor cell values.  So, the output of the SC block is valid only during Phase-2.  Application Note “AN2041 - Understanding Switched Capacitor Blocks” is a very good source of information on this subject.

If an ADC’s input is connected to the output of another SC Block, say a 3 Op-Amp Instrumentation Amplifier or a Filter, the output of this source SC block is 0 during Phase-1 of the SC Block operation.  The ADC modulator which is also an SC Block samples the input signal on Phase-1. Because of this, the ADC will always see 0V at its input and will always produce an output of 0.  Under this circumstance, the ClockPhase parameter of the ADC should be set to “Swapped”.  Now, the ADC modulator will sample its input on Phase-2 of the SC Block cycle when the input is valid.  This will produce the correct ADC result.  If the input to the ADC is a continuous signal from a CT block, the Analog bus or a direct pin, then the ClockPhase parameter can be set to either “Normal” or “Swapped”

Golden Rule-4: Always set the correct value for the ClockPhase parameter

WAITING FOR THE RESULT INSIDE AN ISR

Many a time, you may want to initiate an ADC conversion inside the ISR of another user module, say a timer or counter and process the output of the ADC inside the ISR.  The code may look like this:

void TimerISR (void)  // This is an ISR
{
    int Result;
    ADC_GetSamples(1);
    while(ADC_fIsDataAvailable() == 0;
    Result = ADC_iGetDataClearFlag();
}

The above code will result in the program stuck in the while loop waiting for the ADC result to be available.

When an ISR is entered, the Global Interrupt is disabled and is enabled only when the ISR exits.  So, inside an ISR no other interrupts will be serviced.  The ADC requires the interrupt to work for its conversion to be completed.  As the ADC interrupts will not be serviced inside the TimerISR, the ADC conversion would never complete.  Re-enabling Global interrupt inside the ISR would be a solution.  But this is not preferable as this could also result in nested interrupts locking the CPU inside ISRs forever.

So, do not wait for an ADC result to be available inside another ISR.  Instead set some flag inside the ISR and do the ADC conversion in the foreground.

Golden Rule-5: Never wait for the ADC result inside an ISR

Follow the above five Golden Rules and see the PSoC 1 ADCs work happily for you!!

Rating: (4.6/5) by 9 users
Comments (0)
Oct 12, 2009

PSoC3 has 8 bit voltage and current DACs.  Higher resolution DACs may be created by combining two 8 bit DACs, one for MSB and one for LSB and summing their outputs together.  The DAC section of the TRM shows how to chain two 8 bit DACs to get a 12 bit DAC.

The MSB is implemented using an 8 bit DAC configured for 2.040mA full scale.  Each bit of the MSB DAC now corresponds to 8uA.  To create a 12 bit DAC, we need to extend the resolution by four bits and each bit should be equal to 1/16 of the MSB DAC, which is 0.5uA.   This can be implemented by using Bits 2 to 5 of another 8 bit DAC configured for a 32uA output.  The schematic diagram of a PSoC3 project to create a high resolution current DAC is shown below.


Below is the code that is used to split the 12 bit DAC value and update the MSB and LSB DACs.

DAC_MSB_SetRange(DAC_MSB_RANGE_2mA);
DAC_LSB_SetRange(DAC_LSB_RANGE_32uA);
DAC_MSB_Start();
DAC_LSB_Start);
DacValue = 4095;
DAC_MSB_SetValue((DacValue >> 4) & 0xFF);
DAC_LSB_SetValue((DacValue << 2) & 0x3C);

To implement a 10 bit DAC, the code would be:

DAC_MSB_SetRange(DAC_MSB_RANGE_2mA);
DAC_LSB_SetRange(DAC_LSB_RANGE_32uA);
DAC_MSB_Start();
DAC_LSB_Start);
DacValue = 1023;
DAC_MSB_SetValue((DacValue >> 2) & 0xFF);
DAC_LSB_SetValue((DacValue << 4) & 0x30);


But this method also has its disadvantages.  Any mismatch in gain between the MSB and LSB DACs will result in non-linearity and unequal step sizes.  This can be compensated by calibration, either by trimming the DAC using the DACx_TR register or by using the unused least significant bits of the LSB DAC.  Stay tuned!!

Rating: (4.3/5) by 6 users
Comments (0)
Oct 05, 2009

Any microcontroller project will have the need to control a GPIO pin using a CPU.  Applications could be simple ones like switching On/Off an LED, a relay, a buzzer etc to complex ones like generating a software PWM, bit-banging I2C protocol etc.

There are many options to control a PSoC3 GPIO in firmware.  Let us take a look at some of these options and their pros and cons.

Option-1: Use Port in APIs

When a Digital Output Port component is placed in the project, the PSoC Creator generates API functions to control the port pin.  The function used to write to the pin is <Pin Name>_Write.  For example, for a Port Pin component named Out1,

Out1_Write(1);   // Set the pin
Out1_Write(0);  // Clear the pin

Pros: The advantage of using the API function is the ease of programming and portability.

Cons: The disadvantage of using this method is the high number of processor cycles taken to update the pin.    Following is the listing of the Output1_Write function.
; void Out1_Write(uint8 value)

    RSEG  ?PR?_Out1_Write?OUT1
_Out1_Write:
    USING    0
            ; SOURCE LINE # 34
    MOV      DPTR,#value?040
    MOV      A,R7
    MOVX     @DPTR,A
; {
            ; SOURCE LINE # 35
;     uint8 staticBits = Out1_DR & ~Out1_MASK;
            ; SOURCE LINE # 36
    MOV      DPTR,#05100H
    MOVX     A,@DPTR
    MOV      R7,A
    MOV      A,R7
    ANL      A,#0EFH
    MOV      R7,A
    MOV      DPTR,#staticBits?041
    MOV      A,R7
    MOVX     @DPTR,A
;     Out1_DR = staticBits | ((value << Out1_SHIFT) & Out1_MASK);
            ; SOURCE LINE # 37
    MOV      DPTR,#value?040
    MOVX     A,@DPTR
    MOV      R7,A
    MOV      A,R7
    SWAP     A
    ANL      A,#0F0H
    MOV      R7,A
    MOV      A,R7
    ANL      A,#010H
    MOV      R7,A
    MOV      DPTR,#staticBits?041
    MOVX     A,@DPTR
    MOV      R6,A
    MOV      A,R7
    ORL      A,R6
    MOV      R7,A
    MOV      DPTR,#05100H
    MOV      A,R7
    MOVX     @DPTR,A
; }
            ; SOURCE LINE # 38
?C0001:
    RET     

The above code takes 58 CPU cycles including the call and return.

Option-2: Use Port Data Register and Mask

In this method, the port data register is accessed in the external data memory space using the PHUB.   For every Port Pin placed in the schematic, a header file <Pin Name>.h has the declarations for the Port data register and the mask.  For a Port Pin named Out1, the Port data register and the mask are Out1_DR Out1_MASK respectively.  For example:

Out1_DR |= Out1_MASK;  // Set the pin
Out1_DR &= ~Out1_MASK; // Clear the pin

Pros: This type of access takes less processor cycles than the API function call.  The example code #1 above results in the below compiled code.

;         Out1_DR |= Out1_MASK;

            ; SOURCE LINE # 41
    MOV      DPTR,#05100H
    MOVX     A,@DPTR
    MOV      R7,A
    MOV      A,R7
    ORL      A,#010H
    MOV      R7,A
    MOV      A,R7
    MOVX     @DPTR,A

This takes 13 CPU cycles, about 4 times faster than the Port pin API function call.

Cons: There isn’t any great disadvantage of using this method other than the fact that this method is still slower than Option-3 below, and sacrifices some amount of readability compared to Option-1.

Option-3: Control the pin using the SFR register space

In this method, the port data register is accessed directly in the SFR register space.  There are two SFRs that need to be controlled.

SFRPRTxDR: The register bits control the bits of the corresponding port.  For example the value in Bit5 of SFRPRT0DR register controls the state of P0[5].

SFRPRTxSEL: Setting the corresponding bit in this register, enables the control of the port pin through the SFRPRTxDR register.  For example, if Bit5 of SFRPRT0SEL is set, then the state of P0[5] is controlled by the value of Bit5 of SFRPRT0DR register.  If the bit is cleared, the port can be controlled only through the PHUB interface.

These registers are declared in PSoC3_8051.h header file under the cy_boot folder in the workspace explorer.  Details of these registers may be found in PSoC3 Technical Reference Manual under Section 4.6.4 – I/O Port Access SFRs.

For example, to switch On/Off P1[5]

// First enable SFR access for P1[5].  This has to be done only once in the beginning of code
SFRPRT1SEL |= 0x20;

// To switch on
SFRPRT1DR |= 0x20;

// To switch off
SFRPRT1DR &= ~0x20;

// To toggle
SFRPRT1DR ^= 0x20;

Pros: This is the fastest way to modify the port pin state.  This takes  a single direct memory access instruction and takes 3 CPU cycles to execute.

Cons: Not portable.  If you relocate a Port Pin component in the schematic to a different GPIO port, then all the code that access the pin through SFRs have to be rewritten.

Summary:

1. If the project does not have any timing restrictions and you prefer readability over execution time, use Option-1
2. If you are working on time critical application where every processor cycle counts, and readability or portability is not important, use Option-3.
3. Option-2 is the ideal solution where it has 4x speed over Option-1 and does not sacrifice portability.

Rating: (4.6/5) by 5 users
Comments (1)
Sep 22, 2009

The E2PROM user module is a very handy user module for emulating an E2PROM in the Flash program memory.  Here are some key points to remember while using the E2PROM user module. 

My apologies for the very long article.  Wanted to keep it short, but as I kept adding information this grew into a 1000+ word blo(n)g. 

User Module Parameters:

Starting Block: This specifies the flash block number where the E2PROM begins.  Always place the E2PROM in the last blocks of the flash.  For example, in a 32K device, a 256 byte (4 blocks) E2PROM should be placed in blocks 508 to 511.  So, the first block should be 508.  This ensures that maximum space is available for the code memory and prevents clash between code memory and E2PROM.

Remember to modify the flashsecurity.txt file and set the protection level of the flash blocks to “U” or “R”

w  w  w  w  w   w   w   w   w   w   w   w   w   w   w   w ;    Base Address 7800
w  w  w  w  w   w   w   w   w   w   w   w   u   u   u   u ;    Base Address 7C00
; End 32K parts

Length: This parameter sets the size of the E2PROM in bytes.  For the above example, the length parameter would be 256.

E2PROM Write Function:

To write data to the E2PROM, use the E2PROM_bE2Write function.  The prototype of the function is

char E2PROM_bE2Write(WORD wAddr, BYTE *pbData, WORD wByteCount, char Temperature);

wAddr: This is the location in the E2PROM where you would like to write the data.  A very common mistake made is people enter the physical address of the flash location.  The value should be relative location in the E2PROM, not in flash.  For the example mentioned above, to write data to the first location in the E2PROM, the value of wAddr should be 0x0000, not 0x7F00.

*pbData: This is the pointer to the buffer that holds the data you want to write to the E2PROM.  If the data is not in a char buffer, then you use typecasting.  For example, if to write a structure MyStruct, typecast the pointer to (char*)&MyStruct.

wByteCount: This is the number of bytes to be written to the E2PROM.  More about it later.

Temperature: The Temperature parameter is used by the E2PROM API to calculate the flash write pulse width.  At higher temperatures the flash has to be written with a smaller pulse width and at lower temperatures with a longer pulse width.  The flash will meet its maximum endurance and write cycles if it is written with the correct pulse width.  If the device is going to operate within a temperature range of 0 to 50 degrees, it is ok to pass the value of 25 for temperature.  But for operation over the full temperature range, use FlashTemp user module and pass the correct die temperature.  If this is not done, either the data retention or the flash endurance will be compromised.  If the temperature value passed is less than the operating temperature, the flash will be written with a longer pulse width than required.  This will reduce the flash endurance.  On the other hand, if the temperature value passed is higher than the operating temperature, the flash will be written with a lower pulse width than required.  While this does not affect the endurance, the data retention will be less than the guaranteed 10 years. 

The bE2Write function returns the status of the write operation.  A return value of 0x00 means the write was successful.  -1 means error in writing which could be because the flash is write protected. -2 means stack overflow.  Always check the return value in your program to make sure that the write was successful.

Full Block Write vs. Partial Write

E2PROM writes always take place in 64 byte blocks.  When you write less than 64 bytes of data, it is called a partial write.  The flash write API first reads all the 64 bytes from the flash block into RAM, modifies the desired bytes and writes back the 64 bytes of data to Flash.  This results in a heavy RAM overhead requiring 103 bytes of stack space, whereas a full block write takes only 32 bytes of stack.  In devices with only one RAM page, the global variables and stack share the 256 bytes.  If the RAM usage of the globals is high, this could lead to stack overflow errors while performing partial writes.  Under such condition, it is always advisable to perform a 64 byte write.  Even if you are writing say 10 bytes of data, set the ByteCount to 64.  The first 10 bytes will be the actual data followed by data from subsequent RAM locations.  Check out the E2PROM user module data sheet under the section “Efficient Memory Usage” for more details.

Initializing the E2PROM with data:

Many a times you may be using the E2PROM to store calibration data or any other system related data, where you would like to load the E2PROM with some initial values while the device is programmed.  Following method may be used to achieve this.

In C: Use the “#pragma abs_address” directive.  For example, if the E2PROM is placed in the last flash block in a 32K device, and if you wanted to initialize the first 10 bytes with some value:

 

#pragma abs_address 0x7FC0
const char InitialValues[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
#pragma end_abs_address

In assembly:

area eeprom(rom, abs)
org 0x7FC0
db 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09

A Practical Situation – Storing and Retrieving a Structure

Let us take a look at a practical E2PROM usage where a calibration structure is stored, read and written in the E2PROM.

Create a typedef for the structure 

 

typedef struct CAL_STRUCT
{
    int Offset;
    float Scale;
}CAL_STRUCT;

RAM variable to store the calibration values 

 

CAL_STRUCT CalValuesRam;

Initialize the E2PROM with initial values: 

 

#pragma abs_address 0x7FC0
const CAL_STRUCT CalValuesEeprom = {
0x0023,    // Initial value for offset
2.5456   // Initial value for scale
};

Now to read the value from the E2PROM to the RAM you can use either the E2PROM_Read function:

 

E2PROM_E2Read(0x0000, char* &CalValuesRam, sizeof(CalValuesRam));

Or:

 

CalValuesRam = CalValuesEeprom;

To write the values from the RAM to E2PROM

E2PROM_bE2Write(0x0000,(char*)&CalValuesRam, 64, 25);

Rating: (5/5) by 4 users
Comments (0)

  1 to 5 of 10 Results  |   Next  >
ALL CONTENT AND MATERIALS ON THIS SITE ARE PROVIDED "AS IS". CYPRESS SEMICONDUCTOR AND ITS RESPECTIVE SUPPLIERS MAKE NO REPRESENTATIONS ABOUT THE SUITABILITY OF THESE MATERIALS FOR ANY PURPOSE AND DISCLAIM ALL WARRANTIES AND CONDITIONS WITH REGARD TO THESE MATERIALS, INCLUDING BUT NOT LIMITED TO, ALL IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT OF ANY THIRD PARTY INTELLECTUAL PROPERTY RIGHT. NO LICENSE, EITHER EXPRESS OR IMPLIED, BY ESTOPPEL OR OTHERWISE, IS GRANTED BY CYPRESS SEMICONDUCTOR. USE OF THE INFORMATION ON THIS SITE MAY REQUIRE A LICENSE FROM A THIRD PARTY, OR A LICENSE FROM CYPRESS SEMICONDUCTOR.

Content on this site may contain or be subject to specific guidelines or limitations on use. All postings and use of the content on this site are subject to the Terms and Conditions of the site; third parties using this content agree to abide by any limitations or guidelines and to comply with the Terms and Conditions of this site. Cypress Semiconductor and its suppliers reserve the right to make corrections, deletions, modifications, enhancements, improvements and other changes to the content and materials, its products, programs and services at any time or to move or discontinue any content, products, programs, or services without notice.

 
 
FB1.png Twitter1.png linkedin youtube