Hi,
I am trying to generate PPM signal using MCS9S08SG4 microcontroller but have been unsuccessful so far. I am using time pulse modualtor to generate these pulses. Since time period for sending logic 0 and logic 1 is different so I have set two mod counter( TPM1MOD) one for each logic 0 and logic 1 after checking which bit has to be sent. But the problem is that I am redundant pulses which do not want. The whole purpose of writing this code is that I am writing this code to implement steering wheel control for a vehicle whose input is given to car audio. Can anybody tell me if I am using the correct approach. Please see my code below. Also check PDP for the protocol that I am trying to implement.
#include <hidef.h> /* for EnableInterrupts macro */
#include "derivative.h" /* include peripheral declarations */
// DEFINE ////////////////////////////////////////////////////////////
//---------------------------------------------------------------------
#define CH7 0b00000000
// GLOBAL VARIABLES ///////////////////////////////////////////////////
//---------------------------------------------------------------------
#pragma CONST_SEG SATINDER
const unsigned int Count;
unsigned int repeat_frame=0;
unsigned int i=0;
unsigned int flag=0;
unsigned int bit=0;
const unsigned int Mode[33] = {1,0,0,1,1,1,0,1,0,1,1,0,0,0,1,0,1,1,0,0,1,0,0,0,0,0,1,1,0,1,1,1,1};
const unsigned int SeekPlus[] = {1,0,0,1,1,1,0,1,0,1,1,0,0,0,1,0,1,1,0,1,0,0,0,0,0,0,1,0,1,1,1,1,1};
const unsigned int SeekMinus[33] = {1,0,0,1,1,1,0,1,0,1,1,0,0,0,1,0,0,1,0,1,0,0,0,0,1,0,1,0,1,1,1,1,1};
const unsigned int VolumePlus[33] = {1,0,0,1,1,1,0,1,0,1,1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,1,0,1,0,1,1,1,1};
const unsigned int VolumeMinus[33] = {1,0,0,1,1,1,0,1,0,1,1,0,0,0,1,0,1,0,1,0,1,0,0,0,0,1,0,1,0,1,1,1,1};
#pragma CONST_SEG DEFAULT
unsigned int Input_Voltage=0;
// FUNCTION HEADERS ///////////////////////////////////////////////////
//---------------------------------------------------------------------
void MCU_Init(void);
void MCU_Delay(word delay);
void Gen_PWM(const unsigned int data[]);
word read_adc(byte chan); // Analog-to-Digital Converter byte word: enter channel to read
// *** MAIN ***********************************************************
//---------------------------------------------------------------------
void main(void) {
//MCU_Init(); // MCU Initialization; has to be done prior to anything else
unsigned int j=0;
SOPT1=0;// disable COP, enable Stop, map TPMCH1 to PTB5; Reset & Bkgd = BDM
SOPT2=0x00;
ADCCFG=0x28;
APCTL1=0xFF;
ICSC1=0x04;
ICSC2=0x40; //8 Mhz clock.
//ICSTRM=0x80;
EnableInterrupts;
for(;
{
//Poll for StartPI flag
Input_Voltage = read_adc(CH7);
if(Input_Voltage>=0 && Input_Voltage<=895) {
Gen_PWM(SeekPlus);
}
/*else if(Input_Voltage>=941 && Input_Voltage<=945){
Gen_PWM(SeekMinus);
}
else if(Input_Voltage>=735 && Input_Voltage<738) {
Gen_PWM(VolumePlus);
}
else if(Input_Voltage>=819 && Input_Voltage<=821){
Gen_PWM(VolumeMinus);
}
else if(Input_Voltage>=625 && Input_Voltage<=628){
Gen_PWM(Mode);
} */
}
}
void Gen_PWM(const unsigned int data[])
{
i=0;
APCTL1=0x00;
// ### Init_TPM init PWM code
TPM1C0SC = 0; // Channel 0 int flag clearing (first part)
TPM1C0SC = 0x28; //OUTPUT COMPARE MODE Edge Aligned High True Pulses.
TPM1SC = 0x4C; // Prescale clock 16
while(i!=33)
{
if(flag==0) {
if(data[i]==0)
{
TPM1C0V = 0x230; // time-period of 1.125ms
TPM1C0V=0x117; // Duty Cycle of .565ms //Logic 0
flag=1;
}
if(data[i]==1) {
TPM1MOD =0x464; //time-period of 2.25ms
TPM1C0V=0x117; // Duty Cycle of .565ms
flag=1;
//Logic 1
}
}
}
}
void MCU_Delay(word delay) // Delay in multiples of 100us (e.g. use 10000 for 1 second)
{
word delw;
for (delw=0;delw<delay;delw++)
{
}
}
word read_adc(byte chan) // Analog-to-Digital Converter byte read: enter channel to read
{
ADCSC1=chan;
while (!ADCSC1_COCO);
return ADCR;
}
void interrupt VectorNumber_Vtpm1ovf Vtpm1ovf_ISR(void)
{
TPM1SC;
TPM1SC_TOF = 0;
i++;
flag=0;
}
已解决! 转到解答。
Hello,
I think that your ISR code is faulty, and you are managing to add two different delays together at the beginning of the "start gap". I cannot see that it is nessary to create two further variables to flag the start sequence. Simply set the variable 'flag' to an initial value of 2. The operation of the ISR is then simpler to understand and control.
flag
2 Start sequence in progress
1 Data sequence in progress
0 Sequence completed
I also notice that you now wait until the pulse sequence is complete before exiting from the start_seq() function. The previous arrangement where the wait occurred until the previous sequence was completed, and a new sequence then started, is more efficient. If other operations need to occur, these can then commence during the pulse sequence output.
Additionally, the initialisation of the APCTL1 and TPM1SC registers within the start_seq() function would seem inappropriate. If these settings do not alter, this code should be associated with the rest of the MCU initialisation.
See if the following code will provide the correct start sequence.
// Timing values, assuming TPM1 clock is 1 MHz#define PULSEW 565#define GAPW_0 1125#define GAPW_1 2250#define LPULSEON 9000#define LPULSEOFF 4500#define BITMAX 32 // Number of gaps in pulse sequence
void start_seq( const byte *p){ // Wait for completion of previous sequence while (flag) __RESET_WATCHDOG(); flag = 2; // Commence new sequence gp = p; // Initialise global pointer TPM1C0V +=10 ; // Arbitrary delay to first pulse of sequence TPM1C0SC_CH0F = 0; // Ensure flag is clear TPM1C0SC = 0x5C; // Set channel output on next compare}__interrupt VectorNumber_Vtpm1ch0 void ISR_TPM1CH0( void){ static byte i = 0; // Bit (gap) counter TPM1C0SC_CH0F = 0; // Clear flag if (TPM1C0SC_ELS0A) { // Channel output last set (start of pulse) if (flag == 2) TPM1C0V += LPULSEON; // Start pulse else TPM1C0V += PULSEW; // Normal pulse TPM1C0SC = 0x58; // Clear output on next compare } else { // Channel output last cleared (gap period) if (flag == 2) { TPM1C0V += LPULSEOFF;// Start gap TPM1C0SC = 0x5C; // Set output on next compare flag = 1; } else { // flag < 2 if (i < BITMAX) { // Sequence not yet complete if (*gp) // Next logic 1 TPM1C0V += GAPW_1; else // Next logic 0 TPM1C0V += GAPW_0; gp++; i++; TPM1C0SC = 0x5C; // Set output on next compare } else { // Pulse sequence is complete TPM1C0SC = 0x00; // Disable further interrupts i = 0; // Ready for next sequence flag = 0; // Flag completion of sequence } } }}
Regards,
Mac
Hello, and welcome to the forum.
Note that for a PPM signal, the gap width will indicate the current bit state, and there will be one more pulse than there are gaps. It is unclear whether you need 32 gaps and 33 pulses, or 33 gaps and 34 pulses. I suspect it is the first combination. So the overall PPM sequence may be perceived as a "start" pulse, followed by the required number of gap-pulse combinations, or alternatively as the required number of pulse-gap combinations, followed by an "end" pulse.
The use of PWM mode to generate this signal has difficulties because of the coherency mechanism associated with altering the TPM1MOD setting. The TPM1MOD value will not be updated until just prior to overflow occurring (and the pulse output going high), just prior to the next output pulse. Additionally, the update of TPM1MOD will need to be synchronised for each bit, requiring that an interrupt occur every TPM overflow period.
A simpler process for the data synchronisation, would be to make use of output compare mode for a free-running TPM module, with the penalty of twice as many interrupts - one for each pulse edge. With this method, the TPM1C0V setting provides the delay to the next edge, whether positive or negative.
I also have a general comment - the code will be more efficient for a 8-bit MCU, if 'unsigned char' (or 'byte') sized variables are used, rather than 'unsigned int', where the smaller size will suffice. For your constant tables, this will also halve the storage space required.
The following code snippet example demonstrates the use of output compare mode. It uses 33 pulse-gap combinations, as set by the macro BITMAX, followed by an "end" pulse to terminate the sequence.
// Global variables:volatile byte *gp; // Global pointer to pulse sequence datavolatile byte flag; // Pulse sequence complete flagconst byte Mode[] = { 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1 };const byte SeekPlus[] = { 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1 };const byte SeekMinus[] = { 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1 };const byte VolumePlus[] = { 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1 };const byte VolumeMinus[] = { 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1 };// Function prototypes:void start_seq( byte *p);
// Timing values, assuming TPM1 clock is 1 MHz#define PULSEW 565#define GAPW_0 1125#define GAPW_1 2250#define BITMAX 33 // Number of gaps in pulse sequence/***********************************************************************/// Start pulse output sequence// On entry, p points to the start of the required sequence datavoid start_seq( byte *p){ // Wait for completion of previous sequence while (flag) __RESET_WATCHDOG(); flag = 1; // Commence new sequence gp = p; // Initialise global pointer TPM1C0V += 10; // Arbitrary delay to first pulse of sequence TPM1C0SC_CH0F = 0; // Ensure flag is clear TPM1C0SC = 0x5C; // Set channel output on next compare}/***********************************************************************/// ISR functions:// TPM1 module, channel 0:__interrupt VectorNumber_Vtpm1ch0 void ISR_TPM1CH0( void){ static byte i = 0; // Bit (gap) counter TPM1C0SC_CH0F = 0; // Clear flag if (TPM1C0SC_ELS0A) { // Channel output last set (start of pulse) TPM1C0V += PULSEW; // Next compare at end of pulse TPM1C0SC = 0x58; // Clear output on next compare } else { // Channel output last cleared (gap period) if (i < BITMAX) { // Sequence not yet complete if (*gp) // Next logic 1 TPM1C0V += GAPW_1; else // Next logic 0 TPM1C0V += GAPW_0; gp++; i++; TPM1C0SC = 0x5C; // Set output on next compare } else { // Pulse sequence is complete TPM1C0SC = 0x00; // Disable further interrupts i = 0; // Ready for next sequence flag = 0; // Flag completion } }}
You may start a PPM sequence with the following invocation example. The cast avoids a compiler warning message because the data pointed is 'const'.
start_seq( (byte *)SeekPlus);
If the actual data sequence happened to consist of 32 bits, each sequence could be expressed as a 32-bit value, greatly compressing the constant data. Obviously this would need a different method to access each bit value.
Regards,
Mac
Hello,
Some additional information has come to light. It turns out that the PPM protocol is actually comprised of two bytes only, an address byte and a command byte. However, to provide a constant transmission duration for each codeword, two addittional bytes have been incorporated.
Byte 1: Address byte
Byte 2: One's complement of address byte
Byte 3: Command byte
Byte 4: One's complement of command byte
This means that there will be an equal number of one's and zero's within each 32 bit transmission. The consequence of this is that the storage requirement for each codeword type may be reduced from 33 bytes, down to two bytes, a significant saving.
const byte Mode[] = { 0x9D, 0xC8 }; // Addr + Cmdconst byte SeekPlus[] = { 0x9D, 0xD0 };const byte SeekMinus[] = { 0x9D, 0x50 };const byte VolumePlus[] = { 0x9D, 0x28 };const byte VolumeMinus[] = { 0x9D, 0xA8 };
To handle the codeword data with this format, a few changes to the remaining code will be necessary.
typedef union { dword dw; byte b[4];} DWORDSTR;// Global variables:volatile DWORDSTR send_dat;volatile byte flag; // Pulse sequence complete flag// Function prototypes:void start_seq( const byte *p);
// Start pulse output sequence// On entry, p points to the start of the required sequence datavoid start_seq( const byte *p){ // Wait for completion of previous sequence while (flag) __RESET_WATCHDOG(); // Transfer sequence data to working register send_dat.b[0] = *p; send_dat.b[1] = ~(*p); p++; send_dat.b[2] = *p; send_dat.b[3] = ~(*p); flag = 1; // Commence new sequence TPM1C0V += 10; // Arbitrary delay to first pulse of sequence TPM1C0SC_CH0F = 0; // Ensure flag is clear TPM1C0SC = 0x5C; // Set channel output on next compare}/**********************************************************************/// ISR functions:// TPM1 module, channel 0:__interrupt VectorNumber_Vtpm1ch0 void ISR_TPM1CH0( void){ static byte i = 0; // Bit (gap) counter TPM1C0SC_CH0F = 0; // Clear flag if (TPM1C0SC_ELS0A) { // Channel output last set (start of pulse) TPM1C0V += PULSEW; // Next compare at end of pulse TPM1C0SC = 0x58; // Clear output on next compare } else { // Channel output last cleared (gap period) if (i < BITMAX) { // Sequence not yet complete if (send_dat.b[0] & 0x80) // Next logic 1 TPM1C0V += GAPW_1; else // Next logic 0 TPM1C0V += GAPW_0; send_dat.dw <<= 1; // Next bit to MSB position i++; TPM1C0SC = 0x5C; // Set output on next compare } else { // Pulse sequence is complete TPM1C0SC = 0x00; // Disable further interrupts i = 0; // Ready for next sequence flag = 0; // Flag completion } }}
Only two line have altered within the ISR function. This would provide significant reduction of code size.
Regards,
Mac
hi mac,
Thanks you for the second alternative solution, but I want one little thing to be added in the program. Before beginning of each transmission I need to send a leader pulse code of 9ms followed by 4.5 ms of off time. I have made changes to my code for the same but my off-time is more than 4.5ms( it is 6.8 ms) which also explains the missing first bit of the sequence. Kindly see the attachement for details. Also check my changes made to ISR.
Hello,
I think that your ISR code is faulty, and you are managing to add two different delays together at the beginning of the "start gap". I cannot see that it is nessary to create two further variables to flag the start sequence. Simply set the variable 'flag' to an initial value of 2. The operation of the ISR is then simpler to understand and control.
flag
2 Start sequence in progress
1 Data sequence in progress
0 Sequence completed
I also notice that you now wait until the pulse sequence is complete before exiting from the start_seq() function. The previous arrangement where the wait occurred until the previous sequence was completed, and a new sequence then started, is more efficient. If other operations need to occur, these can then commence during the pulse sequence output.
Additionally, the initialisation of the APCTL1 and TPM1SC registers within the start_seq() function would seem inappropriate. If these settings do not alter, this code should be associated with the rest of the MCU initialisation.
See if the following code will provide the correct start sequence.
// Timing values, assuming TPM1 clock is 1 MHz#define PULSEW 565#define GAPW_0 1125#define GAPW_1 2250#define LPULSEON 9000#define LPULSEOFF 4500#define BITMAX 32 // Number of gaps in pulse sequence
void start_seq( const byte *p){ // Wait for completion of previous sequence while (flag) __RESET_WATCHDOG(); flag = 2; // Commence new sequence gp = p; // Initialise global pointer TPM1C0V +=10 ; // Arbitrary delay to first pulse of sequence TPM1C0SC_CH0F = 0; // Ensure flag is clear TPM1C0SC = 0x5C; // Set channel output on next compare}__interrupt VectorNumber_Vtpm1ch0 void ISR_TPM1CH0( void){ static byte i = 0; // Bit (gap) counter TPM1C0SC_CH0F = 0; // Clear flag if (TPM1C0SC_ELS0A) { // Channel output last set (start of pulse) if (flag == 2) TPM1C0V += LPULSEON; // Start pulse else TPM1C0V += PULSEW; // Normal pulse TPM1C0SC = 0x58; // Clear output on next compare } else { // Channel output last cleared (gap period) if (flag == 2) { TPM1C0V += LPULSEOFF;// Start gap TPM1C0SC = 0x5C; // Set output on next compare flag = 1; } else { // flag < 2 if (i < BITMAX) { // Sequence not yet complete if (*gp) // Next logic 1 TPM1C0V += GAPW_1; else // Next logic 0 TPM1C0V += GAPW_0; gp++; i++; TPM1C0SC = 0x5C; // Set output on next compare } else { // Pulse sequence is complete TPM1C0SC = 0x00; // Disable further interrupts i = 0; // Ready for next sequence flag = 0; // Flag completion of sequence } } }}
Regards,
Mac
Hi,
The code is up and runing beautifully is I check it across pin 16(PTA0) of MCU(See waveform 1.jpg). But in my circuit design the output of pin 16 is fed to ST VND1NV04 MOSFET which is connected in order to drive the load to the input of Audio system. But the MOSFET does an unwanted thing i.e. it inverts the pulses(see waverform 2.jpg) and keeps the output high all the time. To counteract this I have set the PTA0 pin high at the start so that MOSFET drives the output LOW and then waveform should come fine. I also exchanged the values of LPULSEON with LPULSEOFF and GAPW_0 with GAPW_1 but even with this modification i couldn't get the waveforms as same as at pin 16 of the MCU. So the current need is that output should same as that at TPM1CH0 at pin 16 considering that a MOSFET is connected between the pin 16 and audio input.
Hello,
To account for the waveform inversion through the MOSFET, all positive output edges need to become negative edges, and vice versa. You certainly should not swap any of the time intervals since there is no change to the pulse sequence timing.
Why do you need such a high current MOSFET (1.7A) - what does the output of this need to drive?
This type of protocol is normally utilised for IR remote control devices, with peak IR LED current 50-100mA. Does your project use an IR LED? If so, the inversion would not be a problem since the LED would conduct when the MOSFET output was low. Also note that the conventional IR receiving device requires that the pulse sequence modulate a sub-carrier of typically 36kHz, or 38kHz.
Regards,
Mac
Hi mac,
As a matter of fact my circuit doesn't include a IR LED but the output of the MOSFET is fed to the point where IR is decoded of remote control in car audio. In simple terms it works as a wired remote where remote keys are replaced by resistance switch ladder network. Whenever a switch is pressed on a steering wheel such as volume up/down, a resistance comes into network and the voltage drop is sensed by the ADC of the MCU. After sensing which key has been pressed the corresponding waveform is generated and sent through wire to the audio IR decoder. Since the waveform received at IR decoder is same as that of a remote, in other sense there is no need of carrier generator.
Although its working right now if I connect the output of MCU straight to the audio input, but the I need to use MOSFET so that it controls the frequency as well as it saves the audio from getting fried. only problem right now is that the MOSFET keeps the 5v high output all the itme which need to be negated by keeping PTAD0 high.
Regards,
thecoder
Hello,
The remote control IR decoders that I am familar with have an open collector (NPN) transistor at their output, and an internal pullup resistor to the supply voltage (10k - 22k). Their operation is such that, when the IR signal is present, the transistor will conduct.
If this is also true for your setup, this would mean that the drain of the MOSFET should be able to connect directly in parallel with the decoder output, and should work without modification to the code. It should also work with a much smaller MOSFET type.
Regards,
Mac
Hello,
I have just noticed that you have modified the start_seq() function from the previous version. This modification does create a potential problem.
Here is the modified version -
/***********************************************************************/// Start pulse output sequence// On entry, p points to the start of the required sequence datavoid start_seq( byte *p){ // Wait for completion of previous sequence// PTAD |= (unsigned char)0x00; //APCTL1=0x00; gp = p; // Initialise global pointer TPM1C0V += 1; // Arbitrary delay to first pulse of sequence TPM1C0SC_CH0F = 0; // Ensure flag is clear TPM1C0SC = 0x5C; // Set channel output on next compare while (flag) __RESET_WATCHDOG(); { flag=2; // Commence new sequence }}
and here is the original version of the function.
void start_seq( const byte *p){ // Wait for completion of previous sequence while (flag) __RESET_WATCHDOG(); flag = 2; // Commence new sequence gp = p; // Initialise global pointer TPM1C0V += 10; // Arbitrary delay to first pulse of sequence TPM1C0SC_CH0F = 0; // Ensure flag is clear TPM1C0SC = 0x5C; // Set channel output on next compare}
Can you see the potential problem? The pointer 'gp' is a global pointer that is modified within the ISR function. By moving the 'while' loop, you have initialised this pointer, ready for the next sequence, potentially before the ISR is done with using the pointer for the current sequence. This is likely to corrupt both of the output sequences.
I had originally allowed 10us for the flag to be cleared, and the ouput compare interrupt to be enabled, prior to the first output compare event. You have reduced this to 1 us, which may be insufficient, even allowing for correct placement of the 'for' loop. If the first interrupt is missed, the start of the sequence will be unecessarily delayed by a full overflow cycle for the TPM (65.5 ms).
Regards,
Mac
Hey mac,
Could you help me with one last thing, for one switch i.e. Mode I need to send only one pulse if the button is kept pressed for and rest of subsequent pulses should have only leader pulse on-off with no data trailing behind. How can I achieve this?
Regards,
thecoder
Hi mac,
I am happy to tell you that I was able to generate the waveforms correctly after negating the inversion of MOSFET. See the attached program for changes that I have made. Thanks a lot It wouldn't have been possible without your help.
Hi Mac,
I think you're right. But that doesn't concerns me right now, what concerns me is that my waveforms are coming upside down because of the MOSFET that I have told you about. As you have told me earlier that to correct that I need to replace all the positive edges by negative ones and vice-versa but yet again I am unsuccessful in doing that. What I am doing right now is that I am keeping the PTA0 high at the very start so that MOSFET negates that. After that in ISR I switched places of LPULSEON with LPULSEOFF and GAPW_1 with GAPW_0. But I am unable to get them right. Is my approach correct to keep the port high. If would be helpful if you could reply with a code snippet. Thanks. Check the waveforms attached which are taken with just keeping the PTA0 high and rest of the code remaining the same.
Regards,
thecoder