How can I create a bidirectional bus using a 18F4550? - c

Basically what Im trying to do its to use a single port of the PIC18F4550 as both inputs and outputs. Im going to connect a 7-segment-display to 7 pins of the port and with 4 pins of the same port. The four inputs are for introduce the numbers from 0 to 15 (F) in binary and the number in binary will represent in decimal in the display. Im using PIC C Compiler for the code.
Update: Following the first comment I modify the code and I think it is basically the same, I just read directly the input of the port B and use a switch-case in order to send the corresponding decimal number, also I added a default state so when I connected it should be a 0 in the display. Here it is the connection diagram, my teacher told me that I can ignore the logic gates but I'm not shure about that, in case I want to added I just need to use another port like port D to send a single high or low depending on how the port B is working.
Update 2: It finally work, it seems that the fact that I can ignore the logic gates it was partially true, the code was easier than I though, I just added a three port D high and low outputs to the EN and OE of both 74Ls244 and 74HC573 corresponding inputs so I can enable or disable depending on the port B state (output or input). Thank you all.
#include<18F4550.h>
#fuses XT, NOWDT, NOLVP, NOPROTECT, PUT
#use delay(clock = 4M, crystal = 8M)
#use fast_io(b)
void main(){
int8 INDSP;
int8 OUTDSP;
while(true){
SET_TRIS_B(0xFF);
INDSP = input_b()&0x0F;
switch(INDSP){
case 0b00000000:
OUTDSP = 0x3F;
break;
case 0b00000001:
OUTDSP = 0x0C;
break;
case 0b00000010:
OUTDSP = 0x76;
break;
case 0b00000011:
OUTDSP = 0x5E;
break;
case 0b00000100:
OUTDSP = 0x4D;
break;
case 0b00000101:
OUTDSP = 0x5B;
break;
case 0b00000110:
OUTDSP = 0x7B;
break;
case 0b00000111:
OUTDSP = 0x0E;
break;
case 0b00001000:
OUTDSP = 0x7F;
break;
case 0b00001001:
OUTDSP = 0x4F;
break;
case 0b00001010:
OUTDSP = 0x6F;
break;
case 0b00001011:
OUTDSP = 0x79;
break;
case 0b00001100:
OUTDSP = 0x33;
break;
case 0b00001101:
OUTDSP = 0x7C;
break;
case 0b00001110:
OUTDSP = 0x73;
break;
case 0b00001111:
OUTDSP = 0x63;
break;
DEFAULT:
OUTDSP = 0x3F;
break;
}
delay_ms(500);
SET_TRIS_B(0x00);
output_b(OUTDSP);
delay_ms(500);
}
}

Caveat: Not a total solution, but some suggestions ...
We can simplify your main as it has large amounts of replicated code:
void
main()
{
SET_TRIS_B(0b00001111);
DSPIN = input_b();
while (true) {
if ((DSPIN >= 0b00000000) && (DSPIN <= 0b00001111))
DSPOUT = DSPIN;
else
DSPOUT = 0;
delay_ms(1000);
SET_TRIS_B(0b11111111);
if ((DSPOUT >= 0) && (DSPOUT <= 15))
output_b(DSPVALOR[DSPOUT]);
else
output_b(DSPVALOR[0]);
delay_ms(1000);
}
}
Now, given the top part of main:
SET_TRIS_B(0b00001111);
DSPIN = input_b();
The question I have is: should this be inside the while loop at the top???
Otherwise, there's no need for a loop as DSPIN and DSPOUT will remain constant.
UPDATE:
This is a total guess, but after cursorily looking at the datasheet ...
I think you're mixing up the value for the TRIS port. The second set_tris_b call should have 0b00000000 to set all bits for output (and not 0b11111111 as you have which sets all bits for input).
I'm not sure you can do this with a single 8 bit port. You need four bits for the input value. But, you need seven bits for output to the 7 segment display.
This adds up to more than 8 bits. So, I think you need two ports. Since you're using port "b", I presume there is a port "a".
You may be able to time division multiplex a given bit for input and output as you've done, but I think it's dicey and I don't know enough about the specifics of the 18F's ports to say for sure.
Here's refactored code that assumes a separate port "a":
void
main()
{
while (true) {
// set port B lower four bits for input
SET_TRIS_B(0b00001111);
// get input and mask off don't care bits
DSPIN = input_b();
DSPIN &= 0b00001111;
if ((DSPIN >= 0b00000000) && (DSPIN <= 0b00001111))
DSPOUT = DSPIN;
else
DSPOUT = 0;
delay_ms(1000);
// set port A pins for output
SET_TRIS_A(0b00000000);
if ((DSPOUT >= 0) && (DSPOUT <= 15))
output_a(DSPVALOR[DSPOUT]);
else
output_a(DSPVALOR[0]);
delay_ms(1000);
}
}
But, if the 18F lets you change the tristate bits on the fly and you can connect both the input bit switches on bits 0-3 and the 7 segment output bits on bits 0-6, you can simply change all *_a calls into *_b calls.
UPDATE #2:
Upon further reflection [and, again, this is speculation], if the 18F allows one port to be switched back and forth repeatedly between input and output, the "duty cycle" in the above code is [probably] incorrect.
With 1000ms for both delays, the duty cycle is [only] 50% for output, so the 7 segment display will probably flicker badly.
That's because, when in tristate/input mode, the 7 segment display is not being driven with the correct segment mask/output value
The solution is to increase the duty cycle to (e.g.) 99.44% [the purity of Ivory Soap :-)]. So, most of the time, the port is in output mode. We only enter tristate/input mode briefly to sample the [DIP switch] input pins.
Here's a refactored version that keeps the port in output mode a high percentage of the time:
// NOTE: these may be need to be adjusted
enum {
INPUT_STABILIZE_DELAY = 1, // as short as possible
OUTPUT_STABILIZE_DELAY = 1, // as short as possible
OUTPUT_HOLD_DELAY = 100, // as long as possible
};
void
main()
{
while (true) {
// set port B lower four bits for input
SET_TRIS_B(0b00001111);
// allow port to stabilize
delay_ms(INPUT_STABILIZE_DELAY);
// get input and mask off don't care bits
DSPIN = input_b();
DSPIN &= 0b00001111;
// set output mode based on input
if ((DSPIN >= 0b00000000) && (DSPIN <= 0b00001111))
DSPOUT = DSPIN;
else
DSPOUT = 0;
// set port B pins for output
SET_TRIS_B(0b00000000);
// allow tristate pins to stabilize
delay_ms(OUTPUT_STABILIZE_DELAY);
// output to 7 segment delay
output_b(DSPVALOR[DSPOUT]);
// ensure that port is in output mode 99.44% of the time
delay_ms(OUTPUT_HOLD_DELAY);
}
}

Related

C script for guiding a line follower

So for class I am trying to write a C script for a line follower robot. The robot has 3 sensors (ABC) which give logic 1 on black and logic 0 on white, A is on the left side, B in the middle and C on the right when looking straight down on the robot. It also has 2 motors, one on each side.
The board I am using is an Texas Instruments MSP-EXP430G2 and I am using the ports P1.0 - P1.7.
Now, I have literally 0 experience writing C scripts so all pointers are very much appreciated.
Here is the code.
# include "msp430G2553.h"
# include "stdio.h"
# include "math.h"
#define Both_On 1 // States
#define Left_On 2
#define Right_On 3
int main (void)
{
char STATE;
P1DIR|=0x0F; // Port1 four lower bits as OUTput, others as INput(0000 1111)
WDTCTL = WDTPW + WDTHOLD;// stop watch dog
STATE = Both_On; // Start here
char MASK1=0x10; //Sensor mask 0001 0000 (P1.0 - P1.7 from left to right)
char L; //Direction switch
while(1)
{
L = P1IN & MASK1; //Sensor value based on input-port
switch (STATE)
{
case Both_On:
P1OUT = 0x03;//Both motors on
puts("Both_On"); //for testing
if (L == 0x00 ){
P1OUT=0x01;//left motor on
STATE = Left_On;}
else if (L==0x10){
P1OUT=0x03;
STATE = Both_On;}
break;
case Left_On:
P1OUT = 0x01;//left motor on
puts("Left_on"); //for testing
if (L == 0x10 ){
P1OUT=0x03;//both motors on
STATE = Both_On;}
else if (L==0x00){
P1OUT= 0x02;
STATE = Right_On;}
break;
case Right_On:
P1OUT = 0x02;//right motor on
puts("Right_on"); //for testing
if (L == 0x10 ){
P1OUT=0x03;//both motors on
STATE = Both_On;}
else if (L==0x00){
P1OUT= 0x01;
STATE = Left_On;}
break;
break;
}//end of Switch
}// end of while
I think I've understood the method for switching from one active motor to another or both. The first 4 bits are reserved for the input from the sensors and the last 4 bits direct the motors. So P1OUT=0x03 basically means the output is 0000 0011 so the ports P1.6 and P1.7 are active and the motors wired to those ports would turn on.
I've been instructed to use an "L" variable as the direction switch. My problem is that I don't quite understand how this works.
I've been thinking that I would wire the B sensor to the port "L" takes it value from and then saying, if L==1 then both motors should be on and if L==0 then the state should change from "Left_on" to "Right_on" in a loop but this behavior doesn't feel very logical, I would want better detection.
Could I change the mask to be "MASK1=0x70" (0111) which would give me 3 ports to wire the sensors into, and guide the motors using for example:
else if (L==0x40){ //0100 only the left side sensor A is on black
P1OUT= 0x02; //0010 P1.6
STATE = Right_On;} // turn the right motor on to keep following the line
break;
As I said, all pointers and suggestions are highly appreciated.

PIC18F25K80 sending string through USART not working

I'm programming a few libraries for a board with a PIC18F25K80 built-in. Right now I'm trying to program the UART library and I've tried everything but I cannot make it work when it comes to send a string of chars.
I'm using the XC8 compiler and I have the following code in the library: (I haven't programmed the interruptions yet, that's why you can't see anything related to that in the code.
void UARTsend(char data){
UARTbusy();
TXREG1 = data;
}
void UARTsendTEXT(unsigned char *text){
while(*text != '\0'){
UARTsend(*(text++));
}
}
char UARTbusy(){
return TXSTA1bits.TRMT;
}
And this code in the main file:
int main() {
UARTconfig(_BR_19200, _RxINT_OFF, _TxINT_OFF, _8BIT);
unsigned char data[20] = "ABCDEFGHIJ";
while(1){
if(UARTrxREAD() == 'a'){
UARTsendTEXT(data);
}
}
return 0;
}
So, when I hit 'a', the string should be sent but instead of seeing the string, I receive the first and last letter, and sometimes just the first letter.
Honestly, I believe it is a really basic problem with the code, but I tried everything I could think about and nothing worked, so maybe someone here can help me with that.
EDIT: just in case you need to check it out:
void UARTconfig(unsigned int BaudRate, unsigned int RxINT, unsigned int TxINT, unsigned int BIT){
int br_data;
//Clock configuration at 16MHz
OSCCONbits.IRCF = 0b111;
//9-bit transmit enable bit.
if(BIT == 1) {
RCSTA1bits.RX9 = 1; //Reception 9-bit.
TXSTA1bits.TX9 = 1; //Transmission 9-bit.
}
else {
RCSTA1bits.RX9 = 0; //Reception 8-bit.
TXSTA1bits.TX9 = 0; //Transmission 8-bit.
}
//Enable serial port.
RCSTA1bits.SPEN = 1;
//Enable continuous reception.
RCSTA1bits.CREN = 1;
//Setting asynchronous mode.
TXSTA1bits.SYNC = 0;
//Enable transmission
TXSTA1bits.TXEN = 1;
//Setting Rx/Tx pins as output.
TRISC7 = 0;
TRISC6 = 0;
//Baud rate configuration
BAUDCON1bits.BRG16 = 1; //16bit Baud Rate generator
TXSTA1bits.BRGH = 0; //Low speed BR.
switch(BaudRate){
case 0:
br_data=415;
break;
case 1:
br_data=103;
break;
case 2:
br_data=51;
break;
case 3:
br_data=16;
break;
case 4:
br_data=8;
}
SPBRGH1:SPBRG1 = br_data;
}
The function UARTbusy should be waiting for status, or its caller should be. But the transmit register status is read once and ignored.
There is typically a two-stage buffer for transmitted data. Values written to the interface register are held until the shift register is empty, and the device remains 'busy' until then.
When you write the first data, it is transferred immediately to the shift register, so writing the second data immediately is harmless. But you then keep on over-writing the contents of the output register until it holds the last character of your message. Then when the shift register becomes empty, the last character of your message is sent.
Remember that the shift register is shifting bits out relatively slowly - at the baud rate. That is why the status bit needs to be checked.

AVR Programming - How to read in consecutive button presses in C

Here's what I have to dp:
Consider an ATmega324A development board and a CSSE2010/CSSE7201 IO Board. Switches
S3 to S0 are connected to AVR port B pins 3 to 0. Push button B0 is connected to AVR port A
pin 0. LEDs L0 and L2 and connected to AVR port C pins 0 and 2 respectively.
LED L0 (red) is the “Locked” LED and should only be on when the lock is locked. LED L2
(green) is the “Unlocked” LED and should only be on when the lock is unlocked. The lock
initially starts in the locked state. The user enters the binary code for a digit on the switches (S3 to S0) and then presses and releases push button B0 to “enter” the first digit. The user then enters the binary code for the second digit on the switches and presses and releases push button B0 to “enter” the second digit. If the digits match the expected value (the last digit of your student number followed by the third digit of your student number) then the lock should be “unlocked”, otherwise it should stay in the locked state until the two digits are entered correctly.
Here is my code so far:
#include <avr/io.h>
/* Seven segment display values */
uint8_t seven_seg[16] = { 63,6,91,79,102,109,125,7,127,111,119,124,57,94,121,113};
int main(void) {
uint8_t digit;
uint8_t temp;
uint8_t digit2;
uint8_t code[2] = {6,3}
DDRA = 11111110; //port A is input (last bit)
DDRB = 0X00; //port B is input
DDRC = 0x0F; //port c is output
DDRD = 0XFF; //set port D to be output
while(1) {
/* Read in a digit from lower half of port C pins */
/* We read the whole byte and mask out upper bits */
PORTB = 1; //Led is red
clock = PINA & 00000001; //read in last bit of port A
temp = PINB & 0X0F; //read in lower half of port b
/* Checks to see the first digit is correct */
if(temp == code[0] && clock == 1) {
digit = temp;
PORTD = seven_seg[temp];
} else {
PORTD = 0;
}
}
I'm getting stuck at the point where I have to read in the second digit. Would I be doing this inside a nested loop of the first? Or how would I go about reading in two digits from my switches, which is clocked in each time from the press of a button?
To make a variable program that can be used for longer number sequences, simply use a loop. for(uint8_t i=0; i<NUMBER_OF_DIGITS; i++). The port reading needs to be inside the loop.
However, you cannot read buttons the way you do. All buttons have an electro-mechanical signal bounce, which you need to filter out to prevent false readings. You must do this on any kind of embedded system.
The simplest way to do this is to sample the button once, save the result, wait a few milliseconds, then sample it again. If the samples compare equal, accept it as result (pressed or not pressed).
Alternatively, you can trigger an interrupt on the edge of the button signal, from there start a timer, and then when the timer runs out, read the port.
More advanced methods use some form of median filters.
So if i get you right you wanna do something like this
Start:
Wait for pushbutton
Wait for first digit
Wait for pushbutton
Wait for second digit
If digits they are the same
Turn off Red LED
Turn on Green LED
Else
Goto start
Can you confirm this? It is always a good thing to do your code in pseudo first. It gives very good overview of what you want to do.

Write a block to SD with SPI, strange response from SD

Here is my code for writing a 512byte block into an SD card. The code works fine, but when I check that everything went good (by reading the response by SD), I read 0xFF.
That values should be something like (from SD reference manual):
‘010’—Data accepted.
‘101’—Data rejected due to a CRC error.
‘110’—Data rejected due to a Write Error
This is the code:
uint8_t SdCard_SendBlock(uint32_t block, uint8_t * data)
{
switch (sd_write_blk_machine.fields.state)
{
case WRITE_START:
//Enable Card
GPIOC_PDOR &= ~GPIO_PDOR_PDO(GPIO_PIN(10));
sd_cmd_arg.sd_cmd_tot_argument = block << SD_BLOCK_SHIFT;
sd_write_blk_machine.fields.state = WRITE_SEND_CMD24;
/*INIZIALIZZO LE VARIABILI LOCALI*/
write_send_data_counter = 0;
sd_cmd_machine.sd_cmd_machine = 0;
break;
case WRITE_SEND_CMD24:
send_command_return = SdSendCmd(CMD24|0x40,ASPECTED_OK_RESPONSE);
if( send_command_return == SDCARD_CMD_FAILS)
{
//Disable Card
GPIOC_PDOR |= GPIO_PDOR_PDO(GPIO_PIN(10));
sd_write_blk_machine.fields.complete = 1;
system_error.flags.sdcard_error = SDCARD_WRITE_FAIL;
return(SDCARD_WRITE_FAIL);
}
sd_write_blk_machine.fields.state = WRITE_SEND_START_TOKEN;
}
break;
case WRITE_SEND_START_TOKEN:
Spi_writeData(SPI0,SD_TOK_WRITE_SBLOCK); //SD_TOK_WRITE_SBLOCK = 0xFE ,
spi_control_machine.spi_control_machine = 0;
sd_write_blk_machine.fields.state = WRITE_SEND_DATA;
break;
case WRITE_SEND_DATA:
if (write_send_data_counter < SDCARD_BLOCK_SIZE) //SDCARD_BLOCK_SIZE = 512 byte
{
Spi_writeData(SPI0, data[write_send_data_counter]);
{
spi_control_machine.spi_control_machine = 0;
write_send_data_counter++;
}
}
else
sd_write_blk_machine.fields.state = WRITE_SEND_IDLE_1;
break;
case WRITE_SEND_IDLE_1:
Spi_writeData(SPI0,0xFF); // 0xFF = SPI_IDLE
sd_write_blk_machine.fields.state = WRITE_SEND_IDLE_2;
break;
case WRITE_SEND_IDLE_2:
Spi_writeData(SPI0,0xFF); // 0xFF = SPI_IDLE
sd_write_blk_machine.fields.state = WRITE_READ_RESPONSE_TOKEN;
break;
case WRITE_READ_RESPONSE_TOKEN:
/*Every data block written to the card will be acknowledged by a data response token. It is one byte long
and has the following format:
x x x 0 Status 1
The meaning of the status bits is defined as follows:
010 - Data accepted.
101 - Data rejected due to a CRC error.
110 - Data Rejected due to a Write Error*/
spi_control_machine.spi_control_machine = 0;
Spi_readData(SPI0, &write_read_response); // HERE IS THE PROBLEM !!!! write_read_response = 0xFF
if ( (write_read_response & 0x0F) != SD_ACCEPTED_WRITE_DATA )
{
//disabilita carta
GPIOC_PDOR |= GPIO_PDOR_PDO(GPIO_PIN(10));
system_error.flags.sdcard_error = SDCARD_WRITE_FAIL;
sd_write_blk_machine.fields.complete = 1;
SendBlockReturn=0;
return (SDCARD_WRITE_FAIL);
}
sd_write_blk_machine.fields.complete = 1;
status.flags.sdwrite_wait_attemp = 1;
SendBlockReturn=1;
return (TERMINATE_OK);
break;
}
The issue is in the last case of the switch.
It looks like the card is returning 0xFF until it has completed the write. According to the Physical Layer Simplified Specification in section 7.2.4:
As long as the card is busy programming, a continuous stream of busy tokens will be sent to the host (Effectively holding the DataOut line low).
That said, I'm not sure why the card isn't returning the '010' accepted response first - it could be a case of the card manufacturer not following the spec.
What you want to do is repeatedly call Spi_readData until it the returned byte becomes 0 or '010' accepted.
If you look at the diagrams on the SD specification the busy tokens come after the data response, what you're seeing is normal, after you send the last byte you should keep reading data until you get the data response token, then the card will start programming and you'll get the busy tokens until the programming completes. When the card is programming you'll read 0's (data line is held LOW). After you transfer the last byte I don't think the spec says what the state of the data line will be, I always get 0xFF too but you should not count on it so just keep checking for the response token until you get it.

Problems with PIC A/D conversion

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.

Resources