I'm trying to write a code in C (Using Keil µVision 5, device: AT89C51AC3) that lets me enter 2 Integer numbers, add them and then print them out. The problem is that I'm limited to a byte code size of max. 2048.
My actual code needs 2099 Bytes to run.
Any idea how I could do the same thing using less memory?
#include <stdio.h>
#include <REG52.H>
int main()
{
int a, b;
/*------------------------------------------------
Setup the serial port for 1200 baud at 16MHz.
------------------------------------------------*/
#ifndef MONITOR51
SCON = 0x50; /* SCON: mode 1, 8-bit UART, enable rcvr */
TMOD |= 0x20; /* TMOD: timer 1, mode 2, 8-bit reload */
TH1 = 221; /* TH1: reload value for 1200 baud # 16MHz */
TR1 = 1; /* TR1: timer 1 run */
TI = 1; /* TI: set TI to send first char of UART */
#endif
printf("Enter 2 numbers\n");
scanf("%d%d",&a,&b);
printf("%d\n",a+b);
return 0;
}
You should hiccup when you see this simple code take up 2k+ of memory. That's a lot! The reason for this is that the stdio functions are terribly inefficient.
If you need to save memory and execution speed, you need to code these yourself. Which is not so hard, since you probably just need to read integers and not everything else those function can handle (float numbers, strings etc).
Also get rid of the int type, use the fixed size types from stdint.h instead. (If this is a 8 bit MCU, you should also avoid 16 bit numbers unless they are necessary.)
In addition, you will have to code the I/O part as well. On a microcontroller this would probably mean writing your own UART driver.
You should be able to reduce the code size to a couple of hundred bytes, depending on how code (in)efficient your microcontroller is.
If you just want to print the sum of int a and int b, you should be able to get rid of the
/------------------------------------------------
Setup the serial port for 1200 baud at 16MHz.
------------------------------------------------/
#ifndef MONITOR51
SCON = 0x50; /* SCON: mode 1, 8-bit UART, enable rcvr /
TMOD |= 0x20; / TMOD: timer 1, mode 2, 8-bit reload /
TH1 = 221; / TH1: reload value for 1200 baud # 16MHz /
TR1 = 1; / TR1: timer 1 run /
TI = 1; / TI: set TI to send first char of UART */
#endif:
`
code. Just keep the printf()... and scarf()... functions.
Related
I'm learning embedded c for microcontrollers using a PICKIT. When I send the voltage readings from an LDR via serial communication to RealTerm I end up with the voltages just continuing on the line (see pic) and not starting a new line every reading, how would I change it to look like this? Thanks in advance.
0.9V
0.89V
0.9V
RealTerm data
#pragma config FEXTOSC = HS // External Oscillator mode Selection bits (HS (crystal oscillator)above 8 MHz; PFM set to high power)
#pragma config RSTOSC = EXTOSC_4PLL// Power-up default value for COSC bits (EXTOSC with 4x PLL, with EXTOSC operating per FEXTOSC bits)
// CONFIG3L
#pragma config WDTE = OFF // WDT operating mode (WDT enabled regardless of sleep)
#include <xc.h>
#include <stdio.h>
#include "LCD.h"`
#include "serial.h"
#include ADC.h
define _XTAL_FREQ 64000000 //note intrinsic _delay function is 62.5ns at 64,000,000Hz
void main(void) {
LCD_Init();
initUSART4(); //Initialise EUSART4
ADC_init();
char LDR_arr[16];
unsigned int x;
unsigned int int_part; //initialise int part of voltage
unsigned int frac_part; // initialise fraction part of voltage
//char buf = getCharSerial4();
//Clear Screen
LCD_sendbyte(0b00000001, 0);
while (1)
{
x = ADC_getval();
//ADC2String(LDR_arr, x);
/* max voltage = 3.3V, max LDR value = 255
* 255/3.3 = 77.3 so use 77 for division
* int_part = LDR value/77
* frac_part = (LDRvalue * 100)/77 - int_part*100, giving first 2 DP as integer
*/
int_part = x/77;
frac_part = (x*100)/77 - int_part * 100;
// and format as a string using sprintf (see GitHub readme)
// %02d ensures that 2 numbers are always displayed frac_part
// e.g. frac_part = 5, get 0.05 after decimal instead of 0.5
// which would get if used %01d or 0.005 if used %03d
sprintf(LDR_arr,"%d.%02dV",int_part,frac_part);
sendStringSerial4(LDR_arr); // send voltage to RealTerm
__delay_ms(1000);
LCD_sendbyte(0b00000001, 0); //clear screen
__delay_ms(1.53);
}
}
void sendCharSerial4(char charToSend) {
while (!PIR4bits.TX4IF); // wait for flag to be set
TX4REG = charToSend; //transfer char to transmitter
void sendStringSerial4(char *string){
while(*string!=0){
sendCharSerial4(*string++);
}
}
Edit if I do
sprintf(LDR_arr,"%d.%02dV \n",int_part,frac_part);
or
sprintf(LDR_arr,"%d.%02dV '\n'",int_part,frac_part);
I end up with what the voltages going along a diagonal
RealTerm with '\n' inside
If you look at your ascii table 0x0D is a carriage return CR, 0x0A is a newline. The unix world where C came from or where they developed together tends to be limited to just the new line, before during and after serial terminals default to the separate characters of carriage return (start on a new column) and new line (move down to a new line) the combination is the preferred way to move down and left. \r is carriage return 0x0D and \n is newline 0x0A.
While you can change some dumb terminals to turn a newline into both it is more useful and portable to just do it right in the program. So as pointed out in a comment add \r\n to the end of the string to be printed (or the beginning, your choice).
You need to do one of perhaps three things:
Configure your terminal for "LF-only" line ends, so it implicitly inserted a CR before each LF - all terminal emulation software supports such a configuration,
explicitly output CR+LF line end: "\r\n" in your sprintf() calls.
Modify your low level serial I/O layer to insert a \r before every \n ("text mode").
Of those I'd recommend the first one, since for either of the other two you'd still have to configure the terminal software for CR+LF in any case. It is a coin toss what any particular terminal might be configured for by default - though largely determined by whether it was originally developed for Windows or POSIX perhaps.
In TeraTerm for example:
I am writing a c program to send a digital voltage value in bits to DAC (SAM4E) in order to get an analog output.
The program is below:
#include "asf.h"
#include "conf_board.h"
#include "conf_clock.h"
#include "dacc_example.h"
int main (void)
{
sysclk_init();
board_init();
/* Reset DACC registers */
dacc_reset(DACC_BASE);
/* Half word transfer mode */
dacc_set_transfer_mode(DACC_BASE, 0);
dacc_set_timing(DACC_BASE,0, 0x10);
/* Disable TAG and select output channel DACC_CHANNEL */
dacc_set_channel_selection(DACC_BASE, DACC_CHANNEL);
/* Enable output channel DACC_CHANNEL */
dacc_enable_channel(DACC_BASE, DACC_CHANNEL);
/* Set up analog current */
dacc_set_analog_control(DACC_BASE, DACC_ANALOG_CONTROL);
uint32_t dac_val = 0;
while (1)
{
// set voltage to minimum
#define SET_DAC_VAL 0x000
//set voltage to midpoint
// set voltage to 2.7V
//set voltage to 3.2V
//write the conversion value
dacc_write_conversion_data(DACC_BASE, SET_DAC_VAL, DACC_CHANNEL);
}
}
In the above code, I have configured the DAC and in the while loop I am trying to send the digital values to be converted into data conversion register. I want to set the voltages to minimum, midpoint, 2.7V and 3.2V. The maximum voltage is 3.3v and DAC is of 12 bit resolution.
Can anyone help me to set the voltage ??
or
May I know how we represent 2.7 V in 12 bit binary format??
Can anyone help me to set the voltage ??
Setting the voltage is going to depend on how you are interacting with the DAC. Assuming that you are using a microcontroller, then you need to refer to the datasheet. It will describe the registers in the DAC module and how they work. Most of the time there is a code example in there as well.
May I know how we represent 2.7 V in 12 bit binary format??
Your DAC has a 12-bit resolution. This means that you have 12-bits to represent the voltage value that you want to output on the DAC. This means you can represent 2^12 - 1 increments. Assuming that the DAC has a reference voltage of 3.3V, then each increment is 3.3/(2^12 - 1) volts. So if we wanted 2.7V, it would be represented by the value 2.7/(3.3/(2^12 - 1)) = 3350.
I am designing an IIR 2nd order Lowpass filter with sampling frequency = 100Hz and cutoff frequency = 10 Hz. The filter coefficients are of Chebyshev Type I using fdatool in Matlab.
But the code is not able to filter the signal (i.e. for all frequencies it gives the output with same amplitudes as the input signal) . Only minor decrease in amplitude is observed for an input signal of 10 KHz and above. I assure you that the ADC and DAC are working fine as i have tested the for FFT filter.
Here is the code:
/* Include core modules */
#include "stm32f4xx.h"
#include "stdint.h"
#include "stdlib.h"
#include "arm_math.h"
#include "my_files.h"
#define URS 2
#define numStages 1
#define NUM_TAPS 5*numStages
#define samples 3
////////ADC FUNCTION//////////////////
void ADC_configure(void)
{
RCC->APB2ENR|=1Ul<<8; // ADC1 clock enabled
ADC1->CR2|=0x00000001; // enable ADC
ADC1->CR1|=0; // single conversion ADC1 pin 0 has been selected
}
int32_t readADC(void)
{
ADC1->CR2|=(1UL<<30);
return(ADC1->DR);
}
////////DAC FUNCTION/////////////////
int32_t dv1,dv2,ds;
//---function declaration--//
// initilising DAC---------//
void DAC_init(void)
{
RCC->APB1ENR|=1UL<<29;
DAC->CR|=((1UL<<16)|(1UL<<0));
RCC->AHB1ENR|=0x00000001; // clock to gpio A
GPIOA->MODER|=0x00000F03; // pt0,4,5 in Analog mode
}
// Sending to DAC...........//
void Send_DAC(int32_t data_in1, int32_t data_in2)
{ dv1=data_in1;
dv2=data_in2<<16;
ds=dv2+dv1;
DAC->DHR12RD=ds;
}
/* IIR settings */
float32_t pState[2*numStages];
const float pCoeffs[NUM_TAPS] = {1,2,1,-1.1997,0.5157};//{b0,b1,b2,a1,a2}
/* Global variables */
float32_t Input[samples]; /* Data to be read from ADC */
float32_t InputData[samples]; /* Data to be processed */
float32_t Output[samples]; /* Output filtered Data */
arm_biquad_cascade_df2T_instance_f32 S; /* ARM IIR module */
uint16_t i;
void TIM3_Init (void) {
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; /* enable clock for TIM1 */
TIM3->PSC = 8600; /* set prescaler = 10KHz */
TIM3->ARR = 100; /* set auto-reload = 10ms */
TIM3->RCR = 0; /* set repetition counter */
TIM3->CR1 |= (1UL << URS);
TIM3->DIER = TIM_DIER_UIE; /* Update Interrupt enable */
NVIC_EnableIRQ(TIM3_IRQn); /* TIM1 Interrupt enable */
NVIC_SetPriority (TIM3_IRQn, 0);
TIM3->CR1 |= TIM_CR1_CEN; /* timer enable */
}
void TIM3_IRQHandler() {
/*Shift Operation*/
for(i=samples-1;i>0;i--){
Input[i]= Input[i-1];
InputData[i]= Input[i];
}
/* Input part from the ADC */
Input[0] = (float32_t)readADC();
InputData[0] = Input[0];
//////////IIR//////////////////////
/* Initialize the IIR module */
arm_biquad_cascade_df2T_init_f32(&S, numStages, pCoeffs, pState);
/* Process the data through the IIR module */
arm_biquad_cascade_df2T_f32(&S, InputData, Output, samples);
////////DAC Output/////////////////
Send_DAC(Input[0], Output[0]);
}
/////////main function///////////////
int main(void) {
/* Initialize system */
SystemInit();
DAC_init();
ADC_configure();
TIM3_Init();
while (1) {
}
}
Any suggestion or solution would be of great help.
Some possible problems:
Did you enable the FPU?
Check alignment for ADC (and DAC?).
Ensure the interrupt-handler does not run too long (overflow).
Good you do not use the stdlib for much more tha init, btw. But you really should use symbolic constants for the register initialization! This does not cost extra.
Not directly related, but will(!) give wrong results: If I get it right, you trigger each conversion in readADC. This leads to jitter (resulting in noise on the digitized signal); trigger the conversations by a timer (that's what the trigger system is for actually) and use the ADC-interrupt to read the data or use a DMA (the STM DMA provides a double-buffer mode which is perfect for this). In this simple example, if using DMA, you can even get along completely without interrupt and do the calculations in the main program.
For the DAC you should the same.
Not sure why use a timer anyway; the ADC can self-trigger. Is that not sufficient?
You do not need to init IIR filter every time. Do it only once in init code. Init procedure clears previous values in pState, but they are required for IIR to perform correctly. That's the reason why your filter doesn't work. Presence of FPU only influences the speed of computation.
I am using STM32F2 controller and I am interfacing with an ST7036 LCD display via 8 bit parallel interface.
The datasheet says there should be a 20 nano second delay between address hold and setup time.
How do I generate a 20 nanosecond delay in C?
Use stopwatch_delay(4) below to accomplish approximately 24ns of delay. It uses the STM32's DWT_CYCCNT register, which is specifically designed to count actual clock ticks, located at address 0xE0001004.
To verify the delay accuracy (see main), you can call STOPWATCH_START, run stopwatch_delay(ticks), then call STOPWATCH_STOP and verify with CalcNanosecondsFromStopwatch(m_nStart, m_nStop). Adjust ticks as needed.
uint32_t m_nStart; //DEBUG Stopwatch start cycle counter value
uint32_t m_nStop; //DEBUG Stopwatch stop cycle counter value
#define DEMCR_TRCENA 0x01000000
/* Core Debug registers */
#define DEMCR (*((volatile uint32_t *)0xE000EDFC))
#define DWT_CTRL (*(volatile uint32_t *)0xe0001000)
#define CYCCNTENA (1<<0)
#define DWT_CYCCNT ((volatile uint32_t *)0xE0001004)
#define CPU_CYCLES *DWT_CYCCNT
#define CLK_SPEED 168000000 // EXAMPLE for CortexM4, EDIT as needed
#define STOPWATCH_START { m_nStart = *((volatile unsigned int *)0xE0001004);}
#define STOPWATCH_STOP { m_nStop = *((volatile unsigned int *)0xE0001004);}
static inline void stopwatch_reset(void)
{
/* Enable DWT */
DEMCR |= DEMCR_TRCENA;
*DWT_CYCCNT = 0;
/* Enable CPU cycle counter */
DWT_CTRL |= CYCCNTENA;
}
static inline uint32_t stopwatch_getticks()
{
return CPU_CYCLES;
}
static inline void stopwatch_delay(uint32_t ticks)
{
uint32_t end_ticks = ticks + stopwatch_getticks();
while(1)
{
if (stopwatch_getticks() >= end_ticks)
break;
}
}
uint32_t CalcNanosecondsFromStopwatch(uint32_t nStart, uint32_t nStop)
{
uint32_t nDiffTicks;
uint32_t nSystemCoreTicksPerMicrosec;
// Convert (clk speed per sec) to (clk speed per microsec)
nSystemCoreTicksPerMicrosec = CLK_SPEED / 1000000;
// Elapsed ticks
nDiffTicks = nStop - nStart;
// Elapsed nanosec = 1000 * (ticks-elapsed / clock-ticks in a microsec)
return 1000 * nDiffTicks / nSystemCoreTicksPerMicrosec;
}
void main(void)
{
int timeDiff = 0;
stopwatch_reset();
// =============================================
// Example: use a delay, and measure how long it took
STOPWATCH_START;
stopwatch_delay(168000); // 168k ticks is 1ms for 168MHz core
STOPWATCH_STOP;
timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
printf("My delay measured to be %d nanoseconds\n", timeDiff);
// =============================================
// Example: measure function duration in nanosec
STOPWATCH_START;
// run_my_function() => do something here
STOPWATCH_STOP;
timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
printf("My function took %d nanoseconds\n", timeDiff);
}
The first specification I found of Stm32f2 assumes a clock frequency of 120 MHz. That's about 8ns per clock cycle. You would need about three single cycle instructions between successive write or read/write operations. In C, a++; will probably do (if a is located in stack).
You should look into the FSMC peripheral available in your chip. While the configuration might be complicated, especially if you're not dropping in a memory part that it was designed for, you might find that your parallel interfaced device maps pretty well to one of the memory interface modes.
These sorts of external memory controllers must have a bunch of configurable timing options to support the range of different memory chips out there so you'll be able to guarantee the timings required by your datasheet.
The nice benefit of being able to do this is your LCD will then seem like any old memory mapped peripheral, abstracting away the lower level interfacing details.
I am trying to read analogic signal for a sort of mouse with a pic18f14k50 controller. Here the simple circuit: http://dl.dropbox.com/u/14663091/schematiconew.pdf . I have to read analogic signal from AN9 circuit port. Main function reads from the port, and blinks 30 time if threshold is reached:
void main(void) {
InitializeSystem();
#if defined(USB_INTERRUPT)
USBDeviceAttach();
#endif
while(1) {
if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) continue;
if(!HIDTxHandleBusy(lastTransmission))
{
int readed = myReadADC2(); //Here i tried both myReadADC2() or myReadADC1()
if(readed>40) { //If read threshold > 40, blink led 30 times
int i;
for(i=0; i<30; i++) {
Delay1KTCYx(0);
mLED_1_On();
Delay1KTCYx(0);
mLED_1_Off();
}
}
lastTransmission = HIDTxPacket(HID_EP, (BYTE*)hid_report_in, 0x03);
}//end while
}//end main
I used two method to read from the AN9 port, myReadADC() that uses OpenADC() API method:
int myReadADC(void) {
#define ADC_REF_VDD_VDD_X 0b11110011
OpenADC(ADC_FOSC_RC & ADC_RIGHT_JUST & ADC_12_TAD, ADC_CH9 & ADC_INT_OFF, ADC_REF_VDD_VDD_X & ADC_REF_VDD_VSS, 0b00000010); // channel 9
SetChanADC(ADC_CH9);
ConvertADC(); // Start conversion
while(BusyADC()); // Wait for completion
return ReadADC(); // Read result
}
and myReadADC2(), that implements manual read from the port.
int myReadADC2() {
int iRet;
OSCCON=0x70; // Select 16 MHz internal clock
ANSEL = 0b00000010; // Set PORT AN9 to analog input
ANSELH = 0; // Set other PORTS as Digital I/O
/* Init ADC */
ADCON0=0b00100101; // ADC port channel 9 (AN9), Enable ADC
ADCON1=0b00000000; // Use Internal Voltage Reference (Vdd and Vss)
ADCON2=0b10101011; // Right justify result, 12 TAD, Select the FRC for 16 MHz
iRet=100;
ADCON0bits.GO=1;
while (ADCON0bits.GO); // Wait conversion done
iRet=ADRESL; // Get the 8 bit LSB result
iRet += (ADRESH << 8); // Get the 2 bit MSB result
return iDelay;
}
Both cases doesn't works, i touch (sending analogic signal) port AN9 but when I set high threshold (~50) led don't blinks, with low threshold (~0) it blinks immidiatly when i provide power to the PIC. Maybe i'm using wrong port? I'm actually passing AN9 as reading port? Or maybe threshold is wrong? How can i found the right value? Thank you
Here the MPLAB C18 Apis http://dl.dropbox.com/u/14663091/API%20microchip%20C18.pdf .
Regarding function myReadADC2(): you need to switch ANSEL and ANSELH configs as RC7/AN9 is configured in bit 1 of ANSELH. Also call me paranoid but for the line
iRet += (ADRESH << 8);
I always like to either save it a temporary variable first or cast explicitly the value ADRESH before shifting it up:
iRet += (((UINT) ADRESH) << 8);
That way I know for sure the bits won't get lost when shifting up which has bitten me before.
Regarding function myReadADC():
OpenADC() only takes two parameters. I presume that bitfield in the third parameter field is for the analog enable (ADRESH/ADRES). I'm assuming that's handled by SetChanADC() but you may have to set ADRESH/ADRES manually. It may help to set a breakpoint in the debugger and stop after configuration is complete to make sure your registers are set appropriatley.