This year we're making some "talking toys".
The idea is to have a base with the sound player part in it, and a couple of connections exposed on a top surface, connected to a couple of magnets. Different characters will be placed on the base, triggering different sounds.
By simply embedding a resistor inside each of the characters, we can change the "bottom half" of a voltage divider and detect which character has been placed on top of the contacts.
Since we're using pretty much the same code as last time for playing WAVs, we've a limited number of pins we can use for the analogue input. Luckily, most of the pins on the 16F1825 can be used as analogue inputs - so we picked RC2 (pin8) as it wasn't being used for anything important.
Now before we go hacking our wav player code, we just wanted to check that we could read analogue input values accurately. In Oshonsoft, this was a doddle - two lines of code. In our now-preferred compiler, SourceboostC it's not so obvious.
There are a few functions that can assist - things like adc_get(x) but in practice they didn't work very well. After doing a bit of searching, it seems that you need to prepare the correct registers for your target chip before the built-in Sourceboost ADC functions will work properly.
Well, while we're digging about in the datasheets we thought we may as well do the whole thing "old-school" using registers instead of pre-built functions and libraries (SourceboostC has renewed our interest in understanding what these magic little black boxes can do by hitting the datasheet quite often, while Oshonsoft tends to encourage you to rely on pre-built functions; some people like the simplicity of the abstracted layers - we like to know what's going on under the hood!)
For the 16F1825 there are two registers you need to focus on: ADCON0 and ADCON1
The chip puts the analogue inputs into "channels" and you really need to use the datasheet to get the channel numbers - you couldn't just guess that, in our case, RC2 is analogue channel 6!
We need to tell the chip which "channel" to listen to for the analogue-to-digital conversion by setting bits 6-2 of the ADCON0 register. Then set bit zero to tell it to activate the A2D peripheral.
ADCON1 is where we set the acquisition speed, positive and negative voltage references and so on. We went for the slowest option (fosc/64) to hopefully give more reliable values.
In the code below, we sample the analogue input (RC2) every 1/4 second and convert the received value into an id number (our ID numbers run from 0-9). If the received ID number is different to the previous value, we then display a message over UART. In the fullness of time, this will be used to start/stop different sound samples - for now we're just focussing on getting the A2D conversion working, so replying over UART/serial is just fine!
#include <system.h>
// ##################################
// using a 32 Mhz internal oscillator
// on a PIC16F1825
#pragma DATA _CONFIG1, 0x0804
#pragma DATA _CONFIG2, 0x1DFF
#pragma CLOCK_FREQ 32000000
// ##################################
char lastID;
char id;
// program initialise
void initialise(){
osccon=0b11110000; // oscillator=32mhz
delay_ms(50); // wait for internal voltages to settle
intcon =0b11000000; // global interrupts enabled and peripheral interrupts
apfcon0=0b01000000; // alternative tx/rx so pickit can use prog pins for uart
trisa=0x00; // portA is all outputs
ansela=0x00; // turn off analogue pins on portA
trisc= 0b00000100; // RC.2 is an input (analogue in)
wpuc = 0b00000100; // any inputs on portC have weak pull-ups (except RC2)
anselc=0b00000100; // turn off analogue pins on portC except RC2
porta=0x00; // set all output pins on portA off
portc=0x00; // turn off all output pins on portC
option_reg=0x00;
option_reg.7=0; // weak pull-ups enabled on any input pins
}
void initUART(){
//
// UART
//
baudcon.4 = 0; // SCKP synchronous bit polarity
baudcon.3 = 0; // BRG16 enable 16 bit brg
baudcon.1 = 0; // WUE wake up enable off
baudcon.0 = 0; // ABDEN auto baud detect
txsta.6 = 0; // TX9 8 bit transmission
txsta.5 = 1; // TXEN transmit enable
txsta.4 = 0; // SYNC async mode
txsta.3 = 0; // SEDNB break character
txsta.2 = 0; // BRGH high baudrate
txsta.0 = 0; // TX9D bit 9
rcsta.7 = 1; // SPEN serial port enable
rcsta.6 = 0; // RX9 8 bit operation
rcsta.5 = 1; // SREN enable receiver
rcsta.4 = 1; // CREN continuous receive enable
spbrgh = 0; // brg high byte
spbrg = 51; // brg low byte (see datasheet for 9600@32Mhz)
apfcon0.2=1; // tx onto RA.0 (for PicKit2 sharing)
pie1.5=1; // rcie bit 5 on the pie1 enables interrupt on usart receive
}
void UARTSend(unsigned char c){
txreg = c;
while(!txsta.1);
}
void uart_print(char t){UARTSend(t);}
void uart_print(char *s){
while(*s) {
UARTSend(*s);
s++;
}
}
void uart_println(char *s){
uart_print(s);
uart_print("\r\n");
}
void uart_print_dec(char number){
if(number > 99) {uart_print(((number / 100) % 10) + '0');}
if(number > 9) {uart_print(((number / 10) % 10) + '0');}
uart_print((number % 10) + '0');
}
unsigned char getAnalogueInput(){
unsigned char temp;
// start the analogue circuit
adcon0.1=1;
// wait for the result to come in
// (hardware clears the bit we just set when done)
while(adcon0.1==1){}
// return the result
temp=adresl;
return(temp);
}
void readInput(){
char iA;
iA=getAnalogueInput();
if(iA<20){
id=9;
}else if(iA<40){
id=8;
}else if(iA<60){
id=7;
}else if(iA<80){
id=6;
}else if(iA<100){
id=5;
}else if(iA<120){
id=4;
}else if(iA<140){
id=3;
}else if(iA<160){
id=2;
}else if(iA<180){
id=1;
}else{
id=0;
}
if(id!=lastID){
uart_print("new id ");
uart_print_dec(id);
uart_println(" ");
}
lastID=id;
}
// main program executes from here
void main(){
initialise();
delay_ms(50);
initUART();
delay_ms(250);
uart_println("Let's go!");
// start the analogue circuit (right justified)
adcon0=0b00011001; // bit 7 blank, 6-2 are the channel and bit one activates
adcon1=0b11110000;
// this is the main code loop
while(1){
readInput();
delay_ms(250);
}
}
The key to capturing analogue data is in the getAnalogueInput function.
Set the first bit to start capturing data on the selected analogue "channel". When the data acquisition is complete, and the PIC has converted the analogue input into a digital value, this same bit gets cleared. In our example above, we've used a simple "blocking" loop - i.e. loop until the bit is cleared (or, while the bit is set, it amounts to the same thing). Once the data has been converted, the first bit of the ADCON0 register is cleared (by the hardware) and the rest of the code can execute.
The 10 bit value is stored in the ADRESH and ADRESL registers.
Simply read these values to get the converted value. You can set the ADCON1 register to determine whether this value should be "left shifted" or "right shifted" in the registers. For testing, we're just taking the value from the lower byte register, but in a working product, we should really be combining these two values into a two-byte integer for better accuracy.
But that's it - the code to get data from the analogue-to-digital converter isn't actually more more complex by reading/writing to registers than it is to use the abstracted adc_ functions in SourceBoost. And by doing it this way, we've reacquainted ourselves with the 16F1825 chip and the underlying hardware.
No comments:
Post a Comment