I've been trying to figure out why I cannot get a sensible value from multiplying an unsigned int with a float value.
Doing something like 65535*0.1 works as expected but multiplying a float with a uint from memory creates mad values. I have a function that reads an ADC and returns an uin16_t. With this value I am printing it to a 4-digit led-display, which is working fine.
Multiplying the same value with 1.0 returns something different entirely (it's too large for my display so I don't really know what it is).
My code is below but the area of contention is at the bottom in main(). Any help would be great. Thanks
main.c:
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <stdint.h>
#define BAUD 9600
#include <util/setbaud.h>
#define DISP_BRIGHT_CMD 'z'
#define DISP_RESET 'v'
#define ADC_AVG 3
volatile uint8_t hi,lo;
volatile uint16_t result;
ISR(ADC_vect)
{
lo = ADCL;
hi = ADCH;
MCUCR &= ~_BV(SE); //Clear enable sleep
}
void initSerial(void)
{
// set baud rate
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
// set frame format
UCSR0C |= (0x3 << UCSZ00); // 8n1
// set enable tx/rx
UCSR0B = _BV(RXEN0) | _BV(TXEN0);
}
void initADC(void)
{
// AVCC and ADC0
ADMUX = _BV(REFS0);
// Enable, div128, + 1st setup
ADCSRA |= _BV(ADEN)|_BV(ADSC)|_BV(ADPS2)|_BV(ADPS1)|_BV(ADPS0)|_BV(ADIE);
}
uint16_t readADC(void)
{
uint16_t average=0;
// Start Conversion
ADCSRA |= _BV(ADSC);
for (char i=0;i<ADC_AVG;i++) {
MCUCR |= _BV(SE);
ADCSRA |= _BV(ADSC);
__asm volatile("sleep");
MCUCR &= ~_BV(SE);
result = (hi<<8);
result |= lo;
average += result;
}
average /= ADC_AVG;
return average;
}
void sendByte(char val)
{
while (! (UCSR0A & (1<<UDRE0)) ); //wait until tx is complete
UDR0 = val;
}
/*
* Convert voltage to temperature based on a negative coefficient for MAX6613
*/
uint16_t analogToTemp(uint16_t val)
{
uint16_t temp;
//v = 5 * (val/1023.0);
//temp = (1.8455 - (5.0*(val/1023.0)))/0.01123;
temp = (1.8455 - (5.0*(val/1023.0)))*89;
//temp = val * M_PI;
//v = 5 * ( val/1024);
//temp = (2 - v) * 89;
return temp;
}
void initDisplay()
{
sendByte(DISP_RESET);
sendByte(DISP_BRIGHT_CMD);
sendByte(0);
}
void serialSegments(uint16_t val)
{
// 4 digit display
sendByte(val / 1000);
sendByte((val / 100) % 10);
sendByte((val / 10) % 10);
sendByte(val % 10);
}
int main(void)
{
uint16_t calc=0,sense=0;
DDRB |= _BV(DDB5);
PORTB |= _BV(PORTB5);
initSerial();
initADC();
initDisplay();
sei();
MCUCR |= (1 << SM0); // Setting sleep mode to "ADC Noise Reduction"
MCUCR |= (1 << SE); // Sleep enable
for(;;) {
//PORTB ^= _BV(PORTB5);
if (calc>=9999){ // I can't see the real value. Max val on display is 9999
//if (sense>=330){
PORTB |= _BV(PORTB5);
} else {
PORTB &= ~_BV(PORTB5);
}
sense = readADC();
//calc = sense*1.0; // refuses to calculate properly
calc = analogToTemp(sense); // a bunch of zeroes
//calc = 65535*0.1; // a-ok
serialSegments(calc);
_delay_ms(500);
serialSegments(sense);
_delay_ms(500);
}
return 0;
}
Makefile:
# AVR-GCC Makefile
PROJECT=Temp_Display
SOURCES=main.c
CC=avr-gcc
OBJCOPY=avr-objcopy
MMCU=atmega328p
OSC_HZ=16000000UL
OPTIMISATION=2
PORT=/dev/ttyUSB0
CFLAGS=-mmcu=${MMCU} -std=gnu99 -Wall -O${OPTIMISATION} -DF_CPU=${OSC_HZ} -lm -lc
${PROJECT}.hex: ${PROJECT}.out
${OBJCOPY} -j .text -O ihex ${PROJECT}.out ${PROJECT}.hex
avr-size ${PROJECT}.out
$(PROJECT).out: $(SOURCES)
${CC} ${CFLAGS} -I./ -o ${PROJECT}.out ${SOURCES}
program: ${PROJECT}.hex
stty -F ${PORT} hupcl
avrdude -V -F -c arduino -p m168 -b 57600 -P ${PORT} -U flash:w:${PROJECT}.hex
clean:
rm -f ${PROJECT}.out
rm -f ${PROJECT}.hex
EDIT:
OK, i've simplified the code somewhat
#include <avr/io.h>
#include <util/delay.h>
#include <stdint.h>
#define BAUD 9600
#include <util/setbaud.h>
#define DISP_BRIGHT_CMD 'z'
#define DISP_RESET 'v'
void initSerial(void)
{
// set baud rate
UBRR0H = UBRRH_VALUE;
UBRR0L = UBRRL_VALUE;
// set frame format
UCSR0C |= (0x3 << UCSZ00); // 8n1
// set enable tx/rx
UCSR0B = _BV(TXEN0);
}
void sendByte(char val)
{
while (! (UCSR0A & (1<<UDRE0)) ); //wait until tx is complete
UDR0 = val;
}
void initDisplay()
{
sendByte(DISP_RESET);
sendByte(DISP_BRIGHT_CMD);
sendByte(0);
}
void serialSegments(uint16_t val) {
// 4 digit display
sendByte(val / 1000);
sendByte((val / 100) % 10);
sendByte((val / 10) % 10);
sendByte(val % 10);
}
int main(void)
{
uint16_t i=0,val;
DDRB |= _BV(DDB5);
initSerial();
initDisplay();
for(;;) {
val = (uint16_t)(i++ * 1.5);
serialSegments(i);
_delay_ms(500);
serialSegments(val);
_delay_ms(500);
if (val > 9999){
PORTB |= _BV(PORTB5);
} else {
PORTB &= ~_BV(PORTB5);
}
}
return 0;
}
Unsuffixed floating point constant are of type double not float.
Use f suffix to have a float literal, e.g., 0.1f
This can make a huge overhead as MCUs like atmega8 don't have a floating point unit and all floating point operations have to be implemented in firmware by the implementation.
With small devices like atmega8 one usually try to avoid using float operations as without a FPU they are very expensive in CPU cycles.
Now there is no reason an implementation would no correctly translate an expression like:
calc = sense * 1.0;
when calc and sense are of uint16_t type.
Not exactly your code, maybe close enough, maybe not.
First off when I display the output and compare these to lines:
val = (unsigned int)(i++ * 1.5);
...
val = i+(i>>1); i++;
the result is the same. Disassembling shows a few things as well. First off avr-gcc
avr-gcc --version
avr-gcc (GCC) 4.3.4
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
uses floats not doubles, so the comments about 1.5F vs 1.5 are quite valid in general but not relevant here. Second it is producing floating point values, single precision, and is doing floating point math, the compiler did not take a shortcut there, it converts to float, does the multiply then converts back.
Using my hex display routine and your decimal display routine (modified to output on a serial terminal), here again it produces the same output, the floating point math does not appear to be the problem.
I started this task to see if the floating point performance was a killer, and it is, but the timing changes depending on how I test it. it took as much as 157 times longer for the code with the floating point compared to fixed point. If I leave in the call to serialSegments(), but have that call a dummy routine instead of the serial port it is 3 times slower for the float. Also I built two different ways and pulled in a libc/m which used a different set of floating point routines, the floating point routines chosen by the C compiler were 7 times slower than the libc/libm.a sitting in the /usr/lib64/avr/lib/ directory. Once you add the waiting on serial port and other delays you may not notice the difference in timing so this experiment while demonstrating that the float is quite painful, is probably not the smoking gun even if your code is timing sensitive, we are talking a few milliseconds.
In addition to the code below I tried this as well:
for(i=0;i<9999;i++)
{
vala = (unsigned int)(i * 1.5);
valb = i+(i>>1); i++;
if(vala!=valb)
{
hexstring16(i);
hexstring16(vala);
hexstring16(valb);
}
}
No fail. I limited to 9999 because serialSegments() only chops up decimals from 0 to 9999. Now your loop goes beyond that to 65535, but you would see that cause problems without the float though, right?
avr.c
#define UCSRA (*((volatile unsigned char *)(0xC0)))
#define UDR (*((volatile unsigned char *)(0xC6)))
#define TCCR0A (*((volatile unsigned char *)(0x44)))
#define TCCR0B (*((volatile unsigned char *)(0x45)))
#define TCNT0 (*((volatile unsigned char *)(0x46)))
#define TCCR1A (*((volatile unsigned char *)(0x80)))
#define TCCR1B (*((volatile unsigned char *)(0x81)))
#define TCNT1L (*((volatile unsigned char *)(0x84)))
#define TCNT1H (*((volatile unsigned char *)(0x85)))
void dummy ( unsigned int );
void uart_putc ( unsigned char c )
{
while(1) if(UCSRA&0x20) break;
UDR=c;
}
void hexstring16 ( unsigned int d )
{
unsigned int rb;
unsigned int rc;
rb=16;
while(1)
{
rb-=4;
rc=(d>>rb)&0xF;
if(rc>9) rc+=0x37; else rc+=0x30;
uart_putc(rc);
if(rb==0) break;
}
uart_putc(0x0D);
uart_putc(0x0A);
}
#ifdef SEGMENTS
void sendByte(char val)
{
uart_putc(0x30+val);
}
void serialSegments(unsigned int val) {
// 4 digit display
dummy(val / 1000);
dummy((val / 100) % 10);
dummy((val / 10) % 10);
dummy(val % 10);
}
//void serialSegments(unsigned int val) {
//// 4 digit display
//sendByte(val / 1000);
//sendByte((val / 100) % 10);
//sendByte((val / 10) % 10);
//sendByte(val % 10);
//uart_putc(0x0D);
//uart_putc(0x0A);
//}
#else
void serialSegments(unsigned int val)
{
dummy(val);
}
//void serialSegments(unsigned int val)
//{
//hexstring(val);
//}
#endif
int main(void)
{
unsigned int i,val;
volatile unsigned int xal,xbl,xcl;
volatile unsigned int xah,xbh,xch;
hexstring16(0x1234);
TCCR1A = 0x00;
TCCR1B = 0x05;
xal=TCNT1L;
xah=TCNT1H;
for(i=0;i<9999;)
{
val = (unsigned int)(i++ * 1.5);
//serialSegments(val);
//hexstring16(val);
dummy(val);
}
xbl=TCNT1L;
xbh=TCNT1H;
for(i=0;i<9999;)
{
val = i+(i>>1); i++;
//serialSegments(val);
//hexstring16(val);
dummy(val);
}
xcl=TCNT1L;
xch=TCNT1H;
xal|=xah<<8;
xbl|=xbh<<8;
xcl|=xch<<8;
hexstring16(xal);
hexstring16(xbl);
hexstring16(xcl);
hexstring16(xbl-xal);
hexstring16(xcl-xbl);
return 0;
}
dummy.s
.globl dummy
dummy:
ret
vectors.s
.globl _start
_start:
rjmp reset
reset:
rcall main
1:
rjmp 1b
.globl dummy
dummy:
ret
Makefile
all : avrone.hex avrtwo.hex
avrone.hex : avr.c dummy.s
avr-as dummy.s -o dummy.o
avr-gcc avr.c dummy.o -o avrone.elf -mmcu=atmega328p -std=gnu99 -Wall -O2 -DSEGMENTS
avr-objdump -D avrone.elf > avrone.list
avr-objcopy avrone.elf -O ihex avrone.hex
a vrtwo.hex : avr.c vectors.s
avr-as vectors.s -o vectors.o
avr-as dummy.s -o dummy.o
avr-gcc -c avr.c -o avrtwo.o -mmcu=atmega328p -std=gnu99 -Wall -O2 -nostartfiles
avr-ld vectors.o avrtwo.o -o avrtwo.elf libc.a libm.a
avr-objdump -D avrtwo.elf > avrtwo.list
avr-objcopy avrtwo.elf -O ihex avrtwo.hex
clean :
rm -f *.hex
rm -f *.elf
This was all run on an arduino pro mini (atmega328p).
Multiplication 65535*0.1 works because it is optimized and precalculated by the compiler, so it translates to 6554.
calc and sense variables are of type uint16_t, and your function analogToTemp() is of that same type and returns that type too. Your runtime calculation inside this function is with uint16_t. It should be done with float, and then truncated to just integer part and casted to the uint16_t result of the function.
Related
#include <linux/module.h>
#include <linux/kernel.h>
//#include <linux/init.h>
#include <linux/types.h>
#include <linux/input.h>
//#include <linux/input-polldev.h>
//#include <linux/delay.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>
/*
MAHIR SHAHRIAR
30027575
CPSC 359
ASSIGNMENT 2
*/
#define GPIO_CLOCKPIN 23 // pin used for clock output GPIO 11 (SPI_SCLK)
#define GPIO_LATCHPIN 21 // pin used for latch output GPIO 9 (SPI_MISO)
#define GPIO_DATAPIN 19 // pin used for data input from controller GPIO 10 (SPI_MOSI)
#define GPIO_POWERPIN 1 // 3.3 VOLT POWER SOURCE Programmble input to set high/low
#define GPIO_GROUNDPIN 9 // Pin used for grounding
#define GPIO_BASEADDRESS 0xfe200000 // Base address used to offset to other registers
//volatile unsigned *gpio = (unsigned*)0xfe200000; // gpio pointer to base address
volatile uint32_t *gpio = (unsigned*)0xfe200000;
volatile uint32_t *GPFSEL1 = (unsigned*)0xfe200000 + 0x04/4; //pointer to register GPFSEL1 for pins 10-19
volatile unsigned *GPFSEL2 = (unsigned*)0xfe200000 + 0x08/4; //pointer to register GPFSEL2 for pins 20-29
volatile unsigned *GPSET0 = (unsigned*)0xfe200000 + 0x1c/4; //pointer to register GPSET0 for setting high
volatile unsigned *GPCLR0 = (unsigned*)0xfe200000 + 0x28/4; //pointer to register GPCLR0 for setting low
volatile unsigned *GPLEV0 = (unsigned*)0xfe200000 + 0x34/4; //pointer to register GPLEV0 for reading data from pin
volatile unsigned *GPIO_PUP_PDN_CNTRL_REG1 = (unsigned*)0xfe200000 + 0xe8/4; //pointer to register for controlling pull up pull down
// INIT GPIO Function takes user inputs for Initializing a particular GPIO Pin number.
// It takes data such as the PIN NUMBBER , string "input" or "output" ,
// string "high" or "low " and string "pullup" or "pulldown"
// Depending on the arguments passed into the Init_GPIO function or subroutine
static void Init_GPIO(int pin_number, char type, char high_low, char pullup_pulldown) {
// setting valueinput valueoutput and mask to set the input output of the pin type
int valueinput = 0b000 << ( (pin_number-10)*3 ); // bits to be set for input
int valueoutput = 0b001 << ( (pin_number-10)*3 ); // bits to be set for output
int mask = 0b111 << ( (pin_number-10)*3 ); // for ensuring other bits dont change
// condition to check which register to access for the bits and setting to input or output based on
// function calls from main and arguments passed
if(pin_number<=19){
if(type == 'i'){
printf("%x", GPFSEL1);
//*GPFSEL1 = (*GPFSEL1 & ~mask) | (valueinput * mask); //problem
}
else if(type == 'o'){
//*GPFSEL1 = (*GPFSEL1 & ~mask) | (valueoutput * mask);
}
else{
//no type specified
printf("Input type not specified please use 'o' or 'i' ignore if not specifying output input type for pin\n");
}
if(pin_number > 19 && pin_number <=29 ){
if(type == 'i'){
//*GPFSEL2 = (*GPFSEL2 & ~mask) | (valueinput * mask);
}
else if(type == 'o'){
//*GPFSEL2 = (*GPFSEL2 & ~mask) | (valueoutput * mask);
}
else{
//no type specified
printf("Input type not specified please use 'o' or 'i' ignore if not specifying output input type for pin\n");
}
}
// Setting the pin to either high or low
if(high_low == 'h'){
//*GPSET0 = 1 << pin_number;
}
else if(high_low == 'l'){
//*GPCLR0 = 1 << pin_number;
}
else{
// high low not specified
printf("high low not specified please use 'h' or 'l' ignore if not specifying highlow for pin\n");
}
}
// Setting if the pin uses pull up or pull down register
int value_pullup = 0b01 << ( (pin_number -16)*2 );
int value_pulldown = 0b10 << ( (pin_number -16)*2 );
mask = 0b11 << ( (pin_number -16)*2 );
if(pullup_pulldown == 'p'){
//*GPIO_PUP_PDN_CNTRL_REG1 = ( *GPIO_PUP_PDN_CNTRL_REG1 & ~mask ) | (value_pullup) & mask;
}
else if(pullup_pulldown == 'n'){
//*GPIO_PUP_PDN_CNTRL_REG1 = ( *GPIO_PUP_PDN_CNTRL_REG1 & ~mask ) | (value_pulldown) & mask;
}
else {
// user does not set any pull up or pull down value for the assigned pin
printf("pull up pull down not specified please use 'p' or 'n' ignore if not specifying pullup pulldown for pin\n");
}
}
void main() {
printf("pointer address %p " , gpio);
Init_GPIO(18, 'i', 'l', 'p');
volatile uint32_t valueinput = 0b000 << ( (18-10)*3 ); // bits to be set for input
volatile uint32_t valueoutput = 0b001 << ( (18-10)*3 ); // bits to be set for output
volatile uint32_t mask = 0b111 << ( (18-10)*3 );
*GPFSEL1 = (*GPFSEL1 & ~mask) | (valueinput * mask);
}
I am getting segmentation error for the last line in main. I dont know why this is doing that. How do I fix the issue I am trying to change the bits in the register GPFSEL1 for the raspberry pi 4 GPIO for changing the input mode by changing bits to 000 for my pin 18 for putting in into input mode. Any help or direction would be greatly appreciated!
I'm new in the programingo of STM32, I've whatched aome tutorials, but whe I've put in practice the last, keil give me some warnings (the tutorial is about of GPIO INPUT OUTPUT LIBRARY FROM SCRATCH).
the warnigs are:
gpi_drive.c(35): warning: implicit conversion changes signedness: 'int' to 'unsigned long' [-Wsign-conversion]
*CR |= ((dir<<(pin*4)) | (opt<<(pin*4+2)));
~~ ~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
gpi_drive.c(34): warning: variable 'CR' may be uninitialized when used here [-Wconditional-uninitialized]
*CR &= ~(0xfu<<(pin)*4); /*Reset the target pin*/
^~
gpi_drive.c(7): note: initialize the variable 'CR' to silence this warning
volatile unsigned long * CR;
#include "gpi_drive.h"
void init_GP(unsigned short port, unsigned short pin, unsigned short dir, unsigned short opt)
{
volatile unsigned long * CR;
unsigned short offset = 0x00;
if (pin > 7)
{
pin -=8;
offset = 0x01;
}
if (port == 1)
{
RCC_APB2ENR |= 4; /*Enabling port A*/
CR = (volatile unsigned long *) (&GPIO_A + offset);
}
if (port == 2)
{
RCC_APB2ENR |= 8; /*Enabling port B*/
CR = (volatile unsigned long *) (&GPIO_B + offset);
}
if (port == 3)
{
RCC_APB2ENR |= 0x10; /*Enabling port C*/
CR = (volatile unsigned long *) (&GPIO_C + offset);
}
*CR &= ~(0xfu<<(pin)*4); /*Reset the target pin*/ //this is the line 34
*CR |= ((dir<<(pin*4u)) | (opt<<(pin*4+2))); //this is the line 35
}
This is the code that I have, I don't know how to make that the numbers of the rows appears, sorry for this. And if you need more information let know, and thak you so much
I have to build synthesizer and I am using C for programing my ATmega128A. I need to record the keypads pressed and play them after some time. For keypad press I am using polling in the main.c. For playing the keypads I am using Timer1. Every time when the timer expires I am storing the keypad frequency and increment counter for it. During play, I calculate the duration firstle, then play it for that interval. When I want to play the stored song, it ticks for some time and starts to make a long sound.
Also, I want to make possible to press, record and palay simultanous keypads. Can you suggest some algorithm for this?
main.c
#include <avr/io.h>
#include <avr/interrupt.h>
#include "keypad.h"
unsigned char temp; // to get keyboard input to play a note
unsigned char option; //to choose the embedded music to play
#define DELAY 1000
int main(void)
{
DDRG = 0xff; // To send sound to BUZ speakers (BUZ is connected to PG.4)
DDRD = 0x00; // Make it input, to get corresponding key to play a note
PORTD = 0xff; // All bits are 1s, so no button is pressed in the beginning
sei(); //Set Interrupt flag as enabled in SREG register
option = no_music; //No music is played on startup, this is default mode for free playing
// This loop keeps playing forever, so the main functionality
// of the program is below
DDRB = 0xff;
DDRD = 0x00; //ready for input
while(1)
{
temp = PIND; //store keyboard input for temporary variable
//PORTB = PIND;
switch(temp)
{
case 254: { // if 1st pin of PORTD is pressed
play_note(notes5[0]); // play corresponding note from octave 5 for 200ms
break;
}
case 253: { // if 2nd pin of PORTD is pressed
play_note(notes5[1]);
break;
}
case 251: { // if 3rd pin of PORTD is pressed
play_note(notes5[2]);
break;
}
case 247: { // if 4th pin of PORTD is pressed
play_note(notes5[3]);
break;
}
case 239: { // if 5th pin of PORTD is pressed
play_note(notes5[4]);
break;
}
case 223: { // if 6th pin of PORTD is pressed
play_note(notes5[5]);
break;
}
case 191: { // if 7th pin of PORTD is pressed
play_note(notes5[6]);
break;
}
case 127: {
if(isRecordingEnabled){
disableRecording();
//toggling LED as the sign of playing the record
toggleLED();
custom_delay_ms(DELAY);
toggleLED();
custom_delay_ms(DELAY);
custom_delay_ms(DELAY);
play_record();
}else{
//toggling LED as the sign of record start
toggleLED();
enableRecording();
}
}
}
}
return 0;
}
keypad.c
#include "structs.h"
#include "play.h"
#define F_CPU 16000000UL // 16 MHz
#include <util/delay.h>
#define BUFFER_SIZE 100
struct played_note buffer[BUFFER_SIZE];
int i = 0;
int8_t isRecordingEnabled = 0;
int8_t recordIndex = 0;
int8_t pressedNote;
int8_t isPressed = 0;
int8_t isPlaying = 0;
unsigned int ms_count = 0;
#define INTERVAL 100
#define DELAY_VALUE 0xFF
ISR(TIMER1_COMPA_vect){
// every time when timer0 reaches corresponding frequency,
// invert the output signal for BUZ, so it creates reflection, which leads to sound generation
//check whether the key was pressed because
//when the recording is enabled the interrupt is working make sound
if(isPressed || isPlaying)
PORTG = ~(PORTG);
if(isRecordingEnabled){
if(PIND == DELAY_VALUE)
pressedNote = DELAY_VALUE;
if(i == 0){
buffer[i].note = pressedNote;
buffer[i].counter = 0;
i++;
}else{
if(buffer[i - 1].note == pressedNote){
//the same note is being pressed
buffer[i - 1].counter++;
}else{
buffer[i++].note = pressedNote;
buffer[i].counter = 0;
}
}
}
}
void initTimer1(){
TIMSK = (1 << OCIE1A); //Timer1 Comparator Interrupt is enabled
TCCR1B |= (1 << WGM12) | (1 << CS12); //CTC mode, prescale = 256
}
void stopTimer1(){
TIMSK &= ~(1UL << OCIE1A);
TCCR1A = 0; //stop the timer1
TIFR = (1 << OCF1A); //Clear the timer1 Comparator Match flag
}
void enableRecording(){
isRecordingEnabled = 1;
i = 0;
ms_count = 0;
initTimer1();
}
void disableRecording(){
isRecordingEnabled = 0;
stopTimer1();
}
//Timer1A
void play_note_during(unsigned int note, unsigned int duration){
OCR1A = note;
pressedNote = note;
isPressed = 1;
initTimer1();
custom_delay_ms(duration);
stopTimer1();
isPressed = 0;
}
//Timer1A
void play_note(unsigned int note){
play_note_during(note, INTERVAL);
}
void play_record(){
isPlaying = 1;
recordIndex = 0;
int duration;
while(recordIndex < i){
PORTB = buffer[return].counter << 8;
duration = INTERVAL * buffer[recordIndex].counter;
if(buffer[recordIndex].note == DELAY_VALUE)
custom_delay_ms(duration);
else
play_note_during(buffer[recordIndex].note, duration);
recordIndex++;
}
isPlaying = 0;
}
Further references can be found in the following github repository:
https://github.com/bedilbek/music_simulation
Actually your question about how to record and replay key presses, should be anticipated by another question about how to play several sounds simultaneously.
Now you're using just PWM output with variable frequency. But this allows you to generate only a single wave of a square form. You cannot play two notes (except of using another timer and another PWM output).
Instead of that, I suggest you to use PWM at the highest frequency and apply a RC or LC filters to smooth high-frequency PWM signal into a waveform, and then apply that waveform to the amplifier and to the speaker to make a sound.
Having this approach you make generate different waveforms, mix them together, make them louder or more silent and even apply a "fade-out" effect to make them sound like a piano.
But your question is not about how to generate that kind of wave-forms, so if you want to know you should start another question.
So, returning to your question.
Instead of having single procedure, which is starting note, holding a pause and only then returning back; I suggest you to have a several procedures, one play_note(note) - which will start playing a note and returns immediately (while note continue playing). And, of course, stop_note(note) - which will stop specified note, if it is played. Also I suggest you to pass a note number, rather than frequency or timer period to the play function. Let's assume 0 is the lowest possible note (e.g. C2), and then they go sequentially by semitones: 1 - C#2, 2 - D2, .... 11 - B2, 12 - C3 ... etc.
For the first time, you may remake your single-note playing procedures to match that.
// #include <avr/pgmspace.h> to store tables in the flash memory
PROGMEM uint16_t const note_ocr_table[] = {
OCR1A_VALUE_FOR_C2, OCR1A_VALUE_FOR_C2_SHARP, ... etc
}; // a table to map note number into OCR1A value
#define NOTE_NONE 0xFF
static uint8_t current_note = NOTE_NONE;
void play_note(uint8_t note) {
if (note >= (sizeof(note_ocr_table) / sizeof(note_ocr_table[0])) return; // do nothing on the wrong parameter;
uint16_t ocr1a_val = pgm_read_word(¬e_ocr_table[note]);
TIMSK = (1 << OCIE1A); //Timer1 Comparator Interrupt is enabled // why you need this? May be you want to use just inverting OC1A output?
TCCR1B |= (1 << WGM12) | (1 << CS12); //CTC mode, prescale = 256 // you may want to use lesser prescalers and higher OCR1A values ?
OCR1A = ocr1a_val;
if (TCNT1 >= ocr1a_val) TCNT1 = 0; // do not miss the compare match when ORC1A is changed to lower values;
current_note = note;
}
void stop_note(uint8_t note) {
if (note == current_note) { // ignore stop for non-current note.
TIMSK &= ~(1UL << OCIE1A);
TCCR1A = 0; //stop the timer1
TIFR = (1 << OCF1A); //Clear the timer1 Comparator Match flag
current_note = NOTE_NONE; // No note is playing
}
}
so, now your task is very simple: you should just pull the state of the keys periodically, let's say 61 times per second (based on the some 8-bit timer with 1:1024 prescaler overflow), and they to note: which keys are changed their state. If some key is pressed, you call play_note to start coressponding note. If the key is released, you call stop_note, also you're counting how many timer cycles passed since the last event.
When recording, you just push those events into array "key X is pressed" or "key X is released" or "X timer cycles expired".
When playing back, you just performing a backward process, scaning your array, and executing commands: calling play_note, stop_note, or waiting exact amount of timer cycles, if it is a pause.
Instead of writing giant switch statement, you may also use a table to scan the buttons
// number of element in the port-state arrays
#define A 0
#define B 1
#define C 2
#define D 3
#define E 4
#define F 5
typedef struct {
port_index uint8_t;
mask uint8_t;
} KeyLocation;
PROGMEM KeyLocation const key_location[] = {
{ B, (1 << 1) }, // where C2 is located, e.g. PB1
{ E, (1 << 3) }, // where C#2 is located, e.g. PE3
...
}
uint16_t ticks_from_prev_event = 0;
uint8_t port_state_prev[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0XFF};
for (;;) { // main loop
wait_tick_timer_to_overflow();
// latching state of the pins
uint8_t port_state[6] = {PINA, PINB, PINC, PIND, PINE, PINF};
for (uint8_t i = 0 ; i < (sizeof(key_location) / sizeof(key_location[0])) ; i++) {
uint8_t port_idx = pgm_read_byte(&key_location[i].port_index);
uint8_t mask = pgm_read_byte(&key_location[i].mask);
if ((port_state[port_idx] & mask) != (port_state_prev[port_idx] & mask)) { // if pin state was changed
if (is_recording && (ticks_from_prev_event > 0)) {
put_into_record_pause(ticks_from_prev_event); // implement it on your own
}
if ((port_state[port_idx] & mask) == 0) { // key is pressed
play_note(i);
if (is_recording) {
put_into_record_play_note(i); // implement
}
} else { // key is released
stop_note(i);
if (is_recording) {
put_into_record_stop_note(i); // implement
}
}
}
}
// the current state of the pins now becomes a previous
for (uint8_t i = 0 ; i < (sizeof(port_state) / sizeof(port_state[0])) ; i++) {
port_state_prev[i] = port_state[i];
}
if (ticks_from_prev_event < 65535) ticks_from_prev_event++;
}
put_into_record_... implement as you wish.
the playback would be the same simple (below just the template, you'll suggest from the function name what they should do)
while (has_more_data_in_the_recording()) {
if (next_is_play()) {
play_note(get_note_from_recording())
} else if (next_is_stop()) {
play_note(get_note_from_recording())
} else {
uint16_t pause = get_pause_value_from_recording();
while (pause > 0) {
pause--;
wait_tick_timer_to_overflow();
}
}
}
This approach gives you two benefits:
1) It doesn't matter how many notes the playing module can play, the keys are recorder while they are pressed and released, so, all simultaneous keys will be recorded and replayed in the same time.
2) It doesn't matter how many notes are pressed in exact the same moment. Since pause and key events are recorded separately, during the replay all the same-time key presses will be replayed in the same time, without an "arpeggio" effect
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I spent more time and struggling to understand this code.
I edited and manage to display the data on LCD but
I would like to understand it.
I have added some comment to the code to show my bit understanding.
// Control 16 character LCD (2x8 chars) with 4 bit interface
// Copyright (C) 2012 Joonas Pihlajamaa. Released to public domain.
// No warranties, use at your own responsibility.
#include <avr/io.h>
#define F_CPU 12000000UL // 12 MHz
#include <util/delay.h>
#define DATA_PORT_DIR DDRB // macro for data port direction
#define DATA_PORT PORTB //macro for data port
#define DATA_PORT_IN PINB //macro for data port pin
#define RW_PIN (1<<PD4) // PORTD Pin4 is defined as for RW
#define RS_PIN (1<<PD5) // PORTD Pin5 is defined as for RS
#define EN_PIN (1<<PD6) // PORTD Pin6 is defined as for EN
// macro or something else? confused?
#define SET_CTRL_BIT(pin) (PORTD |= pin)
#define CLEAR_CTRL_BIT(pin) (PORTD &= ~pin)
// assumes EN_PIN is LOW in the beginning
void lcd_write(char rs, unsigned char data)
{
if(DATA_PORT_DIR != 0xFF) // condition to test if DATA_PORT_DIR is true
//make DATA_PORT_DIR as output to write data to ldc
DATA_PORT_DIR = 0xFF;
CLEAR_CTRL_BIT(RW_PIN);
if(rs)
SET_CTRL_BIT(RS_PIN);
else
CLEAR_CTRL_BIT(RS_PIN);
DATA_PORT = data;
_delay_us(2);
SET_CTRL_BIT(EN_PIN);
_delay_us(2);
CLEAR_CTRL_BIT(EN_PIN);
}
unsigned char lcd_read(char rs)
{
unsigned char data;
if(DATA_PORT_DIR != 0)
DATA_PORT_DIR = 0;
SET_CTRL_BIT(RW_PIN);
if(rs)
SET_CTRL_BIT(RS_PIN);
else
CLEAR_CTRL_BIT(RS_PIN);
_delay_us(2);
SET_CTRL_BIT(EN_PIN);
_delay_us(2);
data = DATA_PORT_IN;
CLEAR_CTRL_BIT(EN_PIN);
return data;
}
void lcd_wait()
{
while(lcd_read(0) & 0x80); // wait until display is ready
}
void lcd_init()
{
_delay_ms(50); // wait for VDD to rise
lcd_write(0, 0x30);
_delay_ms(5);
lcd_write(0, 0x30);
_delay_ms(1); // _delay_us(120);
lcd_write(0, 0x30);
_delay_ms(1); // _delay_us(120);
lcd_write(0, 0x38); // 2 lines, normal font
_delay_ms(1);
lcd_write(0, 0xC); // display on
_delay_ms(1);
lcd_write(0, 1); // display clear
_delay_ms(1);
lcd_write(0, 0x6); // increment, don't shift
_delay_ms(1);
}
void lcd_puts(char * string)
{
char i;
lcd_write(0, 0x80); // move to 1st line
lcd_wait();
for(i=0; i<8; i++)
{
if(string[i] == '\0')
return;
lcd_write(1, string[i]);
lcd_wait();
}
lcd_write(0, 0x80+0x40); // move to 2nd line
lcd_wait();
for(i=8; i<16; i++)
{
if(string[i] == '\0')
return;
lcd_write(1, string[i]);
lcd_wait();
}
}
int main(void)
{
unsigned char i = 0;
char message[] = "nn Mississippi..";
DDRD = RS_PIN + EN_PIN + RW_PIN + LED_PIN; // Control outputs
DDRB = 0xFF; // Port B as DB0..DB7
lcd_init();
lcd_puts("Hello, World!!!");
_delay_ms(2000);
while(1)
{
if(++i >= 100)
i = 1;
if(i >= 10)
message[0] = i/10+'0';
else
message[0] = ' ';
message[1] = i%10+'0';
lcd_puts(message);
_delay_ms(1000);
}
return 1;
}
These define constants that have only the bit specified on
#define RW_PIN (1<<PD4) // PORTD Pin4 is defined as for RW
#define RS_PIN (1<<PD5) // PORTD Pin5 is defined as for RS
#define EN_PIN (1<<PD6) // PORTD Pin6 is defined as for EN
These Macros can then use the above can be used to set the bit (by OR'ing it on) or clearing the bit by using AND with the 1's complement of the constant (all bits on but one).
// macro or something else? confused?
#define SET_CTRL_BIT(pin) (PORTD |= pin)
#define CLEAR_CTRL_BIT(pin) (PORTD &= ~pin)
Here is what it looks that the write is doing: If first puts all the port bits in output mode (so it can write the data). It then sets RW pin low (I assume to put it in write mode) and resets the display (if rs is set) by toggling the RS bit. It then loads the data into the DATA_PORT and toggles the EN pin (I assume to load it).
// assumes EN_PIN is LOW in the beginning
void lcd_write(char rs, unsigned char data)
{
if(DATA_PORT_DIR != 0xFF) // condition to test if DATA_PORT_DIR is true
//make DATA_PORT_DIR as output to write data to ldc
DATA_PORT_DIR = 0xFF;
CLEAR_CTRL_BIT(RW_PIN);
if(rs)
SET_CTRL_BIT(RS_PIN);
else
CLEAR_CTRL_BIT(RS_PIN);
DATA_PORT = data;
_delay_us(2);
SET_CTRL_BIT(EN_PIN);
_delay_us(2);
CLEAR_CTRL_BIT(EN_PIN);
}
Here is what it looks that the read is doing: If first puts all the port bits in input mode (so it can read the data). It then sets RW pin high (I assume to put it in read mode) and resets the display (if rs is set) by toggling the RS bit. It then sets the EN bit and fetches the data from the DATA_PORT and turns the EN pin off again.
unsigned char lcd_read(char rs)
{
unsigned char data;
if(DATA_PORT_DIR != 0)
DATA_PORT_DIR = 0;
SET_CTRL_BIT(RW_PIN);
if(rs)
SET_CTRL_BIT(RS_PIN);
else
CLEAR_CTRL_BIT(RS_PIN);
_delay_us(2);
SET_CTRL_BIT(EN_PIN);
_delay_us(2);
data = DATA_PORT_IN;
CLEAR_CTRL_BIT(EN_PIN);
return data;
}
Is that enough for you to figure out what the rest is doing?
I have PIC18F87J11 with 25LC1024 external EEPROM, and I would like to store some data on it and be able to read it later on. I have done some research, but unfortunately I could not find a tutorial that uses similar board as mine. I am using MPLAB IDE with C18 compiler.
PIC18F87J11
Note: two more links are written as comment below.
This is where my problem is ...
In order to write to the 25LC1024 external EEPROM I followed the tutorial from microchip. The first problem is that this tut is written for PIC18F1220 and I'm using PIC18F87J11. So upon opening the project I get two files not found error, but I simply ignored them.
PICTURE
I copied the file AN1018.h and AN1018_SPI.c to the project I am working on, and I copied some piece of code from AN1018.c file.
Code from AN1018.c file
void main(void)
{
#define PAGESIZE 16
static unsigned char data[PAGESIZE]; // One-page data array
static unsigned char i;
init(); // Initialize PIC
data[0] = 0xCC; // Initialize first data byte
/* Low-density byte function calls */
LowDensByteWrite(data[0], 0x133); // Write 1 byte of data at 0x133
data[0] = 0xFF;
LowDensByteRead(data, 0x133);
printf("%x",data);
while(1){};
}
void init(void)
{
ADCON1 = 0x7F; // Configure digital I/O
PORTA = 0x08; // Set CS high (inactive)
TRISA = 0b11110111; // Configure PORTA I/O
PORTB = 0; // Clear all PORTB pins
TRISB = 0b11111100; // Configure PORTB I/O
}
My second problem is that the output message is always 1e0. In other words, I do not know if the write was successfully made or not. Also I am not sure about what I might be missing.
If I can receive some kind of help, I would appreciate it. To sum up everything, I want to store data to my external EEPROM and retain it when needed. Please know I am a beginner with Microcontroller programming.
As a first step (before reading & writing) you have to be sure that your SPI interface (hardware and software) is correctly configured. To check this step you can read the "Status Register" from the 25LC1024. Look the datasheet for "RDSR", the instruction to send to the eeprom should be 0b00000101 so (int)5.
Here some code for 18F* + 25LC* wirtten in sdcc of a really old project. The code is very basic, no external library used, you just have to replace register variable names and init config for your pic.
Some code comes from here, thanks to bitberzerkir!
spi.c
#ifndef SPI_HH
#define SPI_HH
#define SpiWrite(x) spiRW(x)
#define SpiRead() spiRW(0)
unsigned char spiRW(unsigned char data_){
SSPBUF = data_;
while(!PIR1bits.SSPIF);
PIR1bits.SSPIF = 0;
return SSPBUF;
}
void SpiInit() {
SSPSTAT = 0x40; // 01000000
SSPCON1 = 0x20; // 00100000
PIR1bits.SSPIF = 0;
}
#endif
eeprom.c
Note: Since the addr of 25LC1024 are 3x8bits make sure your compiler 'long' type has at least 24bit
#ifndef EEPROM_HH
#define EEPROM_HH
#include "spi.c"
#define CS PORTCbits.RC2
void EepromInit() {
SpiInit();
CS = 1;
}
unsigned char EReadStatus () {
unsigned char c;
CS = 0;
SpiWrite(0x05);
c = SpiRead();
CS = 1;
return c;
}
unsigned char EWriting() {
unsigned char c;
CS = 0;
SpiWrite(0x05);
c = SpiRead();
CS = 1;
return c & 1;
}
unsigned char EReadCh (unsigned long addr) {
unsigned char c;
// Send READ command and addr, then read data
CS = 0;
SpiWrite(0x03);
// Address in 3x8 bit mode for 25lc1024
SpiWrite(addr>>16);
SpiWrite(addr>>8);
SpiWrite((unsigned char) addr);
c = SpiRead();
CS = 1;
return c;
}
void EWriteCh (unsigned char c, unsigned long addr) {
// Enable Write Latch
CS = 0;
SpiWrite(0x06);
CS = 1;
// Send WRITE command, addr and data
CS = 0;
SpiWrite(0x02);
SpiWrite(addr>>16);
SpiWrite(addr>>8);
SpiWrite((unsigned char) addr);
SpiWrite(c);
CS = 1;
}
#endif
main.c
Set your init according to the datasheet
#include <pic18fregs.h>
#include "eeprom.c"
void main(void) {
char out;
TRISB = 0x01;
TRISC = 0x00;
PORTB = 0x00;
PORTC = 0x00;
EepromInit();
EWriteCh('a', 0x00);
out = EReadCh(0x00);
while(1);
}
If you want to read/write a buffer take care of pagination. Eg here:
// Page byte size, 64 for 25lc256 and 256 for 25lc1024
#define PSIZE 256
// Addr mem limit 7FFF for 25lc256, 1FFFF for 25lc1024
#define MLIMIT 0x1FFFF
void EReadBuff (unsigned char c[], unsigned long dim, unsigned long addr) {
unsigned int i;
// Send READ command and addr, then read data
CS = 0;
SpiWrite(0x03);
SpiWrite(addr>>16);
SpiWrite(addr>>8);
SpiWrite((unsigned char) addr);
for(i = 0; i < dim; ++i)
c[i] = SpiRead();
CS = 1;
}
void EWriteBuff (unsigned char c[], unsigned long dim, unsigned long addr) {
unsigned char i;
unsigned int begin = 0;
unsigned int end = dim > PSIZE ? PSIZE : dim;
while (end > begin && addr + end <= MLIMIT) { // check if addr is a siutable address [0, MLIMIT]
// Enable Write Latch
CS = 0;
SpiWrite(0x06);
CS = 1;
// Send WRITE command, addr and data
CS = 0;
SpiWrite(0x02);
SpiWrite(addr>>8);
SpiWrite((unsigned char) addr);
for(i = begin; i < end; ++i)
SpiWrite(c[i]);
CS = 1;
while(EWriting());
dim -= PSIZE;
begin += PSIZE;
addr += PSIZE;
end = begin + (dim > PSIZE ? PSIZE : dim);
}
}
#endif
I think before directly using the AN1018.h/AN1018_spi.c you will need to verify that it is compatible with your micro-controller. I recommend to check the datasheet of both micro-controllers and see the difference specifically for SPI module as the external EEPROM which you are using will be connected to SPI bus. If these two micro-controller has same register configuration/module for SPI then you can use it else you will have to write the driver on your own. You can use AN1018_spi.c for reference I guess you will just need to change some registers if required.
Then in you init function, you are not initializing SPI module, you will need to specify correct SPI clock, SPI mode based on your external device. Once you have properly initialize SPI module. You will need to write EEPROM_Read/EEPROM_Write function. In which you will have to following protocol given in datasheet of your external device for sending/receiving data from device using.
hi i googled and get a very good website Where i found post on Interfacing external EEPROM with PIC Microcontroller via i2c protocol with FM24C64 and the code which they given in post which i tested and working fine. i give that link may it help you. http://www.nbcafe.in/interfacing-external-eeprom-with-pic-microcontroller/